mirror of
https://github.com/KDE/kdeconnect-android
synced 2025-08-22 09:58:08 +00:00
Make the verification key change every time in protocol v8
This commit is contained in:
parent
b4ee6e30b1
commit
7a4fb8b584
@ -176,6 +176,7 @@ SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted
|
|||||||
<string name="error_not_reachable">Device not reachable</string>
|
<string name="error_not_reachable">Device not reachable</string>
|
||||||
<string name="error_already_paired">Device already paired</string>
|
<string name="error_already_paired">Device already paired</string>
|
||||||
<string name="error_timed_out">Timed out</string>
|
<string name="error_timed_out">Timed out</string>
|
||||||
|
<string name="error_clocks_not_match">Device clocks are out of sync</string>
|
||||||
<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="encryption_info_title">Encryption Info</string>
|
<string name="encryption_info_title">Encryption Info</string>
|
||||||
|
@ -159,19 +159,18 @@ class Device : PacketReceiver {
|
|||||||
val certificate: Certificate
|
val certificate: Certificate
|
||||||
get() = deviceInfo.certificate
|
get() = deviceInfo.certificate
|
||||||
|
|
||||||
|
val verificationKey: String?
|
||||||
|
get() = pairingHandler.verificationKey()
|
||||||
|
|
||||||
// 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
|
||||||
fun compareProtocolVersion(): Int =
|
fun compareProtocolVersion(): Int =
|
||||||
deviceInfo.protocolVersion - DeviceHelper.ProtocolVersion
|
deviceInfo.protocolVersion - DeviceHelper.ProtocolVersion
|
||||||
|
|
||||||
|
|
||||||
val isPaired: Boolean
|
val isPaired: Boolean
|
||||||
get() = pairingHandler.state == PairingHandler.PairState.Paired
|
get() = pairingHandler.state == PairingHandler.PairState.Paired
|
||||||
|
|
||||||
val isPairRequested: Boolean
|
val pairStatus : PairingHandler.PairState
|
||||||
get() = pairingHandler.state == PairingHandler.PairState.Requested
|
get() = pairingHandler.state
|
||||||
|
|
||||||
val isPairRequestedByPeer: Boolean
|
|
||||||
get() = pairingHandler.state == PairingHandler.PairState.RequestedByPeer
|
|
||||||
|
|
||||||
fun addPairingCallback(callback: PairingCallback) = pairingCallbacks.add(callback)
|
fun addPairingCallback(callback: PairingCallback) = pairingCallbacks.add(callback)
|
||||||
|
|
||||||
@ -289,8 +288,6 @@ class Device : PacketReceiver {
|
|||||||
|
|
||||||
val notificationManager = ContextCompat.getSystemService(context, NotificationManager::class.java)!!
|
val notificationManager = ContextCompat.getSystemService(context, NotificationManager::class.java)!!
|
||||||
|
|
||||||
val verificationKey = SslHelper.getVerificationKey(SslHelper.certificate, deviceInfo.certificate)
|
|
||||||
|
|
||||||
val noti = NotificationCompat.Builder(context, NotificationHelper.Channels.DEFAULT)
|
val noti = NotificationCompat.Builder(context, NotificationHelper.Channels.DEFAULT)
|
||||||
.setContentTitle(res.getString(R.string.pairing_request_from, name))
|
.setContentTitle(res.getString(R.string.pairing_request_from, name))
|
||||||
.setContentText(res.getString(R.string.pairing_verification_code, verificationKey))
|
.setContentText(res.getString(R.string.pairing_verification_code, verificationKey))
|
||||||
|
@ -282,31 +282,4 @@ public class SslHelper {
|
|||||||
return IETFUtils.valueToString(rdn.getFirst().getValue());
|
return IETFUtils.valueToString(rdn.getFirst().getValue());
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String getVerificationKey(Certificate 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 (byte value : hash) {
|
|
||||||
formatter.format("%02x", value);
|
|
||||||
}
|
|
||||||
return formatter.toString().substring(0,8).toUpperCase(Locale.ROOT);
|
|
||||||
} catch(Exception e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
return "error";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -8,7 +8,13 @@ package org.kde.kdeconnect
|
|||||||
import android.util.Log
|
import android.util.Log
|
||||||
import androidx.annotation.VisibleForTesting
|
import androidx.annotation.VisibleForTesting
|
||||||
import kotlinx.coroutines.*
|
import kotlinx.coroutines.*
|
||||||
|
import org.bouncycastle.util.Arrays
|
||||||
|
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.security.cert.Certificate
|
||||||
|
import java.util.Formatter
|
||||||
|
import kotlin.math.abs
|
||||||
import kotlin.time.Duration.Companion.seconds
|
import kotlin.time.Duration.Companion.seconds
|
||||||
|
|
||||||
class PairingHandler(private val device: Device, private val callback: PairingCallback, var state: PairState) {
|
class PairingHandler(private val device: Device, private val callback: PairingCallback, var state: PairState) {
|
||||||
@ -31,6 +37,7 @@ class PairingHandler(private val device: Device, private val callback: PairingCa
|
|||||||
|
|
||||||
private val pairingJob = SupervisorJob()
|
private val pairingJob = SupervisorJob()
|
||||||
private val pairingScope = CoroutineScope(Dispatchers.IO + pairingJob)
|
private val pairingScope = CoroutineScope(Dispatchers.IO + pairingJob)
|
||||||
|
private var pairingTimestamp = 0L
|
||||||
|
|
||||||
fun packetReceived(np: NetworkPacket) {
|
fun packetReceived(np: NetworkPacket) {
|
||||||
cancelTimer()
|
cancelTimer()
|
||||||
@ -50,10 +57,26 @@ class PairingHandler(private val device: Device, private val callback: PairingCa
|
|||||||
Log.w("PairingHandler", "Received pairing request from a device we already trusted.")
|
Log.w("PairingHandler", "Received pairing request from a device we already trusted.")
|
||||||
// It would be nice to auto-accept the pairing request here, but since the pairing accept and pairing request
|
// It would be nice to auto-accept the pairing request here, but since the pairing accept and pairing request
|
||||||
// messages are identical, this could create an infinite loop if both devices are "accepting" each other pairs.
|
// messages are identical, this could create an infinite loop if both devices are "accepting" each other pairs.
|
||||||
// Instead, unpair and handle as if "NotPaired".
|
// Instead, unpair and handle as if "NotPaired". TODO: No longer true in protocol version 8
|
||||||
state = PairState.NotPaired
|
state = PairState.NotPaired
|
||||||
callback.unpaired()
|
callback.unpaired()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (device.protocolVersion >= 8) {
|
||||||
|
pairingTimestamp = np.getLong("timestamp", -1L)
|
||||||
|
if (pairingTimestamp == -1L) {
|
||||||
|
state = PairState.NotPaired
|
||||||
|
callback.unpaired()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
val currentTimestamp = System.currentTimeMillis() / 1000L
|
||||||
|
if (abs(pairingTimestamp - currentTimestamp) > allowedTimestampDifferenceSeconds) {
|
||||||
|
state = PairState.NotPaired
|
||||||
|
callback.pairingFailed(device.context.getString(R.string.error_clocks_not_match))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
state = PairState.RequestedByPeer
|
state = PairState.RequestedByPeer
|
||||||
|
|
||||||
pairingScope.launch {
|
pairingScope.launch {
|
||||||
@ -85,6 +108,18 @@ class PairingHandler(private val device: Device, private val callback: PairingCa
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun verificationKey(): String? {
|
||||||
|
return if (device.protocolVersion >= 8) {
|
||||||
|
if (state != PairState.Requested && state != PairState.RequestedByPeer) {
|
||||||
|
return null
|
||||||
|
} else {
|
||||||
|
getVerificationKey(SslHelper.certificate, device.certificate, pairingTimestamp)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
getVerificationKeyV7(SslHelper.certificate, device.certificate)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun requestPairing() {
|
fun requestPairing() {
|
||||||
cancelTimer()
|
cancelTimer()
|
||||||
|
|
||||||
@ -126,6 +161,8 @@ class PairingHandler(private val device: Device, private val callback: PairingCa
|
|||||||
}
|
}
|
||||||
val np = NetworkPacket(NetworkPacket.PACKET_TYPE_PAIR)
|
val np = NetworkPacket(NetworkPacket.PACKET_TYPE_PAIR)
|
||||||
np["pair"] = true
|
np["pair"] = true
|
||||||
|
pairingTimestamp = System.currentTimeMillis() / 1000L
|
||||||
|
np["timestamp"] = pairingTimestamp
|
||||||
device.sendPacket(np, statusCallback)
|
device.sendPacket(np, statusCallback)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -181,4 +218,36 @@ class PairingHandler(private val device: Device, private val callback: PairingCa
|
|||||||
private fun cancelTimer() {
|
private fun cancelTimer() {
|
||||||
pairingJob.cancelChildren()
|
pairingJob.cancelChildren()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val allowedTimestampDifferenceSeconds = 1_800 // 30 minutes
|
||||||
|
|
||||||
|
// Concatenate in a deterministic order so on both devices the result is the same
|
||||||
|
private fun sortedConcat(a: ByteArray, b: ByteArray): ByteArray {
|
||||||
|
return if (Arrays.compareUnsigned(a, b) < 0) {
|
||||||
|
b + a
|
||||||
|
} else {
|
||||||
|
a + b
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun humanReadableHash(bytes: ByteArray): String {
|
||||||
|
val hash = MessageDigest.getInstance("SHA-256").digest(bytes)
|
||||||
|
val formatter = Formatter()
|
||||||
|
for (value in hash) {
|
||||||
|
formatter.format("%02x", value)
|
||||||
|
}
|
||||||
|
return formatter.toString().substring(0, 8).uppercase()
|
||||||
|
}
|
||||||
|
fun getVerificationKey(certificateA: Certificate, certificateB: Certificate, timestamp: Long): String {
|
||||||
|
val certsConcat = sortedConcat(certificateA.publicKey.encoded, certificateB.publicKey.encoded)
|
||||||
|
return humanReadableHash(certsConcat + timestamp.toString().toByteArray())
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getVerificationKeyV7(certificateA: Certificate, certificateB: Certificate): String {
|
||||||
|
val certsConcat = sortedConcat(certificateA.publicKey.encoded, certificateB.publicKey.encoded)
|
||||||
|
return humanReadableHash(certsConcat)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -115,16 +115,9 @@ class DeviceFragment : Fragment() {
|
|||||||
this.refreshDevicesAction()
|
this.refreshDevicesAction()
|
||||||
}
|
}
|
||||||
|
|
||||||
requirePairingBinding().pairVerification.text = SslHelper.getVerificationKey(SslHelper.certificate, device?.certificate)
|
|
||||||
|
|
||||||
requirePairingBinding().pairButton.setOnClickListener {
|
requirePairingBinding().pairButton.setOnClickListener {
|
||||||
with(requirePairingBinding()) {
|
|
||||||
pairButton.visibility = View.GONE
|
|
||||||
pairMessage.text = getString(R.string.pair_requested)
|
|
||||||
pairProgress.visibility = View.VISIBLE
|
|
||||||
}
|
|
||||||
device?.requestPairing()
|
device?.requestPairing()
|
||||||
mActivity?.invalidateOptionsMenu()
|
refreshUI()
|
||||||
}
|
}
|
||||||
requirePairingBinding().acceptButton.setOnClickListener {
|
requirePairingBinding().acceptButton.setOnClickListener {
|
||||||
device?.apply {
|
device?.apply {
|
||||||
@ -242,7 +235,7 @@ class DeviceFragment : Fragment() {
|
|||||||
true
|
true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (device.isPairRequested) {
|
if (device.pairStatus == PairingHandler.PairState.Requested) {
|
||||||
menu.add(R.string.cancel_pairing).setOnMenuItemClickListener {
|
menu.add(R.string.cancel_pairing).setOnMenuItemClickListener {
|
||||||
device.cancelPairing()
|
device.cancelPairing()
|
||||||
true
|
true
|
||||||
@ -275,19 +268,36 @@ class DeviceFragment : Fragment() {
|
|||||||
//Once in-app, there is no point in keep displaying the notification if any
|
//Once in-app, there is no point in keep displaying the notification if any
|
||||||
device.hidePairingNotification()
|
device.hidePairingNotification()
|
||||||
|
|
||||||
if (device.isPairRequestedByPeer) {
|
when (device.pairStatus) {
|
||||||
with (requirePairingBinding()) {
|
PairingHandler.PairState.NotPaired -> {
|
||||||
pairMessage.setText(R.string.pair_requested)
|
requireErrorBinding().errorMessageContainer.visibility = View.GONE
|
||||||
pairVerification.visibility = View.VISIBLE
|
requireDeviceBinding().deviceView.visibility = View.GONE
|
||||||
pairVerification.text = SslHelper.getVerificationKey(SslHelper.certificate, device.certificate)
|
requirePairingBinding().pairingButtons.visibility = View.VISIBLE
|
||||||
pairingButtons.visibility = View.VISIBLE
|
requirePairingBinding().pairVerification.visibility = View.GONE
|
||||||
pairProgress.visibility = View.GONE
|
|
||||||
pairButton.visibility = View.GONE
|
|
||||||
pairRequestButtons.visibility = View.VISIBLE
|
|
||||||
}
|
}
|
||||||
requireDeviceBinding().deviceView.visibility = View.GONE
|
PairingHandler.PairState.Requested -> {
|
||||||
} else {
|
with(requirePairingBinding()) {
|
||||||
if (device.isPaired) {
|
pairButton.visibility = View.GONE
|
||||||
|
pairMessage.text = getString(R.string.pair_requested)
|
||||||
|
pairProgress.visibility = View.VISIBLE
|
||||||
|
pairVerification.text = device.verificationKey
|
||||||
|
pairVerification.visibility = View.VISIBLE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
PairingHandler.PairState.RequestedByPeer -> {
|
||||||
|
with (requirePairingBinding()) {
|
||||||
|
pairMessage.setText(R.string.pair_requested)
|
||||||
|
pairVerification.visibility = View.VISIBLE
|
||||||
|
pairingButtons.visibility = View.VISIBLE
|
||||||
|
pairProgress.visibility = View.GONE
|
||||||
|
pairButton.visibility = View.GONE
|
||||||
|
pairRequestButtons.visibility = View.VISIBLE
|
||||||
|
pairVerification.text = device.verificationKey
|
||||||
|
pairVerification.visibility = View.VISIBLE
|
||||||
|
}
|
||||||
|
requireDeviceBinding().deviceView.visibility = View.GONE
|
||||||
|
}
|
||||||
|
PairingHandler.PairState.Paired -> {
|
||||||
requirePairingBinding().pairingButtons.visibility = View.GONE
|
requirePairingBinding().pairingButtons.visibility = View.GONE
|
||||||
if (device.isReachable) {
|
if (device.isReachable) {
|
||||||
val context = requireContext()
|
val context = requireContext()
|
||||||
@ -305,13 +315,9 @@ class DeviceFragment : Fragment() {
|
|||||||
requireErrorBinding().errorMessageContainer.visibility = View.VISIBLE
|
requireErrorBinding().errorMessageContainer.visibility = View.VISIBLE
|
||||||
requireDeviceBinding().deviceView.visibility = View.GONE
|
requireDeviceBinding().deviceView.visibility = View.GONE
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
requireErrorBinding().errorMessageContainer.visibility = View.GONE
|
|
||||||
requireDeviceBinding().deviceView.visibility = View.GONE
|
|
||||||
requirePairingBinding().pairingButtons.visibility = View.VISIBLE
|
|
||||||
}
|
}
|
||||||
mActivity?.invalidateOptionsMenu()
|
|
||||||
}
|
}
|
||||||
|
mActivity?.invalidateOptionsMenu()
|
||||||
}
|
}
|
||||||
|
|
||||||
private val pairingCallback: PairingHandler.PairingCallback = object : PairingHandler.PairingCallback {
|
private val pairingCallback: PairingHandler.PairingCallback = object : PairingHandler.PairingCallback {
|
||||||
|
@ -12,6 +12,7 @@ import org.junit.Before
|
|||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.kde.kdeconnect.Helpers.SecurityHelpers.SslHelper
|
import org.kde.kdeconnect.Helpers.SecurityHelpers.SslHelper
|
||||||
import org.kde.kdeconnect.MockSharedPreference
|
import org.kde.kdeconnect.MockSharedPreference
|
||||||
|
import org.kde.kdeconnect.PairingHandler
|
||||||
import org.mockito.ArgumentMatchers
|
import org.mockito.ArgumentMatchers
|
||||||
import org.mockito.MockedStatic
|
import org.mockito.MockedStatic
|
||||||
import org.mockito.Mockito
|
import org.mockito.Mockito
|
||||||
@ -22,7 +23,6 @@ import java.util.Base64
|
|||||||
class SSLHelperTest {
|
class SSLHelperTest {
|
||||||
private lateinit var context: Context
|
private lateinit var context: Context
|
||||||
private lateinit var sharedPreferences: MockSharedPreference
|
private lateinit var sharedPreferences: MockSharedPreference
|
||||||
/** Certificate captured from debug session */
|
|
||||||
private val certificateBase64 = "MIIBkzCCATmgAwIBAgIBATAKBggqhkjOPQQDBDBTMS0wKwYDVQQDDCRlZTA2MWE3NV9lNDAzXzRlY2NfOTI2MV81ZmZlMjcyMmY2OTgxFDASBgNVBAsMC0tERSBDb25uZWN0MQwwCgYDVQQKDANLREUwHhcNMjMwOTE1MjIwMDAwWhcNMzQwOTE1MjIwMDAwWjBTMS0wKwYDVQQDDCRlZTA2MWE3NV9lNDAzXzRlY2NfOTI2MV81ZmZlMjcyMmY2OTgxFDASBgNVBAsMC0tERSBDb25uZWN0MQwwCgYDVQQKDANLREUwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAASqOIKTm5j6x8DKgYSkItLmjCgIXP0gkOW6bmVvloDGsYnvqYLMFGe7YW8g8lT/qPBTEfDOM4UpQ8X6jidE+XrnMAoGCCqGSM49BAMEA0gAMEUCIEpk6VNpbt3tfbWDf0TmoJftRq3wAs3Dke7d5vMZlivyAiEA/ZXtSRqPjs/2RN9SynKhSUA9/z0PNq6LYoAaC6TdomM="
|
private val certificateBase64 = "MIIBkzCCATmgAwIBAgIBATAKBggqhkjOPQQDBDBTMS0wKwYDVQQDDCRlZTA2MWE3NV9lNDAzXzRlY2NfOTI2MV81ZmZlMjcyMmY2OTgxFDASBgNVBAsMC0tERSBDb25uZWN0MQwwCgYDVQQKDANLREUwHhcNMjMwOTE1MjIwMDAwWhcNMzQwOTE1MjIwMDAwWjBTMS0wKwYDVQQDDCRlZTA2MWE3NV9lNDAzXzRlY2NfOTI2MV81ZmZlMjcyMmY2OTgxFDASBgNVBAsMC0tERSBDb25uZWN0MQwwCgYDVQQKDANLREUwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAASqOIKTm5j6x8DKgYSkItLmjCgIXP0gkOW6bmVvloDGsYnvqYLMFGe7YW8g8lT/qPBTEfDOM4UpQ8X6jidE+XrnMAoGCCqGSM49BAMEA0gAMEUCIEpk6VNpbt3tfbWDf0TmoJftRq3wAs3Dke7d5vMZlivyAiEA/ZXtSRqPjs/2RN9SynKhSUA9/z0PNq6LYoAaC6TdomM="
|
||||||
private val certificateHash = "fc:1f:b3:d3:d3:3b:23:42:e4:5c:74:b1:a6:13:dc:df:e5:e1:f0:29:d6:68:24:9f:50:49:52:a9:a8:04:1e:31:"
|
private val certificateHash = "fc:1f:b3:d3:d3:3b:23:42:e4:5c:74:b1:a6:13:dc:df:e5:e1:f0:29:d6:68:24:9f:50:49:52:a9:a8:04:1e:31:"
|
||||||
private val deviceId = "testDevice"
|
private val deviceId = "testDevice"
|
||||||
@ -40,13 +40,13 @@ class SSLHelperTest {
|
|||||||
mockBase64.`when`<Any> {
|
mockBase64.`when`<Any> {
|
||||||
android.util.Base64.encodeToString(ArgumentMatchers.any(ByteArray::class.java), ArgumentMatchers.anyInt())
|
android.util.Base64.encodeToString(ArgumentMatchers.any(ByteArray::class.java), ArgumentMatchers.anyInt())
|
||||||
}.thenAnswer { invocation: InvocationOnMock ->
|
}.thenAnswer { invocation: InvocationOnMock ->
|
||||||
java.util.Base64.getMimeEncoder().encodeToString(invocation.arguments[0] as ByteArray)
|
Base64.getMimeEncoder().encodeToString(invocation.arguments[0] as ByteArray)
|
||||||
}
|
}
|
||||||
|
|
||||||
mockBase64.`when`<Any> {
|
mockBase64.`when`<Any> {
|
||||||
android.util.Base64.decode(ArgumentMatchers.anyString(), ArgumentMatchers.anyInt())
|
android.util.Base64.decode(ArgumentMatchers.anyString(), ArgumentMatchers.anyInt())
|
||||||
}.thenAnswer { invocation: InvocationOnMock ->
|
}.thenAnswer { invocation: InvocationOnMock ->
|
||||||
java.util.Base64.getMimeDecoder().decode(invocation.arguments[0] as String)
|
Base64.getMimeDecoder().decode(invocation.arguments[0] as String)
|
||||||
}
|
}
|
||||||
|
|
||||||
this.mockBase64 = mockBase64
|
this.mockBase64 = mockBase64
|
||||||
@ -112,14 +112,4 @@ class SSLHelperTest {
|
|||||||
Assert.assertEquals(expected, commonName)
|
Assert.assertEquals(expected, commonName)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
}
|
||||||
fun getVerificationKey() {
|
|
||||||
sharedPreferences.edit().putString(certificateKey, certificateBase64).apply()
|
|
||||||
val cert = SslHelper.getDeviceCertificate(context, deviceId)
|
|
||||||
|
|
||||||
// Normally not used with same certificate, but it's fine for testing
|
|
||||||
val verificationKey = SslHelper.getVerificationKey(cert, cert)
|
|
||||||
val expected = "97A75917"
|
|
||||||
Assert.assertEquals(expected, verificationKey)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
47
tests/org/kde/kdeconnect/PairingHandlerTest.kt
Normal file
47
tests/org/kde/kdeconnect/PairingHandlerTest.kt
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2025 Albert Vaca Cintora <albertvaka@gmail.com>
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
||||||
|
*/
|
||||||
|
package org.kde.kdeconnect
|
||||||
|
|
||||||
|
import org.junit.Assert
|
||||||
|
import org.junit.Test
|
||||||
|
import org.kde.kdeconnect.Helpers.SecurityHelpers.SslHelper
|
||||||
|
import java.util.Base64
|
||||||
|
|
||||||
|
class PairingHandlerTest {
|
||||||
|
private val certA = SslHelper.parseCertificate(Base64.getMimeDecoder().decode(
|
||||||
|
"MIIBkzCCATmgAwIBAgIBATAKBggqhkjOPQQDBDBTMS0wKwYDVQQDDCRlZTA2MWE3NV9lNDAzXzRlY2NfOTI2" +
|
||||||
|
"MV81ZmZlMjcyMmY2OTgxFDASBgNVBAsMC0tERSBDb25uZWN0MQwwCgYDVQQKDANLREUwHhcNMjMwOTE1MjIw" +
|
||||||
|
"MDAwWhcNMzQwOTE1MjIwMDAwWjBTMS0wKwYDVQQDDCRlZTA2MWE3NV9lNDAzXzRlY2NfOTI2MV81ZmZlMjcy" +
|
||||||
|
"MmY2OTgxFDASBgNVBAsMC0tERSBDb25uZWN0MQwwCgYDVQQKDANLREUwWTATBgcqhkjOPQIBBggqhkjOPQMB" +
|
||||||
|
"BwNCAASqOIKTm5j6x8DKgYSkItLmjCgIXP0gkOW6bmVvloDGsYnvqYLMFGe7YW8g8lT/qPBTEfDOM4UpQ8X6" +
|
||||||
|
"jidE+XrnMAoGCCqGSM49BAMEA0gAMEUCIEpk6VNpbt3tfbWDf0TmoJftRq3wAs3Dke7d5vMZlivyAiEA/ZXt" +
|
||||||
|
"SRqPjs/2RN9SynKhSUA9/z0PNq6LYoAaC6TdomM="
|
||||||
|
))
|
||||||
|
private val certB = SslHelper.parseCertificate(Base64.getMimeDecoder().decode(
|
||||||
|
"MIIBkzCCATmgAwIBAgIBATAKBggqhkjOPQQDBDBTMS0wKwYDVQQDDCQxNTdiYmMyOF82ZjJiXzRiMTZfYmQw" +
|
||||||
|
"Ml8xMzM0NWMwMjU0M2MxFDASBgNVBAsMC0tERSBDb25uZWN0MQwwCgYDVQQKDANLREUwHhcNMjQwMTE3MjMw" +
|
||||||
|
"MDAwWhcNMzUwMTE3MjMwMDAwWjBTMS0wKwYDVQQDDCQxNTdiYmMyOF82ZjJiXzRiMTZfYmQwMl8xMzM0NWMw" +
|
||||||
|
"MjU0M2MxFDASBgNVBAsMC0tERSBDb25uZWN0MQwwCgYDVQQKDANLREUwWTATBgcqhkjOPQIBBggqhkjOPQMB" +
|
||||||
|
"BwNCAAQ5W53rrDJps9v/sszQf0eLtvoGiRbfsY+snO6IJJfi1pFeHDQj2nAE+aTyUYelrcx1eIuqxFHnJTFt" +
|
||||||
|
"/HqXwuAvMAoGCCqGSM49BAMEA0gAMEUCIBIk3zKPz/M0c82nvCGFDXGGmfdojHsx3G5DbYNNKqFVAiEAzhBG" +
|
||||||
|
"e960/4NDiaVcOplBaeg5xNJKs3Kq+22J6JOii4Y="))
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun getVerificationKey() {
|
||||||
|
val timestampA = 1737228658L
|
||||||
|
val timestampB = 2737228658L
|
||||||
|
Assert.assertEquals("54DC916E", PairingHandler.getVerificationKey(certA, certB, timestampA))
|
||||||
|
Assert.assertEquals("54DC916E", PairingHandler.getVerificationKey(certB, certA, timestampA))
|
||||||
|
Assert.assertEquals("8C07153A", PairingHandler.getVerificationKey(certA, certB, timestampB))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun getVerificationKeyV7() {
|
||||||
|
Assert.assertEquals("F3900DB5", PairingHandler.getVerificationKeyV7(certA, certB))
|
||||||
|
Assert.assertEquals("F3900DB5", PairingHandler.getVerificationKeyV7(certB, certA))
|
||||||
|
Assert.assertEquals("97A75917", PairingHandler.getVerificationKeyV7(certA, certA))
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user