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:
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") { _, _ -> }