2015-06-10 12:29:53 +05:30
|
|
|
/*
|
|
|
|
* 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;
|
|
|
|
|
|
|
|
import android.content.Context;
|
|
|
|
import android.content.SharedPreferences;
|
|
|
|
import android.test.AndroidTestCase;
|
|
|
|
import android.util.Base64;
|
|
|
|
import android.util.Log;
|
|
|
|
|
2016-06-12 21:07:01 +02:00
|
|
|
import org.kde.kdeconnect.Backends.BasePairingHandler;
|
2016-06-09 13:42:15 +02:00
|
|
|
import org.kde.kdeconnect.Backends.LanBackend.LanLink;
|
|
|
|
import org.kde.kdeconnect.Backends.LanBackend.LanLinkProvider;
|
2016-06-12 21:07:01 +02:00
|
|
|
import org.kde.kdeconnect.Backends.LanBackend.LanPairingHandler;
|
2016-06-09 13:42:15 +02:00
|
|
|
import org.mockito.Mockito;
|
2016-06-12 21:07:01 +02:00
|
|
|
import org.spongycastle.asn1.x500.X500NameBuilder;
|
|
|
|
import org.spongycastle.asn1.x500.style.BCStyle;
|
|
|
|
import org.spongycastle.cert.X509v3CertificateBuilder;
|
|
|
|
import org.spongycastle.cert.jcajce.JcaX509CertificateConverter;
|
|
|
|
import org.spongycastle.cert.jcajce.JcaX509v3CertificateBuilder;
|
|
|
|
import org.spongycastle.jce.provider.BouncyCastleProvider;
|
|
|
|
import org.spongycastle.operator.ContentSigner;
|
|
|
|
import org.spongycastle.operator.jcajce.JcaContentSignerBuilder;
|
2015-06-10 12:29:53 +05:30
|
|
|
|
|
|
|
import java.lang.reflect.Method;
|
2015-06-21 17:30:29 +05:30
|
|
|
import java.math.BigInteger;
|
2015-06-10 12:29:53 +05:30
|
|
|
import java.security.KeyPair;
|
|
|
|
import java.security.KeyPairGenerator;
|
2015-06-21 17:30:29 +05:30
|
|
|
import java.security.cert.X509Certificate;
|
|
|
|
import java.util.Date;
|
2015-06-10 12:29:53 +05:30
|
|
|
|
|
|
|
public class DeviceTest extends AndroidTestCase {
|
|
|
|
|
|
|
|
// Creating a paired device before each test case
|
|
|
|
@Override
|
|
|
|
protected void setUp() throws Exception {
|
|
|
|
super.setUp();
|
|
|
|
|
|
|
|
// Dexmaker has problems guessing cache directory, setting manually
|
|
|
|
System.setProperty("dexmaker.dexcache", getContext().getCacheDir().getPath());
|
|
|
|
|
|
|
|
// Save new test device in settings
|
|
|
|
Context context = getContext();
|
|
|
|
|
|
|
|
String deviceId = "testDevice";
|
|
|
|
String name = "Test Device";
|
|
|
|
|
|
|
|
KeyPair keyPair;
|
|
|
|
try {
|
|
|
|
KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
|
|
|
|
keyGen.initialize(2048);
|
|
|
|
keyPair = keyGen.genKeyPair();
|
2018-03-03 16:06:52 +01:00
|
|
|
} catch (Exception e) {
|
2015-06-10 12:29:53 +05:30
|
|
|
e.printStackTrace();
|
|
|
|
Log.e("KDE/initializeRsaKeys", "Exception");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
SharedPreferences settings = context.getSharedPreferences(deviceId, Context.MODE_PRIVATE);
|
|
|
|
|
|
|
|
//Store device information needed to create a Device object in a future
|
|
|
|
SharedPreferences.Editor editor = settings.edit();
|
|
|
|
editor.putString("deviceName", name);
|
|
|
|
editor.putString("deviceType", Device.DeviceType.Phone.toString());
|
|
|
|
editor.putString("publicKey", Base64.encodeToString(keyPair.getPublic().getEncoded(), 0).trim() + "\n");
|
|
|
|
editor.apply();
|
|
|
|
|
|
|
|
SharedPreferences preferences = context.getSharedPreferences("trusted_devices", Context.MODE_PRIVATE);
|
2018-03-03 16:06:52 +01:00
|
|
|
preferences.edit().putBoolean(deviceId, true).apply();
|
2015-06-10 12:29:53 +05:30
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Removing paired device info after each test case
|
|
|
|
@Override
|
|
|
|
protected void tearDown() throws Exception {
|
|
|
|
super.tearDown();
|
|
|
|
|
|
|
|
// Remove saved test device
|
|
|
|
Context context = getContext();
|
|
|
|
|
|
|
|
String deviceId = "testDevice";
|
|
|
|
|
|
|
|
SharedPreferences settings = context.getSharedPreferences(deviceId, Context.MODE_PRIVATE);
|
|
|
|
|
|
|
|
//Store device information needed to create a Device object in a future
|
|
|
|
SharedPreferences.Editor editor = settings.edit();
|
|
|
|
editor.clear();
|
|
|
|
editor.apply();
|
|
|
|
|
|
|
|
SharedPreferences preferences = context.getSharedPreferences("trusted_devices", Context.MODE_PRIVATE);
|
|
|
|
preferences.edit().remove(deviceId).apply();
|
|
|
|
}
|
|
|
|
|
|
|
|
// Basic paired device testing
|
2018-03-03 16:06:52 +01:00
|
|
|
public void testDevice() {
|
2015-06-10 12:29:53 +05:30
|
|
|
|
|
|
|
Device device = new Device(getContext(), "testDevice");
|
|
|
|
|
|
|
|
assertEquals(device.getDeviceId(), "testDevice");
|
|
|
|
assertEquals(device.getDeviceType(), Device.DeviceType.Phone);
|
|
|
|
assertEquals(device.getName(), "Test Device");
|
|
|
|
assertEquals(device.isPaired(), true);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
// Testing pairing done using reflection since it is private
|
|
|
|
// Created an unpaired device inside this test
|
2018-03-03 16:06:52 +01:00
|
|
|
public void testPairingDone() {
|
2015-06-10 12:29:53 +05:30
|
|
|
|
2018-03-04 11:31:37 +01:00
|
|
|
NetworkPacket fakeNetworkPacket = new NetworkPacket(NetworkPacket.PACKET_TYPE_IDENTITY);
|
|
|
|
fakeNetworkPacket.set("deviceId", "unpairedTestDevice");
|
|
|
|
fakeNetworkPacket.set("deviceName", "Unpaired Test Device");
|
|
|
|
fakeNetworkPacket.set("protocolVersion", NetworkPacket.ProtocolVersion);
|
|
|
|
fakeNetworkPacket.set("deviceType", Device.DeviceType.Phone.toString());
|
2015-06-10 12:29:53 +05:30
|
|
|
|
|
|
|
LanLinkProvider linkProvider = Mockito.mock(LanLinkProvider.class);
|
|
|
|
Mockito.when(linkProvider.getName()).thenReturn("LanLinkProvider");
|
|
|
|
LanLink link = Mockito.mock(LanLink.class);
|
|
|
|
Mockito.when(link.getLinkProvider()).thenReturn(linkProvider);
|
2016-06-12 21:07:01 +02:00
|
|
|
Mockito.when(link.getPairingHandler(Mockito.any(Device.class), Mockito.any(BasePairingHandler.PairingHandlerCallback.class))).thenReturn(Mockito.mock(LanPairingHandler.class));
|
2018-03-04 11:31:37 +01:00
|
|
|
Device device = new Device(getContext(), fakeNetworkPacket, link);
|
2015-06-10 12:29:53 +05:30
|
|
|
|
|
|
|
KeyPair keyPair;
|
|
|
|
try {
|
|
|
|
KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
|
|
|
|
keyGen.initialize(2048);
|
|
|
|
keyPair = keyGen.genKeyPair();
|
2018-03-03 16:06:52 +01:00
|
|
|
} catch (Exception e) {
|
2015-06-10 12:29:53 +05:30
|
|
|
e.printStackTrace();
|
|
|
|
Log.e("KDE/initializeRsaKeys", "Exception");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
device.publicKey = keyPair.getPublic();
|
|
|
|
|
|
|
|
assertNotNull(device);
|
|
|
|
assertEquals(device.getDeviceId(), "unpairedTestDevice");
|
|
|
|
assertEquals(device.getName(), "Unpaired Test Device");
|
|
|
|
assertEquals(device.getDeviceType(), Device.DeviceType.Phone);
|
2015-06-21 17:30:29 +05:30
|
|
|
assertNotNull(device.publicKey);
|
|
|
|
assertNull(device.certificate);
|
2015-06-10 12:29:53 +05:30
|
|
|
|
|
|
|
Method method;
|
|
|
|
try {
|
|
|
|
method = Device.class.getDeclaredMethod("pairingDone");
|
|
|
|
method.setAccessible(true);
|
|
|
|
method.invoke(device);
|
2018-03-03 16:06:52 +01:00
|
|
|
} catch (Exception e) {
|
2015-06-10 12:29:53 +05:30
|
|
|
e.printStackTrace();
|
|
|
|
}
|
|
|
|
|
|
|
|
assertEquals(device.isPaired(), true);
|
|
|
|
|
|
|
|
SharedPreferences preferences = getContext().getSharedPreferences("trusted_devices", Context.MODE_PRIVATE);
|
|
|
|
assertEquals(preferences.getBoolean(device.getDeviceId(), false), true);
|
|
|
|
|
2018-03-03 16:06:52 +01:00
|
|
|
SharedPreferences settings = getContext().getSharedPreferences(device.getDeviceId(), Context.MODE_PRIVATE);
|
2015-06-10 12:29:53 +05:30
|
|
|
assertEquals(settings.getString("deviceName", "Unknown device"), "Unpaired Test Device");
|
|
|
|
assertEquals(settings.getString("deviceType", "tablet"), "phone");
|
|
|
|
|
|
|
|
// Cleanup for unpaired test device
|
|
|
|
preferences.edit().remove(device.getDeviceId()).apply();
|
|
|
|
settings.edit().clear().apply();
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2018-03-03 16:06:52 +01:00
|
|
|
public void testPairingDoneWithCertificate() throws Exception {
|
2015-06-21 17:30:29 +05:30
|
|
|
KeyPair keyPair = null;
|
|
|
|
try {
|
|
|
|
KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
|
|
|
|
keyGen.initialize(2048);
|
|
|
|
keyPair = keyGen.genKeyPair();
|
2018-03-03 16:06:52 +01:00
|
|
|
} catch (Exception e) {
|
2015-06-21 17:30:29 +05:30
|
|
|
e.printStackTrace();
|
|
|
|
Log.e("KDE/initializeRsaKeys", "Exception");
|
|
|
|
}
|
|
|
|
|
|
|
|
X509Certificate certificate = null;
|
|
|
|
try {
|
|
|
|
|
|
|
|
BouncyCastleProvider BC = new BouncyCastleProvider();
|
|
|
|
|
|
|
|
X500NameBuilder nameBuilder = new X500NameBuilder(BCStyle.INSTANCE);
|
|
|
|
nameBuilder.addRDN(BCStyle.CN, "testDevice");
|
|
|
|
nameBuilder.addRDN(BCStyle.OU, "KDE Connect");
|
|
|
|
nameBuilder.addRDN(BCStyle.O, "KDE");
|
|
|
|
Date notBefore = new Date(System.currentTimeMillis());
|
|
|
|
Date notAfter = new Date(System.currentTimeMillis() + System.currentTimeMillis());
|
|
|
|
X509v3CertificateBuilder certificateBuilder = new JcaX509v3CertificateBuilder(
|
|
|
|
nameBuilder.build(),
|
|
|
|
BigInteger.ONE,
|
|
|
|
notBefore,
|
|
|
|
notAfter,
|
|
|
|
nameBuilder.build(),
|
|
|
|
keyPair.getPublic()
|
|
|
|
);
|
|
|
|
ContentSigner contentSigner = new JcaContentSignerBuilder("SHA256WithRSAEncryption").setProvider(BC).build(keyPair.getPrivate());
|
|
|
|
certificate = new JcaX509CertificateConverter().setProvider(BC).getCertificate(certificateBuilder.build(contentSigner));
|
|
|
|
|
2018-03-03 16:06:52 +01:00
|
|
|
} catch (Exception e) {
|
2015-06-21 17:30:29 +05:30
|
|
|
e.printStackTrace();
|
2016-06-12 21:07:01 +02:00
|
|
|
Log.e("KDE/initialiseCert", "Exception");
|
2015-06-21 17:30:29 +05:30
|
|
|
}
|
|
|
|
|
2018-03-04 11:31:37 +01:00
|
|
|
NetworkPacket fakeNetworkPacket = new NetworkPacket(NetworkPacket.PACKET_TYPE_IDENTITY);
|
|
|
|
fakeNetworkPacket.set("deviceId", "unpairedTestDevice");
|
|
|
|
fakeNetworkPacket.set("deviceName", "Unpaired Test Device");
|
|
|
|
fakeNetworkPacket.set("protocolVersion", NetworkPacket.ProtocolVersion);
|
|
|
|
fakeNetworkPacket.set("deviceType", Device.DeviceType.Phone.toString());
|
|
|
|
fakeNetworkPacket.set("certificate", Base64.encodeToString(certificate.getEncoded(), 0));
|
2015-06-21 17:30:29 +05:30
|
|
|
|
|
|
|
LanLinkProvider linkProvider = Mockito.mock(LanLinkProvider.class);
|
|
|
|
Mockito.when(linkProvider.getName()).thenReturn("LanLinkProvider");
|
|
|
|
LanLink link = Mockito.mock(LanLink.class);
|
2016-06-12 21:07:01 +02:00
|
|
|
Mockito.when(link.getPairingHandler(Mockito.any(Device.class), Mockito.any(BasePairingHandler.PairingHandlerCallback.class))).thenReturn(Mockito.mock(LanPairingHandler.class));
|
2015-06-21 17:30:29 +05:30
|
|
|
Mockito.when(link.getLinkProvider()).thenReturn(linkProvider);
|
2018-03-04 11:31:37 +01:00
|
|
|
Device device = new Device(getContext(), fakeNetworkPacket, link);
|
2015-06-21 17:30:29 +05:30
|
|
|
device.publicKey = keyPair.getPublic();
|
|
|
|
|
|
|
|
assertNotNull(device);
|
|
|
|
assertEquals(device.getDeviceId(), "unpairedTestDevice");
|
|
|
|
assertEquals(device.getName(), "Unpaired Test Device");
|
|
|
|
assertEquals(device.getDeviceType(), Device.DeviceType.Phone);
|
|
|
|
assertNotNull(device.publicKey);
|
|
|
|
assertNotNull(device.certificate);
|
|
|
|
|
|
|
|
Method method;
|
|
|
|
try {
|
|
|
|
method = Device.class.getDeclaredMethod("pairingDone");
|
|
|
|
method.setAccessible(true);
|
|
|
|
method.invoke(device);
|
2018-03-03 16:06:52 +01:00
|
|
|
} catch (Exception e) {
|
2015-06-21 17:30:29 +05:30
|
|
|
e.printStackTrace();
|
|
|
|
}
|
|
|
|
|
|
|
|
assertEquals(device.isPaired(), true);
|
|
|
|
|
|
|
|
SharedPreferences preferences = getContext().getSharedPreferences("trusted_devices", Context.MODE_PRIVATE);
|
|
|
|
assertEquals(preferences.getBoolean(device.getDeviceId(), false), true);
|
|
|
|
|
2018-03-03 16:06:52 +01:00
|
|
|
SharedPreferences settings = getContext().getSharedPreferences(device.getDeviceId(), Context.MODE_PRIVATE);
|
2015-06-21 17:30:29 +05:30
|
|
|
assertEquals(settings.getString("deviceName", "Unknown device"), "Unpaired Test Device");
|
|
|
|
assertEquals(settings.getString("deviceType", "tablet"), "phone");
|
|
|
|
|
|
|
|
// Cleanup for unpaired test device
|
|
|
|
preferences.edit().remove(device.getDeviceId()).apply();
|
|
|
|
settings.edit().clear().apply();
|
|
|
|
}
|
|
|
|
|
2018-03-03 16:06:52 +01:00
|
|
|
public void testUnpair() {
|
2015-06-10 12:29:53 +05:30
|
|
|
|
|
|
|
Device device = new Device(getContext(), "testDevice");
|
|
|
|
|
|
|
|
device.unpair();
|
|
|
|
|
|
|
|
assertEquals(device.isPaired(), false);
|
|
|
|
|
|
|
|
SharedPreferences preferences = getContext().getSharedPreferences("trusted_devices", Context.MODE_PRIVATE);
|
|
|
|
assertEquals(preferences.getBoolean(device.getDeviceId(), false), false);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
}
|