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
|
package app.passwordstore.data.crypto
|
||||||
|
|
||||||
import app.passwordstore.crypto.GpgIdentifier
|
import app.passwordstore.crypto.GpgIdentifier
|
||||||
|
import app.passwordstore.crypto.PGPDecryptOptions
|
||||||
|
import app.passwordstore.crypto.PGPEncryptOptions
|
||||||
import app.passwordstore.crypto.PGPKeyManager
|
import app.passwordstore.crypto.PGPKeyManager
|
||||||
import app.passwordstore.crypto.PGPainlessCryptoHandler
|
import app.passwordstore.crypto.PGPainlessCryptoHandler
|
||||||
import app.passwordstore.crypto.errors.CryptoHandlerException
|
import app.passwordstore.crypto.errors.CryptoHandlerException
|
||||||
|
@ -42,8 +44,9 @@ constructor(
|
||||||
message: ByteArrayInputStream,
|
message: ByteArrayInputStream,
|
||||||
out: ByteArrayOutputStream,
|
out: ByteArrayOutputStream,
|
||||||
): Result<Unit, CryptoHandlerException> {
|
): Result<Unit, CryptoHandlerException> {
|
||||||
|
val decryptionOptions = PGPDecryptOptions.Builder().build()
|
||||||
val keys = pgpKeyManager.getAllKeys().unwrap()
|
val keys = pgpKeyManager.getAllKeys().unwrap()
|
||||||
return pgpCryptoHandler.decrypt(keys, password, message, out)
|
return pgpCryptoHandler.decrypt(keys, password, message, out, decryptionOptions)
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun encryptPgp(
|
private suspend fun encryptPgp(
|
||||||
|
@ -51,11 +54,13 @@ constructor(
|
||||||
content: ByteArrayInputStream,
|
content: ByteArrayInputStream,
|
||||||
out: ByteArrayOutputStream,
|
out: ByteArrayOutputStream,
|
||||||
): Result<Unit, CryptoHandlerException> {
|
): Result<Unit, CryptoHandlerException> {
|
||||||
|
val encryptionOptions = PGPEncryptOptions.Builder().build()
|
||||||
val keys = identities.map { id -> pgpKeyManager.getKeyById(id) }.getAll()
|
val keys = identities.map { id -> pgpKeyManager.getKeyById(id) }.getAll()
|
||||||
return pgpCryptoHandler.encrypt(
|
return pgpCryptoHandler.encrypt(
|
||||||
keys,
|
keys,
|
||||||
content,
|
content,
|
||||||
out,
|
out,
|
||||||
|
encryptionOptions,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,7 +11,7 @@ import java.io.InputStream
|
||||||
import java.io.OutputStream
|
import java.io.OutputStream
|
||||||
|
|
||||||
/** Generic interface to implement cryptographic operations on top of. */
|
/** 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
|
* Decrypt the given [ciphertextStream] using a set of potential [keys] and [passphrase], and
|
||||||
|
@ -24,6 +24,7 @@ public interface CryptoHandler<Key> {
|
||||||
passphrase: String,
|
passphrase: String,
|
||||||
ciphertextStream: InputStream,
|
ciphertextStream: InputStream,
|
||||||
outputStream: OutputStream,
|
outputStream: OutputStream,
|
||||||
|
options: DecryptOpts,
|
||||||
): Result<Unit, CryptoHandlerException>
|
): Result<Unit, CryptoHandlerException>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -35,6 +36,7 @@ public interface CryptoHandler<Key> {
|
||||||
keys: List<Key>,
|
keys: List<Key>,
|
||||||
plaintextStream: InputStream,
|
plaintextStream: InputStream,
|
||||||
outputStream: OutputStream,
|
outputStream: OutputStream,
|
||||||
|
options: EncOpts,
|
||||||
): Result<Unit, CryptoHandlerException>
|
): Result<Unit, CryptoHandlerException>
|
||||||
|
|
||||||
/** Given a [fileName], return whether this instance can handle it. */
|
/** 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.key.protection.SecretKeyRingProtector
|
||||||
import org.pgpainless.util.Passphrase
|
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(
|
public override fun decrypt(
|
||||||
keys: List<PGPKey>,
|
keys: List<PGPKey>,
|
||||||
passphrase: String,
|
passphrase: String,
|
||||||
ciphertextStream: InputStream,
|
ciphertextStream: InputStream,
|
||||||
outputStream: OutputStream,
|
outputStream: OutputStream,
|
||||||
|
options: PGPDecryptOptions,
|
||||||
): Result<Unit, CryptoHandlerException> =
|
): Result<Unit, CryptoHandlerException> =
|
||||||
runCatching {
|
runCatching {
|
||||||
if (keys.isEmpty()) throw NoKeysProvided("No keys provided for encryption")
|
if (keys.isEmpty()) throw NoKeysProvided("No keys provided for encryption")
|
||||||
|
@ -63,6 +65,7 @@ public class PGPainlessCryptoHandler @Inject constructor() : CryptoHandler<PGPKe
|
||||||
keys: List<PGPKey>,
|
keys: List<PGPKey>,
|
||||||
plaintextStream: InputStream,
|
plaintextStream: InputStream,
|
||||||
outputStream: OutputStream,
|
outputStream: OutputStream,
|
||||||
|
options: PGPEncryptOptions,
|
||||||
): Result<Unit, CryptoHandlerException> =
|
): Result<Unit, CryptoHandlerException> =
|
||||||
runCatching {
|
runCatching {
|
||||||
if (keys.isEmpty()) throw NoKeysProvided("No keys provided for encryption")
|
if (keys.isEmpty()) throw NoKeysProvided("No keys provided for encryption")
|
||||||
|
|
|
@ -41,6 +41,7 @@ class PGPainlessCryptoHandlerTest {
|
||||||
encryptionKey.keySet,
|
encryptionKey.keySet,
|
||||||
CryptoConstants.PLAIN_TEXT.byteInputStream(Charsets.UTF_8),
|
CryptoConstants.PLAIN_TEXT.byteInputStream(Charsets.UTF_8),
|
||||||
ciphertextStream,
|
ciphertextStream,
|
||||||
|
PGPEncryptOptions.Builder().build(),
|
||||||
)
|
)
|
||||||
assertIs<Ok<Unit>>(encryptRes)
|
assertIs<Ok<Unit>>(encryptRes)
|
||||||
val plaintextStream = ByteArrayOutputStream()
|
val plaintextStream = ByteArrayOutputStream()
|
||||||
|
@ -50,6 +51,7 @@ class PGPainlessCryptoHandlerTest {
|
||||||
CryptoConstants.KEY_PASSPHRASE,
|
CryptoConstants.KEY_PASSPHRASE,
|
||||||
ciphertextStream.toByteArray().inputStream(),
|
ciphertextStream.toByteArray().inputStream(),
|
||||||
plaintextStream,
|
plaintextStream,
|
||||||
|
PGPDecryptOptions.Builder().build(),
|
||||||
)
|
)
|
||||||
assertIs<Ok<Unit>>(decryptRes)
|
assertIs<Ok<Unit>>(decryptRes)
|
||||||
assertEquals(CryptoConstants.PLAIN_TEXT, plaintextStream.toString(Charsets.UTF_8))
|
assertEquals(CryptoConstants.PLAIN_TEXT, plaintextStream.toString(Charsets.UTF_8))
|
||||||
|
@ -63,6 +65,7 @@ class PGPainlessCryptoHandlerTest {
|
||||||
encryptionKey.keySet,
|
encryptionKey.keySet,
|
||||||
CryptoConstants.PLAIN_TEXT.byteInputStream(Charsets.UTF_8),
|
CryptoConstants.PLAIN_TEXT.byteInputStream(Charsets.UTF_8),
|
||||||
ciphertextStream,
|
ciphertextStream,
|
||||||
|
PGPEncryptOptions.Builder().build(),
|
||||||
)
|
)
|
||||||
assertIs<Ok<Unit>>(encryptRes)
|
assertIs<Ok<Unit>>(encryptRes)
|
||||||
val plaintextStream = ByteArrayOutputStream()
|
val plaintextStream = ByteArrayOutputStream()
|
||||||
|
@ -72,6 +75,7 @@ class PGPainlessCryptoHandlerTest {
|
||||||
"very incorrect passphrase",
|
"very incorrect passphrase",
|
||||||
ciphertextStream.toByteArray().inputStream(),
|
ciphertextStream.toByteArray().inputStream(),
|
||||||
plaintextStream,
|
plaintextStream,
|
||||||
|
PGPDecryptOptions.Builder().build(),
|
||||||
)
|
)
|
||||||
assertIs<Err<Throwable>>(result)
|
assertIs<Err<Throwable>>(result)
|
||||||
assertIs<IncorrectPassphraseException>(result.getError())
|
assertIs<IncorrectPassphraseException>(result.getError())
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue