2
0
mirror of https://github.com/KDE/kdeconnect-android synced 2025-08-22 09:58:08 +00:00

Add a verification key that's displayed when pairing

The key is a sha256 of both devices' certificates. Both should generate the
same key, so hey user can check they are pairing against the right device.

Thanks Matthias Gerstner <mgerstner@suse.de> for reporting this.
This commit is contained in:
Albert Vaca Cintora 2020-11-26 11:30:46 +01:00
parent dd210259b9
commit d09ad45e11
6 changed files with 63 additions and 4 deletions

10
res/drawable/ic_key.xml Normal file
View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M12.65,10C11.83,7.67 9.61,6 7,6c-3.31,0 -6,2.69 -6,6s2.69,6 6,6c2.61,0 4.83,-1.67 5.65,-4H17v4h4v-4h2v-4H12.65zM7,14c-1.1,0 -2,-0.9 -2,-2s0.9,-2 2,-2 2,0.9 2,2 -0.9,2 -2,2z"/>
</vector>

View File

@ -30,6 +30,18 @@
android:text="@string/device_not_paired"
android:textAppearance="?android:attr/textAppearanceMedium"/>
<TextView
android:id="@+id/pair_verification"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:drawablePadding="5dp"
android:drawableLeft="@drawable/ic_key"
android:drawableStart="@drawable/ic_key"
android:layout_marginBottom="8dip"
android:visibility="gone"
android:text=""
android:textAppearance="?android:attr/textAppearanceMedium"/>
<Button
android:id="@+id/pair_button"
android:layout_width="match_parent"

View File

@ -126,9 +126,10 @@
<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_fingerprint">SHA1 fingerprint of your device certificate is:</string>
<string name="remote_device_fingerprint">SHA1 fingerprint of remote device certificate is:</string>
<string name="remote_device_fingerprint">SHA256 fingerprint of remote device certificate is:</string>
<string name="pair_requested">Pair requested</string>
<string name="pairing_request_from">Pairing request from %1s</string>
<string name="pairing_verification_code" translatable="false">🔑%1s...</string>
<plurals name="incoming_file_title">Receiving file from %1s>
<item quantity="one">Receiving %1$d file from %2$s</item>
<item quantity="other">Receiving %1$d files from %2$s</item>

View File

@ -391,10 +391,11 @@ public class Device implements BaseLink.PacketReceiver {
final NotificationManager notificationManager = ContextCompat.getSystemService(getContext(), NotificationManager.class);
String verificationKeyShort = SslHelper.getVerificationKey(SslHelper.certificate, certificate).substring(8);
Notification noti = new NotificationCompat.Builder(getContext(), NotificationHelper.Channels.DEFAULT)
.setContentTitle(res.getString(R.string.pairing_request_from, getName()))
.setContentText(res.getString(R.string.tap_to_answer))
.setContentIntent(pendingIntent)
.setContentText(res.getString(R.string.pairing_verification_code, verificationKeyShort))
.setTicker(res.getString(R.string.pair_requested))
.setSmallIcon(R.drawable.ic_notification)
.addAction(R.drawable.ic_accept_pairing_24dp, res.getString(R.string.pairing_accept), acceptedPendingIntent)

View File

@ -30,6 +30,7 @@ import org.spongycastle.cert.jcajce.JcaX509v3CertificateBuilder;
import org.spongycastle.jce.provider.BouncyCastleProvider;
import org.spongycastle.operator.ContentSigner;
import org.spongycastle.operator.jcajce.JcaContentSignerBuilder;
import org.spongycastle.util.Arrays;
import java.io.IOException;
import java.math.BigInteger;
@ -254,7 +255,7 @@ public class SslHelper {
public static String getCertificateHash(Certificate certificate) {
try {
byte[] hash = MessageDigest.getInstance("SHA-1").digest(certificate.getEncoded());
byte[] hash = MessageDigest.getInstance("SHA-256").digest(certificate.getEncoded());
Formatter formatter = new Formatter();
int i;
for (i = 0; i < hash.length - 1; i++) {
@ -279,4 +280,31 @@ public class SslHelper {
return IETFUtils.valueToString(rdn.getFirst().getValue());
}
public static String getVerificationKey(X509Certificate certificateA, Certificate certificateB) {
try {
byte[] a = certificateA.getPublicKey().getEncoded();
byte[] b = certificateB.getPublicKey().getEncoded();
if (Arrays.compareUnsigned(a, b) < 0) {
// Swap them so on both devices they are in the same order
byte[] aux = a;
a = b;
b = aux;
}
byte[] concat = new byte[a.length + b.length];
System.arraycopy(a, 0, concat, 0, a.length);
System.arraycopy(b, 0, concat, a.length, b.length);
byte[] hash = MessageDigest.getInstance("SHA-256").digest(concat);
Formatter formatter = new Formatter();
for (int i = 0; i < hash.length - 1; i++) {
formatter.format("%02x", hash[i]);
}
return formatter.toString();
} catch(Exception e) {
e.printStackTrace();
return "error";
}
}
}

View File

@ -104,6 +104,8 @@ public class DeviceFragment extends Fragment {
binding.pairButton.setOnClickListener(v -> {
binding.pairButton.setVisibility(View.GONE);
binding.pairMessage.setText("");
binding.pairVerification.setVisibility(View.VISIBLE);
binding.pairVerification.setText(SslHelper.getVerificationKey(SslHelper.certificate, device.certificate));
binding.pairProgress.setVisibility(View.VISIBLE);
BackgroundService.RunCommand(mActivity, service -> {
device = service.getDevice(mDeviceId);
@ -259,6 +261,8 @@ public class DeviceFragment extends Fragment {
public void run() {
if (device.isPairRequestedByPeer()) {
binding.pairMessage.setText(R.string.pair_requested);
binding.pairVerification.setVisibility(View.VISIBLE);
binding.pairVerification.setText(SslHelper.getVerificationKey(SslHelper.certificate, device.certificate));
binding.pairingButtons.setVisibility(View.VISIBLE);
binding.pairProgress.setVisibility(View.GONE);
binding.pairButton.setVisibility(View.GONE);
@ -333,6 +337,8 @@ public class DeviceFragment extends Fragment {
mActivity.runOnUiThread(() -> {
if (binding.getRoot() == null) return;
binding.pairMessage.setText(error);
binding.pairVerification.setText("");
binding.pairVerification.setVisibility(View.GONE);
binding.pairProgress.setVisibility(View.GONE);
binding.pairButton.setVisibility(View.VISIBLE);
binding.pairRequestButtons.setVisibility(View.GONE);
@ -345,6 +351,7 @@ public class DeviceFragment extends Fragment {
mActivity.runOnUiThread(() -> {
if (binding.getRoot() == null) return;
binding.pairMessage.setText(R.string.device_not_paired);
binding.pairVerification.setVisibility(View.GONE);
binding.pairProgress.setVisibility(View.GONE);
binding.pairButton.setVisibility(View.VISIBLE);
binding.pairRequestButtons.setVisibility(View.GONE);