2
0
mirror of https://github.com/KDE/kdeconnect-android synced 2025-08-28 20:57:42 +00:00

Fixed bugs pointed out by Albert on CR

This commit is contained in:
Vineet Garg 2015-06-25 04:20:03 +05:30
parent ed6aef42a6
commit 1b726018d3
15 changed files with 336 additions and 173 deletions

View File

@ -54,9 +54,7 @@ dependencies {
compile 'org.apache.sshd:sshd-core:0.8.0'
compile 'io.netty:netty-all:4.0.23.Final'
compile 'org.bouncycastle:bcpkix-jdk15on:1.52'
androidTestCompile 'org.mockito:mockito-core:1.10.19'
// Because mockito has some problems with dex environment
androidTestCompile 'com.google.dexmaker:dexmaker:1.1'
androidTestCompile 'com.google.dexmaker:dexmaker-mockito:1.1'

View File

@ -25,39 +25,6 @@
android:layout_gravity="left|center_vertical"
/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:visibility="gone"
android:id="@+id/secret_keys"
android:layout_gravity="center"
android:paddingTop="5dp"
android:paddingBottom="5dp">
<TextView
android:id="@+id/my_device_key"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/pairing_accept"
android:layout_weight="1"
/>
<TextView
android:id="@+id/remote_device_key"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/pairing_reject"
android:layout_weight="1" />
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/security_message"
android:layout_weight="1"/>
</LinearLayout>
<Button
android:id="@+id/pair_button"
android:layout_width="match_parent"

View File

@ -61,9 +61,10 @@
<string name="error_canceled_by_user">Canceled by user</string>
<string name="error_canceled_by_other_peer">Canceled by other peer</string>
<string name="error_invalid_key">Invalid key received</string>
<string name="encryption_info_title">Encryption Info</string>
<string name="encryption_info_msg_no_ssl">The other device doesn\'t use a recent version of KDE Connect, using the legacy encryption method.</string>
<string name="my_device_key">My device key : </string>
<string name="remote_device_key">Remote device key : </string>
<string name="security_message">Be sure to match these keys before pairing for security purposes</string>
<string name="pair_requested">Pair requested</string>
<string name="pairing_request_from">Pairing request from %1s</string>
<string name="received_url_title">Received link from %1s</string>

View File

@ -30,6 +30,7 @@ import java.util.ArrayList;
public abstract class BaseLinkProvider {
private final ArrayList<ConnectionReceiver> connectionReceivers = new ArrayList<ConnectionReceiver>();
public BasePairingHandler pairingHandler;
public interface ConnectionReceiver {
public void onConnectionReceived(NetworkPackage identityPackage, BaseLink link);

View File

@ -0,0 +1,35 @@
/*
* Copyright 2015 Vineet Garg <grg.vineet@gmail.com>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of
* the License or (at your option) version 3 or any later version
* accepted by the membership of KDE e.V. (or its successor approved
* by the membership of KDE e.V.), which shall act as a proxy
* defined in Section 14 of version 3 of the license.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.kde.kdeconnect.Backends;
import org.kde.kdeconnect.Device;
import org.kde.kdeconnect.NetworkPackage;
public abstract class BasePairingHandler {
public void packageReceived(Device device, NetworkPackage np) throws Exception{}
public void requestPairing(Device device, NetworkPackage np) {}
public void accept_pairing(Device device, NetworkPackage np) {}
public void rejectPairing(Device device, NetworkPackage np) {}
public void pairingDone(Device device) {}
public void unpair(Device device, NetworkPackage np) {}
}

View File

@ -45,6 +45,7 @@ import javax.net.ssl.SSLServerSocketFactory;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
public class LanLink extends BaseLink {

View File

@ -27,15 +27,16 @@ import android.util.Base64;
import android.util.Log;
import org.kde.kdeconnect.Backends.BaseLinkProvider;
import org.kde.kdeconnect.BackgroundService;
import org.kde.kdeconnect.Device;
import org.kde.kdeconnect.Helpers.SecurityHelpers.SslHelper;
import org.kde.kdeconnect.NetworkPackage;
import org.kde.kdeconnect.UserInterface.CustomDevicesActivity;
import org.kde.kdeconnect.UserInterface.MainSettingsActivity;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.SocketException;
import java.security.cert.Certificate;
import java.util.ArrayList;
import java.util.HashMap;
@ -47,6 +48,7 @@ import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
@ -55,6 +57,8 @@ import io.netty.channel.EventLoopGroup;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.DatagramPacket;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.SocketChannelConfig;
import io.netty.channel.socket.nio.NioDatagramChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
@ -80,17 +84,22 @@ public class LanLinkProvider extends BaseLinkProvider {
private final LongSparseArray<Channel> nioChannels = new LongSparseArray<Channel>();
private EventLoopGroup bossGroup, workerGroup, udpGroup, clientGroup;
private TcpHandler tcpHandler = new TcpHandler();
private UdpHandler udpHandler = new UdpHandler();
private class TcpHandler extends SimpleChannelInboundHandler{
@ChannelHandler.Sharable
private class TcpHandler extends SimpleChannelInboundHandler<String>{
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
// If certificate changed, getting SocketException, connection reset by peer
// Ssl engines closes by itself, so do channel and link is removed
// Close channel for any sudden exception
ctx.fireChannelInactive();
}
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
// Not called if remote device closes session unexpectedly, like wifi off
Log.e("KDE/LanLinkProvider", "Channel inactive");
try {
long id = ctx.channel().hashCode();
final LanLink brokenLink = nioLinks.get(id);
@ -117,7 +126,7 @@ public class LanLinkProvider extends BaseLinkProvider {
public void run() {
//Wait a bit before emitting connectionLost, in case the same device re-appears
try {
Thread.sleep(200);
Thread.sleep(500);
} catch (InterruptedException e) {
}
connectionLost(brokenLink);
@ -132,19 +141,17 @@ public class LanLinkProvider extends BaseLinkProvider {
}
}
@Override
public void channelRead0(ChannelHandlerContext ctx, Object message) throws Exception {
public void channelRead0(ChannelHandlerContext ctx, String message) throws Exception {
// Log.e("LanLinkProvider","Incoming package, address: " + ctx.channel().remoteAddress());
// Log.e("LanLinkProvider","Received:"+message);
String theMessage = (String) message;
if (theMessage.isEmpty()) {
Log.e("KDE/LanLinkProvider","Empty package received");
if (message.isEmpty()) {
Log.e("KDE/LanLinkProvider", "Empty package received");
return;
}
final NetworkPackage np = NetworkPackage.unserialize(theMessage);
final NetworkPackage np = NetworkPackage.unserialize(message);
if (np.getType().equals(NetworkPackage.PACKAGE_TYPE_IDENTITY)) {
@ -159,9 +166,9 @@ public class LanLinkProvider extends BaseLinkProvider {
nioLinks.put(ctx.channel().hashCode(), link);
//Log.i("KDE/LanLinkProvider","nioLinks.size(): " + nioLinks.size());
// Check if ssl supported on other device and enabled on my device, and add ssl handler
// Add ssl handler if device uses new protocol
try {
if (myIdentityPackage.getBoolean("sslSupported") && np.getBoolean("sslSupported", false)) {
if (NetworkPackage.ProtocolVersion <= np.getInt("protocolVersion")) {
Log.e("KDE/LanLinkProvider", "Remote device " + np.getString("deviceName") + " supports ssl");
final SSLEngine sslEngine = SslHelper.getSslEngine(context, np.getString("deviceId"), SslHelper.SslMode.Client);
SslHandler sslHandler = new SslHandler(sslEngine);
@ -174,8 +181,22 @@ public class LanLinkProvider extends BaseLinkProvider {
Certificate certificate = sslEngine.getSession().getPeerCertificates()[0];
np.set("certificate", Base64.encodeToString(certificate.getEncoded(), 0));
link.setOnSsl(true);
addLink(np, link);
Log.i("KDE/LanLinkProvider","Session with " + np.getString("deviceName") + " secured with " + sslEngine.getSession().getCipherSuite());
} else {
// Unpair if handshake failed
Log.e("KDE/LanLinkProvider", "Handshake failed with " + np.getString("deviceName"));
future.cause().printStackTrace();
BackgroundService.RunCommand(context, new BackgroundService.InstanceCallback() {
@Override
public void onServiceStart(BackgroundService service) {
Device device = service.getDevice(np.getString("deviceId"));
if (device == null) return;
device.unpair();
}
});
}
addLink(np, link);
}
});
} else {
@ -183,7 +204,6 @@ public class LanLinkProvider extends BaseLinkProvider {
}
} catch (Exception e) {
e.printStackTrace();
addLink(np, link); // If exception getting ssl engine
}
} else {
@ -198,6 +218,7 @@ public class LanLinkProvider extends BaseLinkProvider {
}
}
@ChannelHandler.Sharable
private class UdpHandler extends SimpleChannelInboundHandler<DatagramPacket> {
@Override
@ -223,16 +244,7 @@ public class LanLinkProvider extends BaseLinkProvider {
Bootstrap b = new Bootstrap();
b.group(clientGroup);
b.channel(NioSocketChannel.class);
b.handler(new ChannelInitializer<Channel>() {
@Override
protected void initChannel(Channel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast(new DelimiterBasedFrameDecoder(8192, Delimiters.lineDelimiter()));
pipeline.addLast(new StringDecoder());
pipeline.addLast(new StringEncoder());
pipeline.addLast(new TcpHandler());
}
});
b.handler(new TcpInitializer());
int tcpPort = identityPackage.getInt("tcpPort", port);
final ChannelFuture channelFuture = b.connect(packet.sender().getAddress(), tcpPort).sync();
channelFuture.addListener(new ChannelFutureListener() {
@ -242,14 +254,13 @@ public class LanLinkProvider extends BaseLinkProvider {
Log.i("KDE/LanLinkProvider", "Connection successful: " + channel.isActive());
// If I and remote device supports ssl, add ssl handler to channel
if (PreferenceManager.getDefaultSharedPreferences(context).getBoolean(MainSettingsActivity.KEY_USE_SSL_PREFERENCE, true)
&& identityPackage.getBoolean("sslSupported", false)) {
// Add ssl handler if device supports new protocol
if (NetworkPackage.ProtocolVersion <= identityPackage.getInt("protocolVersion")) {
// add ssl handler with start tls true
SSLEngine sslEngine = SslHelper.getSslEngine(context, identityPackage.getString("deviceId"), SslHelper.SslMode.Server);
SslHandler sslHandler = new SslHandler(sslEngine, true);
channel.pipeline().addFirst(sslHandler);
Log.e("KDE/LanLinkProvider", "Remote device supports ssl, ssl handler added");
Log.i("KDE/LanLinkProvider", "Remote device supports ssl, ssl handler added");
}
final LanLink link = new LanLink(context, channel, identityPackage.getString("deviceId"), LanLinkProvider.this);
@ -277,11 +288,25 @@ public class LanLinkProvider extends BaseLinkProvider {
Certificate certificate = sslHandler.engine().getSession().getPeerCertificates()[0];
identityPackage.set("certificate", Base64.encodeToString(certificate.getEncoded(), 0));
link.setOnSsl(true);
addLink(identityPackage, link);
} catch (Exception e){
e.printStackTrace();
}
} else {
// Unpair if handshake failed
// Any exception or handshake exception ?
Log.e("KDE/LanLinkProvider", "Handshake failed with " + identityPackage.getString("deviceName"));
future.cause().printStackTrace();
BackgroundService.RunCommand(context, new BackgroundService.InstanceCallback() {
@Override
public void onServiceStart(BackgroundService service) {
Device device = service.getDevice(identityPackage.getString("deviceId"));
if (device == null) return;
device.unpair();
}
});
}
addLink(identityPackage, link);
}
});
} else {
@ -312,6 +337,19 @@ public class LanLinkProvider extends BaseLinkProvider {
}
public class TcpInitializer extends ChannelInitializer<SocketChannel> {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
ch.config().setAllowHalfClosure(false); // Not sure how it will work, but we certainly don't want half closure
ch.config().setKeepAlive(true);
pipeline.addLast(new DelimiterBasedFrameDecoder(512 * 1024, Delimiters.lineDelimiter()));
pipeline.addLast(new StringDecoder());
pipeline.addLast(new StringEncoder());
pipeline.addLast(tcpHandler);
}
}
private void addLink(NetworkPackage identityPackage, LanLink link) {
String deviceId = identityPackage.getString("deviceId");
Log.i("KDE/LanLinkProvider","addLink to "+deviceId);
@ -341,18 +379,8 @@ public class LanLinkProvider extends BaseLinkProvider {
tcpBootstrap.channel(NioServerSocketChannel.class);
tcpBootstrap.option(ChannelOption.SO_BACKLOG, 100);
tcpBootstrap.handler(new LoggingHandler(LogLevel.INFO));
tcpBootstrap.childOption(ChannelOption.SO_KEEPALIVE, true);
tcpBootstrap.childOption(ChannelOption.SO_REUSEADDR, true);
tcpBootstrap.childHandler(new ChannelInitializer<Channel>() {
@Override
protected void initChannel(Channel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast(new DelimiterBasedFrameDecoder(8192, Delimiters.lineDelimiter()));
pipeline.addLast(new StringDecoder());
pipeline.addLast(new StringEncoder());
pipeline.addLast(new TcpHandler());
}
});
tcpBootstrap.option(ChannelOption.SO_REUSEADDR, true);
tcpBootstrap.childHandler(new TcpInitializer());
tcpBootstrap.bind(new InetSocketAddress(port)).sync();
}catch (Exception e) {
e.printStackTrace();
@ -368,10 +396,10 @@ public class LanLinkProvider extends BaseLinkProvider {
@Override
protected void initChannel(Channel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast(new DelimiterBasedFrameDecoder(8192, Delimiters.lineDelimiter()));
pipeline.addLast(new DelimiterBasedFrameDecoder(512 * 1024, Delimiters.lineDelimiter()));
pipeline.addLast(new StringDecoder());
pipeline.addLast(new StringEncoder());
pipeline.addLast(new UdpHandler());
pipeline.addLast(udpHandler);
}
});
udpBootstrap.bind(new InetSocketAddress(port)).sync();
@ -381,6 +409,8 @@ public class LanLinkProvider extends BaseLinkProvider {
clientGroup = new NioEventLoopGroup();
pairingHandler = new LanPairingHandler();
}
@Override
@ -388,6 +418,7 @@ public class LanLinkProvider extends BaseLinkProvider {
Log.e("KDE/LanLinkProvider", "onStart");
new Thread(new Runnable() {
@Override
public void run() {
@ -437,7 +468,7 @@ public class LanLinkProvider extends BaseLinkProvider {
@Override
public void onNetworkChange() {
Log.e("KDE/LanLinkProvider","onNetworkChange");
Log.e("KDE/LanLinkProvider", "onNetworkChange");
//FilesHelper.LogOpenFileCount();

View File

@ -0,0 +1,90 @@
/*
* Copyright 2015 Vineet Garg <grg.vineet@gmail.com>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of
* the License or (at your option) version 3 or any later version
* accepted by the membership of KDE e.V. (or its successor approved
* by the membership of KDE e.V.), which shall act as a proxy
* defined in Section 14 of version 3 of the license.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.kde.kdeconnect.Backends.LanBackend;
import android.content.Context;
import android.content.SharedPreferences;
import android.preference.PreferenceManager;
import android.util.Base64;
import android.util.Log;
import org.kde.kdeconnect.Backends.BasePairingHandler;
import org.kde.kdeconnect.Device;
import org.kde.kdeconnect.NetworkPackage;
import org.kde.kdeconnect_tp.R;
import java.security.KeyFactory;
import java.security.cert.CertificateEncodingException;
import java.security.spec.X509EncodedKeySpec;
public class LanPairingHandler extends BasePairingHandler{
@Override
public void packageReceived(Device device, NetworkPackage np) throws Exception{
try {
String publicKeyContent = np.getString("publicKey").replace("-----BEGIN PUBLIC KEY-----\n","").replace("-----END PUBLIC KEY-----\n", "");
byte[] publicKeyBytes = Base64.decode(publicKeyContent, 0);
device.publicKey = KeyFactory.getInstance("RSA").generatePublic(new X509EncodedKeySpec(publicKeyBytes));
} catch(Exception e) {
e.printStackTrace();
Log.e("KDE/Device","Pairing exception: Received incorrect key");
throw e;
}
}
@Override
public void requestPairing(Device device, NetworkPackage np) {
SharedPreferences globalSettings = PreferenceManager.getDefaultSharedPreferences(device.getContext());
String publicKey = "-----BEGIN PUBLIC KEY-----\n" + globalSettings.getString("publicKey", "").trim()+ "\n-----END PUBLIC KEY-----\n";
np.set("publicKey", publicKey);
}
@Override
public void accept_pairing(Device device, NetworkPackage np) {
SharedPreferences globalSettings = PreferenceManager.getDefaultSharedPreferences(device.getContext());
String publicKey = "-----BEGIN PUBLIC KEY-----\n" + globalSettings.getString("publicKey", "").trim()+ "\n-----END PUBLIC KEY-----\n";
np.set("publicKey", publicKey);
}
@Override
public void pairingDone(Device device) {
//Store device information needed to create a Device object in a future
SharedPreferences.Editor editor = device.getContext().getSharedPreferences(device.getDeviceId(), Context.MODE_PRIVATE).edit();
editor.putString("deviceName", device.getName());
editor.putString("deviceType", device.getDeviceType().toString());
String encodedPublicKey = Base64.encodeToString(device.publicKey.getEncoded(), 0);
editor.putString("publicKey", encodedPublicKey);
try {
String encodedCertificate = Base64.encodeToString(device.certificate.getEncoded(), 0);
editor.putString("certificate", encodedCertificate);
} catch (NullPointerException n) {
Log.e("KDE/PairingDone", "Certificate is null, remote device does not support ssl");
} catch (CertificateEncodingException c) {
Log.e("KDE/PairingDOne", "Error encoding certificate");
} catch (Exception e) {
e.printStackTrace();
Log.e("KDE/Pairng", "Some exception while encoding string");
}
editor.apply();
}
}

View File

@ -31,6 +31,7 @@ public class LoopbackLinkProvider extends BaseLinkProvider {
public LoopbackLinkProvider(Context context) {
this.context = context;
pairingHandler = new LoopbackPairingHandler();
}
@Override

View File

@ -0,0 +1,27 @@
/*
* Copyright 2015 Vineet Garg <grg.vineet@gmail.com>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of
* the License or (at your option) version 3 or any later version
* accepted by the membership of KDE e.V. (or its successor approved
* by the membership of KDE e.V.), which shall act as a proxy
* defined in Section 14 of version 3 of the license.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.kde.kdeconnect.Backends.LoopbackBackend;
import org.kde.kdeconnect.Backends.LanBackend.LanPairingHandler;
public class LoopbackPairingHandler extends LanPairingHandler{
// Extending from LanPairingHandler, as it is similar to it
}

View File

@ -28,7 +28,6 @@ import android.content.SharedPreferences;
import android.os.Binder;
import android.os.IBinder;
import android.preference.PreferenceManager;
import android.util.Base64;
import android.util.Log;
import org.kde.kdeconnect.Backends.BaseLink;
@ -38,8 +37,6 @@ import org.kde.kdeconnect.Helpers.SecurityHelpers.RsaHelper;
import org.kde.kdeconnect.Helpers.SecurityHelpers.SslHelper;
import org.kde.kdeconnect.UserInterface.MainSettingsActivity;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Set;
@ -99,6 +96,10 @@ public class BackgroundService extends Service {
}
public ArrayList<BaseLinkProvider> getLinkProviders() {
return linkProviders;
}
public Device getDevice(String id) {
return devices.get(id);
}

View File

@ -39,6 +39,7 @@ import org.bouncycastle.cert.X509CertificateHolder;
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.kde.kdeconnect.Backends.BaseLink;
import org.kde.kdeconnect.Backends.BaseLinkProvider;
import org.kde.kdeconnect.Plugins.Plugin;
import org.kde.kdeconnect.Plugins.PluginFactory;
import org.kde.kdeconnect.UserInterface.PairActivity;
@ -175,6 +176,10 @@ public class Device implements BaseLink.PackageReceiver {
return deviceId;
}
public Context getContext() {
return context;
}
//Returns 0 if the version matches, < 0 if it is older or > 0 if it is newer
public int compareProtocolVersion() {
return protocolVersion - NetworkPackage.ProtocolVersion;
@ -236,9 +241,14 @@ public class Device implements BaseLink.PackageReceiver {
return;
}
//Send our own public key
NetworkPackage np = NetworkPackage.createPublicKeyPackage(context);
sendPackage(np, new SendPackageStatusCallback(){
// Each link can set whatever they want in pair package
NetworkPackage pairPackage = new NetworkPackage(NetworkPackage.PACKAGE_TYPE_PAIR);
for (BaseLinkProvider linkProvider : ((BackgroundService)context).getLinkProviders()) {
linkProvider.pairingHandler.requestPairing(this, pairPackage);
}
pairPackage.set("pair", true);
sendPackage(pairPackage, new SendPackageStatusCallback() {
@Override
public void onSuccess() {
if (pairingTimer != null) pairingTimer.cancel();
@ -281,7 +291,13 @@ public class Device implements BaseLink.PackageReceiver {
SharedPreferences preferences = context.getSharedPreferences("trusted_devices", Context.MODE_PRIVATE);
preferences.edit().remove(deviceId).apply();
SharedPreferences devicePreferences = context.getSharedPreferences(deviceId, Context.MODE_PRIVATE);
devicePreferences.edit().clear().apply();
NetworkPackage np = new NetworkPackage(NetworkPackage.PACKAGE_TYPE_PAIR);
for (BaseLinkProvider linkProvider : ((BackgroundService)context).getLinkProviders()) {
linkProvider.pairingHandler.unpair(this, np);
}
np.set("pair", false);
sendPackage(np);
@ -303,20 +319,8 @@ public class Device implements BaseLink.PackageReceiver {
SharedPreferences preferences = context.getSharedPreferences("trusted_devices", Context.MODE_PRIVATE);
preferences.edit().putBoolean(deviceId,true).apply();
//Store device information needed to create a Device object in a future
SharedPreferences.Editor editor = settings.edit();
try {
editor.putString("deviceName", getName());
editor.putString("deviceType", deviceType.toString());
String encodedPublicKey = Base64.encodeToString(publicKey.getEncoded(), 0);
editor.putString("publicKey", encodedPublicKey);
String encodedCertificate = Base64.encodeToString(certificate.getEncoded(), 0);
editor.putString("certificate", encodedCertificate);
} catch (Exception e){
Log.e("KDE/Device", "Exception pairing done");
e.printStackTrace();
} finally {
editor.apply();
for (BaseLinkProvider linkProvider : ((BackgroundService)context).getLinkProviders()) {
linkProvider.pairingHandler.pairingDone(this);
}
reloadPluginsFromSettings();
@ -329,10 +333,16 @@ public class Device implements BaseLink.PackageReceiver {
public void acceptPairing() {
Log.i("KDE/Device","Accepted pair request started by the other device");
Log.i("KDE/Device", "Accepted pair request started by the other device");
final NetworkPackage np = new NetworkPackage(NetworkPackage.PACKAGE_TYPE_PAIR);
for (BaseLinkProvider linkProvider : ((BackgroundService)context).getLinkProviders()) {
linkProvider.pairingHandler.accept_pairing(this, np);
}
np.set("pair", true);
//Send our own public key
NetworkPackage np = NetworkPackage.createPublicKeyPackage(context);
// NetworkPackage np = NetworkPackage.createPublicKeyPackage(context);
sendPackage(np, new SendPackageStatusCallback() {
@Override
protected void onSuccess() {
@ -358,6 +368,9 @@ public class Device implements BaseLink.PackageReceiver {
pairStatus = PairStatus.NotPaired;
NetworkPackage np = new NetworkPackage(NetworkPackage.PACKAGE_TYPE_PAIR);
for (BaseLinkProvider linkProvider : ((BackgroundService)context).getLinkProviders()) {
linkProvider.pairingHandler.rejectPairing(this, np);
}
np.set("pair", false);
sendPackage(np);
@ -445,7 +458,7 @@ public class Device implements BaseLink.PackageReceiver {
link.removePackageReceiver(this);
links.remove(link);
Log.i("KDE/Device","removeLink: "+link.getLinkProvider().getName() + " -> "+getName() + " active links: "+ links.size());
Log.i("KDE/Device", "removeLink: " + link.getLinkProvider().getName() + " -> " + getName() + " active links: " + links.size());
if (links.isEmpty()) {
reloadPluginsFromSettings();
}
@ -456,7 +469,7 @@ public class Device implements BaseLink.PackageReceiver {
if (np.getType().equals(NetworkPackage.PACKAGE_TYPE_PAIR)) {
Log.i("KDE/Device","Pair package");
Log.i("KDE/Device", "Pair package");
boolean wantsPair = np.getBoolean("pair");
@ -468,24 +481,27 @@ public class Device implements BaseLink.PackageReceiver {
for (PairingCallback cb : pairingCallback) {
cb.pairingFailed(context.getString(R.string.error_canceled_by_other_peer));
}
return;
} if (pairStatus == PairStatus.Paired) {
// Simple unpair without sending pair package
pairStatus = PairStatus.NotPaired;
for (PairingCallback cb : pairingCallback) cb.unpaired();
reloadPluginsFromSettings();
}
return;
}
if (wantsPair) {
//Retrieve their public key
try {
String publicKeyContent = np.getString("publicKey").replace("-----BEGIN PUBLIC KEY-----\n","").replace("-----END PUBLIC KEY-----\n", "");
byte[] publicKeyBytes = Base64.decode(publicKeyContent, 0);
publicKey = KeyFactory.getInstance("RSA").generatePublic(new X509EncodedKeySpec(publicKeyBytes));
} catch(Exception e) {
e.printStackTrace();
Log.e("KDE/Device","Pairing exception: Received incorrect key");
for (PairingCallback cb : pairingCallback) {
cb.pairingFailed(context.getString(R.string.error_invalid_key));
for (BaseLinkProvider linkProvider : ((BackgroundService)context).getLinkProviders()) {
try {
linkProvider.pairingHandler.packageReceived(this, np);
} catch (Exception e) {
for (Device.PairingCallback cb : pairingCallback) {
cb.pairingFailed(context.getString(R.string.error_invalid_key));
}
return;
}
return;
}
if (pairStatus == PairStatus.Requested) { //We started pairing

View File

@ -38,11 +38,13 @@ import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
import java.math.BigInteger;
import java.security.KeyStore;
import java.security.MessageDigest;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.cert.X509Certificate;
import java.util.Date;
import java.util.Formatter;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
@ -209,4 +211,20 @@ public class SslHelper {
}
return null;
}
public static String getCertificateHash(X509Certificate certificate) {
try {
byte[] hash = MessageDigest.getInstance("SHA-1").digest(certificate.getEncoded());
Formatter formatter = new Formatter();
// Using first 4 bytes out of 20, is this secure ?
for (int i = 0; i < 4; i++) {
formatter.format("%02x", hash[i]);
}
return formatter.toString().toUpperCase();
} catch (Exception e) {
return null;
}
}
}

View File

@ -22,6 +22,7 @@ package org.kde.kdeconnect;
import android.content.Context;
import android.content.SharedPreferences;
import android.os.Build;
import android.preference.PreferenceManager;
import android.provider.Settings;
import android.util.Base64;
@ -45,7 +46,7 @@ import javax.crypto.Cipher;
public class NetworkPackage {
public final static int ProtocolVersion = 5;
public final static int ProtocolVersion = 6;
//TODO: Move these to their respective plugins
public final static String PACKAGE_TYPE_IDENTITY = "kdeconnect.identity";
@ -206,7 +207,6 @@ public class NetworkPackage {
DeviceHelper.getDeviceName()));
np.mBody.put("protocolVersion", NetworkPackage.ProtocolVersion);
np.mBody.put("deviceType", DeviceHelper.isTablet()? "tablet" : "phone");
np.mBody.put("sslSupported", PreferenceManager.getDefaultSharedPreferences(context).getBoolean(MainSettingsActivity.KEY_USE_SSL_PREFERENCE, true));
} catch (Exception e) {
e.printStackTrace();
Log.e("NetworkPacakge","Exception on createIdentityPackage");
@ -216,21 +216,6 @@ public class NetworkPackage {
}
static public NetworkPackage createPublicKeyPackage(Context context) {
NetworkPackage np = new NetworkPackage(NetworkPackage.PACKAGE_TYPE_PAIR);
np.set("pair", true);
SharedPreferences globalSettings = PreferenceManager.getDefaultSharedPreferences(context);
String publicKey = "-----BEGIN PUBLIC KEY-----\n" + globalSettings.getString("publicKey", "").trim()+ "\n-----END PUBLIC KEY-----\n";
np.set("publicKey", publicKey);
return np;
}
public void setPayload(byte[] data) {
setPayload(new ByteArrayInputStream(data), data.length);
}

View File

@ -20,12 +20,15 @@
package org.kde.kdeconnect.UserInterface;
import android.app.AlertDialog;
import android.app.NotificationManager;
import android.content.Context;
import android.content.DialogInterface;
import android.os.Bundle;
import android.support.v7.app.ActionBar;
import android.support.v7.app.ActionBarActivity;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
@ -35,9 +38,6 @@ import org.kde.kdeconnect.Device;
import org.kde.kdeconnect.Helpers.SecurityHelpers.SslHelper;
import org.kde.kdeconnect_tp.R;
import java.security.MessageDigest;
import java.util.Formatter;
public class PairActivity extends ActionBarActivity {
private String deviceId;
@ -53,7 +53,6 @@ public class PairActivity extends ActionBarActivity {
((TextView) findViewById(R.id.pair_message)).setText(R.string.pair_requested);
findViewById(R.id.pair_progress).setVisibility(View.GONE);
findViewById(R.id.pair_button).setVisibility(View.GONE);
findViewById(R.id.secret_keys).setVisibility(View.VISIBLE);
findViewById(R.id.pair_request).setVisibility(View.VISIBLE);
}
});
@ -73,7 +72,6 @@ public class PairActivity extends ActionBarActivity {
public void run() {
((TextView) findViewById(R.id.pair_message)).setText(error);
findViewById(R.id.pair_progress).setVisibility(View.GONE);
findViewById(R.id.secret_keys).setVisibility(View.VISIBLE);
findViewById(R.id.pair_button).setVisibility(View.VISIBLE);
findViewById(R.id.pair_request).setVisibility(View.GONE);
}
@ -111,32 +109,6 @@ public class PairActivity extends ActionBarActivity {
});
// Show secret keys based on certificate if device are connected using ssl
BackgroundService.RunCommand(PairActivity.this, new BackgroundService.InstanceCallback() {
@Override
public void onServiceStart(BackgroundService service) {
device = service.getDevice(deviceId);
if (device == null) {
Log.e("KDE/PairActivity", "Device is null");
return;
}
if (device.certificate == null) {
Log.e("KDE/PairActivity", "Device certificate is null");
return;
}
try {
((TextView) findViewById(R.id.remote_device_key)).setText(getApplicationContext().getResources().getString(R.string.remote_device_key) + byteArray2Hex(MessageDigest.getInstance("SHA-1").digest(device.certificate.getEncoded())).toUpperCase());
((TextView) findViewById(R.id.my_device_key)).setText(getApplicationContext().getResources().getString(R.string.my_device_key) + byteArray2Hex(MessageDigest.getInstance("SHA-1").digest(SslHelper.certificate.getEncoded())).toUpperCase());
findViewById(R.id.secret_keys).setVisibility(View.VISIBLE);
} catch (Exception e) {
e.printStackTrace();
}
}
});
final Button pairButton = (Button)findViewById(R.id.pair_button);
pairButton.setOnClickListener(new View.OnClickListener() {
@Override
@ -206,13 +178,32 @@ public class PairActivity extends ActionBarActivity {
super.onStop();
}
private static String byteArray2Hex(final byte[] hash) {
Formatter formatter = new Formatter();
// Using first 4 bytes out of 20, is this secure ?
for (int i=0 ; i<4 ; i++) {
formatter.format("%02x", hash[i]);
}
return formatter.toString();
}
@Override
public boolean onPrepareOptionsMenu(Menu menu) {
super.onPrepareOptionsMenu(menu);
menu.clear();
menu.add(R.string.encryption_info_title).setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(MenuItem menuItem) {
Context context = PairActivity.this;
AlertDialog.Builder builder = new AlertDialog.Builder(context);
builder.setTitle(context.getResources().getString(R.string.encryption_info_title));
builder.setPositiveButton(context.getResources().getString(R.string.ok), new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
dialog.dismiss();
}
});
if (device.certificate == null) {
builder.setMessage(R.string.encryption_info_msg_no_ssl);
} else {
builder.setMessage(context.getResources().getString(R.string.my_device_key) + " " + SslHelper.getCertificateHash(device.certificate) + "\n"
+ context.getResources().getString(R.string.remote_device_key) + " " + SslHelper.getCertificateHash(SslHelper.certificate));
}
builder.create().show();
return true;
}
});
return true;
}
}