feat(crypto-common): support passing arbitrary crypto options

This commit is contained in:
Harsh Shandilya 2022-10-29 04:36:00 +05:30
parent 390286f95f
commit 633cbe2714
No known key found for this signature in database
7 changed files with 81 additions and 3 deletions

View file

@ -6,6 +6,8 @@
package app.passwordstore.data.crypto
import app.passwordstore.crypto.GpgIdentifier
import app.passwordstore.crypto.PGPDecryptOptions
import app.passwordstore.crypto.PGPEncryptOptions
import app.passwordstore.crypto.PGPKeyManager
import app.passwordstore.crypto.PGPainlessCryptoHandler
import app.passwordstore.crypto.errors.CryptoHandlerException
@ -42,8 +44,9 @@ constructor(
message: ByteArrayInputStream,
out: ByteArrayOutputStream,
): Result<Unit, CryptoHandlerException> {
val decryptionOptions = PGPDecryptOptions.Builder().build()
val keys = pgpKeyManager.getAllKeys().unwrap()
return pgpCryptoHandler.decrypt(keys, password, message, out)
return pgpCryptoHandler.decrypt(keys, password, message, out, decryptionOptions)
}
private suspend fun encryptPgp(
@ -51,11 +54,13 @@ constructor(
content: ByteArrayInputStream,
out: ByteArrayOutputStream,
): Result<Unit, CryptoHandlerException> {
val encryptionOptions = PGPEncryptOptions.Builder().build()
val keys = identities.map { id -> pgpKeyManager.getKeyById(id) }.getAll()
return pgpCryptoHandler.encrypt(
keys,
content,
out,
encryptionOptions,
)
}
}

View file

@ -11,7 +11,7 @@ import java.io.InputStream
import java.io.OutputStream
/** Generic interface to implement cryptographic operations on top of. */
public interface CryptoHandler<Key> {
public interface CryptoHandler<Key, EncOpts : CryptoOptions, DecryptOpts : CryptoOptions> {
/**
* Decrypt the given [ciphertextStream] using a set of potential [keys] and [passphrase], and
@ -24,6 +24,7 @@ public interface CryptoHandler<Key> {
passphrase: String,
ciphertextStream: InputStream,
outputStream: OutputStream,
options: DecryptOpts,
): Result<Unit, CryptoHandlerException>
/**
@ -35,6 +36,7 @@ public interface CryptoHandler<Key> {
keys: List<Key>,
plaintextStream: InputStream,
outputStream: OutputStream,
options: EncOpts,
): Result<Unit, CryptoHandlerException>
/** Given a [fileName], return whether this instance can handle it. */

View file

@ -0,0 +1,8 @@
package app.passwordstore.crypto
/** Defines the contract for a grab-bag of options for individual cryptographic operations. */
public interface CryptoOptions {
/** Returns a [Boolean] indicating if the [option] is enabled for this operation. */
public fun isOptionEnabled(option: String): Boolean
}

View file

@ -0,0 +1,21 @@
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())
}
}
}

View file

@ -0,0 +1,35 @@
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)
}
}
}

View file

@ -27,13 +27,15 @@ import org.pgpainless.exception.WrongPassphraseException
import org.pgpainless.key.protection.SecretKeyRingProtector
import org.pgpainless.util.Passphrase
public class PGPainlessCryptoHandler @Inject constructor() : CryptoHandler<PGPKey> {
public class PGPainlessCryptoHandler @Inject constructor() :
CryptoHandler<PGPKey, PGPEncryptOptions, PGPDecryptOptions> {
public override fun decrypt(
keys: List<PGPKey>,
passphrase: String,
ciphertextStream: InputStream,
outputStream: OutputStream,
options: PGPDecryptOptions,
): Result<Unit, CryptoHandlerException> =
runCatching {
if (keys.isEmpty()) throw NoKeysProvided("No keys provided for encryption")
@ -63,6 +65,7 @@ public class PGPainlessCryptoHandler @Inject constructor() : CryptoHandler<PGPKe
keys: List<PGPKey>,
plaintextStream: InputStream,
outputStream: OutputStream,
options: PGPEncryptOptions,
): Result<Unit, CryptoHandlerException> =
runCatching {
if (keys.isEmpty()) throw NoKeysProvided("No keys provided for encryption")

View file

@ -41,6 +41,7 @@ class PGPainlessCryptoHandlerTest {
encryptionKey.keySet,
CryptoConstants.PLAIN_TEXT.byteInputStream(Charsets.UTF_8),
ciphertextStream,
PGPEncryptOptions.Builder().build(),
)
assertIs<Ok<Unit>>(encryptRes)
val plaintextStream = ByteArrayOutputStream()
@ -50,6 +51,7 @@ class PGPainlessCryptoHandlerTest {
CryptoConstants.KEY_PASSPHRASE,
ciphertextStream.toByteArray().inputStream(),
plaintextStream,
PGPDecryptOptions.Builder().build(),
)
assertIs<Ok<Unit>>(decryptRes)
assertEquals(CryptoConstants.PLAIN_TEXT, plaintextStream.toString(Charsets.UTF_8))
@ -63,6 +65,7 @@ class PGPainlessCryptoHandlerTest {
encryptionKey.keySet,
CryptoConstants.PLAIN_TEXT.byteInputStream(Charsets.UTF_8),
ciphertextStream,
PGPEncryptOptions.Builder().build(),
)
assertIs<Ok<Unit>>(encryptRes)
val plaintextStream = ByteArrayOutputStream()
@ -72,6 +75,7 @@ class PGPainlessCryptoHandlerTest {
"very incorrect passphrase",
ciphertextStream.toByteArray().inputStream(),
plaintextStream,
PGPDecryptOptions.Builder().build(),
)
assertIs<Err<Throwable>>(result)
assertIs<IncorrectPassphraseException>(result.getError())