diff options
Diffstat (limited to 'crypto-pgpainless/src/test/kotlin/app')
6 files changed, 362 insertions, 0 deletions
diff --git a/crypto-pgpainless/src/test/kotlin/app/passwordstore/crypto/CryptoConstants.kt b/crypto-pgpainless/src/test/kotlin/app/passwordstore/crypto/CryptoConstants.kt new file mode 100644 index 00000000..d827e169 --- /dev/null +++ b/crypto-pgpainless/src/test/kotlin/app/passwordstore/crypto/CryptoConstants.kt @@ -0,0 +1,14 @@ +/* + * Copyright © 2014-2021 The Android Password Store Authors. All Rights Reserved. + * SPDX-License-Identifier: GPL-3.0-only + */ + +package app.passwordstore.crypto + +object CryptoConstants { + const val KEY_PASSPHRASE = "hunter2" + const val PLAIN_TEXT = "encryption worthy content" + const val KEY_NAME = "John Doe" + const val KEY_EMAIL = "john.doe@example.com" + const val KEY_ID = 0x08edf7567183ce27 +} diff --git a/crypto-pgpainless/src/test/kotlin/app/passwordstore/crypto/GpgIdentifierTest.kt b/crypto-pgpainless/src/test/kotlin/app/passwordstore/crypto/GpgIdentifierTest.kt new file mode 100644 index 00000000..009976a2 --- /dev/null +++ b/crypto-pgpainless/src/test/kotlin/app/passwordstore/crypto/GpgIdentifierTest.kt @@ -0,0 +1,41 @@ +/* + * Copyright © 2014-2022 The Android Password Store Authors. All Rights Reserved. + * SPDX-License-Identifier: GPL-3.0-only + */ + +package app.passwordstore.crypto + +import kotlin.test.Test +import kotlin.test.assertNotNull +import kotlin.test.assertTrue + +class GpgIdentifierTest { + + @Test + fun parseHexKeyIdWithout0xPrefix() { + val identifier = GpgIdentifier.fromString("79E8208280490C77") + assertNotNull(identifier) + assertTrue { identifier is GpgIdentifier.KeyId } + } + + @Test + fun parseHexKeyId() { + val identifier = GpgIdentifier.fromString("0x79E8208280490C77") + assertNotNull(identifier) + assertTrue { identifier is GpgIdentifier.KeyId } + } + + @Test + fun parseValidEmail() { + val identifier = GpgIdentifier.fromString("john.doe@example.org") + assertNotNull(identifier) + assertTrue { identifier is GpgIdentifier.UserId } + } + + @Test + fun parseEmailWithoutTLD() { + val identifier = GpgIdentifier.fromString("john.doe@example") + assertNotNull(identifier) + assertTrue { identifier is GpgIdentifier.UserId } + } +} diff --git a/crypto-pgpainless/src/test/kotlin/app/passwordstore/crypto/KeyUtilsTest.kt b/crypto-pgpainless/src/test/kotlin/app/passwordstore/crypto/KeyUtilsTest.kt new file mode 100644 index 00000000..209865ea --- /dev/null +++ b/crypto-pgpainless/src/test/kotlin/app/passwordstore/crypto/KeyUtilsTest.kt @@ -0,0 +1,24 @@ +package app.passwordstore.crypto + +import app.passwordstore.crypto.KeyUtils.tryGetId +import app.passwordstore.crypto.KeyUtils.tryParseKeyring +import app.passwordstore.crypto.TestUtils.getArmoredPrivateKeyWithMultipleIdentities +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertIs +import kotlin.test.assertNotNull +import org.bouncycastle.openpgp.PGPSecretKeyRing + +class KeyUtilsTest { + @Test + fun parseKeyWithMultipleIdentities() { + val key = PGPKey(getArmoredPrivateKeyWithMultipleIdentities()) + val keyring = tryParseKeyring(key) + assertNotNull(keyring) + assertIs<PGPSecretKeyRing>(keyring) + val keyId = tryGetId(key) + assertNotNull(keyId) + assertIs<GpgIdentifier.KeyId>(keyId) + assertEquals("b950ae2813841585", keyId.toString()) + } +} diff --git a/crypto-pgpainless/src/test/kotlin/app/passwordstore/crypto/PGPKeyManagerTest.kt b/crypto-pgpainless/src/test/kotlin/app/passwordstore/crypto/PGPKeyManagerTest.kt new file mode 100644 index 00000000..c446cee4 --- /dev/null +++ b/crypto-pgpainless/src/test/kotlin/app/passwordstore/crypto/PGPKeyManagerTest.kt @@ -0,0 +1,189 @@ +package app.passwordstore.crypto + +import app.passwordstore.crypto.GpgIdentifier.KeyId +import app.passwordstore.crypto.GpgIdentifier.UserId +import app.passwordstore.crypto.TestUtils.getArmoredPrivateKeyWithMultipleIdentities +import app.passwordstore.crypto.errors.KeyAlreadyExistsException +import app.passwordstore.crypto.errors.KeyNotFoundException +import app.passwordstore.crypto.errors.NoKeysAvailableException +import com.github.michaelbull.result.unwrap +import com.github.michaelbull.result.unwrapError +import java.io.File +import kotlin.test.AfterTest +import kotlin.test.BeforeTest +import kotlin.test.Test +import kotlin.test.assertContentEquals +import kotlin.test.assertEquals +import kotlin.test.assertIs +import kotlin.test.assertNotNull +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.StandardTestDispatcher +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.resetMain +import kotlinx.coroutines.test.runTest +import kotlinx.coroutines.test.setMain +import org.junit.Rule +import org.junit.rules.TemporaryFolder + +@OptIn(ExperimentalCoroutinesApi::class) +class PGPKeyManagerTest { + + @get:Rule val temporaryFolder: TemporaryFolder = TemporaryFolder() + private val filesDir by unsafeLazy { temporaryFolder.root } + private val keysDir by unsafeLazy { File(filesDir, PGPKeyManager.KEY_DIR_NAME) } + private val dispatcher = StandardTestDispatcher() + private val scope = TestScope(dispatcher) + private val keyManager by unsafeLazy { PGPKeyManager(filesDir.absolutePath, dispatcher) } + private val key = PGPKey(TestUtils.getArmoredPrivateKey()) + + private fun <T> unsafeLazy(initializer: () -> T) = + lazy(LazyThreadSafetyMode.NONE) { initializer.invoke() } + + @BeforeTest + fun setUp() { + Dispatchers.setMain(dispatcher) + } + + @AfterTest + fun tearDown() { + Dispatchers.resetMain() + } + + @Test + fun addKey() = + scope.runTest { + // Check if the key id returned is correct + val keyId = keyManager.getKeyId(keyManager.addKey(key).unwrap()) + assertEquals(KeyId(CryptoConstants.KEY_ID), keyId) + + // Check if the keys directory have one file + assertEquals(1, filesDir.list()?.size) + + // Check if the file name is correct + val keyFile = keysDir.listFiles()?.first() + assertEquals(keyFile?.name, "$keyId.${PGPKeyManager.KEY_EXTENSION}") + } + + @Test + fun addKeyWithoutReplaceFlag() = + scope.runTest { + // Check adding the keys twice + keyManager.addKey(key, false).unwrap() + val error = keyManager.addKey(key, false).unwrapError() + + assertIs<KeyAlreadyExistsException>(error) + } + + @Test + fun addKeyWithReplaceFlag() = + scope.runTest { + // Check adding the keys twice + keyManager.addKey(key, true).unwrap() + val keyId = keyManager.getKeyId(keyManager.addKey(key, true).unwrap()) + + assertEquals(KeyId(CryptoConstants.KEY_ID), keyId) + } + + @Test + fun removeKey() = + scope.runTest { + // Add key using KeyManager + keyManager.addKey(key).unwrap() + + // Check if the key id returned is correct + val keyId = keyManager.getKeyId(keyManager.removeKey(key).unwrap()) + assertEquals(KeyId(CryptoConstants.KEY_ID), keyId) + + // Check if the keys directory have 0 files + val keysDir = File(filesDir, PGPKeyManager.KEY_DIR_NAME) + assertEquals(0, keysDir.list()?.size) + } + + @Test + fun getKeyById() = + scope.runTest { + // Add key using KeyManager + keyManager.addKey(key).unwrap() + + val keyId = keyManager.getKeyId(key) + assertNotNull(keyId) + assertEquals(KeyId(CryptoConstants.KEY_ID), keyManager.getKeyId(key)) + + // Check returned key id matches the expected id and the created key id + val returnedKey = keyManager.getKeyById(keyId).unwrap() + assertEquals(keyManager.getKeyId(key), keyManager.getKeyId(returnedKey)) + } + + @Test + fun getKeyByFullUserId() = + scope.runTest { + keyManager.addKey(key).unwrap() + + val keyId = "${CryptoConstants.KEY_NAME} <${CryptoConstants.KEY_EMAIL}>" + val returnedKey = keyManager.getKeyById(UserId(keyId)).unwrap() + assertEquals(keyManager.getKeyId(key), keyManager.getKeyId(returnedKey)) + } + + @Test + fun getKeyByEmailUserId() = + scope.runTest { + keyManager.addKey(key).unwrap() + + val keyId = CryptoConstants.KEY_EMAIL + val returnedKey = keyManager.getKeyById(UserId(keyId)).unwrap() + assertEquals(keyManager.getKeyId(key), keyManager.getKeyId(returnedKey)) + } + + @Test + fun getNonExistentKey() = + scope.runTest { + // Add key using KeyManager + keyManager.addKey(key).unwrap() + + val keyId = KeyId(0x08edf7567183ce44) + + // Check returned key + val error = keyManager.getKeyById(keyId).unwrapError() + assertIs<KeyNotFoundException>(error) + assertEquals("No key found with id: $keyId", error.message) + } + + @Test + fun findNonExistentKey() = + scope.runTest { + // Check returned key + val error = keyManager.getKeyById(KeyId(0x08edf7567183ce44)).unwrapError() + assertIs<NoKeysAvailableException>(error) + assertEquals("No keys were found", error.message) + } + + @Test + fun getAllKeys() = + scope.runTest { + // Check if KeyManager returns no key + val noKeyList = keyManager.getAllKeys().unwrap() + assertEquals(0, noKeyList.size) + + // Add key using KeyManager + keyManager.addKey(key).unwrap() + keyManager.addKey(PGPKey(getArmoredPrivateKeyWithMultipleIdentities())).unwrap() + + // Check if KeyManager returns one key + val singleKeyList = keyManager.getAllKeys().unwrap() + assertEquals(2, singleKeyList.size) + } + + @Test + fun getMultipleIdentityKeyWithAllIdentities() { + scope.runTest { + val key = PGPKey(getArmoredPrivateKeyWithMultipleIdentities()) + keyManager.addKey(key).unwrap() + + val johnKey = keyManager.getKeyById(UserId("john@doe.org")).unwrap() + val janeKey = keyManager.getKeyById(UserId("jane@doe.org")).unwrap() + + assertContentEquals(johnKey.contents, janeKey.contents) + } + } +} diff --git a/crypto-pgpainless/src/test/kotlin/app/passwordstore/crypto/PGPainlessCryptoHandlerTest.kt b/crypto-pgpainless/src/test/kotlin/app/passwordstore/crypto/PGPainlessCryptoHandlerTest.kt new file mode 100644 index 00000000..60e8fb6e --- /dev/null +++ b/crypto-pgpainless/src/test/kotlin/app/passwordstore/crypto/PGPainlessCryptoHandlerTest.kt @@ -0,0 +1,78 @@ +/* + * Copyright © 2014-2021 The Android Password Store Authors. All Rights Reserved. + * SPDX-License-Identifier: GPL-3.0-only + */ + +package app.passwordstore.crypto + +import app.passwordstore.crypto.errors.IncorrectPassphraseException +import com.github.michaelbull.result.Err +import com.github.michaelbull.result.getError +import com.google.testing.junit.testparameterinjector.TestParameter +import com.google.testing.junit.testparameterinjector.TestParameterInjector +import java.io.ByteArrayOutputStream +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertFalse +import kotlin.test.assertIs +import kotlin.test.assertTrue +import org.junit.runner.RunWith + +@Suppress("Unused") // Test runner handles it internally +enum class EncryptionKey(val key: PGPKey) { + PUBLIC(PGPKey(TestUtils.getArmoredPublicKey())), + SECRET(PGPKey(TestUtils.getArmoredPrivateKey())), +} + +@RunWith(TestParameterInjector::class) +class PGPainlessCryptoHandlerTest { + + @TestParameter private lateinit var encryptionKey: EncryptionKey + private val cryptoHandler = PGPainlessCryptoHandler() + private val privateKey = PGPKey(TestUtils.getArmoredPrivateKey()) + + @Test + fun encryptAndDecrypt() { + val ciphertextStream = ByteArrayOutputStream() + cryptoHandler.encrypt( + listOf(encryptionKey.key), + CryptoConstants.PLAIN_TEXT.byteInputStream(Charsets.UTF_8), + ciphertextStream, + ) + val plaintextStream = ByteArrayOutputStream() + cryptoHandler.decrypt( + privateKey, + CryptoConstants.KEY_PASSPHRASE, + ciphertextStream.toByteArray().inputStream(), + plaintextStream, + ) + assertEquals(CryptoConstants.PLAIN_TEXT, plaintextStream.toString(Charsets.UTF_8)) + } + + @Test + fun decryptWithWrongPassphrase() { + val ciphertextStream = ByteArrayOutputStream() + cryptoHandler.encrypt( + listOf(encryptionKey.key), + CryptoConstants.PLAIN_TEXT.byteInputStream(Charsets.UTF_8), + ciphertextStream, + ) + val plaintextStream = ByteArrayOutputStream() + val result = + cryptoHandler.decrypt( + privateKey, + "very incorrect passphrase", + ciphertextStream.toByteArray().inputStream(), + plaintextStream, + ) + assertIs<Err<Throwable>>(result) + assertIs<IncorrectPassphraseException>(result.getError()) + } + + @Test + fun canHandleFiltersFormats() { + assertFalse { cryptoHandler.canHandle("example.com") } + assertTrue { cryptoHandler.canHandle("example.com.gpg") } + assertFalse { cryptoHandler.canHandle("example.com.asc") } + } +} diff --git a/crypto-pgpainless/src/test/kotlin/app/passwordstore/crypto/TestUtils.kt b/crypto-pgpainless/src/test/kotlin/app/passwordstore/crypto/TestUtils.kt new file mode 100644 index 00000000..1e5aea8c --- /dev/null +++ b/crypto-pgpainless/src/test/kotlin/app/passwordstore/crypto/TestUtils.kt @@ -0,0 +1,16 @@ +/* + * Copyright © 2014-2021 The Android Password Store Authors. All Rights Reserved. + * SPDX-License-Identifier: GPL-3.0-only + */ +@file:Suppress("RECEIVER_NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS") + +package app.passwordstore.crypto + +object TestUtils { + fun getArmoredPrivateKey() = this::class.java.classLoader.getResource("private_key").readBytes() + fun getArmoredPublicKey() = this::class.java.classLoader.getResource("public_key").readBytes() + fun getArmoredPrivateKeyWithMultipleIdentities() = + this::class.java.classLoader.getResource("private_key_multiple_identities").readBytes() + fun getArmoredPublicKeyWithMultipleIdentities() = + this::class.java.classLoader.getResource("public_key_multiple_identities").readBytes() +} |