From 4a90b8721f45868e883bcc706fe8ca1541845e02 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Wed, 12 Feb 2025 19:32:22 +0100 Subject: [PATCH] Port ConsumerOptions, SigningOptions to new OpenPGPCertificate, OpenPGPKey classes --- .../ConsumerOptions.kt | 47 +-- .../OpenPgpMessageInputStream.kt | 19 +- .../encryption_signing/EncryptionStream.kt | 3 +- .../encryption_signing/SigningOptions.kt | 306 +++++++++++------- .../org/pgpainless/key/SubkeyIdentifier.kt | 3 + .../key/protection/UnlockSecretKey.kt | 32 ++ 6 files changed, 260 insertions(+), 150 deletions(-) diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/ConsumerOptions.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/ConsumerOptions.kt index 363bc7fa..a11e3536 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/ConsumerOptions.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/ConsumerOptions.kt @@ -12,6 +12,7 @@ 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.api.OpenPGPSignature.OpenPGPDocumentSignature import org.bouncycastle.openpgp.operator.PublicKeyDataDecryptorFactory import org.pgpainless.PGPainless import org.pgpainless.decryption_verification.cleartext_signatures.InMemoryMultiPassStrategy @@ -73,12 +74,20 @@ class ConsumerOptions { this.certificates.addCertificate(verificationCert) } + fun addVerificationCerts(verificationCerts: Collection): ConsumerOptions = + apply { + for (cert in verificationCerts) { + addVerificationCert(cert) + } + } + /** * Add a certificate (public key ring) for signature verification. * * @param verificationCert certificate for signature verification * @return options */ + @Deprecated("Pass OpenPGPCertificate instead.") fun addVerificationCert(verificationCert: PGPPublicKeyRing): ConsumerOptions = apply { this.certificates.addCertificate(verificationCert) } @@ -89,6 +98,7 @@ class ConsumerOptions { * @param verificationCerts certificates for signature verification * @return options */ + @Deprecated("Use of methods taking PGPPublicKeyRingCollections is discouraged.") fun addVerificationCerts(verificationCerts: PGPPublicKeyRingCollection): ConsumerOptions = apply { for (cert in verificationCerts) { @@ -125,6 +135,14 @@ class ConsumerOptions { } } + fun addVerificationOfDetachedSignature(signature: OpenPGPDocumentSignature): ConsumerOptions = + apply { + if (signature.issuerCertificate != null) { + addVerificationCert(signature.issuerCertificate) + } + addVerificationOfDetachedSignature(signature.signature) + } + /** * Add a detached signature for the signature verification process. * @@ -178,6 +196,7 @@ class ConsumerOptions { * @return options */ @JvmOverloads + @Deprecated("Pass OpenPGPKey instead.") fun addDecryptionKey( key: PGPSecretKeyRing, protector: SecretKeyRingProtector = SecretKeyRingProtector.unprotectedKeys(), @@ -192,6 +211,7 @@ class ConsumerOptions { * @return options */ @JvmOverloads + @Deprecated("Pass OpenPGPKey instances instead.") fun addDecryptionKeys( keys: PGPSecretKeyRingCollection, protector: SecretKeyRingProtector = SecretKeyRingProtector.unprotectedKeys() @@ -201,21 +221,6 @@ class ConsumerOptions { } } - /** - * Add a passphrase for message decryption. This passphrase will be used to try to decrypt - * messages which were symmetrically encrypted for a passphrase. - * - * See - * [Symmetrically Encrypted Data Packet](https://datatracker.ietf.org/doc/html/rfc4880#section-5.7) - * - * @param passphrase passphrase - * @return options - */ - @Deprecated( - "Deprecated in favor of addMessagePassphrase", - ReplaceWith("addMessagePassphrase(passphrase)")) - fun addDecryptionPassphrase(passphrase: Passphrase) = addMessagePassphrase(passphrase) - /** * Add a passphrase for message decryption. This passphrase will be used to try to decrypt * messages which were symmetrically encrypted for a passphrase. @@ -255,21 +260,21 @@ class ConsumerOptions { * * @return decryption keys */ - fun getDecryptionKeys() = decryptionKeys.keys.toSet() + fun getDecryptionKeys(): Set = decryptionKeys.keys.toSet() /** * Return the set of available message decryption passphrases. * * @return decryption passphrases */ - fun getDecryptionPassphrases() = decryptionPassphrases.toSet() + fun getDecryptionPassphrases(): Set = decryptionPassphrases.toSet() /** * Return an object holding available certificates for signature verification. * * @return certificate source */ - fun getCertificateSource() = certificates + fun getCertificateSource(): CertificateSource = certificates /** * Return the callback that gets called when a certificate for signature verification is @@ -277,7 +282,7 @@ class ConsumerOptions { * * @return missing public key callback */ - fun getMissingCertificateCallback() = missingCertificateCallback + fun getMissingCertificateCallback(): MissingPublicKeyCallback? = missingCertificateCallback /** * Return the [SecretKeyRingProtector] for the given [PGPSecretKeyRing]. @@ -321,7 +326,7 @@ class ConsumerOptions { this.ignoreMDCErrors = ignoreMDCErrors } - fun isIgnoreMDCErrors() = ignoreMDCErrors + fun isIgnoreMDCErrors(): Boolean = ignoreMDCErrors /** * Force PGPainless to handle the data provided by the [InputStream] as non-OpenPGP data. This @@ -337,7 +342,7 @@ class ConsumerOptions { * * @return true if non-OpenPGP data is forced */ - fun isForceNonOpenPgpData() = forceNonOpenPgpData + fun isForceNonOpenPgpData(): Boolean = forceNonOpenPgpData /** * Specify the [MissingKeyPassphraseStrategy]. This strategy defines, how missing passphrases diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/OpenPgpMessageInputStream.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/OpenPgpMessageInputStream.kt index d193003f..e4a79d5b 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/OpenPgpMessageInputStream.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/OpenPgpMessageInputStream.kt @@ -26,6 +26,7 @@ import org.bouncycastle.openpgp.PGPPublicKeyEncryptedData import org.bouncycastle.openpgp.PGPSignature import org.bouncycastle.openpgp.api.OpenPGPCertificate import org.bouncycastle.openpgp.api.OpenPGPKey +import org.bouncycastle.openpgp.api.OpenPGPKey.OpenPGPPrivateKey import org.bouncycastle.openpgp.api.OpenPGPKey.OpenPGPSecretKey import org.bouncycastle.openpgp.api.OpenPGPSignature.OpenPGPDocumentSignature import org.bouncycastle.openpgp.operator.PBEDataDecryptorFactory @@ -58,6 +59,7 @@ import org.pgpainless.exception.UnacceptableAlgorithmException import org.pgpainless.exception.WrongPassphraseException import org.pgpainless.implementation.ImplementationFactory import org.pgpainless.key.SubkeyIdentifier +import org.pgpainless.key.protection.UnlockSecretKey.Companion.unlockSecretKey import org.pgpainless.policy.Policy import org.pgpainless.signature.consumer.CertificateValidator import org.pgpainless.signature.consumer.OnePassSignatureCheck @@ -420,10 +422,15 @@ class OpenPgpMessageInputStream( continue } - val privateKey = secretKey.unlock(protector) + val privateKey = + try { + unlockSecretKey(secretKey, protector) + } catch (e: PGPException) { + throw WrongPassphraseException(secretKey.keyIdentifier, e) + } if (decryptWithPrivateKey( esks, - privateKey, + privateKey.unlockedKey, SubkeyIdentifier( secretKey.openPGPKey.pgpSecretKeyRing, secretKey.keyIdentifier), pkesk)) { @@ -451,7 +458,7 @@ class OpenPgpMessageInputStream( val privateKey = decryptionKey.unlock(protector) if (decryptWithPrivateKey( - esks, privateKey, SubkeyIdentifier(decryptionKey), pkesk)) { + esks, privateKey.unlockedKey, SubkeyIdentifier(decryptionKey), pkesk)) { return true } } @@ -476,13 +483,13 @@ class OpenPgpMessageInputStream( LOGGER.debug( "Attempt decryption with key $decryptionKeyId while interactively requesting its passphrase.") val protector = options.getSecretKeyProtector(decryptionKeys) ?: continue - val privateKey = + val privateKey: OpenPGPPrivateKey = try { - secretKey.unlock(protector) + unlockSecretKey(secretKey, protector) } catch (e: PGPException) { throw WrongPassphraseException(secretKey.keyIdentifier, e) } - if (decryptWithPrivateKey(esks, privateKey, decryptionKeyId, pkesk)) { + if (decryptWithPrivateKey(esks, privateKey.unlockedKey, decryptionKeyId, pkesk)) { return true } } diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/encryption_signing/EncryptionStream.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/encryption_signing/EncryptionStream.kt index f2617c34..5d226d06 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/encryption_signing/EncryptionStream.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/encryption_signing/EncryptionStream.kt @@ -17,6 +17,7 @@ import org.pgpainless.algorithm.CompressionAlgorithm import org.pgpainless.algorithm.StreamEncoding import org.pgpainless.algorithm.SymmetricKeyAlgorithm import org.pgpainless.implementation.ImplementationFactory +import org.pgpainless.key.SubkeyIdentifier import org.pgpainless.util.ArmoredOutputStreamFactory import org.slf4j.LoggerFactory @@ -250,7 +251,7 @@ class EncryptionStream( options.signingOptions.signingMethods.entries.reversed().forEach { (key, method) -> method.signatureGenerator.generate().let { sig -> if (method.isDetached) { - resultBuilder.addDetachedSignature(key, sig) + resultBuilder.addDetachedSignature(SubkeyIdentifier(key), sig) } if (!method.isDetached || options.isCleartextSigned) { sig.encode(signatureLayerStream) diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/encryption_signing/SigningOptions.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/encryption_signing/SigningOptions.kt index 0f193e2f..da2ebb42 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/encryption_signing/SigningOptions.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/encryption_signing/SigningOptions.kt @@ -7,19 +7,22 @@ package org.pgpainless.encryption_signing import java.util.* import org.bouncycastle.bcpg.KeyIdentifier import org.bouncycastle.openpgp.* +import org.bouncycastle.openpgp.api.OpenPGPKey +import org.bouncycastle.openpgp.api.OpenPGPKey.OpenPGPPrivateKey +import org.bouncycastle.openpgp.api.OpenPGPKey.OpenPGPSecretKey import org.pgpainless.PGPainless.Companion.getPolicy import org.pgpainless.PGPainless.Companion.inspectKeyRing import org.pgpainless.algorithm.DocumentSignatureType import org.pgpainless.algorithm.HashAlgorithm import org.pgpainless.algorithm.PublicKeyAlgorithm.Companion.requireFromId import org.pgpainless.algorithm.negotiation.HashAlgorithmNegotiator.Companion.negotiateSignatureHashAlgorithm -import org.pgpainless.bouncycastle.extensions.unlock +import org.pgpainless.bouncycastle.extensions.toOpenPGPKey import org.pgpainless.exception.KeyException import org.pgpainless.exception.KeyException.* import org.pgpainless.implementation.ImplementationFactory import org.pgpainless.key.OpenPgpFingerprint.Companion.of -import org.pgpainless.key.SubkeyIdentifier import org.pgpainless.key.protection.SecretKeyRingProtector +import org.pgpainless.key.protection.UnlockSecretKey.Companion.unlockSecretKey import org.pgpainless.policy.Policy import org.pgpainless.signature.subpackets.BaseSignatureSubpackets.Callback import org.pgpainless.signature.subpackets.SignatureSubpackets @@ -27,7 +30,7 @@ import org.pgpainless.signature.subpackets.SignatureSubpacketsHelper class SigningOptions { - val signingMethods: Map = mutableMapOf() + val signingMethods: Map = mutableMapOf() private var _hashAlgorithmOverride: HashAlgorithm? = null private var _evaluationDate: Date = Date() @@ -62,17 +65,33 @@ class SigningOptions { * Sign the message using an inline signature made by the provided signing key. * * @param signingKeyProtector protector to unlock the signing key - * @param signingKey key ring containing the signing key + * @param signingKey OpenPGPKey containing the signing (sub-)key. * @return this * @throws KeyException if something is wrong with the key * @throws PGPException if the key cannot be unlocked or a signing method cannot be created */ @Throws(KeyException::class, PGPException::class) + fun addSignature( + signingKeyProtector: SecretKeyRingProtector, + signingKey: OpenPGPKey + ): SigningOptions = apply { + addInlineSignature( + signingKeyProtector, signingKey, null, DocumentSignatureType.BINARY_DOCUMENT) + } + + /** + * Sign the message using an inline signature made by the provided signing key. + * + * @param signingKeyProtector protector to unlock the signing key + * @param signingKey key ring containing the signing key + * @return this + * @throws KeyException if something is wrong with the key + * @throws PGPException if the key cannot be unlocked or a signing method cannot be created + */ + @Deprecated("Pass an OpenPGPKey instead.") + @Throws(KeyException::class, PGPException::class) fun addSignature(signingKeyProtector: SecretKeyRingProtector, signingKey: PGPSecretKeyRing) = - apply { - addInlineSignature( - signingKeyProtector, signingKey, null, DocumentSignatureType.BINARY_DOCUMENT) - } + addSignature(signingKeyProtector, signingKey.toOpenPGPKey()) /** * Add inline signatures with all secret key rings in the provided secret key ring collection. @@ -86,6 +105,7 @@ class SigningOptions { * created */ @Throws(KeyException::class, PGPException::class) + @Deprecated("Repeatedly call addInlineSignature(), passing an OpenPGPKey instead.") fun addInlineSignatures( signingKeyProtector: SecretKeyRingProtector, signingKeys: Iterable, @@ -94,6 +114,12 @@ class SigningOptions { signingKeys.forEach { addInlineSignature(signingKeyProtector, it, null, signatureType) } } + fun addInlineSignature( + signingKeyProtector: SecretKeyRingProtector, + signingKey: OpenPGPKey, + signatureType: DocumentSignatureType = DocumentSignatureType.BINARY_DOCUMENT + ): SigningOptions = addInlineSignature(signingKeyProtector, signingKey, null, signatureType) + /** * Add an inline-signature. Inline signatures are being embedded into the message itself and can * be processed in one pass, thanks to the use of one-pass-signature packets. @@ -106,11 +132,49 @@ class SigningOptions { * @throws PGPException if the key cannot be unlocked or the signing method cannot be created */ @Throws(KeyException::class, PGPException::class) + @Deprecated("Pass an OpenPGPKey instead.") fun addInlineSignature( signingKeyProtector: SecretKeyRingProtector, signingKey: PGPSecretKeyRing, signatureType: DocumentSignatureType - ) = apply { addInlineSignature(signingKeyProtector, signingKey, null, signatureType) } + ) = addInlineSignature(signingKeyProtector, signingKey.toOpenPGPKey(), signatureType) + + fun addInlineSignature( + signingKeyProtector: SecretKeyRingProtector, + signingKey: OpenPGPKey, + userId: CharSequence? = null, + signatureType: DocumentSignatureType = DocumentSignatureType.BINARY_DOCUMENT, + subpacketsCallback: Callback? = null + ) = apply { + val keyRingInfo = inspectKeyRing(signingKey, evaluationDate) + if (userId != null && !keyRingInfo.isUserIdValid(userId)) { + throw UnboundUserIdException( + of(signingKey.pgpSecretKeyRing), + userId.toString(), + keyRingInfo.getLatestUserIdCertification(userId), + keyRingInfo.getUserIdRevocation(userId)) + } + + val signingPubKeys = keyRingInfo.signingSubkeys + if (signingPubKeys.isEmpty()) { + throw UnacceptableSigningKeyException(of(signingKey.pgpSecretKeyRing)) + } + + for (signingPubKey in signingPubKeys) { + val signingSecKey: OpenPGPSecretKey = + signingKey.getSecretKey(signingPubKey) + ?: throw MissingSecretKeyException( + of(signingKey.pgpSecretKeyRing), signingPubKey.keyIdentifier.keyId) + val signingPrivKey: OpenPGPPrivateKey = + unlockSecretKey(signingSecKey, signingKeyProtector) + val hashAlgorithms = + if (userId != null) keyRingInfo.getPreferredHashAlgorithms(userId) + else keyRingInfo.getPreferredHashAlgorithms(signingPubKey.keyIdentifier) + val hashAlgorithm: HashAlgorithm = negotiateHashAlgorithm(hashAlgorithms, getPolicy()) + addSigningMethod( + signingPrivKey, hashAlgorithm, signatureType, false, subpacketsCallback) + } + } /** * Add an inline-signature. Inline signatures are being embedded into the message itself and can @@ -129,6 +193,7 @@ class SigningOptions { * @throws KeyException if the key is invalid * @throws PGPException if the key cannot be unlocked or the signing method cannot be created */ + @Deprecated("Pass an OpenPGPKey instead.") @Throws(KeyException::class, PGPException::class) @JvmOverloads fun addInlineSignature( @@ -137,34 +202,36 @@ class SigningOptions { userId: CharSequence? = null, signatureType: DocumentSignatureType = DocumentSignatureType.BINARY_DOCUMENT, subpacketsCallback: Callback? = null - ) = apply { - val keyRingInfo = inspectKeyRing(signingKey, evaluationDate) - if (userId != null && !keyRingInfo.isUserIdValid(userId)) { - throw UnboundUserIdException( - of(signingKey), - userId.toString(), - keyRingInfo.getLatestUserIdCertification(userId), - keyRingInfo.getUserIdRevocation(userId)) - } + ) = + addInlineSignature( + signingKeyProtector, + signingKey.toOpenPGPKey(), + userId, + signatureType, + subpacketsCallback) + fun addInlineSignature( + signingKeyProtector: SecretKeyRingProtector, + signingKey: OpenPGPSecretKey, + signatureType: DocumentSignatureType = DocumentSignatureType.BINARY_DOCUMENT, + subpacketsCallback: Callback? = null + ): SigningOptions = apply { + val openPGPKey = signingKey.openPGPKey + val keyRingInfo = inspectKeyRing(openPGPKey, evaluationDate) val signingPubKeys = keyRingInfo.signingSubkeys if (signingPubKeys.isEmpty()) { - throw UnacceptableSigningKeyException(of(signingKey)) + throw UnacceptableSigningKeyException(of(openPGPKey.pgpSecretKeyRing)) } - for (signingPubKey in signingPubKeys) { - val signingSecKey: PGPSecretKey = - signingKey.getSecretKey(signingPubKey.keyIdentifier) - ?: throw MissingSecretKeyException( - of(signingKey), signingPubKey.keyIdentifier.keyId) - val signingSubkey: PGPPrivateKey = signingSecKey.unlock(signingKeyProtector) - val hashAlgorithms = - if (userId != null) keyRingInfo.getPreferredHashAlgorithms(userId) - else keyRingInfo.getPreferredHashAlgorithms(signingPubKey.keyIdentifier) - val hashAlgorithm: HashAlgorithm = negotiateHashAlgorithm(hashAlgorithms, getPolicy()) - addSigningMethod( - signingKey, signingSubkey, hashAlgorithm, signatureType, false, subpacketsCallback) + if (!signingPubKeys.any { it.keyIdentifier.matches(signingKey.keyIdentifier) }) { + throw MissingSecretKeyException( + of(openPGPKey.pgpSecretKeyRing), signingKey.keyIdentifier.keyId) } + + val signingPrivKey = unlockSecretKey(signingKey, signingKeyProtector) + val hashAlgorithms = keyRingInfo.getPreferredHashAlgorithms(signingKey.keyIdentifier) + val hashAlgorithm: HashAlgorithm = negotiateHashAlgorithm(hashAlgorithms, getPolicy()) + addSigningMethod(signingPrivKey, hashAlgorithm, signatureType, false, subpacketsCallback) } /** @@ -191,31 +258,13 @@ class SigningOptions { keyId: Long, signatureType: DocumentSignatureType = DocumentSignatureType.BINARY_DOCUMENT, subpacketsCallback: Callback? = null - ) = apply { - val keyRingInfo = inspectKeyRing(signingKey, evaluationDate) - val signingPubKeys = keyRingInfo.signingSubkeys - if (signingPubKeys.isEmpty()) { - throw UnacceptableSigningKeyException(of(signingKey)) - } - - for (signingPubKey in signingPubKeys) { - if (!signingPubKey.keyIdentifier.matches(KeyIdentifier(keyId))) { - continue - } - - val signingSecKey = - signingKey.getSecretKey(signingPubKey.keyIdentifier) - ?: throw MissingSecretKeyException( - of(signingKey), signingPubKey.keyIdentifier.keyId) - val signingSubkey = signingSecKey.unlock(signingKeyProtector) - val hashAlgorithms = keyRingInfo.getPreferredHashAlgorithms(signingPubKey.keyIdentifier) - val hashAlgorithm: HashAlgorithm = negotiateHashAlgorithm(hashAlgorithms, getPolicy()) - addSigningMethod( - signingKey, signingSubkey, hashAlgorithm, signatureType, false, subpacketsCallback) - return this - } - throw MissingSecretKeyException(of(signingKey), keyId) - } + ) = + addInlineSignature( + signingKeyProtector, + signingKey.toOpenPGPKey().getSecretKey(KeyIdentifier(keyId)) + ?: throw MissingSecretKeyException(of(signingKey), keyId), + signatureType, + subpacketsCallback) /** * Add detached signatures with all key rings from the provided secret key ring collection. @@ -229,6 +278,7 @@ class SigningOptions { * method cannot be created */ @Throws(KeyException::class, PGPException::class) + @Deprecated("Repeatedly call addDetachedSignature(), passing an OpenPGPKey instead.") fun addDetachedSignatures( signingKeyProtector: SecretKeyRingProtector, signingKeys: Iterable, @@ -237,6 +287,12 @@ class SigningOptions { signingKeys.forEach { addDetachedSignature(signingKeyProtector, it, null, signatureType) } } + fun addDetachedSignature( + signingKeyProtector: SecretKeyRingProtector, + signingKey: OpenPGPKey, + signatureType: DocumentSignatureType = DocumentSignatureType.BINARY_DOCUMENT + ): SigningOptions = addDetachedSignature(signingKeyProtector, signingKey, null, signatureType) + /** * Create a detached signature. Detached signatures are not being added into the PGP message * itself. Instead, they can be distributed separately to the message. Detached signatures are @@ -250,6 +306,7 @@ class SigningOptions { * @throws PGPException if the key cannot be validated or unlocked, or if no signature method * can be created */ + @Deprecated("Pass an OpenPGPKey instead.") @Throws(KeyException::class, PGPException::class) fun addDetachedSignature( signingKeyProtector: SecretKeyRingProtector, @@ -257,6 +314,37 @@ class SigningOptions { signatureType: DocumentSignatureType ) = apply { addDetachedSignature(signingKeyProtector, signingKey, null, signatureType) } + fun addDetachedSignature( + signingKeyProtector: SecretKeyRingProtector, + signingKey: OpenPGPKey, + userId: CharSequence? = null, + signatureType: DocumentSignatureType = DocumentSignatureType.BINARY_DOCUMENT, + subpacketCallback: Callback? = null + ): SigningOptions = apply { + val keyRingInfo = inspectKeyRing(signingKey, evaluationDate) + if (userId != null && !keyRingInfo.isUserIdValid(userId)) { + throw UnboundUserIdException( + of(signingKey.pgpSecretKeyRing), + userId.toString(), + keyRingInfo.getLatestUserIdCertification(userId), + keyRingInfo.getUserIdRevocation(userId)) + } + + val signingPubKeys = keyRingInfo.signingSubkeys + if (signingPubKeys.isEmpty()) { + throw UnacceptableSigningKeyException(of(signingKey.pgpSecretKeyRing)) + } + + for (signingPubKey in signingPubKeys) { + val signingSecKey: OpenPGPSecretKey = + signingKey.getSecretKey(signingPubKey.keyIdentifier) + ?: throw MissingSecretKeyException( + of(signingKey.pgpSecretKeyRing), signingPubKey.keyIdentifier.keyId) + addDetachedSignature( + signingKeyProtector, signingSecKey, userId, signatureType, subpacketCallback) + } + } + /** * Create a detached signature. Detached signatures are not being added into the PGP message * itself. Instead, they can be distributed separately to the message. Detached signatures are @@ -275,6 +363,7 @@ class SigningOptions { * @throws PGPException if the key cannot be validated or unlocked, or if no signature method * can be created */ + @Deprecated("Pass an OpenPGPKey instead.") @JvmOverloads @Throws(KeyException::class, PGPException::class) fun addDetachedSignature( @@ -283,34 +372,28 @@ class SigningOptions { userId: String? = null, signatureType: DocumentSignatureType = DocumentSignatureType.BINARY_DOCUMENT, subpacketCallback: Callback? = null - ) = apply { - val keyRingInfo = inspectKeyRing(signingKey, evaluationDate) - if (userId != null && !keyRingInfo.isUserIdValid(userId)) { - throw UnboundUserIdException( - of(signingKey), - userId.toString(), - keyRingInfo.getLatestUserIdCertification(userId), - keyRingInfo.getUserIdRevocation(userId)) - } + ) = + addDetachedSignature( + signingKeyProtector, + signingKey.toOpenPGPKey(), + userId, + signatureType, + subpacketCallback) - val signingPubKeys = keyRingInfo.signingSubkeys - if (signingPubKeys.isEmpty()) { - throw UnacceptableSigningKeyException(of(signingKey)) - } - - for (signingPubKey in signingPubKeys) { - val signingSecKey: PGPSecretKey = - signingKey.getSecretKey(signingPubKey.keyIdentifier) - ?: throw MissingSecretKeyException( - of(signingKey), signingPubKey.keyIdentifier.keyId) - val signingSubkey: PGPPrivateKey = signingSecKey.unlock(signingKeyProtector) - val hashAlgorithms = - if (userId != null) keyRingInfo.getPreferredHashAlgorithms(userId) - else keyRingInfo.getPreferredHashAlgorithms(signingPubKey.keyIdentifier) - val hashAlgorithm: HashAlgorithm = negotiateHashAlgorithm(hashAlgorithms, getPolicy()) - addSigningMethod( - signingKey, signingSubkey, hashAlgorithm, signatureType, true, subpacketCallback) - } + fun addDetachedSignature( + signingKeyProtector: SecretKeyRingProtector, + signingKey: OpenPGPSecretKey, + userId: CharSequence? = null, + signatureType: DocumentSignatureType = DocumentSignatureType.BINARY_DOCUMENT, + subpacketCallback: Callback? = null + ): SigningOptions = apply { + val keyRingInfo = inspectKeyRing(signingKey.openPGPKey, evaluationDate) + val signingPrivKey: OpenPGPPrivateKey = signingKey.unlock(signingKeyProtector) + val hashAlgorithms = + if (userId != null) keyRingInfo.getPreferredHashAlgorithms(userId) + else keyRingInfo.getPreferredHashAlgorithms(signingKey.keyIdentifier) + val hashAlgorithm: HashAlgorithm = negotiateHashAlgorithm(hashAlgorithms, getPolicy()) + addSigningMethod(signingPrivKey, hashAlgorithm, signatureType, true, subpacketCallback) } /** @@ -331,65 +414,44 @@ class SigningOptions { */ @Throws(KeyException::class, PGPException::class) @JvmOverloads + @Deprecated("Pass an OpenPGPSecretKey instead.") fun addDetachedSignature( signingKeyProtector: SecretKeyRingProtector, signingKey: PGPSecretKeyRing, keyId: Long, signatureType: DocumentSignatureType = DocumentSignatureType.BINARY_DOCUMENT, subpacketsCallback: Callback? = null - ) = apply { - val keyRingInfo = inspectKeyRing(signingKey, evaluationDate) - - val signingPubKeys = keyRingInfo.signingSubkeys - if (signingPubKeys.isEmpty()) { - throw UnacceptableSigningKeyException(of(signingKey)) - } - - for (signingPubKey in signingPubKeys) { - if (signingPubKey.keyIdentifier.matches(KeyIdentifier(keyId))) { - val signingSecKey: PGPSecretKey = - signingKey.getSecretKey(signingPubKey.keyIdentifier) - ?: throw MissingSecretKeyException( - of(signingKey), signingPubKey.keyIdentifier.keyId) - val signingSubkey: PGPPrivateKey = signingSecKey.unlock(signingKeyProtector) - val hashAlgorithms = - keyRingInfo.getPreferredHashAlgorithms(signingPubKey.keyIdentifier) - val hashAlgorithm: HashAlgorithm = - negotiateHashAlgorithm(hashAlgorithms, getPolicy()) - addSigningMethod( - signingKey, - signingSubkey, - hashAlgorithm, - signatureType, - true, - subpacketsCallback) - return this - } - } - - throw MissingSecretKeyException(of(signingKey), keyId) - } + ) = + addDetachedSignature( + signingKeyProtector, + signingKey.toOpenPGPKey().getSecretKey(KeyIdentifier(keyId)) + ?: throw MissingSecretKeyException(of(signingKey), keyId), + null, + signatureType, + subpacketsCallback) private fun addSigningMethod( - signingKey: PGPSecretKeyRing, - signingSubkey: PGPPrivateKey, + signingKey: OpenPGPPrivateKey, hashAlgorithm: HashAlgorithm, signatureType: DocumentSignatureType, detached: Boolean, subpacketCallback: Callback? = null ) { - val signingKeyIdentifier = SubkeyIdentifier(signingKey, signingSubkey.keyID) - val signingSecretKey: PGPSecretKey = signingKey.getSecretKey(signingSubkey.keyID) + val signingSecretKey: PGPSecretKey = signingKey.secretKey.pgpSecretKey val publicKeyAlgorithm = requireFromId(signingSecretKey.publicKey.algorithm) val bitStrength = signingSecretKey.publicKey.bitStrength if (!getPolicy().publicKeyAlgorithmPolicy.isAcceptable(publicKeyAlgorithm, bitStrength)) { throw UnacceptableSigningKeyException( PublicKeyAlgorithmPolicyException( - of(signingKey), signingSecretKey.keyID, publicKeyAlgorithm, bitStrength)) + of(signingKey.secretKey.pgpSecretKey), + signingSecretKey.keyID, + publicKeyAlgorithm, + bitStrength)) } val generator: PGPSignatureGenerator = - createSignatureGenerator(signingSubkey, hashAlgorithm, signatureType) + createSignatureGenerator( + signingKey.unlockedKey.privateKey, hashAlgorithm, signatureType) // Subpackets val hashedSubpackets = @@ -405,7 +467,7 @@ class SigningOptions { val signingMethod = if (detached) SigningMethod.detachedSignature(generator, hashAlgorithm) else SigningMethod.inlineSignature(generator, hashAlgorithm) - (signingMethods as MutableMap)[signingKeyIdentifier] = signingMethod + (signingMethods as MutableMap)[signingKey] = signingMethod } /** diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/key/SubkeyIdentifier.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/key/SubkeyIdentifier.kt index 0ee58cc6..02966a51 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/key/SubkeyIdentifier.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/key/SubkeyIdentifier.kt @@ -8,6 +8,7 @@ import org.bouncycastle.bcpg.KeyIdentifier import org.bouncycastle.openpgp.PGPKeyRing import org.bouncycastle.openpgp.PGPPublicKey import org.bouncycastle.openpgp.api.OpenPGPCertificate.OpenPGPComponentKey +import org.bouncycastle.openpgp.api.OpenPGPKey.OpenPGPPrivateKey /** * Tuple class used to identify a subkey by fingerprints of the primary key of the subkeys key ring, @@ -32,6 +33,8 @@ class SubkeyIdentifier( OpenPgpFingerprint.of(key.certificate.pgpPublicKeyRing), OpenPgpFingerprint.of(key.pgpPublicKey)) + constructor(key: OpenPGPPrivateKey) : this(key.secretKey) + constructor( keys: PGPKeyRing, subkeyFingerprint: OpenPgpFingerprint diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/key/protection/UnlockSecretKey.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/key/protection/UnlockSecretKey.kt index b3b0308f..597be3e6 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/key/protection/UnlockSecretKey.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/key/protection/UnlockSecretKey.kt @@ -10,6 +10,8 @@ import openpgp.openPgpKeyId import org.bouncycastle.openpgp.PGPException import org.bouncycastle.openpgp.PGPPrivateKey import org.bouncycastle.openpgp.PGPSecretKey +import org.bouncycastle.openpgp.api.OpenPGPKey.OpenPGPPrivateKey +import org.bouncycastle.openpgp.api.OpenPGPKey.OpenPGPSecretKey import org.bouncycastle.openpgp.operator.PBESecretKeyDecryptor import org.pgpainless.PGPainless import org.pgpainless.bouncycastle.extensions.isEncrypted @@ -35,6 +37,36 @@ class UnlockSecretKey { } } + @JvmStatic + @Throws(PGPException::class) + fun unlockSecretKey( + secretKey: OpenPGPSecretKey, + protector: SecretKeyRingProtector + ): OpenPGPPrivateKey { + val privateKey = + try { + secretKey.unlock(protector) + } catch (e: PGPException) { + throw WrongPassphraseException(secretKey.keyIdentifier, e) + } + + if (privateKey == null) { + if (secretKey.pgpSecretKey.s2K.type in 100..110) { + throw PGPException( + "Cannot decrypt secret key ${secretKey.keyIdentifier}: \n" + + "Unsupported private S2K type ${secretKey.pgpSecretKey.s2K.type}") + } + throw PGPException("Cannot decrypt secret key.") + } + + if (PGPainless.getPolicy().isEnableKeyParameterValidation()) { + PublicKeyParameterValidationUtil.verifyPublicKeyParameterIntegrity( + privateKey.unlockedKey.privateKey, privateKey.unlockedKey.publicKey) + } + + return privateKey + } + @JvmStatic @Throws(PGPException::class) fun unlockSecretKey(