mirror of
https://github.com/pgpainless/pgpainless.git
synced 2025-12-08 13:21:09 +01:00
WIP: Transparent decryption
This commit is contained in:
parent
510f8276e7
commit
de47a683d9
13 changed files with 223 additions and 116 deletions
|
|
@ -17,6 +17,7 @@ import org.bouncycastle.openpgp.operator.PublicKeyDataDecryptorFactory
|
|||
import org.pgpainless.PGPainless
|
||||
import org.pgpainless.decryption_verification.cleartext_signatures.InMemoryMultiPassStrategy
|
||||
import org.pgpainless.decryption_verification.cleartext_signatures.MultiPassStrategy
|
||||
import org.pgpainless.hardware.HardwareTokenBackend
|
||||
import org.pgpainless.key.SubkeyIdentifier
|
||||
import org.pgpainless.key.protection.SecretKeyRingProtector
|
||||
import org.pgpainless.signature.SignatureUtils
|
||||
|
|
@ -44,6 +45,7 @@ class ConsumerOptions(private val api: PGPainless) {
|
|||
private var missingKeyPassphraseStrategy = MissingKeyPassphraseStrategy.INTERACTIVE
|
||||
private var multiPassStrategy: MultiPassStrategy = InMemoryMultiPassStrategy()
|
||||
private var allowDecryptionWithNonEncryptionKey: Boolean = false
|
||||
val hardwareTokenBackends: List<HardwareTokenBackend> = mutableListOf()
|
||||
|
||||
/**
|
||||
* Consider signatures on the message made before the given timestamp invalid. Null means no
|
||||
|
|
@ -236,6 +238,10 @@ class ConsumerOptions(private val api: PGPainless) {
|
|||
decryptionPassphrases.add(passphrase)
|
||||
}
|
||||
|
||||
fun addHardwareTokenBackend(backend: HardwareTokenBackend) = apply {
|
||||
(hardwareTokenBackends as MutableList).add(backend)
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a custom [PublicKeyDataDecryptorFactory] which enable decryption of messages, e.g. using
|
||||
* hardware-backed secret keys. (See e.g.
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@ import org.bouncycastle.openpgp.api.OpenPGPKey.OpenPGPSecretKey
|
|||
import org.bouncycastle.openpgp.api.OpenPGPSignature.OpenPGPDocumentSignature
|
||||
import org.bouncycastle.openpgp.api.exception.MalformedOpenPGPSignatureException
|
||||
import org.bouncycastle.openpgp.operator.PBEDataDecryptorFactory
|
||||
import org.bouncycastle.openpgp.operator.PGPDataDecryptorFactory
|
||||
import org.bouncycastle.openpgp.operator.PublicKeyDataDecryptorFactory
|
||||
import org.bouncycastle.util.io.TeeInputStream
|
||||
import org.pgpainless.PGPainless
|
||||
|
|
@ -66,7 +67,9 @@ import org.pgpainless.exception.MissingPassphraseException
|
|||
import org.pgpainless.exception.SignatureValidationException
|
||||
import org.pgpainless.exception.UnacceptableAlgorithmException
|
||||
import org.pgpainless.exception.WrongPassphraseException
|
||||
import org.pgpainless.hardware.HardwareTokenBackend
|
||||
import org.pgpainless.key.SubkeyIdentifier
|
||||
import org.pgpainless.key.protection.SecretKeyRingProtector
|
||||
import org.pgpainless.key.protection.UnlockSecretKey.Companion.unlockSecretKey
|
||||
import org.pgpainless.signature.consumer.OnePassSignatureCheck
|
||||
import org.pgpainless.util.ArmoredInputStreamFactory
|
||||
|
|
@ -434,9 +437,6 @@ class OpenPgpMessageInputStream(
|
|||
"Message is encrypted for ${secretKey.keyIdentifier}, but the key is not encryption capable.")
|
||||
continue
|
||||
}
|
||||
if (hasUnsupportedS2KSpecifier(secretKey)) {
|
||||
continue
|
||||
}
|
||||
|
||||
LOGGER.debug("Attempt decryption using secret key ${decryptionKeys.keyIdentifier}")
|
||||
val protector = options.getSecretKeyProtector(decryptionKeys) ?: continue
|
||||
|
|
@ -447,6 +447,28 @@ class OpenPgpMessageInputStream(
|
|||
continue
|
||||
}
|
||||
|
||||
if (secretKey.hasExternalSecretKey()) {
|
||||
LOGGER.debug("Decryption key ${secretKey.keyIdentifier} is located on an external device, e.g. a smartcard.")
|
||||
for (hardwareTokenBackend in options.hardwareTokenBackends) {
|
||||
LOGGER.debug("Attempt decryption with ${hardwareTokenBackend.getBackendName()} backend.")
|
||||
if (decryptWithHardwareKey(
|
||||
hardwareTokenBackend,
|
||||
esks,
|
||||
secretKey,
|
||||
protector,
|
||||
SubkeyIdentifier(secretKey.openPGPKey.pgpSecretKeyRing, secretKey.keyIdentifier),
|
||||
pkesk)) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
/*
|
||||
if (hasUnsupportedS2KSpecifier(secretKey)) {
|
||||
continue
|
||||
}
|
||||
|
||||
*/
|
||||
|
||||
val privateKey =
|
||||
try {
|
||||
unlockSecretKey(secretKey, protector)
|
||||
|
|
@ -527,6 +549,25 @@ class OpenPgpMessageInputStream(
|
|||
return false
|
||||
}
|
||||
|
||||
private fun decryptWithHardwareKey(
|
||||
hardwareTokenBackend: HardwareTokenBackend,
|
||||
esks: ESKsAndData,
|
||||
secretKey: OpenPGPSecretKey,
|
||||
protector: SecretKeyRingProtector,
|
||||
subkeyIdentifier: SubkeyIdentifier,
|
||||
pkesk: PGPPublicKeyEncryptedData
|
||||
): Boolean {
|
||||
val decryptors = hardwareTokenBackend.provideDecryptorsFor(secretKey, protector, pkesk)
|
||||
while (decryptors.hasNext()) {
|
||||
val decryptor = decryptors.next()
|
||||
val success = decryptPKESKAndStream(esks, subkeyIdentifier, decryptor, pkesk)
|
||||
if (success) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
private fun decryptWithPrivateKey(
|
||||
esks: ESKsAndData,
|
||||
privateKey: PGPKeyPair,
|
||||
|
|
|
|||
|
|
@ -1,6 +1,20 @@
|
|||
package org.pgpainless.hardware
|
||||
|
||||
import org.bouncycastle.openpgp.PGPPublicKeyEncryptedData
|
||||
import org.bouncycastle.openpgp.api.OpenPGPKey
|
||||
import org.bouncycastle.openpgp.operator.PublicKeyDataDecryptorFactory
|
||||
import org.pgpainless.key.protection.SecretKeyRingProtector
|
||||
|
||||
interface HardwareTokenBackend {
|
||||
|
||||
fun getBackendName(): String
|
||||
|
||||
fun provideDecryptorsFor(
|
||||
secKey: OpenPGPKey.OpenPGPSecretKey,
|
||||
protector: SecretKeyRingProtector,
|
||||
pkesk: PGPPublicKeyEncryptedData
|
||||
): Iterator<PublicKeyDataDecryptorFactory>
|
||||
|
||||
fun listDeviceSerials(): List<ByteArray>
|
||||
|
||||
fun listKeyFingerprints(): Map<ByteArray, List<ByteArray>>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue