2
0
mirror of https://github.com/KDE/kdeconnect-android synced 2025-08-29 13:17:43 +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 'org.apache.sshd:sshd-core:0.8.0'
compile 'io.netty:netty-all:4.0.23.Final' compile 'io.netty:netty-all:4.0.23.Final'
compile 'org.bouncycastle:bcpkix-jdk15on:1.52' compile 'org.bouncycastle:bcpkix-jdk15on:1.52'
androidTestCompile 'org.mockito:mockito-core:1.10.19' androidTestCompile 'org.mockito:mockito-core:1.10.19'
// Because mockito has some problems with dex environment // Because mockito has some problems with dex environment
androidTestCompile 'com.google.dexmaker:dexmaker:1.1' androidTestCompile 'com.google.dexmaker:dexmaker:1.1'
androidTestCompile 'com.google.dexmaker:dexmaker-mockito:1.1' androidTestCompile 'com.google.dexmaker:dexmaker-mockito:1.1'

View File

@ -25,39 +25,6 @@
android:layout_gravity="left|center_vertical" 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 <Button
android:id="@+id/pair_button" android:id="@+id/pair_button"
android:layout_width="match_parent" 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_user">Canceled by user</string>
<string name="error_canceled_by_other_peer">Canceled by other peer</string> <string name="error_canceled_by_other_peer">Canceled by other peer</string>
<string name="error_invalid_key">Invalid key received</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="my_device_key">My device key : </string>
<string name="remote_device_key">Remote 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="pair_requested">Pair requested</string>
<string name="pairing_request_from">Pairing request from %1s</string> <string name="pairing_request_from">Pairing request from %1s</string>
<string name="received_url_title">Received link 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 { public abstract class BaseLinkProvider {
private final ArrayList<ConnectionReceiver> connectionReceivers = new ArrayList<ConnectionReceiver>(); private final ArrayList<ConnectionReceiver> connectionReceivers = new ArrayList<ConnectionReceiver>();
public BasePairingHandler pairingHandler;
public interface ConnectionReceiver { public interface ConnectionReceiver {
public void onConnectionReceived(NetworkPackage identityPackage, BaseLink link); 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.Channel;
import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
public class LanLink extends BaseLink { public class LanLink extends BaseLink {

View File

@ -27,15 +27,16 @@ import android.util.Base64;
import android.util.Log; import android.util.Log;
import org.kde.kdeconnect.Backends.BaseLinkProvider; import org.kde.kdeconnect.Backends.BaseLinkProvider;
import org.kde.kdeconnect.BackgroundService;
import org.kde.kdeconnect.Device; import org.kde.kdeconnect.Device;
import org.kde.kdeconnect.Helpers.SecurityHelpers.SslHelper; import org.kde.kdeconnect.Helpers.SecurityHelpers.SslHelper;
import org.kde.kdeconnect.NetworkPackage; import org.kde.kdeconnect.NetworkPackage;
import org.kde.kdeconnect.UserInterface.CustomDevicesActivity; import org.kde.kdeconnect.UserInterface.CustomDevicesActivity;
import org.kde.kdeconnect.UserInterface.MainSettingsActivity;
import java.net.DatagramSocket; import java.net.DatagramSocket;
import java.net.InetAddress; import java.net.InetAddress;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.net.SocketException;
import java.security.cert.Certificate; import java.security.cert.Certificate;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
@ -47,6 +48,7 @@ import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.Channel; import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener; import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption; import io.netty.channel.ChannelOption;
@ -55,6 +57,8 @@ import io.netty.channel.EventLoopGroup;
import io.netty.channel.SimpleChannelInboundHandler; import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.DatagramPacket; 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.NioDatagramChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel; import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel; 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 final LongSparseArray<Channel> nioChannels = new LongSparseArray<Channel>();
private EventLoopGroup bossGroup, workerGroup, udpGroup, clientGroup; 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 @Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace(); cause.printStackTrace();
// If certificate changed, getting SocketException, connection reset by peer // Close channel for any sudden exception
// Ssl engines closes by itself, so do channel and link is removed ctx.fireChannelInactive();
} }
@Override @Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception { 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 { try {
long id = ctx.channel().hashCode(); long id = ctx.channel().hashCode();
final LanLink brokenLink = nioLinks.get(id); final LanLink brokenLink = nioLinks.get(id);
@ -117,7 +126,7 @@ public class LanLinkProvider extends BaseLinkProvider {
public void run() { public void run() {
//Wait a bit before emitting connectionLost, in case the same device re-appears //Wait a bit before emitting connectionLost, in case the same device re-appears
try { try {
Thread.sleep(200); Thread.sleep(500);
} catch (InterruptedException e) { } catch (InterruptedException e) {
} }
connectionLost(brokenLink); connectionLost(brokenLink);
@ -132,19 +141,17 @@ public class LanLinkProvider extends BaseLinkProvider {
} }
} }
@Override @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","Incoming package, address: " + ctx.channel().remoteAddress());
// Log.e("LanLinkProvider","Received:"+message); // Log.e("LanLinkProvider","Received:"+message);
String theMessage = (String) message; if (message.isEmpty()) {
if (theMessage.isEmpty()) { Log.e("KDE/LanLinkProvider", "Empty package received");
Log.e("KDE/LanLinkProvider","Empty package received");
return; return;
} }
final NetworkPackage np = NetworkPackage.unserialize(theMessage); final NetworkPackage np = NetworkPackage.unserialize(message);
if (np.getType().equals(NetworkPackage.PACKAGE_TYPE_IDENTITY)) { if (np.getType().equals(NetworkPackage.PACKAGE_TYPE_IDENTITY)) {
@ -159,9 +166,9 @@ public class LanLinkProvider extends BaseLinkProvider {
nioLinks.put(ctx.channel().hashCode(), link); nioLinks.put(ctx.channel().hashCode(), link);
//Log.i("KDE/LanLinkProvider","nioLinks.size(): " + nioLinks.size()); //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 { 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"); Log.e("KDE/LanLinkProvider", "Remote device " + np.getString("deviceName") + " supports ssl");
final SSLEngine sslEngine = SslHelper.getSslEngine(context, np.getString("deviceId"), SslHelper.SslMode.Client); final SSLEngine sslEngine = SslHelper.getSslEngine(context, np.getString("deviceId"), SslHelper.SslMode.Client);
SslHandler sslHandler = new SslHandler(sslEngine); SslHandler sslHandler = new SslHandler(sslEngine);
@ -174,8 +181,22 @@ public class LanLinkProvider extends BaseLinkProvider {
Certificate certificate = sslEngine.getSession().getPeerCertificates()[0]; Certificate certificate = sslEngine.getSession().getPeerCertificates()[0];
np.set("certificate", Base64.encodeToString(certificate.getEncoded(), 0)); np.set("certificate", Base64.encodeToString(certificate.getEncoded(), 0));
link.setOnSsl(true); 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 { } else {
@ -183,7 +204,6 @@ public class LanLinkProvider extends BaseLinkProvider {
} }
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); e.printStackTrace();
addLink(np, link); // If exception getting ssl engine
} }
} else { } else {
@ -198,6 +218,7 @@ public class LanLinkProvider extends BaseLinkProvider {
} }
} }
@ChannelHandler.Sharable
private class UdpHandler extends SimpleChannelInboundHandler<DatagramPacket> { private class UdpHandler extends SimpleChannelInboundHandler<DatagramPacket> {
@Override @Override
@ -223,16 +244,7 @@ public class LanLinkProvider extends BaseLinkProvider {
Bootstrap b = new Bootstrap(); Bootstrap b = new Bootstrap();
b.group(clientGroup); b.group(clientGroup);
b.channel(NioSocketChannel.class); b.channel(NioSocketChannel.class);
b.handler(new ChannelInitializer<Channel>() { b.handler(new TcpInitializer());
@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());
}
});
int tcpPort = identityPackage.getInt("tcpPort", port); int tcpPort = identityPackage.getInt("tcpPort", port);
final ChannelFuture channelFuture = b.connect(packet.sender().getAddress(), tcpPort).sync(); final ChannelFuture channelFuture = b.connect(packet.sender().getAddress(), tcpPort).sync();
channelFuture.addListener(new ChannelFutureListener() { channelFuture.addListener(new ChannelFutureListener() {
@ -242,14 +254,13 @@ public class LanLinkProvider extends BaseLinkProvider {
Log.i("KDE/LanLinkProvider", "Connection successful: " + channel.isActive()); Log.i("KDE/LanLinkProvider", "Connection successful: " + channel.isActive());
// If I and remote device supports ssl, add ssl handler to channel // Add ssl handler if device supports new protocol
if (PreferenceManager.getDefaultSharedPreferences(context).getBoolean(MainSettingsActivity.KEY_USE_SSL_PREFERENCE, true) if (NetworkPackage.ProtocolVersion <= identityPackage.getInt("protocolVersion")) {
&& identityPackage.getBoolean("sslSupported", false)) {
// add ssl handler with start tls true // add ssl handler with start tls true
SSLEngine sslEngine = SslHelper.getSslEngine(context, identityPackage.getString("deviceId"), SslHelper.SslMode.Server); SSLEngine sslEngine = SslHelper.getSslEngine(context, identityPackage.getString("deviceId"), SslHelper.SslMode.Server);
SslHandler sslHandler = new SslHandler(sslEngine, true); SslHandler sslHandler = new SslHandler(sslEngine, true);
channel.pipeline().addFirst(sslHandler); 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); 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]; Certificate certificate = sslHandler.engine().getSession().getPeerCertificates()[0];
identityPackage.set("certificate", Base64.encodeToString(certificate.getEncoded(), 0)); identityPackage.set("certificate", Base64.encodeToString(certificate.getEncoded(), 0));
link.setOnSsl(true); link.setOnSsl(true);
addLink(identityPackage, link);
} catch (Exception e){ } catch (Exception e){
e.printStackTrace(); 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 { } 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) { private void addLink(NetworkPackage identityPackage, LanLink link) {
String deviceId = identityPackage.getString("deviceId"); String deviceId = identityPackage.getString("deviceId");
Log.i("KDE/LanLinkProvider","addLink to "+deviceId); Log.i("KDE/LanLinkProvider","addLink to "+deviceId);
@ -341,18 +379,8 @@ public class LanLinkProvider extends BaseLinkProvider {
tcpBootstrap.channel(NioServerSocketChannel.class); tcpBootstrap.channel(NioServerSocketChannel.class);
tcpBootstrap.option(ChannelOption.SO_BACKLOG, 100); tcpBootstrap.option(ChannelOption.SO_BACKLOG, 100);
tcpBootstrap.handler(new LoggingHandler(LogLevel.INFO)); tcpBootstrap.handler(new LoggingHandler(LogLevel.INFO));
tcpBootstrap.childOption(ChannelOption.SO_KEEPALIVE, true); tcpBootstrap.option(ChannelOption.SO_REUSEADDR, true);
tcpBootstrap.childOption(ChannelOption.SO_REUSEADDR, true); tcpBootstrap.childHandler(new TcpInitializer());
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.bind(new InetSocketAddress(port)).sync(); tcpBootstrap.bind(new InetSocketAddress(port)).sync();
}catch (Exception e) { }catch (Exception e) {
e.printStackTrace(); e.printStackTrace();
@ -368,10 +396,10 @@ public class LanLinkProvider extends BaseLinkProvider {
@Override @Override
protected void initChannel(Channel ch) throws Exception { protected void initChannel(Channel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline(); 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 StringDecoder());
pipeline.addLast(new StringEncoder()); pipeline.addLast(new StringEncoder());
pipeline.addLast(new UdpHandler()); pipeline.addLast(udpHandler);
} }
}); });
udpBootstrap.bind(new InetSocketAddress(port)).sync(); udpBootstrap.bind(new InetSocketAddress(port)).sync();
@ -381,6 +409,8 @@ public class LanLinkProvider extends BaseLinkProvider {
clientGroup = new NioEventLoopGroup(); clientGroup = new NioEventLoopGroup();
pairingHandler = new LanPairingHandler();
} }
@Override @Override
@ -388,6 +418,7 @@ public class LanLinkProvider extends BaseLinkProvider {
Log.e("KDE/LanLinkProvider", "onStart"); Log.e("KDE/LanLinkProvider", "onStart");
new Thread(new Runnable() { new Thread(new Runnable() {
@Override @Override
public void run() { public void run() {
@ -437,7 +468,7 @@ public class LanLinkProvider extends BaseLinkProvider {
@Override @Override
public void onNetworkChange() { public void onNetworkChange() {
Log.e("KDE/LanLinkProvider","onNetworkChange"); Log.e("KDE/LanLinkProvider", "onNetworkChange");
//FilesHelper.LogOpenFileCount(); //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) { public LoopbackLinkProvider(Context context) {
this.context = context; this.context = context;
pairingHandler = new LoopbackPairingHandler();
} }
@Override @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.Binder;
import android.os.IBinder; import android.os.IBinder;
import android.preference.PreferenceManager; import android.preference.PreferenceManager;
import android.util.Base64;
import android.util.Log; import android.util.Log;
import org.kde.kdeconnect.Backends.BaseLink; 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.Helpers.SecurityHelpers.SslHelper;
import org.kde.kdeconnect.UserInterface.MainSettingsActivity; import org.kde.kdeconnect.UserInterface.MainSettingsActivity;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.Set; import java.util.Set;
@ -99,6 +96,10 @@ public class BackgroundService extends Service {
} }
public ArrayList<BaseLinkProvider> getLinkProviders() {
return linkProviders;
}
public Device getDevice(String id) { public Device getDevice(String id) {
return devices.get(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.cert.jcajce.JcaX509CertificateConverter;
import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.kde.kdeconnect.Backends.BaseLink; import org.kde.kdeconnect.Backends.BaseLink;
import org.kde.kdeconnect.Backends.BaseLinkProvider;
import org.kde.kdeconnect.Plugins.Plugin; import org.kde.kdeconnect.Plugins.Plugin;
import org.kde.kdeconnect.Plugins.PluginFactory; import org.kde.kdeconnect.Plugins.PluginFactory;
import org.kde.kdeconnect.UserInterface.PairActivity; import org.kde.kdeconnect.UserInterface.PairActivity;
@ -175,6 +176,10 @@ public class Device implements BaseLink.PackageReceiver {
return deviceId; return deviceId;
} }
public Context getContext() {
return context;
}
//Returns 0 if the version matches, < 0 if it is older or > 0 if it is newer //Returns 0 if the version matches, < 0 if it is older or > 0 if it is newer
public int compareProtocolVersion() { public int compareProtocolVersion() {
return protocolVersion - NetworkPackage.ProtocolVersion; return protocolVersion - NetworkPackage.ProtocolVersion;
@ -236,9 +241,14 @@ public class Device implements BaseLink.PackageReceiver {
return; return;
} }
//Send our own public key // Each link can set whatever they want in pair package
NetworkPackage np = NetworkPackage.createPublicKeyPackage(context); NetworkPackage pairPackage = new NetworkPackage(NetworkPackage.PACKAGE_TYPE_PAIR);
sendPackage(np, new SendPackageStatusCallback(){ for (BaseLinkProvider linkProvider : ((BackgroundService)context).getLinkProviders()) {
linkProvider.pairingHandler.requestPairing(this, pairPackage);
}
pairPackage.set("pair", true);
sendPackage(pairPackage, new SendPackageStatusCallback() {
@Override @Override
public void onSuccess() { public void onSuccess() {
if (pairingTimer != null) pairingTimer.cancel(); if (pairingTimer != null) pairingTimer.cancel();
@ -281,7 +291,13 @@ public class Device implements BaseLink.PackageReceiver {
SharedPreferences preferences = context.getSharedPreferences("trusted_devices", Context.MODE_PRIVATE); SharedPreferences preferences = context.getSharedPreferences("trusted_devices", Context.MODE_PRIVATE);
preferences.edit().remove(deviceId).apply(); 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); NetworkPackage np = new NetworkPackage(NetworkPackage.PACKAGE_TYPE_PAIR);
for (BaseLinkProvider linkProvider : ((BackgroundService)context).getLinkProviders()) {
linkProvider.pairingHandler.unpair(this, np);
}
np.set("pair", false); np.set("pair", false);
sendPackage(np); sendPackage(np);
@ -303,20 +319,8 @@ public class Device implements BaseLink.PackageReceiver {
SharedPreferences preferences = context.getSharedPreferences("trusted_devices", Context.MODE_PRIVATE); SharedPreferences preferences = context.getSharedPreferences("trusted_devices", Context.MODE_PRIVATE);
preferences.edit().putBoolean(deviceId,true).apply(); preferences.edit().putBoolean(deviceId,true).apply();
//Store device information needed to create a Device object in a future for (BaseLinkProvider linkProvider : ((BackgroundService)context).getLinkProviders()) {
SharedPreferences.Editor editor = settings.edit(); linkProvider.pairingHandler.pairingDone(this);
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();
} }
reloadPluginsFromSettings(); reloadPluginsFromSettings();
@ -329,10 +333,16 @@ public class Device implements BaseLink.PackageReceiver {
public void acceptPairing() { 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 //Send our own public key
NetworkPackage np = NetworkPackage.createPublicKeyPackage(context); // NetworkPackage np = NetworkPackage.createPublicKeyPackage(context);
sendPackage(np, new SendPackageStatusCallback() { sendPackage(np, new SendPackageStatusCallback() {
@Override @Override
protected void onSuccess() { protected void onSuccess() {
@ -358,6 +368,9 @@ public class Device implements BaseLink.PackageReceiver {
pairStatus = PairStatus.NotPaired; pairStatus = PairStatus.NotPaired;
NetworkPackage np = new NetworkPackage(NetworkPackage.PACKAGE_TYPE_PAIR); NetworkPackage np = new NetworkPackage(NetworkPackage.PACKAGE_TYPE_PAIR);
for (BaseLinkProvider linkProvider : ((BackgroundService)context).getLinkProviders()) {
linkProvider.pairingHandler.rejectPairing(this, np);
}
np.set("pair", false); np.set("pair", false);
sendPackage(np); sendPackage(np);
@ -445,7 +458,7 @@ public class Device implements BaseLink.PackageReceiver {
link.removePackageReceiver(this); link.removePackageReceiver(this);
links.remove(link); 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()) { if (links.isEmpty()) {
reloadPluginsFromSettings(); reloadPluginsFromSettings();
} }
@ -456,7 +469,7 @@ public class Device implements BaseLink.PackageReceiver {
if (np.getType().equals(NetworkPackage.PACKAGE_TYPE_PAIR)) { 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"); boolean wantsPair = np.getBoolean("pair");
@ -468,24 +481,27 @@ public class Device implements BaseLink.PackageReceiver {
for (PairingCallback cb : pairingCallback) { for (PairingCallback cb : pairingCallback) {
cb.pairingFailed(context.getString(R.string.error_canceled_by_other_peer)); 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) { if (wantsPair) {
//Retrieve their public key //Retrieve their public key
try { for (BaseLinkProvider linkProvider : ((BackgroundService)context).getLinkProviders()) {
String publicKeyContent = np.getString("publicKey").replace("-----BEGIN PUBLIC KEY-----\n","").replace("-----END PUBLIC KEY-----\n", ""); try {
byte[] publicKeyBytes = Base64.decode(publicKeyContent, 0); linkProvider.pairingHandler.packageReceived(this, np);
publicKey = KeyFactory.getInstance("RSA").generatePublic(new X509EncodedKeySpec(publicKeyBytes)); } catch (Exception e) {
} catch(Exception e) { for (Device.PairingCallback cb : pairingCallback) {
e.printStackTrace(); cb.pairingFailed(context.getString(R.string.error_invalid_key));
Log.e("KDE/Device","Pairing exception: Received incorrect key"); }
for (PairingCallback cb : pairingCallback) { return;
cb.pairingFailed(context.getString(R.string.error_invalid_key));
} }
return;
} }
if (pairStatus == PairStatus.Requested) { //We started pairing 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.math.BigInteger;
import java.security.KeyStore; import java.security.KeyStore;
import java.security.MessageDigest;
import java.security.PrivateKey; import java.security.PrivateKey;
import java.security.PublicKey; import java.security.PublicKey;
import java.security.SecureRandom; import java.security.SecureRandom;
import java.security.cert.X509Certificate; import java.security.cert.X509Certificate;
import java.util.Date; import java.util.Date;
import java.util.Formatter;
import javax.net.ssl.KeyManagerFactory; import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext; import javax.net.ssl.SSLContext;
@ -209,4 +211,20 @@ public class SslHelper {
} }
return null; 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.Context;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.os.Build;
import android.preference.PreferenceManager; import android.preference.PreferenceManager;
import android.provider.Settings; import android.provider.Settings;
import android.util.Base64; import android.util.Base64;
@ -45,7 +46,7 @@ import javax.crypto.Cipher;
public class NetworkPackage { public class NetworkPackage {
public final static int ProtocolVersion = 5; public final static int ProtocolVersion = 6;
//TODO: Move these to their respective plugins //TODO: Move these to their respective plugins
public final static String PACKAGE_TYPE_IDENTITY = "kdeconnect.identity"; public final static String PACKAGE_TYPE_IDENTITY = "kdeconnect.identity";
@ -206,7 +207,6 @@ public class NetworkPackage {
DeviceHelper.getDeviceName())); DeviceHelper.getDeviceName()));
np.mBody.put("protocolVersion", NetworkPackage.ProtocolVersion); np.mBody.put("protocolVersion", NetworkPackage.ProtocolVersion);
np.mBody.put("deviceType", DeviceHelper.isTablet()? "tablet" : "phone"); 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) { } catch (Exception e) {
e.printStackTrace(); e.printStackTrace();
Log.e("NetworkPacakge","Exception on createIdentityPackage"); 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) { public void setPayload(byte[] data) {
setPayload(new ByteArrayInputStream(data), data.length); setPayload(new ByteArrayInputStream(data), data.length);
} }

View File

@ -20,12 +20,15 @@
package org.kde.kdeconnect.UserInterface; package org.kde.kdeconnect.UserInterface;
import android.app.AlertDialog;
import android.app.NotificationManager; import android.app.NotificationManager;
import android.content.Context; import android.content.Context;
import android.content.DialogInterface;
import android.os.Bundle; import android.os.Bundle;
import android.support.v7.app.ActionBar; import android.support.v7.app.ActionBar;
import android.support.v7.app.ActionBarActivity; import android.support.v7.app.ActionBarActivity;
import android.util.Log; import android.view.Menu;
import android.view.MenuItem;
import android.view.View; import android.view.View;
import android.widget.Button; import android.widget.Button;
import android.widget.TextView; import android.widget.TextView;
@ -35,9 +38,6 @@ import org.kde.kdeconnect.Device;
import org.kde.kdeconnect.Helpers.SecurityHelpers.SslHelper; import org.kde.kdeconnect.Helpers.SecurityHelpers.SslHelper;
import org.kde.kdeconnect_tp.R; import org.kde.kdeconnect_tp.R;
import java.security.MessageDigest;
import java.util.Formatter;
public class PairActivity extends ActionBarActivity { public class PairActivity extends ActionBarActivity {
private String deviceId; private String deviceId;
@ -53,7 +53,6 @@ public class PairActivity extends ActionBarActivity {
((TextView) findViewById(R.id.pair_message)).setText(R.string.pair_requested); ((TextView) findViewById(R.id.pair_message)).setText(R.string.pair_requested);
findViewById(R.id.pair_progress).setVisibility(View.GONE); findViewById(R.id.pair_progress).setVisibility(View.GONE);
findViewById(R.id.pair_button).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); findViewById(R.id.pair_request).setVisibility(View.VISIBLE);
} }
}); });
@ -73,7 +72,6 @@ public class PairActivity extends ActionBarActivity {
public void run() { public void run() {
((TextView) findViewById(R.id.pair_message)).setText(error); ((TextView) findViewById(R.id.pair_message)).setText(error);
findViewById(R.id.pair_progress).setVisibility(View.GONE); 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_button).setVisibility(View.VISIBLE);
findViewById(R.id.pair_request).setVisibility(View.GONE); 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); final Button pairButton = (Button)findViewById(R.id.pair_button);
pairButton.setOnClickListener(new View.OnClickListener() { pairButton.setOnClickListener(new View.OnClickListener() {
@Override @Override
@ -206,13 +178,32 @@ public class PairActivity extends ActionBarActivity {
super.onStop(); super.onStop();
} }
private static String byteArray2Hex(final byte[] hash) { @Override
Formatter formatter = new Formatter(); public boolean onPrepareOptionsMenu(Menu menu) {
// Using first 4 bytes out of 20, is this secure ? super.onPrepareOptionsMenu(menu);
for (int i=0 ; i<4 ; i++) { menu.clear();
formatter.format("%02x", hash[i]); menu.add(R.string.encryption_info_title).setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
} @Override
return formatter.toString(); 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;
}
} }