feat(crypto-common): support passing arbitrary crypto options
This commit is contained in:
parent
390286f95f
commit
633cbe2714
7 changed files with 81 additions and 3 deletions
|
@ -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,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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. */
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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())
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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")
|
||||
|
|
|
@ -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())
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue