From 051d455c9f68d7edbc75abbc8d9293dd34d1d250 Mon Sep 17 00:00:00 2001 From: Harsh Shandilya Date: Mon, 15 Feb 2021 13:05:09 +0530 Subject: Add tests for GPG identifier parsing (#1319) --- .../aps/ui/crypto/PasswordCreationActivity.kt | 37 ++------------------- .../dev/msfjarvis/aps/util/crypto/GpgIdentifier.kt | 38 ++++++++++++++++++++++ .../msfjarvis/aps/util/crypto/GpgIdentifierTest.kt | 38 ++++++++++++++++++++++ 3 files changed, 78 insertions(+), 35 deletions(-) create mode 100644 app/src/main/java/dev/msfjarvis/aps/util/crypto/GpgIdentifier.kt create mode 100644 app/src/test/java/dev/msfjarvis/aps/util/crypto/GpgIdentifierTest.kt (limited to 'app/src') diff --git a/app/src/main/java/dev/msfjarvis/aps/ui/crypto/PasswordCreationActivity.kt b/app/src/main/java/dev/msfjarvis/aps/ui/crypto/PasswordCreationActivity.kt index 43e9b70b..749ff741 100644 --- a/app/src/main/java/dev/msfjarvis/aps/ui/crypto/PasswordCreationActivity.kt +++ b/app/src/main/java/dev/msfjarvis/aps/ui/crypto/PasswordCreationActivity.kt @@ -34,6 +34,7 @@ import dev.msfjarvis.aps.ui.dialogs.PasswordGeneratorDialogFragment import dev.msfjarvis.aps.ui.dialogs.XkPasswordGeneratorDialogFragment import dev.msfjarvis.aps.util.autofill.AutofillPreferences import dev.msfjarvis.aps.util.autofill.DirectoryStructure +import dev.msfjarvis.aps.util.crypto.GpgIdentifier import dev.msfjarvis.aps.util.extensions.base64 import dev.msfjarvis.aps.util.extensions.commitChange import dev.msfjarvis.aps.util.extensions.getString @@ -50,7 +51,6 @@ import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import me.msfjarvis.openpgpktx.util.OpenPgpApi import me.msfjarvis.openpgpktx.util.OpenPgpServiceConnection -import me.msfjarvis.openpgpktx.util.OpenPgpUtils class PasswordCreationActivity : BasePgpActivity(), OpenPgpServiceConnection.OnBound { @@ -271,39 +271,6 @@ class PasswordCreationActivity : BasePgpActivity(), OpenPgpServiceConnection.OnB otpImportButton.isVisible = !entry.hasTotp() } - private sealed class GpgIdentifier { - data class KeyId(val id: Long) : GpgIdentifier() - data class UserId(val email: String) : GpgIdentifier() - } - - @OptIn(ExperimentalUnsignedTypes::class) - private fun parseGpgIdentifier(identifier: String): GpgIdentifier? { - if (identifier.isEmpty()) return null - // Match long key IDs: - // FF22334455667788 or 0xFF22334455667788 - val maybeLongKeyId = identifier.removePrefix("0x").takeIf { - it.matches("[a-fA-F0-9]{16}".toRegex()) - } - if (maybeLongKeyId != null) { - val keyId = maybeLongKeyId.toULong(16) - return GpgIdentifier.KeyId(keyId.toLong()) - } - - // Match fingerprints: - // FF223344556677889900112233445566778899 or 0xFF223344556677889900112233445566778899 - val maybeFingerprint = identifier.removePrefix("0x").takeIf { - it.matches("[a-fA-F0-9]{40}".toRegex()) - } - if (maybeFingerprint != null) { - // Truncating to the long key ID is not a security issue since OpenKeychain only accepts - // non-ambiguous key IDs. - val keyId = maybeFingerprint.takeLast(16).toULong(16) - return GpgIdentifier.KeyId(keyId.toLong()) - } - - return OpenPgpUtils.splitUserId(identifier).email?.let { GpgIdentifier.UserId(it) } - } - /** * Encrypts the password and the extra content */ @@ -340,7 +307,7 @@ class PasswordCreationActivity : BasePgpActivity(), OpenPgpServiceConnection.OnB val gpgIdentifiers = gpgIdentifierFile.readLines() .filter { it.isNotBlank() } .map { line -> - parseGpgIdentifier(line) ?: run { + GpgIdentifier.fromString(line) ?: run { // The line being empty means this is most likely an empty `.gpg-id` file // we created. Skip the validation so we can make the user add a real ID. if (line.isEmpty()) return@run diff --git a/app/src/main/java/dev/msfjarvis/aps/util/crypto/GpgIdentifier.kt b/app/src/main/java/dev/msfjarvis/aps/util/crypto/GpgIdentifier.kt new file mode 100644 index 00000000..51b3b76b --- /dev/null +++ b/app/src/main/java/dev/msfjarvis/aps/util/crypto/GpgIdentifier.kt @@ -0,0 +1,38 @@ +package dev.msfjarvis.aps.util.crypto + +import me.msfjarvis.openpgpktx.util.OpenPgpUtils + +sealed class GpgIdentifier { + data class KeyId(val id: Long) : GpgIdentifier() + data class UserId(val email: String) : GpgIdentifier() + + companion object { + @OptIn(ExperimentalUnsignedTypes::class) + fun fromString(identifier: String): GpgIdentifier? { + if (identifier.isEmpty()) return null + // Match long key IDs: + // FF22334455667788 or 0xFF22334455667788 + val maybeLongKeyId = identifier.removePrefix("0x").takeIf { + it.matches("[a-fA-F0-9]{16}".toRegex()) + } + if (maybeLongKeyId != null) { + val keyId = maybeLongKeyId.toULong(16) + return KeyId(keyId.toLong()) + } + + // Match fingerprints: + // FF223344556677889900112233445566778899 or 0xFF223344556677889900112233445566778899 + val maybeFingerprint = identifier.removePrefix("0x").takeIf { + it.matches("[a-fA-F0-9]{40}".toRegex()) + } + if (maybeFingerprint != null) { + // Truncating to the long key ID is not a security issue since OpenKeychain only accepts + // non-ambiguous key IDs. + val keyId = maybeFingerprint.takeLast(16).toULong(16) + return KeyId(keyId.toLong()) + } + + return OpenPgpUtils.splitUserId(identifier).email?.let { UserId(it) } + } + } +} diff --git a/app/src/test/java/dev/msfjarvis/aps/util/crypto/GpgIdentifierTest.kt b/app/src/test/java/dev/msfjarvis/aps/util/crypto/GpgIdentifierTest.kt new file mode 100644 index 00000000..efb08f7b --- /dev/null +++ b/app/src/test/java/dev/msfjarvis/aps/util/crypto/GpgIdentifierTest.kt @@ -0,0 +1,38 @@ +package dev.msfjarvis.aps.util.crypto + +import kotlin.test.Ignore +import kotlin.test.assertNotNull +import kotlin.test.assertTrue +import kotlin.test.Test + +class GpgIdentifierTest { + + @Test + fun `parses hexadecimal key id without leading 0x`() { + val identifier = GpgIdentifier.fromString("79E8208280490C77") + assertNotNull(identifier) + assertTrue { identifier is GpgIdentifier.KeyId } + } + + @Test + fun `parses hexadecimal key id`() { + val identifier = GpgIdentifier.fromString("0x79E8208280490C77") + assertNotNull(identifier) + assertTrue { identifier is GpgIdentifier.KeyId } + } + + @Test + fun `parses email as user id`() { + val identifier = GpgIdentifier.fromString("aps@msfjarvis.dev") + assertNotNull(identifier) + assertTrue { identifier is GpgIdentifier.UserId } + } + + @Test + @Ignore("OpenKeychain can't yet handle these so we don't either") + fun `parses non-email user id`() { + val identifier = GpgIdentifier.fromString("john.doe") + assertNotNull(identifier) + assertTrue { identifier is GpgIdentifier.UserId } + } +} -- cgit v1.2.3