commit bc015d5d6057ff2425ca0ff9798c46edf1352fb3
parent 5e63f8fae7e44c4fd17856d33893ee3b80589938
Author: rhunk <101876869+rhunk@users.noreply.github.com>
Date:   Sat, 30 Sep 2023 19:09:23 +0200

feat(e2ee): secret fingerprint

Diffstat:
Mapp/src/main/kotlin/me/rhunk/snapenhance/e2ee/E2EEImplementation.kt | 16++++++++++++++++
Mcore/src/main/aidl/me/rhunk/snapenhance/bridge/e2ee/E2eeInterface.aidl | 12++++++++++++
Mcore/src/main/kotlin/me/rhunk/snapenhance/features/impl/experiments/EndToEndEncryption.kt | 20+++++++++++++++++---
3 files changed, 45 insertions(+), 3 deletions(-)

diff --git a/app/src/main/kotlin/me/rhunk/snapenhance/e2ee/E2EEImplementation.kt b/app/src/main/kotlin/me/rhunk/snapenhance/e2ee/E2EEImplementation.kt @@ -5,6 +5,7 @@ import me.rhunk.snapenhance.bridge.e2ee.E2eeInterface import me.rhunk.snapenhance.bridge.e2ee.EncryptionResult import org.bouncycastle.pqc.crypto.crystals.kyber.* import java.io.File +import java.security.MessageDigest import java.security.SecureRandom import javax.crypto.Cipher import javax.crypto.spec.IvParameterSpec @@ -115,6 +116,21 @@ class E2EEImplementation ( return File(e2eeFolder, "$friendId.key").exists() } + override fun getSecretFingerprint(friendId: String): String? { + val sharedSecretKey = runCatching { + File(e2eeFolder, "$friendId.key").readBytes() + }.onFailure { + context.log.error("Failed to read shared secret key", it) + return null + }.getOrThrow() + + return MessageDigest.getInstance("SHA-256") + .digest(sharedSecretKey) + .joinToString("") { "%02x".format(it) } + .chunked(5) + .joinToString(" ") + } + override fun encryptMessage(friendId: String, message: ByteArray): EncryptionResult? { val encryptionKey = runCatching { File(e2eeFolder, "$friendId.key").readBytes() diff --git a/core/src/main/aidl/me/rhunk/snapenhance/bridge/e2ee/E2eeInterface.aidl b/core/src/main/aidl/me/rhunk/snapenhance/bridge/e2ee/E2eeInterface.aidl @@ -26,8 +26,20 @@ interface E2eeInterface { */ boolean acceptPairingResponse(String friendId, in byte[] encapsulatedSecret); + /** + * Check if a friend key exists + * @param friendId + * @return true if the friend key exists + */ boolean friendKeyExists(String friendId); + /** + * Get the fingerprint of a secret key + * @param friendId + * @return the fingerprint of the secret key + */ + @nullable String getSecretFingerprint(String friendId); + @nullable EncryptionResult encryptMessage(String friendId, in byte[] message); @nullable byte[] decryptMessage(String friendId, in byte[] message, in byte[] iv); diff --git a/core/src/main/kotlin/me/rhunk/snapenhance/features/impl/experiments/EndToEndEncryption.kt b/core/src/main/kotlin/me/rhunk/snapenhance/features/impl/experiments/EndToEndEncryption.kt @@ -153,21 +153,35 @@ class EndToEndEncryption : MessagingRuleFeature( private fun openManagementPopup() { val conversationId = context.feature(Messaging::class).openedConversationUUID?.toString() ?: return + val friendId = context.database.getDMOtherParticipant(conversationId) - if (context.database.getDMOtherParticipant(conversationId) == null) { + if (friendId == null) { context.shortToast("This menu is only available in direct messages.") return } val actions = listOf( - "Initiate a new shared secret" + "Initiate a new shared secret", + "Show shared key fingerprint" ) ViewAppearanceHelper.newAlertDialogBuilder(context.mainActivity!!).apply { setTitle("End-to-end encryption") setItems(actions.toTypedArray()) { _, which -> when (which) { - 0 -> askForKeys(conversationId) + 0 -> { + warnKeyOverwrite(friendId) { + askForKeys(conversationId) + } + } + 1 -> { + val fingerprint = e2eeInterface.getSecretFingerprint(friendId) + ViewAppearanceHelper.newAlertDialogBuilder(context).apply { + setTitle("End-to-end encryption") + setMessage("Your fingerprint is:\n\n$fingerprint\n\nMake sure to check if it matches your friend's fingerprint!") + setPositiveButton("OK") { _, _ -> } + }.show() + } } } setPositiveButton("OK") { _, _ -> }