1
0
Fork 0
mirror of https://github.com/pgpainless/pgpainless.git synced 2025-09-09 10:19:39 +02:00

WIP: Transform Options and OpenPgpMessageInputStream

This commit is contained in:
Paul Schaub 2025-02-10 17:05:16 +01:00
parent 932c7d878b
commit aa3b78b374
Signed by: vanitasvitae
GPG key ID: 62BEE9264BF17311
13 changed files with 231 additions and 111 deletions

View file

@ -10,6 +10,7 @@ import org.bouncycastle.bcpg.SecretKeyPacket;
import org.bouncycastle.bcpg.SecretSubkeyPacket;
import org.bouncycastle.openpgp.PGPSecretKey;
import org.bouncycastle.openpgp.PGPSecretKeyRing;
import org.bouncycastle.openpgp.api.OpenPGPKey;
import org.pgpainless.key.SubkeyIdentifier;
import javax.annotation.Nonnull;
@ -60,6 +61,11 @@ public final class GnuPGDummyKeyUtil {
return hardwareBackedKeys;
}
public static Builder modify(@Nonnull OpenPGPKey key)
{
return modify(key.getPGPSecretKeyRing());
}
/**
* Modify the given {@link PGPSecretKeyRing}.
*

View file

@ -11,7 +11,10 @@ import org.bouncycastle.openpgp.PGPPublicKeyRing
import org.bouncycastle.openpgp.PGPSecretKeyRing
import org.bouncycastle.openpgp.PGPSignature
import org.bouncycastle.openpgp.api.OpenPGPApi
import org.bouncycastle.openpgp.api.OpenPGPCertificate
import org.bouncycastle.openpgp.api.OpenPGPImplementation
import org.bouncycastle.openpgp.api.OpenPGPKey
import org.bouncycastle.openpgp.api.OpenPGPKeyReader
import org.bouncycastle.openpgp.api.bc.BcOpenPGPApi
import org.pgpainless.algorithm.OpenPGPKeyVersion
import org.pgpainless.bouncycastle.PolicyAdapter
@ -43,6 +46,14 @@ class PGPainless(
fun generateKey(version: OpenPGPKeyVersion = OpenPGPKeyVersion.v4): KeyRingTemplates =
KeyRingTemplates(version)
fun readKey(): OpenPGPKeyReader = api.readKeyOrCertificate()
fun toKey(secretKeyRing: PGPSecretKeyRing): OpenPGPKey =
OpenPGPKey(secretKeyRing, implementation)
fun toCertificate(publicKeyRing: PGPPublicKeyRing): OpenPGPCertificate =
OpenPGPCertificate(publicKeyRing, implementation)
companion object {
@Volatile private var instance: PGPainless? = null
@ -81,7 +92,9 @@ class PGPainless(
*
* @return builder
*/
@JvmStatic fun readKeyRing() = KeyRingReader()
@Deprecated("Use readKey() instead.", replaceWith = ReplaceWith("readKey()"))
@JvmStatic
fun readKeyRing() = KeyRingReader()
/**
* Extract a public key certificate from a secret key.
@ -90,6 +103,7 @@ class PGPainless(
* @return public key certificate
*/
@JvmStatic
@Deprecated("Use toKey() and then .toCertificate() instead.")
fun extractCertificate(secretKey: PGPSecretKeyRing) =
KeyRingUtils.publicKeyRingFrom(secretKey)
@ -190,6 +204,9 @@ class PGPainless(
fun inspectKeyRing(key: PGPKeyRing, referenceTime: Date = Date()) =
KeyRingInfo(key, referenceTime)
fun inspectKeyRing(key: OpenPGPKey, referenceTime: Date = Date()) =
KeyRingInfo(key, getPolicy(), referenceTime)
/**
* Access, and make changes to PGPainless policy on acceptable/default algorithms etc.
*

View file

@ -0,0 +1,8 @@
package org.pgpainless.bouncycastle.extensions
import org.bouncycastle.openpgp.PGPOnePassSignature
import org.bouncycastle.openpgp.api.OpenPGPCertificate
import org.bouncycastle.openpgp.api.OpenPGPCertificate.OpenPGPComponentKey
fun OpenPGPCertificate.getSigningKeyFor(ops: PGPOnePassSignature): OpenPGPComponentKey? =
this.getKey(ops.keyIdentifier)

View file

@ -0,0 +1,8 @@
package org.pgpainless.bouncycastle.extensions
import org.bouncycastle.openpgp.PGPPublicKeyEncryptedData
import org.bouncycastle.openpgp.api.OpenPGPKey
import org.bouncycastle.openpgp.api.OpenPGPKey.OpenPGPSecretKey
fun OpenPGPKey.getSecretKeyFor(pkesk: PGPPublicKeyEncryptedData): OpenPGPSecretKey? =
this.getSecretKey(pkesk.keyIdentifier)

View file

@ -73,7 +73,4 @@ fun PGPSecretKeyRing.getSecretKeyFor(onePassSignature: PGPOnePassSignature): PGP
this.getSecretKey(onePassSignature.keyID)
fun PGPSecretKeyRing.getSecretKeyFor(pkesk: PGPPublicKeyEncryptedData): PGPSecretKey? =
when (pkesk.version) {
3 -> this.getSecretKey(pkesk.keyID)
else -> throw NotImplementedError("Version 6 PKESKs are not yet supported.")
}
this.getSecretKey(pkesk.keyIdentifier)

View file

@ -4,11 +4,16 @@
package org.pgpainless.decryption_verification
import org.bouncycastle.bcpg.KeyIdentifier
import java.io.IOException
import java.io.InputStream
import java.util.*
import org.bouncycastle.openpgp.*
import org.bouncycastle.openpgp.api.OpenPGPCertificate
import org.bouncycastle.openpgp.api.OpenPGPImplementation
import org.bouncycastle.openpgp.api.OpenPGPKey
import org.bouncycastle.openpgp.operator.PublicKeyDataDecryptorFactory
import org.pgpainless.PGPainless
import org.pgpainless.bouncycastle.extensions.getPublicKeyFor
import org.pgpainless.decryption_verification.cleartext_signatures.InMemoryMultiPassStrategy
import org.pgpainless.decryption_verification.cleartext_signatures.MultiPassStrategy
@ -19,7 +24,9 @@ import org.pgpainless.util.Passphrase
import org.pgpainless.util.SessionKey
/** Options for decryption and signature verification. */
class ConsumerOptions {
class ConsumerOptions(
private val implementation: OpenPGPImplementation
) {
private var ignoreMDCErrors = false
var isDisableAsciiArmorCRC = false
@ -34,7 +41,7 @@ class ConsumerOptions {
private var sessionKey: SessionKey? = null
private val customDecryptorFactories =
mutableMapOf<SubkeyIdentifier, PublicKeyDataDecryptorFactory>()
private val decryptionKeys = mutableMapOf<PGPSecretKeyRing, SecretKeyRingProtector>()
private val decryptionKeys = mutableMapOf<OpenPGPKey, SecretKeyRingProtector>()
private val decryptionPassphrases = mutableSetOf<Passphrase>()
private var missingKeyPassphraseStrategy = MissingKeyPassphraseStrategy.INTERACTIVE
private var multiPassStrategy: MultiPassStrategy = InMemoryMultiPassStrategy()
@ -65,6 +72,10 @@ class ConsumerOptions {
fun getVerifyNotAfter() = verifyNotAfter
fun addVerificationCert(verificationCert: OpenPGPCertificate): ConsumerOptions = apply {
this.certificates.addCertificate(verificationCert)
}
/**
* Add a certificate (public key ring) for signature verification.
*
@ -155,6 +166,12 @@ class ConsumerOptions {
fun getSessionKey() = sessionKey
@JvmOverloads
fun addDecryptionKey(
key: OpenPGPKey,
protector: SecretKeyRingProtector = SecretKeyRingProtector.unprotectedKeys()
) = apply { decryptionKeys[key] = protector }
/**
* Add a key for message decryption. If the key is encrypted, the [SecretKeyRingProtector] is
* used to decrypt it when needed.
@ -167,7 +184,7 @@ class ConsumerOptions {
fun addDecryptionKey(
key: PGPSecretKeyRing,
protector: SecretKeyRingProtector = SecretKeyRingProtector.unprotectedKeys()
) = apply { decryptionKeys[key] = protector }
) = addDecryptionKey(OpenPGPKey(key, implementation), protector)
/**
* Add the keys in the provided key collection for message decryption.
@ -270,7 +287,7 @@ class ConsumerOptions {
* @param decryptionKeyRing secret key
* @return protector for that particular secret key
*/
fun getSecretKeyProtector(decryptionKeyRing: PGPSecretKeyRing): SecretKeyRingProtector? {
fun getSecretKeyProtector(decryptionKeyRing: OpenPGPKey): SecretKeyRingProtector? {
return decryptionKeys[decryptionKeyRing]
}
@ -378,14 +395,20 @@ class ConsumerOptions {
* available signer certificates.
*/
class CertificateSource {
private val explicitCertificates: MutableSet<PGPPublicKeyRing> = mutableSetOf()
private val explicitCertificates: MutableSet<OpenPGPCertificate> = mutableSetOf()
/**
* Add a certificate as verification cert explicitly.
*
* @param certificate certificate
*/
fun addCertificate(certificate: PGPPublicKeyRing) {
fun addCertificate(certificate: PGPPublicKeyRing,
implementation: OpenPGPImplementation = PGPainless.getInstance().implementation
) {
explicitCertificates.add(OpenPGPCertificate(certificate, implementation))
}
fun addCertificate(certificate: OpenPGPCertificate) {
explicitCertificates.add(certificate)
}
@ -394,7 +417,7 @@ class ConsumerOptions {
*
* @return explicitly set verification certs
*/
fun getExplicitCertificates(): Set<PGPPublicKeyRing> {
fun getExplicitCertificates(): Set<OpenPGPCertificate> {
return explicitCertificates.toSet()
}
@ -406,15 +429,23 @@ class ConsumerOptions {
* @param keyId key id
* @return certificate
*/
fun getCertificate(keyId: Long): PGPPublicKeyRing? {
return explicitCertificates.firstOrNull { it.getPublicKey(keyId) != null }
fun getCertificate(keyId: Long): OpenPGPCertificate? {
return getCertificate(KeyIdentifier(keyId))
}
fun getCertificate(signature: PGPSignature): PGPPublicKeyRing? =
explicitCertificates.firstOrNull { it.getPublicKeyFor(signature) != null }
fun getCertificate(identifier: KeyIdentifier): OpenPGPCertificate? {
return explicitCertificates.firstOrNull { it.getKey(identifier) != null }
}
fun getCertificate(signature: PGPSignature): OpenPGPCertificate? =
explicitCertificates.firstOrNull { it.getSigningKeyFor(signature) != null }
}
companion object {
@JvmStatic fun get() = ConsumerOptions()
@JvmStatic
@JvmOverloads
fun get(
implementation: OpenPGPImplementation = PGPainless.getInstance().implementation
) = ConsumerOptions(implementation)
}
}

View file

@ -4,10 +4,12 @@
package org.pgpainless.decryption_verification
import org.bouncycastle.bcpg.KeyIdentifier
import java.util.*
import javax.annotation.Nonnull
import org.bouncycastle.openpgp.PGPKeyRing
import org.bouncycastle.openpgp.PGPLiteralData
import org.bouncycastle.openpgp.api.OpenPGPCertificate
import org.pgpainless.algorithm.CompressionAlgorithm
import org.pgpainless.algorithm.StreamEncoding
import org.pgpainless.algorithm.SymmetricKeyAlgorithm
@ -47,9 +49,15 @@ class MessageMetadata(val message: Message) {
if (encryptionAlgorithm == null) false
else encryptionAlgorithm != SymmetricKeyAlgorithm.NULL
fun isEncryptedFor(keys: PGPKeyRing): Boolean {
fun isEncryptedFor(cert: OpenPGPCertificate): Boolean {
return encryptionLayers.asSequence().any {
it.recipients.any { keyId -> keys.getPublicKey(keyId) != null }
it.recipients.any { keyId -> cert.getKey(KeyIdentifier(keyId)) != null }
}
}
fun isEncryptedFor(cert: PGPKeyRing): Boolean {
return encryptionLayers.asSequence().any {
it.recipients.any { keyId -> cert.getPublicKey(keyId) != null }
}
}
@ -270,6 +278,9 @@ class MessageMetadata(val message: Message) {
fun isVerifiedSignedBy(keys: PGPKeyRing) =
verifiedSignatures.any { keys.matches(it.signingKey) }
fun isVerifiedSignedBy(cert: OpenPGPCertificate) =
verifiedSignatures.any { cert.pgpKeyRing.matches(it.signingKey) }
fun isVerifiedDetachedSignedBy(fingerprint: OpenPgpFingerprint) =
verifiedDetachedSignatures.any { it.signingKey.matches(fingerprint) }

View file

@ -18,6 +18,7 @@ import org.bouncycastle.openpgp.PGPCompressedData
import org.bouncycastle.openpgp.PGPEncryptedData
import org.bouncycastle.openpgp.PGPEncryptedDataList
import org.bouncycastle.openpgp.PGPException
import org.bouncycastle.openpgp.PGPKeyPair
import org.bouncycastle.openpgp.PGPOnePassSignature
import org.bouncycastle.openpgp.PGPPBEEncryptedData
import org.bouncycastle.openpgp.PGPPrivateKey
@ -27,6 +28,10 @@ import org.bouncycastle.openpgp.PGPPublicKeyRing
import org.bouncycastle.openpgp.PGPSecretKey
import org.bouncycastle.openpgp.PGPSecretKeyRing
import org.bouncycastle.openpgp.PGPSignature
import org.bouncycastle.openpgp.api.OpenPGPCertificate
import org.bouncycastle.openpgp.api.OpenPGPKey
import org.bouncycastle.openpgp.api.OpenPGPKey.OpenPGPSecretKey
import org.bouncycastle.openpgp.api.OpenPGPSignature.OpenPGPDocumentSignature
import org.bouncycastle.openpgp.operator.PBEDataDecryptorFactory
import org.bouncycastle.openpgp.operator.PublicKeyDataDecryptorFactory
import org.bouncycastle.util.io.TeeInputStream
@ -37,6 +42,7 @@ import org.pgpainless.algorithm.StreamEncoding
import org.pgpainless.algorithm.SymmetricKeyAlgorithm
import org.pgpainless.bouncycastle.extensions.getPublicKeyFor
import org.pgpainless.bouncycastle.extensions.getSecretKeyFor
import org.pgpainless.bouncycastle.extensions.getSigningKeyFor
import org.pgpainless.bouncycastle.extensions.issuerKeyId
import org.pgpainless.bouncycastle.extensions.unlock
import org.pgpainless.decryption_verification.MessageMetadata.CompressedData
@ -66,6 +72,7 @@ import org.pgpainless.signature.consumer.SignatureValidator
import org.pgpainless.util.ArmoredInputStreamFactory
import org.pgpainless.util.SessionKey
import org.slf4j.LoggerFactory
import kotlin.math.sign
class OpenPgpMessageInputStream(
type: Type,
@ -400,30 +407,33 @@ class OpenPgpMessageInputStream(
}
val postponedDueToMissingPassphrase =
mutableListOf<Pair<PGPSecretKey, PGPPublicKeyEncryptedData>>()
mutableListOf<Pair<OpenPGPSecretKey, PGPPublicKeyEncryptedData>>()
// try (known) secret keys
esks.pkesks.forEach { pkesk ->
LOGGER.debug("Encountered PKESK for recipient ${pkesk.keyID.openPgpKeyId()}")
LOGGER.debug("Encountered PKESK for recipient ${pkesk.keyIdentifier}")
val decryptionKeyCandidates = getDecryptionKeys(pkesk)
for (decryptionKeys in decryptionKeyCandidates) {
val secretKey = decryptionKeys.getSecretKeyFor(pkesk)!!
val decryptionKeyId = SubkeyIdentifier(decryptionKeys, secretKey.keyID)
if (hasUnsupportedS2KSpecifier(secretKey, decryptionKeyId)) {
if (hasUnsupportedS2KSpecifier(secretKey)) {
continue
}
LOGGER.debug("Attempt decryption using secret key $decryptionKeyId")
LOGGER.debug("Attempt decryption using secret key ${decryptionKeys.keyIdentifier}")
val protector = options.getSecretKeyProtector(decryptionKeys) ?: continue
if (!protector.hasPassphraseFor(secretKey.keyID)) {
if (!protector.hasPassphraseFor(secretKey.keyIdentifier)) {
LOGGER.debug(
"Missing passphrase for key $decryptionKeyId. Postponing decryption until all other keys were tried.")
"Missing passphrase for key ${decryptionKeys.keyIdentifier}. Postponing decryption until all other keys were tried.")
postponedDueToMissingPassphrase.add(secretKey to pkesk)
continue
}
val privateKey = secretKey.unlock(protector)
if (decryptWithPrivateKey(esks, privateKey, decryptionKeyId, pkesk)) {
if (decryptWithPrivateKey(
esks,
privateKey,
SubkeyIdentifier(secretKey.openPGPKey.pgpSecretKeyRing, secretKey.keyIdentifier),
pkesk)) {
return true
}
}
@ -431,24 +441,27 @@ class OpenPgpMessageInputStream(
// try anonymous secret keys
for (pkesk in esks.anonPkesks) {
for ((decryptionKeys, secretKey) in findPotentialDecryptionKeys(pkesk)) {
val decryptionKeyId = SubkeyIdentifier(decryptionKeys, secretKey.keyID)
if (hasUnsupportedS2KSpecifier(secretKey, decryptionKeyId)) {
for (decryptionKeys in findPotentialDecryptionKeys(pkesk)) {
if (hasUnsupportedS2KSpecifier(decryptionKeys)) {
continue
}
LOGGER.debug("Attempt decryption of anonymous PKESK with key $decryptionKeyId.")
val protector = options.getSecretKeyProtector(decryptionKeys) ?: continue
LOGGER.debug("Attempt decryption of anonymous PKESK with key $decryptionKeys.")
val protector = options.getSecretKeyProtector(decryptionKeys.openPGPKey) ?: continue
if (!protector.hasPassphraseFor(secretKey.keyID)) {
if (!protector.hasPassphraseFor(decryptionKeys.keyIdentifier)) {
LOGGER.debug(
"Missing passphrase for key $decryptionKeyId. Postponing decryption until all other keys were tried.")
postponedDueToMissingPassphrase.add(secretKey to pkesk)
"Missing passphrase for key ${decryptionKeys.keyIdentifier}. Postponing decryption until all other keys were tried.")
postponedDueToMissingPassphrase.add(decryptionKeys to pkesk)
continue
}
val privateKey = secretKey.unlock(protector)
if (decryptWithPrivateKey(esks, privateKey, decryptionKeyId, pkesk)) {
val privateKey = decryptionKeys.unlock(protector)
if (decryptWithPrivateKey(
esks,
privateKey,
SubkeyIdentifier(decryptionKeys.openPGPKey.pgpSecretKeyRing, privateKey.keyIdentifier),
pkesk)) {
return true
}
}
@ -463,10 +476,10 @@ class OpenPgpMessageInputStream(
} else if (options.getMissingKeyPassphraseStrategy() ==
MissingKeyPassphraseStrategy.INTERACTIVE) {
for ((secretKey, pkesk) in postponedDueToMissingPassphrase) {
val keyId = secretKey.keyID
val keyId = secretKey.keyIdentifier
val decryptionKeys = getDecryptionKey(pkesk)!!
val decryptionKeyId = SubkeyIdentifier(decryptionKeys, keyId)
if (hasUnsupportedS2KSpecifier(secretKey, decryptionKeyId)) {
val decryptionKeyId = SubkeyIdentifier(decryptionKeys.pgpSecretKeyRing, keyId)
if (hasUnsupportedS2KSpecifier(secretKey)) {
continue
}
@ -489,24 +502,23 @@ class OpenPgpMessageInputStream(
private fun decryptWithPrivateKey(
esks: SortedESKs,
privateKey: PGPPrivateKey,
privateKey: PGPKeyPair,
decryptionKeyId: SubkeyIdentifier,
pkesk: PGPPublicKeyEncryptedData
): Boolean {
val decryptorFactory =
ImplementationFactory.getInstance().getPublicKeyDataDecryptorFactory(privateKey)
ImplementationFactory.getInstance().getPublicKeyDataDecryptorFactory(privateKey.privateKey)
return decryptPKESKAndStream(esks, decryptionKeyId, decryptorFactory, pkesk)
}
private fun hasUnsupportedS2KSpecifier(
secretKey: PGPSecretKey,
decryptionKeyId: SubkeyIdentifier
secretKey: OpenPGPSecretKey
): Boolean {
val s2k = secretKey.s2K
val s2k = secretKey.pgpSecretKey.s2K
if (s2k != null) {
if (s2k.type in 100..110) {
LOGGER.debug(
"Skipping PKESK because key $decryptionKeyId has unsupported private S2K specifier ${s2k.type}")
"Skipping PKESK because key ${secretKey.keyIdentifier} has unsupported private S2K specifier ${s2k.type}")
return true
}
}
@ -672,26 +684,26 @@ class OpenPgpMessageInputStream(
return MessageMetadata((layerMetadata as Message))
}
private fun getDecryptionKey(keyId: Long): PGPSecretKeyRing? =
private fun getDecryptionKey(keyId: Long): OpenPGPKey? =
options.getDecryptionKeys().firstOrNull {
it.any { k -> k.keyID == keyId }
it.pgpSecretKeyRing.any { k -> k.keyID == keyId }
.and(
PGPainless.inspectKeyRing(it).decryptionSubkeys.any { k ->
k.keyIdentifier.keyId == keyId
})
}
private fun getDecryptionKey(pkesk: PGPPublicKeyEncryptedData): PGPSecretKeyRing? =
private fun getDecryptionKey(pkesk: PGPPublicKeyEncryptedData): OpenPGPKey? =
options.getDecryptionKeys().firstOrNull {
it.getSecretKeyFor(pkesk) != null &&
it.pgpSecretKeyRing.getSecretKeyFor(pkesk) != null &&
PGPainless.inspectKeyRing(it).decryptionSubkeys.any { subkey ->
pkesk.keyIdentifier.matches(subkey.keyIdentifier)
}
}
private fun getDecryptionKeys(pkesk: PGPPublicKeyEncryptedData): List<PGPSecretKeyRing> =
private fun getDecryptionKeys(pkesk: PGPPublicKeyEncryptedData): List<OpenPGPKey> =
options.getDecryptionKeys().filter {
it.getSecretKeyFor(pkesk) != null &&
it.pgpSecretKeyRing.getSecretKeyFor(pkesk) != null &&
PGPainless.inspectKeyRing(it).decryptionSubkeys.any { subkey ->
pkesk.keyIdentifier.matches(subkey.keyIdentifier)
}
@ -699,15 +711,15 @@ class OpenPgpMessageInputStream(
private fun findPotentialDecryptionKeys(
pkesk: PGPPublicKeyEncryptedData
): List<Pair<PGPSecretKeyRing, PGPSecretKey>> {
): List<OpenPGPSecretKey> {
val algorithm = pkesk.algorithm
val candidates = mutableListOf<Pair<PGPSecretKeyRing, PGPSecretKey>>()
val candidates = mutableListOf<OpenPGPSecretKey>()
options.getDecryptionKeys().forEach {
val info = PGPainless.inspectKeyRing(it)
for (key in info.decryptionSubkeys) {
if (key.pgpPublicKey.algorithm == algorithm &&
info.isSecretKeyAvailable(key.keyIdentifier)) {
candidates.add(it to it.getSecretKey(key.keyIdentifier))
candidates.add(it.getSecretKey(key.keyIdentifier))
}
}
}
@ -753,8 +765,8 @@ class OpenPgpMessageInputStream(
}
private class Signatures(val options: ConsumerOptions) : OutputStream() {
val detachedSignatures = mutableListOf<SignatureCheck>()
val prependedSignatures = mutableListOf<SignatureCheck>()
val detachedSignatures = mutableListOf<OpenPGPDocumentSignature>()
val prependedSignatures = mutableListOf<OpenPGPDocumentSignature>()
val onePassSignatures = mutableListOf<OnePassSignatureCheck>()
val opsUpdateStack = ArrayDeque<MutableList<OnePassSignatureCheck>>()
var literalOPS = mutableListOf<OnePassSignatureCheck>()
@ -798,22 +810,21 @@ class OpenPgpMessageInputStream(
}
}
fun initializeSignature(signature: PGPSignature): SignatureCheck? {
fun initializeSignature(signature: PGPSignature): OpenPGPDocumentSignature? {
val certificate = findCertificate(signature) ?: return null
val publicKey = certificate.getPublicKeyFor(signature) ?: return null
val verifierKey = SubkeyIdentifier(certificate, publicKey.keyID)
initialize(signature, publicKey)
return SignatureCheck(signature, certificate, verifierKey)
val publicKey = certificate.getSigningKeyFor(signature) ?: return null
initialize(signature, publicKey.pgpPublicKey)
return OpenPGPDocumentSignature(signature, publicKey)
}
fun addOnePassSignature(signature: PGPOnePassSignature) {
val certificate = findCertificate(signature)
if (certificate != null) {
val publicKey = certificate.getPublicKeyFor(signature)
val publicKey = certificate.getSigningKeyFor(signature)
if (publicKey != null) {
val ops = OnePassSignatureCheck(signature, certificate)
initialize(signature, publicKey)
initialize(signature, publicKey.pgpPublicKey)
onePassSignatures.add(ops)
literalOPS.add(ops)
}
@ -844,7 +855,7 @@ class OpenPgpMessageInputStream(
val verification =
SignatureVerification(
signature,
SubkeyIdentifier(check.verificationKeys, check.onePassSignature.keyID))
SubkeyIdentifier(check.verificationKeys.pgpPublicKeyRing, check.onePassSignature.keyIdentifier))
try {
SignatureValidator.signatureWasCreatedInBounds(
@ -882,7 +893,7 @@ class OpenPgpMessageInputStream(
opsUpdateStack.removeFirst()
}
private fun findCertificate(signature: PGPSignature): PGPPublicKeyRing? {
private fun findCertificate(signature: PGPSignature): OpenPGPCertificate? {
val cert = options.getCertificateSource().getCertificate(signature)
if (cert != null) {
return cert
@ -896,7 +907,7 @@ class OpenPgpMessageInputStream(
return null // TODO: Missing cert for sig
}
private fun findCertificate(signature: PGPOnePassSignature): PGPPublicKeyRing? {
private fun findCertificate(signature: PGPOnePassSignature): OpenPGPCertificate? {
val cert = options.getCertificateSource().getCertificate(signature.keyID)
if (cert != null) {
return cert
@ -977,7 +988,7 @@ class OpenPgpMessageInputStream(
for (prepended in prependedSignatures) {
val verification =
SignatureVerification(prepended.signature, prepended.signingKeyIdentifier)
SignatureVerification(prepended.signature, prepended.keyIdentifier)
try {
SignatureValidator.signatureWasCreatedInBounds(
options.getVerifyNotBefore(), options.getVerifyNotAfter())

View file

@ -4,15 +4,17 @@
package org.pgpainless.key.protection
import kotlin.jvm.Throws
import org.bouncycastle.bcpg.KeyIdentifier
import org.bouncycastle.openpgp.PGPException
import org.bouncycastle.openpgp.PGPSecretKey
import org.bouncycastle.openpgp.PGPSecretKeyRing
import org.bouncycastle.openpgp.api.KeyPassphraseProvider
import org.bouncycastle.openpgp.operator.PBESecretKeyDecryptor
import org.bouncycastle.openpgp.operator.PBESecretKeyEncryptor
import org.pgpainless.key.protection.passphrase_provider.SecretKeyPassphraseProvider
import org.pgpainless.key.protection.passphrase_provider.SolitaryPassphraseProvider
import org.pgpainless.util.Passphrase
import kotlin.Throws
/**
* Task of the [SecretKeyRingProtector] is to map encryptor/decryptor objects to key-ids.
@ -22,7 +24,7 @@ import org.pgpainless.util.Passphrase
* While it is easy to create an implementation of this interface that fits your needs, there are a
* bunch of implementations ready for use.
*/
interface SecretKeyRingProtector {
interface SecretKeyRingProtector : KeyPassphraseProvider {
/**
* Returns true, if the protector has a passphrase for the key with the given key-id.
@ -30,7 +32,9 @@ interface SecretKeyRingProtector {
* @param keyId key id
* @return true if it has a passphrase, false otherwise
*/
fun hasPassphraseFor(keyId: Long): Boolean
fun hasPassphraseFor(keyId: Long): Boolean = hasPassphraseFor(KeyIdentifier(keyId))
fun hasPassphraseFor(keyIdentifier: KeyIdentifier): Boolean
/**
* Return a decryptor for the key of id `keyId`. This method returns null if the key is
@ -39,7 +43,10 @@ interface SecretKeyRingProtector {
* @param keyId id of the key
* @return decryptor for the key
*/
@Throws(PGPException::class) fun getDecryptor(keyId: Long): PBESecretKeyDecryptor?
@Throws(PGPException::class) fun getDecryptor(keyId: Long): PBESecretKeyDecryptor? =
getDecryptor(KeyIdentifier(keyId))
@Throws(PGPException::class) fun getDecryptor(keyIdentifier: KeyIdentifier): PBESecretKeyDecryptor?
/**
* Return an encryptor for the key of id `keyId`. This method returns null if the key is
@ -48,7 +55,10 @@ interface SecretKeyRingProtector {
* @param keyId id of the key
* @return encryptor for the key
*/
@Throws(PGPException::class) fun getEncryptor(keyId: Long): PBESecretKeyEncryptor?
@Throws(PGPException::class) fun getEncryptor(keyId: Long): PBESecretKeyEncryptor? =
getEncryptor(KeyIdentifier(keyId))
@Throws(PGPException::class) fun getEncryptor(keyIdentifier: KeyIdentifier): PBESecretKeyEncryptor?
companion object {

View file

@ -3,14 +3,21 @@
// SPDX-License-Identifier: Apache-2.0
package org.pgpainless.key.protection
import org.bouncycastle.bcpg.KeyIdentifier
import org.bouncycastle.openpgp.api.OpenPGPKey
import org.bouncycastle.openpgp.operator.PBESecretKeyDecryptor
import org.bouncycastle.openpgp.operator.PBESecretKeyEncryptor
/**
* Implementation of the [SecretKeyRingProtector] which assumes that all handled keys are not
* password protected.
*/
class UnprotectedKeysProtector : SecretKeyRingProtector {
override fun hasPassphraseFor(keyId: Long) = true
override fun hasPassphraseFor(keyIdentifier: KeyIdentifier): Boolean = true
override fun getDecryptor(keyId: Long) = null
override fun getDecryptor(keyIdentifier: KeyIdentifier): PBESecretKeyDecryptor? = null
override fun getEncryptor(keyId: Long) = null
override fun getEncryptor(keyIdentifier: KeyIdentifier): PBESecretKeyEncryptor? = null
override fun getKeyPassword(p0: OpenPGPKey.OpenPGPSecretKey?): CharArray? = null
}

View file

@ -7,6 +7,9 @@ package org.pgpainless.signature.consumer
import org.bouncycastle.openpgp.PGPOnePassSignature
import org.bouncycastle.openpgp.PGPPublicKeyRing
import org.bouncycastle.openpgp.PGPSignature
import org.bouncycastle.openpgp.api.OpenPGPCertificate
import org.bouncycastle.openpgp.api.OpenPGPCertificate.OpenPGPComponentKey
import org.pgpainless.bouncycastle.extensions.getSigningKeyFor
import org.pgpainless.key.SubkeyIdentifier
/**
@ -20,7 +23,7 @@ import org.pgpainless.key.SubkeyIdentifier
*/
data class OnePassSignatureCheck(
val onePassSignature: PGPOnePassSignature,
val verificationKeys: PGPPublicKeyRing,
val verificationKeys: OpenPGPCertificate,
var signature: PGPSignature? = null
) {
@ -30,5 +33,5 @@ data class OnePassSignatureCheck(
* @return signing key fingerprint
*/
val signingKey: SubkeyIdentifier
get() = SubkeyIdentifier(verificationKeys, onePassSignature.keyID)
get() = SubkeyIdentifier(verificationKeys.pgpPublicKeyRing, onePassSignature.keyID)
}

View file

@ -17,6 +17,8 @@ import org.bouncycastle.bcpg.S2K;
import org.bouncycastle.bcpg.SecretKeyPacket;
import org.bouncycastle.openpgp.PGPSecretKey;
import org.bouncycastle.openpgp.PGPSecretKeyRing;
import org.bouncycastle.openpgp.api.OpenPGPKey;
import org.bouncycastle.openpgp.api.OpenPGPKeyReader;
import org.junit.jupiter.api.Test;
import org.pgpainless.PGPainless;
import org.pgpainless.key.SubkeyIdentifier;
@ -178,8 +180,9 @@ public class GnuPGDummyKeyUtilTest {
@Test
public void testMoveAllKeysToCard() throws IOException {
PGPSecretKeyRing secretKeys = PGPainless.readKeyRing().secretKeyRing(FULL_KEY);
PGPSecretKeyRing expected = PGPainless.readKeyRing().secretKeyRing(ALL_KEYS_ON_CARD);
OpenPGPKeyReader reader = PGPainless.getInstance().readKey();
OpenPGPKey secretKeys = reader.parseKey(FULL_KEY);
OpenPGPKey expected = reader.parseKey(ALL_KEYS_ON_CARD);
PGPSecretKeyRing onCard = GnuPGDummyKeyUtil.modify(secretKeys)
.divertPrivateKeysToCard(GnuPGDummyKeyUtil.KeyFilter.any(), cardSerial);
@ -190,46 +193,50 @@ public class GnuPGDummyKeyUtilTest {
assertEquals(S2K.GNU_PROTECTION_MODE_DIVERT_TO_CARD, s2K.getProtectionMode());
}
assertArrayEquals(expected.getEncoded(), onCard.getEncoded());
assertArrayEquals(expected.getPGPSecretKeyRing().getEncoded(), onCard.getEncoded());
}
@Test
public void testMovePrimaryKeyToCard() throws IOException {
PGPSecretKeyRing secretKeys = PGPainless.readKeyRing().secretKeyRing(FULL_KEY);
PGPSecretKeyRing expected = PGPainless.readKeyRing().secretKeyRing(PRIMARY_KEY_ON_CARD);
OpenPGPKeyReader reader = PGPainless.getInstance().readKey();
OpenPGPKey secretKeys = reader.parseKey(FULL_KEY);
OpenPGPKey expected = reader.parseKey(PRIMARY_KEY_ON_CARD);
PGPSecretKeyRing onCard = GnuPGDummyKeyUtil.modify(secretKeys)
.divertPrivateKeysToCard(GnuPGDummyKeyUtil.KeyFilter.only(primaryKeyId), cardSerial);
assertArrayEquals(expected.getEncoded(), onCard.getEncoded());
assertArrayEquals(expected.getPGPSecretKeyRing().getEncoded(), onCard.getEncoded());
}
@Test
public void testMoveEncryptionKeyToCard() throws IOException {
PGPSecretKeyRing secretKeys = PGPainless.readKeyRing().secretKeyRing(FULL_KEY);
PGPSecretKeyRing expected = PGPainless.readKeyRing().secretKeyRing(ENCRYPTION_KEY_ON_CARD);
OpenPGPKeyReader reader = PGPainless.getInstance().readKey();
OpenPGPKey secretKeys = reader.parseKey(FULL_KEY);
OpenPGPKey expected = reader.parseKey(ENCRYPTION_KEY_ON_CARD);
PGPSecretKeyRing onCard = GnuPGDummyKeyUtil.modify(secretKeys)
.divertPrivateKeysToCard(GnuPGDummyKeyUtil.KeyFilter.only(encryptionKeyId), cardSerial);
assertArrayEquals(expected.getEncoded(), onCard.getEncoded());
assertArrayEquals(expected.getPGPSecretKeyRing().getEncoded(), onCard.getEncoded());
}
@Test
public void testMoveSigningKeyToCard() throws IOException {
PGPSecretKeyRing secretKeys = PGPainless.readKeyRing().secretKeyRing(FULL_KEY);
PGPSecretKeyRing expected = PGPainless.readKeyRing().secretKeyRing(SIGNATURE_KEY_ON_CARD);
OpenPGPKeyReader reader = PGPainless.getInstance().readKey();
OpenPGPKey secretKeys = reader.parseKey(FULL_KEY);
OpenPGPKey expected = reader.parseKey(SIGNATURE_KEY_ON_CARD);
PGPSecretKeyRing onCard = GnuPGDummyKeyUtil.modify(secretKeys)
.divertPrivateKeysToCard(GnuPGDummyKeyUtil.KeyFilter.only(signatureKeyId), cardSerial);
assertArrayEquals(expected.getEncoded(), onCard.getEncoded());
assertArrayEquals(expected.getPGPSecretKeyRing().getEncoded(), onCard.getEncoded());
}
@Test
public void testRemoveAllKeys() throws IOException {
PGPSecretKeyRing secretKeys = PGPainless.readKeyRing().secretKeyRing(FULL_KEY);
PGPSecretKeyRing expected = PGPainless.readKeyRing().secretKeyRing(ALL_KEYS_REMOVED);
OpenPGPKeyReader reader = PGPainless.getInstance().readKey();
OpenPGPKey secretKeys = reader.parseKey(FULL_KEY);
OpenPGPKey expected = reader.parseKey(ALL_KEYS_REMOVED);
PGPSecretKeyRing removedSecretKeys = GnuPGDummyKeyUtil.modify(secretKeys)
.removePrivateKeys(GnuPGDummyKeyUtil.KeyFilter.any());
@ -240,33 +247,35 @@ public class GnuPGDummyKeyUtilTest {
assertEquals(GnuPGDummyExtension.NO_PRIVATE_KEY.getId(), s2k.getProtectionMode());
}
assertArrayEquals(expected.getEncoded(), removedSecretKeys.getEncoded());
assertArrayEquals(expected.getPGPSecretKeyRing().getEncoded(), removedSecretKeys.getEncoded());
}
@Test
public void testGetSingleIdOfHardwareBackedKey() throws IOException {
PGPSecretKeyRing secretKeys = PGPainless.readKeyRing().secretKeyRing(FULL_KEY);
assertTrue(GnuPGDummyKeyUtil.getIdsOfKeysWithGnuPGS2KDivertedToCard(secretKeys).isEmpty());
OpenPGPKeyReader reader = PGPainless.getInstance().readKey();
OpenPGPKey secretKeys = reader.parseKey(FULL_KEY);
assertTrue(GnuPGDummyKeyUtil.getIdsOfKeysWithGnuPGS2KDivertedToCard(secretKeys.getPGPSecretKeyRing()).isEmpty());
PGPSecretKeyRing withHardwareBackedEncryptionKey = GnuPGDummyKeyUtil.modify(secretKeys)
.divertPrivateKeysToCard(GnuPGDummyKeyUtil.KeyFilter.only(encryptionKeyId));
Set<SubkeyIdentifier> hardwareBackedKeys = GnuPGDummyKeyUtil
.getIdsOfKeysWithGnuPGS2KDivertedToCard(withHardwareBackedEncryptionKey);
assertEquals(Collections.singleton(new SubkeyIdentifier(secretKeys, encryptionKeyId)), hardwareBackedKeys);
assertEquals(Collections.singleton(new SubkeyIdentifier(secretKeys.getPGPSecretKeyRing(), encryptionKeyId)), hardwareBackedKeys);
}
@Test
public void testGetIdsOfFullyHardwareBackedKey() throws IOException {
PGPSecretKeyRing secretKeys = PGPainless.readKeyRing().secretKeyRing(FULL_KEY);
assertTrue(GnuPGDummyKeyUtil.getIdsOfKeysWithGnuPGS2KDivertedToCard(secretKeys).isEmpty());
OpenPGPKeyReader reader = PGPainless.getInstance().readKey();
OpenPGPKey secretKeys = reader.parseKey(FULL_KEY);
assertTrue(GnuPGDummyKeyUtil.getIdsOfKeysWithGnuPGS2KDivertedToCard(secretKeys.getPGPSecretKeyRing()).isEmpty());
PGPSecretKeyRing withHardwareBackedEncryptionKey = GnuPGDummyKeyUtil.modify(secretKeys)
.divertPrivateKeysToCard(GnuPGDummyKeyUtil.KeyFilter.any());
Set<SubkeyIdentifier> expected = new HashSet<>();
for (PGPSecretKey key : secretKeys) {
expected.add(new SubkeyIdentifier(secretKeys, key.getKeyID()));
for (PGPSecretKey key : secretKeys.getPGPSecretKeyRing()) {
expected.add(new SubkeyIdentifier(secretKeys.getPGPSecretKeyRing(), key.getKeyID()));
}
Set<SubkeyIdentifier> hardwareBackedKeys = GnuPGDummyKeyUtil

View file

@ -15,8 +15,9 @@ import java.io.IOException;
import java.nio.charset.StandardCharsets;
import org.bouncycastle.openpgp.PGPException;
import org.bouncycastle.openpgp.PGPPublicKeyRing;
import org.bouncycastle.openpgp.PGPSecretKeyRing;
import org.bouncycastle.openpgp.api.OpenPGPCertificate;
import org.bouncycastle.openpgp.api.OpenPGPKey;
import org.bouncycastle.openpgp.api.OpenPGPKeyReader;
import org.bouncycastle.util.io.Streams;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
@ -134,15 +135,16 @@ public class DecryptOrVerify {
"=9PiO\n" +
"-----END PGP MESSAGE-----";
private static PGPSecretKeyRing secretKey;
private static PGPPublicKeyRing certificate;
private static OpenPGPKey secretKey;
private static OpenPGPCertificate certificate;
@BeforeAll
public static void prepare() throws IOException {
OpenPGPKeyReader reader = PGPainless.getInstance().readKey();
// read the secret key
secretKey = PGPainless.readKeyRing().secretKeyRing(KEY);
secretKey = reader.parseKey(KEY);
// certificate is the public part of the key
certificate = PGPainless.extractCertificate(secretKey);
certificate = secretKey.toCertificate();
}
/**
@ -153,7 +155,7 @@ public class DecryptOrVerify {
*/
@Test
public void decryptMessage() throws PGPException, IOException {
ConsumerOptions consumerOptions = new ConsumerOptions()
ConsumerOptions consumerOptions = ConsumerOptions.get()
.addDecryptionKey(secretKey, keyProtector); // add the decryption key ring
ByteArrayOutputStream plaintextOut = new ByteArrayOutputStream();
@ -186,7 +188,7 @@ public class DecryptOrVerify {
*/
@Test
public void decryptMessageAndVerifySignatures() throws PGPException, IOException {
ConsumerOptions consumerOptions = new ConsumerOptions()
ConsumerOptions consumerOptions = ConsumerOptions.get()
.addDecryptionKey(secretKey, keyProtector) // provide the secret key of the recipient for decryption
.addVerificationCert(certificate); // provide the signers public key for signature verification
@ -218,7 +220,7 @@ public class DecryptOrVerify {
*/
@Test
public void verifySignatures() throws PGPException, IOException {
ConsumerOptions options = new ConsumerOptions()
ConsumerOptions options = ConsumerOptions.get()
.addVerificationCert(certificate); // provide the signers certificate for verification of signatures
for (String signed : new String[] {INBAND_SIGNED, CLEARTEXT_SIGNED}) {
@ -257,7 +259,7 @@ public class DecryptOrVerify {
SigningOptions signingOptions = SigningOptions.get();
// for cleartext signed messages, we need to add a detached signature...
signingOptions.addDetachedSignature(keyProtector, secretKey, DocumentSignatureType.CANONICAL_TEXT_DOCUMENT);
signingOptions.addDetachedSignature(keyProtector, secretKey.getPGPSecretKeyRing(), DocumentSignatureType.CANONICAL_TEXT_DOCUMENT);
ProducerOptions producerOptions = ProducerOptions.sign(signingOptions)
.setCleartextSigned(); // and declare that the message will be cleartext signed
@ -279,7 +281,7 @@ public class DecryptOrVerify {
// and pass it to the decryption stream
DecryptionStream verificationStream = PGPainless.decryptAndOrVerify()
.onInputStream(signedIn)
.withOptions(new ConsumerOptions().addVerificationCert(certificate));
.withOptions(ConsumerOptions.get().addVerificationCert(certificate));
// plain will receive the plaintext message
ByteArrayOutputStream plain = new ByteArrayOutputStream();