1
0
Fork 0
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:
Paul Schaub 2025-12-01 23:42:44 +01:00
parent 510f8276e7
commit de47a683d9
Signed by: vanitasvitae
GPG key ID: 62BEE9264BF17311
13 changed files with 223 additions and 116 deletions

View file

@ -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.

View file

@ -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,

View file

@ -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>>