mirror of
https://github.com/pgpainless/pgpainless.git
synced 2025-09-09 10:19:39 +02:00
Improve API for signatures in results
This commit is contained in:
parent
049f7422c0
commit
335cf8d162
9 changed files with 91 additions and 50 deletions
|
@ -15,8 +15,8 @@ import java.nio.charset.StandardCharsets;
|
|||
|
||||
import org.bouncycastle.bcpg.ArmoredOutputStream;
|
||||
import org.bouncycastle.openpgp.PGPException;
|
||||
import org.bouncycastle.openpgp.PGPSecretKeyRing;
|
||||
import org.bouncycastle.openpgp.PGPSignature;
|
||||
import org.bouncycastle.openpgp.api.OpenPGPKey;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.pgpainless.PGPainless;
|
||||
import org.pgpainless.algorithm.CompressionAlgorithm;
|
||||
|
@ -350,12 +350,13 @@ public class RoundTripInlineSignInlineVerifyCmdTest extends CLITest {
|
|||
|
||||
@Test
|
||||
public void createMalformedMessage() throws IOException, PGPException {
|
||||
PGPainless api = PGPainless.getInstance();
|
||||
String msg = "Hello, World!\n";
|
||||
PGPSecretKeyRing key = PGPainless.readKeyRing().secretKeyRing(KEY_2);
|
||||
OpenPGPKey key = api.readKey().parseKey(KEY_2);
|
||||
ByteArrayOutputStream ciphertext = new ByteArrayOutputStream();
|
||||
EncryptionStream encryptionStream = PGPainless.encryptAndOrSign()
|
||||
EncryptionStream encryptionStream = api.generateMessage()
|
||||
.onOutputStream(ciphertext)
|
||||
.withOptions(ProducerOptions.sign(SigningOptions.get()
|
||||
.withOptions(ProducerOptions.sign(SigningOptions.get(api)
|
||||
.addDetachedSignature(SecretKeyRingProtector.unprotectedKeys(), key)
|
||||
).overrideCompressionAlgorithm(CompressionAlgorithm.UNCOMPRESSED)
|
||||
.setAsciiArmor(false));
|
||||
|
|
|
@ -152,7 +152,9 @@ class PGPainless(
|
|||
*/
|
||||
@JvmStatic
|
||||
@JvmOverloads
|
||||
@Deprecated("Call buildKey() on an instance of PGPainless instead.")
|
||||
@Deprecated(
|
||||
"Call buildKey() on an instance of PGPainless instead.",
|
||||
replaceWith = ReplaceWith("buildKey(version)"))
|
||||
fun buildKeyRing(version: OpenPGPKeyVersion = OpenPGPKeyVersion.v4): KeyRingBuilder =
|
||||
getInstance().buildKey(version)
|
||||
|
||||
|
@ -186,7 +188,8 @@ class PGPainless(
|
|||
* @throws PGPException in case of an error
|
||||
*/
|
||||
@JvmStatic
|
||||
@Deprecated("Use mergeCertificate() instead.")
|
||||
@Deprecated(
|
||||
"Use mergeCertificate() instead.", replaceWith = ReplaceWith("mergeCertificate()"))
|
||||
fun mergeCertificate(
|
||||
originalCopy: PGPPublicKeyRing,
|
||||
updatedCopy: PGPPublicKeyRing
|
||||
|
|
|
@ -779,34 +779,37 @@ class OpenPgpMessageInputStream(
|
|||
fun addDetachedSignature(signature: PGPSignature) {
|
||||
val check = initializeSignature(signature)
|
||||
val keyId = signature.issuerKeyId
|
||||
if (check != null) {
|
||||
if (check.issuer != null) {
|
||||
detachedSignatures.add(check)
|
||||
} else {
|
||||
LOGGER.debug(
|
||||
"No suitable certificate for verification of signature by key ${keyId.openPgpKeyId()} found.")
|
||||
detachedSignaturesWithMissingCert.add(
|
||||
SignatureVerification.Failure(
|
||||
signature, null, SignatureValidationException("Missing verification key.")))
|
||||
check, SignatureValidationException("Missing verification key.")))
|
||||
}
|
||||
}
|
||||
|
||||
fun addPrependedSignature(signature: PGPSignature) {
|
||||
val check = initializeSignature(signature)
|
||||
val keyId = signature.issuerKeyId
|
||||
if (check != null) {
|
||||
if (check.issuer != null) {
|
||||
prependedSignatures.add(check)
|
||||
} else {
|
||||
LOGGER.debug(
|
||||
"No suitable certificate for verification of signature by key ${keyId.openPgpKeyId()} found.")
|
||||
prependedSignaturesWithMissingCert.add(
|
||||
SignatureVerification.Failure(
|
||||
signature, null, SignatureValidationException("Missing verification key")))
|
||||
check, SignatureValidationException("Missing verification key")))
|
||||
}
|
||||
}
|
||||
|
||||
fun initializeSignature(signature: PGPSignature): OpenPGPDocumentSignature? {
|
||||
val certificate = findCertificate(signature) ?: return null
|
||||
val publicKey = certificate.getSigningKeyFor(signature) ?: return null
|
||||
fun initializeSignature(signature: PGPSignature): OpenPGPDocumentSignature {
|
||||
val certificate =
|
||||
findCertificate(signature) ?: return OpenPGPDocumentSignature(signature, null)
|
||||
val publicKey =
|
||||
certificate.getSigningKeyFor(signature)
|
||||
?: return OpenPGPDocumentSignature(signature, null)
|
||||
initialize(signature, publicKey.pgpPublicKey)
|
||||
return OpenPGPDocumentSignature(signature, publicKey)
|
||||
}
|
||||
|
@ -845,12 +848,7 @@ class OpenPgpMessageInputStream(
|
|||
val documentSignature =
|
||||
OpenPGPDocumentSignature(
|
||||
signature, check.verificationKeys.getSigningKeyFor(signature))
|
||||
val verification =
|
||||
SignatureVerification(
|
||||
signature,
|
||||
SubkeyIdentifier(
|
||||
check.verificationKeys.pgpPublicKeyRing,
|
||||
check.onePassSignature.keyIdentifier))
|
||||
val verification = SignatureVerification(documentSignature)
|
||||
|
||||
try {
|
||||
signature.assertCreatedInBounds(
|
||||
|
@ -879,7 +877,8 @@ class OpenPgpMessageInputStream(
|
|||
"No suitable certificate for verification of signature by key ${signature.issuerKeyId.openPgpKeyId()} found.")
|
||||
inbandSignaturesWithMissingCert.add(
|
||||
SignatureVerification.Failure(
|
||||
signature, null, SignatureValidationException("Missing verification key.")))
|
||||
OpenPGPDocumentSignature(signature, null),
|
||||
SignatureValidationException("Missing verification key.")))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -967,8 +966,7 @@ class OpenPgpMessageInputStream(
|
|||
|
||||
fun finish(layer: Layer) {
|
||||
for (detached in detachedSignatures) {
|
||||
val verification =
|
||||
SignatureVerification(detached.signature, SubkeyIdentifier(detached.issuer))
|
||||
val verification = SignatureVerification(detached)
|
||||
try {
|
||||
detached.signature.assertCreatedInBounds(
|
||||
options.getVerifyNotBefore(), options.getVerifyNotAfter())
|
||||
|
@ -988,8 +986,7 @@ class OpenPgpMessageInputStream(
|
|||
}
|
||||
|
||||
for (prepended in prependedSignatures) {
|
||||
val verification =
|
||||
SignatureVerification(prepended.signature, SubkeyIdentifier(prepended.issuer))
|
||||
val verification = SignatureVerification(prepended)
|
||||
try {
|
||||
prepended.signature.assertCreatedInBounds(
|
||||
options.getVerifyNotBefore(), options.getVerifyNotAfter())
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
package org.pgpainless.decryption_verification
|
||||
|
||||
import org.bouncycastle.openpgp.PGPSignature
|
||||
import org.bouncycastle.openpgp.api.OpenPGPSignature.OpenPGPDocumentSignature
|
||||
import org.pgpainless.decryption_verification.SignatureVerification.Failure
|
||||
import org.pgpainless.exception.SignatureValidationException
|
||||
import org.pgpainless.key.SubkeyIdentifier
|
||||
|
@ -20,7 +21,10 @@ import org.pgpainless.signature.SignatureUtils
|
|||
* @param signingKey [SubkeyIdentifier] of the (sub-) key that is used for signature verification.
|
||||
* Note, that this might be null, e.g. in case of a [Failure] due to missing verification key.
|
||||
*/
|
||||
data class SignatureVerification(val signature: PGPSignature, val signingKey: SubkeyIdentifier) {
|
||||
data class SignatureVerification(val documentSignature: OpenPGPDocumentSignature) {
|
||||
|
||||
val signature: PGPSignature = documentSignature.signature
|
||||
val signingKey: SubkeyIdentifier = SubkeyIdentifier(documentSignature.issuer)
|
||||
|
||||
override fun toString(): String {
|
||||
return "Signature: ${SignatureUtils.getSignatureDigestPrefix(signature)};" +
|
||||
|
@ -36,15 +40,16 @@ data class SignatureVerification(val signature: PGPSignature, val signingKey: Su
|
|||
* @param validationException exception that caused the verification to fail
|
||||
*/
|
||||
data class Failure(
|
||||
val signature: PGPSignature,
|
||||
val signingKey: SubkeyIdentifier?,
|
||||
val documentSignature: OpenPGPDocumentSignature,
|
||||
val validationException: SignatureValidationException
|
||||
) {
|
||||
val signature: PGPSignature = documentSignature.signature
|
||||
val signingKey: SubkeyIdentifier? = documentSignature.issuer?.let { SubkeyIdentifier(it) }
|
||||
|
||||
constructor(
|
||||
verification: SignatureVerification,
|
||||
validationException: SignatureValidationException
|
||||
) : this(verification.signature, verification.signingKey, validationException)
|
||||
) : this(verification.documentSignature, validationException)
|
||||
|
||||
override fun toString(): String {
|
||||
return "Signature: ${SignatureUtils.getSignatureDigestPrefix(signature)}; Key: ${signingKey?.toString() ?: "null"}; Failure: ${validationException.message}"
|
||||
|
|
|
@ -9,6 +9,7 @@ import org.bouncycastle.openpgp.PGPLiteralData
|
|||
import org.bouncycastle.openpgp.PGPPublicKeyRing
|
||||
import org.bouncycastle.openpgp.PGPSignature
|
||||
import org.bouncycastle.openpgp.api.OpenPGPCertificate
|
||||
import org.bouncycastle.openpgp.api.OpenPGPSignature.OpenPGPDocumentSignature
|
||||
import org.pgpainless.algorithm.CompressionAlgorithm
|
||||
import org.pgpainless.algorithm.StreamEncoding
|
||||
import org.pgpainless.algorithm.SymmetricKeyAlgorithm
|
||||
|
@ -19,13 +20,25 @@ import org.pgpainless.util.MultiMap
|
|||
data class EncryptionResult(
|
||||
val encryptionAlgorithm: SymmetricKeyAlgorithm,
|
||||
val compressionAlgorithm: CompressionAlgorithm,
|
||||
val detachedSignatures: MultiMap<SubkeyIdentifier, PGPSignature>,
|
||||
val detachedDocumentSignatures: OpenPGPSignatureSet<OpenPGPDocumentSignature>,
|
||||
val recipients: Set<SubkeyIdentifier>,
|
||||
val fileName: String,
|
||||
val modificationDate: Date,
|
||||
val fileEncoding: StreamEncoding
|
||||
) {
|
||||
|
||||
@Deprecated(
|
||||
"Use detachedSignatures instead", replaceWith = ReplaceWith("detachedDocumentSignatures"))
|
||||
// TODO: Remove in 2.1
|
||||
val detachedSignatures: MultiMap<SubkeyIdentifier, PGPSignature>
|
||||
get() {
|
||||
val map = MultiMap<SubkeyIdentifier, PGPSignature>()
|
||||
detachedDocumentSignatures.signatures
|
||||
.map { SubkeyIdentifier(it.issuer) to it.signature }
|
||||
.forEach { map.put(it.first, it.second) }
|
||||
return map
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true, if the message is marked as for-your-eyes-only. This is typically done by
|
||||
* setting the filename "_CONSOLE".
|
||||
|
@ -59,7 +72,7 @@ data class EncryptionResult(
|
|||
var _encryptionAlgorithm: SymmetricKeyAlgorithm? = null
|
||||
var _compressionAlgorithm: CompressionAlgorithm? = null
|
||||
|
||||
val detachedSignatures: MultiMap<SubkeyIdentifier, PGPSignature> = MultiMap()
|
||||
val detachedSignatures: MutableList<OpenPGPDocumentSignature> = mutableListOf()
|
||||
val recipients: Set<SubkeyIdentifier> = mutableSetOf()
|
||||
private var _fileName = ""
|
||||
private var _modificationDate = Date(0)
|
||||
|
@ -85,10 +98,9 @@ data class EncryptionResult(
|
|||
(recipients as MutableSet).add(recipient)
|
||||
}
|
||||
|
||||
fun addDetachedSignature(
|
||||
signingSubkeyIdentifier: SubkeyIdentifier,
|
||||
detachedSignature: PGPSignature
|
||||
) = apply { detachedSignatures.put(signingSubkeyIdentifier, detachedSignature) }
|
||||
fun addDetachedSignature(signature: OpenPGPDocumentSignature): Builder = apply {
|
||||
detachedSignatures.add(signature)
|
||||
}
|
||||
|
||||
fun build(): EncryptionResult {
|
||||
checkNotNull(_encryptionAlgorithm) { "Encryption algorithm not set." }
|
||||
|
@ -97,7 +109,7 @@ data class EncryptionResult(
|
|||
return EncryptionResult(
|
||||
_encryptionAlgorithm!!,
|
||||
_compressionAlgorithm!!,
|
||||
detachedSignatures,
|
||||
OpenPGPSignatureSet(detachedSignatures),
|
||||
recipients,
|
||||
_fileName,
|
||||
_modificationDate,
|
||||
|
|
|
@ -13,11 +13,11 @@ import org.bouncycastle.openpgp.PGPCompressedDataGenerator
|
|||
import org.bouncycastle.openpgp.PGPEncryptedDataGenerator
|
||||
import org.bouncycastle.openpgp.PGPException
|
||||
import org.bouncycastle.openpgp.PGPLiteralDataGenerator
|
||||
import org.bouncycastle.openpgp.api.OpenPGPSignature.OpenPGPDocumentSignature
|
||||
import org.pgpainless.PGPainless
|
||||
import org.pgpainless.algorithm.CompressionAlgorithm
|
||||
import org.pgpainless.algorithm.StreamEncoding
|
||||
import org.pgpainless.algorithm.SymmetricKeyAlgorithm
|
||||
import org.pgpainless.key.SubkeyIdentifier
|
||||
import org.pgpainless.util.ArmoredOutputStreamFactory
|
||||
|
||||
// 1 << 8 causes wrong partial body length encoding
|
||||
|
@ -246,8 +246,9 @@ class EncryptionStream(
|
|||
|
||||
options.signingOptions.signingMethods.entries.reversed().forEach { (key, method) ->
|
||||
method.signatureGenerator.generate().let { sig ->
|
||||
val documentSignature = OpenPGPDocumentSignature(sig, key.publicKey)
|
||||
if (method.isDetached) {
|
||||
resultBuilder.addDetachedSignature(SubkeyIdentifier(key), sig)
|
||||
resultBuilder.addDetachedSignature(documentSignature)
|
||||
}
|
||||
if (!method.isDetached || options.isCleartextSigned) {
|
||||
sig.encode(signatureLayerStream)
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
// SPDX-FileCopyrightText: 2025 Paul Schaub <vanitasvitae@fsfe.org>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package org.pgpainless.encryption_signing
|
||||
|
||||
import org.bouncycastle.openpgp.api.OpenPGPCertificate
|
||||
import org.bouncycastle.openpgp.api.OpenPGPSignature
|
||||
|
||||
class OpenPGPSignatureSet<S : OpenPGPSignature>(val signatures: List<S>) : Iterable<S> {
|
||||
|
||||
fun getSignaturesBy(cert: OpenPGPCertificate): List<S> =
|
||||
signatures.filter { sig -> sig.signature.keyIdentifiers.any { cert.getKey(it) != null } }
|
||||
|
||||
fun getSignaturesBy(componentKey: OpenPGPCertificate.OpenPGPComponentKey): List<S> =
|
||||
signatures.filter { sig ->
|
||||
sig.signature.keyIdentifiers.any { componentKey.keyIdentifier.matches(it) }
|
||||
}
|
||||
|
||||
override fun iterator(): Iterator<S> {
|
||||
return signatures.iterator()
|
||||
}
|
||||
}
|
|
@ -14,9 +14,9 @@ import java.io.InputStream;
|
|||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
import org.bouncycastle.openpgp.PGPException;
|
||||
import org.bouncycastle.openpgp.PGPSignature;
|
||||
import org.bouncycastle.openpgp.api.OpenPGPCertificate;
|
||||
import org.bouncycastle.openpgp.api.OpenPGPKey;
|
||||
import org.bouncycastle.openpgp.api.OpenPGPSignature;
|
||||
import org.bouncycastle.util.io.Streams;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
@ -26,7 +26,6 @@ import org.pgpainless.encryption_signing.EncryptionResult;
|
|||
import org.pgpainless.encryption_signing.EncryptionStream;
|
||||
import org.pgpainless.encryption_signing.ProducerOptions;
|
||||
import org.pgpainless.encryption_signing.SigningOptions;
|
||||
import org.pgpainless.key.SubkeyIdentifier;
|
||||
import org.pgpainless.key.protection.SecretKeyRingProtector;
|
||||
import org.pgpainless.util.ArmorUtils;
|
||||
|
||||
|
@ -70,7 +69,7 @@ public class Sign {
|
|||
/**
|
||||
* Demonstration of how to create a detached signature for a message.
|
||||
* A detached signature can be distributed alongside the message/file itself.
|
||||
*
|
||||
* <p>
|
||||
* The message/file doesn't need to be altered for detached signature creation.
|
||||
*/
|
||||
@Test
|
||||
|
@ -82,9 +81,9 @@ public class Sign {
|
|||
// After signing, you want to distribute the original value of 'message' along with the 'detachedSignature'
|
||||
// from below.
|
||||
ByteArrayOutputStream ignoreMe = new ByteArrayOutputStream();
|
||||
EncryptionStream signingStream = PGPainless.encryptAndOrSign()
|
||||
EncryptionStream signingStream = api.generateMessage()
|
||||
.onOutputStream(ignoreMe)
|
||||
.withOptions(ProducerOptions.sign(SigningOptions.get()
|
||||
.withOptions(ProducerOptions.sign(SigningOptions.get(api)
|
||||
.addDetachedSignature(protector, key, DocumentSignatureType.CANONICAL_TEXT_DOCUMENT))
|
||||
.setAsciiArmor(false)
|
||||
);
|
||||
|
@ -94,9 +93,9 @@ public class Sign {
|
|||
|
||||
EncryptionResult result = signingStream.getResult();
|
||||
|
||||
OpenPGPCertificate.OpenPGPComponentKey signingKey = PGPainless.inspectKeyRing(key).getSigningSubkeys().get(0);
|
||||
PGPSignature signature = result.getDetachedSignatures().get(new SubkeyIdentifier(signingKey)).iterator().next();
|
||||
String detachedSignature = ArmorUtils.toAsciiArmoredString(signature.getEncoded());
|
||||
OpenPGPCertificate.OpenPGPComponentKey signingKey = api.inspect(key).getSigningSubkeys().get(0);
|
||||
OpenPGPSignature.OpenPGPDocumentSignature signature = result.getDetachedDocumentSignatures().getSignaturesBy(signingKey).get(0);
|
||||
String detachedSignature = ArmorUtils.toAsciiArmoredString(signature.getSignature().getEncoded());
|
||||
|
||||
assertTrue(detachedSignature.startsWith("-----BEGIN PGP SIGNATURE-----"));
|
||||
|
||||
|
@ -126,9 +125,9 @@ public class Sign {
|
|||
"limitations under the License.";
|
||||
InputStream messageIn = new ByteArrayInputStream(message.getBytes(StandardCharsets.UTF_8));
|
||||
ByteArrayOutputStream signedOut = new ByteArrayOutputStream();
|
||||
EncryptionStream signingStream = PGPainless.encryptAndOrSign()
|
||||
EncryptionStream signingStream = api.generateMessage()
|
||||
.onOutputStream(signedOut)
|
||||
.withOptions(ProducerOptions.sign(SigningOptions.get()
|
||||
.withOptions(ProducerOptions.sign(SigningOptions.get(api)
|
||||
.addDetachedSignature(protector, key, DocumentSignatureType.CANONICAL_TEXT_DOCUMENT)) // Human-readable text document
|
||||
.setCleartextSigned() // <- Explicitly use Cleartext Signature Framework!!!
|
||||
);
|
||||
|
|
|
@ -73,16 +73,16 @@ class DetachedSignImpl(private val api: PGPainless) : DetachedSign {
|
|||
// forget passphrases
|
||||
protector.clear()
|
||||
|
||||
val signatures = result.detachedSignatures.map { it.value }.flatten()
|
||||
val signatures = result.detachedDocumentSignatures
|
||||
val out =
|
||||
if (armor) ArmoredOutputStreamFactory.get(outputStream) else outputStream
|
||||
|
||||
signatures.forEach { it.encode(out) }
|
||||
signatures.forEach { it.signature.encode(out) }
|
||||
out.close()
|
||||
outputStream.close()
|
||||
|
||||
return SigningResult.builder()
|
||||
.setMicAlg(micAlgFromSignatures(signatures))
|
||||
.setMicAlg(micAlgFromSignatures(signatures.map { it.signature }))
|
||||
.build()
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue