aboutsummaryrefslogtreecommitdiff
path: root/crypto-pgpainless/src/main/kotlin/dev/msfjarvis
diff options
context:
space:
mode:
authorHarsh Shandilya <me@msfjarvis.dev>2022-07-15 00:53:48 +0530
committerHarsh Shandilya <me@msfjarvis.dev>2022-07-15 01:13:47 +0530
commit549ee790d3e52bc62565ddf92e6a556e98b5195e (patch)
treed5758e5eb80093704e683c8da926838e18182588 /crypto-pgpainless/src/main/kotlin/dev/msfjarvis
parent010c6e227c9cc27f4d01bc912311f977b2aeb3a7 (diff)
all: re-do package structure yet again
Diffstat (limited to 'crypto-pgpainless/src/main/kotlin/dev/msfjarvis')
-rw-r--r--crypto-pgpainless/src/main/kotlin/dev/msfjarvis/aps/crypto/GpgIdentifier.kt99
-rw-r--r--crypto-pgpainless/src/main/kotlin/dev/msfjarvis/aps/crypto/KeyUtils.kt37
-rw-r--r--crypto-pgpainless/src/main/kotlin/dev/msfjarvis/aps/crypto/PGPKey.kt12
-rw-r--r--crypto-pgpainless/src/main/kotlin/dev/msfjarvis/aps/crypto/PGPKeyManager.kt135
-rw-r--r--crypto-pgpainless/src/main/kotlin/dev/msfjarvis/aps/crypto/PGPainlessCryptoHandler.kt110
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"
- }
-}