aboutsummaryrefslogtreecommitdiff
path: root/crypto-pgpainless
diff options
context:
space:
mode:
authorHarsh Shandilya <me@msfjarvis.dev>2023-08-10 03:21:47 +0530
committerHarsh Shandilya <me@msfjarvis.dev>2023-08-10 03:31:08 +0530
commit959a56d7ffbc20db60721d7a09f399c8bdefe07e (patch)
tree0d9b14e0bb770fd6da2e04295f2c22c087142439 /crypto-pgpainless
parentefef72b6327c8e683c8844146e23d12104f12dd1 (diff)
refactor: un-flatten module structure
Diffstat (limited to 'crypto-pgpainless')
-rw-r--r--crypto-pgpainless/build.gradle.kts18
-rw-r--r--crypto-pgpainless/lint-baseline.xml4
-rw-r--r--crypto-pgpainless/src/main/kotlin/app/passwordstore/crypto/KeyUtils.kt52
-rw-r--r--crypto-pgpainless/src/main/kotlin/app/passwordstore/crypto/PGPDecryptOptions.kt21
-rw-r--r--crypto-pgpainless/src/main/kotlin/app/passwordstore/crypto/PGPEncryptOptions.kt35
-rw-r--r--crypto-pgpainless/src/main/kotlin/app/passwordstore/crypto/PGPIdentifier.kt128
-rw-r--r--crypto-pgpainless/src/main/kotlin/app/passwordstore/crypto/PGPKey.kt12
-rw-r--r--crypto-pgpainless/src/main/kotlin/app/passwordstore/crypto/PGPKeyManager.kt154
-rw-r--r--crypto-pgpainless/src/main/kotlin/app/passwordstore/crypto/PGPainlessCryptoHandler.kt139
-rw-r--r--crypto-pgpainless/src/test/kotlin/app/passwordstore/crypto/CryptoConstants.kt14
-rw-r--r--crypto-pgpainless/src/test/kotlin/app/passwordstore/crypto/KeyUtilsTest.kt35
-rw-r--r--crypto-pgpainless/src/test/kotlin/app/passwordstore/crypto/PGPIdentifierTest.kt41
-rw-r--r--crypto-pgpainless/src/test/kotlin/app/passwordstore/crypto/PGPKeyManagerTest.kt236
-rw-r--r--crypto-pgpainless/src/test/kotlin/app/passwordstore/crypto/PGPainlessCryptoHandlerTest.kt148
-rw-r--r--crypto-pgpainless/src/test/kotlin/app/passwordstore/crypto/TestUtils.kt32
-rw-r--r--crypto-pgpainless/src/test/resources/aead_pub51
-rw-r--r--crypto-pgpainless/src/test/resources/aead_sec107
-rw-r--r--crypto-pgpainless/src/test/resources/alice_owner@example_com16
-rw-r--r--crypto-pgpainless/src/test/resources/bobby_owner@example_com16
-rw-r--r--crypto-pgpainless/src/test/resources/public_key21
-rw-r--r--crypto-pgpainless/src/test/resources/public_key_multiple_identities51
-rw-r--r--crypto-pgpainless/src/test/resources/secret_key26
-rw-r--r--crypto-pgpainless/src/test/resources/secret_key_multiple_identities93
23 files changed, 0 insertions, 1450 deletions
diff --git a/crypto-pgpainless/build.gradle.kts b/crypto-pgpainless/build.gradle.kts
deleted file mode 100644
index 22363f23..00000000
--- a/crypto-pgpainless/build.gradle.kts
+++ /dev/null
@@ -1,18 +0,0 @@
-/*
- * Copyright © 2014-2021 The Android Password Store Authors. All Rights Reserved.
- * SPDX-License-Identifier: GPL-3.0-only
- */
-plugins { id("com.github.android-password-store.kotlin-jvm-library") }
-
-dependencies {
- api(projects.cryptoCommon)
- implementation(projects.coroutineUtils)
- implementation(libs.androidx.annotation)
- implementation(libs.dagger.hilt.core)
- implementation(libs.kotlinx.coroutines.core)
- implementation(libs.thirdparty.kotlinResult)
- implementation(libs.thirdparty.pgpainless)
- testImplementation(libs.bundles.testDependencies)
- testImplementation(libs.kotlinx.coroutines.test)
- testImplementation(libs.testing.testparameterinjector)
-}
diff --git a/crypto-pgpainless/lint-baseline.xml b/crypto-pgpainless/lint-baseline.xml
deleted file mode 100644
index 2ed8a3bb..00000000
--- a/crypto-pgpainless/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="6" by="lint 8.2.0-alpha14" type="baseline" client="gradle" dependencies="false" name="AGP (8.2.0-alpha14)" variant="all" version="8.2.0-alpha14">
-
-</issues>
diff --git a/crypto-pgpainless/src/main/kotlin/app/passwordstore/crypto/KeyUtils.kt b/crypto-pgpainless/src/main/kotlin/app/passwordstore/crypto/KeyUtils.kt
deleted file mode 100644
index 5b23dc18..00000000
--- a/crypto-pgpainless/src/main/kotlin/app/passwordstore/crypto/KeyUtils.kt
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * Copyright © 2014-2022 The Android Password Store Authors. All Rights Reserved.
- * SPDX-License-Identifier: GPL-3.0-only
- */
-
-package app.passwordstore.crypto
-
-import app.passwordstore.crypto.PGPIdentifier.KeyId
-import app.passwordstore.crypto.PGPIdentifier.UserId
-import com.github.michaelbull.result.get
-import com.github.michaelbull.result.runCatching
-import org.bouncycastle.openpgp.PGPKeyRing
-import org.pgpainless.PGPainless
-import org.pgpainless.key.parsing.KeyRingReader
-
-/** Utility methods to deal with [PGPKey]s. */
-public object KeyUtils {
- /**
- * Attempts to parse a [PGPKeyRing] from a given [key]. The key is first tried as a secret key and
- * then as a public one before the method gives up and returns null.
- */
- public fun tryParseKeyring(key: PGPKey): PGPKeyRing? {
- return runCatching { KeyRingReader.readKeyRing(key.contents.inputStream()) }.get()
- }
-
- /** Parses a [PGPKeyRing] from the given [key] and calculates its long key ID */
- public fun tryGetId(key: PGPKey): KeyId? {
- val keyRing = tryParseKeyring(key) ?: return null
- return KeyId(keyRing.publicKey.keyID)
- }
-
- /**
- * Attempts to parse the given [PGPKey] into a [PGPKeyRing] and obtains the [UserId] of the
- * corresponding public key.
- */
- public fun tryGetEmail(key: PGPKey): UserId? {
- val keyRing = tryParseKeyring(key) ?: return null
- return UserId(keyRing.publicKey.userIDs.next())
- }
-
- /**
- * Tests if the given [key] can be used for encryption, which is a bare minimum necessity for the
- * app.
- */
- public fun isKeyUsable(key: PGPKey): Boolean {
- return runCatching {
- val keyRing = tryParseKeyring(key) ?: return false
- PGPainless.inspectKeyRing(keyRing).isUsableForEncryption
- }
- .get() != null
- }
-}
diff --git a/crypto-pgpainless/src/main/kotlin/app/passwordstore/crypto/PGPDecryptOptions.kt b/crypto-pgpainless/src/main/kotlin/app/passwordstore/crypto/PGPDecryptOptions.kt
deleted file mode 100644
index 15ce92f0..00000000
--- a/crypto-pgpainless/src/main/kotlin/app/passwordstore/crypto/PGPDecryptOptions.kt
+++ /dev/null
@@ -1,21 +0,0 @@
-package app.passwordstore.crypto
-
-/** [CryptoOptions] implementation for PGPainless decrypt operations. */
-public class PGPDecryptOptions
-private constructor(
- private val values: Map<String, Boolean>,
-) : CryptoOptions {
-
- override fun isOptionEnabled(option: String): Boolean {
- return values.getOrDefault(option, false)
- }
-
- /** Implementation of a builder pattern for [PGPDecryptOptions]. */
- public class Builder {
-
- /** Build the final [PGPDecryptOptions] object. */
- public fun build(): PGPDecryptOptions {
- return PGPDecryptOptions(emptyMap())
- }
- }
-}
diff --git a/crypto-pgpainless/src/main/kotlin/app/passwordstore/crypto/PGPEncryptOptions.kt b/crypto-pgpainless/src/main/kotlin/app/passwordstore/crypto/PGPEncryptOptions.kt
deleted file mode 100644
index 90de6b51..00000000
--- a/crypto-pgpainless/src/main/kotlin/app/passwordstore/crypto/PGPEncryptOptions.kt
+++ /dev/null
@@ -1,35 +0,0 @@
-package app.passwordstore.crypto
-
-/** [CryptoOptions] implementation for PGPainless encrypt operations. */
-public class PGPEncryptOptions
-private constructor(
- private val values: Map<String, Boolean>,
-) : CryptoOptions {
-
- internal companion object {
- const val ASCII_ARMOR = "ASCII_ARMOR"
- }
-
- override fun isOptionEnabled(option: String): Boolean {
- return values.getOrDefault(option, false)
- }
-
- /** Implementation of a builder pattern for [PGPEncryptOptions]. */
- public class Builder {
- private val optionsMap = mutableMapOf<String, Boolean>()
-
- /**
- * Toggle whether the encryption operation output will be ASCII armored or in OpenPGP's binary
- * format.
- */
- public fun withAsciiArmor(enabled: Boolean): Builder {
- optionsMap[ASCII_ARMOR] = enabled
- return this
- }
-
- /** Build the final [PGPEncryptOptions] object. */
- public fun build(): PGPEncryptOptions {
- return PGPEncryptOptions(optionsMap)
- }
- }
-}
diff --git a/crypto-pgpainless/src/main/kotlin/app/passwordstore/crypto/PGPIdentifier.kt b/crypto-pgpainless/src/main/kotlin/app/passwordstore/crypto/PGPIdentifier.kt
deleted file mode 100644
index 98a1de2e..00000000
--- a/crypto-pgpainless/src/main/kotlin/app/passwordstore/crypto/PGPIdentifier.kt
+++ /dev/null
@@ -1,128 +0,0 @@
-/*
- * Copyright © 2014-2022 The Android Password Store Authors. All Rights Reserved.
- * SPDX-License-Identifier: GPL-3.0-only
- */
-
-package app.passwordstore.crypto
-
-import java.util.Locale
-import java.util.regex.Pattern
-
-/** Supertype for valid identifiers of PGP keys. */
-public sealed class PGPIdentifier {
-
- /** A [PGPIdentifier] that represents either a long key ID or a fingerprint. */
- public data class KeyId(val id: Long) : PGPIdentifier() {
- override fun toString(): String {
- return convertKeyIdToHex(id)
- }
-
- /** Convert a [Long] key ID to a formatted string. */
- private fun convertKeyIdToHex(keyId: Long): String {
- return convertKeyIdToHex32bit(keyId shr HEX_32_BIT_COUNT) + convertKeyIdToHex32bit(keyId)
- }
-
- /**
- * Converts [keyId] to an unsigned [Long] then uses [java.lang.Long.toHexString] to convert it
- * to a lowercase hex ID.
- */
- private fun convertKeyIdToHex32bit(keyId: Long): String {
- var hexString = java.lang.Long.toHexString(keyId and HEX_32_BITMASK).lowercase(Locale.ENGLISH)
- while (hexString.length < HEX_32_STRING_LENGTH) {
- hexString = "0$hexString"
- }
- return hexString
- }
- }
-
- /**
- * A [PGPIdentifier] that represents the textual name/email combination corresponding to a key.
- * Despite the [email] property in this class, the value is not guaranteed to be a valid email.
- */
- public data class UserId(val email: String) : PGPIdentifier() {
- override fun toString(): String {
- return email
- }
- }
-
- public companion object {
- private const val HEX_RADIX = 16
- private const val HEX_32_BIT_COUNT = 32
- private const val HEX_32_BITMASK = 0xffffffffL
- private const val HEX_32_STRING_LENGTH = 8
- private const val TRUNCATED_FINGERPRINT_LENGTH = 16
-
- /**
- * Attempts to parse an untyped String identifier into a concrete subtype of [PGPIdentifier].
- */
- @Suppress("ReturnCount")
- public fun fromString(identifier: String): PGPIdentifier? {
- if (identifier.isEmpty()) return null
- // Match long key IDs:
- // FF22334455667788 or 0xFF22334455667788
- val maybeLongKeyId =
- identifier.removePrefix("0x").takeIf { it.matches("[a-fA-F\\d]{16}".toRegex()) }
- if (maybeLongKeyId != null) {
- val keyId = maybeLongKeyId.toULong(HEX_RADIX)
- return KeyId(keyId.toLong())
- }
-
- // Match fingerprints:
- // FF223344556677889900112233445566778899 or 0xFF223344556677889900112233445566778899
- val maybeFingerprint =
- identifier.removePrefix("0x").takeIf { it.matches("[a-fA-F\\d]{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(TRUNCATED_FINGERPRINT_LENGTH).toULong(HEX_RADIX)
- return KeyId(keyId.toLong())
- }
-
- return splitUserId(identifier)?.let { UserId(it) }
- }
-
- private object UserIdRegex {
- val PATTERN: Pattern = Pattern.compile("^(.*?)(?: \\((.*)\\))?(?: <(.*)>)?$")
- const val NAME = 1
- const val EMAIL = 3
- }
-
- private object EmailRegex {
- val PATTERN: Pattern = Pattern.compile("^<?\"?([^<>\"]*@[^<>\"]*[.]?[^<>\"]*)\"?>?$")
- const val EMAIL = 1
- }
-
- /**
- * Takes a 'Name (Comment) <Email>' user ID in any of its permutations and attempts to extract
- * an email from it.
- */
- @Suppress("NestedBlockDepth")
- private fun splitUserId(userId: String): String? {
- if (userId.isNotEmpty()) {
- val matcher = UserIdRegex.PATTERN.matcher(userId)
- if (matcher.matches()) {
- var name =
- if (matcher.group(UserIdRegex.NAME)?.isEmpty() == true) null
- else matcher.group(UserIdRegex.NAME)
- var email = matcher.group(UserIdRegex.EMAIL)
- if (email != null && name != null) {
- val emailMatcher = EmailRegex.PATTERN.matcher(name)
- if (emailMatcher.matches() && email == emailMatcher.group(EmailRegex.EMAIL)) {
- email = emailMatcher.group(EmailRegex.EMAIL)
- name = null
- }
- }
- if (email == null && name != null) {
- val emailMatcher = EmailRegex.PATTERN.matcher(name)
- if (emailMatcher.matches()) {
- email = emailMatcher.group(EmailRegex.EMAIL)
- }
- }
- return email
- }
- }
- return null
- }
- }
-}
diff --git a/crypto-pgpainless/src/main/kotlin/app/passwordstore/crypto/PGPKey.kt b/crypto-pgpainless/src/main/kotlin/app/passwordstore/crypto/PGPKey.kt
deleted file mode 100644
index a33655d4..00000000
--- a/crypto-pgpainless/src/main/kotlin/app/passwordstore/crypto/PGPKey.kt
+++ /dev/null
@@ -1,12 +0,0 @@
-/*
- * Copyright © 2014-2021 The Android Password Store Authors. All Rights Reserved.
- * SPDX-License-Identifier: GPL-3.0-only
- */
-
-package app.passwordstore.crypto
-
-/**
- * A simple value class wrapping over a [ByteArray] that can represent a PGP key. The implementation
- * details of public/ private parts as well as identities are deferred to [PGPKeyManager].
- */
-@JvmInline public value class PGPKey(public val contents: ByteArray)
diff --git a/crypto-pgpainless/src/main/kotlin/app/passwordstore/crypto/PGPKeyManager.kt b/crypto-pgpainless/src/main/kotlin/app/passwordstore/crypto/PGPKeyManager.kt
deleted file mode 100644
index aed1acf2..00000000
--- a/crypto-pgpainless/src/main/kotlin/app/passwordstore/crypto/PGPKeyManager.kt
+++ /dev/null
@@ -1,154 +0,0 @@
-/*
- * Copyright © 2014-2021 The Android Password Store Authors. All Rights Reserved.
- * SPDX-License-Identifier: GPL-3.0-only
- */
-@file:Suppress("BlockingMethodInNonBlockingContext")
-
-package app.passwordstore.crypto
-
-import androidx.annotation.VisibleForTesting
-import app.passwordstore.crypto.KeyUtils.isKeyUsable
-import app.passwordstore.crypto.KeyUtils.tryGetId
-import app.passwordstore.crypto.KeyUtils.tryParseKeyring
-import app.passwordstore.crypto.errors.InvalidKeyException
-import app.passwordstore.crypto.errors.KeyAlreadyExistsException
-import app.passwordstore.crypto.errors.KeyDeletionFailedException
-import app.passwordstore.crypto.errors.KeyDirectoryUnavailableException
-import app.passwordstore.crypto.errors.KeyNotFoundException
-import app.passwordstore.crypto.errors.NoKeysAvailableException
-import app.passwordstore.crypto.errors.UnusableKeyException
-import app.passwordstore.util.coroutines.runSuspendCatching
-import com.github.michaelbull.result.Result
-import com.github.michaelbull.result.unwrap
-import java.io.File
-import javax.inject.Inject
-import kotlinx.coroutines.CoroutineDispatcher
-import kotlinx.coroutines.withContext
-import org.bouncycastle.openpgp.PGPPublicKeyRing
-import org.bouncycastle.openpgp.PGPSecretKeyRing
-import org.pgpainless.PGPainless
-import org.pgpainless.util.selection.userid.SelectUserId
-
-public class PGPKeyManager
-@Inject
-constructor(
- filesDir: String,
- private val dispatcher: CoroutineDispatcher,
-) : KeyManager<PGPKey, PGPIdentifier> {
-
- private val keyDir = File(filesDir, KEY_DIR_NAME)
-
- /** @see KeyManager.addKey */
- override suspend fun addKey(key: PGPKey, replace: Boolean): Result<PGPKey, Throwable> =
- withContext(dispatcher) {
- runSuspendCatching {
- if (!keyDirExists()) throw KeyDirectoryUnavailableException
- val incomingKeyRing = tryParseKeyring(key) ?: throw InvalidKeyException
- if (!isKeyUsable(key)) throw UnusableKeyException
- val keyFile = File(keyDir, "${tryGetId(key)}.$KEY_EXTENSION")
- if (keyFile.exists()) {
- val existingKeyBytes = keyFile.readBytes()
- val existingKeyRing =
- tryParseKeyring(PGPKey(existingKeyBytes)) ?: throw InvalidKeyException
- when {
- existingKeyRing is PGPPublicKeyRing && incomingKeyRing is PGPSecretKeyRing -> {
- keyFile.writeBytes(key.contents)
- return@runSuspendCatching key
- }
- existingKeyRing is PGPPublicKeyRing && incomingKeyRing is PGPPublicKeyRing -> {
- val updatedPublicKey = PGPainless.mergeCertificate(existingKeyRing, incomingKeyRing)
- val keyBytes = PGPainless.asciiArmor(updatedPublicKey).encodeToByteArray()
- keyFile.writeBytes(keyBytes)
- return@runSuspendCatching key
- }
- }
- // Check for replace flag first and if it is false, throw an error
- if (!replace)
- throw KeyAlreadyExistsException(
- tryGetId(key)?.toString() ?: "Failed to retrieve key ID"
- )
- if (!keyFile.delete()) throw KeyDeletionFailedException
- }
-
- keyFile.writeBytes(key.contents)
-
- key
- }
- }
-
- /** @see KeyManager.removeKey */
- override suspend fun removeKey(identifier: PGPIdentifier): Result<Unit, Throwable> =
- withContext(dispatcher) {
- runSuspendCatching {
- if (!keyDirExists()) throw KeyDirectoryUnavailableException
- val key = getKeyById(identifier).unwrap()
- val keyFile = File(keyDir, "${tryGetId(key)}.$KEY_EXTENSION")
- if (keyFile.exists()) {
- if (!keyFile.delete()) throw KeyDeletionFailedException
- }
- }
- }
-
- /** @see KeyManager.getKeyById */
- override suspend fun getKeyById(id: PGPIdentifier): Result<PGPKey, Throwable> =
- withContext(dispatcher) {
- runSuspendCatching {
- if (!keyDirExists()) throw KeyDirectoryUnavailableException
- val keyFiles = keyDir.listFiles()
- if (keyFiles.isNullOrEmpty()) throw NoKeysAvailableException
- val keys = keyFiles.map { file -> PGPKey(file.readBytes()) }
-
- val matchResult =
- when (id) {
- is PGPIdentifier.KeyId -> {
- val keyIdMatch =
- keys
- .map { key -> key to tryGetId(key) }
- .firstOrNull { (_, keyId) -> keyId?.id == id.id }
- keyIdMatch?.first
- }
- is PGPIdentifier.UserId -> {
- val selector = SelectUserId.byEmail(id.email)
- val userIdMatch =
- keys
- .map { key -> key to tryParseKeyring(key) }
- .firstOrNull { (_, keyRing) -> selector.firstMatch(keyRing) != null }
- userIdMatch?.first
- }
- }
-
- if (matchResult != null) {
- return@runSuspendCatching matchResult
- }
-
- throw KeyNotFoundException("$id")
- }
- }
-
- /** @see KeyManager.getAllKeys */
- override suspend fun getAllKeys(): Result<List<PGPKey>, Throwable> =
- withContext(dispatcher) {
- runSuspendCatching {
- if (!keyDirExists()) throw KeyDirectoryUnavailableException
- val keyFiles = keyDir.listFiles()
- if (keyFiles.isNullOrEmpty()) return@runSuspendCatching emptyList()
- keyFiles.map { keyFile -> PGPKey(keyFile.readBytes()) }.toList()
- }
- }
-
- /** @see KeyManager.getKeyById */
- override suspend fun getKeyId(key: PGPKey): PGPIdentifier? = tryGetId(key)
-
- /** Checks if [keyDir] exists and attempts to create it if not. */
- private fun keyDirExists(): Boolean {
- return keyDir.exists() || keyDir.mkdirs()
- }
-
- public companion object {
-
- @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
- internal const val KEY_DIR_NAME: String = "keys"
- @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
- internal const val KEY_EXTENSION: String = "key"
- }
-}
diff --git a/crypto-pgpainless/src/main/kotlin/app/passwordstore/crypto/PGPainlessCryptoHandler.kt b/crypto-pgpainless/src/main/kotlin/app/passwordstore/crypto/PGPainlessCryptoHandler.kt
deleted file mode 100644
index a7087acf..00000000
--- a/crypto-pgpainless/src/main/kotlin/app/passwordstore/crypto/PGPainlessCryptoHandler.kt
+++ /dev/null
@@ -1,139 +0,0 @@
-/*
- * 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.CryptoHandlerException
-import app.passwordstore.crypto.errors.IncorrectPassphraseException
-import app.passwordstore.crypto.errors.NoKeysProvidedException
-import app.passwordstore.crypto.errors.UnknownError
-import com.github.michaelbull.result.Result
-import com.github.michaelbull.result.mapError
-import com.github.michaelbull.result.runCatching
-import java.io.InputStream
-import java.io.OutputStream
-import javax.inject.Inject
-import org.bouncycastle.openpgp.PGPPublicKeyRing
-import org.bouncycastle.openpgp.PGPPublicKeyRingCollection
-import org.bouncycastle.openpgp.PGPSecretKeyRing
-import org.bouncycastle.openpgp.PGPSecretKeyRingCollection
-import org.bouncycastle.util.io.Streams
-import org.pgpainless.PGPainless
-import org.pgpainless.decryption_verification.ConsumerOptions
-import org.pgpainless.encryption_signing.EncryptionOptions
-import org.pgpainless.encryption_signing.ProducerOptions
-import org.pgpainless.exception.WrongPassphraseException
-import org.pgpainless.key.protection.SecretKeyRingProtector
-import org.pgpainless.util.Passphrase
-
-public class PGPainlessCryptoHandler @Inject constructor() :
- CryptoHandler<PGPKey, PGPEncryptOptions, PGPDecryptOptions> {
-
- /**
- * Decrypts the given [ciphertextStream] using [PGPainless] and writes the decrypted output to
- * [outputStream]. The provided [passphrase] is wrapped in a [SecretKeyRingProtector] and the
- * [keys] argument is defensively checked to ensure it has at least one key present.
- *
- * @see CryptoHandler.decrypt
- */
- public override fun decrypt(
- keys: List<PGPKey>,
- passphrase: String,
- ciphertextStream: InputStream,
- outputStream: OutputStream,
- options: PGPDecryptOptions,
- ): Result<Unit, CryptoHandlerException> =
- runCatching {
- if (keys.isEmpty()) {
- throw NoKeysProvidedException
- }
- val keyringCollection =
- keys
- .map { key -> PGPainless.readKeyRing().secretKeyRing(key.contents) }
- .run(::PGPSecretKeyRingCollection)
- val protector = SecretKeyRingProtector.unlockAnyKeyWith(Passphrase.fromPassword(passphrase))
- val decryptionStream =
- PGPainless.decryptAndOrVerify()
- .onInputStream(ciphertextStream)
- .withOptions(
- ConsumerOptions()
- .addDecryptionKeys(keyringCollection, protector)
- .addDecryptionPassphrase(Passphrase.fromPassword(passphrase))
- )
- Streams.pipeAll(decryptionStream, outputStream)
- decryptionStream.close()
- keyringCollection.forEach { keyRing ->
- check(decryptionStream.metadata.isEncryptedFor(keyRing)) {
- "Stream should be encrypted for ${keyRing.secretKey.keyID} but wasn't"
- }
- }
- return@runCatching
- }
- .mapError { error ->
- when (error) {
- is WrongPassphraseException -> IncorrectPassphraseException(error)
- is CryptoHandlerException -> error
- else -> UnknownError(error)
- }
- }
-
- /**
- * Encrypts the provided [plaintextStream] and writes the encrypted output to [outputStream]. The
- * [keys] argument is defensively checked to contain at least one key.
- *
- * @see CryptoHandler.encrypt
- */
- public override fun encrypt(
- keys: List<PGPKey>,
- plaintextStream: InputStream,
- outputStream: OutputStream,
- options: PGPEncryptOptions,
- ): Result<Unit, CryptoHandlerException> =
- runCatching {
- if (keys.isEmpty()) {
- throw NoKeysProvidedException
- }
- val publicKeyRings =
- keys.mapNotNull(KeyUtils::tryParseKeyring).mapNotNull { keyRing ->
- when (keyRing) {
- is PGPPublicKeyRing -> keyRing
- is PGPSecretKeyRing -> PGPainless.extractCertificate(keyRing)
- else -> null
- }
- }
- require(keys.size == publicKeyRings.size) {
- "Failed to parse all keys: ${keys.size} keys were provided but only ${publicKeyRings.size} were valid"
- }
- if (publicKeyRings.isEmpty()) {
- throw NoKeysProvidedException
- }
- val publicKeyRingCollection = PGPPublicKeyRingCollection(publicKeyRings)
- val encryptionOptions = EncryptionOptions().addRecipients(publicKeyRingCollection)
- val producerOptions =
- ProducerOptions.encrypt(encryptionOptions)
- .setAsciiArmor(options.isOptionEnabled(PGPEncryptOptions.ASCII_ARMOR))
- val encryptionStream =
- PGPainless.encryptAndOrSign().onOutputStream(outputStream).withOptions(producerOptions)
- Streams.pipeAll(plaintextStream, encryptionStream)
- encryptionStream.close()
- val result = encryptionStream.result
- publicKeyRingCollection.forEach { keyRing ->
- require(result.isEncryptedFor(keyRing)) {
- "Stream should be encrypted for ${keyRing.publicKey.keyID} but wasn't"
- }
- }
- }
- .mapError { error ->
- when (error) {
- is CryptoHandlerException -> error
- else -> UnknownError(error)
- }
- }
-
- /** Runs a naive check on the extension for the given [fileName] to check if it is a PGP file. */
- public override fun canHandle(fileName: String): Boolean {
- return fileName.substringAfterLast('.', "") == "gpg"
- }
-}
diff --git a/crypto-pgpainless/src/test/kotlin/app/passwordstore/crypto/CryptoConstants.kt b/crypto-pgpainless/src/test/kotlin/app/passwordstore/crypto/CryptoConstants.kt
deleted file mode 100644
index d827e169..00000000
--- a/crypto-pgpainless/src/test/kotlin/app/passwordstore/crypto/CryptoConstants.kt
+++ /dev/null
@@ -1,14 +0,0 @@
-/*
- * 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/KeyUtilsTest.kt b/crypto-pgpainless/src/test/kotlin/app/passwordstore/crypto/KeyUtilsTest.kt
deleted file mode 100644
index a0f84402..00000000
--- a/crypto-pgpainless/src/test/kotlin/app/passwordstore/crypto/KeyUtilsTest.kt
+++ /dev/null
@@ -1,35 +0,0 @@
-package app.passwordstore.crypto
-
-import app.passwordstore.crypto.KeyUtils.isKeyUsable
-import app.passwordstore.crypto.KeyUtils.tryGetId
-import app.passwordstore.crypto.KeyUtils.tryParseKeyring
-import app.passwordstore.crypto.TestUtils.AllKeys
-import app.passwordstore.crypto.TestUtils.getArmoredSecretKeyWithMultipleIdentities
-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(getArmoredSecretKeyWithMultipleIdentities())
- val keyring = tryParseKeyring(key)
- assertNotNull(keyring)
- assertIs<PGPSecretKeyRing>(keyring)
- val keyId = tryGetId(key)
- assertNotNull(keyId)
- assertIs<PGPIdentifier.KeyId>(keyId)
- assertEquals("b950ae2813841585", keyId.toString())
- }
-
- @Test
- fun isKeyUsable() {
- val params = AllKeys.entries.map { it to (it != AllKeys.AEAD_PUB && it != AllKeys.AEAD_SEC) }
- params.forEach { (allKeys, isUsable) ->
- val key = PGPKey(allKeys.keyMaterial)
- assertEquals(isUsable, isKeyUsable(key), "${allKeys.name} failed expectation:")
- }
- }
-}
diff --git a/crypto-pgpainless/src/test/kotlin/app/passwordstore/crypto/PGPIdentifierTest.kt b/crypto-pgpainless/src/test/kotlin/app/passwordstore/crypto/PGPIdentifierTest.kt
deleted file mode 100644
index efc6e0ba..00000000
--- a/crypto-pgpainless/src/test/kotlin/app/passwordstore/crypto/PGPIdentifierTest.kt
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * 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 PGPIdentifierTest {
-
- @Test
- fun parseHexKeyIdWithout0xPrefix() {
- val identifier = PGPIdentifier.fromString("79E8208280490C77")
- assertNotNull(identifier)
- assertTrue { identifier is PGPIdentifier.KeyId }
- }
-
- @Test
- fun parseHexKeyId() {
- val identifier = PGPIdentifier.fromString("0x79E8208280490C77")
- assertNotNull(identifier)
- assertTrue { identifier is PGPIdentifier.KeyId }
- }
-
- @Test
- fun parseValidEmail() {
- val identifier = PGPIdentifier.fromString("john.doe@example.org")
- assertNotNull(identifier)
- assertTrue { identifier is PGPIdentifier.UserId }
- }
-
- @Test
- fun parseEmailWithoutTLD() {
- val identifier = PGPIdentifier.fromString("john.doe@example")
- assertNotNull(identifier)
- assertTrue { identifier is PGPIdentifier.UserId }
- }
-}
diff --git a/crypto-pgpainless/src/test/kotlin/app/passwordstore/crypto/PGPKeyManagerTest.kt b/crypto-pgpainless/src/test/kotlin/app/passwordstore/crypto/PGPKeyManagerTest.kt
deleted file mode 100644
index 85cf8e1b..00000000
--- a/crypto-pgpainless/src/test/kotlin/app/passwordstore/crypto/PGPKeyManagerTest.kt
+++ /dev/null
@@ -1,236 +0,0 @@
-package app.passwordstore.crypto
-
-import app.passwordstore.crypto.KeyUtils.tryGetId
-import app.passwordstore.crypto.PGPIdentifier.KeyId
-import app.passwordstore.crypto.PGPIdentifier.UserId
-import app.passwordstore.crypto.errors.KeyAlreadyExistsException
-import app.passwordstore.crypto.errors.KeyNotFoundException
-import app.passwordstore.crypto.errors.NoKeysAvailableException
-import app.passwordstore.crypto.errors.UnusableKeyException
-import com.github.michaelbull.result.Err
-import com.github.michaelbull.result.Ok
-import com.github.michaelbull.result.unwrap
-import com.github.michaelbull.result.unwrapError
-import java.io.File
-import kotlin.test.Test
-import kotlin.test.assertContentEquals
-import kotlin.test.assertEquals
-import kotlin.test.assertIs
-import kotlin.test.assertNotEquals
-import kotlin.test.assertNotNull
-import kotlinx.coroutines.test.StandardTestDispatcher
-import kotlinx.coroutines.test.TestScope
-import kotlinx.coroutines.test.runTest
-import org.junit.Rule
-import org.junit.rules.TemporaryFolder
-
-class PGPKeyManagerTest {
-
- @get:Rule val temporaryFolder: TemporaryFolder = TemporaryFolder()
- private val dispatcher = StandardTestDispatcher()
- private val scope = TestScope(dispatcher)
- private val filesDir by unsafeLazy { temporaryFolder.root }
- private val keysDir by unsafeLazy { File(filesDir, PGPKeyManager.KEY_DIR_NAME) }
- private val keyManager by unsafeLazy { PGPKeyManager(filesDir.absolutePath, dispatcher) }
- private val secretKey = PGPKey(TestUtils.getArmoredSecretKey())
- private val publicKey = PGPKey(TestUtils.getArmoredPublicKey())
-
- private fun <T> unsafeLazy(initializer: () -> T) =
- lazy(LazyThreadSafetyMode.NONE) { initializer.invoke() }
-
- @Test
- fun addKey() =
- runTest(dispatcher) {
- // Check if the key id returned is correct
- val keyId = keyManager.getKeyId(keyManager.addKey(secretKey).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() =
- runTest(dispatcher) {
- // Check adding the keys twice
- keyManager.addKey(secretKey, false).unwrap()
- val error = keyManager.addKey(secretKey, false).unwrapError()
-
- assertIs<KeyAlreadyExistsException>(error)
- }
-
- @Test
- fun addKeyWithReplaceFlag() =
- runTest(dispatcher) {
- // Check adding the keys twice
- keyManager.addKey(secretKey, true).unwrap()
- val keyId = keyManager.getKeyId(keyManager.addKey(secretKey, true).unwrap())
-
- assertEquals(KeyId(CryptoConstants.KEY_ID), keyId)
- }
-
- @Test
- fun addKeyWithUnusableKey() =
- runTest(dispatcher) {
- val error = keyManager.addKey(PGPKey(TestUtils.getAEADSecretKey())).unwrapError()
- assertEquals(UnusableKeyException, error)
- }
-
- @Test
- fun removeKey() =
- runTest(dispatcher) {
- // Add key using KeyManager
- keyManager.addKey(secretKey).unwrap()
- // Remove key
- keyManager.removeKey(tryGetId(secretKey)!!).unwrap()
- // Check that no keys remain
- val keys = keyManager.getAllKeys().unwrap()
- assertEquals(0, keys.size)
- }
-
- @Test
- fun getKeyById() =
- runTest(dispatcher) {
- // Add key using KeyManager
- keyManager.addKey(secretKey).unwrap()
- val keyId = keyManager.getKeyId(secretKey)
- assertNotNull(keyId)
- assertEquals(KeyId(CryptoConstants.KEY_ID), keyManager.getKeyId(secretKey))
- // Check returned key id matches the expected id and the created key id
- val returnedKey = keyManager.getKeyById(keyId).unwrap()
- assertEquals(keyManager.getKeyId(secretKey), keyManager.getKeyId(returnedKey))
- }
-
- @Test
- fun getKeyByFullUserId() =
- runTest(dispatcher) {
- keyManager.addKey(secretKey).unwrap()
- val keyId = "${CryptoConstants.KEY_NAME} <${CryptoConstants.KEY_EMAIL}>"
- val returnedKey = keyManager.getKeyById(UserId(keyId)).unwrap()
- assertEquals(keyManager.getKeyId(secretKey), keyManager.getKeyId(returnedKey))
- }
-
- @Test
- fun getKeyByEmailUserId() =
- runTest(dispatcher) {
- keyManager.addKey(secretKey).unwrap()
- val keyId = CryptoConstants.KEY_EMAIL
- val returnedKey = keyManager.getKeyById(UserId(keyId)).unwrap()
- assertEquals(keyManager.getKeyId(secretKey), keyManager.getKeyId(returnedKey))
- }
-
- @Test
- fun getNonExistentKey() =
- runTest(dispatcher) {
- // Add key using KeyManager
- keyManager.addKey(secretKey).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() =
- runTest(dispatcher) {
- // Check returned key
- val error = keyManager.getKeyById(KeyId(0x08edf7567183ce44)).unwrapError()
- assertIs<NoKeysAvailableException>(error)
- assertEquals("No keys were found", error.message)
- }
-
- @Test
- fun getAllKeys() =
- runTest(dispatcher) {
- // Check if KeyManager returns no key
- val noKeyList = keyManager.getAllKeys().unwrap()
- assertEquals(0, noKeyList.size)
- // Add key using KeyManager
- keyManager.addKey(secretKey).unwrap()
- keyManager.addKey(PGPKey(TestUtils.getArmoredSecretKeyWithMultipleIdentities())).unwrap()
- // Check if KeyManager returns one key
- val singleKeyList = keyManager.getAllKeys().unwrap()
- assertEquals(2, singleKeyList.size)
- }
-
- @Test
- fun getMultipleIdentityKeyWithAllIdentities() =
- runTest(dispatcher) {
- val key = PGPKey(TestUtils.getArmoredSecretKeyWithMultipleIdentities())
- 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)
- }
-
- @Test
- fun replaceSecretKeyWithPublicKey() =
- runTest(dispatcher) {
- assertIs<Ok<PGPKey>>(keyManager.addKey(secretKey))
- assertIs<Err<KeyAlreadyExistsException>>(keyManager.addKey(publicKey))
- }
-
- @Test
- fun replacePublicKeyWithSecretKey() =
- runTest(dispatcher) {
- assertIs<Ok<PGPKey>>(keyManager.addKey(publicKey))
- assertIs<Ok<PGPKey>>(keyManager.addKey(secretKey))
- }
-
- @Test
- fun replacePublicKeyWithPublicKey() =
- runTest(dispatcher) {
- assertIs<Ok<PGPKey>>(keyManager.addKey(publicKey))
- assertIs<Ok<PGPKey>>(keyManager.addKey(publicKey))
- val allKeys = keyManager.getAllKeys()
- assertIs<Ok<List<PGPKey>>>(allKeys)
- assertEquals(1, allKeys.value.size)
- val key = allKeys.value[0]
- assertContentEquals(publicKey.contents, key.contents)
- }
-
- @Test
- fun replaceSecretKeyWithSecretKey() =
- runTest(dispatcher) {
- assertIs<Ok<PGPKey>>(keyManager.addKey(secretKey))
- assertIs<Err<KeyAlreadyExistsException>>(keyManager.addKey(secretKey))
- }
-
- @Test
- fun addMultipleKeysWithSameEmail() =
- runTest(dispatcher) {
- val alice =
- PGPKey(this::class.java.classLoader.getResource("alice_owner@example_com")!!.readBytes())
- val bobby =
- PGPKey(this::class.java.classLoader.getResource("bobby_owner@example_com")!!.readBytes())
- assertIs<Ok<PGPKey>>(keyManager.addKey(alice))
- assertIs<Ok<PGPKey>>(keyManager.addKey(bobby))
-
- keyManager.getAllKeys().apply {
- assertIs<Ok<List<PGPKey>>>(this)
- assertEquals(2, this.value.size)
- }
- val longKeyIds =
- arrayOf(
- KeyId(-7087927403306410599), // Alice
- KeyId(-961222705095032109), // Bobby
- )
- val userIds =
- arrayOf(
- UserId("Alice <owner@example.com>"),
- UserId("Bobby <owner@example.com>"),
- )
-
- for (idCollection in arrayOf(longKeyIds, userIds)) {
- val alice1 = keyManager.getKeyById(idCollection[0])
- val bobby1 = keyManager.getKeyById(idCollection[1])
- assertIs<Ok<PGPKey>>(alice1)
- assertIs<Ok<PGPKey>>(bobby1)
- assertNotEquals(alice1.value.contents, bobby1.value.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
deleted file mode 100644
index 8bf6ba1e..00000000
--- a/crypto-pgpainless/src/test/kotlin/app/passwordstore/crypto/PGPainlessCryptoHandlerTest.kt
+++ /dev/null
@@ -1,148 +0,0 @@
-/*
- * Copyright © 2014-2021 The Android Password Store Authors. All Rights Reserved.
- * SPDX-License-Identifier: GPL-3.0-only
- */
-@file:Suppress("JUnitMalformedDeclaration") // The test runner takes care of it
-
-package app.passwordstore.crypto
-
-import app.passwordstore.crypto.CryptoConstants.KEY_PASSPHRASE
-import app.passwordstore.crypto.CryptoConstants.PLAIN_TEXT
-import app.passwordstore.crypto.errors.IncorrectPassphraseException
-import com.github.michaelbull.result.Err
-import com.github.michaelbull.result.Ok
-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.assertContains
-import kotlin.test.assertEquals
-import kotlin.test.assertFalse
-import kotlin.test.assertIs
-import kotlin.test.assertTrue
-import org.junit.runner.RunWith
-import org.pgpainless.PGPainless
-import org.pgpainless.decryption_verification.MessageInspector
-
-@Suppress("Unused") // Test runner handles it internally
-enum class EncryptionKey(val keySet: List<PGPKey>) {
- PUBLIC(listOf(PGPKey(TestUtils.getArmoredPublicKey()))),
- SECRET(listOf(PGPKey(TestUtils.getArmoredSecretKey()))),
- ALL(listOf(PGPKey(TestUtils.getArmoredPublicKey()), PGPKey(TestUtils.getArmoredSecretKey()))),
-}
-
-@RunWith(TestParameterInjector::class)
-class PGPainlessCryptoHandlerTest {
-
- private val cryptoHandler = PGPainlessCryptoHandler()
- private val secretKey = PGPKey(TestUtils.getArmoredSecretKey())
-
- @Test
- fun encryptAndDecrypt(@TestParameter encryptionKey: EncryptionKey) {
- val ciphertextStream = ByteArrayOutputStream()
- val encryptRes =
- cryptoHandler.encrypt(
- encryptionKey.keySet,
- PLAIN_TEXT.byteInputStream(Charsets.UTF_8),
- ciphertextStream,
- PGPEncryptOptions.Builder().build(),
- )
- assertIs<Ok<Unit>>(encryptRes)
- val plaintextStream = ByteArrayOutputStream()
- val decryptRes =
- cryptoHandler.decrypt(
- listOf(secretKey),
- KEY_PASSPHRASE,
- ciphertextStream.toByteArray().inputStream(),
- plaintextStream,
- PGPDecryptOptions.Builder().build(),
- )
- assertIs<Ok<Unit>>(decryptRes)
- assertEquals(PLAIN_TEXT, plaintextStream.toString(Charsets.UTF_8))
- }
-
- @Test
- fun decryptWithWrongPassphrase(@TestParameter encryptionKey: EncryptionKey) {
- val ciphertextStream = ByteArrayOutputStream()
- val encryptRes =
- cryptoHandler.encrypt(
- encryptionKey.keySet,
- PLAIN_TEXT.byteInputStream(Charsets.UTF_8),
- ciphertextStream,
- PGPEncryptOptions.Builder().build(),
- )
- assertIs<Ok<Unit>>(encryptRes)
- val plaintextStream = ByteArrayOutputStream()
- val result =
- cryptoHandler.decrypt(
- listOf(secretKey),
- "very incorrect passphrase",
- ciphertextStream.toByteArray().inputStream(),
- plaintextStream,
- PGPDecryptOptions.Builder().build(),
- )
- assertIs<Err<Throwable>>(result)
- assertIs<IncorrectPassphraseException>(result.getError())
- }
-
- @Test
- fun encryptAsciiArmored(@TestParameter encryptionKey: EncryptionKey) {
- val ciphertextStream = ByteArrayOutputStream()
- val encryptRes =
- cryptoHandler.encrypt(
- encryptionKey.keySet,
- PLAIN_TEXT.byteInputStream(Charsets.UTF_8),
- ciphertextStream,
- PGPEncryptOptions.Builder().withAsciiArmor(true).build(),
- )
- assertIs<Ok<Unit>>(encryptRes)
- val ciphertext = ciphertextStream.toString(Charsets.UTF_8)
- assertContains(ciphertext, "Version: PGPainless")
- assertContains(ciphertext, "-----BEGIN PGP MESSAGE-----")
- assertContains(ciphertext, "-----END PGP MESSAGE-----")
- }
-
- @Test
- fun encryptMultiple() {
- val alice =
- PGPainless.generateKeyRing().modernKeyRing("Alice <owner@example.com>", KEY_PASSPHRASE)
- val bob = PGPainless.generateKeyRing().modernKeyRing("Bob <owner@example.com>", KEY_PASSPHRASE)
- val aliceKey = PGPKey(PGPainless.asciiArmor(alice).encodeToByteArray())
- val bobKey = PGPKey(PGPainless.asciiArmor(bob).encodeToByteArray())
- val ciphertextStream = ByteArrayOutputStream()
- val encryptRes =
- cryptoHandler.encrypt(
- listOf(aliceKey, bobKey),
- PLAIN_TEXT.byteInputStream(Charsets.UTF_8),
- ciphertextStream,
- PGPEncryptOptions.Builder().withAsciiArmor(true).build(),
- )
- assertIs<Ok<Unit>>(encryptRes)
- val message = ciphertextStream.toByteArray().decodeToString()
- val info = MessageInspector.determineEncryptionInfoForMessage(message)
- assertTrue(info.isEncrypted)
- assertEquals(2, info.keyIds.size)
- assertFalse(info.isSignedOnly)
- for (key in listOf(aliceKey, bobKey)) {
- val ciphertextStreamCopy = message.byteInputStream()
- val plaintextStream = ByteArrayOutputStream()
- val res =
- cryptoHandler.decrypt(
- listOf(key),
- KEY_PASSPHRASE,
- ciphertextStreamCopy,
- plaintextStream,
- PGPDecryptOptions.Builder().build(),
- )
- assertIs<Ok<Unit>>(res)
- }
- }
-
- @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
deleted file mode 100644
index 90b98ac9..00000000
--- a/crypto-pgpainless/src/test/kotlin/app/passwordstore/crypto/TestUtils.kt
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * 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 getArmoredSecretKey() = this::class.java.classLoader.getResource("secret_key").readBytes()
-
- fun getArmoredPublicKey() = this::class.java.classLoader.getResource("public_key").readBytes()
-
- fun getArmoredSecretKeyWithMultipleIdentities() =
- this::class.java.classLoader.getResource("secret_key_multiple_identities").readBytes()
-
- fun getArmoredPublicKeyWithMultipleIdentities() =
- this::class.java.classLoader.getResource("public_key_multiple_identities").readBytes()
-
- fun getAEADPublicKey() = this::class.java.classLoader.getResource("aead_pub").readBytes()
-
- fun getAEADSecretKey() = this::class.java.classLoader.getResource("aead_sec").readBytes()
-
- enum class AllKeys(val keyMaterial: ByteArray) {
- ARMORED_SEC(getArmoredSecretKey()),
- ARMORED_PUB(getArmoredPublicKey()),
- MULTIPLE_IDENTITIES_SEC(getArmoredSecretKeyWithMultipleIdentities()),
- MULTIPLE_IDENTITIES_PUB(getArmoredPublicKeyWithMultipleIdentities()),
- AEAD_SEC(getAEADSecretKey()),
- AEAD_PUB(getAEADPublicKey()),
- }
-}
diff --git a/crypto-pgpainless/src/test/resources/aead_pub b/crypto-pgpainless/src/test/resources/aead_pub
deleted file mode 100644
index f6ae1e82..00000000
--- a/crypto-pgpainless/src/test/resources/aead_pub
+++ /dev/null
@@ -1,51 +0,0 @@
------BEGIN PGP PUBLIC KEY BLOCK-----
-
-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
-=lwhh
------END PGP PUBLIC KEY BLOCK-----
diff --git a/crypto-pgpainless/src/test/resources/aead_sec b/crypto-pgpainless/src/test/resources/aead_sec
deleted file mode 100644
index 8e13ac0e..00000000
--- a/crypto-pgpainless/src/test/resources/aead_sec
+++ /dev/null
@@ -1,107 +0,0 @@
------BEGIN PGP PRIVATE KEY BLOCK-----
-
-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
-iHcmZE4pREmRPz9CinzsQXCm5yqJkucSfXtqn4c=
-=m5E4
------END PGP PRIVATE KEY BLOCK-----
diff --git a/crypto-pgpainless/src/test/resources/alice_owner@example_com b/crypto-pgpainless/src/test/resources/alice_owner@example_com
deleted file mode 100644
index d2612b0d..00000000
--- a/crypto-pgpainless/src/test/resources/alice_owner@example_com
+++ /dev/null
@@ -1,16 +0,0 @@
------BEGIN PGP PRIVATE KEY BLOCK-----
-
-lIYEY2to0BYJKwYBBAHaRw8BAQdAuI+Z2XyvQv6qBnA06ZAoKArgfMXFN783oYWl
-Vh1DqpT+BwMCSqjdffS3e+X/Kfnv30tVSSrb8j2nX2C+P0ODVvS7xWs8MG8TN33d
-NJXWUkfct513yADC520EL2KpXPU6GThIxsYmxBXPdyBb3CAiQQDJWLQZQWxpY2Ug
-PG93bmVyQGV4YW1wbGUuY29tPoiQBBMWCAA4FiEEMqSK1ESF5td0+gNunaKflIQq
-HZkFAmNraNACGwMFCwkIBwIGFQoJCAsCBBYCAwECHgECF4AACgkQnaKflIQqHZk6
-sAD/Xx8MQbtXcKPJi/0UGXkyRHEYbcC+zzhECFalHsQsNh0A/3Naih9zRixXt3v0
-JVCv7fbKaXpfKdGi8tj9muSa+5QBnIsEY2to0BIKKwYBBAGXVQEFAQEHQJqcQkrg
-shQO4tyyshE9Ng74LAdu4zRD/yb9aQet+61BAwEIB/4HAwIEJ6OJJ8OAFf/0IyRK
-Frmj2UHklu1UT1P2JPF5RPzgPlAxZB1eGHFcCQoJX/ro2AsbQ5KZUwraSs1QgX5b
-GKAdcyJqFwtbz+pkTpBvOWS6iHgEGBYIACAWIQQypIrURIXm13T6A26dop+UhCod
-mQUCY2to0AIbDAAKCRCdop+UhCodmYTSAP0U5Q6clPUsFcjIcwKA+x5G1Q+wzODx
-7/pUS2Vg+cKOMAEAuY5wW5k0eCuWMC/uzXy8l2a3BwsMN3nlApuGk0zOcwM=
-=ApnA
------END PGP PRIVATE KEY BLOCK-----
diff --git a/crypto-pgpainless/src/test/resources/bobby_owner@example_com b/crypto-pgpainless/src/test/resources/bobby_owner@example_com
deleted file mode 100644
index 6bd548e7..00000000
--- a/crypto-pgpainless/src/test/resources/bobby_owner@example_com
+++ /dev/null
@@ -1,16 +0,0 @@
------BEGIN PGP PRIVATE KEY BLOCK-----
-
-lIYEY2tpShYJKwYBBAHaRw8BAQdA0syygn/sjv82T226XDe7ZmsJ897HQ88pruR6
-uMSdtYP+BwMCAaOsMuRJoq3/eHOl9Df1jlr3zfIBdw0hQrmcJ2qVOS4xQGDegjLW
-Bbqnmjw7cCRUN1knjHdMWYwrnm8G9YmhOhhwHwdmhxw/LJOA00SyVLQZQm9iYnkg
-PG93bmVyQGV4YW1wbGUuY29tPoiQBBMWCAA4FiEEPnwcCqistth5tnEB8qkNCuDF
-QtMFAmNraUoCGwMFCwkIBwIGFQoJCAsCBBYCAwECHgECF4AACgkQ8qkNCuDFQtOx
-NAD+MzrKYoQQxgLWkqf08Jhc58sa+2xeZBI3Sq0o+huMql0A/39hfDlJnzD61gEZ
-vwOeZuTNb+LH23ha2uG8UpoMx4oLnIsEY2tpShIKKwYBBAGXVQEFAQEHQIVF4gjr
-Wpw7KN/IYZQdml9Rn7zlBMsXNIxXhcMVjxlZAwEIB/4HAwJenw1L/ZS8WP+e3uzg
-khxkk1dQ3fZbTaR90z0wzLDGngVJO1J2XmfIPnTeU8conEeak8Yyt8+85QdM9MK0
-ch0MyhEa/8hRgtCL8Fo3XkLKiHgEGBYIACAWIQQ+fBwKqKy22Hm2cQHyqQ0K4MVC
-0wUCY2tpSgIbDAAKCRDyqQ0K4MVC04+aAQDEW/aasrpOYw35DIddH/Wp4tSrWi65
-kv18HvDPl/c6KwEAw6ZxYsfWmxMtzY6efTIzVnvb4T3OZEVWG6XetZoDTAI=
-=OFsg
------END PGP PRIVATE KEY BLOCK-----
diff --git a/crypto-pgpainless/src/test/resources/public_key b/crypto-pgpainless/src/test/resources/public_key
deleted file mode 100644
index 987bac6f..00000000
--- a/crypto-pgpainless/src/test/resources/public_key
+++ /dev/null
@@ -1,21 +0,0 @@
------BEGIN PGP PUBLIC KEY BLOCK-----
-Version: PGPainless
-Comment: BC98 82EF 93DC 22F8 D7D4 47AD 08ED F756 7183 CE27
-Comment: John Doe <john.doe@example.com>
-
-mDMEYT33+BYJKwYBBAHaRw8BAQdAoofwCvOfKJ4pGxEO4s64wFD+QnePpNY5zXgW
-TTOFb2+0H0pvaG4gRG9lIDxqb2huLmRvZUBleGFtcGxlLmNvbT6IeAQTFgoAIAUC
-YT33+AIbAQUWAgMBAAQLCQgHBRUKCQgLAh4BAhkBAAoJEAjt91Zxg84n5dYA/AiA
-BqBdt2ItWgDPLCNEqt9wIMgRpkDrAMtXXyyLSkWsAQCoowpenGsq5fxhuRcS3w6Q
-s+/Qw1GqnoidxhioR9J+ALg4BGE99/gSCisGAQQBl1UBBQEBB0C7eFVsFUif4q9S
-taBI6JAwsI+hQSAo3I6V4jU3rix8XwMBCAeIdQQYFgoAHQUCYT33+AIbDAUWAgMB
-AAQLCQgHBRUKCQgLAh4BAAoJEAjt91Zxg84nmn4BALmD8WYxTdrJqUZUE1TcFvzG
-5r0//rPM8Vut5X+KwUXjAQDWVP22KaA8VXpevSxkS3n/ti0KjQVKEFzGbmwB2dTT
-CbgzBGE99/gWCSsGAQQB2kcPAQEHQJXfqDjCO9L4qBu62/UPpQ5q0638kG8+AGf/
-hJH2q2BTiNUEGBYKAH0FAmE99/gCGwIFFgIDAQAECwkIBwUVCgkICwIeAV8gBBkW
-CgAGBQJhPff4AAoJEGSLoii3QC8mrhcBALzpJQTHF8cJJRA9+DQ3qZ85Eu217MJi
-x1aYA1i0zyP5AQD/jN/aBsSTqAHF+zU8/ezzHeoilyBYgxLS9Q2qelDeDAAKCRAI
-7fdWcYPOJ7aHAP9EBq0rzV3c6GtVl8bPnk+llpV/1aodxTSnijQtVSMuMAD+JMUD
-Jd2bimlhuVwpu0DFiF7IF64SAxmVifTwsTWYiQs=
-=jGlC
------END PGP PUBLIC KEY BLOCK-----
diff --git a/crypto-pgpainless/src/test/resources/public_key_multiple_identities b/crypto-pgpainless/src/test/resources/public_key_multiple_identities
deleted file mode 100644
index 2cc896c2..00000000
--- a/crypto-pgpainless/src/test/resources/public_key_multiple_identities
+++ /dev/null
@@ -1,51 +0,0 @@
------BEGIN PGP PUBLIC KEY BLOCK-----
-
-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
-GorvBkqXcIR7
-=dL2N
------END PGP PUBLIC KEY BLOCK-----
diff --git a/crypto-pgpainless/src/test/resources/secret_key b/crypto-pgpainless/src/test/resources/secret_key
deleted file mode 100644
index 61334b01..00000000
--- a/crypto-pgpainless/src/test/resources/secret_key
+++ /dev/null
@@ -1,26 +0,0 @@
------BEGIN PGP PRIVATE KEY BLOCK-----
-Version: PGPainless
-Comment: BC98 82EF 93DC 22F8 D7D4 47AD 08ED F756 7183 CE27
-Comment: John Doe <john.doe@example.com>
-
-lIYEYT33+BYJKwYBBAHaRw8BAQdAoofwCvOfKJ4pGxEO4s64wFD+QnePpNY5zXgW
-TTOFb2/+CQMCh3Bp60ThtX9g8u+uxtuLdeeU5UC14Ox4zVD/x2L7sUzN94XVocOn
-WVJTIgeZ1CBhrsSOMg5grj0Zwf1YODlBpZ85V8stPebpjZ2mCZUz1rQfSm9obiBE
-b2UgPGpvaG4uZG9lQGV4YW1wbGUuY29tPoh4BBMWCgAgBQJhPff4AhsBBRYCAwEA
-BAsJCAcFFQoJCAsCHgECGQEACgkQCO33VnGDzifl1gD8CIAGoF23Yi1aAM8sI0Sq
-33AgyBGmQOsAy1dfLItKRawBAKijCl6cayrl/GG5FxLfDpCz79DDUaqeiJ3GGKhH
-0n4AnIsEYT33+BIKKwYBBAGXVQEFAQEHQLt4VWwVSJ/ir1K1oEjokDCwj6FBICjc
-jpXiNTeuLHxfAwEIB/4JAwKHcGnrROG1f2AcnEUWhC2rDrztJB3JK7pe+PVJbMaK
-O2eYKLiBZOT6Dy1rexMi0vS19IMYLf1V2qgsO9phoglOD+m95tr8Ha9FhfbpJjua
-iHUEGBYKAB0FAmE99/gCGwwFFgIDAQAECwkIBwUVCgkICwIeAQAKCRAI7fdWcYPO
-J5p+AQC5g/FmMU3ayalGVBNU3Bb8xua9P/6zzPFbreV/isFF4wEA1lT9timgPFV6
-Xr0sZEt5/7YtCo0FShBcxm5sAdnU0wmchgRhPff4FgkrBgEEAdpHDwEBB0CV36g4
-wjvS+Kgbutv1D6UOatOt/JBvPgBn/4SR9qtgU/4JAwKHcGnrROG1f2A1hnm2UXZL
-Go/tPJo3pJCJDLClIKi7I5RoHruafuQ2ODvznLbCnbuft9B2cA5MZUMFCk6nBvoU
-k6hwGWxOSNJIOmrCx+PMiNUEGBYKAH0FAmE99/gCGwIFFgIDAQAECwkIBwUVCgkI
-CwIeAV8gBBkWCgAGBQJhPff4AAoJEGSLoii3QC8mrhcBALzpJQTHF8cJJRA9+DQ3
-qZ85Eu217MJix1aYA1i0zyP5AQD/jN/aBsSTqAHF+zU8/ezzHeoilyBYgxLS9Q2q
-elDeDAAKCRAI7fdWcYPOJ7aHAP9EBq0rzV3c6GtVl8bPnk+llpV/1aodxTSnijQt
-VSMuMAD+JMUDJd2bimlhuVwpu0DFiF7IF64SAxmVifTwsTWYiQs=
-=/dDf
------END PGP PRIVATE KEY BLOCK-----
diff --git a/crypto-pgpainless/src/test/resources/secret_key_multiple_identities b/crypto-pgpainless/src/test/resources/secret_key_multiple_identities
deleted file mode 100644
index 5da8ac81..00000000
--- a/crypto-pgpainless/src/test/resources/secret_key_multiple_identities
+++ /dev/null
@@ -1,93 +0,0 @@
------BEGIN PGP PRIVATE KEY BLOCK-----
-
-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
-hmaBvZKKiR6/QRMyZMQ34dx9GorvBkqXcIR7
-=8IuC
------END PGP PRIVATE KEY BLOCK-----