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:
parent
ed6aef42a6
commit
1b726018d3
@ -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'
|
||||
|
@ -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"
|
||||
|
@ -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>
|
||||
|
@ -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);
|
||||
|
35
src/org/kde/kdeconnect/Backends/BasePairingHandler.java
Normal file
35
src/org/kde/kdeconnect/Backends/BasePairingHandler.java
Normal 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) {}
|
||||
|
||||
}
|
@ -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 {
|
||||
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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();
|
||||
|
||||
}
|
||||
}
|
@ -31,6 +31,7 @@ public class LoopbackLinkProvider extends BaseLinkProvider {
|
||||
|
||||
public LoopbackLinkProvider(Context context) {
|
||||
this.context = context;
|
||||
pairingHandler = new LoopbackPairingHandler();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -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
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user