diff --git a/tests/org/kde/kdeconnect/Helpers/SslHelperTest.kt b/tests/org/kde/kdeconnect/Helpers/SslHelperTest.kt new file mode 100644 index 00000000..e529e4ff --- /dev/null +++ b/tests/org/kde/kdeconnect/Helpers/SslHelperTest.kt @@ -0,0 +1,125 @@ +/* + * SPDX-FileCopyrightText: 2024 TPJ Schikhof + * + * SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL +*/ +package org.kde.kdeconnect.Helpers + +import android.content.Context +import org.junit.After +import org.junit.Assert +import org.junit.Before +import org.junit.Test +import org.kde.kdeconnect.Helpers.SecurityHelpers.SslHelper +import org.kde.kdeconnect.MockSharedPreference +import org.mockito.ArgumentMatchers +import org.mockito.MockedStatic +import org.mockito.Mockito +import org.mockito.invocation.InvocationOnMock +import java.security.cert.X509Certificate +import java.util.Base64 + +class SSLHelperTest { + private lateinit var context: Context + private lateinit var sharedPreferences: MockSharedPreference + /** Certificate captured from debug session */ + 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 deviceId = "testDevice" + private val certificateKey = "certificate" + private lateinit var mockBase64: MockedStatic + + @Before + fun setup() { + context = Mockito.mock(Context::class.java) + sharedPreferences = MockSharedPreference() + Mockito.`when`(context.getSharedPreferences(deviceId, Context.MODE_PRIVATE)).thenReturn(sharedPreferences) + + val mockBase64 = Mockito.mockStatic(android.util.Base64::class.java) + + mockBase64.`when` { + android.util.Base64.encodeToString(ArgumentMatchers.any(ByteArray::class.java), ArgumentMatchers.anyInt()) + }.thenAnswer { invocation: InvocationOnMock -> + java.util.Base64.getMimeEncoder().encodeToString(invocation.arguments[0] as ByteArray) + } + + mockBase64.`when` { + android.util.Base64.decode(ArgumentMatchers.anyString(), ArgumentMatchers.anyInt()) + }.thenAnswer { invocation: InvocationOnMock -> + java.util.Base64.getMimeDecoder().decode(invocation.arguments[0] as String) + } + + this.mockBase64 = mockBase64 + } + + @After + fun tearDown() { + mockBase64.close() + } + + @Test + fun testNoCertificateStored() { + val isStored = SslHelper.isCertificateStored(context, deviceId) + Assert.assertFalse(isStored) + } + + @Test + fun testCertificateStored() { + sharedPreferences.edit().putString(certificateKey, certificateBase64).apply() + Assert.assertTrue(SslHelper.isCertificateStored(context, deviceId)) + sharedPreferences.edit().remove(certificateKey).apply() + Assert.assertFalse(SslHelper.isCertificateStored(context, deviceId)) + } + + @Test + fun getAnyCertificate() { + Assert.assertThrows(Exception::class.java) { SslHelper.getDeviceCertificate(context, deviceId) } + sharedPreferences.edit().putString(certificateKey, certificateBase64).apply() + Assert.assertNotNull(SslHelper.getDeviceCertificate(context, deviceId)) + } + + @Test + fun getExpectedCertificate() { + sharedPreferences.edit().putString(certificateKey, certificateBase64).apply() + val cert = SslHelper.getDeviceCertificate(context, deviceId) + Assert.assertEquals(certificateBase64, Base64.getEncoder().encodeToString(cert.encoded)) + } + + @Test + fun getCertificateHash() { + sharedPreferences.edit().putString(certificateKey, certificateBase64).apply() + val cert = SslHelper.getDeviceCertificate(context, deviceId) + val hash = SslHelper.getCertificateHash(cert) + Assert.assertEquals(certificateHash, hash) + } + + @Test + fun parseCertificate() { + val bytes = Base64.getDecoder().decode(certificateBase64) + val cert = SslHelper.parseCertificate(bytes) + val hash = SslHelper.getCertificateHash(cert) + Assert.assertEquals(certificateHash, hash) + } + + @Test + fun getCommonName() { + sharedPreferences.edit().putString(certificateKey, certificateBase64).apply() + val cert = SslHelper.getDeviceCertificate(context, deviceId) + val method = SslHelper::class.java.getDeclaredMethod("getCommonNameFromCertificate", X509Certificate::class.java) + method.isAccessible = true + val commonName = method.invoke(null, cert) as String + val expected = "ee061a75_e403_4ecc_9261_5ffe2722f698" + 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) + } +} \ No newline at end of file