From 30771f470a31e6fc094dd6824eb85cdfd1cc9cc0 Mon Sep 17 00:00:00 2001 From: Bastien JANSEN Date: Wed, 8 Feb 2023 09:16:53 +0100 Subject: [PATCH] Support version 3 signature packets --- .../consumer/SignatureValidator.java | 10 ++- .../subpackets/SignatureSubpacketsUtil.java | 3 + .../VerifyVersion3SignaturePacketTest.java | 65 +++++++++++++++++++ 3 files changed, 75 insertions(+), 3 deletions(-) create mode 100644 pgpainless-core/src/test/java/org/pgpainless/decryption_verification/VerifyVersion3SignaturePacketTest.java diff --git a/pgpainless-core/src/main/java/org/pgpainless/signature/consumer/SignatureValidator.java b/pgpainless-core/src/main/java/org/pgpainless/signature/consumer/SignatureValidator.java index af245235..cf0dc1fb 100644 --- a/pgpainless-core/src/main/java/org/pgpainless/signature/consumer/SignatureValidator.java +++ b/pgpainless-core/src/main/java/org/pgpainless/signature/consumer/SignatureValidator.java @@ -158,8 +158,10 @@ public abstract class SignatureValidator { @Override public void verify(PGPSignature signature) throws SignatureValidationException { signatureIsNotMalformed(signingKey).verify(signature); - signatureDoesNotHaveCriticalUnknownNotations(policy.getNotationRegistry()).verify(signature); - signatureDoesNotHaveCriticalUnknownSubpackets().verify(signature); + if (signature.getVersion() >= 4) { + signatureDoesNotHaveCriticalUnknownNotations(policy.getNotationRegistry()).verify(signature); + signatureDoesNotHaveCriticalUnknownSubpackets().verify(signature); + } signatureUsesAcceptableHashAlgorithm(policy).verify(signature); signatureUsesAcceptablePublicKeyAlgorithm(policy, signingKey).verify(signature); } @@ -373,7 +375,9 @@ public abstract class SignatureValidator { return new SignatureValidator() { @Override public void verify(PGPSignature signature) throws SignatureValidationException { - signatureHasHashedCreationTime().verify(signature); + if (signature.getVersion() >= 4) { + signatureHasHashedCreationTime().verify(signature); + } signatureDoesNotPredateSigningKey(creator).verify(signature); if (signature.getSignatureType() != SignatureType.PRIMARYKEY_BINDING.getCode()) { signatureDoesNotPredateSigningKeyBindingDate(creator).verify(signature); diff --git a/pgpainless-core/src/main/java/org/pgpainless/signature/subpackets/SignatureSubpacketsUtil.java b/pgpainless-core/src/main/java/org/pgpainless/signature/subpackets/SignatureSubpacketsUtil.java index cbcbeafc..1105a813 100644 --- a/pgpainless-core/src/main/java/org/pgpainless/signature/subpackets/SignatureSubpacketsUtil.java +++ b/pgpainless-core/src/main/java/org/pgpainless/signature/subpackets/SignatureSubpacketsUtil.java @@ -141,6 +141,9 @@ public final class SignatureSubpacketsUtil { * @return signature creation time subpacket */ public static @Nullable SignatureCreationTime getSignatureCreationTime(PGPSignature signature) { + if (signature.getVersion() == 3) { + return new SignatureCreationTime(false, signature.getCreationTime()); + } return hashed(signature, SignatureSubpacket.signatureCreationTime); } diff --git a/pgpainless-core/src/test/java/org/pgpainless/decryption_verification/VerifyVersion3SignaturePacketTest.java b/pgpainless-core/src/test/java/org/pgpainless/decryption_verification/VerifyVersion3SignaturePacketTest.java new file mode 100644 index 00000000..54b9c94b --- /dev/null +++ b/pgpainless-core/src/test/java/org/pgpainless/decryption_verification/VerifyVersion3SignaturePacketTest.java @@ -0,0 +1,65 @@ +package org.pgpainless.decryption_verification; + +import org.bouncycastle.openpgp.PGPException; +import org.bouncycastle.openpgp.PGPPrivateKey; +import org.bouncycastle.openpgp.PGPSecretKeyRing; +import org.bouncycastle.openpgp.PGPSignature; +import org.bouncycastle.openpgp.PGPV3SignatureGenerator; +import org.bouncycastle.openpgp.operator.PGPContentSignerBuilder; +import org.bouncycastle.util.io.Streams; +import org.junit.jupiter.api.Test; +import org.pgpainless.PGPainless; +import org.pgpainless.algorithm.HashAlgorithm; +import org.pgpainless.algorithm.PublicKeyAlgorithm; +import org.pgpainless.algorithm.SignatureType; +import org.pgpainless.implementation.ImplementationFactory; +import org.pgpainless.key.TestKeys; +import org.pgpainless.key.protection.SecretKeyRingProtector; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.nio.charset.StandardCharsets; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +class VerifyVersion3SignaturePacketTest { + + + protected static final byte[] DATA = "hello".getBytes(StandardCharsets.UTF_8); + + @Test + void verifyDetachedVersion3Signature() throws PGPException, IOException { + PGPSignature version3Signature = generateV3Signature(); + + ConsumerOptions options = new ConsumerOptions() + .addVerificationCert(TestKeys.getEmilPublicKeyRing()) + .addVerificationOfDetachedSignatures(new ByteArrayInputStream(version3Signature.getEncoded())); + + DecryptionStream verifier = PGPainless.decryptAndOrVerify() + .onInputStream(new ByteArrayInputStream(DATA)) + .withOptions(options); + + OpenPgpMetadata metadata = processSignedData(verifier); + assertTrue(metadata.containsVerifiedSignatureFrom(TestKeys.getEmilPublicKeyRing())); + } + + private static PGPSignature generateV3Signature() throws IOException, PGPException { + PGPContentSignerBuilder builder = ImplementationFactory.getInstance().getPGPContentSignerBuilder(PublicKeyAlgorithm.ECDSA, HashAlgorithm.SHA512); + PGPV3SignatureGenerator signatureGenerator = new PGPV3SignatureGenerator(builder); + + PGPSecretKeyRing secretKeys = TestKeys.getEmilSecretKeyRing(); + SecretKeyRingProtector protector = SecretKeyRingProtector.unprotectedKeys(); + PGPPrivateKey privateKey = secretKeys.getSecretKey().extractPrivateKey(protector.getDecryptor(secretKeys.getSecretKey().getKeyID())); + + signatureGenerator.init(SignatureType.CANONICAL_TEXT_DOCUMENT.getCode(), privateKey); + signatureGenerator.update(DATA); + + return signatureGenerator.generate(); + } + + private OpenPgpMetadata processSignedData(DecryptionStream verifier) throws IOException { + Streams.drain(verifier); + verifier.close(); + return verifier.getResult(); + } +}