diff options
author | Harsh Shandilya <me@msfjarvis.dev> | 2022-07-15 00:53:48 +0530 |
---|---|---|
committer | Harsh Shandilya <me@msfjarvis.dev> | 2022-07-15 01:13:47 +0530 |
commit | 549ee790d3e52bc62565ddf92e6a556e98b5195e (patch) | |
tree | d5758e5eb80093704e683c8da926838e18182588 /crypto-pgpainless/src/main/kotlin/dev/msfjarvis | |
parent | 010c6e227c9cc27f4d01bc912311f977b2aeb3a7 (diff) |
all: re-do package structure yet again
Diffstat (limited to 'crypto-pgpainless/src/main/kotlin/dev/msfjarvis')
5 files changed, 0 insertions, 393 deletions
diff --git a/crypto-pgpainless/src/main/kotlin/dev/msfjarvis/aps/crypto/GpgIdentifier.kt b/crypto-pgpainless/src/main/kotlin/dev/msfjarvis/aps/crypto/GpgIdentifier.kt deleted file mode 100644 index b7dcdd0d..00000000 --- a/crypto-pgpainless/src/main/kotlin/dev/msfjarvis/aps/crypto/GpgIdentifier.kt +++ /dev/null @@ -1,99 +0,0 @@ -/* - * Copyright © 2014-2022 The Android Password Store Authors. All Rights Reserved. - * SPDX-License-Identifier: GPL-3.0-only - */ - -package dev.msfjarvis.aps.crypto - -import java.util.Locale -import java.util.regex.Pattern - -public sealed class GpgIdentifier { - public data class KeyId(val id: Long) : GpgIdentifier() { - 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 32) + 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 0xffffffffL).lowercase(Locale.ENGLISH) - while (hexString.length < 8) { - hexString = "0$hexString" - } - return hexString - } - } - public data class UserId(val email: String) : GpgIdentifier() { - override fun toString(): String { - return email - } - } - - public companion object { - public 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-F\\d]{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-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(16).toULong(16) - return KeyId(keyId.toLong()) - } - - return splitUserId(identifier)?.let { UserId(it) } - } - - private val USER_ID_PATTERN = Pattern.compile("^(.*?)(?: \\((.*)\\))?(?: <(.*)>)?$") - private val EMAIL_PATTERN = Pattern.compile("^<?\"?([^<>\"]*@[^<>\"]*[.]?[^<>\"]*)\"?>?$") - - /** - * Takes a 'Name (Comment) <Email>' user ID in any of its permutations and attempts to extract - * an email from it. - */ - private fun splitUserId(userId: String): String? { - if (userId.isNotEmpty()) { - val matcher = USER_ID_PATTERN.matcher(userId) - if (matcher.matches()) { - var name = if (matcher.group(1)?.isEmpty() == true) null else matcher.group(1) - var email = matcher.group(3) - if (email != null && name != null) { - val emailMatcher = EMAIL_PATTERN.matcher(name) - if (emailMatcher.matches() && email == emailMatcher.group(1)) { - email = emailMatcher.group(1) - name = null - } - } - if (email == null && name != null) { - val emailMatcher = EMAIL_PATTERN.matcher(name) - if (emailMatcher.matches()) { - email = emailMatcher.group(1) - } - } - return email - } - } - return null - } - } -} diff --git a/crypto-pgpainless/src/main/kotlin/dev/msfjarvis/aps/crypto/KeyUtils.kt b/crypto-pgpainless/src/main/kotlin/dev/msfjarvis/aps/crypto/KeyUtils.kt deleted file mode 100644 index 1251463a..00000000 --- a/crypto-pgpainless/src/main/kotlin/dev/msfjarvis/aps/crypto/KeyUtils.kt +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright © 2014-2022 The Android Password Store Authors. All Rights Reserved. - * SPDX-License-Identifier: GPL-3.0-only - */ - -package dev.msfjarvis.aps.crypto - -import com.github.michaelbull.result.get -import com.github.michaelbull.result.runCatching -import dev.msfjarvis.aps.crypto.GpgIdentifier.KeyId -import org.bouncycastle.openpgp.PGPKeyRing -import org.pgpainless.PGPainless - -/** 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? { - val secKeyRing = runCatching { PGPainless.readKeyRing().secretKeyRing(key.contents) }.get() - if (secKeyRing != null) { - return secKeyRing - } - val pubKeyRing = runCatching { PGPainless.readKeyRing().publicKeyRing(key.contents) }.get() - if (pubKeyRing != null) { - return pubKeyRing - } - return null - } - - /** 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) - } -} diff --git a/crypto-pgpainless/src/main/kotlin/dev/msfjarvis/aps/crypto/PGPKey.kt b/crypto-pgpainless/src/main/kotlin/dev/msfjarvis/aps/crypto/PGPKey.kt deleted file mode 100644 index 2e1311a1..00000000 --- a/crypto-pgpainless/src/main/kotlin/dev/msfjarvis/aps/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 dev.msfjarvis.aps.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/dev/msfjarvis/aps/crypto/PGPKeyManager.kt b/crypto-pgpainless/src/main/kotlin/dev/msfjarvis/aps/crypto/PGPKeyManager.kt deleted file mode 100644 index 137bd141..00000000 --- a/crypto-pgpainless/src/main/kotlin/dev/msfjarvis/aps/crypto/PGPKeyManager.kt +++ /dev/null @@ -1,135 +0,0 @@ -/* - * Copyright © 2014-2021 The Android Password Store Authors. All Rights Reserved. - * SPDX-License-Identifier: GPL-3.0-only - */ -@file:Suppress("BlockingMethodInNonBlockingContext") - -package dev.msfjarvis.aps.crypto - -import androidx.annotation.VisibleForTesting -import com.github.michaelbull.result.Result -import dev.msfjarvis.aps.crypto.KeyUtils.tryGetId -import dev.msfjarvis.aps.crypto.KeyUtils.tryParseKeyring -import dev.msfjarvis.aps.crypto.errors.InvalidKeyException -import dev.msfjarvis.aps.crypto.errors.KeyAlreadyExistsException -import dev.msfjarvis.aps.crypto.errors.KeyDeletionFailedException -import dev.msfjarvis.aps.crypto.errors.KeyDirectoryUnavailableException -import dev.msfjarvis.aps.crypto.errors.KeyNotFoundException -import dev.msfjarvis.aps.crypto.errors.NoKeysAvailableException -import dev.msfjarvis.aps.util.coroutines.runSuspendCatching -import java.io.File -import javax.inject.Inject -import kotlinx.coroutines.CoroutineDispatcher -import kotlinx.coroutines.withContext -import org.pgpainless.util.selection.userid.SelectUserId - -public class PGPKeyManager -@Inject -constructor( - filesDir: String, - private val dispatcher: CoroutineDispatcher, -) : KeyManager<PGPKey, GpgIdentifier> { - - private val keyDir = File(filesDir, KEY_DIR_NAME) - - override suspend fun addKey(key: PGPKey, replace: Boolean): Result<PGPKey, Throwable> = - withContext(dispatcher) { - runSuspendCatching { - if (!keyDirExists()) throw KeyDirectoryUnavailableException - if (tryParseKeyring(key) == null) throw InvalidKeyException - val keyFile = File(keyDir, "${tryGetId(key)}.$KEY_EXTENSION") - if (keyFile.exists()) { - // 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 - } - } - - override suspend fun removeKey(key: PGPKey): Result<PGPKey, Throwable> = - withContext(dispatcher) { - runSuspendCatching { - if (!keyDirExists()) throw KeyDirectoryUnavailableException - if (tryParseKeyring(key) == null) throw InvalidKeyException - val keyFile = File(keyDir, "${tryGetId(key)}.$KEY_EXTENSION") - if (keyFile.exists()) { - if (!keyFile.delete()) throw KeyDeletionFailedException - } - - key - } - } - - override suspend fun getKeyById(id: GpgIdentifier): 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 GpgIdentifier.KeyId -> { - val keyIdMatch = - keys - .map { key -> key to tryGetId(key) } - .firstOrNull { (_, keyId) -> keyId?.id == id.id } - keyIdMatch?.first - } - is GpgIdentifier.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") - } - } - - 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() - } - } - - override suspend fun getKeyId(key: PGPKey): GpgIdentifier? = tryGetId(key) - - // TODO: This is a temp hack for now and in future it should check that the GPGKeyManager can - // decrypt the file. - override fun canHandle(fileName: String): Boolean { - return fileName.endsWith(KEY_EXTENSION) - } - - /** 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/dev/msfjarvis/aps/crypto/PGPainlessCryptoHandler.kt b/crypto-pgpainless/src/main/kotlin/dev/msfjarvis/aps/crypto/PGPainlessCryptoHandler.kt deleted file mode 100644 index 24b3e665..00000000 --- a/crypto-pgpainless/src/main/kotlin/dev/msfjarvis/aps/crypto/PGPainlessCryptoHandler.kt +++ /dev/null @@ -1,110 +0,0 @@ -/* - * Copyright © 2014-2021 The Android Password Store Authors. All Rights Reserved. - * SPDX-License-Identifier: GPL-3.0-only - */ - -package dev.msfjarvis.aps.crypto - -import com.github.michaelbull.result.Result -import com.github.michaelbull.result.mapError -import com.github.michaelbull.result.runCatching -import dev.msfjarvis.aps.crypto.errors.CryptoHandlerException -import dev.msfjarvis.aps.crypto.errors.IncorrectPassphraseException -import dev.msfjarvis.aps.crypto.errors.NoKeysProvided -import dev.msfjarvis.aps.crypto.errors.UnknownError -import java.io.ByteArrayInputStream -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.PGPSecretKeyRingCollection -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.PasswordBasedSecretKeyRingProtector -import org.pgpainless.util.Passphrase - -public class PGPainlessCryptoHandler @Inject constructor() : CryptoHandler<PGPKey> { - - public override fun decrypt( - privateKey: PGPKey, - passphrase: String, - ciphertextStream: InputStream, - outputStream: OutputStream, - ): Result<Unit, CryptoHandlerException> = - runCatching { - val pgpSecretKeyRing = PGPainless.readKeyRing().secretKeyRing(privateKey.contents) - val keyringCollection = PGPSecretKeyRingCollection(listOf(pgpSecretKeyRing)) - val protector = - PasswordBasedSecretKeyRingProtector.forKey( - pgpSecretKeyRing, - Passphrase.fromPassword(passphrase) - ) - PGPainless.decryptAndOrVerify() - .onInputStream(ciphertextStream) - .withOptions( - ConsumerOptions() - .addDecryptionKeys(keyringCollection, protector) - .addDecryptionPassphrase(Passphrase.fromPassword(passphrase)) - ) - .use { decryptionStream -> decryptionStream.copyTo(outputStream) } - return@runCatching - } - .mapError { error -> - when (error) { - is WrongPassphraseException -> IncorrectPassphraseException(error) - else -> UnknownError(error) - } - } - - public override fun encrypt( - keys: List<PGPKey>, - plaintextStream: InputStream, - outputStream: OutputStream, - ): Result<Unit, CryptoHandlerException> = - runCatching { - if (keys.isEmpty()) throw NoKeysProvided("No keys provided for encryption") - val publicKeyRings = arrayListOf<PGPPublicKeyRing>() - val armoredKeys = - keys.joinToString("\n") { key -> key.contents.decodeToString() }.toByteArray() - val secKeysStream = ByteArrayInputStream(armoredKeys) - val secretKeyRingCollection = - PGPainless.readKeyRing().secretKeyRingCollection(secKeysStream) - secretKeyRingCollection.forEach { secretKeyRing -> - publicKeyRings.add(PGPainless.extractCertificate(secretKeyRing)) - } - if (publicKeyRings.isEmpty()) { - val pubKeysStream = ByteArrayInputStream(armoredKeys) - val publicKeyRingCollection = - PGPainless.readKeyRing().publicKeyRingCollection(pubKeysStream) - publicKeyRings.addAll(publicKeyRingCollection) - } - require(publicKeyRings.isNotEmpty()) { "No public keys to encrypt message to" } - val publicKeyRingCollection = PGPPublicKeyRingCollection(publicKeyRings) - val encryptionOptions = EncryptionOptions().addRecipients(publicKeyRingCollection) - val producerOptions = ProducerOptions.encrypt(encryptionOptions).setAsciiArmor(false) - val encryptor = - PGPainless.encryptAndOrSign().onOutputStream(outputStream).withOptions(producerOptions) - plaintextStream.copyTo(encryptor) - encryptor.close() - val result = encryptor.result - publicKeyRingCollection.keyRings.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) - } - } - - public override fun canHandle(fileName: String): Boolean { - return fileName.split('.').lastOrNull() == "gpg" - } -} |