From 64cc9ecca4525b2a7e849f16caaa85123406030e Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Mon, 26 Apr 2021 13:38:12 +0200 Subject: [PATCH] Proper Signature Verification --- build.gradle | 4 + .../algorithm/PublicKeyAlgorithm.java | 46 +- .../pgpainless/algorithm/SignatureType.java | 28 + .../DecryptionBuilder.java | 18 +- .../DecryptionBuilderInterface.java | 11 + .../DecryptionStream.java | 15 +- .../DecryptionStreamFactory.java | 56 +- .../OpenPgpMetadata.java | 2 + .../SignatureValidationUtil.java | 101 - .../SignatureVerifyingInputStream.java | 34 +- .../encryption_signing/EncryptionBuilder.java | 237 +- .../EncryptionBuilderInterface.java | 88 +- .../encryption_signing/EncryptionStream.java | 55 +- .../org/pgpainless/key/KeyRingValidator.java | 260 +++ .../java/org/pgpainless/key/KeyValidator.java | 59 + .../org/pgpainless/key/SubkeyIdentifier.java | 109 + .../key/generation/KeyRingBuilder.java | 2 +- .../key/generation/type/KeyType.java | 8 +- .../key/generation/type/ecc/ecdh/ECDH.java | 10 - .../key/generation/type/ecc/ecdsa/ECDSA.java | 10 - .../key/generation/type/eddsa/EdDSA.java | 9 - .../key/generation/type/elgamal/ElGamal.java | 10 - .../key/generation/type/rsa/RSA.java | 10 - .../key/generation/type/xdh/XDH.java | 9 - .../org/pgpainless/key/info/KeyRingInfo.java | 311 ++- .../secretkeyring/SecretKeyRingEditor.java | 18 +- .../key/util/RevocationAttributes.java | 24 +- .../java/org/pgpainless/policy/Policy.java | 36 + .../DetachedSignature.java | 23 +- .../OnePassSignature.java | 15 +- .../signature/SelectSignatureFromKey.java | 392 ++++ .../signature/SignatureChainValidator.java | 182 ++ .../SignatureCreationDateComparator.java | 47 + .../pgpainless/signature/SignaturePicker.java | 314 +++ .../util => signature}/SignatureUtils.java | 69 +- .../SignatureValidationException.java | 45 + .../signature/SignatureValidator.java | 522 +++++ .../SignatureValidityComparator.java | 49 + .../pgpainless/signature/package-info.java | 19 + .../SignatureSubpacketGeneratorUtil.java | 2 +- .../subpackets/SignatureSubpacketsUtil.java | 359 +++ .../signature/subpackets/package-info.java | 19 + .../main/java/org/pgpainless/util/BCUtil.java | 31 +- .../org/pgpainless/util/NotationRegistry.java | 12 +- .../main/java/org/pgpainless/util/Tuple.java | 34 + .../util/selection/key/SelectPublicKey.java | 311 +++ .../impl/HasAnyKeyFlagSelectionStrategy.java | 5 +- .../bouncycastle/PGPPublicKeyRingTest.java | 60 + .../src/test/java/org/junit/JUtils.java | 7 + .../EncryptDecryptTest.java | 66 + .../encryption_signing/SigningTest.java | 2 +- .../pgpainless/key/KeyRingValidatorTest.java | 289 +++ .../GenerateKeyWithAdditionalUserIdTest.java | 2 +- .../pgpainless/key/info/KeyRingInfoTest.java | 8 +- .../key/info/UserIdRevocationTest.java | 2 +- .../modification/ChangeExpirationTest.java | 28 +- ...gnatureSubpacketsArePreservedOnNewSig.java | 4 +- ...ithoutPreferredAlgorithmsOnPrimaryKey.java | 4 +- .../BindingSignatureSubpacketsTest.java | 1945 +++++++++++++++++ .../signature/IgnoreMarkerPackets.java | 269 +++ .../signature/KeyRevocationTest.java | 265 +++ .../signature/KeyRingValidationTest.java | 136 ++ .../SignatureChainValidatorTest.java | 1309 +++++++++++ .../pgpainless/util/NotationRegistryTest.java | 12 +- .../SignatureSubpacketGeneratorUtilTest.java | 1 + .../java/org/pgpainless/util/TestUtils.java | 13 + .../signature/SelectSignatureFromKeyTest.java | 186 ++ 67 files changed, 7950 insertions(+), 688 deletions(-) delete mode 100644 pgpainless-core/src/main/java/org/pgpainless/decryption_verification/SignatureValidationUtil.java create mode 100644 pgpainless-core/src/main/java/org/pgpainless/key/KeyRingValidator.java create mode 100644 pgpainless-core/src/main/java/org/pgpainless/key/KeyValidator.java create mode 100644 pgpainless-core/src/main/java/org/pgpainless/key/SubkeyIdentifier.java rename pgpainless-core/src/main/java/org/pgpainless/{decryption_verification => signature}/DetachedSignature.java (61%) rename pgpainless-core/src/main/java/org/pgpainless/{decryption_verification => signature}/OnePassSignature.java (79%) create mode 100644 pgpainless-core/src/main/java/org/pgpainless/signature/SelectSignatureFromKey.java create mode 100644 pgpainless-core/src/main/java/org/pgpainless/signature/SignatureChainValidator.java create mode 100644 pgpainless-core/src/main/java/org/pgpainless/signature/SignatureCreationDateComparator.java create mode 100644 pgpainless-core/src/main/java/org/pgpainless/signature/SignaturePicker.java rename pgpainless-core/src/main/java/org/pgpainless/{key/util => signature}/SignatureUtils.java (76%) create mode 100644 pgpainless-core/src/main/java/org/pgpainless/signature/SignatureValidationException.java create mode 100644 pgpainless-core/src/main/java/org/pgpainless/signature/SignatureValidator.java create mode 100644 pgpainless-core/src/main/java/org/pgpainless/signature/SignatureValidityComparator.java create mode 100644 pgpainless-core/src/main/java/org/pgpainless/signature/package-info.java rename pgpainless-core/src/main/java/org/pgpainless/{util => signature/subpackets}/SignatureSubpacketGeneratorUtil.java (99%) create mode 100644 pgpainless-core/src/main/java/org/pgpainless/signature/subpackets/SignatureSubpacketsUtil.java create mode 100644 pgpainless-core/src/main/java/org/pgpainless/signature/subpackets/package-info.java create mode 100644 pgpainless-core/src/main/java/org/pgpainless/util/Tuple.java create mode 100644 pgpainless-core/src/main/java/org/pgpainless/util/selection/key/SelectPublicKey.java create mode 100644 pgpainless-core/src/test/java/org/bouncycastle/PGPPublicKeyRingTest.java create mode 100644 pgpainless-core/src/test/java/org/pgpainless/key/KeyRingValidatorTest.java create mode 100644 pgpainless-core/src/test/java/org/pgpainless/signature/BindingSignatureSubpacketsTest.java create mode 100644 pgpainless-core/src/test/java/org/pgpainless/signature/IgnoreMarkerPackets.java create mode 100644 pgpainless-core/src/test/java/org/pgpainless/signature/KeyRevocationTest.java create mode 100644 pgpainless-core/src/test/java/org/pgpainless/signature/KeyRingValidationTest.java create mode 100644 pgpainless-core/src/test/java/org/pgpainless/signature/SignatureChainValidatorTest.java create mode 100644 pgpainless-core/src/test/java/org/pgpainless/util/selection/signature/SelectSignatureFromKeyTest.java diff --git a/build.gradle b/build.gradle index fc4d9702..62184564 100644 --- a/build.gradle +++ b/build.gradle @@ -96,6 +96,10 @@ allprojects { test { useJUnitPlatform() + testLogging { + events "passed", "skipped", "failed" + exceptionFormat "full" + } } } diff --git a/pgpainless-core/src/main/java/org/pgpainless/algorithm/PublicKeyAlgorithm.java b/pgpainless-core/src/main/java/org/pgpainless/algorithm/PublicKeyAlgorithm.java index 1b940551..3bdc248a 100644 --- a/pgpainless-core/src/main/java/org/pgpainless/algorithm/PublicKeyAlgorithm.java +++ b/pgpainless-core/src/main/java/org/pgpainless/algorithm/PublicKeyAlgorithm.java @@ -30,7 +30,7 @@ public enum PublicKeyAlgorithm { /** * RSA capable of encryption and signatures. */ - RSA_GENERAL (PublicKeyAlgorithmTags.RSA_GENERAL), + RSA_GENERAL (PublicKeyAlgorithmTags.RSA_GENERAL, true, true), /** * RSA with usage encryption. @@ -38,7 +38,7 @@ public enum PublicKeyAlgorithm { * @deprecated see https://tools.ietf.org/html/rfc4880#section-13.5 */ @Deprecated - RSA_ENCRYPT (PublicKeyAlgorithmTags.RSA_ENCRYPT), + RSA_ENCRYPT (PublicKeyAlgorithmTags.RSA_ENCRYPT, false, true), /** * RSA with usage of creating signatures. @@ -46,34 +46,34 @@ public enum PublicKeyAlgorithm { * @deprecated see https://tools.ietf.org/html/rfc4880#section-13.5 */ @Deprecated - RSA_SIGN (PublicKeyAlgorithmTags.RSA_SIGN), + RSA_SIGN (PublicKeyAlgorithmTags.RSA_SIGN, true, false), /** * ElGamal with usage encryption. */ - ELGAMAL_ENCRYPT (PublicKeyAlgorithmTags.ELGAMAL_ENCRYPT), + ELGAMAL_ENCRYPT (PublicKeyAlgorithmTags.ELGAMAL_ENCRYPT, false, true), /** * Digital Signature Algorithm. */ - DSA (PublicKeyAlgorithmTags.DSA), + DSA (PublicKeyAlgorithmTags.DSA, true, false), /** * EC is deprecated. * @deprecated use {@link #ECDH} instead. */ @Deprecated - EC (PublicKeyAlgorithmTags.EC), + EC (PublicKeyAlgorithmTags.EC, false, true), /** * Elliptic Curve Diffie-Hellman. */ - ECDH (PublicKeyAlgorithmTags.ECDH), + ECDH (PublicKeyAlgorithmTags.ECDH, false, true), /** * Elliptic Curve Digital Signature Algorithm. */ - ECDSA (PublicKeyAlgorithmTags.ECDSA), + ECDSA (PublicKeyAlgorithmTags.ECDSA, true, false), /** * ElGamal General. @@ -81,17 +81,17 @@ public enum PublicKeyAlgorithm { * @deprecated see https://tools.ietf.org/html/rfc4880#section-13.8 */ @Deprecated - ELGAMAL_GENERAL (PublicKeyAlgorithmTags.ELGAMAL_GENERAL), + ELGAMAL_GENERAL (PublicKeyAlgorithmTags.ELGAMAL_GENERAL, true, true), /** * Diffie-Hellman key exchange algorithm. */ - DIFFIE_HELLMAN (PublicKeyAlgorithmTags.DIFFIE_HELLMAN), + DIFFIE_HELLMAN (PublicKeyAlgorithmTags.DIFFIE_HELLMAN, false, true), /** * Digital Signature Algorithm based on twisted Edwards Curves. */ - EDDSA (PublicKeyAlgorithmTags.EDDSA), + EDDSA (PublicKeyAlgorithmTags.EDDSA, true, false), ; private static final Map MAP = new ConcurrentHashMap<>(); @@ -114,9 +114,13 @@ public enum PublicKeyAlgorithm { } private final int algorithmId; + private final boolean signingCapable; + private final boolean encryptionCapable; - PublicKeyAlgorithm(int algorithmId) { + PublicKeyAlgorithm(int algorithmId, boolean signingCapable, boolean encryptionCapable) { this.algorithmId = algorithmId; + this.signingCapable = signingCapable; + this.encryptionCapable = encryptionCapable; } /** @@ -127,4 +131,22 @@ public enum PublicKeyAlgorithm { public int getAlgorithmId() { return algorithmId; } + + /** + * Return true if this public key algorithm is able to create signatures. + * + * @return true if can sign + */ + public boolean isSigningCapable() { + return signingCapable; + } + + /** + * Return true if this public key algorithm can be used as an encryption algorithm. + * + * @return true if can encrypt + */ + public boolean isEncryptionCapable() { + return encryptionCapable; + } } diff --git a/pgpainless-core/src/main/java/org/pgpainless/algorithm/SignatureType.java b/pgpainless-core/src/main/java/org/pgpainless/algorithm/SignatureType.java index e8689155..30caef04 100644 --- a/pgpainless-core/src/main/java/org/pgpainless/algorithm/SignatureType.java +++ b/pgpainless-core/src/main/java/org/pgpainless/algorithm/SignatureType.java @@ -202,4 +202,32 @@ public enum SignatureType { return code; } + public static boolean isRevocationSignature(int signatureType) { + return isRevocationSignature(SignatureType.valueOf(signatureType)); + } + + public static boolean isRevocationSignature(SignatureType signatureType) { + switch (signatureType) { + case BINARY_DOCUMENT: + case CANONICAL_TEXT_DOCUMENT: + case STANDALONE: + case GENERIC_CERTIFICATION: + case NO_CERTIFICATION: + case CASUAL_CERTIFICATION: + case POSITIVE_CERTIFICATION: + case SUBKEY_BINDING: + case PRIMARYKEY_BINDING: + case DIRECT_KEY: + case TIMESTAMP: + case THIRD_PARTY_CONFIRMATION: + return false; + case KEY_REVOCATION: + case SUBKEY_REVOCATION: + case CERTIFICATION_REVOCATION: + return true; + default: + throw new IllegalArgumentException("Unknown type: " + signatureType); + } + } + } diff --git a/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/DecryptionBuilder.java b/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/DecryptionBuilder.java index cd739017..8bafe6ff 100644 --- a/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/DecryptionBuilder.java +++ b/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/DecryptionBuilder.java @@ -18,17 +18,20 @@ package org.pgpainless.decryption_verification; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; +import java.util.Collections; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Set; import javax.annotation.Nonnull; +import org.bouncycastle.bcpg.MarkerPacket; import org.bouncycastle.openpgp.PGPCompressedData; import org.bouncycastle.openpgp.PGPException; import org.bouncycastle.openpgp.PGPObjectFactory; import org.bouncycastle.openpgp.PGPPublicKeyRing; import org.bouncycastle.openpgp.PGPPublicKeyRingCollection; +import org.bouncycastle.openpgp.PGPSecretKeyRing; import org.bouncycastle.openpgp.PGPSecretKeyRingCollection; import org.bouncycastle.openpgp.PGPSignature; import org.bouncycastle.openpgp.PGPSignatureList; @@ -67,6 +70,13 @@ public class DecryptionBuilder implements DecryptionBuilderInterface { return new VerifyImpl(); } + @Override + public Verify decryptWith(@Nonnull SecretKeyRingProtector decryptor, @Nonnull PGPSecretKeyRing secretKeyRing) throws PGPException, IOException { + DecryptionBuilder.this.decryptionKeys = new PGPSecretKeyRingCollection(Collections.singleton(secretKeyRing)); + DecryptionBuilder.this.decryptionKeyDecryptor = decryptor; + return new VerifyImpl(); + } + @Override public Verify decryptWith(@Nonnull Passphrase passphrase) { if (passphrase.isEmpty()) { @@ -94,6 +104,10 @@ public class DecryptionBuilder implements DecryptionBuilderInterface { pgpIn, keyFingerPrintCalculator); Object nextObject = objectFactory.nextObject(); while (nextObject != null) { + if (nextObject instanceof MarkerPacket) { + nextObject = objectFactory.nextObject(); + continue; + } if (nextObject instanceof PGPCompressedData) { PGPCompressedData compressedData = (PGPCompressedData) nextObject; objectFactory = new PGPObjectFactory(compressedData.getDataStream(), keyFingerPrintCalculator); @@ -205,8 +219,8 @@ public class DecryptionBuilder implements DecryptionBuilderInterface { @Override public DecryptionStream build() throws IOException, PGPException { - return DecryptionStreamFactory.create(inputStream, - decryptionKeys, decryptionKeyDecryptor, decryptionPassphrase, detachedSignatures, verificationKeys, missingPublicKeyCallback); + return DecryptionStreamFactory.create(inputStream, decryptionKeys, decryptionKeyDecryptor, + decryptionPassphrase, detachedSignatures, verificationKeys, missingPublicKeyCallback); } } } diff --git a/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/DecryptionBuilderInterface.java b/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/DecryptionBuilderInterface.java index fe98fd06..e1949d26 100644 --- a/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/DecryptionBuilderInterface.java +++ b/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/DecryptionBuilderInterface.java @@ -26,6 +26,7 @@ import java.util.Set; import org.bouncycastle.openpgp.PGPException; import org.bouncycastle.openpgp.PGPPublicKeyRing; import org.bouncycastle.openpgp.PGPPublicKeyRingCollection; +import org.bouncycastle.openpgp.PGPSecretKeyRing; import org.bouncycastle.openpgp.PGPSecretKeyRingCollection; import org.bouncycastle.openpgp.PGPSignature; import org.pgpainless.key.OpenPgpV4Fingerprint; @@ -68,6 +69,16 @@ public interface DecryptionBuilderInterface { */ Verify decryptWith(@Nonnull SecretKeyRingProtector decryptor, @Nonnull PGPSecretKeyRingCollection secretKeyRings); + /** + * Decrypt the encrypted data using the provided {@link PGPSecretKeyRing}. + * The secret key is unlocked by the provided {@link SecretKeyRingProtector}. + * + * @param decryptor for unlocking locked secret key + * @param secretKeyRing secret key + * @return api handle + */ + Verify decryptWith(@Nonnull SecretKeyRingProtector decryptor, @Nonnull PGPSecretKeyRing secretKeyRing) throws PGPException, IOException; + /** * Decrypt the encrypted data using a passphrase. * Note: The passphrase MUST NOT be empty. diff --git a/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/DecryptionStream.java b/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/DecryptionStream.java index 54f5f36e..145a883d 100644 --- a/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/DecryptionStream.java +++ b/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/DecryptionStream.java @@ -15,14 +15,18 @@ */ package org.pgpainless.decryption_verification; -import javax.annotation.Nonnull; import java.io.IOException; import java.io.InputStream; import java.util.List; import java.util.logging.Level; import java.util.logging.Logger; +import javax.annotation.Nonnull; -import org.bouncycastle.openpgp.PGPException; +import org.bouncycastle.openpgp.PGPPublicKeyRing; +import org.pgpainless.PGPainless; +import org.pgpainless.signature.DetachedSignature; +import org.pgpainless.signature.SignatureChainValidator; +import org.pgpainless.signature.SignatureValidationException; import org.pgpainless.util.IntegrityProtectedInputStream; /** @@ -73,9 +77,10 @@ public class DecryptionStream extends InputStream { private void maybeVerifyDetachedSignatures() { for (DetachedSignature s : resultBuilder.getDetachedSignatures()) { try { - s.setVerified(s.getSignature().verify()); - } catch (PGPException e) { - LOGGER.log(Level.WARNING, "Could not verify signature of key " + s.getFingerprint(), e); + boolean verified = SignatureChainValidator.validateSignature(s.getSignature(), (PGPPublicKeyRing) s.getSigningKeyRing(), PGPainless.getPolicy()); + s.setVerified(verified); + } catch (SignatureValidationException e) { + LOGGER.log(Level.WARNING, "Could not verify signature of key " + s.getSigningKeyIdentifier(), e); } } } diff --git a/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/DecryptionStreamFactory.java b/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/DecryptionStreamFactory.java index 67a00510..1c2c233b 100644 --- a/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/DecryptionStreamFactory.java +++ b/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/DecryptionStreamFactory.java @@ -58,7 +58,10 @@ import org.pgpainless.algorithm.SymmetricKeyAlgorithm; import org.pgpainless.exception.MessageNotIntegrityProtectedException; import org.pgpainless.implementation.ImplementationFactory; import org.pgpainless.key.OpenPgpV4Fingerprint; +import org.pgpainless.key.SubkeyIdentifier; import org.pgpainless.key.protection.SecretKeyRingProtector; +import org.pgpainless.signature.DetachedSignature; +import org.pgpainless.signature.OnePassSignature; import org.pgpainless.util.IntegrityProtectedInputStream; import org.pgpainless.util.Passphrase; @@ -107,13 +110,14 @@ public final class DecryptionStreamFactory { if (detachedSignatures != null) { pgpInputStream = inputStream; for (PGPSignature signature : detachedSignatures) { - PGPPublicKey signingKey = factory.findSignatureVerificationKey(signature.getKeyID()); - if (signingKey == null) { + PGPPublicKeyRing signingKeyRing = factory.findSignatureVerificationKeyRing(signature.getKeyID()); + if (signingKeyRing == null) { continue; } + PGPPublicKey signingKey = signingKeyRing.getPublicKey(signature.getKeyID()); signature.init(ImplementationFactory.getInstance().getPGPContentVerifierBuilderProvider(), signingKey); factory.resultBuilder.addDetachedSignature( - new DetachedSignature(signature, new OpenPgpV4Fingerprint(signingKey))); + new DetachedSignature(signature, signingKeyRing, new SubkeyIdentifier(signingKeyRing, signature.getKeyID()))); } } else { PGPObjectFactory objectFactory = new PGPObjectFactory( @@ -312,59 +316,31 @@ public final class DecryptionStreamFactory { LOGGER.log(LEVEL, "Message contains OnePassSignature from " + Long.toHexString(keyId)); // Find public key - PGPPublicKey verificationKey = findSignatureVerificationKey(keyId); - if (verificationKey == null) { + PGPPublicKeyRing verificationKeyRing = findSignatureVerificationKeyRing(keyId); + if (verificationKeyRing == null) { LOGGER.log(LEVEL, "Missing verification key from " + Long.toHexString(keyId)); return; } + PGPPublicKey verificationKey = verificationKeyRing.getPublicKey(keyId); signature.init(verifierBuilderProvider, verificationKey); OpenPgpV4Fingerprint fingerprint = new OpenPgpV4Fingerprint(verificationKey); - OnePassSignature onePassSignature = new OnePassSignature(signature, fingerprint); + OnePassSignature onePassSignature = new OnePassSignature(signature, verificationKeyRing); resultBuilder.addOnePassSignature(onePassSignature); verifiableOnePassSignatures.put(fingerprint, onePassSignature); } - private PGPPublicKey findSignatureVerificationKey(long keyId) { - PGPPublicKey verificationKey = null; + private PGPPublicKeyRing findSignatureVerificationKeyRing(long keyId) { + PGPPublicKeyRing verificationKeyRing = null; for (PGPPublicKeyRing publicKeyRing : verificationKeys) { - verificationKey = publicKeyRing.getPublicKey(keyId); + PGPPublicKey verificationKey = publicKeyRing.getPublicKey(keyId); if (verificationKey != null) { LOGGER.log(LEVEL, "Found public key " + Long.toHexString(keyId) + " for signature verification"); + verificationKeyRing = publicKeyRing; break; } } - if (verificationKey == null) { - verificationKey = handleMissingVerificationKey(keyId); - } - - return verificationKey; + return verificationKeyRing; } - - private PGPPublicKey handleMissingVerificationKey(long keyId) { - LOGGER.log(Level.FINER, "No public key found for signature of " + Long.toHexString(keyId)); - - if (missingPublicKeyCallback == null) { - LOGGER.log(Level.FINER, "No MissingPublicKeyCallback registered. " + - "Skip signature of " + Long.toHexString(keyId)); - return null; - } - - PGPPublicKey missingPublicKey = missingPublicKeyCallback.onMissingPublicKeyEncountered(keyId); - if (missingPublicKey == null) { - LOGGER.log(Level.FINER, "MissingPublicKeyCallback did not provider key. " + - "Skip signature of " + Long.toHexString(keyId)); - return null; - } - - if (missingPublicKey.getKeyID() != keyId) { - throw new IllegalArgumentException("KeyID of the provided public key differs from the signatures keyId. " + - "The signature was created from " + Long.toHexString(keyId) + " while the provided key has ID " + - Long.toHexString(missingPublicKey.getKeyID())); - } - - return missingPublicKey; - } - } diff --git a/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/OpenPgpMetadata.java b/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/OpenPgpMetadata.java index ca444293..6f8b73eb 100644 --- a/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/OpenPgpMetadata.java +++ b/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/OpenPgpMetadata.java @@ -32,6 +32,8 @@ import org.pgpainless.algorithm.CompressionAlgorithm; import org.pgpainless.algorithm.StreamEncoding; import org.pgpainless.algorithm.SymmetricKeyAlgorithm; import org.pgpainless.key.OpenPgpV4Fingerprint; +import org.pgpainless.signature.DetachedSignature; +import org.pgpainless.signature.OnePassSignature; public class OpenPgpMetadata { diff --git a/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/SignatureValidationUtil.java b/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/SignatureValidationUtil.java deleted file mode 100644 index 3cd1d5fe..00000000 --- a/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/SignatureValidationUtil.java +++ /dev/null @@ -1,101 +0,0 @@ -/* - * Copyright 2021 Paul Schaub. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.pgpainless.decryption_verification; - -import java.util.Date; - -import org.bouncycastle.bcpg.sig.NotationData; -import org.bouncycastle.openpgp.PGPDataValidationException; -import org.bouncycastle.openpgp.PGPException; -import org.bouncycastle.openpgp.PGPSignature; -import org.bouncycastle.openpgp.PGPSignatureSubpacketVector; -import org.pgpainless.algorithm.SignatureSubpacket; -import org.pgpainless.util.NotationRegistry; - -/** - * Utility class that implements validation of signatures. - */ -public class SignatureValidationUtil { - - public static void validate(PGPSignature signature) throws PGPException { - validateHashedAreaHasSignatureCreationTime(signature); - validateSignatureCreationTimeIsNotInUnhashedArea(signature); - validateSignatureDoesNotContainCriticalUnknownSubpackets(signature); - validateSignatureDoesNotContainCriticalUnknownNotations(signature); - } - - public static void validateHashedAreaHasSignatureCreationTime(PGPSignature signature) throws PGPDataValidationException { - PGPSignatureSubpacketVector hashedSubpackets = signature.getHashedSubPackets(); - if (hashedSubpackets.getSignatureCreationTime() == null) { - throw new PGPDataValidationException("Hashed area of the signature MUST carry signature creation time subpacket."); - } - } - - public static void validateSignatureCreationTimeIsNotInUnhashedArea(PGPSignature signature) throws PGPDataValidationException { - PGPSignatureSubpacketVector unhashedSubpackets = signature.getUnhashedSubPackets(); - Date unhashedCreationTime = unhashedSubpackets.getSignatureCreationTime(); - if (unhashedCreationTime == null) { - return; - } - throw new PGPDataValidationException("Signature creation time MUST be in hashed area of the signature."); - } - - public static void validateSignatureDoesNotContainCriticalUnknownSubpackets(PGPSignature signature) throws PGPDataValidationException { - try { - throwIfContainsCriticalUnknownSubpacket(signature.getHashedSubPackets()); - } catch (PGPDataValidationException e) { - throw new PGPDataValidationException("Signature has unknown critical subpacket in hashed area.\n" + e.getMessage()); - } - try { - throwIfContainsCriticalUnknownSubpacket(signature.getHashedSubPackets()); - } catch (PGPDataValidationException e) { - throw new PGPDataValidationException("Signature has unknown critical subpacket in unhashed area.\n" + e.getMessage()); - } - } - - private static void throwIfContainsCriticalUnknownSubpacket(PGPSignatureSubpacketVector subpacketVector) throws PGPDataValidationException { - for (int critical : subpacketVector.getCriticalTags()) { - try { - SignatureSubpacket.fromCode(critical); - } catch (IllegalArgumentException e) { - throw new PGPDataValidationException("Unknown critical signature subpacket: " + Long.toHexString(critical)); - } - } - } - - public static void validateSignatureDoesNotContainCriticalUnknownNotations(PGPSignature signature) throws PGPDataValidationException { - PGPSignatureSubpacketVector hashedSubpackets = signature.getHashedSubPackets(); - try { - throwIfSubpacketsContainCriticalUnknownNotation(hashedSubpackets); - } catch (PGPDataValidationException e) { - throw new PGPDataValidationException("Signature contains unknown critical notation in hashed area:\n" + e.getMessage()); - } - PGPSignatureSubpacketVector unhashedSubpackets = signature.getUnhashedSubPackets(); - try { - throwIfSubpacketsContainCriticalUnknownNotation(unhashedSubpackets); - } catch (PGPDataValidationException e) { - throw new PGPDataValidationException("Signature contains unknown critical notation in unhashed area:\n" + e.getMessage()); - } - } - - private static void throwIfSubpacketsContainCriticalUnknownNotation(PGPSignatureSubpacketVector subpacketVector) throws PGPDataValidationException { - for (NotationData notation : subpacketVector.getNotationDataOccurrences()) { - if (notation.isCritical() && !NotationRegistry.getInstance().isKnownNotation(notation.getNotationName())) { - throw new PGPDataValidationException("Critical unknown notation encountered: " + notation.getNotationName()); - } - } - } -} diff --git a/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/SignatureVerifyingInputStream.java b/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/SignatureVerifyingInputStream.java index 0c3e51ca..2274d28f 100644 --- a/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/SignatureVerifyingInputStream.java +++ b/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/SignatureVerifyingInputStream.java @@ -15,20 +15,30 @@ */ package org.pgpainless.decryption_verification; -import javax.annotation.Nonnull; +import static org.pgpainless.signature.SignatureValidator.signatureIsEffective; +import static org.pgpainless.signature.SignatureValidator.signatureStructureIsAcceptable; + import java.io.FilterInputStream; import java.io.IOException; import java.io.InputStream; import java.security.SignatureException; +import java.util.Date; import java.util.Map; import java.util.logging.Level; import java.util.logging.Logger; +import javax.annotation.Nonnull; import org.bouncycastle.openpgp.PGPException; import org.bouncycastle.openpgp.PGPObjectFactory; +import org.bouncycastle.openpgp.PGPPublicKey; import org.bouncycastle.openpgp.PGPSignature; import org.bouncycastle.openpgp.PGPSignatureList; +import org.pgpainless.PGPainless; import org.pgpainless.key.OpenPgpV4Fingerprint; +import org.pgpainless.policy.Policy; +import org.pgpainless.signature.OnePassSignature; +import org.pgpainless.signature.SignatureChainValidator; +import org.pgpainless.signature.SignatureValidationException; public class SignatureVerifyingInputStream extends FilterInputStream { @@ -93,20 +103,30 @@ public class SignatureVerifyingInputStream extends FilterInputStream { continue; } - verifySignatureOrThrowSignatureException(signature, fingerprint, onePassSignature); + verifySignatureOrThrowSignatureException(signature, onePassSignature); } } catch (PGPException | SignatureException e) { throw new IOException(e.getMessage(), e); } } - private void verifySignatureOrThrowSignatureException(PGPSignature signature, OpenPgpV4Fingerprint fingerprint, - OnePassSignature onePassSignature) + private void verifySignatureOrThrowSignatureException(PGPSignature signature, OnePassSignature onePassSignature) throws PGPException, SignatureException { - if (onePassSignature.verify(signature)) { - LOGGER.log(LEVEL, "Verified signature of key " + Long.toHexString(signature.getKeyID())); - } else { + Policy policy = PGPainless.getPolicy(); + try { + PGPPublicKey signingKey = onePassSignature.getVerificationKeys().getPublicKey(signature.getKeyID()); + signatureStructureIsAcceptable(signingKey, policy).verify(signature); + signatureIsEffective(new Date()).verify(signature); + + SignatureChainValidator.validateSigningKey(signature, onePassSignature.getVerificationKeys(), PGPainless.getPolicy(), signature.getCreationTime()); + + } catch (SignatureValidationException e) { + throw new SignatureException("Signature key is not valid.", e); + } + if (!onePassSignature.verify(signature)) { throw new SignatureException("Bad Signature of key " + signature.getKeyID()); + } else { + LOGGER.log(LEVEL, "Verified signature of key " + Long.toHexString(signature.getKeyID())); } } diff --git a/pgpainless-core/src/main/java/org/pgpainless/encryption_signing/EncryptionBuilder.java b/pgpainless-core/src/main/java/org/pgpainless/encryption_signing/EncryptionBuilder.java index ba80eb6f..68882e63 100644 --- a/pgpainless-core/src/main/java/org/pgpainless/encryption_signing/EncryptionBuilder.java +++ b/pgpainless-core/src/main/java/org/pgpainless/encryption_signing/EncryptionBuilder.java @@ -33,37 +33,36 @@ import org.bouncycastle.openpgp.PGPPublicKeyRing; import org.bouncycastle.openpgp.PGPPublicKeyRingCollection; import org.bouncycastle.openpgp.PGPSecretKey; import org.bouncycastle.openpgp.PGPSecretKeyRing; -import org.bouncycastle.openpgp.PGPSecretKeyRingCollection; import org.bouncycastle.openpgp.operator.PBESecretKeyDecryptor; +import org.pgpainless.PGPainless; import org.pgpainless.algorithm.CompressionAlgorithm; import org.pgpainless.algorithm.HashAlgorithm; import org.pgpainless.algorithm.KeyFlag; import org.pgpainless.algorithm.SignatureType; import org.pgpainless.algorithm.SymmetricKeyAlgorithm; import org.pgpainless.decryption_verification.OpenPgpMetadata; -import org.pgpainless.exception.SecretKeyNotFoundException; +import org.pgpainless.key.KeyRingValidator; import org.pgpainless.key.OpenPgpV4Fingerprint; +import org.pgpainless.key.SubkeyIdentifier; import org.pgpainless.key.protection.SecretKeyRingProtector; +import org.pgpainless.util.Passphrase; +import org.pgpainless.util.Tuple; import org.pgpainless.util.selection.key.PublicKeySelectionStrategy; import org.pgpainless.util.selection.key.SecretKeySelectionStrategy; +import org.pgpainless.util.selection.key.impl.And; import org.pgpainless.util.selection.key.impl.EncryptionKeySelectionStrategy; import org.pgpainless.util.selection.key.impl.NoRevocation; import org.pgpainless.util.selection.key.impl.SignatureKeySelectionStrategy; -import org.pgpainless.util.selection.key.impl.And; -import org.pgpainless.util.selection.keyring.PublicKeyRingSelectionStrategy; -import org.pgpainless.util.selection.keyring.SecretKeyRingSelectionStrategy; -import org.pgpainless.util.MultiMap; -import org.pgpainless.util.Passphrase; public class EncryptionBuilder implements EncryptionBuilderInterface { private final EncryptionStream.Purpose purpose; private OutputStream outputStream; - private final Set encryptionKeys = new HashSet<>(); + private final Map encryptionKeys = new ConcurrentHashMap<>(); private final Set encryptionPassphrases = new HashSet<>(); private boolean detachedSignature = false; private SignatureType signatureType = SignatureType.BINARY_DOCUMENT; - private final Set signingKeys = new HashSet<>(); + private final Map signingKeys = new ConcurrentHashMap<>(); private SecretKeyRingProtector signingKeysDecryptor; private SymmetricKeyAlgorithm symmetricKeyAlgorithm = SymmetricKeyAlgorithm.AES_128; private HashAlgorithm hashAlgorithm = HashAlgorithm.SHA256; @@ -89,90 +88,54 @@ public class EncryptionBuilder implements EncryptionBuilderInterface { class ToRecipientsImpl implements ToRecipients { @Override - public WithAlgorithms toRecipients(@Nonnull PGPPublicKey... keys) { - if (keys.length != 0) { - List encryptionKeys = new ArrayList<>(); - for (PGPPublicKey k : keys) { + public WithAlgorithms toRecipients(@Nonnull PGPPublicKeyRing... keys) { + if (keys.length == 0) { + throw new IllegalArgumentException("No public keys provided."); + } + + Map encryptionKeys = new ConcurrentHashMap<>(); + for (PGPPublicKeyRing ring : keys) { + PGPPublicKeyRing validatedKeyRing = KeyRingValidator.validate(ring, PGPainless.getPolicy()); + for (PGPPublicKey k : validatedKeyRing) { if (encryptionKeySelector().accept(k)) { - encryptionKeys.add(k); - } else { - throw new IllegalArgumentException("Key " + k.getKeyID() + " is not a valid encryption key."); + encryptionKeys.put(new SubkeyIdentifier(ring, k.getKeyID()), ring); } } - - if (encryptionKeys.isEmpty()) { - throw new IllegalArgumentException("No valid encryption keys found!"); - } - EncryptionBuilder.this.encryptionKeys.addAll(encryptionKeys); } + if (encryptionKeys.isEmpty()) { + throw new IllegalArgumentException("No valid encryption keys found!"); + } + EncryptionBuilder.this.encryptionKeys.putAll(encryptionKeys); return new WithAlgorithmsImpl(); } - @Override - public WithAlgorithms toRecipients(@Nonnull PGPPublicKeyRing... keys) { - if (keys.length != 0) { - List encryptionKeys = new ArrayList<>(); - for (PGPPublicKeyRing ring : keys) { - for (PGPPublicKey k : ring) { - if (encryptionKeySelector().accept(k)) { - encryptionKeys.add(k); - } - } - } - if (encryptionKeys.isEmpty()) { - throw new IllegalArgumentException("No valid encryption keys found!"); - } - EncryptionBuilder.this.encryptionKeys.addAll(encryptionKeys); - } - - return new WithAlgorithmsImpl(); + private String getPrimaryUserId(PGPPublicKey publicKey) { + // TODO: Use real function to get primary userId. + return publicKey.getUserIDs().next(); } @Override public WithAlgorithms toRecipients(@Nonnull PGPPublicKeyRingCollection... keys) { - if (keys.length != 0) { - List encryptionKeys = new ArrayList<>(); - for (PGPPublicKeyRingCollection collection : keys) { - for (PGPPublicKeyRing ring : collection) { - for (PGPPublicKey k : ring) { - if (encryptionKeySelector().accept(k)) { - encryptionKeys.add(k); - } - } - } - } - - if (encryptionKeys.isEmpty()) { - throw new IllegalArgumentException("No valid encryption keys found!"); - } - - EncryptionBuilder.this.encryptionKeys.addAll(encryptionKeys); + if (keys.length == 0) { + throw new IllegalArgumentException("No key ring collections provided."); } - return new WithAlgorithmsImpl(); - } - - @Override - public WithAlgorithms toRecipients(@Nonnull PublicKeyRingSelectionStrategy ringSelectionStrategy, - @Nonnull MultiMap keys) { - if (keys.isEmpty()) { - throw new IllegalArgumentException("Recipient map MUST NOT be empty."); - } - MultiMap acceptedKeyRings = ringSelectionStrategy.selectKeyRingsFromCollections(keys); - for (O identifier : acceptedKeyRings.keySet()) { - Set acceptedSet = acceptedKeyRings.get(identifier); - for (PGPPublicKeyRing ring : acceptedSet) { + for (PGPPublicKeyRingCollection collection : keys) { + for (PGPPublicKeyRing ring : collection) { + Map encryptionKeys = new ConcurrentHashMap<>(); for (PGPPublicKey k : ring) { if (encryptionKeySelector().accept(k)) { - EncryptionBuilder.this.encryptionKeys.add(k); + encryptionKeys.put(new SubkeyIdentifier(ring, k.getKeyID()), ring); } } - } - } - if (EncryptionBuilder.this.encryptionKeys.isEmpty()) { - throw new IllegalArgumentException("No valid encryption keys found!"); + if (encryptionKeys.isEmpty()) { + throw new IllegalArgumentException("No valid encryption keys found!"); + } + + EncryptionBuilder.this.encryptionKeys.putAll(encryptionKeys); + } } return new WithAlgorithmsImpl(); @@ -199,33 +162,23 @@ public class EncryptionBuilder implements EncryptionBuilderInterface { class WithAlgorithmsImpl implements WithAlgorithms { - @Override - public WithAlgorithms andToSelf(@Nonnull PGPPublicKey... keys) { - if (keys.length == 0) { - throw new IllegalArgumentException("Recipient list MUST NOT be empty."); - } - for (PGPPublicKey k : keys) { - if (encryptionKeySelector().accept(k)) { - EncryptionBuilder.this.encryptionKeys.add(k); - } else { - throw new IllegalArgumentException("Key " + k.getKeyID() + " is not a valid encryption key."); - } - } - return this; - } - @Override public WithAlgorithms andToSelf(@Nonnull PGPPublicKeyRing... keys) { if (keys.length == 0) { throw new IllegalArgumentException("Recipient list MUST NOT be empty."); } for (PGPPublicKeyRing ring : keys) { + Map encryptionKeys = new ConcurrentHashMap<>(); for (Iterator i = ring.getPublicKeys(); i.hasNext(); ) { PGPPublicKey key = i.next(); if (encryptionKeySelector().accept(key)) { - EncryptionBuilder.this.encryptionKeys.add(key); + encryptionKeys.put(new SubkeyIdentifier(ring, key.getKeyID()), ring); } } + if (encryptionKeys.isEmpty()) { + throw new IllegalArgumentException("No suitable encryption key found in key ring " + new OpenPgpV4Fingerprint(ring)); + } + EncryptionBuilder.this.encryptionKeys.putAll(encryptionKeys); } return this; } @@ -233,34 +186,17 @@ public class EncryptionBuilder implements EncryptionBuilderInterface { @Override public WithAlgorithms andToSelf(@Nonnull PGPPublicKeyRingCollection keys) { for (PGPPublicKeyRing ring : keys) { + Map encryptionKeys = new ConcurrentHashMap<>(); for (Iterator i = ring.getPublicKeys(); i.hasNext(); ) { PGPPublicKey key = i.next(); if (encryptionKeySelector().accept(key)) { - EncryptionBuilder.this.encryptionKeys.add(key); + encryptionKeys.put(new SubkeyIdentifier(ring, key.getKeyID()), ring); } } - } - return this; - } - - @Override - public WithAlgorithms andToSelf(@Nonnull PublicKeyRingSelectionStrategy ringSelectionStrategy, - @Nonnull MultiMap keys) { - if (keys.isEmpty()) { - throw new IllegalArgumentException("Recipient list MUST NOT be empty."); - } - MultiMap acceptedKeyRings = - ringSelectionStrategy.selectKeyRingsFromCollections(keys); - for (O identifier : acceptedKeyRings.keySet()) { - Set acceptedSet = acceptedKeyRings.get(identifier); - for (PGPPublicKeyRing k : acceptedSet) { - for (Iterator i = k.getPublicKeys(); i.hasNext(); ) { - PGPPublicKey key = i.next(); - if (encryptionKeySelector().accept(key)) { - EncryptionBuilder.this.encryptionKeys.add(key); - } - } + if (encryptionKeys.isEmpty()) { + throw new IllegalArgumentException("No suitable encryption key found in key ring " + new OpenPgpV4Fingerprint(ring)); } + EncryptionBuilder.this.encryptionKeys.putAll(encryptionKeys); } return this; } @@ -305,84 +241,39 @@ public class EncryptionBuilder implements EncryptionBuilderInterface { return new ArmorImpl(); } - @Override - public DocumentType signWith(@Nonnull SecretKeyRingProtector decryptor, @Nonnull PGPSecretKey... keys) { - return new SignWithImpl().signWith(decryptor, keys); - } - @Override public DocumentType signWith(@Nonnull SecretKeyRingProtector decryptor, @Nonnull PGPSecretKeyRing... keyRings) { return new SignWithImpl().signWith(decryptor, keyRings); } - @Override - public DocumentType signWith(@Nonnull SecretKeyRingSelectionStrategy selectionStrategy, - @Nonnull SecretKeyRingProtector decryptor, - @Nonnull MultiMap keys) - throws SecretKeyNotFoundException { - return new SignWithImpl().signWith(selectionStrategy, decryptor, keys); - } } class SignWithImpl implements SignWith { @Override public DocumentType signWith(@Nonnull SecretKeyRingProtector decryptor, - @Nonnull PGPSecretKey... keys) { - if (keys.length == 0) { + @Nonnull PGPSecretKeyRing... keyRings) { + if (keyRings.length == 0) { throw new IllegalArgumentException("Recipient list MUST NOT be empty."); } - for (PGPSecretKey s : keys) { - if (EncryptionBuilder.this.signingKeySelector().accept(s)) { - signingKeys.add(s); - } else { - throw new IllegalArgumentException("Key " + s.getKeyID() + " is not a valid signing key."); - } - } - EncryptionBuilder.this.signingKeysDecryptor = decryptor; - return new DocumentTypeImpl(); - } - - @Override - public DocumentType signWith(@Nonnull SecretKeyRingProtector decryptor, - @Nonnull PGPSecretKeyRing... keys) { - if (keys.length == 0) { - throw new IllegalArgumentException("Recipient list MUST NOT be empty."); - } - for (PGPSecretKeyRing key : keys) { - for (Iterator i = key.getSecretKeys(); i.hasNext(); ) { + for (PGPSecretKeyRing ring : keyRings) { + Map signingKeys = new ConcurrentHashMap<>(); + for (Iterator i = ring.getSecretKeys(); i.hasNext(); ) { PGPSecretKey s = i.next(); if (EncryptionBuilder.this.signingKeySelector().accept(s)) { - EncryptionBuilder.this.signingKeys.add(s); + signingKeys.put(new SubkeyIdentifier(ring, s.getKeyID()), ring); } } + + if (signingKeys.isEmpty()) { + throw new IllegalArgumentException("No suitable signing key found in key ring " + new OpenPgpV4Fingerprint(ring)); + } + + EncryptionBuilder.this.signingKeys.putAll(signingKeys); } EncryptionBuilder.this.signingKeysDecryptor = decryptor; return new DocumentTypeImpl(); } - - @Override - public DocumentType signWith(@Nonnull SecretKeyRingSelectionStrategy ringSelectionStrategy, - @Nonnull SecretKeyRingProtector decryptor, - @Nonnull MultiMap keys) { - if (keys.isEmpty()) { - throw new IllegalArgumentException("Recipient list MUST NOT be empty."); - } - MultiMap acceptedKeyRings = - ringSelectionStrategy.selectKeyRingsFromCollections(keys); - for (O identifier : acceptedKeyRings.keySet()) { - Set acceptedSet = acceptedKeyRings.get(identifier); - for (PGPSecretKeyRing k : acceptedSet) { - for (Iterator i = k.getSecretKeys(); i.hasNext(); ) { - PGPSecretKey s = i.next(); - if (EncryptionBuilder.this.signingKeySelector().accept(s)) { - EncryptionBuilder.this.signingKeys.add(s); - } - } - } - } - return new DocumentTypeImpl(); - } } class DocumentTypeImpl implements DocumentType { @@ -416,11 +307,13 @@ public class EncryptionBuilder implements EncryptionBuilderInterface { private EncryptionStream build() throws IOException, PGPException { - Map privateKeys = new ConcurrentHashMap<>(); - for (PGPSecretKey secretKey : signingKeys) { + Map> privateKeys = new ConcurrentHashMap<>(); + for (SubkeyIdentifier signingKey : signingKeys.keySet()) { + PGPSecretKeyRing secretKeyRing = signingKeys.get(signingKey); + PGPSecretKey secretKey = secretKeyRing.getSecretKey(signingKey.getSubkeyFingerprint().getKeyId()); PBESecretKeyDecryptor decryptor = signingKeysDecryptor.getDecryptor(secretKey.getKeyID()); PGPPrivateKey privateKey = secretKey.extractPrivateKey(decryptor); - privateKeys.put(new OpenPgpV4Fingerprint(secretKey), privateKey); + privateKeys.put(signingKey, new Tuple<>(secretKeyRing, privateKey)); } return new EncryptionStream( diff --git a/pgpainless-core/src/main/java/org/pgpainless/encryption_signing/EncryptionBuilderInterface.java b/pgpainless-core/src/main/java/org/pgpainless/encryption_signing/EncryptionBuilderInterface.java index d1b7b01e..9e584dfa 100644 --- a/pgpainless-core/src/main/java/org/pgpainless/encryption_signing/EncryptionBuilderInterface.java +++ b/pgpainless-core/src/main/java/org/pgpainless/encryption_signing/EncryptionBuilderInterface.java @@ -15,29 +15,21 @@ */ package org.pgpainless.encryption_signing; -import javax.annotation.Nonnull; import java.io.IOException; import java.io.OutputStream; import java.util.Date; +import javax.annotation.Nonnull; import org.bouncycastle.openpgp.PGPException; -import org.bouncycastle.openpgp.PGPPublicKey; import org.bouncycastle.openpgp.PGPPublicKeyRing; import org.bouncycastle.openpgp.PGPPublicKeyRingCollection; -import org.bouncycastle.openpgp.PGPSecretKey; import org.bouncycastle.openpgp.PGPSecretKeyRing; -import org.bouncycastle.openpgp.PGPSecretKeyRingCollection; import org.pgpainless.algorithm.CompressionAlgorithm; import org.pgpainless.algorithm.HashAlgorithm; import org.pgpainless.algorithm.StreamEncoding; import org.pgpainless.algorithm.SymmetricKeyAlgorithm; import org.pgpainless.decryption_verification.OpenPgpMetadata; -import org.pgpainless.exception.SecretKeyNotFoundException; import org.pgpainless.key.protection.SecretKeyRingProtector; -import org.pgpainless.key.protection.UnprotectedKeysProtector; -import org.pgpainless.util.selection.keyring.PublicKeyRingSelectionStrategy; -import org.pgpainless.util.selection.keyring.SecretKeyRingSelectionStrategy; -import org.pgpainless.util.MultiMap; import org.pgpainless.util.Passphrase; public interface EncryptionBuilderInterface { @@ -93,14 +85,6 @@ public interface EncryptionBuilderInterface { interface ToRecipients { - /** - * Pass in a list of trusted public keys of the recipients. - * - * @param keys recipient keys for which the message will be encrypted. - * @return api handle - */ - WithAlgorithms toRecipients(@Nonnull PGPPublicKey... keys); - /** * Pass in a list of trusted public key rings of the recipients. * @@ -117,17 +101,6 @@ public interface EncryptionBuilderInterface { */ WithAlgorithms toRecipients(@Nonnull PGPPublicKeyRingCollection... keys); - /** - * Pass in a map of recipient key ring collections along with a strategy for key selection. - * - * @param selectionStrategy selection strategy that is used to select suitable encryption keys. - * @param keys public keys - * @param selection criteria type (eg. email address) on which the selection strategy is based - * @return api handle - */ - WithAlgorithms toRecipients(@Nonnull PublicKeyRingSelectionStrategy selectionStrategy, - @Nonnull MultiMap keys); - /** * Encrypt to one or more symmetric passphrases. * Note that the passphrases MUST NOT be empty. @@ -148,14 +121,6 @@ public interface EncryptionBuilderInterface { interface WithAlgorithms { - /** - * Add our own public key to the list of recipient keys. - * - * @param keys own public keys - * @return api handle - */ - WithAlgorithms andToSelf(@Nonnull PGPPublicKey... keys); - /** * Add our own public key to the list of recipient keys. * @@ -172,17 +137,6 @@ public interface EncryptionBuilderInterface { */ WithAlgorithms andToSelf(@Nonnull PGPPublicKeyRingCollection keys); - /** - * Add our own public keys to the list of recipient keys. - * - * @param selectionStrategy key selection strategy used to determine suitable keys for encryption. - * @param keys public keys - * @param selection criteria type (eg. email address) used by the selection strategy. - * @return api handle - */ - WithAlgorithms andToSelf(@Nonnull PublicKeyRingSelectionStrategy selectionStrategy, - @Nonnull MultiMap keys); - /** * Specify which algorithms should be used for the encryption. * @@ -227,28 +181,6 @@ public interface EncryptionBuilderInterface { interface SignWith { - /** - * Pass in a list of secret keys used for signing. - * Those keys are considered unlocked (ie. not password protected). - * If you need to use password protected keys instead, use {@link #signWith(SecretKeyRingProtector, PGPSecretKey...)}. - * - * @param keys secret keys - * @return api handle - */ - default DocumentType signWith(@Nonnull PGPSecretKey... keys) { - return signWith(new UnprotectedKeysProtector(), keys); - } - - /** - * Pass in a list of secret keys used for signing, along with a {@link SecretKeyRingProtector} used to unlock - * the secret keys. - * - * @param decryptor {@link SecretKeyRingProtector} used to unlock the secret keys - * @param keys secret keys used for signing - * @return api handle - */ - DocumentType signWith(@Nonnull SecretKeyRingProtector decryptor, @Nonnull PGPSecretKey... keys); - /** * Pass in a list of secret keys used for signing, along with a {@link SecretKeyRingProtector} used to unlock * the secret keys. @@ -259,24 +191,6 @@ public interface EncryptionBuilderInterface { */ DocumentType signWith(@Nonnull SecretKeyRingProtector decryptor, @Nonnull PGPSecretKeyRing... keyRings); - /** - * Pass in a map of secret keys for signing, as well as a {@link org.pgpainless.util.selection.key.SecretKeySelectionStrategy} - * that is used to determine suitable secret keys. - * If the keys are locked by a password, the provided {@link SecretKeyRingProtector} will be used to unlock the keys. - * - * @param selectionStrategy key selection strategy - * @param decryptor decryptor for unlocking secret keys - * @param keys secret keys - * @param selection criteria type (eg. email address) - * @return api handle - * - * @throws SecretKeyNotFoundException in case no suitable secret key can be found - */ - DocumentType signWith(@Nonnull SecretKeyRingSelectionStrategy selectionStrategy, - @Nonnull SecretKeyRingProtector decryptor, - @Nonnull MultiMap keys) - throws SecretKeyNotFoundException; - } interface DocumentType { diff --git a/pgpainless-core/src/main/java/org/pgpainless/encryption_signing/EncryptionStream.java b/pgpainless-core/src/main/java/org/pgpainless/encryption_signing/EncryptionStream.java index b19092ca..3c44b290 100644 --- a/pgpainless-core/src/main/java/org/pgpainless/encryption_signing/EncryptionStream.java +++ b/pgpainless-core/src/main/java/org/pgpainless/encryption_signing/EncryptionStream.java @@ -33,6 +33,8 @@ import org.bouncycastle.openpgp.PGPException; import org.bouncycastle.openpgp.PGPLiteralDataGenerator; import org.bouncycastle.openpgp.PGPPrivateKey; import org.bouncycastle.openpgp.PGPPublicKey; +import org.bouncycastle.openpgp.PGPPublicKeyRing; +import org.bouncycastle.openpgp.PGPSecretKeyRing; import org.bouncycastle.openpgp.PGPSignature; import org.bouncycastle.openpgp.PGPSignatureGenerator; import org.bouncycastle.openpgp.operator.PBEKeyEncryptionMethodGenerator; @@ -45,12 +47,13 @@ import org.pgpainless.algorithm.CompressionAlgorithm; import org.pgpainless.algorithm.HashAlgorithm; import org.pgpainless.algorithm.SignatureType; import org.pgpainless.algorithm.SymmetricKeyAlgorithm; -import org.pgpainless.decryption_verification.DetachedSignature; import org.pgpainless.decryption_verification.OpenPgpMetadata; import org.pgpainless.implementation.ImplementationFactory; -import org.pgpainless.key.OpenPgpV4Fingerprint; +import org.pgpainless.key.SubkeyIdentifier; +import org.pgpainless.signature.DetachedSignature; import org.pgpainless.util.ArmoredOutputStreamFactory; import org.pgpainless.util.Passphrase; +import org.pgpainless.util.Tuple; /** * This class is based upon Jens Neuhalfen's Bouncy-GPG PGPEncryptingStream. @@ -83,16 +86,16 @@ public final class EncryptionStream extends OutputStream { private final SymmetricKeyAlgorithm symmetricKeyAlgorithm; private final HashAlgorithm hashAlgorithm; private final CompressionAlgorithm compressionAlgorithm; - private final Set encryptionKeys; + private final Map encryptionKeys; private final Set encryptionPassphrases; private final boolean detachedSignature; private final SignatureType signatureType; - private final Map signingKeys; + private final Map> signingKeys; private final boolean asciiArmor; private final OpenPgpMetadata.Builder resultBuilder = OpenPgpMetadata.getBuilder(); - private Map signatureGenerators = new ConcurrentHashMap<>(); + private Map> signatureGenerators = new ConcurrentHashMap<>(); private boolean closed = false; OutputStream outermostStream = null; @@ -107,11 +110,11 @@ public final class EncryptionStream extends OutputStream { private OutputStream literalDataStream; EncryptionStream(@Nonnull OutputStream targetOutputStream, - @Nonnull Set encryptionKeys, + @Nonnull Map encryptionKeys, @Nonnull Set encryptionPassphrases, boolean detachedSignature, SignatureType signatureType, - @Nonnull Map signingKeys, + @Nonnull Map> signingKeys, @Nonnull SymmetricKeyAlgorithm symmetricKeyAlgorithm, @Nonnull HashAlgorithm hashAlgorithm, @Nonnull CompressionAlgorithm compressionAlgorithm, @@ -122,7 +125,7 @@ public final class EncryptionStream extends OutputStream { this.symmetricKeyAlgorithm = symmetricKeyAlgorithm; this.hashAlgorithm = hashAlgorithm; this.compressionAlgorithm = compressionAlgorithm; - this.encryptionKeys = Collections.unmodifiableSet(encryptionKeys); + this.encryptionKeys = Collections.unmodifiableMap(encryptionKeys); this.encryptionPassphrases = Collections.unmodifiableSet(encryptionPassphrases); this.detachedSignature = detachedSignature; this.signatureType = signatureType; @@ -170,8 +173,9 @@ public final class EncryptionStream extends OutputStream { PGPEncryptedDataGenerator encryptedDataGenerator = new PGPEncryptedDataGenerator(dataEncryptorBuilder); - for (PGPPublicKey key : encryptionKeys) { - LOGGER.log(LEVEL, "Encrypt for key " + Long.toHexString(key.getKeyID())); + for (SubkeyIdentifier keyIdentifier : encryptionKeys.keySet()) { + LOGGER.log(LEVEL, "Encrypt for key " + keyIdentifier); + PGPPublicKey key = encryptionKeys.get(keyIdentifier).getPublicKey(keyIdentifier.getSubkeyFingerprint().getKeyId()); PublicKeyKeyEncryptionMethodGenerator keyEncryption = ImplementationFactory.getInstance().getPublicKeyKeyEncryptionMethodGenerator(key); encryptedDataGenerator.addMethod(keyEncryption); @@ -193,17 +197,17 @@ public final class EncryptionStream extends OutputStream { } LOGGER.log(LEVEL, "At least one signing key is available -> sign " + hashAlgorithm + " hash of message"); - for (OpenPgpV4Fingerprint fingerprint : signingKeys.keySet()) { - PGPPrivateKey privateKey = signingKeys.get(fingerprint); - LOGGER.log(LEVEL, "Sign using key " + fingerprint); + for (SubkeyIdentifier subkeyIdentifier : signingKeys.keySet()) { + LOGGER.log(LEVEL, "Sign using key " + subkeyIdentifier); + + PGPPrivateKey privateKey = signingKeys.get(subkeyIdentifier).getSecond(); PGPContentSignerBuilder contentSignerBuilder = ImplementationFactory.getInstance() .getPGPContentSignerBuilder( privateKey.getPublicKeyPacket().getAlgorithm(), hashAlgorithm.getAlgorithmId()); - PGPSignatureGenerator signatureGenerator = new PGPSignatureGenerator(contentSignerBuilder); signatureGenerator.init(signatureType.getCode(), privateKey); - signatureGenerators.put(fingerprint, signatureGenerator); + signatureGenerators.put(subkeyIdentifier, new Tuple<>(signingKeys.get(subkeyIdentifier).getFirst(), signatureGenerator)); } } @@ -220,7 +224,8 @@ public final class EncryptionStream extends OutputStream { } private void prepareOnePassSignatures() throws IOException, PGPException { - for (PGPSignatureGenerator signatureGenerator : signatureGenerators.values()) { + for (SubkeyIdentifier identifier : signatureGenerators.keySet()) { + PGPSignatureGenerator signatureGenerator = signatureGenerators.get(identifier).getSecond(); signatureGenerator.generateOnePassVersion(false).encode(outermostStream); } } @@ -236,8 +241,8 @@ public final class EncryptionStream extends OutputStream { } private void prepareResultBuilder() { - for (PGPPublicKey recipient : encryptionKeys) { - resultBuilder.addRecipientKeyId(recipient.getKeyID()); + for (SubkeyIdentifier recipient : encryptionKeys.keySet()) { + resultBuilder.addRecipientKeyId(recipient.getSubkeyFingerprint().getKeyId()); } resultBuilder.setSymmetricKeyAlgorithm(symmetricKeyAlgorithm); resultBuilder.setCompressionAlgorithm(compressionAlgorithm); @@ -247,7 +252,8 @@ public final class EncryptionStream extends OutputStream { public void write(int data) throws IOException { outermostStream.write(data); - for (PGPSignatureGenerator signatureGenerator : signatureGenerators.values()) { + for (SubkeyIdentifier signingKey : signatureGenerators.keySet()) { + PGPSignatureGenerator signatureGenerator = signatureGenerators.get(signingKey).getSecond(); byte asByte = (byte) (data & 0xff); signatureGenerator.update(asByte); } @@ -262,7 +268,8 @@ public final class EncryptionStream extends OutputStream { @Override public void write(byte[] buffer, int off, int len) throws IOException { outermostStream.write(buffer, 0, len); - for (PGPSignatureGenerator signatureGenerator : signatureGenerators.values()) { + for (SubkeyIdentifier signingKey : signatureGenerators.keySet()) { + PGPSignatureGenerator signatureGenerator = signatureGenerators.get(signingKey).getSecond(); signatureGenerator.update(buffer, 0, len); } } @@ -303,14 +310,16 @@ public final class EncryptionStream extends OutputStream { } private void writeSignatures() throws IOException { - for (OpenPgpV4Fingerprint fingerprint : signatureGenerators.keySet()) { - PGPSignatureGenerator signatureGenerator = signatureGenerators.get(fingerprint); + for (SubkeyIdentifier signingKey : signatureGenerators.keySet()) { + PGPSignatureGenerator signatureGenerator = signatureGenerators.get(signingKey).getSecond(); try { PGPSignature signature = signatureGenerator.generate(); if (!detachedSignature) { signature.encode(outermostStream); } - resultBuilder.addDetachedSignature(new DetachedSignature(signature, fingerprint)); + DetachedSignature detachedSignature = new DetachedSignature( + signature, signatureGenerators.get(signingKey).getFirst(), signingKey); + resultBuilder.addDetachedSignature(detachedSignature); } catch (PGPException e) { throw new IOException(e); } diff --git a/pgpainless-core/src/main/java/org/pgpainless/key/KeyRingValidator.java b/pgpainless-core/src/main/java/org/pgpainless/key/KeyRingValidator.java new file mode 100644 index 00000000..12be46cc --- /dev/null +++ b/pgpainless-core/src/main/java/org/pgpainless/key/KeyRingValidator.java @@ -0,0 +1,260 @@ +/* + * Copyright 2021 Paul Schaub. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.pgpainless.key; + +import java.util.Collections; +import java.util.Date; +import java.util.Iterator; +import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.bouncycastle.openpgp.PGPException; +import org.bouncycastle.openpgp.PGPKeyRing; +import org.bouncycastle.openpgp.PGPPublicKey; +import org.bouncycastle.openpgp.PGPPublicKeyRing; +import org.bouncycastle.openpgp.PGPSecretKeyRing; +import org.bouncycastle.openpgp.PGPSignature; +import org.bouncycastle.openpgp.PGPUserAttributeSubpacketVector; +import org.pgpainless.algorithm.SignatureType; +import org.pgpainless.implementation.ImplementationFactory; +import org.pgpainless.key.info.KeyRingInfo; +import org.pgpainless.key.util.KeyRingUtils; +import org.pgpainless.policy.Policy; +import org.pgpainless.signature.SelectSignatureFromKey; +import org.pgpainless.signature.SignatureCreationDateComparator; +import org.pgpainless.signature.SignatureValidationException; +import org.pgpainless.signature.SignatureValidator; +import org.pgpainless.util.CollectionUtils; + +public class KeyRingValidator { + + private static final Logger LOGGER = Logger.getLogger(KeyRingValidator.class.getName()); + + public static R validate(R keyRing, Policy policy) { + try { + return validate(keyRing, policy, policy.getSignatureValidationDate()); + } catch (PGPException e) { + return null; + } + } + + public static R validate(R keyRing, Policy policy, Date validationDate) throws PGPException { + return getKeyRingAtDate(keyRing, policy, validationDate); + } + + private static R getKeyRingAtDate(R keyRing, Policy policy, Date validationDate) throws PGPException { + PGPPublicKey primaryKey = keyRing.getPublicKey(); + primaryKey = evaluatePrimaryKey(primaryKey, policy, validationDate); + if (keyRing instanceof PGPPublicKeyRing) { + PGPPublicKeyRing publicKeys = (PGPPublicKeyRing) keyRing; + publicKeys = PGPPublicKeyRing.insertPublicKey(publicKeys, primaryKey); + keyRing = (R) publicKeys; + } + + return keyRing; + } + + private static PGPPublicKey evaluatePrimaryKey(PGPPublicKey primaryKey, Policy policy, Date validationDate) throws PGPException { + + PGPPublicKey blank = new PGPPublicKey(primaryKey.getPublicKeyPacket(), ImplementationFactory.getInstance().getKeyFingerprintCalculator()); + + Iterator directKeyIterator = primaryKey.getSignaturesOfType(SignatureType.DIRECT_KEY.getCode()); + List directKeyCertifications = CollectionUtils.iteratorToList(directKeyIterator); + Collections.sort(directKeyCertifications, new SignatureCreationDateComparator(SignatureCreationDateComparator.Order.new_to_old)); + for (PGPSignature signature : directKeyCertifications) { + try { + if (SignatureValidator.verifyDirectKeySignature(signature, blank, policy, validationDate)) { + blank = PGPPublicKey.addCertification(blank, signature); + } + } catch (SignatureValidationException e) { + LOGGER.log(Level.INFO, "Rejecting direct key signature", e); + } + } + + Iterator revocationIterator = primaryKey.getSignaturesOfType(SignatureType.KEY_REVOCATION.getCode()); + List directKeyRevocations = CollectionUtils.iteratorToList(revocationIterator); + Collections.sort(directKeyRevocations, new SignatureCreationDateComparator(SignatureCreationDateComparator.Order.new_to_old)); + for (PGPSignature signature : directKeyRevocations) { + try { + if (SignatureValidator.verifyKeyRevocationSignature(signature, primaryKey, policy, validationDate)) { + blank = PGPPublicKey.addCertification(blank, signature); + } + } catch (SignatureValidationException e) { + LOGGER.log(Level.INFO, "Rejecting key revocation signature", e); + } + } + + Iterator userIdIterator = primaryKey.getUserIDs(); + while (userIdIterator.hasNext()) { + String userId = userIdIterator.next(); + Iterator userIdSigs = primaryKey.getSignaturesForID(userId); + List signatures = CollectionUtils.iteratorToList(userIdSigs); + Collections.sort(signatures, new SignatureCreationDateComparator(SignatureCreationDateComparator.Order.new_to_old)); + for (PGPSignature signature : signatures) { + try { + if (SignatureType.valueOf(signature.getSignatureType()) == SignatureType.CERTIFICATION_REVOCATION) { + if (SignatureValidator.verifyUserIdRevocation(userId, signature, primaryKey, policy, validationDate)) { + blank = PGPPublicKey.addCertification(blank, userId, signature); + } + } else { + if (SignatureValidator.verifyUserIdCertification(userId, signature, primaryKey, policy, validationDate)) { + blank = PGPPublicKey.addCertification(blank, userId, signature); + } + } + } catch (SignatureValidationException e) { + LOGGER.log(Level.INFO, "Rejecting user-id certification for user-id " + userId, e); + } + } + } + + Iterator userAttributes = primaryKey.getUserAttributes(); + while (userAttributes.hasNext()) { + PGPUserAttributeSubpacketVector userAttribute = userAttributes.next(); + Iterator userAttributeSignatureIterator = primaryKey.getSignaturesForUserAttribute(userAttribute); + while (userAttributeSignatureIterator.hasNext()) { + PGPSignature signature = userAttributeSignatureIterator.next(); + try { + if (SignatureType.valueOf(signature.getSignatureType()) == SignatureType.CERTIFICATION_REVOCATION) { + if (SignatureValidator.verifyUserAttributesRevocation(userAttribute, signature, primaryKey, policy, validationDate)) { + blank = PGPPublicKey.addCertification(blank, userAttribute, signature); + } + } else { + if (SignatureValidator.verifyUserAttributesCertification(userAttribute, signature, primaryKey, policy, validationDate)) { + blank = PGPPublicKey.addCertification(blank, userAttribute, signature); + } + } + } catch (SignatureValidationException e) { + LOGGER.log(Level.INFO, "Rejecting user-attribute signature", e); + } + } + } + + return blank; + } + + public static R getKeyRingAtDate(R keyRing, KeyRingInfo info) { + Iterator iterator = keyRing.getPublicKeys(); + while (iterator.hasNext()) { + PGPPublicKey publicKey = iterator.next(); + if (publicKey.isMasterKey()) { + keyRing = assessPrimaryKeyAtDate(publicKey, keyRing, info); + } else { + keyRing = assessSubkeyAtDate(publicKey, keyRing, info); + } + } + return keyRing; + } + + private static R assessPrimaryKeyAtDate(PGPPublicKey primaryKey, PGPKeyRing keyRing, KeyRingInfo info) { + if (!primaryKey.isMasterKey()) { + throw new IllegalArgumentException("Passed in key is not a primary key"); + } + + // Direct Key Signatures + PGPSignature latestSelfSig = info.getCurrentDirectKeySelfSignature(); + PGPSignature latestSelfRevocation = info.getRevocationSelfSignature(); + + + // User-ID certifications + Iterator userIdIterator = primaryKey.getUserIDs(); + while (userIdIterator.hasNext()) { + String userId = userIdIterator.next(); + boolean isUserIdBound = false; + Iterator userIdSigIterator = primaryKey.getSignaturesForID(userId); + while (userIdSigIterator.hasNext()) { + PGPSignature userIdSig = userIdSigIterator.next(); + if (!SelectSignatureFromKey.isValidSignatureOnUserId(userId, primaryKey) + .accept(userIdSig, primaryKey, keyRing)) { + primaryKey = PGPPublicKey.removeCertification(primaryKey, userId, userIdSig); + continue; + } + isUserIdBound = true; + } + if (!isUserIdBound) { + primaryKey = PGPPublicKey.removeCertification(primaryKey, userId); + } + } + + // Revocations + Iterator revocationSignatures = primaryKey.getSignaturesOfType(SignatureType.KEY_REVOCATION.getCode()); + while (revocationSignatures.hasNext()) { + PGPSignature revocationSig = revocationSignatures.next(); + if (!SelectSignatureFromKey.isValidKeyRevocationSignature(primaryKey) + .accept(revocationSig, primaryKey, keyRing)) { + primaryKey = PGPPublicKey.removeCertification(primaryKey, revocationSig); + } + } + + return (R) replacePublicKey(keyRing, primaryKey); + } + + private static R assessSubkeyAtDate(PGPPublicKey subkey, PGPKeyRing keyRing, KeyRingInfo info) { + if (subkey.isMasterKey()) { + throw new IllegalArgumentException("Passed in key is not a subkey"); + } + + // Subkey binding sigs + Iterator subkeyBindingSigIterator = subkey.getSignaturesOfType(SignatureType.SUBKEY_BINDING.getCode()); + while (subkeyBindingSigIterator.hasNext()) { + PGPSignature signature = subkeyBindingSigIterator.next(); + if (!SelectSignatureFromKey.isValidSubkeyBindingSignature(keyRing.getPublicKey(), subkey) + .accept(signature, subkey, keyRing)) { + subkey = PGPPublicKey.removeCertification(subkey, signature); + } + } + + // Subkey revocation sigs + Iterator revocationSigIterator = subkey.getSignaturesOfType(SignatureType.SUBKEY_REVOCATION.getCode()); + while (revocationSigIterator.hasNext()) { + PGPSignature signature = revocationSigIterator.next(); + if (!SelectSignatureFromKey.isValidSubkeyRevocationSignature().accept(signature, subkey, keyRing)) { + subkey = PGPPublicKey.removeCertification(subkey, signature); + } + } + + Iterator directKeySigIterator = subkey.getSignaturesOfType(SignatureType.DIRECT_KEY.getCode()); + while (directKeySigIterator.hasNext()) { + PGPSignature signature = directKeySigIterator.next(); + PGPPublicKey creator = keyRing.getPublicKey(signature.getKeyID()); + if (creator == null) { + // remove external signature + subkey = PGPPublicKey.removeCertification(subkey, signature); + continue; + } + + if (!SelectSignatureFromKey.isValidDirectKeySignature(creator, subkey) + .accept(signature, subkey, keyRing)) { + subkey = PGPPublicKey.removeCertification(subkey, signature); + } + } + + return (R) replacePublicKey(keyRing, subkey); + } + + private static PGPKeyRing replacePublicKey(PGPKeyRing keyRing, PGPPublicKey publicKey) { + if (keyRing instanceof PGPPublicKeyRing) { + keyRing = PGPPublicKeyRing.insertPublicKey((PGPPublicKeyRing) keyRing, publicKey); + } else if (keyRing instanceof PGPSecretKeyRing) { + PGPSecretKeyRing secretKeys = (PGPSecretKeyRing) keyRing; + PGPPublicKeyRing publicKeys = KeyRingUtils.publicKeyRingFrom(secretKeys); + publicKeys = PGPPublicKeyRing.insertPublicKey(publicKeys, publicKey); + secretKeys = PGPSecretKeyRing.replacePublicKeys(secretKeys, publicKeys); + keyRing = secretKeys; + } + return keyRing; + } +} diff --git a/pgpainless-core/src/main/java/org/pgpainless/key/KeyValidator.java b/pgpainless-core/src/main/java/org/pgpainless/key/KeyValidator.java new file mode 100644 index 00000000..f5be11f0 --- /dev/null +++ b/pgpainless-core/src/main/java/org/pgpainless/key/KeyValidator.java @@ -0,0 +1,59 @@ +/* + * Copyright 2021 Paul Schaub. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.pgpainless.key; + +import java.util.Iterator; + +import org.bouncycastle.openpgp.PGPException; +import org.bouncycastle.openpgp.PGPPublicKey; +import org.bouncycastle.openpgp.PGPPublicKeyRing; +import org.bouncycastle.openpgp.PGPSignature; +import org.pgpainless.algorithm.SignatureType; +import org.pgpainless.signature.SelectSignatureFromKey; + +public class KeyValidator { + + public PGPPublicKeyRing validatePublicKeyRing(PGPPublicKeyRing publicKeys) throws PGPException { + PGPPublicKey primaryKey = publicKeys.getPublicKey(); + if (!isValidPrimaryKey(primaryKey, publicKeys)) { + throw new PGPException("Primary key is not valid"); + } + return publicKeys; + } + + public static boolean isValidPrimaryKey(PGPPublicKey publicKey, PGPPublicKeyRing keyRing) { + if (!publicKey.isMasterKey()) { + return false; + } + + if (keyRing.getPublicKey().getKeyID() != publicKey.getKeyID()) { + return false; + } + + Iterator signatures = publicKey.getSignatures(); + while (signatures.hasNext()) { + PGPSignature signature = signatures.next(); + SignatureType signatureType = SignatureType.valueOf(signature.getSignatureType()); + switch (signatureType) { + case KEY_REVOCATION: + if (SelectSignatureFromKey.isValidKeyRevocationSignature(publicKey).accept(signature, publicKey, keyRing)) { + return false; + } + } + } + return true; + } +} diff --git a/pgpainless-core/src/main/java/org/pgpainless/key/SubkeyIdentifier.java b/pgpainless-core/src/main/java/org/pgpainless/key/SubkeyIdentifier.java new file mode 100644 index 00000000..3a7d5856 --- /dev/null +++ b/pgpainless-core/src/main/java/org/pgpainless/key/SubkeyIdentifier.java @@ -0,0 +1,109 @@ +/* + * Copyright 2021 Paul Schaub. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.pgpainless.key; + +import javax.annotation.Nonnull; + +import org.bouncycastle.openpgp.PGPKeyRing; + +/** + * Tuple class used to identify a subkey by fingerprints of the primary key of the subkeys key ring, + * as well as the subkeys fingerprint. + */ +public class SubkeyIdentifier { + + private final OpenPgpV4Fingerprint primaryKeyFingerprint; + private final OpenPgpV4Fingerprint subkeyFingerprint; + + /** + * Create a {@link SubkeyIdentifier} from a {@link PGPKeyRing} and the subkeys key id. + * {@link #getPrimaryKeyFingerprint()} will return the {@link OpenPgpV4Fingerprint} of the keyrings primary key, + * while {@link #getSubkeyFingerprint()} will return the subkeys fingerprint. + * + * @param keyRing keyring the subkey belongs to + * @param keyId keyid of the subkey + */ + public SubkeyIdentifier(@Nonnull PGPKeyRing keyRing, long keyId) { + this(new OpenPgpV4Fingerprint(keyRing.getPublicKey()), new OpenPgpV4Fingerprint(keyRing.getPublicKey(keyId))); + } + + /** + * Create a {@link SubkeyIdentifier} that identifies the primary key with the given fingerprint. + * This means, both {@link #getPrimaryKeyFingerprint()} and {@link #getSubkeyFingerprint()} return the same. + * + * @param primaryKeyFingerprint fingerprint of the identified key + */ + public SubkeyIdentifier(@Nonnull OpenPgpV4Fingerprint primaryKeyFingerprint) { + this(primaryKeyFingerprint, primaryKeyFingerprint); + } + + /** + * Create a {@link SubkeyIdentifier} which points to the subkey with the given subkeyFingerprint, + * which has a primary key with the given primaryKeyFingerprint. + * + * @param primaryKeyFingerprint fingerprint of the primary key + * @param subkeyFingerprint fingerprint of the subkey + */ + public SubkeyIdentifier(@Nonnull OpenPgpV4Fingerprint primaryKeyFingerprint, @Nonnull OpenPgpV4Fingerprint subkeyFingerprint) { + this.primaryKeyFingerprint = primaryKeyFingerprint; + this.subkeyFingerprint = subkeyFingerprint; + } + + /** + * Return the {@link OpenPgpV4Fingerprint} of the primary key of the identified key. + * This might be the same as {@link #getSubkeyFingerprint()} if the identified subkey is the primary key. + * + * @return primary key fingerprint + */ + public @Nonnull OpenPgpV4Fingerprint getPrimaryKeyFingerprint() { + return primaryKeyFingerprint; + } + + /** + * Return the {@link OpenPgpV4Fingerprint} of the identified subkey. + * + * @return subkey fingerprint + */ + public @Nonnull OpenPgpV4Fingerprint getSubkeyFingerprint() { + return subkeyFingerprint; + } + + @Override + public int hashCode() { + return primaryKeyFingerprint.hashCode() * 31 + subkeyFingerprint.hashCode(); + } + + @Override + public boolean equals(Object obj) { + if (obj == null) { + return false; + } + if (this == obj) { + return true; + } + if (!(obj instanceof SubkeyIdentifier)) { + return false; + } + SubkeyIdentifier other = (SubkeyIdentifier) obj; + return getPrimaryKeyFingerprint().equals(other.getPrimaryKeyFingerprint()) + && getSubkeyFingerprint().equals(other.getSubkeyFingerprint()); + } + + @Override + public String toString() { + return getSubkeyFingerprint() + " " + getPrimaryKeyFingerprint(); + } +} diff --git a/pgpainless-core/src/main/java/org/pgpainless/key/generation/KeyRingBuilder.java b/pgpainless-core/src/main/java/org/pgpainless/key/generation/KeyRingBuilder.java index 3e554696..c732f487 100644 --- a/pgpainless-core/src/main/java/org/pgpainless/key/generation/KeyRingBuilder.java +++ b/pgpainless-core/src/main/java/org/pgpainless/key/generation/KeyRingBuilder.java @@ -60,7 +60,7 @@ import org.pgpainless.key.generation.type.xdh.XDHCurve; import org.pgpainless.key.util.UserId; import org.pgpainless.provider.ProviderFactory; import org.pgpainless.util.Passphrase; -import org.pgpainless.util.SignatureSubpacketGeneratorUtil; +import org.pgpainless.signature.subpackets.SignatureSubpacketGeneratorUtil; public class KeyRingBuilder implements KeyRingBuilderInterface { diff --git a/pgpainless-core/src/main/java/org/pgpainless/key/generation/type/KeyType.java b/pgpainless-core/src/main/java/org/pgpainless/key/generation/type/KeyType.java index 668e4fe4..c5e50918 100644 --- a/pgpainless-core/src/main/java/org/pgpainless/key/generation/type/KeyType.java +++ b/pgpainless-core/src/main/java/org/pgpainless/key/generation/type/KeyType.java @@ -57,7 +57,9 @@ public interface KeyType { * * @return true if the key can sign. */ - boolean canSign(); + default boolean canSign() { + return getAlgorithm().isSigningCapable(); + } /** * Return true if the key that is generated from this type is able to carry the CERTIFY_OTHER key flag. @@ -85,7 +87,9 @@ public interface KeyType { * * @return true if the key can encrypt communication */ - boolean canEncryptCommunication(); + default boolean canEncryptCommunication() { + return getAlgorithm().isEncryptionCapable(); + } /** * Return true if the key that is generated from this type is able to carry the ENCRYPT_STORAGE key flag. diff --git a/pgpainless-core/src/main/java/org/pgpainless/key/generation/type/ecc/ecdh/ECDH.java b/pgpainless-core/src/main/java/org/pgpainless/key/generation/type/ecc/ecdh/ECDH.java index b5967874..c91c2b90 100644 --- a/pgpainless-core/src/main/java/org/pgpainless/key/generation/type/ecc/ecdh/ECDH.java +++ b/pgpainless-core/src/main/java/org/pgpainless/key/generation/type/ecc/ecdh/ECDH.java @@ -49,14 +49,4 @@ public final class ECDH implements KeyType { public AlgorithmParameterSpec getAlgorithmSpec() { return new ECNamedCurveGenParameterSpec(curve.getName()); } - - @Override - public boolean canSign() { - return false; - } - - @Override - public boolean canEncryptCommunication() { - return true; - } } diff --git a/pgpainless-core/src/main/java/org/pgpainless/key/generation/type/ecc/ecdsa/ECDSA.java b/pgpainless-core/src/main/java/org/pgpainless/key/generation/type/ecc/ecdsa/ECDSA.java index 6e68e6f6..440019ff 100644 --- a/pgpainless-core/src/main/java/org/pgpainless/key/generation/type/ecc/ecdsa/ECDSA.java +++ b/pgpainless-core/src/main/java/org/pgpainless/key/generation/type/ecc/ecdsa/ECDSA.java @@ -51,14 +51,4 @@ public final class ECDSA implements KeyType { return new ECNamedCurveGenParameterSpec(curve.getName()); } - @Override - public boolean canSign() { - return true; - } - - @Override - public boolean canEncryptCommunication() { - return false; - } - } diff --git a/pgpainless-core/src/main/java/org/pgpainless/key/generation/type/eddsa/EdDSA.java b/pgpainless-core/src/main/java/org/pgpainless/key/generation/type/eddsa/EdDSA.java index 3c8bb171..0db4c179 100644 --- a/pgpainless-core/src/main/java/org/pgpainless/key/generation/type/eddsa/EdDSA.java +++ b/pgpainless-core/src/main/java/org/pgpainless/key/generation/type/eddsa/EdDSA.java @@ -51,13 +51,4 @@ public final class EdDSA implements KeyType { return new ECNamedCurveGenParameterSpec(curve.getName()); } - @Override - public boolean canSign() { - return true; - } - - @Override - public boolean canEncryptCommunication() { - return false; - } } diff --git a/pgpainless-core/src/main/java/org/pgpainless/key/generation/type/elgamal/ElGamal.java b/pgpainless-core/src/main/java/org/pgpainless/key/generation/type/elgamal/ElGamal.java index 690c8f48..b55a2415 100644 --- a/pgpainless-core/src/main/java/org/pgpainless/key/generation/type/elgamal/ElGamal.java +++ b/pgpainless-core/src/main/java/org/pgpainless/key/generation/type/elgamal/ElGamal.java @@ -52,14 +52,4 @@ public final class ElGamal implements KeyType { return new ElGamalParameterSpec(length.getP(), length.getG()); } - @Override - public boolean canSign() { - return false; - } - - @Override - public boolean canEncryptCommunication() { - return true; - } - } diff --git a/pgpainless-core/src/main/java/org/pgpainless/key/generation/type/rsa/RSA.java b/pgpainless-core/src/main/java/org/pgpainless/key/generation/type/rsa/RSA.java index 44aa504a..67d6a051 100644 --- a/pgpainless-core/src/main/java/org/pgpainless/key/generation/type/rsa/RSA.java +++ b/pgpainless-core/src/main/java/org/pgpainless/key/generation/type/rsa/RSA.java @@ -51,14 +51,4 @@ public class RSA implements KeyType { public AlgorithmParameterSpec getAlgorithmSpec() { return new RSAKeyGenParameterSpec(length.getLength(), RSAKeyGenParameterSpec.F4); } - - @Override - public boolean canSign() { - return true; - } - - @Override - public boolean canEncryptCommunication() { - return true; - } } diff --git a/pgpainless-core/src/main/java/org/pgpainless/key/generation/type/xdh/XDH.java b/pgpainless-core/src/main/java/org/pgpainless/key/generation/type/xdh/XDH.java index 50b1aa7b..50cd768d 100644 --- a/pgpainless-core/src/main/java/org/pgpainless/key/generation/type/xdh/XDH.java +++ b/pgpainless-core/src/main/java/org/pgpainless/key/generation/type/xdh/XDH.java @@ -48,13 +48,4 @@ public final class XDH implements KeyType { return new ECNamedCurveGenParameterSpec(curve.getName()); } - @Override - public boolean canSign() { - return false; - } - - @Override - public boolean canEncryptCommunication() { - return true; - } } diff --git a/pgpainless-core/src/main/java/org/pgpainless/key/info/KeyRingInfo.java b/pgpainless-core/src/main/java/org/pgpainless/key/info/KeyRingInfo.java index 96aae0b5..70656001 100644 --- a/pgpainless-core/src/main/java/org/pgpainless/key/info/KeyRingInfo.java +++ b/pgpainless-core/src/main/java/org/pgpainless/key/info/KeyRingInfo.java @@ -15,30 +15,34 @@ */ package org.pgpainless.key.info; -import static org.pgpainless.key.util.SignatureUtils.getLatestValidSignature; -import static org.pgpainless.key.util.SignatureUtils.sortByCreationTimeAscending; import static org.pgpainless.util.CollectionUtils.iteratorToList; import java.util.ArrayList; import java.util.Collections; import java.util.Date; +import java.util.HashSet; import java.util.Iterator; import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; import java.util.regex.Matcher; import java.util.regex.Pattern; -import org.bouncycastle.openpgp.PGPException; +import org.bouncycastle.bcpg.sig.KeyFlags; +import org.bouncycastle.bcpg.sig.PrimaryUserID; import org.bouncycastle.openpgp.PGPKeyRing; import org.bouncycastle.openpgp.PGPPublicKey; import org.bouncycastle.openpgp.PGPPublicKeyRing; import org.bouncycastle.openpgp.PGPSecretKey; import org.bouncycastle.openpgp.PGPSecretKeyRing; import org.bouncycastle.openpgp.PGPSignature; +import org.pgpainless.algorithm.KeyFlag; import org.pgpainless.algorithm.PublicKeyAlgorithm; -import org.pgpainless.algorithm.SignatureType; import org.pgpainless.key.OpenPgpV4Fingerprint; -import org.pgpainless.key.util.KeyRingUtils; -import org.pgpainless.key.util.SignatureUtils; +import org.pgpainless.signature.SignaturePicker; +import org.pgpainless.signature.SignatureUtils; +import org.pgpainless.signature.subpackets.SignatureSubpacketsUtil; /** * Utility class to quickly extract certain information from a {@link PGPPublicKeyRing}/{@link PGPSecretKeyRing}. @@ -49,8 +53,65 @@ public class KeyRingInfo { private final PGPKeyRing keys; + private final PGPSignature revocationSelfSignature; + private final PGPSignature mostRecentSelfSignature; + private final Map mostRecentUserIdSignatures = new ConcurrentHashMap<>(); + private final Map mostRecentUserIdRevocations = new ConcurrentHashMap<>(); + private final Map mostRecentSubkeyBindings = new ConcurrentHashMap<>(); + private final Map mostRecentSubkeyRevocations = new ConcurrentHashMap<>(); + + /** + * Evaluate the key ring at creation time of the given signature. + * + * @param keyRing key ring + * @param signature signature + * @return info of key ring at signature creation time + */ + public static KeyRingInfo evaluateForSignature(PGPKeyRing keyRing, PGPSignature signature) { + return new KeyRingInfo(keyRing, signature.getCreationTime()); + } + + /** + * Evaluate the key ring right now. + * + * @param keys key ring + */ public KeyRingInfo(PGPKeyRing keys) { + this(keys, new Date()); + } + + public KeyRingInfo(PGPKeyRing keys, Date validationDate) { this.keys = keys; + + revocationSelfSignature = SignaturePicker.pickCurrentRevocationSelfSignature(keys, validationDate); + mostRecentSelfSignature = SignaturePicker.pickCurrentDirectKeySelfSignature(keys, validationDate); + + for (Iterator it = keys.getPublicKey().getUserIDs(); it.hasNext(); ) { + String userId = it.next(); + PGPSignature certification = SignaturePicker.pickCurrentUserIdCertificationSignature(keys, userId, validationDate); + if (certification != null) { + mostRecentUserIdSignatures.put(userId, certification); + } + PGPSignature revocation = SignaturePicker.pickCurrentUserIdRevocationSignature(keys, userId, validationDate); + if (revocation != null) { + mostRecentUserIdRevocations.put(userId, revocation); + } + } + + Iterator publicKeys = keys.getPublicKeys(); + publicKeys.next(); // Skip primary key + + while (publicKeys.hasNext()) { + PGPPublicKey subkey = publicKeys.next(); + PGPSignature bindingSig = SignaturePicker.pickCurrentSubkeyBindingSignature(keys, subkey, validationDate); + if (bindingSig != null) { + mostRecentSubkeyBindings.put(subkey.getKeyID(), bindingSig); + } + PGPSignature bindingRevocation = SignaturePicker.pickCurrentSubkeyBindingRevocationSignature(keys, subkey, validationDate); + if (bindingRevocation != null) { + mostRecentSubkeyRevocations.put(subkey.getKeyID(), bindingRevocation); + } + } } /** @@ -74,6 +135,21 @@ public class KeyRingInfo { return keyRing.getPublicKey(keyId); } + public boolean isKeyValidlyBound(long keyId) { + PGPPublicKey publicKey = keys.getPublicKey(keyId); + if (publicKey == null) { + return false; + } + + if (publicKey == getPublicKey()) { + return revocationSelfSignature == null; + } else { + PGPSignature binding = mostRecentSubkeyBindings.get(keyId); + PGPSignature revocation = mostRecentSubkeyRevocations.get(keyId); + return binding != null && revocation == null; + } + } + /** * Return all {@link PGPPublicKey PGPPublicKeys} of this key ring. * The first key in the list being the primary key. @@ -145,15 +221,21 @@ public class KeyRingInfo { return new OpenPgpV4Fingerprint(getPublicKey()); } - public String getPrimaryUserId() throws PGPException { - List userIds = getValidUserIds(); - for (String userId : userIds) { - PGPSignature signature = getLatestValidSignatureOnUserId(userId); - if (signature.getHashedSubPackets().isPrimaryUserID()) { - return userId; + public String getPrimaryUserId() { + String primaryUserId = null; + Date modificationDate = null; + for (String userId : getValidUserIds()) { + PGPSignature signature = mostRecentUserIdSignatures.get(userId); + PrimaryUserID subpacket = SignatureSubpacketsUtil.getPrimaryUserId(signature); + if (subpacket != null && subpacket.isPrimaryUserID()) { + // if there are multiple primary userIDs, return most recently signed + if (modificationDate == null || modificationDate.before(signature.getCreationTime())) { + primaryUserId = userId; + modificationDate = signature.getCreationTime(); + } } } - return null; + return primaryUserId; } /** @@ -179,15 +261,13 @@ public class KeyRingInfo { } public boolean isUserIdValid(String userId) { - return isUserIdValid(getKeyId(), userId); - } + PGPSignature certification = mostRecentUserIdSignatures.get(userId); + PGPSignature revocation = mostRecentUserIdRevocations.get(userId); - public boolean isUserIdValid(long keyId, String userId) { - try { - return SignatureUtils.isUserIdValid(getPublicKey(keyId), userId); - } catch (PGPException e) { + if (certification == null) { return false; } + return revocation == null; } /** @@ -207,6 +287,68 @@ public class KeyRingInfo { return emails; } + public PGPSignature getCurrentDirectKeySelfSignature() { + return mostRecentSelfSignature; + } + + public PGPSignature getRevocationSelfSignature() { + return revocationSelfSignature; + } + + public PGPSignature getCurrentUserIdCertification(String userId) { + return mostRecentUserIdSignatures.get(userId); + } + + public PGPSignature getUserIdRevocation(String userId) { + return mostRecentUserIdRevocations.get(userId); + } + + public PGPSignature getCurrentSubkeyBindingSignature(long keyId) { + return mostRecentSubkeyBindings.get(keyId); + } + + public PGPSignature getSubkeyRevocationSignature(long keyId) { + return mostRecentSubkeyRevocations.get(keyId); + } + + public List getKeyFlagsOf(long keyId) { + if (getPublicKey().getKeyID() == keyId) { + + if (mostRecentSelfSignature != null) { + KeyFlags flags = SignatureSubpacketsUtil.getKeyFlags(mostRecentSelfSignature); + if (flags != null) { + return KeyFlag.fromBitmask(flags.getFlags()); + } + } + + String primaryUserId = getPrimaryUserId(); + if (primaryUserId != null) { + KeyFlags flags = SignatureSubpacketsUtil.getKeyFlags(mostRecentUserIdSignatures.get(primaryUserId)); + if (flags != null) { + return KeyFlag.fromBitmask(flags.getFlags()); + } + } + } + return Collections.emptyList(); + } + + public List getKeyFlagsOf(String userId) { + if (!isUserIdValid(userId)) { + return Collections.emptyList(); + } + + PGPSignature userIdCertification = mostRecentUserIdSignatures.get(userId); + if (userIdCertification == null) { + return Collections.emptyList(); + } + + KeyFlags keyFlags = SignatureSubpacketsUtil.getKeyFlags(userIdCertification); + if (keyFlags != null) { + return KeyFlag.fromBitmask(keyFlags.getFlags()); + } + return Collections.emptyList(); + } + /** * Return the algorithm of the primary key. * @@ -232,17 +374,26 @@ public class KeyRingInfo { * @return last modification date. */ public Date getLastModified() { - Iterator signatures = getPublicKey().getSignatures(); - long last = 0L; - while (signatures.hasNext()) { - PGPSignature signature = signatures.next(); - if (getKeyId() != signature.getKeyID()) { - // Throw away signatures made from others - continue; + PGPSignature mostRecent = getMostRecentSignature(); + return mostRecent.getCreationTime(); + } + + private PGPSignature getMostRecentSignature() { + Set allSignatures = new HashSet<>(); + if (mostRecentSelfSignature != null) allSignatures.add(mostRecentSelfSignature); + if (revocationSelfSignature != null) allSignatures.add(revocationSelfSignature); + allSignatures.addAll(mostRecentUserIdSignatures.values()); + allSignatures.addAll(mostRecentUserIdRevocations.values()); + allSignatures.addAll(mostRecentSubkeyBindings.values()); + allSignatures.addAll(mostRecentSubkeyRevocations.values()); + + PGPSignature mostRecent = null; + for (PGPSignature signature : allSignatures) { + if (mostRecent == null || signature.getCreationTime().after(mostRecent.getCreationTime())) { + mostRecent = signature; } - last = Math.max(last, signature.getCreationTime().getTime()); } - return new Date(last); + return mostRecent; } /** @@ -251,16 +402,7 @@ public class KeyRingInfo { * @return revocation date or null */ public Date getRevocationDate() { - Iterator revocations = getPublicKey().getSignaturesOfType(SignatureType.KEY_REVOCATION.getCode()); - while (revocations.hasNext()) { - PGPSignature revocation = revocations.next(); - if (getKeyId() != revocation.getKeyID()) { - // Throw away signatures made from others - continue; - } - return revocation.getCreationTime(); - } - return null; + return revocationSelfSignature == null ? null : revocationSelfSignature.getCreationTime(); } /** @@ -268,16 +410,32 @@ public class KeyRingInfo { * * @return expiration date */ - public Date getExpirationDate() { - return getExpirationDate(new OpenPgpV4Fingerprint(getPublicKey())); + public Date getPrimaryKeyExpirationDate() { + Date lastExpiration = null; + if (mostRecentSelfSignature != null) { + lastExpiration = SignatureUtils.getKeyExpirationDate(getCreationDate(), mostRecentSelfSignature); + } + + for (String userId : getValidUserIds()) { + PGPSignature signature = getCurrentUserIdCertification(userId); + Date expiration = SignatureUtils.getKeyExpirationDate(getCreationDate(), signature); + if (expiration != null && (lastExpiration == null || expiration.after(lastExpiration))) { + lastExpiration = expiration; + } + } + return lastExpiration; } - public Date getExpirationDate(OpenPgpV4Fingerprint fingerprint) { - long validSeconds = keys.getPublicKey(fingerprint.getKeyId()).getValidSeconds(); - if (validSeconds == 0) { - return null; + public Date getSubkeyExpirationDate(OpenPgpV4Fingerprint fingerprint) { + if (getPublicKey().getKeyID() == fingerprint.getKeyId()) { + return getPrimaryKeyExpirationDate(); } - return new Date(getCreationDate().getTime() + (1000 * validSeconds)); + + PGPPublicKey subkey = getPublicKey(fingerprint.getKeyId()); + if (subkey == null) { + throw new IllegalArgumentException("No subkey with fingerprint " + fingerprint + " found."); + } + return SignatureUtils.getKeyExpirationDate(subkey.getCreationTime(), mostRecentSubkeyBindings.get(fingerprint.getKeyId())); } /** @@ -334,67 +492,4 @@ public class KeyRingInfo { } return false; } - - public List getSelfSignaturesOnKey(long subkeyId) { - PGPPublicKey publicKey = KeyRingUtils.requirePublicKeyFrom(keys, subkeyId); - Iterator it = publicKey.getSignaturesForKeyID(keys.getPublicKey().getKeyID()); - List signatures = iteratorToList(it); - sortByCreationTimeAscending(signatures); - return signatures; - } - - public PGPSignature getLatestValidSelfSignatureOnKey() throws PGPException { - return getLatestValidSelfSignatureOnKey(new OpenPgpV4Fingerprint(getPublicKey())); - } - - public PGPSignature getLatestValidSelfSignatureOnKey(OpenPgpV4Fingerprint fingerprint) throws PGPException { - return getLatestValidSelfSignatureOnKey(fingerprint.getKeyId()); - } - - public PGPSignature getLatestValidSelfSignatureOnKey(long subkeyId) throws PGPException { - PGPPublicKey publicKey = KeyRingUtils.requirePublicKeyFrom(keys, subkeyId); - List signatures = getSelfSignaturesOnKey(keys.getPublicKey().getKeyID()); - return getLatestValidSignature(publicKey, signatures, keys); - } - - public PGPSignature getLatestValidSignatureOnUserId(String userId) throws PGPException { - PGPPublicKey publicKey = KeyRingUtils.requirePrimaryPublicKeyFrom(keys); - Iterator iterator = publicKey.getSignaturesForID(userId); - List signatures = iteratorToList(iterator); - return getLatestValidSignature(publicKey, signatures, keys); - } - - public List getBindingSignaturesOnKey(OpenPgpV4Fingerprint fingerprint) { - return getBindingSignaturesOnKey(fingerprint.getKeyId()); - } - - public List getBindingSignaturesOnKey(long subkeyId) { - if (subkeyId == getKeyId()) { - return Collections.emptyList(); - } - PGPPublicKey publicKey = KeyRingUtils.requirePublicKeyFrom(keys, subkeyId); - return SignatureUtils.getBindingSignatures(publicKey, getKeyId()); - } - - public PGPSignature getLatestValidBindingSignatureOnKey(long subKeyID) throws PGPException { - PGPPublicKey publicKey = KeyRingUtils.requirePublicKeyFrom(keys, subKeyID); - List signatures = getBindingSignaturesOnKey(subKeyID); - return getLatestValidSignature(publicKey, signatures, keys); - } - - public PGPSignature getLatestValidSelfOrBindingSignatureOnKey(OpenPgpV4Fingerprint fingerprint) throws PGPException { - return getLatestValidSelfOrBindingSignatureOnKey(fingerprint.getKeyId()); - } - - public PGPSignature getLatestValidSelfOrBindingSignatureOnKey(long subKeyId) throws PGPException { - PGPSignature self = getLatestValidSelfSignatureOnKey(subKeyId); - PGPSignature binding = getLatestValidBindingSignatureOnKey(subKeyId); - if (self == null) { - return binding; - } - if (binding == null) { - return self; - } - return self.getCreationTime().after(binding.getCreationTime()) ? self : binding; - } } diff --git a/pgpainless-core/src/main/java/org/pgpainless/key/modification/secretkeyring/SecretKeyRingEditor.java b/pgpainless-core/src/main/java/org/pgpainless/key/modification/secretkeyring/SecretKeyRingEditor.java index 1e932f0e..28ad1d9f 100644 --- a/pgpainless-core/src/main/java/org/pgpainless/key/modification/secretkeyring/SecretKeyRingEditor.java +++ b/pgpainless-core/src/main/java/org/pgpainless/key/modification/secretkeyring/SecretKeyRingEditor.java @@ -63,9 +63,9 @@ import org.pgpainless.key.protection.UnprotectedKeysProtector; import org.pgpainless.key.protection.passphrase_provider.SolitaryPassphraseProvider; import org.pgpainless.key.util.KeyRingUtils; import org.pgpainless.key.util.RevocationAttributes; -import org.pgpainless.key.util.SignatureUtils; +import org.pgpainless.signature.SignatureUtils; import org.pgpainless.util.Passphrase; -import org.pgpainless.util.SignatureSubpacketGeneratorUtil; +import org.pgpainless.signature.subpackets.SignatureSubpacketGeneratorUtil; import org.pgpainless.util.selection.userid.SelectUserId; public class SecretKeyRingEditor implements SecretKeyRingEditorInterface { @@ -563,12 +563,16 @@ public class SecretKeyRingEditor implements SecretKeyRingEditorInterface { signatureGenerator.setHashedSubpackets(subPackets); PGPPrivateKey privateKey = primaryKey.extractPrivateKey(protector.getDecryptor(primaryKey.getKeyID())); - SignatureType type = revokeeSubKey.isMasterKey() ? SignatureType.KEY_REVOCATION : SignatureType.SUBKEY_REVOCATION; - signatureGenerator.init(type.getCode(), privateKey); - // Generate revocation - PGPSignature subKeyRevocation = signatureGenerator.generateCertification(primaryKey.getPublicKey(), revokeeSubKey); - return subKeyRevocation; + PGPSignature revocation; + if (revokeeSubKey.isMasterKey()) { + signatureGenerator.init(SignatureType.KEY_REVOCATION.getCode(), privateKey); + revocation = signatureGenerator.generateCertification(revokeeSubKey); + } else { + signatureGenerator.init(SignatureType.SUBKEY_REVOCATION.getCode(), privateKey); + revocation = signatureGenerator.generateCertification(primaryKey.getPublicKey(), revokeeSubKey); + } + return revocation; } @Override diff --git a/pgpainless-core/src/main/java/org/pgpainless/key/util/RevocationAttributes.java b/pgpainless-core/src/main/java/org/pgpainless/key/util/RevocationAttributes.java index ca9e9c13..de9f58c8 100644 --- a/pgpainless-core/src/main/java/org/pgpainless/key/util/RevocationAttributes.java +++ b/pgpainless-core/src/main/java/org/pgpainless/key/util/RevocationAttributes.java @@ -15,6 +15,9 @@ */ package org.pgpainless.key.util; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + public final class RevocationAttributes { public enum Reason { @@ -25,6 +28,26 @@ public final class RevocationAttributes { USER_ID_NO_LONGER_VALID((byte) 32), ; + private static final Map MAP = new ConcurrentHashMap<>(); + static { + for (Reason r : Reason.values()) { + MAP.put(r.reasonCode, r); + } + } + + public static Reason fromCode(byte code) { + Reason reason = MAP.get(code); + if (reason == null) { + throw new IllegalArgumentException("Invalid revocation reason: " + code); + } + return reason; + } + + public static boolean isHardRevocation(byte code) { + Reason reason = MAP.get(code); + return reason != KEY_SUPERSEDED && reason != KEY_RETIRED && reason != USER_ID_NO_LONGER_VALID; + } + private final byte reasonCode; Reason(byte reasonCode) { @@ -35,7 +58,6 @@ public final class RevocationAttributes { return reasonCode; } - @Override public String toString() { return code() + " - " + name(); diff --git a/pgpainless-core/src/main/java/org/pgpainless/policy/Policy.java b/pgpainless-core/src/main/java/org/pgpainless/policy/Policy.java index e2147b2b..69a45981 100644 --- a/pgpainless-core/src/main/java/org/pgpainless/policy/Policy.java +++ b/pgpainless-core/src/main/java/org/pgpainless/policy/Policy.java @@ -17,10 +17,12 @@ package org.pgpainless.policy; import java.util.Arrays; import java.util.Collections; +import java.util.Date; import java.util.List; import org.pgpainless.algorithm.HashAlgorithm; import org.pgpainless.algorithm.SymmetricKeyAlgorithm; +import org.pgpainless.util.NotationRegistry; public final class Policy { @@ -32,6 +34,8 @@ public final class Policy { HashAlgorithmPolicy.defaultRevocationSignatureHashAlgorithmPolicy(); private SymmetricKeyAlgorithmPolicy symmetricKeyAlgorithmPolicy = SymmetricKeyAlgorithmPolicy.defaultSymmetricKeyAlgorithmPolicy(); + private ValidationDateProvider signatureValidationDateProvider = getDefaultSignatureValidationDateProvider(); + private final NotationRegistry notationRegistry = new NotationRegistry(); private Policy() { } @@ -158,4 +162,36 @@ public final class Policy { )); } } + + public Date getSignatureValidationDate() { + return getSignatureValidationDateProvider().getValidationDate(); + } + + public ValidationDateProvider getDefaultSignatureValidationDateProvider() { + return new ValidationDateProvider() { + @Override + public Date getValidationDate() { + return new Date(); // now + } + }; + } + + public ValidationDateProvider getSignatureValidationDateProvider() { + return signatureValidationDateProvider; + } + + public void setValidationDateProvider(ValidationDateProvider validationDateProvider) { + if (validationDateProvider == null) { + throw new NullPointerException("ValidationDateProvider MUST NOT be null."); + } + this.signatureValidationDateProvider = validationDateProvider; + } + + public interface ValidationDateProvider { + Date getValidationDate(); + } + + public NotationRegistry getNotationRegistry() { + return notationRegistry; + } } diff --git a/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/DetachedSignature.java b/pgpainless-core/src/main/java/org/pgpainless/signature/DetachedSignature.java similarity index 61% rename from pgpainless-core/src/main/java/org/pgpainless/decryption_verification/DetachedSignature.java rename to pgpainless-core/src/main/java/org/pgpainless/signature/DetachedSignature.java index 28db7e04..81118aed 100644 --- a/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/DetachedSignature.java +++ b/pgpainless-core/src/main/java/org/pgpainless/signature/DetachedSignature.java @@ -13,19 +13,23 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.pgpainless.decryption_verification; +package org.pgpainless.signature; +import org.bouncycastle.openpgp.PGPKeyRing; import org.bouncycastle.openpgp.PGPSignature; import org.pgpainless.key.OpenPgpV4Fingerprint; +import org.pgpainless.key.SubkeyIdentifier; public class DetachedSignature { private final PGPSignature signature; - private final OpenPgpV4Fingerprint fingerprint; + private final PGPKeyRing signingKeyRing; + private final SubkeyIdentifier signingKeyIdentifier; private boolean verified; - public DetachedSignature(PGPSignature signature, OpenPgpV4Fingerprint fingerprint) { + public DetachedSignature(PGPSignature signature, PGPKeyRing signingKeyRing, SubkeyIdentifier signingKeyIdentifier) { this.signature = signature; - this.fingerprint = fingerprint; + this.signingKeyRing = signingKeyRing; + this.signingKeyIdentifier = signingKeyIdentifier; } public void setVerified(boolean verified) { @@ -40,7 +44,16 @@ public class DetachedSignature { return signature; } + public SubkeyIdentifier getSigningKeyIdentifier() { + return signingKeyIdentifier; + } + + public PGPKeyRing getSigningKeyRing() { + return signingKeyRing; + } + + @Deprecated public OpenPgpV4Fingerprint getFingerprint() { - return fingerprint; + return signingKeyIdentifier.getSubkeyFingerprint(); } } diff --git a/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/OnePassSignature.java b/pgpainless-core/src/main/java/org/pgpainless/signature/OnePassSignature.java similarity index 79% rename from pgpainless-core/src/main/java/org/pgpainless/decryption_verification/OnePassSignature.java rename to pgpainless-core/src/main/java/org/pgpainless/signature/OnePassSignature.java index 65736069..d3ef07f2 100644 --- a/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/OnePassSignature.java +++ b/pgpainless-core/src/main/java/org/pgpainless/signature/OnePassSignature.java @@ -13,22 +13,23 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.pgpainless.decryption_verification; +package org.pgpainless.signature; import org.bouncycastle.openpgp.PGPException; import org.bouncycastle.openpgp.PGPOnePassSignature; +import org.bouncycastle.openpgp.PGPPublicKeyRing; import org.bouncycastle.openpgp.PGPSignature; import org.pgpainless.key.OpenPgpV4Fingerprint; public class OnePassSignature { private final PGPOnePassSignature onePassSignature; - private final OpenPgpV4Fingerprint fingerprint; + private final PGPPublicKeyRing verificationKeys; private PGPSignature signature; private boolean verified; - public OnePassSignature(PGPOnePassSignature onePassSignature, OpenPgpV4Fingerprint fingerprint) { + public OnePassSignature(PGPOnePassSignature onePassSignature, PGPPublicKeyRing verificationKeys) { this.onePassSignature = onePassSignature; - this.fingerprint = fingerprint; + this.verificationKeys = verificationKeys; } public boolean isVerified() { @@ -40,7 +41,7 @@ public class OnePassSignature { } public OpenPgpV4Fingerprint getFingerprint() { - return fingerprint; + return new OpenPgpV4Fingerprint(verificationKeys.getPublicKey(onePassSignature.getKeyID())); } public boolean verify(PGPSignature signature) throws PGPException { @@ -54,4 +55,8 @@ public class OnePassSignature { public PGPSignature getSignature() { return signature; } + + public PGPPublicKeyRing getVerificationKeys() { + return verificationKeys; + } } diff --git a/pgpainless-core/src/main/java/org/pgpainless/signature/SelectSignatureFromKey.java b/pgpainless-core/src/main/java/org/pgpainless/signature/SelectSignatureFromKey.java new file mode 100644 index 00000000..4d90b8cf --- /dev/null +++ b/pgpainless-core/src/main/java/org/pgpainless/signature/SelectSignatureFromKey.java @@ -0,0 +1,392 @@ +/* + * Copyright 2021 Paul Schaub. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.pgpainless.signature; + +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.bouncycastle.openpgp.PGPException; +import org.bouncycastle.openpgp.PGPKeyRing; +import org.bouncycastle.openpgp.PGPPublicKey; +import org.bouncycastle.openpgp.PGPSignature; +import org.bouncycastle.openpgp.PGPSignatureList; +import org.pgpainless.algorithm.KeyFlag; +import org.pgpainless.algorithm.SignatureType; +import org.pgpainless.implementation.ImplementationFactory; +import org.pgpainless.signature.subpackets.SignatureSubpacketsUtil; + +public abstract class SelectSignatureFromKey { + + private static final Logger LOGGER = Logger.getLogger(SelectSignatureFromKey.class.getName()); + + public static SelectSignatureFromKey isValidAt(Date validationDate) { + return new SelectSignatureFromKey() { + @Override + public boolean accept(PGPSignature signature, PGPPublicKey key, PGPKeyRing keyRing) { + Date expirationDate = SignatureUtils.getSignatureExpirationDate(signature); + return !signature.getCreationTime().after(validationDate) && (expirationDate == null || expirationDate.after(validationDate)); + } + }; + } + + public abstract boolean accept(PGPSignature signature, PGPPublicKey key, PGPKeyRing keyRing); + + public List select(List signatures, PGPPublicKey key, PGPKeyRing keyRing) { + List selected = new ArrayList<>(); + for (PGPSignature signature : signatures) { + if (accept(signature, key, keyRing)) { + selected.add(signature); + } + } + return selected; + } + + public static SelectSignatureFromKey isValidSubkeyBindingSignature(PGPPublicKey primaryKey, PGPPublicKey subkey) { + return new SelectSignatureFromKey() { + @Override + public boolean accept(PGPSignature signature, PGPPublicKey key, PGPKeyRing keyRing) { + + if (!isOfType(SignatureType.SUBKEY_BINDING).accept(signature, key, keyRing)) { + return false; + } + + if (signature.getKeyID() != primaryKey.getKeyID()) { + return false; + } + + boolean subkeyBindingSigValid; + try { + signature.init(ImplementationFactory.getInstance().getPGPContentVerifierBuilderProvider(), primaryKey); + subkeyBindingSigValid = signature.verifyCertification(primaryKey, subkey); + } catch (PGPException e) { + LOGGER.log(Level.INFO, "Verification of subkey binding signature failed.", e); + return false; + } + + if (!subkeyBindingSigValid) { + return false; + } + + List flags = KeyFlag.fromBitmask(signature.getHashedSubPackets().getKeyFlags()); + boolean isSigningKey = flags.contains(KeyFlag.SIGN_DATA) || flags.contains(KeyFlag.CERTIFY_OTHER); + + if (isSigningKey && !hasValidPrimaryKeyBindingSignatureSubpacket(subkey, primaryKey) + .accept(signature, subkey, keyRing)) { + LOGGER.log(Level.INFO, "Subkey binding signature on signing key does not carry valid primary key binding signature."); + return false; + } + return true; + } + }; + } + + public static SelectSignatureFromKey isValidPrimaryKeyBindingSignature(PGPPublicKey subkey, PGPPublicKey primaryKey) { + return new SelectSignatureFromKey() { + @Override + public boolean accept(PGPSignature signature, PGPPublicKey key, PGPKeyRing keyRing) { + + if (!isVersion4Signature().accept(signature, key, keyRing)) { + return false; + } + + if (!isOfType(SignatureType.PRIMARYKEY_BINDING).accept(signature, key, keyRing)) { + return false; + } + + if (signature.getKeyID() != subkey.getKeyID()) { + return false; + } + + if (!isSigNotExpired().accept(signature, primaryKey, keyRing)) { + LOGGER.log(Level.INFO, "Primary key binding signature expired."); + return false; + } + + try { + signature.init(ImplementationFactory.getInstance().getPGPContentVerifierBuilderProvider(), subkey); + return signature.verifyCertification(primaryKey, subkey); + } catch (PGPException e) { + return false; + } + } + }; + } + + public static SelectSignatureFromKey hasValidPrimaryKeyBindingSignatureSubpacket(PGPPublicKey subkey, PGPPublicKey primaryKey) { + return new SelectSignatureFromKey() { + @Override + public boolean accept(PGPSignature signature, PGPPublicKey key, PGPKeyRing keyRing) { + try { + PGPSignatureList embeddedSignatures = SignatureSubpacketsUtil.getEmbeddedSignature(signature); + if (embeddedSignatures != null) { + for (PGPSignature embeddedSignature : embeddedSignatures) { + if (isValidPrimaryKeyBindingSignature(subkey, primaryKey).accept(embeddedSignature, subkey, keyRing)) { + return true; + } + } + } + } catch (PGPException e) { + LOGGER.log(Level.WARNING, "Cannot parse embedded signatures:", e); + } + return false; + } + }; + } + + public static SelectSignatureFromKey isValidDirectKeySignature(PGPPublicKey signer, PGPPublicKey signee) { + return new SelectSignatureFromKey() { + @Override + public boolean accept(PGPSignature signature, PGPPublicKey key, PGPKeyRing keyRing) { + if (!isVersion4Signature().accept(signature, key, keyRing)) { + return false; + } + + if (!isOfType(SignatureType.DIRECT_KEY).accept(signature, key, keyRing)) { + return false; + } + + try { + signature.init(ImplementationFactory.getInstance().getPGPContentVerifierBuilderProvider(), signer); + return signature.verifyCertification(signee); + } catch (PGPException e) { + return false; + } + } + }; + } + + public static SelectSignatureFromKey isValidKeyRevocationSignature(PGPPublicKey key) { + return and( + isVersion4Signature(), + isOfType(SignatureType.KEY_REVOCATION), + isCreatedBy(key), + isWellFormed(), + doesNotPredateKeyCreationDate(key), + isVerifyingSignatureOnKey(key, key) + ); + } + + public static SelectSignatureFromKey isValidSubkeyRevocationSignature() { + return new SelectSignatureFromKey() { + @Override + public boolean accept(PGPSignature signature, PGPPublicKey key, PGPKeyRing keyRing) { + return isValidSubkeyRevocationSignature(key, keyRing.getPublicKey()) + .accept(signature, key, keyRing); + } + }; + } + + public static SelectSignatureFromKey isValidSubkeyRevocationSignature(PGPPublicKey subkey, PGPPublicKey primaryKey) { + return SelectSignatureFromKey.and( + isVersion4Signature(), + isOfType(SignatureType.SUBKEY_REVOCATION), + isCreatedBy(primaryKey), + isVerifyingSignatureOnKeys(primaryKey, subkey, primaryKey) + ); + } + + public static SelectSignatureFromKey isValidCertificationRevocationSignature(PGPPublicKey revoker, String userId) { + return and( + isVersion4Signature(), + isCreatedBy(revoker), + isOfType(SignatureType.CERTIFICATION_REVOCATION), + isValidSignatureOnUserId(userId, revoker) + ); + } + + public static SelectSignatureFromKey isValidSignatureOnUserId(String userId, PGPPublicKey signingKey) { + return new SelectSignatureFromKey() { + @Override + public boolean accept(PGPSignature signature, PGPPublicKey key, PGPKeyRing keyRing) { + try { + signature.init(ImplementationFactory.getInstance().getPGPContentVerifierBuilderProvider(), signingKey); + return signature.verifyCertification(userId, key); + } catch (PGPException e) { + LOGGER.log(Level.INFO, "Verification of signature on userID " + userId + " failed.", e); + return false; + } + } + }; + } + + public static SelectSignatureFromKey isVerifyingSignatureOnKey(PGPPublicKey target, PGPPublicKey signer) { + return new SelectSignatureFromKey() { + @Override + public boolean accept(PGPSignature signature, PGPPublicKey key, PGPKeyRing keyRing) { + try { + signature.init(ImplementationFactory.getInstance().getPGPContentVerifierBuilderProvider(), signer); + boolean valid = signature.verifyCertification(target); + return valid; + } catch (PGPException e) { + LOGGER.log(Level.INFO, "Signature verification failed.", e); + return false; + } + } + }; + } + + public static SelectSignatureFromKey isVerifyingSignatureOnKeys(PGPPublicKey primaryKey, PGPPublicKey subkey, PGPPublicKey signingKey) { + if (signingKey.getKeyID() != primaryKey.getKeyID() && signingKey.getKeyID() != subkey.getKeyID()) { + throw new IllegalArgumentException("Signing key MUST be either the primary or subkey."); + } + return new SelectSignatureFromKey() { + @Override + public boolean accept(PGPSignature signature, PGPPublicKey key, PGPKeyRing keyRing) { + try { + signature.init(ImplementationFactory.getInstance().getPGPContentVerifierBuilderProvider(), signingKey); + return signature.verifyCertification(primaryKey, subkey); + } catch (PGPException e) { + LOGGER.log(Level.INFO, "Verification of " + SignatureType.valueOf(signature.getSignatureType()) + " signature failed.", e); + return false; + } + } + }; + } + + public static SelectSignatureFromKey isCertification() { + return new SelectSignatureFromKey() { + @Override + public boolean accept(PGPSignature signature, PGPPublicKey key, PGPKeyRing keyRing) { + return signature.isCertification(); + } + }; + } + + public static SelectSignatureFromKey isWellFormed() { + return and( + hasCreationTimeSubpacket(), + doesNotPredateKeyCreationDate() + ); + } + + public static SelectSignatureFromKey isVersion4Signature() { + return isVersion(4); + } + + public static SelectSignatureFromKey hasCreationTimeSubpacket() { + return new SelectSignatureFromKey() { + @Override + public boolean accept(PGPSignature signature, PGPPublicKey key, PGPKeyRing keyRing) { + return signature.getHashedSubPackets().getSignatureCreationTime() != null; + } + }; + } + + public static SelectSignatureFromKey isCreatedBy(PGPPublicKey publicKey) { + return isCreatedBy(publicKey.getKeyID()); + } + + public static SelectSignatureFromKey isCreatedBy(long keyId) { + return new SelectSignatureFromKey() { + @Override + public boolean accept(PGPSignature signature, PGPPublicKey key, PGPKeyRing keyRing) { + return signature.getKeyID() == keyId; + } + }; + } + + public static SelectSignatureFromKey isSigNotExpired() { + return isSigNotExpired(new Date()); + } + + public static SelectSignatureFromKey isSigNotExpired(Date comparisonDate) { + return new SelectSignatureFromKey() { + @Override + public boolean accept(PGPSignature signature, PGPPublicKey key, PGPKeyRing keyRing) { + return !SignatureUtils.isSignatureExpired(signature, comparisonDate); + } + }; + } + + public static SelectSignatureFromKey doesNotPredateKeyCreationDate() { + return new SelectSignatureFromKey() { + @Override + public boolean accept(PGPSignature signature, PGPPublicKey key, PGPKeyRing keyRing) { + PGPPublicKey creator = keyRing.getPublicKey(signature.getKeyID()); + if (creator == null) { + return false; + } + return doesNotPredateKeyCreationDate(creator).accept(signature, key, keyRing); + } + }; + } + + public static SelectSignatureFromKey doesNotPredateKeyCreationDate(PGPPublicKey creator) { + return new SelectSignatureFromKey() { + @Override + public boolean accept(PGPSignature signature, PGPPublicKey key, PGPKeyRing keyRing) { + return !signature.getCreationTime().before(creator.getCreationTime()); + } + }; + } + + public static SelectSignatureFromKey isVersion(int version) { + return new SelectSignatureFromKey() { + @Override + public boolean accept(PGPSignature signature, PGPPublicKey key, PGPKeyRing keyRing) { + return signature.getVersion() == version; + } + }; + } + + public static SelectSignatureFromKey isOfType(SignatureType signatureType) { + return new SelectSignatureFromKey() { + @Override + public boolean accept(PGPSignature signature, PGPPublicKey key, PGPKeyRing keyRing) { + return signature.getSignatureType() == signatureType.getCode(); + } + }; + } + + public static SelectSignatureFromKey and(SelectSignatureFromKey... selectors) { + return new SelectSignatureFromKey() { + @Override + public boolean accept(PGPSignature signature, PGPPublicKey key, PGPKeyRing keyRing) { + for (SelectSignatureFromKey selector : selectors) { + if (!selector.accept(signature, key, keyRing)) { + return false; + } + } + return true; + } + }; + } + + public static SelectSignatureFromKey or(SelectSignatureFromKey... selectors) { + return new SelectSignatureFromKey() { + @Override + public boolean accept(PGPSignature signature, PGPPublicKey key, PGPKeyRing keyRing) { + boolean accept = false; + for (SelectSignatureFromKey selector : selectors) { + accept |= selector.accept(signature, key, keyRing); + } + return accept; + } + }; + } + + public static SelectSignatureFromKey not(SelectSignatureFromKey selector) { + return new SelectSignatureFromKey() { + @Override + public boolean accept(PGPSignature signature, PGPPublicKey key, PGPKeyRing keyRing) { + return !selector.accept(signature, key, keyRing); + } + }; + } +} diff --git a/pgpainless-core/src/main/java/org/pgpainless/signature/SignatureChainValidator.java b/pgpainless-core/src/main/java/org/pgpainless/signature/SignatureChainValidator.java new file mode 100644 index 00000000..cbe36f8f --- /dev/null +++ b/pgpainless-core/src/main/java/org/pgpainless/signature/SignatureChainValidator.java @@ -0,0 +1,182 @@ +/* + * Copyright 2021 Paul Schaub. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.pgpainless.signature; + +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Date; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.bouncycastle.openpgp.PGPPublicKey; +import org.bouncycastle.openpgp.PGPPublicKeyRing; +import org.bouncycastle.openpgp.PGPSignature; +import org.pgpainless.algorithm.KeyFlag; +import org.pgpainless.algorithm.SignatureType; +import org.pgpainless.policy.Policy; +import org.pgpainless.signature.subpackets.SignatureSubpacketsUtil; + +public class SignatureChainValidator { + + private static final Logger LOGGER = Logger.getLogger(SignatureChainValidator.class.getName()); + + public static boolean validateSigningKey(PGPSignature signature, PGPPublicKeyRing signingKeyRing, Policy policy, Date validationDate) throws SignatureValidationException { + + Map rejections = new ConcurrentHashMap<>(); + + PGPPublicKey signingSubkey = signingKeyRing.getPublicKey(signature.getKeyID()); + if (signingSubkey == null) { + throw new SignatureValidationException("Provided key ring does not contain a subkey with id " + Long.toHexString(signature.getKeyID())); + } + + PGPPublicKey primaryKey = signingKeyRing.getPublicKey(); + + List directKeySignatures = new ArrayList<>(); + Iterator primaryKeyRevocationIterator = primaryKey.getSignaturesOfType(SignatureType.KEY_REVOCATION.getCode()); + while (primaryKeyRevocationIterator.hasNext()) { + PGPSignature revocation = primaryKeyRevocationIterator.next(); + try { + if (SignatureValidator.verifyKeyRevocationSignature(revocation, primaryKey, policy, signature.getCreationTime())) { + directKeySignatures.add(revocation); + } + } catch (SignatureValidationException e) { + rejections.put(revocation, e); + LOGGER.log(Level.FINE, "Rejecting key revocation signature.", e); + } + } + + Iterator keySignatures = primaryKey.getSignaturesOfType(SignatureType.DIRECT_KEY.getCode()); + while (keySignatures.hasNext()) { + PGPSignature keySignature = keySignatures.next(); + try { + if (SignatureValidator.verifyDirectKeySignature(keySignature, primaryKey, policy, signature.getCreationTime())) { + directKeySignatures.add(keySignature); + } + } catch (SignatureValidationException e) { + rejections.put(keySignature, e); + LOGGER.log(Level.FINE, "Rejecting key signature.", e); + } + } + + Collections.sort(directKeySignatures, new SignatureValidityComparator(SignatureCreationDateComparator.Order.new_to_old)); + if (directKeySignatures.isEmpty()) { + + } else { + if (directKeySignatures.get(0).getSignatureType() == SignatureType.KEY_REVOCATION.getCode()) { + throw new SignatureValidationException("Primary key has been revoked."); + } + } + + Iterator userIds = primaryKey.getUserIDs(); + Map> userIdSignatures = new ConcurrentHashMap<>(); + while (userIds.hasNext()) { + List signaturesOnUserId = new ArrayList<>(); + String userId = userIds.next(); + Iterator userIdSigs = primaryKey.getSignaturesForID(userId); + while (userIdSigs.hasNext()) { + PGPSignature userIdSig = userIdSigs.next(); + try { + if (SignatureValidator.verifySignatureOverUserId(userId, userIdSig, primaryKey, policy, signature.getCreationTime())) { + signaturesOnUserId.add(userIdSig); + } + } catch (SignatureValidationException e) { + rejections.put(userIdSig, e); + LOGGER.log(Level.INFO, "Rejecting user-id signature.", e); + } + } + Collections.sort(signaturesOnUserId, new SignatureValidityComparator(SignatureCreationDateComparator.Order.new_to_old)); + userIdSignatures.put(userId, signaturesOnUserId); + } + + boolean userIdValid = false; + for (String userId : userIdSignatures.keySet()) { + if (!userIdSignatures.get(userId).isEmpty()) { + PGPSignature current = userIdSignatures.get(userId).get(0); + if (current.getSignatureType() == SignatureType.CERTIFICATION_REVOCATION.getCode()) { + LOGGER.log(Level.FINE, "User-ID '" + userId + "' is revoked."); + } else { + userIdValid = true; + } + } + } + + if (!userIdValid) { + throw new SignatureValidationException("Key is not valid at this point.", rejections); + } + + if (signingSubkey != primaryKey) { + List subkeySigs = new ArrayList<>(); + Iterator bindingRevocations = signingSubkey.getSignaturesOfType(SignatureType.SUBKEY_REVOCATION.getCode()); + while (bindingRevocations.hasNext()) { + PGPSignature revocation = bindingRevocations.next(); + try { + if (SignatureValidator.verifySubkeyBindingRevocation(revocation, primaryKey, signingSubkey, policy, signature.getCreationTime())) { + subkeySigs.add(revocation); + } + } catch (SignatureValidationException e) { + rejections.put(revocation, e); + LOGGER.log(Level.FINE, "Rejecting subkey revocation signature.", e); + } + } + + Iterator bindingSigs = signingSubkey.getSignaturesOfType(SignatureType.SUBKEY_BINDING.getCode()); + while (bindingSigs.hasNext()) { + PGPSignature bindingSig = bindingSigs.next(); + try { + if (SignatureValidator.verifySubkeyBindingSignature(bindingSig, primaryKey, signingSubkey, policy, signature.getCreationTime())) { + subkeySigs.add(bindingSig); + } + } catch (SignatureValidationException e) { + rejections.put(bindingSig, e); + LOGGER.log(Level.FINE, "Rejecting subkey binding signature.", e); + } + } + + Collections.sort(subkeySigs, new SignatureValidityComparator(SignatureCreationDateComparator.Order.new_to_old)); + if (subkeySigs.isEmpty()) { + throw new SignatureValidationException("Subkey is not bound.", rejections); + } + + PGPSignature currentSig = subkeySigs.get(0); + if (currentSig.getSignatureType() == SignatureType.SUBKEY_REVOCATION.getCode()) { + throw new SignatureValidationException("Subkey is revoked."); + } + + if (!KeyFlag.hasKeyFlag(SignatureSubpacketsUtil.getKeyFlags(currentSig).getFlags(), KeyFlag.SIGN_DATA)) { + throw new SignatureValidationException("Signature was made by key which is not capable of signing."); + } + } + return true; + } + + public static boolean validateSignatureChain(PGPSignature signature, InputStream signedData, PGPPublicKeyRing signingKeyRing, Policy policy, Date validationDate) + throws SignatureValidationException { + validateSigningKey(signature, signingKeyRing, policy, validationDate); + return SignatureValidator.verifyUninitializedSignature(signature, signedData, signingKeyRing.getPublicKey(signature.getKeyID()), policy, validationDate); + } + + public static boolean validateSignature(PGPSignature signature, PGPPublicKeyRing verificationKeys, Policy policy) throws SignatureValidationException { + validateSigningKey(signature, verificationKeys, policy, signature.getCreationTime()); + PGPPublicKey signingKey = verificationKeys.getPublicKey(signature.getKeyID()); + SignatureValidator.verifyInitializedSignature(signature, signingKey, policy, signature.getCreationTime()); + return true; + } +} diff --git a/pgpainless-core/src/main/java/org/pgpainless/signature/SignatureCreationDateComparator.java b/pgpainless-core/src/main/java/org/pgpainless/signature/SignatureCreationDateComparator.java new file mode 100644 index 00000000..7ac3f505 --- /dev/null +++ b/pgpainless-core/src/main/java/org/pgpainless/signature/SignatureCreationDateComparator.java @@ -0,0 +1,47 @@ +/* + * Copyright 2021 Paul Schaub. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.pgpainless.signature; + +import java.util.Comparator; + +import org.bouncycastle.openpgp.PGPSignature; + +public class SignatureCreationDateComparator implements Comparator { + + public static final Order DEFAULT_ORDER = Order.old_to_new; + + public enum Order { + old_to_new, + new_to_old + } + + private final Order order; + + public SignatureCreationDateComparator() { + this(DEFAULT_ORDER); + } + + public SignatureCreationDateComparator(Order order) { + this.order = order; + } + + @Override + public int compare(PGPSignature one, PGPSignature two) { + return order == Order.old_to_new + ? one.getCreationTime().compareTo(two.getCreationTime()) + : two.getCreationTime().compareTo(one.getCreationTime()); + } +} diff --git a/pgpainless-core/src/main/java/org/pgpainless/signature/SignaturePicker.java b/pgpainless-core/src/main/java/org/pgpainless/signature/SignaturePicker.java new file mode 100644 index 00000000..eb2c178d --- /dev/null +++ b/pgpainless-core/src/main/java/org/pgpainless/signature/SignaturePicker.java @@ -0,0 +1,314 @@ +/* + * Copyright 2021 Paul Schaub. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.pgpainless.signature; + +import java.util.Collections; +import java.util.Date; +import java.util.Iterator; +import java.util.List; + +import org.bouncycastle.bcpg.sig.RevocationReason; +import org.bouncycastle.bcpg.sig.SignatureCreationTime; +import org.bouncycastle.openpgp.PGPKeyRing; +import org.bouncycastle.openpgp.PGPPublicKey; +import org.bouncycastle.openpgp.PGPSignature; +import org.pgpainless.algorithm.SignatureType; +import org.pgpainless.key.util.RevocationAttributes; +import org.pgpainless.signature.subpackets.SignatureSubpacketsUtil; +import org.pgpainless.util.CollectionUtils; + +/** + * Pick signatures from keys. + * + * The format of a V4 OpenPGP key is: + * + * Primary-Key + * [Revocation Self Signature] + * [Direct Key Signature...] + * User ID [Signature ...] + * [User ID [Signature ...] ...] + * [User Attribute [Signature ...] ...] + * [[Subkey [Binding-Signature-Revocation] Primary-Key-Binding-Signature] ...] + */ +public class SignaturePicker { + + /** + * Pick the most current (at the time of evaluation) key revocation signature. + * If there is a hard revocation signature, it is picked, regardless of expiration or creation time. + * + * @param keyRing key ring + * @return most recent, valid key revocation signature + */ + public static PGPSignature pickCurrentRevocationSelfSignature(PGPKeyRing keyRing, Date validationDate) { + PGPPublicKey primaryKey = keyRing.getPublicKey(); + + List signatures = getSortedSignaturesOfType(primaryKey, SignatureType.KEY_REVOCATION); + PGPSignature mostCurrentValidSig = null; + + for (PGPSignature signature : signatures) { + if (!SelectSignatureFromKey.isWellFormed().accept(signature, primaryKey, keyRing)) { + // Signature is not well-formed. Reject + continue; + } + + if (!SelectSignatureFromKey.isCreatedBy(keyRing.getPublicKey()).accept(signature, primaryKey, keyRing)) { + // Revocation signature was not created by primary key + continue; + } + + RevocationReason reason = SignatureSubpacketsUtil.getRevocationReason(signature); + if (reason != null && !RevocationAttributes.Reason.isHardRevocation(reason.getRevocationReason())) { + // reason code states soft revocation + if (!SelectSignatureFromKey.isValidAt(validationDate).accept(signature, primaryKey, keyRing)) { + // Soft revocation is either expired or not yet valid + continue; + } + } + + if (!SelectSignatureFromKey.isValidKeyRevocationSignature(primaryKey).accept(signature, primaryKey, keyRing)) { + // sig does not check out + continue; + } + + mostCurrentValidSig = signature; + } + + return mostCurrentValidSig; + } + + /** + * Pick the current direct key self-signature on the primary key. + * @param keyRing key ring + * @param validationDate validation date + * @return direct-key self-signature + */ + public static PGPSignature pickCurrentDirectKeySelfSignature(PGPKeyRing keyRing, Date validationDate) { + PGPPublicKey primaryKey = keyRing.getPublicKey(); + return pickCurrentDirectKeySignature(primaryKey, primaryKey, keyRing, validationDate); + } + + /** + * Pick the current direct-key signature made by the signing key on the signed key. + * + * @param signingKey key that created the signature + * @param signedKey key that carries the signature + * @param keyRing key ring + * @param validationDate validation date + * @return direct key sig + */ + public static PGPSignature pickCurrentDirectKeySignature(PGPPublicKey signingKey, PGPPublicKey signedKey, PGPKeyRing keyRing, Date validationDate) { + List directKeySignatures = getSortedSignaturesOfType(signedKey, SignatureType.DIRECT_KEY); + + PGPSignature mostRecentDirectKeySigBySigningKey = null; + for (PGPSignature signature : directKeySignatures) { + if (!SelectSignatureFromKey.isWellFormed().accept(signature, signingKey, keyRing)) { + // signature is not well formed + continue; + } + + if (!SelectSignatureFromKey.isValidAt(validationDate).accept(signature, signedKey, keyRing)) { + // Signature is either expired or not yet valid + continue; + } + + if (!SelectSignatureFromKey.isValidDirectKeySignature(signingKey, signedKey).accept(signature, signedKey, keyRing)) { + // signature does not check out. + continue; + } + mostRecentDirectKeySigBySigningKey = signature; + } + + return mostRecentDirectKeySigBySigningKey; + } + + /** + * Pick the most recent user-id revocation signature. + * + * @param keyRing key ring + * @param userId user-Id that gets revoked + * @param validationDate validation date + * @return revocation signature + */ + public static PGPSignature pickCurrentUserIdRevocationSignature(PGPKeyRing keyRing, String userId, Date validationDate) { + PGPPublicKey primaryKey = keyRing.getPublicKey(); + + Iterator certificationRevocations = primaryKey.getSignaturesOfType(SignatureType.CERTIFICATION_REVOCATION.getCode()); + List signatures = CollectionUtils.iteratorToList(certificationRevocations); + Collections.sort(signatures, new SignatureCreationDateComparator()); + + PGPSignature mostRecentUserIdRevocation = null; + for (PGPSignature signature : signatures) { + if (!SelectSignatureFromKey.isWellFormed().accept(signature, primaryKey, keyRing)) { + // Sig is not well formed. + continue; + } + + RevocationReason reason = SignatureSubpacketsUtil.getRevocationReason(signature); + if (reason != null && !RevocationAttributes.Reason.isHardRevocation(reason.getRevocationReason())) { + // reason code states soft revocation + if (!SelectSignatureFromKey.isValidAt(validationDate).accept(signature, primaryKey, keyRing)) { + // Soft revocation is either expired or not yet valid + continue; + } + } + + if (!SelectSignatureFromKey.isValidCertificationRevocationSignature(primaryKey, userId) + .accept(signature, primaryKey, keyRing)) { + // sig does not check out for userid + continue; + } + + mostRecentUserIdRevocation = signature; + } + + return mostRecentUserIdRevocation; + } + + /** + * Pick the most current certification self-signature for the given user-id. + * + * @param keyRing keyring + * @param userId userid + * @param validationDate validation date + * @return user-id certification + */ + public static PGPSignature pickCurrentUserIdCertificationSignature(PGPKeyRing keyRing, String userId, Date validationDate) { + PGPPublicKey primaryKey = keyRing.getPublicKey(); + + Iterator userIdSigIterator = primaryKey.getSignaturesForID(userId); + List signatures = CollectionUtils.iteratorToList(userIdSigIterator); + Collections.sort(signatures, new SignatureCreationDateComparator()); + + PGPSignature mostRecentUserIdCertification = null; + for (PGPSignature signature : signatures) { + if (!SelectSignatureFromKey.isWellFormed().accept(signature, primaryKey, keyRing)) { + // Sig not well formed + continue; + } + + if (!SelectSignatureFromKey.isValidAt(validationDate).accept(signature, primaryKey, keyRing)) { + // Sig is either expired or not valid yet + continue; + } + + if (!SelectSignatureFromKey.isValidSignatureOnUserId(userId, primaryKey).accept(signature, primaryKey, keyRing)) { + // Sig does not check out + continue; + } + + mostRecentUserIdCertification = signature; + } + + return mostRecentUserIdCertification; + } + + /** + * Return the current subkey binding revocation signature for the given subkey. + * + * @param keyRing keyring + * @param subkey subkey + * @param validationDate validation date + * @return subkey revocation signature + */ + public static PGPSignature pickCurrentSubkeyBindingRevocationSignature(PGPKeyRing keyRing, PGPPublicKey subkey, Date validationDate) { + PGPPublicKey primaryKey = keyRing.getPublicKey(); + if (primaryKey.getKeyID() == subkey.getKeyID()) { + throw new IllegalArgumentException("Primary key cannot have subkey binding revocations."); + } + + List subkeyRevocationSigs = getSortedSignaturesOfType(subkey, SignatureType.SUBKEY_BINDING); + PGPSignature mostRecentSubkeyRevocation = null; + + for (PGPSignature signature : subkeyRevocationSigs) { + if (!SelectSignatureFromKey.isWellFormed().accept(signature, primaryKey, keyRing)) { + // Signature is not well formed + continue; + } + + RevocationReason reason = SignatureSubpacketsUtil.getRevocationReason(signature); + if (reason != null && !RevocationAttributes.Reason.isHardRevocation(reason.getRevocationReason())) { + // reason code states soft revocation + if (!SelectSignatureFromKey.isValidAt(validationDate).accept(signature, primaryKey, keyRing)) { + // Soft revocation is either expired or not yet valid + continue; + } + } + + if (!SelectSignatureFromKey.isValidSubkeyRevocationSignature().accept(signature, subkey, keyRing)) { + // Signature does not check out + continue; + } + mostRecentSubkeyRevocation = signature; + } + + return mostRecentSubkeyRevocation; + } + + /** + * Return the (at the time of validation) most recent, valid subkey binding signature + * made by the primary key of the key ring on the subkey. + * + * @param keyRing key ring + * @param subkey subkey + * @param validationDate date of validation + * @return most recent valid subkey binding signature + */ + public static PGPSignature pickCurrentSubkeyBindingSignature(PGPKeyRing keyRing, PGPPublicKey subkey, Date validationDate) { + PGPPublicKey primaryKey = keyRing.getPublicKey(); + if (primaryKey.getKeyID() == subkey.getKeyID()) { + throw new IllegalArgumentException("Primary key cannot have subkey binding signature."); + } + + List subkeyBindingSigs = getSortedSignaturesOfType(subkey, SignatureType.SUBKEY_BINDING); + PGPSignature mostCurrentValidSig = null; + + for (PGPSignature signature : subkeyBindingSigs) { + // has hashed creation time, does not predate signing key creation date + if (!SelectSignatureFromKey.isWellFormed().accept(signature, primaryKey, keyRing)) { + // Signature is not well-formed. Reject. + continue; + } + + SignatureCreationTime creationTime = SignatureSubpacketsUtil.getSignatureCreationTime(signature); + if (creationTime.getTime().after(validationDate)) { + // signature is not yet valid + continue; + } + + if (SignatureUtils.isSignatureExpired(signature, validationDate)) { + // Signature is expired + continue; + } + + if (!SelectSignatureFromKey.isValidSubkeyBindingSignature(primaryKey, subkey) + .accept(signature, subkey, keyRing)) { + // Invalid subkey binding signature + continue; + } + + mostCurrentValidSig = signature; + } + + return mostCurrentValidSig; + } + + private static List getSortedSignaturesOfType(PGPPublicKey key, SignatureType type) { + Iterator signaturesOfType = key.getSignaturesOfType(type.getCode()); + List signatureList = CollectionUtils.iteratorToList(signaturesOfType); + Collections.sort(signatureList, new SignatureCreationDateComparator()); + return signatureList; + } +} diff --git a/pgpainless-core/src/main/java/org/pgpainless/key/util/SignatureUtils.java b/pgpainless-core/src/main/java/org/pgpainless/signature/SignatureUtils.java similarity index 76% rename from pgpainless-core/src/main/java/org/pgpainless/key/util/SignatureUtils.java rename to pgpainless-core/src/main/java/org/pgpainless/signature/SignatureUtils.java index e3463f6d..42e819aa 100644 --- a/pgpainless-core/src/main/java/org/pgpainless/key/util/SignatureUtils.java +++ b/pgpainless-core/src/main/java/org/pgpainless/signature/SignatureUtils.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.pgpainless.key.util; +package org.pgpainless.signature; import java.util.ArrayList; import java.util.Collections; @@ -22,6 +22,9 @@ import java.util.Date; import java.util.Iterator; import java.util.List; +import org.bouncycastle.bcpg.sig.KeyExpirationTime; +import org.bouncycastle.bcpg.sig.RevocationReason; +import org.bouncycastle.bcpg.sig.SignatureExpirationTime; import org.bouncycastle.openpgp.PGPException; import org.bouncycastle.openpgp.PGPKeyRing; import org.bouncycastle.openpgp.PGPPublicKey; @@ -33,6 +36,10 @@ import org.pgpainless.PGPainless; import org.pgpainless.algorithm.HashAlgorithm; import org.pgpainless.algorithm.SignatureType; import org.pgpainless.implementation.ImplementationFactory; +import org.pgpainless.key.util.KeyRingUtils; +import org.pgpainless.key.util.OpenPgpKeyAttributeUtil; +import org.pgpainless.key.util.RevocationAttributes; +import org.pgpainless.signature.subpackets.SignatureSubpacketsUtil; public class SignatureUtils { @@ -150,14 +157,36 @@ public class SignatureUtils { return signature.verifyCertification(userId, publicKey); } + public static Date getKeyExpirationDate(Date keyCreationDate, PGPSignature signature) { + KeyExpirationTime keyExpirationTime = SignatureSubpacketsUtil.getKeyExpirationTime(signature); + long expiresInSecs = keyExpirationTime == null ? 0 : keyExpirationTime.getTime(); + return datePlusSeconds(keyCreationDate, expiresInSecs); + } + + public static Date getSignatureExpirationDate(PGPSignature signature) { + Date creationDate = signature.getCreationTime(); + SignatureExpirationTime signatureExpirationTime = SignatureSubpacketsUtil.getSignatureExpirationTime(signature); + long expiresInSecs = signatureExpirationTime == null ? 0 : signatureExpirationTime.getTime(); + return datePlusSeconds(creationDate, expiresInSecs); + } + + public static Date datePlusSeconds(Date date, long seconds) { + if (seconds == 0) { + return null; + } + return new Date(date.getTime() + 1000 * seconds); + } + public static boolean isSignatureExpired(PGPSignature signature) { - long expiration = signature.getHashedSubPackets().getSignatureExpirationTime(); - if (expiration == 0) { + return isSignatureExpired(signature, new Date()); + } + + public static boolean isSignatureExpired(PGPSignature signature, Date comparisonDate) { + Date expirationDate = getSignatureExpirationDate(signature); + if (expirationDate == null) { return false; } - Date now = new Date(); - Date creation = signature.getCreationTime(); - return now.after(new Date(creation.getTime() + 1000 * expiration)); + return comparisonDate.after(expirationDate); } public static void sortByCreationTimeAscending(List signatures) { @@ -221,10 +250,38 @@ public class SignatureUtils { } public static boolean isUserIdValid(PGPPublicKey publicKey, String userId) throws PGPException { + return isUserIdValid(publicKey, userId, new Date()); + } + + public static boolean isUserIdValid(PGPPublicKey publicKey, String userId, Date validationDate) throws PGPException { PGPSignature latestSelfSig = getLatestSelfSignatureForUserId(publicKey, userId); if (latestSelfSig == null) { return false; } + if (latestSelfSig.getCreationTime().after(validationDate)) { + // Signature creation date lays in the future. + return false; + } + if (isSignatureExpired(latestSelfSig, validationDate)) { + return false; + } + return latestSelfSig.getSignatureType() != SignatureType.CERTIFICATION_REVOCATION.getCode(); } + + public static boolean isHardRevocation(PGPSignature signature) { + + SignatureType type = SignatureType.valueOf(signature.getSignatureType()); + if (type != SignatureType.KEY_REVOCATION && type != SignatureType.SUBKEY_REVOCATION && type != SignatureType.CERTIFICATION_REVOCATION) { + // Not a revocation + return false; + } + + RevocationReason reasonSubpacket = SignatureSubpacketsUtil.getRevocationReason(signature); + if (reasonSubpacket == null) { + // no reason -> hard revocation + return true; + } + return RevocationAttributes.Reason.isHardRevocation(reasonSubpacket.getRevocationReason()); + } } diff --git a/pgpainless-core/src/main/java/org/pgpainless/signature/SignatureValidationException.java b/pgpainless-core/src/main/java/org/pgpainless/signature/SignatureValidationException.java new file mode 100644 index 00000000..70d771ee --- /dev/null +++ b/pgpainless-core/src/main/java/org/pgpainless/signature/SignatureValidationException.java @@ -0,0 +1,45 @@ +/* + * Copyright 2021 Paul Schaub. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.pgpainless.signature; + +import java.util.Map; + +import org.bouncycastle.openpgp.PGPSignature; +import org.pgpainless.algorithm.SignatureType; + +public class SignatureValidationException extends Exception { + + public SignatureValidationException(String message) { + super(message); + } + + public SignatureValidationException(String message, Throwable underlying) { + super(message, underlying); + } + + public SignatureValidationException(String message, Map rejections) { + super(message + ": " + exceptionMapToString(rejections)); + } + + private static String exceptionMapToString(Map rejections) { + String out = ""; + out += rejections.size() + " Rejected signatures:\n"; + for (PGPSignature signature : rejections.keySet()) { + out += SignatureType.valueOf(signature.getSignatureType()) + " " + signature.getCreationTime() + ": " + rejections.get(signature).getMessage() + "\n"; + } + return out; + }; +} diff --git a/pgpainless-core/src/main/java/org/pgpainless/signature/SignatureValidator.java b/pgpainless-core/src/main/java/org/pgpainless/signature/SignatureValidator.java new file mode 100644 index 00000000..35b2663e --- /dev/null +++ b/pgpainless-core/src/main/java/org/pgpainless/signature/SignatureValidator.java @@ -0,0 +1,522 @@ +/* + * Copyright 2021 Paul Schaub. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.pgpainless.signature; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Arrays; +import java.util.Date; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import org.bouncycastle.bcpg.sig.NotationData; +import org.bouncycastle.bcpg.sig.SignatureCreationTime; +import org.bouncycastle.openpgp.PGPException; +import org.bouncycastle.openpgp.PGPPublicKey; +import org.bouncycastle.openpgp.PGPSignature; +import org.bouncycastle.openpgp.PGPSignatureList; +import org.bouncycastle.openpgp.PGPSignatureSubpacketVector; +import org.bouncycastle.openpgp.PGPUserAttributeSubpacketVector; +import org.pgpainless.algorithm.HashAlgorithm; +import org.pgpainless.algorithm.PublicKeyAlgorithm; +import org.pgpainless.algorithm.SignatureSubpacket; +import org.pgpainless.algorithm.SignatureType; +import org.pgpainless.implementation.ImplementationFactory; +import org.pgpainless.policy.Policy; +import org.pgpainless.signature.subpackets.SignatureSubpacketsUtil; +import org.pgpainless.util.NotationRegistry; + +public abstract class SignatureValidator { + + public abstract void verify(PGPSignature signature) throws SignatureValidationException; + + public static boolean verifyUninitializedSignature(PGPSignature signature, InputStream signedData, PGPPublicKey signingKey, Policy policy, Date validationDate) throws SignatureValidationException { + initializeSignatureAndUpdateWithSignedData(signature, signedData, signingKey); + return verifyInitializedSignature(signature, signingKey, policy, validationDate); + } + + public static void initializeSignatureAndUpdateWithSignedData(PGPSignature signature, InputStream signedData, PGPPublicKey signingKey) + throws SignatureValidationException { + try { + signature.init(ImplementationFactory.getInstance().getPGPContentVerifierBuilderProvider(), signingKey); + int read; + while ((read = signedData.read()) != -1) { + signature.update((byte) read); + } + } catch (PGPException e) { + throw new SignatureValidationException("Cannot init signature.", e); + } catch (IOException e) { + throw new SignatureValidationException("Cannot update signature.", e); + } + } + + public static boolean verifyInitializedSignature(PGPSignature signature, PGPPublicKey signingKey, Policy policy, Date validationDate) + throws SignatureValidationException { + signatureStructureIsAcceptable(signingKey, policy).verify(signature); + signatureIsEffective(validationDate).verify(signature); + + try { + if (!signature.verify()) { + throw new SignatureValidationException("Signature is not correct."); + } + return true; + } catch (PGPException e) { + throw new SignatureValidationException("Could not verify signature correctness.", e); + } + } + + public static boolean verifySignatureOverUserId(String userId, PGPSignature signature, PGPPublicKey primaryKey, Policy policy, Date validationDate) + throws SignatureValidationException { + return verifySignatureOverUserId(userId, signature, primaryKey, primaryKey, policy, validationDate); + } + + public static boolean verifySignatureOverUserId(String userId, PGPSignature signature, PGPPublicKey signingKey, PGPPublicKey keyWithUserId, Policy policy, Date validationDate) + throws SignatureValidationException { + SignatureType type = SignatureType.valueOf(signature.getSignatureType()); + switch (type) { + case GENERIC_CERTIFICATION: + case NO_CERTIFICATION: + case CASUAL_CERTIFICATION: + case POSITIVE_CERTIFICATION: + return verifyUserIdCertification(userId, signature, signingKey, keyWithUserId, policy, validationDate); + case CERTIFICATION_REVOCATION: + return verifyUserIdRevocation(userId, signature, signingKey, keyWithUserId, policy, validationDate); + default: + throw new SignatureValidationException("Signature is not a valid user-id certification/revocation signature: " + type); + } + } + + public static boolean verifyUserIdCertification(String userId, PGPSignature signature, PGPPublicKey primaryKey, Policy policy, Date validationDate) + throws SignatureValidationException { + return verifyUserIdCertification(userId, signature, primaryKey, primaryKey, policy, validationDate); + } + + public static boolean verifyUserIdCertification(String userId, PGPSignature signature, PGPPublicKey signingKey, PGPPublicKey keyWithUserId, Policy policy, Date validationDate) + throws SignatureValidationException { + + signatureIsCertification().verify(signature); + signatureStructureIsAcceptable(signingKey, policy).verify(signature); + signatureIsEffective(validationDate).verify(signature); + correctSignatureOverUserId(userId, keyWithUserId, signingKey).verify(signature); + + return true; + } + public static boolean verifyUserIdRevocation(String userId, PGPSignature signature, PGPPublicKey primaryKey, Policy policy, Date validationDate) + throws SignatureValidationException { + return verifyUserIdRevocation(userId, signature, primaryKey, primaryKey, policy, validationDate); + } + + public static boolean verifyUserIdRevocation(String userId, PGPSignature signature, PGPPublicKey signingKey, PGPPublicKey keyWithUserId, Policy policy, Date validationDate) + throws SignatureValidationException { + signatureIsOfType(SignatureType.CERTIFICATION_REVOCATION).verify(signature); + signatureStructureIsAcceptable(signingKey, policy).verify(signature); + signatureIsEffective(validationDate).verify(signature); + correctSignatureOverUserId(userId, keyWithUserId, signingKey).verify(signature); + + return true; + } + + public static boolean verifyUserAttributesCertification(PGPUserAttributeSubpacketVector userAttributes, + PGPSignature signature, PGPPublicKey primaryKey, + Policy policy, Date validationDate) + throws SignatureValidationException { + return verifyUserAttributesCertification(userAttributes, signature, primaryKey, primaryKey, policy, validationDate); + } + + public static boolean verifyUserAttributesCertification(PGPUserAttributeSubpacketVector userAttributes, + PGPSignature signature, PGPPublicKey signingKey, + PGPPublicKey keyWithUserAttributes, Policy policy, + Date validationDate) + throws SignatureValidationException { + signatureIsCertification().verify(signature); + signatureStructureIsAcceptable(signingKey, policy).verify(signature); + signatureIsEffective(validationDate).verify(signature); + correctSignatureOverUserAttributes(userAttributes, keyWithUserAttributes, signingKey).verify(signature); + + return true; + } + + public static boolean verifyUserAttributesRevocation(PGPUserAttributeSubpacketVector userAttributes, + PGPSignature signature, PGPPublicKey primaryKey, + Policy policy, Date validationDate) + throws SignatureValidationException { + return verifyUserAttributesRevocation(userAttributes, signature, primaryKey, primaryKey, policy, validationDate); + } + + public static boolean verifyUserAttributesRevocation(PGPUserAttributeSubpacketVector userAttributes, + PGPSignature signature, PGPPublicKey signingKey, + PGPPublicKey keyWithUserAttributes, Policy policy, + Date validationDate) + throws SignatureValidationException { + signatureIsOfType(SignatureType.CERTIFICATION_REVOCATION).verify(signature); + signatureStructureIsAcceptable(signingKey, policy).verify(signature); + signatureIsEffective(validationDate).verify(signature); + correctSignatureOverUserAttributes(userAttributes, keyWithUserAttributes, signingKey).verify(signature); + + return true; + } + + public static boolean verifySubkeyBindingSignature(PGPSignature signature, PGPPublicKey primaryKey, PGPPublicKey subkey, Policy policy, Date validationDate) + throws SignatureValidationException { + signatureIsOfType(SignatureType.SUBKEY_BINDING).verify(signature); + signatureStructureIsAcceptable(primaryKey, policy).verify(signature); + signatureIsEffective(validationDate).verify(signature); + hasValidPrimaryKeyBindingSignatureIfRequired(primaryKey, subkey, policy, validationDate).verify(signature); + correctSignatureOverKey(primaryKey, subkey).verify(signature); + + return true; + } + + public static boolean verifySubkeyBindingRevocation(PGPSignature signature, PGPPublicKey primaryKey, PGPPublicKey subkey, Policy policy, Date validationDate) throws SignatureValidationException { + signatureIsOfType(SignatureType.SUBKEY_REVOCATION).verify(signature); + signatureStructureIsAcceptable(primaryKey, policy).verify(signature); + signatureIsEffective(validationDate).verify(signature); + correctSignatureOverKey(primaryKey, subkey).verify(signature); + + return true; + } + + public static boolean verifyDirectKeySignature(PGPSignature signature, PGPPublicKey primaryKey, Policy policy, Date validationDate) + throws SignatureValidationException { + return verifyDirectKeySignature(signature, primaryKey, primaryKey, policy, validationDate); + } + + public static boolean verifyDirectKeySignature(PGPSignature signature, PGPPublicKey signingKey, PGPPublicKey signedKey, Policy policy, Date validationDate) + throws SignatureValidationException { + signatureIsOfType(SignatureType.DIRECT_KEY).verify(signature); + signatureStructureIsAcceptable(signingKey, policy).verify(signature); + signatureIsEffective(validationDate).verify(signature); + correctSignatureOverKey(signingKey, signedKey).verify(signature); + + return true; + } + + public static boolean verifyKeyRevocationSignature(PGPSignature signature, PGPPublicKey primaryKey, Policy policy, Date validationDate) + throws SignatureValidationException { + signatureIsOfType(SignatureType.KEY_REVOCATION).verify(signature); + signatureStructureIsAcceptable(primaryKey, policy).verify(signature); + signatureIsEffective(validationDate).verify(signature); + correctSignatureOverKey(primaryKey, primaryKey).verify(signature); + + return true; + } + + private static SignatureValidator hasValidPrimaryKeyBindingSignatureIfRequired(PGPPublicKey primaryKey, PGPPublicKey subkey, Policy policy, Date validationDate) { + return new SignatureValidator() { + @Override + public void verify(PGPSignature signature) throws SignatureValidationException { + if (!PublicKeyAlgorithm.fromId(signature.getKeyAlgorithm()).isSigningCapable()) { + // subkey is not signing capable -> No need to process embedded sigs + return; + } + + try { + PGPSignatureList embeddedSignatures = SignatureSubpacketsUtil.getEmbeddedSignature(signature); + boolean hasValidPrimaryKeyBinding = false; + Map rejectedEmbeddedSigs = new ConcurrentHashMap<>(); + for (PGPSignature embedded : embeddedSignatures) { + + if (SignatureType.valueOf(embedded.getSignatureType()) == SignatureType.PRIMARYKEY_BINDING) { + + try { + signatureStructureIsAcceptable(subkey, policy).verify(embedded); + signatureIsEffective(validationDate).verify(embedded); + correctPrimaryKeyBindingSignature(primaryKey, subkey).verify(embedded); + + hasValidPrimaryKeyBinding = true; + break; + } catch (SignatureValidationException e) { + rejectedEmbeddedSigs.put(embedded, e); + } + } + } + + if (!hasValidPrimaryKeyBinding) { + throw new SignatureValidationException("Missing primary key binding signature on signing capable subkey " + Long.toHexString(subkey.getKeyID()), rejectedEmbeddedSigs); + } + } catch (PGPException e) { + throw new SignatureValidationException("Cannot process list of embedded signatures.", e); + } + } + }; + } + + public static SignatureValidator signatureStructureIsAcceptable(PGPPublicKey signingKey, Policy policy) { + return new SignatureValidator() { + @Override + public void verify(PGPSignature signature) throws SignatureValidationException { + signatureIsNotMalformed(signingKey).verify(signature); + signatureDoesNotHaveCriticalUnknownNotations(policy.getNotationRegistry()).verify(signature); + signatureDoesNotHaveCriticalUnknownSubpackets().verify(signature); + signatureUsesAcceptableHashAlgorithm(policy).verify(signature); + } + }; + } + + private static SignatureValidator signatureUsesAcceptableHashAlgorithm(Policy policy) { + return new SignatureValidator() { + @Override + public void verify(PGPSignature signature) throws SignatureValidationException { + HashAlgorithm hashAlgorithm = HashAlgorithm.fromId(signature.getHashAlgorithm()); + + Policy.HashAlgorithmPolicy hashAlgorithmPolicy = null; + SignatureType type = SignatureType.valueOf(signature.getSignatureType()); + if (type == SignatureType.CERTIFICATION_REVOCATION || type == SignatureType.KEY_REVOCATION || type == SignatureType.SUBKEY_REVOCATION) { + hashAlgorithmPolicy = policy.getRevocationSignatureHashAlgorithmPolicy(); + } else { + hashAlgorithmPolicy = policy.getSignatureHashAlgorithmPolicy(); + } + + if (!hashAlgorithmPolicy.isAcceptable(signature.getHashAlgorithm())) { + throw new SignatureValidationException("Signature uses inacceptable hash algorithm " + hashAlgorithm); + } + } + }; + } + + public static SignatureValidator signatureDoesNotHaveCriticalUnknownNotations(NotationRegistry registry) { + return new SignatureValidator() { + @Override + public void verify(PGPSignature signature) throws SignatureValidationException { + List hashedNotations = SignatureSubpacketsUtil.getHashedNotationData(signature); + for (NotationData notation : hashedNotations) { + if (!notation.isCritical()) { + continue; + } + if (!registry.isKnownNotation(notation.getNotationName())) { + throw new SignatureValidationException("Signature contains unknown critical notation '" + notation.getNotationName() + "' in its hashed area."); + } + } + } + }; + } + + public static SignatureValidator signatureDoesNotHaveCriticalUnknownSubpackets() { + return new SignatureValidator() { + @Override + public void verify(PGPSignature signature) throws SignatureValidationException { + PGPSignatureSubpacketVector hashedSubpackets = signature.getHashedSubPackets(); + for (int criticalTag : hashedSubpackets.getCriticalTags()) { + try { + SignatureSubpacket.fromCode(criticalTag); + } catch (IllegalArgumentException e) { + throw new SignatureValidationException("Signature contains unknown critical subpacket of type " + Long.toHexString(criticalTag)); + } + } + } + }; + } + + public static SignatureValidator signatureIsEffective(Date validationDate) { + return new SignatureValidator() { + @Override + public void verify(PGPSignature signature) throws SignatureValidationException { + Date signatureCreationTime = SignatureSubpacketsUtil.getSignatureCreationTime(signature).getTime(); + // For hard revocations, skip the creation time check + if (!SignatureUtils.isHardRevocation(signature)) { + if (signatureCreationTime.after(validationDate)) { + throw new SignatureValidationException("Signature was created at " + signatureCreationTime + " and is therefore not yet valid at " + validationDate); + } + } + + Date signatureExpirationTime = SignatureSubpacketsUtil.getSignatureExpirationTimeAsDate(signature); + if (signatureExpirationTime != null && signatureExpirationTime.before(validationDate)) { + throw new SignatureValidationException("Signature is already expired (expiration: " + signatureExpirationTime + ", validation: " + validationDate + ")"); + } + } + }; + } + + public static SignatureValidator signatureIsNotMalformed(PGPPublicKey creator) { + return new SignatureValidator() { + @Override + public void verify(PGPSignature signature) throws SignatureValidationException { + signatureHasHashedCreationTime().verify(signature); + signatureDoesNotPredateSigningKey(creator).verify(signature); + signatureDoesNotPredateSigningKeyBindingDate(creator).verify(signature); + } + }; + } + + public static SignatureValidator signatureHasHashedCreationTime() { + return new SignatureValidator() { + @Override + public void verify(PGPSignature signature) throws SignatureValidationException { + SignatureCreationTime creationTime = SignatureSubpacketsUtil.getSignatureCreationTime(signature); + if (creationTime == null) { + throw new SignatureValidationException("Malformed signature. Signature has no signature creation time subpacket in its hashed area."); + } + } + }; + } + + public static SignatureValidator signatureDoesNotPredateSigningKey(PGPPublicKey key) { + return new SignatureValidator() { + @Override + public void verify(PGPSignature signature) throws SignatureValidationException { + // TODO: Uncommenting the code below would mean that fake issuers would become a problem for sig verification + /* + long keyId = signature.getKeyID(); + if (keyId == 0) { + OpenPgpV4Fingerprint fingerprint = SignatureSubpacketsUtil.getIssuerFingerprintAsOpenPgpV4Fingerprint(signature); + if (fingerprint == null) { + throw new SignatureValidationException("Signature does not contain an issuer-id, neither an issuer-fingerprint subpacket."); + } + keyId = fingerprint.getKeyId(); + } + if (keyId != key.getKeyID()) { + throw new IllegalArgumentException("Signature was not created using key " + Long.toHexString(key.getKeyID())); + } + */ + + Date keyCreationTime = key.getCreationTime(); + Date signatureCreationTime = signature.getCreationTime(); + + if (keyCreationTime.after(signatureCreationTime)) { + throw new SignatureValidationException("Signature predates its signing key (key creation: " + keyCreationTime + ", signature creation: " + signatureCreationTime + ")"); + } + } + }; + } + + public static SignatureValidator signatureDoesNotPredateSigningKeyBindingDate(PGPPublicKey signingKey) { + return new SignatureValidator() { + @Override + public void verify(PGPSignature signature) throws SignatureValidationException { + if (signingKey.isMasterKey()) { + return; + } + boolean predatesBindingSig = true; + Iterator bindingSignatures = signingKey.getSignaturesOfType(SignatureType.SUBKEY_BINDING.getCode()); + if (!bindingSignatures.hasNext()) { + throw new SignatureValidationException("Signing subkey does not have a subkey binding signature."); + } + while (bindingSignatures.hasNext()) { + PGPSignature bindingSig = bindingSignatures.next(); + if (!bindingSig.getCreationTime().after(signature.getCreationTime())) { + predatesBindingSig = false; + } + } + if (predatesBindingSig) { + throw new SignatureValidationException("Signature was created before the signing key was bound to the key ring."); + } + } + }; + } + + public static SignatureValidator correctPrimaryKeyBindingSignature(PGPPublicKey primaryKey, PGPPublicKey subkey) { + return new SignatureValidator() { + @Override + public void verify(PGPSignature signature) throws SignatureValidationException { + try { + signature.init(ImplementationFactory.getInstance().getPGPContentVerifierBuilderProvider(), subkey); + boolean valid = signature.verifyCertification(primaryKey, subkey); + if (!valid) { + throw new SignatureValidationException("Primary Key Binding Signature is not correct."); + } + } catch (PGPException e) { + throw new SignatureValidationException("Cannot verify primary key binding signature correctness", e); + } + } + }; + } + + public static SignatureValidator correctSignatureOverKey(PGPPublicKey signer, PGPPublicKey signee) { + return new SignatureValidator() { + @Override + public void verify(PGPSignature signature) throws SignatureValidationException { + try { + signature.init(ImplementationFactory.getInstance().getPGPContentVerifierBuilderProvider(), signer); + boolean valid = false; + if (signer.getKeyID() != signee.getKeyID()) { + valid = signature.verifyCertification(signer, signee); + } else { + valid = signature.verifyCertification(signee); + } + if (!valid) { + throw new SignatureValidationException("Signature is not correct."); + } + } catch (PGPException e) { + throw new SignatureValidationException("Cannot verify direct-key signature correctness", e); + } + } + }; + } + + public static SignatureValidator signatureIsCertification() { + return signatureIsOfType( + SignatureType.POSITIVE_CERTIFICATION, + SignatureType.CASUAL_CERTIFICATION, + SignatureType.GENERIC_CERTIFICATION, + SignatureType.NO_CERTIFICATION); + } + + public static SignatureValidator signatureIsOfType(SignatureType... signatureTypes) { + return new SignatureValidator() { + @Override + public void verify(PGPSignature signature) throws SignatureValidationException { + SignatureType type = SignatureType.valueOf(signature.getSignatureType()); + boolean valid = false; + for (SignatureType allowed : signatureTypes) { + if (type == allowed) { + valid = true; + break; + } + } + if (!valid) { + throw new SignatureValidationException("Signature is of type " + type + " while only " + Arrays.toString(signatureTypes) + " are allowed here."); + } + } + }; + } + + public static SignatureValidator correctSignatureOverUserId(String userId, PGPPublicKey certifiedKey, PGPPublicKey certifyingKey) { + return new SignatureValidator() { + @Override + public void verify(PGPSignature signature) throws SignatureValidationException { + try { + signature.init(ImplementationFactory.getInstance().getPGPContentVerifierBuilderProvider(), certifyingKey); + boolean valid = signature.verifyCertification(userId, certifiedKey); + if (!valid) { + throw new SignatureValidationException("Signature over user-id '" + userId + "' is not correct."); + } + } catch (PGPException e) { + throw new SignatureValidationException("Cannot verify signature over user-id '" + userId + "'.", e); + } + } + }; + } + + public static SignatureValidator correctSignatureOverUserAttributes(PGPUserAttributeSubpacketVector userAttributes, PGPPublicKey certifiedKey, PGPPublicKey certifyingKey) { + return new SignatureValidator() { + @Override + public void verify(PGPSignature signature) throws SignatureValidationException { + try { + signature.init(ImplementationFactory.getInstance().getPGPContentVerifierBuilderProvider(), certifyingKey); + boolean valid = signature.verifyCertification(userAttributes, certifiedKey); + if (!valid) { + throw new SignatureValidationException("Signature over user-attribute vector is not correct."); + } + } catch (PGPException e) { + throw new SignatureValidationException("Cannot verify signature over user-attribute vector.", e); + } + } + }; + } + +} diff --git a/pgpainless-core/src/main/java/org/pgpainless/signature/SignatureValidityComparator.java b/pgpainless-core/src/main/java/org/pgpainless/signature/SignatureValidityComparator.java new file mode 100644 index 00000000..67a09045 --- /dev/null +++ b/pgpainless-core/src/main/java/org/pgpainless/signature/SignatureValidityComparator.java @@ -0,0 +1,49 @@ +/* + * Copyright 2021 Paul Schaub. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.pgpainless.signature; + +import java.util.Comparator; + +import org.bouncycastle.openpgp.PGPSignature; + +public class SignatureValidityComparator implements Comparator { + + private final SignatureCreationDateComparator.Order order; + private final SignatureCreationDateComparator creationDateComparator; + + public SignatureValidityComparator() { + this(SignatureCreationDateComparator.DEFAULT_ORDER); + } + + public SignatureValidityComparator(SignatureCreationDateComparator.Order order) { + this.order = order; + this.creationDateComparator = new SignatureCreationDateComparator(order); + } + + @Override + public int compare(PGPSignature one, PGPSignature two) { + int compareByCreationTime = creationDateComparator.compare(one, two); + boolean oneIsHard = SignatureUtils.isHardRevocation(one); + boolean twoIsHard = SignatureUtils.isHardRevocation(two); + + // both have same "hardness", so compare creation time + if (oneIsHard == twoIsHard) { + return compareByCreationTime; + } + // favor the "harder" signature + return oneIsHard ? -1 : 1; + } +} diff --git a/pgpainless-core/src/main/java/org/pgpainless/signature/package-info.java b/pgpainless-core/src/main/java/org/pgpainless/signature/package-info.java new file mode 100644 index 00000000..f0e27779 --- /dev/null +++ b/pgpainless-core/src/main/java/org/pgpainless/signature/package-info.java @@ -0,0 +1,19 @@ +/* + * Copyright 2018 Paul Schaub. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * Classes related to OpenPGP signatures. + */ +package org.pgpainless.signature; diff --git a/pgpainless-core/src/main/java/org/pgpainless/util/SignatureSubpacketGeneratorUtil.java b/pgpainless-core/src/main/java/org/pgpainless/signature/subpackets/SignatureSubpacketGeneratorUtil.java similarity index 99% rename from pgpainless-core/src/main/java/org/pgpainless/util/SignatureSubpacketGeneratorUtil.java rename to pgpainless-core/src/main/java/org/pgpainless/signature/subpackets/SignatureSubpacketGeneratorUtil.java index 11b5e67a..aadfe0e3 100644 --- a/pgpainless-core/src/main/java/org/pgpainless/util/SignatureSubpacketGeneratorUtil.java +++ b/pgpainless-core/src/main/java/org/pgpainless/signature/subpackets/SignatureSubpacketGeneratorUtil.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.pgpainless.util; +package org.pgpainless.signature.subpackets; import java.util.ArrayList; import java.util.Date; 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 new file mode 100644 index 00000000..d27ea65d --- /dev/null +++ b/pgpainless-core/src/main/java/org/pgpainless/signature/subpackets/SignatureSubpacketsUtil.java @@ -0,0 +1,359 @@ +/* + * Copyright 2021 Paul Schaub. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.pgpainless.signature.subpackets; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Date; +import java.util.List; + +import org.bouncycastle.bcpg.sig.Exportable; +import org.bouncycastle.bcpg.sig.Features; +import org.bouncycastle.bcpg.sig.IntendedRecipientFingerprint; +import org.bouncycastle.bcpg.sig.IssuerFingerprint; +import org.bouncycastle.bcpg.sig.IssuerKeyID; +import org.bouncycastle.bcpg.sig.KeyExpirationTime; +import org.bouncycastle.bcpg.sig.KeyFlags; +import org.bouncycastle.bcpg.sig.NotationData; +import org.bouncycastle.bcpg.sig.PreferredAlgorithms; +import org.bouncycastle.bcpg.sig.PrimaryUserID; +import org.bouncycastle.bcpg.sig.Revocable; +import org.bouncycastle.bcpg.sig.RevocationKey; +import org.bouncycastle.bcpg.sig.RevocationReason; +import org.bouncycastle.bcpg.sig.SignatureCreationTime; +import org.bouncycastle.bcpg.sig.SignatureExpirationTime; +import org.bouncycastle.bcpg.sig.SignatureTarget; +import org.bouncycastle.bcpg.sig.SignerUserID; +import org.bouncycastle.bcpg.sig.TrustSignature; +import org.bouncycastle.openpgp.PGPException; +import org.bouncycastle.openpgp.PGPPublicKey; +import org.bouncycastle.openpgp.PGPSignature; +import org.bouncycastle.openpgp.PGPSignatureList; +import org.bouncycastle.openpgp.PGPSignatureSubpacketVector; +import org.bouncycastle.util.encoders.Hex; +import org.pgpainless.algorithm.SignatureSubpacket; +import org.pgpainless.key.OpenPgpV4Fingerprint; + +/** + * Utility class to access signature subpackets from signatures. + * + * Since rfc4880 is not always clear about where a signature subpacket can be located (hashed/unhashed area), + * this class makes some educated guesses as to where the subpacket may be found when necessary. + */ +public class SignatureSubpacketsUtil { + + /** + * Return the issuer-fingerprint subpacket of the signature. + * Since this packet is self-authenticating, we expect it to be in the unhashed area, + * however as it cannot hurt we search for it in the hashed area first. + * + * @param signature signature + * @return issuer fingerprint or null + */ + public static IssuerFingerprint getIssuerFingerprint(PGPSignature signature) { + return hashedOrUnhashed(signature, SignatureSubpacket.issuerFingerprint); + } + + public static OpenPgpV4Fingerprint getIssuerFingerprintAsOpenPgpV4Fingerprint(PGPSignature signature) { + IssuerFingerprint subpacket = getIssuerFingerprint(signature); + if (subpacket == null) { + return null; + } + OpenPgpV4Fingerprint fingerprint = new OpenPgpV4Fingerprint(Hex.encode(subpacket.getFingerprint())); + return fingerprint; + } + + /** + * Return the issuer key-id subpacket of the signature. + * Since this packet is self-authenticating, we expect it to be in the unhashed area, + * however as it cannot hurt we search for it in the hashed area first. + * + * @param signature signature + * @return issuer key-id or null + */ + public static IssuerKeyID getIssuerKeyId(PGPSignature signature) { + return hashedOrUnhashed(signature, SignatureSubpacket.issuerKeyId); + } + + /** + * Return the revocation reason subpacket of the signature. + * Since this packet is rather important for revocations, we only search for it in the + * hashed area of the signature. + * + * @param signature signature + * @return revocation reason + */ + public static RevocationReason getRevocationReason(PGPSignature signature) { + return hashed(signature, SignatureSubpacket.revocationReason); + } + + /** + * Return the signature creation time subpacket. + * Since this packet is rather important, we only search for it in the hashed area + * of the signature. + * + * @param signature signature + * @return signature creation time subpacket + */ + public static SignatureCreationTime getSignatureCreationTime(PGPSignature signature) { + return hashed(signature, SignatureSubpacket.signatureCreationTime); + } + + /** + * Return the signature expiration time subpacket of the signature. + * Since this packet is rather important, we only search for it in the hashed area of the signature. + * + * @param signature signature + * @return signature expiration time + */ + public static SignatureExpirationTime getSignatureExpirationTime(PGPSignature signature) { + return hashed(signature, SignatureSubpacket.signatureExpirationTime); + } + + public static Date getSignatureExpirationTimeAsDate(PGPSignature signature) { + SignatureExpirationTime subpacket = getSignatureExpirationTime(signature); + if (subpacket == null || subpacket.getTime() == 0) { + return null; + } + return new Date(signature.getCreationTime().getTime() + 1000 * subpacket.getTime()); + } + + /** + * Return the key expiration time subpacket of this signature. + * We only look for it in the hashed area of the signature. + * + * @param signature signature + * @return key expiration time + */ + public static KeyExpirationTime getKeyExpirationTime(PGPSignature signature) { + return hashed(signature, SignatureSubpacket.keyExpirationTime); + } + + public static Date getKeyExpirationTimeAsDate(PGPSignature signature, PGPPublicKey signingKey) { + KeyExpirationTime subpacket = getKeyExpirationTime(signature); + if (subpacket == null || subpacket.getTime() == 0) { + return null; + } + if (signature.getKeyID() != signingKey.getKeyID()) { + throw new IllegalArgumentException("Provided key (" + Long.toHexString(signingKey.getKeyID()) + ") did not create the signature (" + Long.toHexString(signature.getKeyID()) + ")"); + } + return new Date(signingKey.getCreationTime().getTime() + 1000 * subpacket.getTime()); + } + + /** + * Return the revocable subpacket of this signature. + * We only look for it in the hashed area of the signature. + * + * @param signature signature + * @return revocable subpacket + */ + public static Revocable getRevocable(PGPSignature signature) { + return hashed(signature, SignatureSubpacket.revocable); + } + + /** + * Return the symmetric algorithm preferences from the signatures hashed area. + * + * @param signature signature + * @return symm. algo. prefs + */ + public static PreferredAlgorithms getPreferredSymmetricAlgorithms(PGPSignature signature) { + return hashed(signature, SignatureSubpacket.preferredSymmetricAlgorithms); + } + + /** + * Return the hash algorithm preferences from the signatures hashed area. + * + * @param signature signature + * @return hash algo prefs + */ + public static PreferredAlgorithms getPreferredHashAlgorithms(PGPSignature signature) { + return hashed(signature, SignatureSubpacket.preferredHashAlgorithms); + } + + /** + * Return the compression algorithm preferences from the signatures hashed area. + * + * @param signature signature + * @return compression algo prefs + */ + public static PreferredAlgorithms getPreferredCompressionAlgorithms(PGPSignature signature) { + return hashed(signature, SignatureSubpacket.preferredCompressionAlgorithms); + } + + /** + * Return the primary user-id subpacket from the signatures hashed area. + * + * @param signature signature + * @return primary user id + */ + public static PrimaryUserID getPrimaryUserId(PGPSignature signature) { + return hashed(signature, SignatureSubpacket.primaryUserId); + } + + /** + * Return the key flags subpacket from the signatures hashed area. + * + * @param signature signature + * @return key flags + */ + public static KeyFlags getKeyFlags(PGPSignature signature) { + return hashed(signature, SignatureSubpacket.keyFlags); + } + + /** + * Return the features subpacket from the signatures hashed area. + * + * @param signature signature + * @return features subpacket + */ + public static Features getFeatures(PGPSignature signature) { + return hashed(signature, SignatureSubpacket.features); + } + + /** + * Return the signature target subpacket from the signature. + * We search for this subpacket in the hashed and unhashed area (in this order). + * + * @param signature signature + * @return signature target + */ + public static SignatureTarget getSignatureTarget(PGPSignature signature) { + return hashedOrUnhashed(signature, SignatureSubpacket.signatureTarget); + } + + /** + * Return the notation data subpackets from the signatures hashed area. + * + * @param signature signature + * @return hashed notations + */ + public static List getHashedNotationData(PGPSignature signature) { + NotationData[] notations = signature.getHashedSubPackets().getNotationDataOccurrences(); + return Arrays.asList(notations); + } + + /** + * Return the notation data subpackets from the signatures unhashed area. + * + * @param signature signture + * @return unhashed notations + */ + public static List getUnhashedNotationData(PGPSignature signature) { + NotationData[] notations = signature.getUnhashedSubPackets().getNotationDataOccurrences(); + return Arrays.asList(notations); + } + + /** + * Return the revocation key subpacket from the signatures hashed area. + * + * @param signature signature + * @return revocation key + */ + public static RevocationKey getRevocationKey(PGPSignature signature) { + return hashed(signature, SignatureSubpacket.revocationKey); + } + + /** + * Return the signers user-id from the hashed area of the signature. + * TODO: Can this subpacket also be found in the unhashed area? + * + * @param signature signature + * @return signers user-id + */ + public static SignerUserID getSignerUserID(PGPSignature signature) { + return hashed(signature, SignatureSubpacket.signerUserId); + } + + /** + * Return the intended recipients fingerprint subpackets from the hashed area of this signature. + * + * @param signature signature + * @return intended recipient fingerprint subpackets + */ + public static List getIntendedRecipientFingerprints(PGPSignature signature) { + org.bouncycastle.bcpg.SignatureSubpacket[] subpackets = signature.getHashedSubPackets().getSubpackets(SignatureSubpacket.intendedRecipientFingerprint.getCode()); + List intendedRecipients = new ArrayList<>(subpackets.length); + for (org.bouncycastle.bcpg.SignatureSubpacket subpacket : subpackets) { + intendedRecipients.add((IntendedRecipientFingerprint) subpacket); + } + return intendedRecipients; + } + + /** + * Return the embedded signature subpacket from the signatures hashed area. + * + * @param signature signature + * @return embedded signature + */ + public static PGPSignatureList getEmbeddedSignature(PGPSignature signature) throws PGPException { + PGPSignatureList hashed = signature.getHashedSubPackets().getEmbeddedSignatures(); + if (!hashed.isEmpty()) { + return hashed; + } + return signature.getUnhashedSubPackets().getEmbeddedSignatures(); + } + + /** + * Return the signatures exportable certification subpacket from the hashed area. + * TODO: Can this packet also be placed in the unhashed area? + * + * @param signature signature + * @return exportable certification subpacket + */ + public static Exportable getExportableCertification(PGPSignature signature) { + return hashed(signature, SignatureSubpacket.exportableCertification); + } + + /** + * Return the trust signature packet from the signatures hashed area. + * + * @param signature signature + * @return trust signature subpacket + */ + public static TrustSignature getTrustSignature(PGPSignature signature) { + return hashed(signature, SignatureSubpacket.trustSignature); + } + + private static

P hashed(PGPSignature signature, SignatureSubpacket type) { + return getSignatureSubpacket(signature.getHashedSubPackets(), type); + } + + private static

P unhashed(PGPSignature signature, SignatureSubpacket type) { + return getSignatureSubpacket(signature.getUnhashedSubPackets(), type); + } + + private static

P hashedOrUnhashed(PGPSignature signature, SignatureSubpacket type) { + P hashedSubpacket = hashed(signature, type); + return hashedSubpacket != null ? hashedSubpacket : unhashed(signature, type); + } + + /** + * Return the last occurence of a subpacket type in the given signature subpacket vector. + * + * @param vector subpacket vector (hashed/unhashed) + * @param type subpacket type + * @param

generic return type of the subpacket + * @return last occurrence of the subpacket in the vector + */ + public static

P getSignatureSubpacket(PGPSignatureSubpacketVector vector, SignatureSubpacket type) { + org.bouncycastle.bcpg.SignatureSubpacket[] allPackets = vector.getSubpackets(type.getCode()); + if (allPackets.length == 0) { + return null; + } + return (P) allPackets[allPackets.length - 1]; // return last + } +} diff --git a/pgpainless-core/src/main/java/org/pgpainless/signature/subpackets/package-info.java b/pgpainless-core/src/main/java/org/pgpainless/signature/subpackets/package-info.java new file mode 100644 index 00000000..790adf7f --- /dev/null +++ b/pgpainless-core/src/main/java/org/pgpainless/signature/subpackets/package-info.java @@ -0,0 +1,19 @@ +/* + * Copyright 2018 Paul Schaub. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * Classes related to OpenPGP signatures. + */ +package org.pgpainless.signature.subpackets; diff --git a/pgpainless-core/src/main/java/org/pgpainless/util/BCUtil.java b/pgpainless-core/src/main/java/org/pgpainless/util/BCUtil.java index 9f02ff1b..5fd9a022 100644 --- a/pgpainless-core/src/main/java/org/pgpainless/util/BCUtil.java +++ b/pgpainless-core/src/main/java/org/pgpainless/util/BCUtil.java @@ -19,7 +19,9 @@ import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; +import java.nio.charset.Charset; import java.util.Arrays; +import java.util.Date; import java.util.HashSet; import java.util.Iterator; import java.util.Set; @@ -29,6 +31,8 @@ import javax.annotation.Nonnull; import org.bouncycastle.openpgp.PGPException; import org.bouncycastle.openpgp.PGPKeyRing; +import org.bouncycastle.openpgp.PGPMarker; +import org.bouncycastle.openpgp.PGPObjectFactory; import org.bouncycastle.openpgp.PGPPublicKey; import org.bouncycastle.openpgp.PGPPublicKeyRing; import org.bouncycastle.openpgp.PGPPublicKeyRingCollection; @@ -36,19 +40,28 @@ import org.bouncycastle.openpgp.PGPSecretKey; import org.bouncycastle.openpgp.PGPSecretKeyRing; import org.bouncycastle.openpgp.PGPSecretKeyRingCollection; import org.bouncycastle.openpgp.PGPSignature; +import org.bouncycastle.openpgp.PGPSignatureList; import org.bouncycastle.openpgp.PGPSignatureSubpacketVector; import org.bouncycastle.openpgp.PGPUtil; import org.bouncycastle.util.io.Streams; import org.pgpainless.algorithm.KeyFlag; +import org.pgpainless.implementation.ImplementationFactory; import org.pgpainless.util.selection.key.PublicKeySelectionStrategy; -import org.pgpainless.util.selection.key.impl.NoRevocation; -import org.pgpainless.util.selection.key.impl.KeyBelongsToKeyRing; import org.pgpainless.util.selection.key.impl.And; +import org.pgpainless.util.selection.key.impl.KeyBelongsToKeyRing; +import org.pgpainless.util.selection.key.impl.NoRevocation; public class BCUtil { private static final Logger LOGGER = Logger.getLogger(BCUtil.class.getName()); + public static Date getExpirationDate(Date creationDate, long validSeconds) { + if (validSeconds == 0) { + return null; + } + return new Date(creationDate.getTime() + 1000 * validSeconds); + } + /* PGPXxxKeyRing -> PGPXxxKeyRingCollection */ @@ -245,4 +258,18 @@ public class BCUtil { long keyId) { return ring.getSecretKey(keyId) != null; } + + public static PGPSignatureList readSignatures(String encoding) throws IOException { + InputStream inputStream = getPgpDecoderInputStream(encoding.getBytes(Charset.forName("UTF8"))); + PGPObjectFactory objectFactory = new PGPObjectFactory(inputStream, ImplementationFactory.getInstance().getKeyFingerprintCalculator()); + Object next = objectFactory.nextObject(); + while (next != null) { + if (next instanceof PGPMarker) { + next = objectFactory.nextObject(); + continue; + } + return (PGPSignatureList) next; + } + return null; + }; } diff --git a/pgpainless-core/src/main/java/org/pgpainless/util/NotationRegistry.java b/pgpainless-core/src/main/java/org/pgpainless/util/NotationRegistry.java index ebd0859f..8e5ff716 100644 --- a/pgpainless-core/src/main/java/org/pgpainless/util/NotationRegistry.java +++ b/pgpainless-core/src/main/java/org/pgpainless/util/NotationRegistry.java @@ -25,22 +25,14 @@ import java.util.Set; * * To add a notation name, call {@link #addKnownNotation(String)}. */ -public final class NotationRegistry { +public class NotationRegistry { - private static NotationRegistry INSTANCE; private final Set knownNotations = new HashSet<>(); - private NotationRegistry() { + public NotationRegistry() { } - public static NotationRegistry getInstance() { - if (INSTANCE == null) { - INSTANCE = new NotationRegistry(); - } - return INSTANCE; - } - /** * Add a known notation name into the registry. * This will cause critical notations with that name to no longer invalidate the signature. diff --git a/pgpainless-core/src/main/java/org/pgpainless/util/Tuple.java b/pgpainless-core/src/main/java/org/pgpainless/util/Tuple.java new file mode 100644 index 00000000..208fcd61 --- /dev/null +++ b/pgpainless-core/src/main/java/org/pgpainless/util/Tuple.java @@ -0,0 +1,34 @@ +/* + * Copyright 2021 Paul Schaub. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.pgpainless.util; + +public class Tuple { + private final A a; + private final B b; + + public Tuple(A a, B b) { + this.a = a; + this.b = b; + } + + public A getFirst() { + return a; + } + + public B getSecond() { + return b; + } +} diff --git a/pgpainless-core/src/main/java/org/pgpainless/util/selection/key/SelectPublicKey.java b/pgpainless-core/src/main/java/org/pgpainless/util/selection/key/SelectPublicKey.java new file mode 100644 index 00000000..a523d1cb --- /dev/null +++ b/pgpainless-core/src/main/java/org/pgpainless/util/selection/key/SelectPublicKey.java @@ -0,0 +1,311 @@ +/* + * Copyright 2021 Paul Schaub. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.pgpainless.util.selection.key; + +import java.util.ArrayList; +import java.util.Date; +import java.util.Iterator; +import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.bouncycastle.openpgp.PGPException; +import org.bouncycastle.openpgp.PGPKeyRing; +import org.bouncycastle.openpgp.PGPPublicKey; +import org.bouncycastle.openpgp.PGPSignature; +import org.pgpainless.algorithm.CompressionAlgorithm; +import org.pgpainless.algorithm.HashAlgorithm; +import org.pgpainless.algorithm.KeyFlag; +import org.pgpainless.algorithm.SignatureType; +import org.pgpainless.algorithm.SymmetricKeyAlgorithm; +import org.pgpainless.signature.SignatureUtils; +import org.pgpainless.util.CollectionUtils; +import org.pgpainless.signature.SelectSignatureFromKey; + +public abstract class SelectPublicKey { + + private static final Logger LOGGER = Logger.getLogger(SelectPublicKey.class.getName()); + + public abstract boolean accept(PGPPublicKey publicKey, PGPKeyRing keyRing); + + public List selectPublicKeys(PGPKeyRing keyRing) { + List selected = new ArrayList<>(); + List publicKeys = CollectionUtils.iteratorToList(keyRing.getPublicKeys()); + for (PGPPublicKey publicKey : publicKeys) { + if (accept(publicKey, keyRing)) { + selected.add(publicKey); + } + } + return selected; + } + + public PGPPublicKey firstMatch(PGPKeyRing keyRing) { + List selected = selectPublicKeys(keyRing); + if (selected.isEmpty()) { + return null; + } + return selected.get(0); + } + + public static SelectPublicKey isPrimaryKey() { + return new SelectPublicKey() { + @Override + public boolean accept(PGPPublicKey publicKey, PGPKeyRing keyRing) { + return publicKey.isMasterKey() && keyRing.getPublicKey().getKeyID() == publicKey.getKeyID(); + } + }; + } + + public static SelectPublicKey isSubKey() { + return new SelectPublicKey() { + @Override + public boolean accept(PGPPublicKey publicKey, PGPKeyRing keyRing) { + if (isPrimaryKey().accept(publicKey, keyRing)) { + return false; + } + PGPPublicKey primaryKey = keyRing.getPublicKey(); + SelectSignatureFromKey bindingSigSelector = SelectSignatureFromKey.isValidSubkeyBindingSignature(primaryKey, publicKey); + + Iterator bindingSigs = publicKey.getSignaturesOfType(SignatureType.SUBKEY_BINDING.getCode()); + while (bindingSigs.hasNext()) { + if (bindingSigSelector.accept(bindingSigs.next(), publicKey, keyRing)) { + return true; + } + } + return false; + } + }; + } + + public static SelectPublicKey validForUserId(String userId) { + return validForUserId(userId, new Date()); + } + + public static SelectPublicKey validForUserId(String userId, Date validationDate) { + return new SelectPublicKey() { + @Override + public boolean accept(PGPPublicKey publicKey, PGPKeyRing keyRing) { + PGPPublicKey primaryKey = keyRing.getPublicKey(); + + // Has userid + List userIds = CollectionUtils.iteratorToList(primaryKey.getUserIDs()); + if (!userIds.contains(userId)) { + LOGGER.log(Level.INFO, "Keyring " + Long.toHexString(primaryKey.getKeyID()) + " does not contain user-id '" + userId + "'"); + } + + // is primary key revoked + if (isRevoked(validationDate).accept(primaryKey, keyRing)) { + LOGGER.log(Level.INFO, "Primary key " + Long.toHexString(primaryKey.getKeyID()) + " has been revoked."); + return false; + } + + // is userid expired + if (isExpired(userId, validationDate).accept(primaryKey, keyRing)) { + LOGGER.log(Level.INFO, "Primary key " + Long.toHexString(primaryKey.getKeyID()) + " has expired."); + return false; + } + + // is userid revoked + if (isUserIdRevoked(userId, validationDate).accept(primaryKey, keyRing)) { + LOGGER.log(Level.INFO, "Primary key " + Long.toHexString(primaryKey.getKeyID()) + " has been revoked."); + } + + // UserId on primary key valid + try { + boolean userIdValid = SignatureUtils.isUserIdValid(primaryKey, userId); + if (!userIdValid) { + LOGGER.log(Level.INFO, "User-id '" + userId + "' is not valid for key " + Long.toHexString(primaryKey.getKeyID())); + return false; + } + } catch (PGPException e) { + LOGGER.log(Level.INFO, "Could not verify signature on primary key " + Long.toHexString(primaryKey.getKeyID()) + " and user-id '" + userId + "'", e); + return false; + } + + // is primary key + if (publicKey == primaryKey) { + return true; + } + + // is subkey + if (!isSubKey().accept(publicKey, keyRing)) { + LOGGER.log(Level.INFO, "Key " + Long.toHexString(publicKey.getKeyID()) + " is not valid subkey of key " + Long.toHexString(primaryKey.getKeyID())); + return false; + } + // is subkey revoked + if (isRevoked(validationDate).accept(publicKey, keyRing)) { + LOGGER.log(Level.INFO, "Subkey " + Long.toHexString(publicKey.getKeyID()) + " of key " + Long.toHexString(primaryKey.getKeyID()) + " is revoked"); + return false; + } + + return true; + } + }; + } + + public static SelectPublicKey isRevoked(Date validationDate) { + return new SelectPublicKey() { + @Override + public boolean accept(PGPPublicKey publicKey, PGPKeyRing keyRing) { + if (publicKey.isMasterKey()) { + if (!publicKey.hasRevocation()) { + return false; + } else { + SelectSignatureFromKey validRevocation = SelectSignatureFromKey.isValidKeyRevocationSignature(publicKey); + Iterator revSigIt = publicKey.getSignaturesOfType(SignatureType.KEY_REVOCATION.getCode()); + List revSigs = CollectionUtils.iteratorToList(revSigIt); + List validRevSigs = validRevocation.select(revSigs, publicKey, keyRing); + return !validRevSigs.isEmpty(); + } + } else { + return publicKey.hasRevocation() || keyRing.getPublicKey().hasRevocation(); + } + } + }; + } + + public static SelectPublicKey isExpired(String userId, Date validationDate) { + return new SelectPublicKey() { + @Override + public boolean accept(PGPPublicKey publicKey, PGPKeyRing keyRing) { + return false; + } + }; + } + + public static SelectPublicKey isUserIdRevoked(String userId, Date validationDate) { + return new SelectPublicKey() { + @Override + public boolean accept(PGPPublicKey publicKey, PGPKeyRing keyRing) { + return false; + } + }; + } + + private static SelectPublicKey hasKeyRevocationSignature() { + return new SelectPublicKey() { + @Override + public boolean accept(PGPPublicKey publicKey, PGPKeyRing keyRing) { + Iterator it = publicKey.getSignatures(); + while (it.hasNext()) { + PGPSignature signature = it.next(); + if (SelectSignatureFromKey.isValidKeyRevocationSignature(publicKey).accept(signature, publicKey, keyRing)) { + return true; + } + } + return false; + } + }; + } + + private static SelectPublicKey hasSubkeyRevocationSignature() { + return new SelectPublicKey() { + @Override + public boolean accept(PGPPublicKey publicKey, PGPKeyRing keyRing) { + Iterator it = publicKey.getKeySignatures(); + while (it.hasNext()) { + PGPSignature signature = it.next(); + if (SelectSignatureFromKey.isValidSubkeyRevocationSignature(publicKey, keyRing.getPublicKey()).accept(signature, publicKey, keyRing)) { + return true; + } + } + return false; + } + }; + } + + private static SelectPublicKey isSubkeyOfRevokedPrimaryKey() { + return new SelectPublicKey() { + @Override + public boolean accept(PGPPublicKey publicKey, PGPKeyRing keyRing) { + return isSubKey().accept(publicKey, keyRing) + && SelectPublicKey.hasKeyRevocationSignature().accept(keyRing.getPublicKey(), keyRing); + } + }; + } + + public static SelectPublicKey hasKeyFlag(String userId, KeyFlag keyFlag) { + return new SelectPublicKey() { + @Override + public boolean accept(PGPPublicKey publicKey, PGPKeyRing keyRing) { + return false; + } + }; + } + + public static SelectPublicKey supportsAlgorithm(SymmetricKeyAlgorithm symmetricKeyAlgorithm) { + return new SelectPublicKey() { + @Override + public boolean accept(PGPPublicKey publicKey, PGPKeyRing keyRing) { + return false; + } + }; + } + + public static SelectPublicKey supportsAlgorithm(HashAlgorithm hashAlgorithm) { + return new SelectPublicKey() { + @Override + public boolean accept(PGPPublicKey publicKey, PGPKeyRing keyRing) { + return false; + } + }; + } + + public static SelectPublicKey supportsAlgorithm(CompressionAlgorithm compressionAlgorithm) { + return new SelectPublicKey() { + @Override + public boolean accept(PGPPublicKey publicKey, PGPKeyRing keyRing) { + return false; + } + }; + } + + public static SelectPublicKey and(SelectPublicKey... selectors) { + return new SelectPublicKey() { + @Override + public boolean accept(PGPPublicKey publicKey, PGPKeyRing keyRing) { + for (SelectPublicKey selector : selectors) { + if (!selector.accept(publicKey, keyRing)) { + return false; + } + } + return true; + } + }; + } + + public static SelectPublicKey or(SelectPublicKey... selectors) { + return new SelectPublicKey() { + @Override + public boolean accept(PGPPublicKey publicKey, PGPKeyRing keyRing) { + boolean accept = false; + for (SelectPublicKey selector : selectors) { + accept |= selector.accept(publicKey, keyRing); + } + return accept; + } + }; + } + + public static SelectPublicKey not(SelectPublicKey selector) { + return new SelectPublicKey() { + @Override + public boolean accept(PGPPublicKey publicKey, PGPKeyRing keyRing) { + return !selector.accept(publicKey, keyRing); + } + }; + } +} diff --git a/pgpainless-core/src/main/java/org/pgpainless/util/selection/key/impl/HasAnyKeyFlagSelectionStrategy.java b/pgpainless-core/src/main/java/org/pgpainless/util/selection/key/impl/HasAnyKeyFlagSelectionStrategy.java index c186ad97..70b7267c 100644 --- a/pgpainless-core/src/main/java/org/pgpainless/util/selection/key/impl/HasAnyKeyFlagSelectionStrategy.java +++ b/pgpainless-core/src/main/java/org/pgpainless/util/selection/key/impl/HasAnyKeyFlagSelectionStrategy.java @@ -44,7 +44,10 @@ public class HasAnyKeyFlagSelectionStrategy { @Override public boolean accept(PGPPublicKey key) { Iterator signatures = key.getSignatures(); - int flags = signatures.next().getHashedSubPackets().getKeyFlags(); + int flags = 0; + while (signatures.hasNext()) { + flags = signatures.next().getHashedSubPackets().getKeyFlags(); + } return (keyFlagMask & flags) != 0; } } diff --git a/pgpainless-core/src/test/java/org/bouncycastle/PGPPublicKeyRingTest.java b/pgpainless-core/src/test/java/org/bouncycastle/PGPPublicKeyRingTest.java new file mode 100644 index 00000000..c69008ea --- /dev/null +++ b/pgpainless-core/src/test/java/org/bouncycastle/PGPPublicKeyRingTest.java @@ -0,0 +1,60 @@ +/* + * Copyright 2021 Paul Schaub. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.bouncycastle; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; + +import java.security.InvalidAlgorithmParameterException; +import java.security.NoSuchAlgorithmException; +import java.util.Iterator; + +import org.bouncycastle.openpgp.PGPException; +import org.bouncycastle.openpgp.PGPPublicKey; +import org.bouncycastle.openpgp.PGPPublicKeyRing; +import org.bouncycastle.openpgp.PGPSecretKeyRing; +import org.junit.jupiter.api.Test; +import org.pgpainless.PGPainless; +import org.pgpainless.key.util.KeyRingUtils; + +public class PGPPublicKeyRingTest { + + /** + * Learning test to see if BC also makes userids available on subkeys. + * It does not. + * + * see also https://security.stackexchange.com/questions/92635/is-it-possible-to-assign-different-uids-to-subkeys-for-the-purpose-of-having-mul + * + * @throws InvalidAlgorithmParameterException + * @throws NoSuchAlgorithmException + * @throws PGPException + */ + @Test + public void subkeysDoNotHaveUserIDsTest() throws InvalidAlgorithmParameterException, NoSuchAlgorithmException, PGPException { + PGPSecretKeyRing secretKeys = PGPainless.generateKeyRing().simpleEcKeyRing("primary@user.id"); + PGPPublicKeyRing publicKeys = KeyRingUtils.publicKeyRingFrom(secretKeys); + PGPPublicKey primaryKey = publicKeys.getPublicKey(); + for (PGPPublicKey subkey : publicKeys) { + Iterator userIds = subkey.getUserIDs(); + if (primaryKey == subkey) { + assertEquals("primary@user.id", userIds.next()); + assertFalse(userIds.hasNext()); + } else { + assertFalse(userIds.hasNext()); + } + } + } +} diff --git a/pgpainless-core/src/test/java/org/junit/JUtils.java b/pgpainless-core/src/test/java/org/junit/JUtils.java index c053027f..553d1f61 100644 --- a/pgpainless-core/src/test/java/org/junit/JUtils.java +++ b/pgpainless-core/src/test/java/org/junit/JUtils.java @@ -17,9 +17,16 @@ package org.junit; import static org.junit.jupiter.api.Assertions.assertTrue; +import org.junit.jupiter.api.Test; + public class JUtils { public static void assertEquals(long a, long b, long delta) { assertTrue(a - delta <= b && a + delta >= b); } + + @Test + public void comparatorLearningTest() { + assertEquals(-1, Integer.compare(5,6), 0); + } } diff --git a/pgpainless-core/src/test/java/org/pgpainless/encryption_signing/EncryptDecryptTest.java b/pgpainless-core/src/test/java/org/pgpainless/encryption_signing/EncryptDecryptTest.java index 6898c31a..666a652e 100644 --- a/pgpainless-core/src/test/java/org/pgpainless/encryption_signing/EncryptDecryptTest.java +++ b/pgpainless-core/src/test/java/org/pgpainless/encryption_signing/EncryptDecryptTest.java @@ -18,6 +18,7 @@ package org.pgpainless.encryption_signing; import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import java.io.ByteArrayInputStream; @@ -36,6 +37,7 @@ import org.bouncycastle.openpgp.PGPPublicKeyRing; import org.bouncycastle.openpgp.PGPSecretKeyRing; import org.bouncycastle.openpgp.PGPSignature; import org.bouncycastle.util.io.Streams; +import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; import org.pgpainless.PGPainless; @@ -280,4 +282,68 @@ public class EncryptDecryptTest { OpenPgpMetadata metadata = verifier.getResult(); assertFalse(metadata.getVerifiedSignatures().isEmpty()); } + + @Test + public void expiredSubkeyBacksigTest() throws IOException { + String key = "-----BEGIN PGP PUBLIC KEY BLOCK-----\n" + + "\n" + + "xsDNBF2lnPIBDAC5cL9PQoQLTMuhjbYvb4Ncuuo0bfmgPRFywX53jPhoFf4Zg6mv\n" + + "/seOXpgecTdOcVttfzC8ycIKrt3aQTiwOG/ctaR4Bk/t6ayNFfdUNxHWk4WCKzdz\n" + + "/56fW2O0F23qIRd8UUJp5IIlN4RDdRCtdhVQIAuzvp2oVy/LaS2kxQoKvph/5pQ/\n" + + "5whqsyroEWDJoSV0yOb25B/iwk/pLUFoyhDG9bj0kIzDxrEqW+7Ba8nocQlecMF3\n" + + "X5KMN5kp2zraLv9dlBBpWW43XktjcCZgMy20SouraVma8Je/ECwUWYUiAZxLIlMv\n" + + "9CurEOtxUw6N3RdOtLmYZS9uEnn5y1UkF88o8Nku890uk6BrewFzJyLAx5wRZ4F0\n" + + "qV/yq36UWQ0JB/AUGhHVPdFf6pl6eaxBwT5GXvbBUibtf8YI2og5RsgTWtXfU7eb\n" + + "SGXrl5ZMpbA6mbfhd0R8aPxWfmDWiIOhBufhMCvUHh1sApMKVZnvIff9/0Dca3wb\n" + + "vLIwa3T4CyshfT0AEQEAAc0hQm9iIEJhYmJhZ2UgPGJvYkBvcGVucGdwLmV4YW1w\n" + + "bGU+wsFIBBMBCgB8BYJfUbC4AgsJCRD7/MgqAV5zMEcUAAAAAAAeACBzYWx0QG5v\n" + + "dGF0aW9ucy5zZXF1b2lhLXBncC5vcmcEc0Prq/Ohwr794nDXrgZXDdDq38GOMsus\n" + + "hDqEwk/zJgMVCAoCmwECHgEWIQTRpm4aI7GCyZgPeIz7/MgqAV5zMAAA3rIL/3cI\n" + + "WywtBrcW40S3lGoQL8zhl4wrI/HiXUGwEvEB/kfyfNk3uS73d5OgbOk4Xiw8QuCK\n" + + "AX8oyAypYheb1M2Q7VW+Iohl6Jpq8QppUX7YKugnH4bYIZsdVQw5VT+69UsuHfj0\n" + + "x6FKXw3ums2QhpB6XErd/G/npJtaK7LGoMo9ZRGKIdS+KwaXp0jU4+pgNVnzfRCA\n" + + "4AcmRCsHI4pgoIbQ79qCdpe9KJLf+blkNZFKCUXrAegbmaQ8wG4MdH4K/hnM0HaG\n" + + "MWiR0CKuKn8Mx4KHtTQz74jpHQAkvlqxgGulyfx+Kl6e8y4+AatJAG/62/3brIAw\n" + + "+tFXYxnONaQm/22h84YvSp/w4DqtuqHxrkkPjjgdE4QzBuVGd6PEa/59spagX6UC\n" + + "+UMyyVE2MadXPO1gkPmEnBcn/nOlEU3ekpysC3D2Etdxwjhso+MeWFUbQlBDdgVi\n" + + "Sk/B/HjCPLmtH1FELnAe778L0exe+G2hLad8UHcnc2INtwFSBNUSIEYbbsYR0s7A\n" + + "zQRdpZzyAQwA1jC/XGxjK6ddgrRfW9j+s/U00++EvIsgTs2kr3Rg0GP7FLWV0YNt\n" + + "R1mpl55/bEl7yAxCDTkOgPUMXcaKlnQh6zrlt6H53mF6Bvs3inOHQvOsGtU0dqvb\n" + + "1vkTF0juLiJgPlM7pWv+pNQ6IA39vKoQsTMBv4v5vYNXP9GgKbg8inUNT17BxzZY\n" + + "Hfw5+q63ectgDm2on1e8CIRCZ76oBVwzdkVxoy3gjh1eENlk2D4P0uJNZzF1Q8GV\n" + + "67yLANGMCDICE/OkWn6daipYDzW4iJQtYPUWP4hWhjdm+CK+hg6IQUEn2Vtvi16D\n" + + "2blRP8BpUNNa4fNuylWVuJV76rIHvsLZ1pbM3LHpRgE8s6jivS3Rz3WRs0TmWCNn\n" + + "vHPqWizQ3VTy+r3UQVJ5AmhJDrZdZq9iaUIuZ01PoE1+CHiJwuxPtWvVAxf2POcm\n" + + "1M/F1fK1J0e+lKlQuyonTXqXR22Y41wrfP2aPk3nPSTW2DUAf3vRMZg57ZpRxLEh\n" + + "EMxcM4/LMR+PABEBAAHCw0IEGAEKAnYFgl9RsLgJEPv8yCoBXnMwRxQAAAAAAB4A\n" + + "IHNhbHRAbm90YXRpb25zLnNlcXVvaWEtcGdwLm9yZykWtbTuOtDrg4F5s48NrAHA\n" + + "kwkoLb8ZgAbb9VV8JPKRApsCwUKgBBkBCgB1BYJfUbC4BYMAeEzgCRB8L6pN+Tw3\n" + + "skcUAAAAAAAeACBzYWx0QG5vdGF0aW9ucy5zZXF1b2lhLXBncC5vcmemJbX0gOh6\n" + + "Z+WJo5dyEuNnG7CDklyLHJ8BY2QKoO88ehYhBB3c4V8JIXzuLzs3YHwvqk35PDey\n" + + "AAAPrwv+OSxllLwrRUB0BqRYS2/D1qFHFOn0qBOMJaL0X2yjint67SeHosxmvqSg\n" + + "5tnQmaHljFrMZkf6PSGYdz0VwalT8XaubcGyljSxrgc7Qs5jdxKL5IhTfjEb0Q4v\n" + + "8TSp3esG02ZafGAZSwIIW1RfUEMk+XHciEk2pRDkraCAlcCvqL2En+eNLCqWzpTI\n" + + "Fcp0lb2JxRlozzqpfVNq++UXaHaqrGflbrTn4x+1i6zuxCVkjt4gHjQRLACDmEFk\n" + + "mSZxqYZmQdvEfkdSg2XgTjg+QhHunpQyCbxrW5R4qYgm7yjctgv9keVDbIy2lRIM\n" + + "kNWZhZWijw1SxPGVWlKVizi+pWZyX9NBrTAj/ES/HZrLda52PR1BKSE4kG74T/73\n" + + "V/jnqYp0jGI/M3y79DRq2tlO5p6Jp+OcmU2SyvItaNhoateGndLIVPZfAT69avbY\n" + + "tMoEbsA/biVL4xN9SqaLian4ow9/pVm/z4Ej6zSRZUC01hZBQWD02z0ntU7t0CPR\n" + + "R58XC9znFiEE0aZuGiOxgsmYD3iM+/zIKgFeczAAAED5C/975SfSeub9RJHilYFA\n" + + "eeeHU6ZaSpOy0/ZrwSUFmvDrxowiCNn7sYZEZmIBVZ/nIlfbCUUTesIF92aLkIZe\n" + + "EMQUiXP0/HtnAx1duQ8htdb+X/EhuWPPJ7hF5bA6AB1oXVKn3lpggHzauGSilI5m\n" + + "dPXXVdDUWuDQfSn459UOv4PwB52uLtGZK3iprVgYD3RzSWktHMhMvcB2GXNQlfyo\n" + + "yWewq9p+wwbIFUFZYMRIGjJNSc6aQcEHusIn85E+Uid/hrDIiblbvQA+7ONcoaqL\n" + + "DiLSL+bh1/usrmzccUK01nLMmTnG03vU3WR3yqmDlzgU/S3XfZRPECwr6AzNSXoe\n" + + "d4u9/SPt2VBxGtZ0yA4PXgO6PbZC6EIZqmgW5oKjSWZwkryQLGKji+vYJU1FzM+3\n" + + "qO6PYqLVGf97n6LS2xD10rrJ2aUq0CQ/M5ykRVsT6HifV9wPiPzR8ilcXWRT8CQ2\n" + + "Ks2WqI282/DM+Lq/GCSd2nXtS3/KwErTFiF1uHi/N3TwdWA=\n" + + "=j1TE\n" + + "-----END PGP PUBLIC KEY BLOCK-----\n"; + + PGPPublicKeyRing publicKeys = PGPainless.readKeyRing().publicKeyRing(key); + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + assertThrows(IllegalArgumentException.class, () -> + PGPainless.encryptAndOrSign().onOutputStream(outputStream) + .toRecipients(publicKeys)); + } } diff --git a/pgpainless-core/src/test/java/org/pgpainless/encryption_signing/SigningTest.java b/pgpainless-core/src/test/java/org/pgpainless/encryption_signing/SigningTest.java index 50c89069..fde520ec 100644 --- a/pgpainless-core/src/test/java/org/pgpainless/encryption_signing/SigningTest.java +++ b/pgpainless-core/src/test/java/org/pgpainless/encryption_signing/SigningTest.java @@ -72,7 +72,7 @@ public class SigningTest { .toRecipients(keys) .andToSelf(KeyRingUtils.publicKeyRingFrom(cryptieKeys)) .usingAlgorithms(SymmetricKeyAlgorithm.AES_192, HashAlgorithm.SHA384, CompressionAlgorithm.ZIP) - .signWith(SecretKeyRingProtector.unlockSingleKeyWith(TestKeys.CRYPTIE_PASSPHRASE, cryptieSigningKey), cryptieSigningKey) + .signWith(SecretKeyRingProtector.unlockSingleKeyWith(TestKeys.CRYPTIE_PASSPHRASE, cryptieSigningKey), cryptieKeys) .signCanonicalText() .asciiArmor(); diff --git a/pgpainless-core/src/test/java/org/pgpainless/key/KeyRingValidatorTest.java b/pgpainless-core/src/test/java/org/pgpainless/key/KeyRingValidatorTest.java new file mode 100644 index 00000000..35fd7ef4 --- /dev/null +++ b/pgpainless-core/src/test/java/org/pgpainless/key/KeyRingValidatorTest.java @@ -0,0 +1,289 @@ +/* + * Copyright 2021 Paul Schaub. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.pgpainless.key; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.IOException; +import java.util.Date; +import java.util.Iterator; + +import org.bouncycastle.openpgp.PGPPublicKey; +import org.bouncycastle.openpgp.PGPPublicKeyRing; +import org.junit.jupiter.api.Test; +import org.pgpainless.PGPainless; +import org.pgpainless.key.info.KeyRingInfo; +import org.pgpainless.util.ArmorUtils; +import org.pgpainless.util.CollectionUtils; +import org.pgpainless.util.TestUtils; + +public class KeyRingValidatorTest { + + @Test + public void testRevokedSubkey() throws IOException { + String key = "-----BEGIN PGP ARMORED FILE-----\n" + + "Comment: ASCII Armor added by openpgp-interoperability-test-suite\n" + + "\n" + + "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\n" + + "Y87zSryajDMFXQS0exdvhN4AXDlPlB3Rrkj7CQ==\n" + + "=yTKS\n" + + "-----END PGP ARMORED FILE-----\n"; + + PGPPublicKeyRing keyRing = PGPainless.readKeyRing().publicKeyRing(key); + PGPPublicKeyRing validated = KeyRingValidator.validate(keyRing, PGPainless.getPolicy()); + + Iterator keys = validated.getPublicKeys(); + assertFalse(keys.next().hasRevocation()); + assertTrue(keys.next().hasRevocation()); + } + + @Test + public void badSignatureTest() throws IOException { + String key = "-----BEGIN PGP PUBLIC KEY BLOCK-----\n" + + "\n" + + "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\n" + + "bVrj5KjhX2PVNEJd3XZRzaXZE2Z/MQ==\n" + + "=6+l9\n" + + "-----END PGP PUBLIC KEY BLOCK-----\n"; + PGPPublicKeyRing publicKeys = PGPainless.readKeyRing().publicKeyRing(key); + PGPPublicKeyRing validated = KeyRingValidator.validate(publicKeys, PGPainless.getPolicy()); + // CHECKSTYLE:OFF + System.out.println(ArmorUtils.toAsciiArmoredString(validated)); + // CHECKSTYLE:ON + } + + @Test + public void unboundSubkey() throws IOException { + String key = "-----BEGIN PGP PUBLIC KEY BLOCK-----\n" + + "\n" + + "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\n" + + "=T4QR\n" + + "-----END PGP PUBLIC KEY BLOCK-----\n"; + + PGPPublicKeyRing publicKeys = PGPainless.readKeyRing().publicKeyRing(key); + PGPPublicKey unbound = CollectionUtils.iteratorToList(publicKeys.getPublicKeys()).get(2); + assertNotNull(unbound); + + Date validationDate = TestUtils.getUTCDate("2019-10-15 10:18:26 UTC"); + KeyRingInfo info = new KeyRingInfo(publicKeys, validationDate); + for (PGPPublicKey publicKey : publicKeys) { + if (publicKey != unbound) { + assertTrue(info.isKeyValidlyBound(publicKey.getKeyID())); + } else { + assertFalse(info.isKeyValidlyBound(publicKey.getKeyID())); + } + } + } + + @Test + public void expired() throws IOException { + String key = "-----BEGIN PGP PUBLIC KEY BLOCK-----\n" + + "\n" + + "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\n" + + "=LxAY\n" + + "-----END PGP PUBLIC KEY BLOCK-----\n"; + + PGPPublicKeyRing publicKeys = PGPainless.readKeyRing().publicKeyRing(key); + PGPPublicKeyRing validated = KeyRingValidator.validate(publicKeys, PGPainless.getPolicy()); + } +} diff --git a/pgpainless-core/src/test/java/org/pgpainless/key/generation/GenerateKeyWithAdditionalUserIdTest.java b/pgpainless-core/src/test/java/org/pgpainless/key/generation/GenerateKeyWithAdditionalUserIdTest.java index 5d359fd3..c60b0258 100644 --- a/pgpainless-core/src/test/java/org/pgpainless/key/generation/GenerateKeyWithAdditionalUserIdTest.java +++ b/pgpainless-core/src/test/java/org/pgpainless/key/generation/GenerateKeyWithAdditionalUserIdTest.java @@ -61,7 +61,7 @@ public class GenerateKeyWithAdditionalUserIdTest { .build(); PGPPublicKeyRing publicKeys = KeyRingUtils.publicKeyRingFrom(secretKeys); - JUtils.assertEquals(expiration.getTime(), PGPainless.inspectKeyRing(publicKeys).getExpirationDate().getTime(),2000); + JUtils.assertEquals(expiration.getTime(), PGPainless.inspectKeyRing(publicKeys).getPrimaryKeyExpirationDate().getTime(),2000); Iterator userIds = publicKeys.getPublicKey().getUserIDs(); assertEquals("primary@user.id", userIds.next()); diff --git a/pgpainless-core/src/test/java/org/pgpainless/key/info/KeyRingInfoTest.java b/pgpainless-core/src/test/java/org/pgpainless/key/info/KeyRingInfoTest.java index e54f0df3..9b246949 100644 --- a/pgpainless-core/src/test/java/org/pgpainless/key/info/KeyRingInfoTest.java +++ b/pgpainless-core/src/test/java/org/pgpainless/key/info/KeyRingInfoTest.java @@ -34,6 +34,7 @@ import org.pgpainless.algorithm.PublicKeyAlgorithm; import org.pgpainless.key.TestKeys; import org.pgpainless.key.protection.UnprotectedKeysProtector; import org.pgpainless.key.util.KeyRingUtils; +import org.pgpainless.util.ArmorUtils; import org.pgpainless.util.Passphrase; public class KeyRingInfoTest { @@ -67,8 +68,8 @@ public class KeyRingInfoTest { assertEquals(TestKeys.EMIL_CREATION_DATE, sInfo.getCreationDate()); assertEquals(TestKeys.EMIL_CREATION_DATE, pInfo.getCreationDate()); - assertNull(sInfo.getExpirationDate()); - assertNull(pInfo.getExpirationDate()); + assertNull(sInfo.getPrimaryKeyExpirationDate()); + assertNull(pInfo.getPrimaryKeyExpirationDate()); assertEquals(TestKeys.EMIL_CREATION_DATE.getTime(), sInfo.getLastModified().getTime(), 50); assertEquals(TestKeys.EMIL_CREATION_DATE.getTime(), pInfo.getLastModified().getTime(), 50); @@ -76,6 +77,9 @@ public class KeyRingInfoTest { assertNull(pInfo.getRevocationDate()); Date revocationDate = new Date(); PGPSecretKeyRing revoked = PGPainless.modifyKeyRing(secretKeys).revoke(new UnprotectedKeysProtector()).done(); + // CHECKSTYLE:OFF + System.out.println(ArmorUtils.toAsciiArmoredString(revoked)); + // CHECKSTYLE:ON KeyRingInfo rInfo = PGPainless.inspectKeyRing(revoked); assertNotNull(rInfo.getRevocationDate()); assertEquals(revocationDate.getTime(), rInfo.getRevocationDate().getTime(), 1000); diff --git a/pgpainless-core/src/test/java/org/pgpainless/key/info/UserIdRevocationTest.java b/pgpainless-core/src/test/java/org/pgpainless/key/info/UserIdRevocationTest.java index 7df59ec4..27632eb9 100644 --- a/pgpainless-core/src/test/java/org/pgpainless/key/info/UserIdRevocationTest.java +++ b/pgpainless-core/src/test/java/org/pgpainless/key/info/UserIdRevocationTest.java @@ -45,7 +45,7 @@ import org.pgpainless.key.protection.PasswordBasedSecretKeyRingProtector; import org.pgpainless.key.protection.SecretKeyRingProtector; import org.pgpainless.key.protection.UnprotectedKeysProtector; import org.pgpainless.key.util.RevocationAttributes; -import org.pgpainless.key.util.SignatureUtils; +import org.pgpainless.signature.SignatureUtils; public class UserIdRevocationTest { diff --git a/pgpainless-core/src/test/java/org/pgpainless/key/modification/ChangeExpirationTest.java b/pgpainless-core/src/test/java/org/pgpainless/key/modification/ChangeExpirationTest.java index e0f02e5e..bb3371ff 100644 --- a/pgpainless-core/src/test/java/org/pgpainless/key/modification/ChangeExpirationTest.java +++ b/pgpainless-core/src/test/java/org/pgpainless/key/modification/ChangeExpirationTest.java @@ -45,17 +45,17 @@ public class ChangeExpirationTest { PGPSecretKeyRing secretKeys = TestKeys.getEmilSecretKeyRing(); KeyRingInfo sInfo = PGPainless.inspectKeyRing(secretKeys); - assertNull(sInfo.getExpirationDate()); - assertNull(sInfo.getExpirationDate(subKeyFingerprint)); + assertNull(sInfo.getPrimaryKeyExpirationDate()); + assertNull(sInfo.getSubkeyExpirationDate(subKeyFingerprint)); Date date = new Date(1606493432000L); secretKeys = PGPainless.modifyKeyRing(secretKeys) .setExpirationDate(date, new UnprotectedKeysProtector()).done(); sInfo = PGPainless.inspectKeyRing(secretKeys); - assertNotNull(sInfo.getExpirationDate()); - assertEquals(date.getTime(), sInfo.getExpirationDate().getTime()); + assertNotNull(sInfo.getPrimaryKeyExpirationDate()); + assertEquals(date.getTime(), sInfo.getPrimaryKeyExpirationDate().getTime()); // subkey unchanged - assertNull(sInfo.getExpirationDate(subKeyFingerprint)); + assertNull(sInfo.getSubkeyExpirationDate(subKeyFingerprint)); // We need to wait for one second as OpenPGP signatures have coarse-grained (up to a second) // accuracy. Creating two signatures within a short amount of time will make the second one @@ -66,8 +66,8 @@ public class ChangeExpirationTest { .setExpirationDate(null, new UnprotectedKeysProtector()).done(); sInfo = PGPainless.inspectKeyRing(secretKeys); - assertNull(sInfo.getExpirationDate()); - assertNull(sInfo.getExpirationDate(subKeyFingerprint)); + assertNull(sInfo.getPrimaryKeyExpirationDate()); + assertNull(sInfo.getSubkeyExpirationDate(subKeyFingerprint)); } @Test @@ -75,16 +75,16 @@ public class ChangeExpirationTest { PGPSecretKeyRing secretKeys = TestKeys.getEmilSecretKeyRing(); KeyRingInfo sInfo = PGPainless.inspectKeyRing(secretKeys); - assertNull(sInfo.getExpirationDate(subKeyFingerprint)); - assertNull(sInfo.getExpirationDate()); + assertNull(sInfo.getSubkeyExpirationDate(subKeyFingerprint)); + assertNull(sInfo.getPrimaryKeyExpirationDate()); Date date = new Date(1606493432000L); secretKeys = PGPainless.modifyKeyRing(secretKeys) .setExpirationDate(subKeyFingerprint, date, new UnprotectedKeysProtector()).done(); sInfo = PGPainless.inspectKeyRing(secretKeys); - assertNotNull(sInfo.getExpirationDate(subKeyFingerprint)); - assertEquals(date.getTime(), sInfo.getExpirationDate(subKeyFingerprint).getTime()); - assertNull(sInfo.getExpirationDate()); + assertNotNull(sInfo.getSubkeyExpirationDate(subKeyFingerprint)); + assertEquals(date.getTime(), sInfo.getSubkeyExpirationDate(subKeyFingerprint).getTime()); + assertNull(sInfo.getPrimaryKeyExpirationDate()); // We need to wait for one second as OpenPGP signatures have coarse-grained (up to a second) // accuracy. Creating two signatures within a short amount of time will make the second one @@ -95,7 +95,7 @@ public class ChangeExpirationTest { .setExpirationDate(subKeyFingerprint, null, new UnprotectedKeysProtector()).done(); sInfo = PGPainless.inspectKeyRing(secretKeys); - assertNull(sInfo.getExpirationDate(subKeyFingerprint)); - assertNull(sInfo.getExpirationDate()); + assertNull(sInfo.getSubkeyExpirationDate(subKeyFingerprint)); + assertNull(sInfo.getPrimaryKeyExpirationDate()); } } diff --git a/pgpainless-core/src/test/java/org/pgpainless/key/modification/OldSignatureSubpacketsArePreservedOnNewSig.java b/pgpainless-core/src/test/java/org/pgpainless/key/modification/OldSignatureSubpacketsArePreservedOnNewSig.java index 550d6beb..23b346e8 100644 --- a/pgpainless-core/src/test/java/org/pgpainless/key/modification/OldSignatureSubpacketsArePreservedOnNewSig.java +++ b/pgpainless-core/src/test/java/org/pgpainless/key/modification/OldSignatureSubpacketsArePreservedOnNewSig.java @@ -47,7 +47,7 @@ public class OldSignatureSubpacketsArePreservedOnNewSig { OpenPgpV4Fingerprint subkeyFingerprint = new OpenPgpV4Fingerprint(PGPainless.inspectKeyRing(secretKeys).getPublicKeys().get(1)); - PGPSignature oldSignature = PGPainless.inspectKeyRing(secretKeys).getLatestValidSelfOrBindingSignatureOnKey(subkeyFingerprint); + PGPSignature oldSignature = PGPainless.inspectKeyRing(secretKeys).getCurrentSubkeyBindingSignature(subkeyFingerprint.getKeyId()); PGPSignatureSubpacketVector oldPackets = oldSignature.getHashedSubPackets(); assertEquals(0, oldPackets.getKeyExpirationTime()); @@ -56,7 +56,7 @@ public class OldSignatureSubpacketsArePreservedOnNewSig { secretKeys = PGPainless.modifyKeyRing(secretKeys) .setExpirationDate(subkeyFingerprint, new Date(), new UnprotectedKeysProtector()) .done(); - PGPSignature newSignature = PGPainless.inspectKeyRing(secretKeys).getLatestValidSelfOrBindingSignatureOnKey(subkeyFingerprint); + PGPSignature newSignature = PGPainless.inspectKeyRing(secretKeys).getCurrentSubkeyBindingSignature(subkeyFingerprint.getKeyId()); PGPSignatureSubpacketVector newPackets = newSignature.getHashedSubPackets(); assertNotEquals(0, newPackets.getKeyExpirationTime()); diff --git a/pgpainless-core/src/test/java/org/pgpainless/key/modification/RevokeKeyWithoutPreferredAlgorithmsOnPrimaryKey.java b/pgpainless-core/src/test/java/org/pgpainless/key/modification/RevokeKeyWithoutPreferredAlgorithmsOnPrimaryKey.java index c5983fa8..d0e9207e 100644 --- a/pgpainless-core/src/test/java/org/pgpainless/key/modification/RevokeKeyWithoutPreferredAlgorithmsOnPrimaryKey.java +++ b/pgpainless-core/src/test/java/org/pgpainless/key/modification/RevokeKeyWithoutPreferredAlgorithmsOnPrimaryKey.java @@ -131,9 +131,9 @@ public class RevokeKeyWithoutPreferredAlgorithmsOnPrimaryKey { secretKeys = modify.done(); KeyRingInfo info = PGPainless.inspectKeyRing(secretKeys); - assertEquals(expirationDate.getTime(), info.getExpirationDate().getTime(), 1000); + assertEquals(expirationDate.getTime(), info.getPrimaryKeyExpirationDate().getTime(), 1000); for (OpenPgpV4Fingerprint fingerprint : fingerprintList) { - assertEquals(expirationDate.getTime(), info.getExpirationDate(fingerprint).getTime(), 1000); + assertEquals(expirationDate.getTime(), info.getSubkeyExpirationDate(fingerprint).getTime(), 1000); } } } diff --git a/pgpainless-core/src/test/java/org/pgpainless/signature/BindingSignatureSubpacketsTest.java b/pgpainless-core/src/test/java/org/pgpainless/signature/BindingSignatureSubpacketsTest.java new file mode 100644 index 00000000..453aed79 --- /dev/null +++ b/pgpainless-core/src/test/java/org/pgpainless/signature/BindingSignatureSubpacketsTest.java @@ -0,0 +1,1945 @@ +/* + * Copyright 2021 Paul Schaub. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.pgpainless.signature; + +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.fail; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.util.Date; + +import org.bouncycastle.openpgp.PGPPublicKeyRing; +import org.bouncycastle.openpgp.PGPSignature; +import org.junit.jupiter.api.Test; +import org.pgpainless.PGPainless; +import org.pgpainless.policy.Policy; +import org.pgpainless.util.BCUtil; + +/** + * Explores how subpackets on binding sigs are handled. + * + * @see Sequoia Test Suite + */ +public class BindingSignatureSubpacketsTest { + + private static final String sig = "-----BEGIN PGP SIGNATURE-----\n" + + "\n" + + "wsE7BAABCgBvBYJgW1JVCRB8L6pN+Tw3skcUAAAAAAAeACBzYWx0QG5vdGF0aW9u\n" + + "cy5zZXF1b2lhLXBncC5vcmcH3ij767MU02jy4exH0wm35AXIhAJQPhlGjPNbE43+\n" + + "2hYhBB3c4V8JIXzuLzs3YHwvqk35PDeyAAAzdAv+NPRZUqmEokyI8fv75HqZyazl\n" + + "yLuOOEt7r3mc+riFPNgvM3HCkTtdwdTrv2RY4tOabfaNqEKAHJsnMqB76ikREHw0\n" + + "1xBDqXdqdzEP1++mMyf6MxleZreQSGjni6NemDwQ72/z5Mdw2bGX/6TLpjlBbQCa\n" + + "OUzo8QvnhUCl1sFtR3CJWodIrhsjBIQsdGaFgTQ+yOQT+Zb4sLHT0a+To53+FGdR\n" + + "P5YOqePYq/ZISmp/TXotWXXN3fBt2BzvioXBIYXi4lURqxp+nuxRLRShEMnSh2u8\n" + + "Cs8qd+bbp9yRyL8jf70729nqt0amV5OCcR+ApuP1iXuSlOdCdqPH5OkzG+kNfBHe\n" + + "qkG+cjt0UTrbtsWGvypul13urFOUdizX+H/oVMWmOFKsmQYCKaWtv5FHTnrriT59\n" + + "Ul5W6BXd5fFnxMhzAyo5+cZWgA99e1zFblBGR0OS8E707hFozCp7XP3bFk2Q4zHY\n" + + "lymkgLgvaY5P+BVqH1ON73u6bjj0d2xbaQ6R3HNu\n" + + "=bVN/\n" + + "-----END PGP SIGNATURE-----\n"; + private static final String data = "Hello World :)"; + + private Date validationDate = new Date(); + private Policy policy = PGPainless.getPolicy(); + + @Test + public void baseCase() throws IOException { + String key = "-----BEGIN PGP PUBLIC KEY BLOCK-----\n" + + "\n" + + "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\n" + + "sZ6RjG6Y7mJDqICYz4LiTtCtimyJOo/Dh6rLiDg=\n" + + "=CNax\n" + + "-----END PGP PUBLIC KEY BLOCK-----"; + expectSignatureValidationSucceeds(key, "Base case. Is valid."); + } + + @Test + public void subkeyBindingIssuerFpOnly() throws IOException { + String key = "-----BEGIN PGP PUBLIC KEY BLOCK-----\n" + + "\n" + + "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\n" + + "eOIzIauuzHYWv31/BYf3L4WftQ==\n" + + "=FtMb\n" + + "-----END PGP PUBLIC KEY BLOCK-----\n"; + expectSignatureValidationSucceeds(key, "Interoperability concern."); + } + + @Test + public void subkeyBindingIssuerV6IssuerFp() throws IOException { + String key = "-----BEGIN PGP PUBLIC KEY BLOCK-----\n" + + "\n" + + "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\n" + + "=+yIH\n" + + "-----END PGP PUBLIC KEY BLOCK-----\n"; + expectSignatureValidationSucceeds(key, "Interoperability concern"); + } + + @Test + public void subkeyBindingIssuerFakeIssuer() throws IOException { + String key = "-----BEGIN PGP PUBLIC KEY BLOCK-----\n" + + "\n" + + "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\n" + + "7BwZPgxUwqx50GLCYPQJsg==\n" + + "=t3u5\n" + + "-----END PGP PUBLIC KEY BLOCK-----"; + expectSignatureValidationSucceeds(key, "Interoperability concern."); + } + + @Test + public void subkeyBindingFakeIssuerIssuer() throws IOException { + String key = "-----BEGIN PGP PUBLIC KEY BLOCK-----\n" + + "\n" + + "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\n" + + "hmGW5T4PCz3rUZR45xjExQ==\n" + + "=xBhS\n" + + "-----END PGP PUBLIC KEY BLOCK-----\n"; + expectSignatureValidationSucceeds(key, "Interop concern"); + } + + @Test + public void subkeyBindingFakeIssuer() throws IOException { + String key = "-----BEGIN PGP PUBLIC KEY BLOCK-----\n" + + "\n" + + "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\n" + + "dy00NMCZ\n" + + "=qPXy\n" + + "-----END PGP PUBLIC KEY BLOCK-----"; + expectSignatureValidationSucceeds(key, "fake issuers do not throw us off here."); + } + + @Test + public void subkeyBindingNoIssuer() throws IOException { + String key = "-----BEGIN PGP PUBLIC KEY BLOCK-----\n" + + "\n" + + "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\n" + + "=WUej\n" + + "-----END PGP PUBLIC KEY BLOCK-----\n"; + expectSignatureValidationSucceeds(key, "subkey binding sig does not need issuer"); + } + + @Test + public void unknownSubpacketHashed() throws IOException { + String key = "-----BEGIN PGP PUBLIC KEY BLOCK-----\n" + + "\n" + + "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\n" + + "=wp4q\n" + + "-----END PGP PUBLIC KEY BLOCK-----\n"; + expectSignatureValidationSucceeds(key, "Unknown subpackets are okay in hashed area"); + } + + @Test + public void subkeyBindingUnknownCriticalSubpacket() throws IOException { + String key = "-----BEGIN PGP PUBLIC KEY BLOCK-----\n" + + "\n" + + "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\n" + + "=ZDQz\n" + + "-----END PGP PUBLIC KEY BLOCK-----"; + expectSignatureValidationFails(key, "Unknown critical subpacket in hashed area invalidates signature"); + } + + @Test + public void subkeyBindingUnknownSubpacketUnhashed() throws IOException { + String key = "-----BEGIN PGP PUBLIC KEY BLOCK-----\n" + + "\n" + + "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\n" + + "=Zn8I\n" + + "-----END PGP PUBLIC KEY BLOCK-----"; + expectSignatureValidationSucceeds(key, "Unknown subpackets may be allowed in unhashed area."); + } + + @Test + public void subkeyBindingUnknownCriticalSubpacketUnhashed() throws IOException { + String key = "-----BEGIN PGP PUBLIC KEY BLOCK-----\n" + + "\n" + + "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\n" + + "=tNHa\n" + + "-----END PGP PUBLIC KEY BLOCK-----\n"; + expectSignatureValidationSucceeds(key, "Critical unknown subpacket is okay in unhashed area."); + } + + @Test + public void subkeyBindingUnknownNotationHashed() throws IOException { + String key = "-----BEGIN PGP PUBLIC KEY BLOCK-----\n" + + "\n" + + "xsDNBF2lnPIBDAC5cL9PQoQLTMuhjbYvb4Ncuuo0bfmgPRFywX53jPhoFf4Zg6mv\n" + + "/seOXpgecTdOcVttfzC8ycIKrt3aQTiwOG/ctaR4Bk/t6ayNFfdUNxHWk4WCKzdz\n" + + "/56fW2O0F23qIRd8UUJp5IIlN4RDdRCtdhVQIAuzvp2oVy/LaS2kxQoKvph/5pQ/\n" + + "5whqsyroEWDJoSV0yOb25B/iwk/pLUFoyhDG9bj0kIzDxrEqW+7Ba8nocQlecMF3\n" + + "X5KMN5kp2zraLv9dlBBpWW43XktjcCZgMy20SouraVma8Je/ECwUWYUiAZxLIlMv\n" + + "9CurEOtxUw6N3RdOtLmYZS9uEnn5y1UkF88o8Nku890uk6BrewFzJyLAx5wRZ4F0\n" + + "qV/yq36UWQ0JB/AUGhHVPdFf6pl6eaxBwT5GXvbBUibtf8YI2og5RsgTWtXfU7eb\n" + + "SGXrl5ZMpbA6mbfhd0R8aPxWfmDWiIOhBufhMCvUHh1sApMKVZnvIff9/0Dca3wb\n" + + "vLIwa3T4CyshfT0AEQEAAc0hQm9iIEJhYmJhZ2UgPGJvYkBvcGVucGdwLmV4YW1w\n" + + "bGU+wsFIBBMBCgB8BYJfarhYAgsJCRD7/MgqAV5zMEcUAAAAAAAeACBzYWx0QG5v\n" + + "dGF0aW9ucy5zZXF1b2lhLXBncC5vcmd5hAoPaQrtdA/HBKZHkjhnFTKBFNIXmLAP\n" + + "fbZ9NyFxxgMVCAoCmwECHgEWIQTRpm4aI7GCyZgPeIz7/MgqAV5zMAAAzKIL/iS7\n" + + "Q7/5szuht5ilCHaE0c6R78YVqmQol4M4DgtRWfjatrYY1hD8lZuaYtAQhvGfG1Ez\n" + + "uQrfFJGewPhsSFXyKYEOFEzLA6K2oTpb1JYzq0p1T+xLH+LJoPm/fP/bMKCKE3gn\n" + + "6AT2zUaEgneeTt9OHZKv0Ie2u2+nrQL9b6jR2VkqOGOnEuWdBfdlUqqvN6QETTHG\n" + + "r4mfJ+t7aREgcQX4NXg6n1YKA1Rc1vJWYQ/RyF1OtyxgenvJLpD1PWDyATT1B9Fo\n" + + "hRzZrdX6iTL2SlBMzwNOfsphnFs2cqRMx3Iaha+5k0nqtbOBkYd02hueGhSSqTcb\n" + + "5fnciAqwW5m+rjBqcXyc+RcmpSAqxNnNbi5K2geO2SnWD221QG5E1sAk1KBx48/N\n" + + "Wh8obYlLavtET0+K28aWJern3jMeK93JIu4g8Kswdz5Zitw9qcTYcBmZfYTe+wdE\n" + + "dMHWNtEYYL1VH8jQ9CZ+rygV5OMStyIc57UmrlP+jR4fDQDtBOWqI6uIfrSoRs7A\n" + + "zQRdpZzyAQwA1jC/XGxjK6ddgrRfW9j+s/U00++EvIsgTs2kr3Rg0GP7FLWV0YNt\n" + + "R1mpl55/bEl7yAxCDTkOgPUMXcaKlnQh6zrlt6H53mF6Bvs3inOHQvOsGtU0dqvb\n" + + "1vkTF0juLiJgPlM7pWv+pNQ6IA39vKoQsTMBv4v5vYNXP9GgKbg8inUNT17BxzZY\n" + + "Hfw5+q63ectgDm2on1e8CIRCZ76oBVwzdkVxoy3gjh1eENlk2D4P0uJNZzF1Q8GV\n" + + "67yLANGMCDICE/OkWn6daipYDzW4iJQtYPUWP4hWhjdm+CK+hg6IQUEn2Vtvi16D\n" + + "2blRP8BpUNNa4fNuylWVuJV76rIHvsLZ1pbM3LHpRgE8s6jivS3Rz3WRs0TmWCNn\n" + + "vHPqWizQ3VTy+r3UQVJ5AmhJDrZdZq9iaUIuZ01PoE1+CHiJwuxPtWvVAxf2POcm\n" + + "1M/F1fK1J0e+lKlQuyonTXqXR22Y41wrfP2aPk3nPSTW2DUAf3vRMZg57ZpRxLEh\n" + + "EMxcM4/LMR+PABEBAAHCw2gEGAEKApwFgl9quFgJEPv8yCoBXnMwKxQAAAAAAB0A\n" + + "BXVua25vd25AdGVzdHMuc2VxdW9pYS1wZ3Aub3JndmFsdWVHFAAAAAAAHgAgc2Fs\n" + + "dEBub3RhdGlvbnMuc2VxdW9pYS1wZ3Aub3JnAbTJJ9IDY5JEWaVrDS1Qlnn3QpxV\n" + + "UtUI3rQNU3O3BpsCmwLBPKAEGQEKAG8Fgl9quFgJEHwvqk35PDeyRxQAAAAAAB4A\n" + + "IHNhbHRAbm90YXRpb25zLnNlcXVvaWEtcGdwLm9yZ2UwGuyOk+sdUBlMryGEd2/H\n" + + "6YxrWQnpOSam0LNoQ4NrFiEEHdzhXwkhfO4vOzdgfC+qTfk8N7IAAAIlC/9cLfoP\n" + + "VEBX1J0BZUNm77G/yUgr902+LIsnYxizu3AXEwv1ihy7Dwmtj4PbbnqzFbq09HYW\n" + + "x3C7PWMWWalCnUMfbOE3vh9oWShsOS2KwEinMuuGopcHH6c0h7BR772y31HV2Fem\n" + + "9YCZ4b56LvgAk7mGCrI11S58Zh6RwwqadyTSmsbefRstjuHlgA2lxwuQN4c/9n2w\n" + + "B5Qkwx0ZZ7s4t4IDUFyWHTG20G+/oRFNav9y3CLyhRvgXJ69IvPre/U9eRZ9Zf5e\n" + + "M453dxP/i8PMKx67j8UFg4yAFQWE5yM2TV2tq4FGINP/7gyFd6F+bwrFwRvwNZfv\n" + + "0tL94ZJcqC9mW85MFyKwUOx/XMb3x+4Jw7gnYyYEmpgY5/5P4zH+HzKpKNO0JWgh\n" + + "L3lTmgJgS4E/X2h3PTqxN7uj93fsWBx9lFtziiIofo9n6x1XL+vFeptZI4b9EK4M\n" + + "Dqj6vD1Mq1WI8wW7Ayc42KupiprxEsMMR0EsEcTbJT6fLbXSgEIarSK7/r0WIQTR\n" + + "pm4aI7GCyZgPeIz7/MgqAV5zMAAADa0L/i7eOsYcA1xBMymhgqJ/r45AoG1KOD19\n" + + "KK6OVu0ywZcKXq1dAI8qDwlUsUtrSDCdwN3AGTEmeJug0Gq/DYoEiTNDRsI3uaTf\n" + + "OViXLds3NUXhyd9HWhS53Q8Mcw9DZvphFrp15ogM+Sq9+WQ7flyuNitzrkOJuqD8\n" + + "OJZ9rgQRJl0Cn9T17dZYrpMlBDMrMe2oFI9vC/kBgcMrPf49gTT0kJwBx84PQ8ye\n" + + "7VejWW6Vjyi15H7u8Qf9abitNxHedG4iwuC7AOooyi9H4XvFV97oIxX9d16DmNn/\n" + + "2kAYbxZ9QBvYUeak5kx4ffFKTN+KmMe8JV8smfWHEuMUEQc0c55oWyyr3Thx7ToJ\n" + + "22nh8ngVS2mdrqlGBeHyYyY+dzEXvCgWagivhmgt5M+xyrNMUBdIueJXaP6RusdI\n" + + "SvztVfD/5GR84rHOGcjBdimx0S+pUCLPij0/wP7QwmAvJ0pNxi3xcD7OjDHumo67\n" + + "mvv6iF1rzSs0UQY73pmGNt20TdHYGaLD+A==\n" + + "=B81E\n" + + "-----END PGP PUBLIC KEY BLOCK-----\n"; + expectSignatureValidationSucceeds(key, "Unknown notation is okay in subkey binding sig."); + } + + @Test + public void subkeyBindingCriticalUnknownNotationHashed() throws IOException { + String key = "-----BEGIN PGP PUBLIC KEY BLOCK-----\n" + + "\n" + + "xsDNBF2lnPIBDAC5cL9PQoQLTMuhjbYvb4Ncuuo0bfmgPRFywX53jPhoFf4Zg6mv\n" + + "/seOXpgecTdOcVttfzC8ycIKrt3aQTiwOG/ctaR4Bk/t6ayNFfdUNxHWk4WCKzdz\n" + + "/56fW2O0F23qIRd8UUJp5IIlN4RDdRCtdhVQIAuzvp2oVy/LaS2kxQoKvph/5pQ/\n" + + "5whqsyroEWDJoSV0yOb25B/iwk/pLUFoyhDG9bj0kIzDxrEqW+7Ba8nocQlecMF3\n" + + "X5KMN5kp2zraLv9dlBBpWW43XktjcCZgMy20SouraVma8Je/ECwUWYUiAZxLIlMv\n" + + "9CurEOtxUw6N3RdOtLmYZS9uEnn5y1UkF88o8Nku890uk6BrewFzJyLAx5wRZ4F0\n" + + "qV/yq36UWQ0JB/AUGhHVPdFf6pl6eaxBwT5GXvbBUibtf8YI2og5RsgTWtXfU7eb\n" + + "SGXrl5ZMpbA6mbfhd0R8aPxWfmDWiIOhBufhMCvUHh1sApMKVZnvIff9/0Dca3wb\n" + + "vLIwa3T4CyshfT0AEQEAAc0hQm9iIEJhYmJhZ2UgPGJvYkBvcGVucGdwLmV4YW1w\n" + + "bGU+wsFIBBMBCgB8BYJfarhYAgsJCRD7/MgqAV5zMEcUAAAAAAAeACBzYWx0QG5v\n" + + "dGF0aW9ucy5zZXF1b2lhLXBncC5vcmd5hAoPaQrtdA/HBKZHkjhnFTKBFNIXmLAP\n" + + "fbZ9NyFxxgMVCAoCmwECHgEWIQTRpm4aI7GCyZgPeIz7/MgqAV5zMAAAzKIL/iS7\n" + + "Q7/5szuht5ilCHaE0c6R78YVqmQol4M4DgtRWfjatrYY1hD8lZuaYtAQhvGfG1Ez\n" + + "uQrfFJGewPhsSFXyKYEOFEzLA6K2oTpb1JYzq0p1T+xLH+LJoPm/fP/bMKCKE3gn\n" + + "6AT2zUaEgneeTt9OHZKv0Ie2u2+nrQL9b6jR2VkqOGOnEuWdBfdlUqqvN6QETTHG\n" + + "r4mfJ+t7aREgcQX4NXg6n1YKA1Rc1vJWYQ/RyF1OtyxgenvJLpD1PWDyATT1B9Fo\n" + + "hRzZrdX6iTL2SlBMzwNOfsphnFs2cqRMx3Iaha+5k0nqtbOBkYd02hueGhSSqTcb\n" + + "5fnciAqwW5m+rjBqcXyc+RcmpSAqxNnNbi5K2geO2SnWD221QG5E1sAk1KBx48/N\n" + + "Wh8obYlLavtET0+K28aWJern3jMeK93JIu4g8Kswdz5Zitw9qcTYcBmZfYTe+wdE\n" + + "dMHWNtEYYL1VH8jQ9CZ+rygV5OMStyIc57UmrlP+jR4fDQDtBOWqI6uIfrSoRs7A\n" + + "zQRdpZzyAQwA1jC/XGxjK6ddgrRfW9j+s/U00++EvIsgTs2kr3Rg0GP7FLWV0YNt\n" + + "R1mpl55/bEl7yAxCDTkOgPUMXcaKlnQh6zrlt6H53mF6Bvs3inOHQvOsGtU0dqvb\n" + + "1vkTF0juLiJgPlM7pWv+pNQ6IA39vKoQsTMBv4v5vYNXP9GgKbg8inUNT17BxzZY\n" + + "Hfw5+q63ectgDm2on1e8CIRCZ76oBVwzdkVxoy3gjh1eENlk2D4P0uJNZzF1Q8GV\n" + + "67yLANGMCDICE/OkWn6daipYDzW4iJQtYPUWP4hWhjdm+CK+hg6IQUEn2Vtvi16D\n" + + "2blRP8BpUNNa4fNuylWVuJV76rIHvsLZ1pbM3LHpRgE8s6jivS3Rz3WRs0TmWCNn\n" + + "vHPqWizQ3VTy+r3UQVJ5AmhJDrZdZq9iaUIuZ01PoE1+CHiJwuxPtWvVAxf2POcm\n" + + "1M/F1fK1J0e+lKlQuyonTXqXR22Y41wrfP2aPk3nPSTW2DUAf3vRMZg57ZpRxLEh\n" + + "EMxcM4/LMR+PABEBAAHCw2gEGAEKApwFgl9quFgJEPv8yCoBXnMwK5QAAAAAAB0A\n" + + "BXVua25vd25AdGVzdHMuc2VxdW9pYS1wZ3Aub3JndmFsdWVHFAAAAAAAHgAgc2Fs\n" + + "dEBub3RhdGlvbnMuc2VxdW9pYS1wZ3Aub3Jn5XlXi/NwcBQfb3HOaH+MrCwYBFew\n" + + "TELPlTKcOe8Nb+UCmwLBPKAEGQEKAG8Fgl9quFgJEHwvqk35PDeyRxQAAAAAAB4A\n" + + "IHNhbHRAbm90YXRpb25zLnNlcXVvaWEtcGdwLm9yZxsq30hTmOT5b8MspZ6YemCz\n" + + "nlUOQx4ZIGQQ2uZE+0uPFiEEHdzhXwkhfO4vOzdgfC+qTfk8N7IAAHNeC/wKhdsB\n" + + "5AJ+cHp45JG+fIXekquGDiV47GktzwXCA83pd7O1I7sDDf5cDbJ32MPKv6+0m6sE\n" + + "kVNOuW/Ygw6n4D0c7efnuu8FeKUsfaQvZ7GkVxT61p4oRzhiVscH6txekAqJl6oY\n" + + "eNLavoqcWN1zhng8vqfKgGFnngB4s6l0xY0s96aKGx2et5Mq/0ssP5ZXcdfu0VvY\n" + + "DahBM+orKZfA69N/QWC9pxpzBG2my8vRKT4lgvLVDW59jiNB/td7JZKicsIzP/hx\n" + + "wwhvIytb15rBA67PuV71BgirD/Le7Bdvntlvo2mYsSP2UWh5/P273Ty5AfUlsYAg\n" + + "OHFfNlUlHhlFp4kZIcMVtkLf+AXxRHSd+DGiSmG2QPMg2PiLMODiqSY1o0uKGQdx\n" + + "GyR376yOQj2kLT3O0x1kOH2dQlIf8iMT4I2RdalafwMHRWLQLbZL6geA/MzZup8g\n" + + "sU7NN7Nt7fhNNdqaOX5kZwg8gzw8WCHlFvirlCD/ee6ME86Guh7uu2io7GsWIQTR\n" + + "pm4aI7GCyZgPeIz7/MgqAV5zMAAAt1kL/0WT0On1SqkxYhmr982Y9Bokh9mN9d82\n" + + "1W5tWtNFY9p32mn7jMRtmh8IMvo4Xrw7ghETINVCG/a+G0YevI1brus8ll9TGL9J\n" + + "4G3aRjIeTXhQYc0zHjBv+sMgn20bF60osZmpLCwsmhLPVISHeR5UcDsi6H4aZKn+\n" + + "b/RTezY7IAvIdqtK6qT7BeBHOqAvIPml5YDBHOIGir9/enpCwD41MViyP/k/WHyR\n" + + "19MJ3VdOrWwwQcFO2SCfhCRoq05lRnwFz8nL5R0acKZFfRcAboV6MrCYozNknjn6\n" + + "YB6GLh9W51ziBsX7lWfdPYNt+sM8E3HqBk9LNy5hnB9RrZbmlk5MDHEuecc1Zrfl\n" + + "lO7s5SgD/B/oncmoNhSzYbnfOCr8dL3nQQcTvcexgJlNnyOycPj2x27xMMBJRBlE\n" + + "YTZ8gx6i8KhOpWlNOPZbPwyVxyXqgLHqO2WMSEO32mFE6BDwQaJKrF2WEUdqKkUn\n" + + "w37Sn8/HoliO/zNjKtop4L8Q67+ZUKLgsw==\n" + + "=bvvd\n" + + "-----END PGP PUBLIC KEY BLOCK-----\n"; + expectSignatureValidationFails(key, "Critical unknown notation invalidates subkey binding sig."); + } + + @Test + public void subkeyBindingUnknownNotationUnhashed() throws IOException { + String key = "-----BEGIN PGP PUBLIC KEY BLOCK-----\n" + + "\n" + + "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\n" + + "P96G+29jGMdTibE1Ev9xhWaG5CjEOVHZyQ==\n" + + "=mPzA\n" + + "-----END PGP PUBLIC KEY BLOCK-----\n"; + expectSignatureValidationSucceeds(key, "Unknown notation is okay in unhashed area."); + } + + @Test + public void subkeyBindingCriticalUnknownNotationUnhashed() throws IOException { + String key = "-----BEGIN PGP PUBLIC KEY BLOCK-----\n" + + "\n" + + "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\n" + + "3bnuOrz+r1DT6AnIg0odhYJrBtZi83Ll2Q==\n" + + "=ndXK\n" + + "-----END PGP PUBLIC KEY BLOCK-----\n"; + expectSignatureValidationSucceeds(key, "Critical unknown notation is okay in unhashed area."); + } + + @Test + public void subkeyBindingBackSigFakeBackSig() throws IOException { + String key = "-----BEGIN PGP PUBLIC KEY BLOCK-----\n" + + "\n" + + "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\n" + + "5d4KXVKpFMKWWMg=\n" + + "=YAxx\n" + + "-----END PGP PUBLIC KEY BLOCK-----\n"; + expectSignatureValidationSucceeds(key, "Back-sig, fake back-sig should succeed to verify"); + } + + @Test + public void subkeyBindingFakeBackSigBackSig() throws IOException { + String key = "-----BEGIN PGP PUBLIC KEY BLOCK-----\n" + + "\n" + + "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\n" + + "zakcV1gD8cov3qM=\n" + + "=IKlB\n" + + "-----END PGP PUBLIC KEY BLOCK-----\n"; + expectSignatureValidationSucceeds(key, "Fake back-sig, back-sig should succeed to verify."); + } + + @Test + public void primaryBindingIssuerFpOnly() throws IOException { + String key = "-----BEGIN PGP PUBLIC KEY BLOCK-----\n" + + "\n" + + "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\n" + + "tGjpZWhbyGMEJRKO5gDGIQCdaQ==\n" + + "=l8cI\n" + + "-----END PGP PUBLIC KEY BLOCK-----"; + expectSignatureValidationSucceeds(key, "issuer fp is enough"); + } + + @Test + public void primaryBindingIssuerV6IssuerFp() throws IOException { + String key = "-----BEGIN PGP PUBLIC KEY BLOCK-----\n" + + "\n" + + "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\n" + + "=dNVx\n" + + "-----END PGP PUBLIC KEY BLOCK-----\n"; + expectSignatureValidationSucceeds(key, "interop"); + } + + @Test + public void primaryBindingIssuerFakeIssuer() throws IOException { + String key = "-----BEGIN PGP PUBLIC KEY BLOCK-----\n" + + "\n" + + "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\n" + + "Id487EfRjp273t9x/SACYw==\n" + + "=WHjR\n" + + "-----END PGP PUBLIC KEY BLOCK-----\n"; + expectSignatureValidationSucceeds(key, "interop"); + } + + @Test + public void primaryBindingFakeIssuerIssuer() throws IOException { + String key = "-----BEGIN PGP PUBLIC KEY BLOCK-----\n" + + "\n" + + "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\n" + + "MbE6iM6nzl9szxdfZwM9fA==\n" + + "=9cqv\n" + + "-----END PGP PUBLIC KEY BLOCK-----\n"; + expectSignatureValidationSucceeds(key, "interop"); + } + + @Test + public void primaryBindingFakeIssuer() throws IOException { + String key = "-----BEGIN PGP PUBLIC KEY BLOCK-----\n" + + "\n" + + "xsDNBF2lnPIBDAC5cL9PQoQLTMuhjbYvb4Ncuuo0bfmgPRFywX53jPhoFf4Zg6mv\n" + + "/seOXpgecTdOcVttfzC8ycIKrt3aQTiwOG/ctaR4Bk/t6ayNFfdUNxHWk4WCKzdz\n" + + "/56fW2O0F23qIRd8UUJp5IIlN4RDdRCtdhVQIAuzvp2oVy/LaS2kxQoKvph/5pQ/\n" + + "5whqsyroEWDJoSV0yOb25B/iwk/pLUFoyhDG9bj0kIzDxrEqW+7Ba8nocQlecMF3\n" + + "X5KMN5kp2zraLv9dlBBpWW43XktjcCZgMy20SouraVma8Je/ECwUWYUiAZxLIlMv\n" + + "9CurEOtxUw6N3RdOtLmYZS9uEnn5y1UkF88o8Nku890uk6BrewFzJyLAx5wRZ4F0\n" + + "qV/yq36UWQ0JB/AUGhHVPdFf6pl6eaxBwT5GXvbBUibtf8YI2og5RsgTWtXfU7eb\n" + + "SGXrl5ZMpbA6mbfhd0R8aPxWfmDWiIOhBufhMCvUHh1sApMKVZnvIff9/0Dca3wb\n" + + "vLIwa3T4CyshfT0AEQEAAc0hQm9iIEJhYmJhZ2UgPGJvYkBvcGVucGdwLmV4YW1w\n" + + "bGU+wsFIBBMBCgB8BYJfarhYAgsJCRD7/MgqAV5zMEcUAAAAAAAeACBzYWx0QG5v\n" + + "dGF0aW9ucy5zZXF1b2lhLXBncC5vcmd5hAoPaQrtdA/HBKZHkjhnFTKBFNIXmLAP\n" + + "fbZ9NyFxxgMVCAoCmwECHgEWIQTRpm4aI7GCyZgPeIz7/MgqAV5zMAAAzKIL/iS7\n" + + "Q7/5szuht5ilCHaE0c6R78YVqmQol4M4DgtRWfjatrYY1hD8lZuaYtAQhvGfG1Ez\n" + + "uQrfFJGewPhsSFXyKYEOFEzLA6K2oTpb1JYzq0p1T+xLH+LJoPm/fP/bMKCKE3gn\n" + + "6AT2zUaEgneeTt9OHZKv0Ie2u2+nrQL9b6jR2VkqOGOnEuWdBfdlUqqvN6QETTHG\n" + + "r4mfJ+t7aREgcQX4NXg6n1YKA1Rc1vJWYQ/RyF1OtyxgenvJLpD1PWDyATT1B9Fo\n" + + "hRzZrdX6iTL2SlBMzwNOfsphnFs2cqRMx3Iaha+5k0nqtbOBkYd02hueGhSSqTcb\n" + + "5fnciAqwW5m+rjBqcXyc+RcmpSAqxNnNbi5K2geO2SnWD221QG5E1sAk1KBx48/N\n" + + "Wh8obYlLavtET0+K28aWJern3jMeK93JIu4g8Kswdz5Zitw9qcTYcBmZfYTe+wdE\n" + + "dMHWNtEYYL1VH8jQ9CZ+rygV5OMStyIc57UmrlP+jR4fDQDtBOWqI6uIfrSoRs7A\n" + + "zQRdpZzyAQwA1jC/XGxjK6ddgrRfW9j+s/U00++EvIsgTs2kr3Rg0GP7FLWV0YNt\n" + + "R1mpl55/bEl7yAxCDTkOgPUMXcaKlnQh6zrlt6H53mF6Bvs3inOHQvOsGtU0dqvb\n" + + "1vkTF0juLiJgPlM7pWv+pNQ6IA39vKoQsTMBv4v5vYNXP9GgKbg8inUNT17BxzZY\n" + + "Hfw5+q63ectgDm2on1e8CIRCZ76oBVwzdkVxoy3gjh1eENlk2D4P0uJNZzF1Q8GV\n" + + "67yLANGMCDICE/OkWn6daipYDzW4iJQtYPUWP4hWhjdm+CK+hg6IQUEn2Vtvi16D\n" + + "2blRP8BpUNNa4fNuylWVuJV76rIHvsLZ1pbM3LHpRgE8s6jivS3Rz3WRs0TmWCNn\n" + + "vHPqWizQ3VTy+r3UQVJ5AmhJDrZdZq9iaUIuZ01PoE1+CHiJwuxPtWvVAxf2POcm\n" + + "1M/F1fK1J0e+lKlQuyonTXqXR22Y41wrfP2aPk3nPSTW2DUAf3vRMZg57ZpRxLEh\n" + + "EMxcM4/LMR+PABEBAAHCwyUEGAEKAlkFgl9quFgJEPv8yCoBXnMwRxQAAAAAAB4A\n" + + "IHNhbHRAbm90YXRpb25zLnNlcXVvaWEtcGdwLm9yZ7V8oqSS6/k73cnFlCAmsAGS\n" + + "aGZH1zQ9IZckmNSy4Xv6ApsCwSWgBBkBCgBOBYJfarhYRxQAAAAAAB4AIHNhbHRA\n" + + "bm90YXRpb25zLnNlcXVvaWEtcGdwLm9yZ9V5KotwniJdgKMu+ao4RRkHpjHCGJXT\n" + + "dSIuAfv/QqEpAAoJEKqqu7vMzN3dHtAL/jhtpQW2/6jD+3U2RHveBjirYc/KZMBC\n" + + "CkvQ5ewcx74GIEFzhl5CnQAUhhE6PAHFXMOtDbV5XiJQyKGiObZiNBJKR+N1vMvj\n" + + "2++idJvmFu287rBEQSd7eXa+EVD3Z9wQ0QprCMenD/nQzFuzMqtgMqZ2KrXE1356\n" + + "qHD4nhIx19G2l2shsoNdG7bywBl3/UivOtu3xXvbigxW8eUfduWN3z75h1SY4JHo\n" + + "55iHt5Apz+PQH9Tyf5+g1CTm1hN/kn2kfj6vho8ovQhGHfp+ahnyJ5m8R08p1hj3\n" + + "qXKShmsfE1wAbM4mq/xtsXYF3TL+QugtldlAzF9KE9aW9pfDlzg573N8Eny9yox0\n" + + "1WvXislYwOnKTHZPHCpOLbLyQrtAKo9eNJTyUs2ObX5LEGOVe5nv3a3Fji6pF9eE\n" + + "42dTxpRTbAfuBBxUmcw12F//UyR7K8ooG9qXmQ3SLLlLRPUYAym1iMiz0tKOHzFK\n" + + "LMcyyDJve60+ejHtf05XJiOdj5T0VwSBahYhBNGmbhojsYLJmA94jPv8yCoBXnMw\n" + + "AABXOgwAr5QUncI/GzGaTGk7/C+KXjhVupJ8m45na9ZEpH9QFa4cjMDQkk66x82t\n" + + "g6UegI5cLYtz3xkjY2Tty9P9TZZHGveoMXIvF7Jr8HBMic1OBzN2Pr03PPCr5/qA\n" + + "lqfkUzq3QMF3IO5tKkiwsKppRIxg/pKxWlLV14HXm2+8cE0QeodTC40WisTcpgeI\n" + + "b57iWNv45q3DbowCl9YRFx4mpFkdGLLpUbZNKF9D4HBCSrny3s8d2mq4SttyKOiI\n" + + "T0Bu6+DVhNwgJbI0tTvXWlN1FnrXtvfgyN48X9cGTyB/0ROgVubEqaW1XysRWsoG\n" + + "eUWzAw0o0clK+oKWXL7zKXI9ORAnYM3YTlD9B6FDQAwnFiXD8R/KR/KssSYcYaUo\n" + + "yCs92odoOtFLTyBITo8AZ0We+QevYMa8gBuwFKRQI7+baJBx3Vatfp9fIInwSz4b\n" + + "rNuk8iSLBJzINxaFVboWhVZNP0s5QeAUHZ4e7DF1nnXMMVRMeV3TxBHjz/blb2nU\n" + + "hQmcUKpz\n" + + "=wOsZ\n" + + "-----END PGP PUBLIC KEY BLOCK-----\n"; + expectSignatureValidationSucceeds(key, "Fake issuer on primary key binding sig is not an issue."); + } + + @Test + public void primaryBindingNoIssuer() throws IOException { + String key = "-----BEGIN PGP PUBLIC KEY BLOCK-----\n" + + "\n" + + "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\n" + + "=CxCD\n" + + "-----END PGP PUBLIC KEY BLOCK-----\n"; + expectSignatureValidationSucceeds(key, "Missing issuer on primary key binding sig is not an issue"); + } + + @Test + public void primaryBindingUnknownSubpacketHashed() throws IOException { + String key = "-----BEGIN PGP PUBLIC KEY BLOCK-----\n" + + "\n" + + "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\n" + + "=IqRH\n" + + "-----END PGP PUBLIC KEY BLOCK-----\n"; + expectSignatureValidationSucceeds(key, "Unknown subpacket in hashed area is not a problem."); + } + + @Test + public void primaryBindingCriticalUnknownSubpacketHashed() throws IOException { + String key = "-----BEGIN PGP PUBLIC KEY BLOCK-----\n" + + "\n" + + "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\n" + + "=Pixl\n" + + "-----END PGP PUBLIC KEY BLOCK-----\n"; + expectSignatureValidationFails(key, "Critical unknown subpacket in hashed area invalidates signature."); + } + + @Test + public void primaryBindingUnknownSubpacketUnhashed() throws IOException { + String key = "-----BEGIN PGP PUBLIC KEY BLOCK-----\n" + + "\n" + + "xsDNBF2lnPIBDAC5cL9PQoQLTMuhjbYvb4Ncuuo0bfmgPRFywX53jPhoFf4Zg6mv\n" + + "/seOXpgecTdOcVttfzC8ycIKrt3aQTiwOG/ctaR4Bk/t6ayNFfdUNxHWk4WCKzdz\n" + + "/56fW2O0F23qIRd8UUJp5IIlN4RDdRCtdhVQIAuzvp2oVy/LaS2kxQoKvph/5pQ/\n" + + "5whqsyroEWDJoSV0yOb25B/iwk/pLUFoyhDG9bj0kIzDxrEqW+7Ba8nocQlecMF3\n" + + "X5KMN5kp2zraLv9dlBBpWW43XktjcCZgMy20SouraVma8Je/ECwUWYUiAZxLIlMv\n" + + "9CurEOtxUw6N3RdOtLmYZS9uEnn5y1UkF88o8Nku890uk6BrewFzJyLAx5wRZ4F0\n" + + "qV/yq36UWQ0JB/AUGhHVPdFf6pl6eaxBwT5GXvbBUibtf8YI2og5RsgTWtXfU7eb\n" + + "SGXrl5ZMpbA6mbfhd0R8aPxWfmDWiIOhBufhMCvUHh1sApMKVZnvIff9/0Dca3wb\n" + + "vLIwa3T4CyshfT0AEQEAAc0hQm9iIEJhYmJhZ2UgPGJvYkBvcGVucGdwLmV4YW1w\n" + + "bGU+wsFIBBMBCgB8BYJffkFBAgsJCRD7/MgqAV5zMEcUAAAAAAAeACBzYWx0QG5v\n" + + "dGF0aW9ucy5zZXF1b2lhLXBncC5vcmczJlC5YIwrzTzm6gIe+t+DikmEeomJlTxx\n" + + "+qVzM4WwDwMVCAoCmwECHgEWIQTRpm4aI7GCyZgPeIz7/MgqAV5zMAAAJ5YMAINJ\n" + + "nVggl0BxncDsXW3ZDKg4NKFIBDfDuGSq1Sk3g6fDd5hZP9scXcpgQvsTI8cHyJr4\n" + + "jk6AHO/OHW8gpWvOEV7pamIp9EP1vn5ZgO0bCkZNXjKPq8a/xlFb00HSO34hV07i\n" + + "wCnBuhyAVdxDlXWHb9CGjChcyt4CtyL8SA7OBu5USO8D01AGSX+US72ZQYT304pf\n" + + "a+0vvrCM9vemXqT/NcppOtS0ZL9rL3VYrwxupPoPbLmbPZgewnEzB321d5fKStHb\n" + + "+jHec0BiHSjVgwhbqhJ2vNfoa41X1SZhiFwvVYTTcqKsZtsZvNv2DISNGjj71A8s\n" + + "BaBzAGkQuLII0QARGc8Uk8FpnSRHBCO9gjlOzIVQ5p0eZpmglJKqZBGVlxWIDio0\n" + + "+qDpPz17nPsoXIyG8+OZyuRXsMByXTB1bCwBUwX5zD5PpFo1f9Zuh9iX7CGhltvE\n" + + "mZsgstx1mjCHWdLFFvPcGCpQHJ1UxtT88Ag/h3f1UFUyhnkk3K4dcJYWsf3cPc7A\n" + + "zQRdpZzyAQwA1jC/XGxjK6ddgrRfW9j+s/U00++EvIsgTs2kr3Rg0GP7FLWV0YNt\n" + + "R1mpl55/bEl7yAxCDTkOgPUMXcaKlnQh6zrlt6H53mF6Bvs3inOHQvOsGtU0dqvb\n" + + "1vkTF0juLiJgPlM7pWv+pNQ6IA39vKoQsTMBv4v5vYNXP9GgKbg8inUNT17BxzZY\n" + + "Hfw5+q63ectgDm2on1e8CIRCZ76oBVwzdkVxoy3gjh1eENlk2D4P0uJNZzF1Q8GV\n" + + "67yLANGMCDICE/OkWn6daipYDzW4iJQtYPUWP4hWhjdm+CK+hg6IQUEn2Vtvi16D\n" + + "2blRP8BpUNNa4fNuylWVuJV76rIHvsLZ1pbM3LHpRgE8s6jivS3Rz3WRs0TmWCNn\n" + + "vHPqWizQ3VTy+r3UQVJ5AmhJDrZdZq9iaUIuZ01PoE1+CHiJwuxPtWvVAxf2POcm\n" + + "1M/F1fK1J0e+lKlQuyonTXqXR22Y41wrfP2aPk3nPSTW2DUAf3vRMZg57ZpRxLEh\n" + + "EMxcM4/LMR+PABEBAAHCw0MEGAEKAncFgl9+QUEJEPv8yCoBXnMwRxQAAAAAAB4A\n" + + "IHNhbHRAbm90YXRpb25zLnNlcXVvaWEtcGdwLm9yZyn+QXu5WbjSVCOYwSoDJKoa\n" + + "dLGXoTeHOR5iXjLeM9chApsCwUOgBBkBCgBvBYJffkFBCRB8L6pN+Tw3skcUAAAA\n" + + "AAAeACBzYWx0QG5vdGF0aW9ucy5zZXF1b2lhLXBncC5vcmfLXnyCoQwKmMYQpR3N\n" + + "zzbq9q6YpG5hv9HUyzROJCykkxYhBB3c4V8JIXzuLzs3YHwvqk35PDeyAAcGf3Zh\n" + + "bHVlgBYL/3VBXFEzMskZ7F81m6RmpFhBFWGRS/DRjBLxX9f8D+GO6Bod0VfBYWmF\n" + + "QbTKPA5CT/q6+UBRywtlIU96FTslsEnbedHupjaCoBuVCJXl3E+EO2oiFZIXXg1r\n" + + "zd4ZlNWEt1hF1lQc+MCyAzJmXxoMdSwCTEYTbxFRe8fsOWAhPfzfMd5D8Qge2dGe\n" + + "4REWA3oEOcy9E1qYqvYaLojTs45Bog3OKQC8rJ2YAW8n0+G7ZFbTZT/dRYs4Ygzx\n" + + "sb2aQ4qN2NbZWHJlLLIqM3jxZj8tviSEIfecv/ckB5PIWzjjaPueqT1jpEO2EteF\n" + + "y1JjW3bcNzv8X0X9svqM+xz04cUtnmZjHQH6W6W9ADAxriVksC2Xjl38WCiuG2qN\n" + + "8d9f8Se2whuc75/EOcUb1DWgoGcHNIFrB66FxDwFTQzPfJ4+F1VvWPQ5xq/KljOb\n" + + "nsW9OnoHbKLwZ+EIDQa0pEamwrNg674NGSNK3oJ9DrKUZ8SvtO/ou0YXp0fd4c/9\n" + + "2qTjSFwqchYhBNGmbhojsYLJmA94jPv8yCoBXnMwAABdrgwAuImuk4YBtCosyvUC\n" + + "YFurpltYNbcDbsiQqPTr+2GLJfpeJ734jpie6hH87KqTQ8ghTEAu1XzYE5FcLRGn\n" + + "a+twuslx2tFs0TMt4fCIIIjAPy5d/ziOa8M/ezWHjGi6QOwHNqrxmaa+MnLNSA6w\n" + + "SCr9aaJZMwclsY/5Tf2SUPzAi1WX8vz0J316Aq+PjW9wukYnOQeKZmnkqPs866/3\n" + + "M0tIQmtCuDib9osGh+KWPizy2cK3l4LEfAPfPtX8lV8kOtF2KD/B7SH/Qx/vx96B\n" + + "bN7ryS4SNMCVbFwRF79Zy80Cwldhc70iBXxRfi8ocDVGB4oYm3TrMBUsbVIJVVji\n" + + "hB1b0jMqaeo6ecOBQKrteeyl/QNuFrrnVO7wa2URuPMU+2VqXNZc+n95rWH1IO49\n" + + "/MHgdefnVHCJH3/WE6yq1olx98DOxi6Ig7GBNdlOh8CeG8dkJH75we6wvfixTmT/\n" + + "5dNfKXIieQI0zHMqeB5zDFGx1Dmki8G7U23BSklnIxuXbzNR\n" + + "=SDad\n" + + "-----END PGP PUBLIC KEY BLOCK-----\n"; + expectSignatureValidationSucceeds(key, "Unknown subpacket is not an issue in the unhashed area"); + } + + @Test + public void primaryBindingCriticalUnknownSubpacketUnhashed() throws IOException { + String key = "-----BEGIN PGP PUBLIC KEY BLOCK-----\n" + + "\n" + + "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\n" + + "=xogK\n" + + "-----END PGP PUBLIC KEY BLOCK-----\n"; + expectSignatureValidationSucceeds(key, "Critical unknown subpacket is acceptable in unhashed area of primary binding sig"); + } + + @Test + public void primaryBindingUnknownNotationHashed() throws IOException { + String key = "-----BEGIN PGP PUBLIC KEY BLOCK-----\n" + + "\n" + + "xsDNBF2lnPIBDAC5cL9PQoQLTMuhjbYvb4Ncuuo0bfmgPRFywX53jPhoFf4Zg6mv\n" + + "/seOXpgecTdOcVttfzC8ycIKrt3aQTiwOG/ctaR4Bk/t6ayNFfdUNxHWk4WCKzdz\n" + + "/56fW2O0F23qIRd8UUJp5IIlN4RDdRCtdhVQIAuzvp2oVy/LaS2kxQoKvph/5pQ/\n" + + "5whqsyroEWDJoSV0yOb25B/iwk/pLUFoyhDG9bj0kIzDxrEqW+7Ba8nocQlecMF3\n" + + "X5KMN5kp2zraLv9dlBBpWW43XktjcCZgMy20SouraVma8Je/ECwUWYUiAZxLIlMv\n" + + "9CurEOtxUw6N3RdOtLmYZS9uEnn5y1UkF88o8Nku890uk6BrewFzJyLAx5wRZ4F0\n" + + "qV/yq36UWQ0JB/AUGhHVPdFf6pl6eaxBwT5GXvbBUibtf8YI2og5RsgTWtXfU7eb\n" + + "SGXrl5ZMpbA6mbfhd0R8aPxWfmDWiIOhBufhMCvUHh1sApMKVZnvIff9/0Dca3wb\n" + + "vLIwa3T4CyshfT0AEQEAAc0hQm9iIEJhYmJhZ2UgPGJvYkBvcGVucGdwLmV4YW1w\n" + + "bGU+wsFIBBMBCgB8BYJffkFBAgsJCRD7/MgqAV5zMEcUAAAAAAAeACBzYWx0QG5v\n" + + "dGF0aW9ucy5zZXF1b2lhLXBncC5vcmczJlC5YIwrzTzm6gIe+t+DikmEeomJlTxx\n" + + "+qVzM4WwDwMVCAoCmwECHgEWIQTRpm4aI7GCyZgPeIz7/MgqAV5zMAAAJ5YMAINJ\n" + + "nVggl0BxncDsXW3ZDKg4NKFIBDfDuGSq1Sk3g6fDd5hZP9scXcpgQvsTI8cHyJr4\n" + + "jk6AHO/OHW8gpWvOEV7pamIp9EP1vn5ZgO0bCkZNXjKPq8a/xlFb00HSO34hV07i\n" + + "wCnBuhyAVdxDlXWHb9CGjChcyt4CtyL8SA7OBu5USO8D01AGSX+US72ZQYT304pf\n" + + "a+0vvrCM9vemXqT/NcppOtS0ZL9rL3VYrwxupPoPbLmbPZgewnEzB321d5fKStHb\n" + + "+jHec0BiHSjVgwhbqhJ2vNfoa41X1SZhiFwvVYTTcqKsZtsZvNv2DISNGjj71A8s\n" + + "BaBzAGkQuLII0QARGc8Uk8FpnSRHBCO9gjlOzIVQ5p0eZpmglJKqZBGVlxWIDio0\n" + + "+qDpPz17nPsoXIyG8+OZyuRXsMByXTB1bCwBUwX5zD5PpFo1f9Zuh9iX7CGhltvE\n" + + "mZsgstx1mjCHWdLFFvPcGCpQHJ1UxtT88Ag/h3f1UFUyhnkk3K4dcJYWsf3cPc7A\n" + + "zQRdpZzyAQwA1jC/XGxjK6ddgrRfW9j+s/U00++EvIsgTs2kr3Rg0GP7FLWV0YNt\n" + + "R1mpl55/bEl7yAxCDTkOgPUMXcaKlnQh6zrlt6H53mF6Bvs3inOHQvOsGtU0dqvb\n" + + "1vkTF0juLiJgPlM7pWv+pNQ6IA39vKoQsTMBv4v5vYNXP9GgKbg8inUNT17BxzZY\n" + + "Hfw5+q63ectgDm2on1e8CIRCZ76oBVwzdkVxoy3gjh1eENlk2D4P0uJNZzF1Q8GV\n" + + "67yLANGMCDICE/OkWn6daipYDzW4iJQtYPUWP4hWhjdm+CK+hg6IQUEn2Vtvi16D\n" + + "2blRP8BpUNNa4fNuylWVuJV76rIHvsLZ1pbM3LHpRgE8s6jivS3Rz3WRs0TmWCNn\n" + + "vHPqWizQ3VTy+r3UQVJ5AmhJDrZdZq9iaUIuZ01PoE1+CHiJwuxPtWvVAxf2POcm\n" + + "1M/F1fK1J0e+lKlQuyonTXqXR22Y41wrfP2aPk3nPSTW2DUAf3vRMZg57ZpRxLEh\n" + + "EMxcM4/LMR+PABEBAAHCw2gEGAEKApwFgl9+QUEJEPv8yCoBXnMwRxQAAAAAAB4A\n" + + "IHNhbHRAbm90YXRpb25zLnNlcXVvaWEtcGdwLm9yZwYyHE0TL++fj0otGEGhkpSI\n" + + "Iu8FqtiFLYoSb/tVrC48ApsCwWigBBkBCgCbBYJffkFBCRB8L6pN+Tw3sisUAAAA\n" + + "AAAdAAV1bmtub3duQHRlc3RzLnNlcXVvaWEtcGdwLm9yZ3ZhbHVlRxQAAAAAAB4A\n" + + "IHNhbHRAbm90YXRpb25zLnNlcXVvaWEtcGdwLm9yZ/ZJUjW/650Zi+XMhaJ9ZZ8R\n" + + "wCz18pnNY6sqr8X1966gFiEEHdzhXwkhfO4vOzdgfC+qTfk8N7IAANEqC/9V9yqn\n" + + "cjMFKUjXrRymSpfIy5QegGHTlhe700rKdxFyWPvbXxwnRiwyO/cly0Fb87No2two\n" + + "m7+ozTWD0UOifNEF9VzCn+p8cT7+sAaQYscgky/RkFm3EnoLW0i9ieJlZ+KfDDB/\n" + + "jHAvoEwUZ0RzhVIyO3yGgvpI25Wk5eeovU7azmAQ2Cj/B3o5dAx8PbPMvvGKxaF1\n" + + "cSHQ2tEImfQkC5YxxDx2VrGEmVkKKiyFEYxUsSV6FKMw7K6tKEsRPY4tVaeZtcYo\n" + + "QC22W/nZn9AxkuqqYptr8VL5ZhfpH8Dq0tvPfapjxKGl805nXRtzqtnhJILLovhV\n" + + "Qt3ZdzzmqWiTt0cW375x2cq2UlYNPIQL/qum0+1rj+WNap7QSJ3DIWo3Igv3dhSk\n" + + "GlyoXuD9++3M12O6shfT7CuBZdHmkwCdJDC9s5EuFOAyCBYb9nLnOhcj/edG3uEY\n" + + "67fe/JLtQmv1L6HU6ewy4F01GPsDNN2z8AX3u0V1Km1YwZhdpBB/+l8sRmYWIQTR\n" + + "pm4aI7GCyZgPeIz7/MgqAV5zMAAAU8cL/3/+rGuID3DOjqfA6HNoNsllkud1gjkC\n" + + "9qoiB+T8OlDM0I1LNENrjtBfzwB4uuKxtqY0fdlBa8MPlJMRnZX83Ev7jAPl8PYS\n" + + "3q3oK1I7ymjmBBNaZ1I7djIxDwecYn8A6r+uc2SwAD6G0sRy3rkUXHNEJGEcjwFf\n" + + "3rbrI+7gWAVCY7qn6DNu2eNSRscV+GU/LQ3W+na4n6hIugd8yKCAPl4fKsZtU85S\n" + + "UgLaUdQWoLJWcD54tSfmYZ0pRcQB85189Mo/gu6csBaz2M4a7GIc4+eXadtoJ72g\n" + + "YcKOnHLFEPR2qkK/NqIoNR6aEfJ+ti3Kdua7jT31RfaiftB2Uj0wctpKItjOlCLS\n" + + "NGSoaR2uvXg1pm+C3Rc395IaywciLfSKE42Dx6XK8TAcN+MGFVojsxeSFkm/PNIg\n" + + "ELpz5C7mOTUCwbVA2UoDazSMXJN8calBUHwGjTf79G98NxE6zW1j3cr7+rPUqOAz\n" + + "iShtvMX59VD5HBxeTZF+U22/cNIRcJ2eUA==\n" + + "=nQN9\n" + + "-----END PGP PUBLIC KEY BLOCK-----\n"; + expectSignatureValidationSucceeds(key, "Unknown notation is acceptable in hashed area of primary binding sig."); + } + + @Test + public void primaryBindingCriticalUnknownNotationHashed() throws IOException { + String key = "-----BEGIN PGP PUBLIC KEY BLOCK-----\n" + + "\n" + + "xsDNBF2lnPIBDAC5cL9PQoQLTMuhjbYvb4Ncuuo0bfmgPRFywX53jPhoFf4Zg6mv\n" + + "/seOXpgecTdOcVttfzC8ycIKrt3aQTiwOG/ctaR4Bk/t6ayNFfdUNxHWk4WCKzdz\n" + + "/56fW2O0F23qIRd8UUJp5IIlN4RDdRCtdhVQIAuzvp2oVy/LaS2kxQoKvph/5pQ/\n" + + "5whqsyroEWDJoSV0yOb25B/iwk/pLUFoyhDG9bj0kIzDxrEqW+7Ba8nocQlecMF3\n" + + "X5KMN5kp2zraLv9dlBBpWW43XktjcCZgMy20SouraVma8Je/ECwUWYUiAZxLIlMv\n" + + "9CurEOtxUw6N3RdOtLmYZS9uEnn5y1UkF88o8Nku890uk6BrewFzJyLAx5wRZ4F0\n" + + "qV/yq36UWQ0JB/AUGhHVPdFf6pl6eaxBwT5GXvbBUibtf8YI2og5RsgTWtXfU7eb\n" + + "SGXrl5ZMpbA6mbfhd0R8aPxWfmDWiIOhBufhMCvUHh1sApMKVZnvIff9/0Dca3wb\n" + + "vLIwa3T4CyshfT0AEQEAAc0hQm9iIEJhYmJhZ2UgPGJvYkBvcGVucGdwLmV4YW1w\n" + + "bGU+wsFIBBMBCgB8BYJffkFBAgsJCRD7/MgqAV5zMEcUAAAAAAAeACBzYWx0QG5v\n" + + "dGF0aW9ucy5zZXF1b2lhLXBncC5vcmczJlC5YIwrzTzm6gIe+t+DikmEeomJlTxx\n" + + "+qVzM4WwDwMVCAoCmwECHgEWIQTRpm4aI7GCyZgPeIz7/MgqAV5zMAAAJ5YMAINJ\n" + + "nVggl0BxncDsXW3ZDKg4NKFIBDfDuGSq1Sk3g6fDd5hZP9scXcpgQvsTI8cHyJr4\n" + + "jk6AHO/OHW8gpWvOEV7pamIp9EP1vn5ZgO0bCkZNXjKPq8a/xlFb00HSO34hV07i\n" + + "wCnBuhyAVdxDlXWHb9CGjChcyt4CtyL8SA7OBu5USO8D01AGSX+US72ZQYT304pf\n" + + "a+0vvrCM9vemXqT/NcppOtS0ZL9rL3VYrwxupPoPbLmbPZgewnEzB321d5fKStHb\n" + + "+jHec0BiHSjVgwhbqhJ2vNfoa41X1SZhiFwvVYTTcqKsZtsZvNv2DISNGjj71A8s\n" + + "BaBzAGkQuLII0QARGc8Uk8FpnSRHBCO9gjlOzIVQ5p0eZpmglJKqZBGVlxWIDio0\n" + + "+qDpPz17nPsoXIyG8+OZyuRXsMByXTB1bCwBUwX5zD5PpFo1f9Zuh9iX7CGhltvE\n" + + "mZsgstx1mjCHWdLFFvPcGCpQHJ1UxtT88Ag/h3f1UFUyhnkk3K4dcJYWsf3cPc7A\n" + + "zQRdpZzyAQwA1jC/XGxjK6ddgrRfW9j+s/U00++EvIsgTs2kr3Rg0GP7FLWV0YNt\n" + + "R1mpl55/bEl7yAxCDTkOgPUMXcaKlnQh6zrlt6H53mF6Bvs3inOHQvOsGtU0dqvb\n" + + "1vkTF0juLiJgPlM7pWv+pNQ6IA39vKoQsTMBv4v5vYNXP9GgKbg8inUNT17BxzZY\n" + + "Hfw5+q63ectgDm2on1e8CIRCZ76oBVwzdkVxoy3gjh1eENlk2D4P0uJNZzF1Q8GV\n" + + "67yLANGMCDICE/OkWn6daipYDzW4iJQtYPUWP4hWhjdm+CK+hg6IQUEn2Vtvi16D\n" + + "2blRP8BpUNNa4fNuylWVuJV76rIHvsLZ1pbM3LHpRgE8s6jivS3Rz3WRs0TmWCNn\n" + + "vHPqWizQ3VTy+r3UQVJ5AmhJDrZdZq9iaUIuZ01PoE1+CHiJwuxPtWvVAxf2POcm\n" + + "1M/F1fK1J0e+lKlQuyonTXqXR22Y41wrfP2aPk3nPSTW2DUAf3vRMZg57ZpRxLEh\n" + + "EMxcM4/LMR+PABEBAAHCw2gEGAEKApwFgl9+QUEJEPv8yCoBXnMwRxQAAAAAAB4A\n" + + "IHNhbHRAbm90YXRpb25zLnNlcXVvaWEtcGdwLm9yZ+YQ9A1f4N1bYZNKumNsSQb3\n" + + "cr3HErC8EfLoT7wWJK6OApsCwWigBBkBCgCbBYJffkFBCRB8L6pN+Tw3siuUAAAA\n" + + "AAAdAAV1bmtub3duQHRlc3RzLnNlcXVvaWEtcGdwLm9yZ3ZhbHVlRxQAAAAAAB4A\n" + + "IHNhbHRAbm90YXRpb25zLnNlcXVvaWEtcGdwLm9yZ2j36YpkuUuxxUBY2kIAFiKA\n" + + "xBS9L2H4g8W85T1MuJguFiEEHdzhXwkhfO4vOzdgfC+qTfk8N7IAAA0sDACbc557\n" + + "KAGoIFSf5x7rgJ31dpjKqJyHHmki0CN6bqW0qSLiFVUm0O7pqLWZW3V5AZMWVVtF\n" + + "zlAs6sgVdhLxC2OIfNiD6OZUysAtHKLwxGNCp0tDxhhiUC4iuE89s4YZ0skGqs/k\n" + + "erDN/5HRVqF3IWhXHA7OogLsQRGbpMPzOqeGcrQRHp+EqnfYO2vQcXIjDg69E6wQ\n" + + "mzbt3W59D6uJJ0mMjANCm239xUCDonoi+hgmGtLNHbJRC58UhyP3JUBmX/IDYG0A\n" + + "ISpvgXgi7xRz2JgnIv1fqqh4rEttTSKjSbSqUmf9eQLU/hfmSMbtIDCZH+PTnRRf\n" + + "ye7X2ab/6i4w62MT/aWiin45u8s6kG6+0AbiLVebTL7rddagLI/lxegFpCEcV5H0\n" + + "Xf4KgpGJL2zZgIAJ0lKzkSoFcYlUUiBg+DGh3YK9nnyrVtK5pB2tRVRvPUbnPEqj\n" + + "kO37EH8E8pdP3gnOUamPWgazAupHFgSOjNM0pVvUrlIH4mI/1iyCC7DA+joWIQTR\n" + + "pm4aI7GCyZgPeIz7/MgqAV5zMAAAaTAL/2bY9eKZLsLVNUGV5NZQ5vEoUOcjTAlO\n" + + "pG3nxtzODY+lnVxfgLcb9jvuWxWz0PnYLBm2qnqoAYqJOt1yRVfQFKZAxEWvtZgB\n" + + "LWZcNIY2/NC3Fy98of7djViD5nHktl2vTOLyRnutqgD8EfviBZqjy6UF12mL08nJ\n" + + "MniAkZtVCgHk1j/HEtXrASO1PWfzVSOew6Jtq6RWfWzhGBHgLqoEsvofYxQnU83F\n" + + "A8IQ6P4GULMxZeboWq1sKyIjKKzoU3crHEdWoIPWuyWKMy5DCuuEda5q5aFGZfWd\n" + + "c+6zwUGNPbecXM4eNvOPeUrtcnZTj0gFpdsutCp59lWd4XkOfqhVgGiqeexZ+Q6H\n" + + "BWp3LTvEo/amrmGq0O4SbCquyap9g8D/g7hh9K7JrREfhHJ9Mp0Ql8VZ6LOIlmQR\n" + + "RV42SzORUrJpJgPviUMSUH27hOVidymUsJkkInktL+IKNCvDnTNm+hoXmMuyQA9r\n" + + "FAOuVlKPStWFgQ1NVsqFzthAcJUp2IxCWw==\n" + + "=fxEe\n" + + "-----END PGP PUBLIC KEY BLOCK-----\n"; + expectSignatureValidationFails(key, "Critical unknown notation in hashed area invalidates primary binding sig"); + } + + @Test + public void primaryBindingUnknownNotationUnhashed() throws IOException { + String key = "-----BEGIN PGP PUBLIC KEY BLOCK-----\n" + + "\n" + + "xsDNBF2lnPIBDAC5cL9PQoQLTMuhjbYvb4Ncuuo0bfmgPRFywX53jPhoFf4Zg6mv\n" + + "/seOXpgecTdOcVttfzC8ycIKrt3aQTiwOG/ctaR4Bk/t6ayNFfdUNxHWk4WCKzdz\n" + + "/56fW2O0F23qIRd8UUJp5IIlN4RDdRCtdhVQIAuzvp2oVy/LaS2kxQoKvph/5pQ/\n" + + "5whqsyroEWDJoSV0yOb25B/iwk/pLUFoyhDG9bj0kIzDxrEqW+7Ba8nocQlecMF3\n" + + "X5KMN5kp2zraLv9dlBBpWW43XktjcCZgMy20SouraVma8Je/ECwUWYUiAZxLIlMv\n" + + "9CurEOtxUw6N3RdOtLmYZS9uEnn5y1UkF88o8Nku890uk6BrewFzJyLAx5wRZ4F0\n" + + "qV/yq36UWQ0JB/AUGhHVPdFf6pl6eaxBwT5GXvbBUibtf8YI2og5RsgTWtXfU7eb\n" + + "SGXrl5ZMpbA6mbfhd0R8aPxWfmDWiIOhBufhMCvUHh1sApMKVZnvIff9/0Dca3wb\n" + + "vLIwa3T4CyshfT0AEQEAAc0hQm9iIEJhYmJhZ2UgPGJvYkBvcGVucGdwLmV4YW1w\n" + + "bGU+wsFIBBMBCgB8BYJffkFBAgsJCRD7/MgqAV5zMEcUAAAAAAAeACBzYWx0QG5v\n" + + "dGF0aW9ucy5zZXF1b2lhLXBncC5vcmczJlC5YIwrzTzm6gIe+t+DikmEeomJlTxx\n" + + "+qVzM4WwDwMVCAoCmwECHgEWIQTRpm4aI7GCyZgPeIz7/MgqAV5zMAAAJ5YMAINJ\n" + + "nVggl0BxncDsXW3ZDKg4NKFIBDfDuGSq1Sk3g6fDd5hZP9scXcpgQvsTI8cHyJr4\n" + + "jk6AHO/OHW8gpWvOEV7pamIp9EP1vn5ZgO0bCkZNXjKPq8a/xlFb00HSO34hV07i\n" + + "wCnBuhyAVdxDlXWHb9CGjChcyt4CtyL8SA7OBu5USO8D01AGSX+US72ZQYT304pf\n" + + "a+0vvrCM9vemXqT/NcppOtS0ZL9rL3VYrwxupPoPbLmbPZgewnEzB321d5fKStHb\n" + + "+jHec0BiHSjVgwhbqhJ2vNfoa41X1SZhiFwvVYTTcqKsZtsZvNv2DISNGjj71A8s\n" + + "BaBzAGkQuLII0QARGc8Uk8FpnSRHBCO9gjlOzIVQ5p0eZpmglJKqZBGVlxWIDio0\n" + + "+qDpPz17nPsoXIyG8+OZyuRXsMByXTB1bCwBUwX5zD5PpFo1f9Zuh9iX7CGhltvE\n" + + "mZsgstx1mjCHWdLFFvPcGCpQHJ1UxtT88Ag/h3f1UFUyhnkk3K4dcJYWsf3cPc7A\n" + + "zQRdpZzyAQwA1jC/XGxjK6ddgrRfW9j+s/U00++EvIsgTs2kr3Rg0GP7FLWV0YNt\n" + + "R1mpl55/bEl7yAxCDTkOgPUMXcaKlnQh6zrlt6H53mF6Bvs3inOHQvOsGtU0dqvb\n" + + "1vkTF0juLiJgPlM7pWv+pNQ6IA39vKoQsTMBv4v5vYNXP9GgKbg8inUNT17BxzZY\n" + + "Hfw5+q63ectgDm2on1e8CIRCZ76oBVwzdkVxoy3gjh1eENlk2D4P0uJNZzF1Q8GV\n" + + "67yLANGMCDICE/OkWn6daipYDzW4iJQtYPUWP4hWhjdm+CK+hg6IQUEn2Vtvi16D\n" + + "2blRP8BpUNNa4fNuylWVuJV76rIHvsLZ1pbM3LHpRgE8s6jivS3Rz3WRs0TmWCNn\n" + + "vHPqWizQ3VTy+r3UQVJ5AmhJDrZdZq9iaUIuZ01PoE1+CHiJwuxPtWvVAxf2POcm\n" + + "1M/F1fK1J0e+lKlQuyonTXqXR22Y41wrfP2aPk3nPSTW2DUAf3vRMZg57ZpRxLEh\n" + + "EMxcM4/LMR+PABEBAAHCw2gEGAEKApwFgl9+QUEJEPv8yCoBXnMwRxQAAAAAAB4A\n" + + "IHNhbHRAbm90YXRpb25zLnNlcXVvaWEtcGdwLm9yZwBFME75y8sQFn1PwmOCDkJc\n" + + "e+TNcZD7kXPBEGV75gvyApsCwWigBBkBCgBvBYJffkFBCRB8L6pN+Tw3skcUAAAA\n" + + "AAAeACBzYWx0QG5vdGF0aW9ucy5zZXF1b2lhLXBncC5vcmfZqaBfsBdflaWMtURV\n" + + "I0vkApvwQRIGwy3VQ1May0E/ohYhBB3c4V8JIXzuLzs3YHwvqk35PDeyACwrFAAA\n" + + "AAAAHQAFdW5rbm93bkB0ZXN0cy5zZXF1b2lhLXBncC5vcmd2YWx1Zf8VC/9h49l4\n" + + "xDe19CqkL5+8yu20xn9kwikZZB4uqrUjCE1I67eTl9UQYMKTIK7u4Leo6vUVHHyy\n" + + "P9kMF4rHOYjfsQj5NLa+v7ihKKzS9ePPt3fugJJnAYpSJ6WoCUa7q27VK820WPnR\n" + + "U790o6R6/xptHQ5KpSSYSXCF7fL49/VoSgZ7fQ895mhQLV/6RxWIczrSNvpb4ONE\n" + + "B8sxzF99IZA3qqltvhjseOvGpjZ//pzOvY0MU17Wy7OQ/c7Ywm/wRnVIEHGn/axU\n" + + "51t9rk/z1x6F6QZ+pFC4iSU1TtC9Bmw6qJmMPT7YfkYnCXHZBlUq3GxigPviVcv0\n" + + "DG97o905GlOWhH83yP/j7XAstGEA/GIkztSsUhlz+VgfsAoHxfdHvJ0oAlafMbAF\n" + + "SmY1FnZoo1Urq256nvp90ewuFklJM+S5Fq272f9CtcqtDgh2PA1oCCQjU906Vkhv\n" + + "uYWwzKIOnzKyJfE607C9ANKLb4jvIk6lb00SS0SBghiX/qrbqpDSaqG9+8gWIQTR\n" + + "pm4aI7GCyZgPeIz7/MgqAV5zMAAALwYMAKvQnX+ThfhZuxxE2Or8l1Zg5i92WLEn\n" + + "IUeT8Z0Sks1LtbddcF4Rg8UQeHfWyiOoPiVARxxQgZwpBDNMs4vFOIbjAW9mitTB\n" + + "pV26R90JOldPCjJzR0FMUV6rIoYkXnxSD8qMahApZ4DPZ0FaHpZ6NugzKwE7ZYqR\n" + + "cd7Ae7cTLb9nrfJQ1Sf9bxMvuYiniKwCL92iqo1rntnJELylu45PimS9wfse95A9\n" + + "6QTQxx2PXjh2BF+XcHm6/fYv5e2I1K+OxPwI2rUJF39kxue4fErlNkIdMuV5JWZO\n" + + "vQYb6FEVPKdbpPp0iXJqEkdpgvaMMAiuvXQwpzxMWaVsrOOPT+EZTXO6JrI7OW1+\n" + + "JD0ffK4SAv4Z9/hIbivkjYc23jq7GNlJSi5E1jpyf4Jra9nmWzboTz7v2BcEvB9U\n" + + "UFf52KA1fDUOmfSm+tTMJNh0yPA+VOfbnwpf3qpUczMO/U9GD4XeHG+FWV+CyrsT\n" + + "NxBszEsvsO6SrcdWYYLyIlHmlIqpuSYVHg==\n" + + "=CcRZ\n" + + "-----END PGP PUBLIC KEY BLOCK-----\n"; + expectSignatureValidationSucceeds(key, "Unknown notation in unhashed area of primary key binding is okay."); + } + + @Test + public void primaryBindingCriticalUnknownNotationUnhashed() throws IOException { + String key = "-----BEGIN PGP PUBLIC KEY BLOCK-----\n" + + "\n" + + "xsDNBF2lnPIBDAC5cL9PQoQLTMuhjbYvb4Ncuuo0bfmgPRFywX53jPhoFf4Zg6mv\n" + + "/seOXpgecTdOcVttfzC8ycIKrt3aQTiwOG/ctaR4Bk/t6ayNFfdUNxHWk4WCKzdz\n" + + "/56fW2O0F23qIRd8UUJp5IIlN4RDdRCtdhVQIAuzvp2oVy/LaS2kxQoKvph/5pQ/\n" + + "5whqsyroEWDJoSV0yOb25B/iwk/pLUFoyhDG9bj0kIzDxrEqW+7Ba8nocQlecMF3\n" + + "X5KMN5kp2zraLv9dlBBpWW43XktjcCZgMy20SouraVma8Je/ECwUWYUiAZxLIlMv\n" + + "9CurEOtxUw6N3RdOtLmYZS9uEnn5y1UkF88o8Nku890uk6BrewFzJyLAx5wRZ4F0\n" + + "qV/yq36UWQ0JB/AUGhHVPdFf6pl6eaxBwT5GXvbBUibtf8YI2og5RsgTWtXfU7eb\n" + + "SGXrl5ZMpbA6mbfhd0R8aPxWfmDWiIOhBufhMCvUHh1sApMKVZnvIff9/0Dca3wb\n" + + "vLIwa3T4CyshfT0AEQEAAc0hQm9iIEJhYmJhZ2UgPGJvYkBvcGVucGdwLmV4YW1w\n" + + "bGU+wsFIBBMBCgB8BYJffkFBAgsJCRD7/MgqAV5zMEcUAAAAAAAeACBzYWx0QG5v\n" + + "dGF0aW9ucy5zZXF1b2lhLXBncC5vcmczJlC5YIwrzTzm6gIe+t+DikmEeomJlTxx\n" + + "+qVzM4WwDwMVCAoCmwECHgEWIQTRpm4aI7GCyZgPeIz7/MgqAV5zMAAAJ5YMAINJ\n" + + "nVggl0BxncDsXW3ZDKg4NKFIBDfDuGSq1Sk3g6fDd5hZP9scXcpgQvsTI8cHyJr4\n" + + "jk6AHO/OHW8gpWvOEV7pamIp9EP1vn5ZgO0bCkZNXjKPq8a/xlFb00HSO34hV07i\n" + + "wCnBuhyAVdxDlXWHb9CGjChcyt4CtyL8SA7OBu5USO8D01AGSX+US72ZQYT304pf\n" + + "a+0vvrCM9vemXqT/NcppOtS0ZL9rL3VYrwxupPoPbLmbPZgewnEzB321d5fKStHb\n" + + "+jHec0BiHSjVgwhbqhJ2vNfoa41X1SZhiFwvVYTTcqKsZtsZvNv2DISNGjj71A8s\n" + + "BaBzAGkQuLII0QARGc8Uk8FpnSRHBCO9gjlOzIVQ5p0eZpmglJKqZBGVlxWIDio0\n" + + "+qDpPz17nPsoXIyG8+OZyuRXsMByXTB1bCwBUwX5zD5PpFo1f9Zuh9iX7CGhltvE\n" + + "mZsgstx1mjCHWdLFFvPcGCpQHJ1UxtT88Ag/h3f1UFUyhnkk3K4dcJYWsf3cPc7A\n" + + "zQRdpZzyAQwA1jC/XGxjK6ddgrRfW9j+s/U00++EvIsgTs2kr3Rg0GP7FLWV0YNt\n" + + "R1mpl55/bEl7yAxCDTkOgPUMXcaKlnQh6zrlt6H53mF6Bvs3inOHQvOsGtU0dqvb\n" + + "1vkTF0juLiJgPlM7pWv+pNQ6IA39vKoQsTMBv4v5vYNXP9GgKbg8inUNT17BxzZY\n" + + "Hfw5+q63ectgDm2on1e8CIRCZ76oBVwzdkVxoy3gjh1eENlk2D4P0uJNZzF1Q8GV\n" + + "67yLANGMCDICE/OkWn6daipYDzW4iJQtYPUWP4hWhjdm+CK+hg6IQUEn2Vtvi16D\n" + + "2blRP8BpUNNa4fNuylWVuJV76rIHvsLZ1pbM3LHpRgE8s6jivS3Rz3WRs0TmWCNn\n" + + "vHPqWizQ3VTy+r3UQVJ5AmhJDrZdZq9iaUIuZ01PoE1+CHiJwuxPtWvVAxf2POcm\n" + + "1M/F1fK1J0e+lKlQuyonTXqXR22Y41wrfP2aPk3nPSTW2DUAf3vRMZg57ZpRxLEh\n" + + "EMxcM4/LMR+PABEBAAHCw2gEGAEKApwFgl9+QUEJEPv8yCoBXnMwRxQAAAAAAB4A\n" + + "IHNhbHRAbm90YXRpb25zLnNlcXVvaWEtcGdwLm9yZ2JRzdjMWDYQtr2T/dLwEDXN\n" + + "l9wkz0jXF3AKqVhx2ivFApsCwWigBBkBCgBvBYJffkFBCRB8L6pN+Tw3skcUAAAA\n" + + "AAAeACBzYWx0QG5vdGF0aW9ucy5zZXF1b2lhLXBncC5vcmfrCrBTMGW6X4ITei21\n" + + "w9xjztPLbhSlEhfXilfL02q65xYhBB3c4V8JIXzuLzs3YHwvqk35PDeyACwrlAAA\n" + + "AAAAHQAFdW5rbm93bkB0ZXN0cy5zZXF1b2lhLXBncC5vcmd2YWx1ZRYdDACFFQuM\n" + + "JlVgqgk2PLO9ONzxq1sPgE1s06P5cg+tmIpI0RhjywmdgdaGmO4RXDczLS761FoX\n" + + "mMS+4AJhygHpJPDb1kjlEb/v/COioMR0tkkOmz8UqZx46fT2nGK+MMhYv13P2oKL\n" + + "rpotnB/fIBKmsA5cr/QcTG/XWTHvS4/jbp0RZIHuubgfLSQ5wOQedW3AsR2bRUAw\n" + + "RcxeRFQVU03ndleN6r+UzKV8D0rHTnERKcxbWab+WA7/cgLdIiStTfRlhlTs+Ksp\n" + + "mIQz4Tc/FrSFxZFSmiqcWbrN9CG7GE9jXxNkzyIsAww6eUqn2nRKWHQJtn0YK3KY\n" + + "eMYIpskvnwCXMjdCEzzP+mVcniZ4zzzt+IfhTFcXCg0PyRBwkWEAUPxI8DALBf6k\n" + + "f0k74BV6eOujq56//1rAodZUW45kQEA+Mgmf9ySONjdSv+KOtHnqwu/B/TGtqiuD\n" + + "yohxrIbiMlWrXVzTzsEh6vfRDdpOOHcYWDnl4ZfzxCXjD2kHEHnQWPe2dGIWIQTR\n" + + "pm4aI7GCyZgPeIz7/MgqAV5zMAAAqk0L/RxPWSjK8cLmEinDA2Br/ari5OLTMJaV\n" + + "wEt0Bju5ccUuH/xkWxsAhjG3SYBAID5mbPAKb6pkkG33mApgxlyD4Jv5VbkxZ5xg\n" + + "Dc03B6B9iK5u3Q8uCoWdwaMmI15G0b+Rg8DXRSph80yPRV7sQqdap+/EI8J20j15\n" + + "zPz6YrzENzNrsl/Z7X/W3T7TFT3CasfndAHRdSRQUmDE9m22Vouj+xRZWB4NCPHN\n" + + "nG2aDVR8g1UoodydEOEqYnDqcs9XvY+tyZXspNxn+a7pp24lpynn5OfJR6IGOUYM\n" + + "UOIX+M9UDPlbVAAaV/I4RJkT1kafh0yTPtDewsEJ3xV31Uk7N+82+v5tKKPWQ/YM\n" + + "05xiIwbj/nfcAz2ujJBULnw03CQ9OzGDQPNRTcd1sCwDDJ3gwl/xmPbfNZKYfkmK\n" + + "xQQ/pQZC+HLdsDqmH8jhAVYiH4V1Ajk1DQR5v0/j6lfqZLfHauLx/LCzt7+K9DRQ\n" + + "3ADU//ZKT2HDol0IEGiTgrGH4jyKBviurg==\n" + + "=nAET\n" + + "-----END PGP PUBLIC KEY BLOCK-----\n"; + expectSignatureValidationSucceeds(key, "Critical unknown notation is acceptable in unhashed area of primary key binding sig."); + } + + private void expectSignatureValidationSucceeds(String key, String message) throws IOException { + PGPPublicKeyRing publicKeys = PGPainless.readKeyRing().publicKeyRing(key); + PGPSignature signature = BCUtil.readSignatures(sig).get(0); + + try { + SignatureChainValidator.validateSignatureChain(signature, getSignedData(data), publicKeys, policy, validationDate); + } catch (SignatureValidationException e) { + // CHECKSTYLE:OFF + e.printStackTrace(); + // CHECKSTYLE:ON + fail(message + ": " + e.getMessage()); + } + } + + private void expectSignatureValidationFails(String key, String message) throws IOException { + PGPPublicKeyRing publicKeys = PGPainless.readKeyRing().publicKeyRing(key); + PGPSignature signature = BCUtil.readSignatures(sig).get(0); + + assertThrows(SignatureValidationException.class, () -> + SignatureChainValidator.validateSignatureChain( + signature, getSignedData(data), publicKeys, policy, validationDate), + message); + } + + private static InputStream getSignedData(String data) { + return new ByteArrayInputStream(data.getBytes(StandardCharsets.UTF_8)); + } +} diff --git a/pgpainless-core/src/test/java/org/pgpainless/signature/IgnoreMarkerPackets.java b/pgpainless-core/src/test/java/org/pgpainless/signature/IgnoreMarkerPackets.java new file mode 100644 index 00000000..e5bcb724 --- /dev/null +++ b/pgpainless-core/src/test/java/org/pgpainless/signature/IgnoreMarkerPackets.java @@ -0,0 +1,269 @@ +/* + * Copyright 2021 Paul Schaub. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.pgpainless.signature; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +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.PGPSignature; +import org.bouncycastle.util.io.Streams; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; +import org.pgpainless.PGPainless; +import org.pgpainless.decryption_verification.DecryptionStream; +import org.pgpainless.decryption_verification.OpenPgpMetadata; +import org.pgpainless.key.OpenPgpV4Fingerprint; +import org.pgpainless.key.protection.SecretKeyRingProtector; +import org.pgpainless.key.util.KeyRingUtils; +import org.pgpainless.util.BCUtil; + +/** + * Test if marker packets are being ignored properly. + * + * @see Sequoia Test-Suite + */ +public class IgnoreMarkerPackets { + + private static final String KEY = "-----BEGIN PGP PRIVATE KEY BLOCK-----\n" + + "Comment: Bob's OpenPGP Transferable Secret Key\n" + + "\n" + + "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\n" + + "=miES\n" + + "-----END PGP PRIVATE KEY BLOCK-----\n"; + + @Test + public void markerPlusDetachedSignature() throws IOException, PGPException { + String sig = "-----BEGIN PGP SIGNATURE-----\n" + + "\n" + + "ygNQR1DCwTsEAAEKAG8FgmB9Y8YJEPv8yCoBXnMwRxQAAAAAAB4AIHNhbHRAbm90\n" + + "YXRpb25zLnNlcXVvaWEtcGdwLm9yZ1j1pQ8+YA70OJUxn1bZxiCar4WPrLMuM2By\n" + + "IITRjS1OFiEE0aZuGiOxgsmYD3iM+/zIKgFeczAAAOdzDACDhEptUvTFB7gx4YYG\n" + + "fFCaPxpFNo8zKnlcB2g1cFkrKEpZ/2It3ozf0beL81TUaj7G0Z4iJVDR4ei6Zrdt\n" + + "93GZRx+zQ6h3Wpj3TAi9mTHx5VRrMKK32o6VwRPuZy/KYCrst/eaM9LdhvAGsevR\n" + + "aQfopMB1xS+/8ySGimOfD6NwUWuLiOUr9fvAf3UyhpZiHL4UJ2mB1rTbQJtM++yf\n" + + "U48k+YsOVas/7B9qxlw3XsYvjVaFcTrKOj0lBn2uy2NMJje9dG+ll1lfdDkaqFFM\n" + + "FNgiJqGeoQ0whIsURurhzcY5zgujEw0qXRLMblI+g+yw2THrNx07EArnr2WzVzIP\n" + + "ifMu939eqm+mP0NKA1jVAPIIm92ZtIKD3+YzyczIepvLx4FwU1y5eAMotc76JrAg\n" + + "VWR7+FdtSA63VnVvLBR6YX7C0PVGR6BJBLEOFcZjNoW/JhN6gpmUvJLeZkFogC+J\n" + + "+J5EAJeGsE8/f/gi6pLtgAhjCNzH0qltOZsdJAfXqmd0NJ4=\n" + + "=5tQ4\n" + + "-----END PGP SIGNATURE-----\n"; + + PGPSecretKeyRing secretKeys = PGPainless.readKeyRing().secretKeyRing(KEY); + PGPPublicKeyRing publicKeys = KeyRingUtils.publicKeyRingFrom(secretKeys); + String data = "Marker + Detached signature"; + PGPSignature signature = BCUtil.readSignatures(sig).get(0); + + DecryptionStream decryptionStream = PGPainless.decryptAndOrVerify().onInputStream(new ByteArrayInputStream(data.getBytes(StandardCharsets.UTF_8))) + .doNotDecrypt() + .verifyDetachedSignature(signature) + .verifyWith(publicKeys) + .ignoreMissingPublicKeys() + .build(); + + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + + Streams.pipeAll(decryptionStream, outputStream); + + decryptionStream.close(); + OpenPgpMetadata metadata = decryptionStream.getResult(); + assertTrue(metadata.getVerifiedSignatures().containsKey(new OpenPgpV4Fingerprint("D1A66E1A23B182C9980F788CFBFCC82A015E7330"))); + } + + @Test + public void markerPlusEncryptedMessage() throws IOException, PGPException { + String msg = "-----BEGIN PGP MESSAGE-----\n" + + "\n" + + "ygNQR1DBwMwDfC+qTfk8N7IBC/4sXk4Al5Hdl38XCky41A1+HdJcDbXJ70/OY34M\n" + + "QYvco/Yk5Lb5XXg2adtSlwM2C3d6a8JMwmU7qFP/pEDXijJiTD+NabbsWO2+BwGd\n" + + "H9aTzAQhUj8PK4io5Q4SyxELosp9uO5XCkMkps5ev91mACwxm2p79tp7qkj8h4Q/\n" + + "3j8Hc9Ea4o0WCKuTIO1p42nX7gHMaUPkmZqnUxhN7ZkUgC4AHsjDgteK0viUdkmg\n" + + "aLza3TwN/e+vgnO8ypH1wBfdkvW+Ose5WPv587XGHZLsTQI6v2WeJo3K6KeuYXGI\n" + + "lxx0kLnaUL+9TswQAfU1jeh3OEy/eUIsMXc+4miYmE0cg4QxfP3ERyeziJpkzYgJ\n" + + "hGwK6cNfQiUXpWpyaAZ1vkCPEc074HIriGssq/CbkqUyUtbMzRuqfujZfMHiFkRc\n" + + "VvHkR3gzvYlMFhSjHVM2Dx9wJWmQqStdeHKfnZg4fZXDD7/zy+xRPVQgQBWelSTR\n" + + "4etbOP4OKLDX4LttGEPwy45KJvLSwZgBbU64o/RGNEbUt6dAXy3QeU5AkVR3h94f\n" + + "sda0b04cn2ZAIAuvWdF5MsgxxACFYOIHwobHzZGW54TTpTMzLVKJo8XuOuGUQE58\n" + + "jKJ+EiXZ3+TrItbhinUkZBdS59cleq2kNZ2dmTZ/ZkBwcOK1hH//Q8qrdLugm+h0\n" + + "YD/OOKudZTEu93TISGb6VeeWsb8UVjncbb2S/A/9/huFgUNYu7zma4231Y6OFx2J\n" + + "0tMVA+trKRWNhoacGwTl4mZnel42+IlgY6qVU+oTOCOtWNzuAeyj7PjnSMizv14u\n" + + "PvVaWDJv61yxNrsnLRjUX0D+CcrYMn10672ICDKg+L9SiXe87saBmsegDYSQWwE9\n" + + "WOtCWaEnKOSjD/Fewy/547dQJQihb9lG37wdM24t+J4qdkPkYKsUMhE3NcdrtLqJ\n" + + "QO7qDhEpxoX8lloNcEAFo0p+HqgcKX86/AzDYHxoLHKqDOYUQEHcKCfwA7mSXBDV\n" + + "JaOSO5Z2Jz+4HwvnD2ZgHP+qgctx87M5AgUQzlHm5mmBj3U3dvQQr6vIB4xtWKuy\n" + + "DganN7X2Jb5wlODBntdlyoM7FUHE1GYsHI2HkGl6d3bGAcEQdy9NjQyiVuWBqbr8\n" + + "OFDpFglcc70anHB091USc25LO23IhhXQ9ORalzULfoixf5lo6lmW3MPJGuYAhoIp\n" + + "SA8m91mkrqvtBckHA7xE2LdDM5JbAFNPYZzvzz5pAgYTEQp1mJB25Va1G2QHZhos\n" + + "dwvafpzRdOUaHM7lpvzTn3o3rM/Ntqfb6wn7GylsFYNpq+Rgtt6Mea68yTh+AeQ=\n" + + "=VSZ3\n" + + "-----END PGP MESSAGE-----\n"; + + PGPSecretKeyRing secretKeys = PGPainless.readKeyRing().secretKeyRing(KEY); + PGPPublicKeyRing publicKeys = KeyRingUtils.publicKeyRingFrom(secretKeys); + String data = "Marker + Encrypted Message"; + + DecryptionStream decryptionStream = PGPainless.decryptAndOrVerify().onInputStream(new ByteArrayInputStream(msg.getBytes(StandardCharsets.UTF_8))) + .decryptWith(SecretKeyRingProtector.unprotectedKeys(), secretKeys) + .verifyWith(publicKeys) + .ignoreMissingPublicKeys() + .build(); + + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + + Streams.pipeAll(decryptionStream, outputStream); + + decryptionStream.close(); + assertArrayEquals(data.getBytes(StandardCharsets.UTF_8), outputStream.toByteArray()); + OpenPgpMetadata metadata = decryptionStream.getResult(); + assertTrue(metadata.getVerifiedSignatures().containsKey(new OpenPgpV4Fingerprint("D1A66E1A23B182C9980F788CFBFCC82A015E7330"))); + } + + @Test + @Disabled // TODO: Fix upstreamed. Enable once BC is bumped + public void markerPlusCertificate() throws IOException { + String pubKeyBlock = "-----BEGIN PGP PUBLIC KEY BLOCK-----\n" + + "\n" + + "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\n" + + "qOFfY9U0Ql3ddlHNpdkTZoAx\n" + + "=TrY7\n" + + "-----END PGP PUBLIC KEY BLOCK-----\n"; + + PGPPublicKeyRing publicKeys = PGPainless.readKeyRing().publicKeyRing(pubKeyBlock); + assertEquals(new OpenPgpV4Fingerprint("D1A66E1A23B182C9980F788CFBFCC82A015E7330"), new OpenPgpV4Fingerprint(publicKeys)); + assertNotNull(publicKeys.getPublicKey(new OpenPgpV4Fingerprint("1DDCE15F09217CEE2F3B37607C2FAA4DF93C37B2").getKeyId())); + } +} diff --git a/pgpainless-core/src/test/java/org/pgpainless/signature/KeyRevocationTest.java b/pgpainless-core/src/test/java/org/pgpainless/signature/KeyRevocationTest.java new file mode 100644 index 00000000..0b0f7788 --- /dev/null +++ b/pgpainless-core/src/test/java/org/pgpainless/signature/KeyRevocationTest.java @@ -0,0 +1,265 @@ +/* + * Copyright 2021 Paul Schaub. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.pgpainless.signature; + +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.Date; + +import org.bouncycastle.openpgp.PGPPublicKeyRing; +import org.bouncycastle.openpgp.PGPSignature; +import org.junit.jupiter.api.Test; +import org.pgpainless.PGPainless; +import org.pgpainless.util.BCUtil; + +public class KeyRevocationTest { + + private static final String data = "Hello, World"; + + @Test + public void subkeySignsPrimaryKeyRevokedNoReason() throws IOException, SignatureValidationException { + String key = "-----BEGIN PGP ARMORED FILE-----\n" + + "Comment: ASCII Armor added by openpgp-interoperability-test-suite\n" + + "\n" + + "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\n" + + "=2oji\n" + + "-----END PGP ARMORED FILE-----\n"; + String sigT0 = "-----BEGIN PGP ARMORED FILE-----\n" + + "Comment: ASCII Armor added by openpgp-interoperability-test-suite\n" + + "\n" + + "wsBzBAABCgAGBYJYaEaAACEJEGhPrWLcA4+7FiEE8tFQpP6Ykl1R6RU5aE+tYtwD\n" + + "j7ttqgf9Gp4T5Q19cNL9Eyz1nlw11HDHT1wxfGHU5li76y7oo4Jqim15sEPDJWmc\n" + + "IpYVrczpCI95aCuaE6yfzpjZlXMzftwex3DjM98vyZH4W9teKcOnpAVjn3dLoQJA\n" + + "i4fiq3VaLgl+1OYOwu3DmwGKJZubHM3oPia9pbuyvL5Scvx+QCG0AVnssnt2QswG\n" + + "uU6J35QgBdjG2iC043sUoyxTSk929iamdQnOGchjcaATb4E4+HvtkRy4IirKxiKK\n" + + "c535BHJRLgWQzUcDDZ5kHf3SPLTNsigFFEwUyf5voFcn/DSMWSzPaVecaafTVJW2\n" + + "u8G1R5mjuxDRup8p//X1BSk1FpSmvw==\n" + + "=3/dv\n" + + "-----END PGP ARMORED FILE-----\n"; + String sigT1T2 = "-----BEGIN PGP ARMORED FILE-----\n" + + "Comment: ASCII Armor added by openpgp-interoperability-test-suite\n" + + "\n" + + "wsBzBAABCgAGBYJa564AACEJEGhPrWLcA4+7FiEE8tFQpP6Ykl1R6RU5aE+tYtwD\n" + + "j7ufRgf/QOsaJZgQaQ5daQrfBItOEcW+5acgY1TCwMVmc/nzBqC32TOvMaM3dypf\n" + + "wJbqzxHQIes+ivKDF872VWlMA2BErifpdsogbS0pdik/qU+AjMhr188xKpZKG/IY\n" + + "6BtuUPeSpsimx3UeEN3kt79fMtewBo0EXo3ujCyPpIF/9Vpd7L9jlJSvRBuM0/aR\n" + + "gbRsclEw4JZ98B3t7F3rLmx+F57Zre0ctzT4tHE6IaCYpEClr6Tepj/UxExYOt2l\n" + + "hKgVN8Wsuug7XYdOFmxqrV967m3CTnF8AspmxwgKK6NXjVLplfqij7Rp2URPWrWn\n" + + "Pp3CQRGUWJdMeMY9P1MpFq6XiXtftw==\n" + + "=Ld1q\n" + + "-----END PGP ARMORED FILE-----\n"; + String sigT2T3 = "-----BEGIN PGP ARMORED FILE-----\n" + + "Comment: ASCII Armor added by openpgp-interoperability-test-suite\n" + + "\n" + + "wsBzBAABCgAGBYJdP4iAACEJEGhPrWLcA4+7FiEE8tFQpP6Ykl1R6RU5aE+tYtwD\n" + + "j7sYXQf8CZw6Kx4oyI8ZJ2c9RjVZmUFEirAoXH7oYA+Ye+wSAY9OtqE/x2SOYaC6\n" + + "QHiB93/wkvpqCVkLy2lenzpD7WXLbuFZ+/5jXp1o+sVHXfLSWo6pfIhOjj9FSr8x\n" + + "qqlqUfKwkbA6WYgju+qKC35SYdSptix7unaFkO41UdsM8wGQh880HSRMBMFPzg07\n" + + "3hMNYXoEJjFlIkxJSMu2WL7N0Q/4xE2iJftsQjUYAtJ/C/YK2I6dhW+CZremnv5R\n" + + "/8W+oH5Q63lYU8YL4wYnJQvkHjKs/kjLpoPmqL8kdHjndSpU+KOYr5w61XuEp2hp\n" + + "r8trtljVaVIQX2rYawSlqKkWXt0yag==\n" + + "=xVd8\n" + + "-----END PGP ARMORED FILE-----\n"; + String sigT3Now = "-----BEGIN PGP ARMORED FILE-----\n" + + "Comment: ASCII Armor added by openpgp-interoperability-test-suite\n" + + "\n" + + "wsBzBAABCgAGBYJe/cFVACEJEGhPrWLcA4+7FiEE8tFQpP6Ykl1R6RU5aE+tYtwD\n" + + "j7vmhQf/UB456IXc8ub8HTExab1d5KqOGSUWpwIznTu8Wk8YuzWKEE8ZeZvPmv8K\n" + + "iJfBoOx59YrlOfpLAKcTR9Ql+IFbWsIkqPxX7U1SGldhfQm7iaK5Dn6+mmQFOz/s\n" + + "ZCIavWJ7opsp11JmQAt4FFojv789YswaS7VI1zjDj7EeRiATtzna/GqCYgeCM0cc\n" + + "sIe/1j1H2oh9YvYIpPMSGDJPo7T1Ji4Ie3iEQEYNYPuw1Hb7gWYncHXZGJq1nDf/\n" + + "WAoI9gSFagpsPW0k9cfEAOVNLNYSyi0CSnQWSjq8THbHKiLPFwsP3hvT2oHycWbK\n" + + "u5SfXaTsbMeVQJNdjCNsHq2bOXPGLw==\n" + + "=2BW4\n" + + "-----END PGP ARMORED FILE-----\n"; + + PGPPublicKeyRing publicKeys = PGPainless.readKeyRing().publicKeyRing(key); + PGPSignature t0 = BCUtil.readSignatures(sigT0).get(0); + PGPSignature t1t2 = BCUtil.readSignatures(sigT1T2).get(0); + PGPSignature t2t3 = BCUtil.readSignatures(sigT2T3).get(0); + PGPSignature t3now = BCUtil.readSignatures(sigT3Now).get(0); + + assertThrows(SignatureValidationException.class, () -> SignatureChainValidator.validateSignatureChain(t0, + new ByteArrayInputStream(data.getBytes(StandardCharsets.UTF_8)), + publicKeys, PGPainless.getPolicy(), new Date())); + assertThrows(SignatureValidationException.class, () -> SignatureChainValidator.validateSignatureChain(t1t2, + new ByteArrayInputStream(data.getBytes(StandardCharsets.UTF_8)), + publicKeys, PGPainless.getPolicy(), new Date())); + assertThrows(SignatureValidationException.class, () -> SignatureChainValidator.validateSignatureChain(t2t3, + new ByteArrayInputStream(data.getBytes(StandardCharsets.UTF_8)), + publicKeys, PGPainless.getPolicy(), new Date())); + assertThrows(SignatureValidationException.class, () -> SignatureChainValidator.validateSignatureChain(t3now, + new ByteArrayInputStream(data.getBytes(StandardCharsets.UTF_8)), + publicKeys, PGPainless.getPolicy(), new Date())); + } + + /** + * Test signature verification with an evolving signing subkey. + * + * @see Sequoia Test-Suite + */ + @Test + public void subkeySignsPrimaryKeyNotRevoked() throws IOException, SignatureValidationException { + String key = "-----BEGIN PGP ARMORED FILE-----\n" + + "Comment: ASCII Armor added by openpgp-interoperability-test-suite\n" + + "\n" + + "xsBNBFpJegABCAC1ePFquP0135m8DYhcybhv7l+ecojitFOd/jRM7hCczIqKgalD\n" + + "1Ro1gNr3VmH6FjRIKIvGT+sOzCKne1v3KyAAPoxtwxjkATTKdOGo15I6v5ZjmO1d\n" + + "rLQOLSt1TF7XbQSt+ns6PUZWJL907DvECUU5b9FkNUqfQ14QqY+gi7MOyAQez3b7\n" + + "Pg5Cyz/kVWQ6TSMW/myDEDEertQ4rDBsptEDFHCC2+iF4hO2LqfiCriu5qyLcKCQ\n" + + "pd6dEuwJQ/jjT0D9A9Fwf+i04x6ZPKSU9oNAWqn8OSAq3/0B/hu9V+0U0iHPnJxe\n" + + "quykvJk7maxhiGhxBWYXTvDJmoon0NOles7LABEBAAHCwHwEHwEKAA8Fgl4L4QAC\n" + + "FQoCmwMCHgEAIQkQaE+tYtwDj7sWIQTy0VCk/piSXVHpFTloT61i3AOPu8ffB/9Q\n" + + "60dg60qhA2rPnd/1dCL2B+c8RWnq44PpijE3gA1RQvcRQE5jNzMSo/MnG0mSL5wH\n" + + "eTsjSd/DRI3nHP06rs6Qub11NoKhNuya3maz9gyzeZMc/jNib83/BzFCrxsSQm+9\n" + + "WHurxXeWXOPMLZs3xS/jG0EDtCJ2Fm4UF19fcIydwN/ssF4NGpfCY82+wTSx4joI\n" + + "3cRKObCFJaaBgG5nl+eFr7cfjEIuqCJCaQsXiqBe7d6V3KqN18t+CgSaybMZXcys\n" + + "Q/USxEkLhIB2pOZwcz4E3TTFgxRAxcr4cs4Bd2PRz3Z5FKTzo0ma/Ft0UfFJR+fC\n" + + "cs55+n6kC9K0y/E7BY2hwsB8BB8BCgAPBYJaSXoAAhUKApsDAh4BACEJEGhPrWLc\n" + + "A4+7FiEE8tFQpP6Ykl1R6RU5aE+tYtwDj7uqDQf7BqTD6GNTwXPOt/0kHQPYmbdI\n" + + "tX+pWP+o3jaB6VTHDXcn27bttA5M82EXZfae4+bC1dMB+1uLal4ciVgO9ImJC9Nw\n" + + "s5fc3JH4R5uuSvpjzjudkJsGu3cAKE3hwiT93Mi6t6ENpLCDSxqxzAmfoOQbVJYW\n" + + "Y7gP7Z4Cj0IAP29aprEc0JWoMjHKpKgYF6u0sWgHWBuEXk/6o6GYb2HZYK4ycpY2\n" + + "WXKgVhy7/iQDYO1FOfcWQXHVGLn8OzILjobKohNenTT20ZhAASi3LUDSDMTQfxSS\n" + + "Vt0nhzWuXJJ4R8PzUVeRJ0A0oMyjZVHivHC6GwMsiQuSUTx8e/GnOByOqfGne80S\n" + + "anVsaWV0QGV4YW1wbGUub3JnwsBzBBMBCgAGBYJaSXoAACEJEGhPrWLcA4+7FiEE\n" + + "8tFQpP6Ykl1R6RU5aE+tYtwDj7tDfQf+PnxsIFu/0juKBUjjtAYfRzkrrYtMepPj\n" + + "taTvGfo1SzUkX/6F/GjdSeVg5Iq6YcBrj8c+cB3EoZpHnScTgWQHwceWQLd9Hhbg\n" + + "TrUNvW1eg2CVzN0RBuYMtWu9JM4pH7ssJW1NmN+/N9B67qb2y+JfBwH/la508NzC\n" + + "rl3xWTxjT5wNy+FGkNZg23s/0qlO2uxCjc+mRAuAlp5EmTOVWOIBbM0xttjBOx39\n" + + "ZmWWQKJZ0nrFjK1jppHqazwWWNX7RHkK81tlbSUtOPoTIJDz38NaiyMcZH3p9okN\n" + + "3DU4XtF+oE18M+Z/E0xUQmumbkajFzcUjmd7enozP5BnGESzdNS5Xc7ATQRaSsuA\n" + + "AQgAykb8tqlWXtqHGGkBqAq3EnpmvBqrKvqejjtZKAXqEszJ9NlibCGUuLwnNOVO\n" + + "R/hcOUlOGH+cyMcApBWJB+7d/83K1eCCdv88nDFVav7hKLKlEBbZJNHgHpJ313pl\n" + + "etzCR4x3STEISrEtO71l2HBdrKSYXaxGgILxYwcSi3i2EjzxRDy+0zyy8s7d+OD5\n" + + "ShFYexgSrKH3Xx1cxQAJzGGJVx75HHU9GVh3xHwJ7nDm26KzHegG2XPIBXJ2z8vm\n" + + "sSVTWyj0AjT4kVVapN0f84AKKjyQ7fguCzXGHFV9jmxDx+YH+9HhjIrHSzbDx6+4\n" + + "wyRsxj7Su+hu/bogJ28nnbTzQwARAQABwsGsBBgBCgAJBYJeC+EAApsCAVcJEGhP\n" + + "rWLcA4+7wHSgBBkBCgAGBYJeC+EAACEJEEpyNKOhITplFiEEUXksDkji/alOk7kR\n" + + "SnI0o6EhOmWnSQgAiu/zdEmHf6Wbwfbs/c6FObfPxGuzLkQr4fZKcqK81MtR1mh1\n" + + "WVLJRgXW4u8cHtZyH5pThngMcUiyzWsa0g6Jaz8w6sr/Wv3e1qdTCITskMrWCDao\n" + + "DhD2teAjmWuk9u8ZBPJ7xhme+Q/UQ90xomQ/NdCJafirk2Ds92p7N7RKSES1KywB\n" + + "hfONJbPw1TdZ9Mts+DGjkucYbe+ZzPxrLpWXur1BSGEqBtTAGW3dS/xpwBYNlhas\n" + + "XHjYMr4HeIYYYOx+oR5JgDYoVfp2k0DwK/QXogbja+/Vjv+LrXdNY0t1bA35FNnl\n" + + "637M8iCNrXvIoRFARbNyge8c/jSWGPLB/tIyNhYhBPLRUKT+mJJdUekVOWhPrWLc\n" + + "A4+7FLwIAK1GngNMnruxWM4EoghKTSmKNrd6p/d3Wsd+y2019A7Nz+4OydkEDvmN\n" + + "VVhlUcfgOf2L6Bf63wdN0ho+ODhCuNSqHe6NL1NhdITbMGnDdKb57IIB9CuJFpIL\n" + + "n9LZ1Ei6JPEpmpiSEaL+VJt1fMnfc8jtF8N3WcRVfJsq1aslXe8Npg709YVgm2OX\n" + + "sNWgktl9fciu4ENTybQGjpN9WTa1aU1nkko6NUoIfjtM+PO4VU7x00M+dTJsYGhn\n" + + "c96EtT8EfSAIFBKZRAkMBFhEcdkxa8hCKI3+nyI3gTq0TcFST3wy05AmoV7wlgzU\n" + + "AMsW7MV2NpG7fJul2Q7puKw+udBUc0TCwawEGAEKAAkFglro/4ACmwIBVwkQaE+t\n" + + "YtwDj7vAdKAEGQEKAAYFglro/4AAIQkQSnI0o6EhOmUWIQRReSwOSOL9qU6TuRFK\n" + + "cjSjoSE6ZeFHB/92jhUTXrEgho6DYhmVFuXa3NGhAjIyZo3yYHMoL9aZ3DUyjxhA\n" + + "yRDpI2CrahQ4JsPhej2m+3fHWa34/tb5mpHYFWEahQvdWSFCcU7p2NUKcq2zNA6i\n" + + "xO2+fQQhmbrYR+TFxYmhLjCGUNt14E/XaIL1VxPQOA5KbiRPpa8BsUNlNik9ASPW\n" + + "yn0ZA0rjJ1ZV7nJarXVbuZDEcUDuDm3cA5tup7juB8fTz2BDcg3Ka+OcPEz0GgZf\n" + + "q9K40di3r9IHLBhNPHieFVIj9j/JyMnTvVOceM3J/Rb0MCWJVbXNBKpRMDibCQh+\n" + + "7fbqyQEM/zIpmk0TgBpTZZqMP0gxYdWImT1IFiEE8tFQpP6Ykl1R6RU5aE+tYtwD\n" + + "j7tOtggAhgAqvOB142L2SkS3ZIdwuhAtWLPHCtEwBOqGtP8Z204rqAmbnJymzo77\n" + + "+OT+SScnDTrwzOUJnCi0qPUxfuxhvHxnBxBIjaoMcF++iKsqF1vf6WuXOjbJ1N8I\n" + + "08pB2niht5MxIZ9rMGDeASj79X7I9Jjzsd30OVGfTZyy3VyYPxcJ6n/sZocNmaTv\n" + + "0/F8K3TirSH6JDXdY5zirRi99GJ3R+AL6OzxrChuvLFSEtIRJrW5XVfg3whc0XD+\n" + + "5J9RsHoL33ub9ZhQHFKsjrf0nGYbEFwMhSdysfTYYMbwKi0CcQeQtPP0Y87zSrya\n" + + "jDMFXQS0exdvhN4AXDlPlB3Rrkj7CQ==\n" + + "=+VTZ\n" + + "-----END PGP ARMORED FILE-----\n"; + String sig = "-----BEGIN PGP ARMORED FILE-----\n" + + "Comment: ASCII Armor added by openpgp-interoperability-test-suite\n" + + "\n" + + "wsBzBAABCgAGBYJdP4iAACEJEEpyNKOhITplFiEEUXksDkji/alOk7kRSnI0o6Eh\n" + + "OmUYXQf/dGNZay40bZEpcnxYl+Kq+gRQESeDhg/xOfGfSCLQncMH+UYPaUKANC2g\n" + + "CfMNN1wd8ZWrvgyTVo3TVfK1P1RYa9nrvKoKN3bjsFcY6V7VciPW58xVNsuxsEEC\n" + + "GEH96TQy+FsP680tRnzQ3Dbw/JT6o6Xi+HLf4JVFceapBgyth61E5gN5w3azxVFr\n" + + "GfwIfHvepOjCIq9tRZsRFEBp3XVZ/AF+zQMG5nfIVSm1kVtZjb7KXc3Bj48DVrmb\n" + + "XLxPJz7PLY0cgOsXXxROIdtFT+mbVQg2j247hxnhItwtLeQrafb5T8ibeihRlkhK\n" + + "1tfKv31EP8tAVqgTjw+qD32bH9h77w==\n" + + "=MOaJ\n" + + "-----END PGP ARMORED FILE-----\n"; + + PGPPublicKeyRing publicKeys = PGPainless.readKeyRing().publicKeyRing(key); + PGPSignature signature = BCUtil.readSignatures(sig).get(0); + + SignatureChainValidator.validateSignatureChain(signature, + new ByteArrayInputStream(data.getBytes(StandardCharsets.UTF_8)), + publicKeys, PGPainless.getPolicy(), new Date()); + } +} diff --git a/pgpainless-core/src/test/java/org/pgpainless/signature/KeyRingValidationTest.java b/pgpainless-core/src/test/java/org/pgpainless/signature/KeyRingValidationTest.java new file mode 100644 index 00000000..376c4132 --- /dev/null +++ b/pgpainless-core/src/test/java/org/pgpainless/signature/KeyRingValidationTest.java @@ -0,0 +1,136 @@ +/* + * Copyright 2021 Paul Schaub. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.pgpainless.signature; + +import java.io.IOException; +import java.util.Collections; +import java.util.Date; + +import org.bouncycastle.openpgp.PGPException; +import org.bouncycastle.openpgp.PGPPublicKeyRing; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.pgpainless.PGPainless; +import org.pgpainless.algorithm.HashAlgorithm; +import org.pgpainless.key.KeyRingValidator; +import org.pgpainless.policy.Policy; +import org.pgpainless.util.ArmorUtils; +import org.pgpainless.util.TestUtils; + +public class KeyRingValidationTest { + + private static Policy.HashAlgorithmPolicy defaultSignatureHashAlgorithmPolicy; + + @BeforeAll + public static void setCustomPolicy() { + Policy policy = PGPainless.getPolicy(); + defaultSignatureHashAlgorithmPolicy = policy.getSignatureHashAlgorithmPolicy(); + + policy.setSignatureHashAlgorithmPolicy(new Policy.HashAlgorithmPolicy(HashAlgorithm.SHA256, Collections.singletonList(HashAlgorithm.SHA256))); + } + + @AfterAll + public static void resetCustomPolicy() { + PGPainless.getPolicy().setSignatureHashAlgorithmPolicy(defaultSignatureHashAlgorithmPolicy); + } + + @Test + public void testSignatureValidationOnPrimaryKey() throws IOException, PGPException { + String key = "-----BEGIN PGP ARMORED FILE-----\n" + + "Comment: ASCII Armor added by openpgp-interoperability-test-suite\n" + + "\n" + + "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\n" + + "exdvhN4AXDlPlB3Rrkj7CQ==\n" + + "=qQpG\n" + + "-----END PGP ARMORED FILE-----\n"; + + PGPPublicKeyRing publicKeys = PGPainless.readKeyRing().publicKeyRing(key); + + Date validationDate = TestUtils.getUTCDate("2019-05-01 00:00:00 UTC"); + Policy policy = PGPainless.getPolicy(); + PGPPublicKeyRing evaluated = KeyRingValidator.validate(publicKeys, policy, validationDate); + + // CHECKSTYLE:OFF + System.out.println(ArmorUtils.toAsciiArmoredString(evaluated)); + // CHECKSTYLE:ON + + + } +} diff --git a/pgpainless-core/src/test/java/org/pgpainless/signature/SignatureChainValidatorTest.java b/pgpainless-core/src/test/java/org/pgpainless/signature/SignatureChainValidatorTest.java new file mode 100644 index 00000000..b89b8382 --- /dev/null +++ b/pgpainless-core/src/test/java/org/pgpainless/signature/SignatureChainValidatorTest.java @@ -0,0 +1,1309 @@ +/* + * Copyright 2021 Paul Schaub. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.pgpainless.signature; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.util.Date; + +import org.bouncycastle.openpgp.PGPPublicKeyRing; +import org.bouncycastle.openpgp.PGPSignature; +import org.junit.jupiter.api.Test; +import org.pgpainless.PGPainless; +import org.pgpainless.policy.Policy; +import org.pgpainless.util.BCUtil; + +public class SignatureChainValidatorTest { + + /** + * Primary Key signs and is hard revoked with reason: unknown. + * + * @see Sequoia Test Suite + */ + @Test + public void testPrimaryKeySignsAndIsHardRevokedUnknown() throws IOException { + String key = "-----BEGIN PGP ARMORED FILE-----\n" + + "Comment: ASCII Armor added by openpgp-interoperability-test-suite\n" + + "\n" + + "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\n" + + "Y87zSryajDMFXQS0exdvhN4AXDlPlB3Rrkj7CQ==\n" + + "=MhJL\n" + + "-----END PGP ARMORED FILE-----\n"; + String sigPredatesPrimaryKey = "-----BEGIN PGP ARMORED FILE-----\n" + + "Comment: ASCII Armor added by openpgp-interoperability-test-suite\n" + + "\n" + + "wsBzBAABCgAGBYJYaEaAACEJEGhPrWLcA4+7FiEE8tFQpP6Ykl1R6RU5aE+tYtwD\n" + + "j7ttqgf9Gp4T5Q19cNL9Eyz1nlw11HDHT1wxfGHU5li76y7oo4Jqim15sEPDJWmc\n" + + "IpYVrczpCI95aCuaE6yfzpjZlXMzftwex3DjM98vyZH4W9teKcOnpAVjn3dLoQJA\n" + + "i4fiq3VaLgl+1OYOwu3DmwGKJZubHM3oPia9pbuyvL5Scvx+QCG0AVnssnt2QswG\n" + + "uU6J35QgBdjG2iC043sUoyxTSk929iamdQnOGchjcaATb4E4+HvtkRy4IirKxiKK\n" + + "c535BHJRLgWQzUcDDZ5kHf3SPLTNsigFFEwUyf5voFcn/DSMWSzPaVecaafTVJW2\n" + + "u8G1R5mjuxDRup8p//X1BSk1FpSmvw==\n" + + "=3/dv\n" + + "-----END PGP ARMORED FILE-----\n"; + String sigSubkeyNotBound = "-----BEGIN PGP ARMORED FILE-----\n" + + "Comment: ASCII Armor added by openpgp-interoperability-test-suite\n" + + "\n" + + "wsBzBAABCgAGBYJa564AACEJEGhPrWLcA4+7FiEE8tFQpP6Ykl1R6RU5aE+tYtwD\n" + + "j7ufRgf/QOsaJZgQaQ5daQrfBItOEcW+5acgY1TCwMVmc/nzBqC32TOvMaM3dypf\n" + + "wJbqzxHQIes+ivKDF872VWlMA2BErifpdsogbS0pdik/qU+AjMhr188xKpZKG/IY\n" + + "6BtuUPeSpsimx3UeEN3kt79fMtewBo0EXo3ujCyPpIF/9Vpd7L9jlJSvRBuM0/aR\n" + + "gbRsclEw4JZ98B3t7F3rLmx+F57Zre0ctzT4tHE6IaCYpEClr6Tepj/UxExYOt2l\n" + + "hKgVN8Wsuug7XYdOFmxqrV967m3CTnF8AspmxwgKK6NXjVLplfqij7Rp2URPWrWn\n" + + "Pp3CQRGUWJdMeMY9P1MpFq6XiXtftw==\n" + + "=Ld1q\n" + + "-----END PGP ARMORED FILE-----\n"; + String sigPrimaryKeyRevoked = "-----BEGIN PGP ARMORED FILE-----\n" + + "Comment: ASCII Armor added by openpgp-interoperability-test-suite\n" + + "\n" + + "wsBzBAABCgAGBYJdP4iAACEJEGhPrWLcA4+7FiEE8tFQpP6Ykl1R6RU5aE+tYtwD\n" + + "j7sYXQf8CZw6Kx4oyI8ZJ2c9RjVZmUFEirAoXH7oYA+Ye+wSAY9OtqE/x2SOYaC6\n" + + "QHiB93/wkvpqCVkLy2lenzpD7WXLbuFZ+/5jXp1o+sVHXfLSWo6pfIhOjj9FSr8x\n" + + "qqlqUfKwkbA6WYgju+qKC35SYdSptix7unaFkO41UdsM8wGQh880HSRMBMFPzg07\n" + + "3hMNYXoEJjFlIkxJSMu2WL7N0Q/4xE2iJftsQjUYAtJ/C/YK2I6dhW+CZremnv5R\n" + + "/8W+oH5Q63lYU8YL4wYnJQvkHjKs/kjLpoPmqL8kdHjndSpU+KOYr5w61XuEp2hp\n" + + "r8trtljVaVIQX2rYawSlqKkWXt0yag==\n" + + "=xVd8\n" + + "-----END PGP ARMORED FILE-----\n"; + String sigPrimaryKeyRevalidated = "-----BEGIN PGP ARMORED FILE-----\n" + + "Comment: ASCII Armor added by openpgp-interoperability-test-suite\n" + + "\n" + + "wsBzBAABCgAGBYJe/cFVACEJEGhPrWLcA4+7FiEE8tFQpP6Ykl1R6RU5aE+tYtwD\n" + + "j7vmhQf/UB456IXc8ub8HTExab1d5KqOGSUWpwIznTu8Wk8YuzWKEE8ZeZvPmv8K\n" + + "iJfBoOx59YrlOfpLAKcTR9Ql+IFbWsIkqPxX7U1SGldhfQm7iaK5Dn6+mmQFOz/s\n" + + "ZCIavWJ7opsp11JmQAt4FFojv789YswaS7VI1zjDj7EeRiATtzna/GqCYgeCM0cc\n" + + "sIe/1j1H2oh9YvYIpPMSGDJPo7T1Ji4Ie3iEQEYNYPuw1Hb7gWYncHXZGJq1nDf/\n" + + "WAoI9gSFagpsPW0k9cfEAOVNLNYSyi0CSnQWSjq8THbHKiLPFwsP3hvT2oHycWbK\n" + + "u5SfXaTsbMeVQJNdjCNsHq2bOXPGLw==\n" + + "=2BW4\n" + + "-----END PGP ARMORED FILE-----\n"; + + PGPPublicKeyRing publicKeys = PGPainless.readKeyRing().publicKeyRing(key); + PGPSignature predatesPrimaryKey = BCUtil.readSignatures(sigPredatesPrimaryKey).get(0); + PGPSignature unboundSubkey = BCUtil.readSignatures(sigSubkeyNotBound).get(0); + PGPSignature primaryKeyRevoked = BCUtil.readSignatures(sigPrimaryKeyRevoked).get(0); + PGPSignature primaryKeyRevalidated = BCUtil.readSignatures(sigPrimaryKeyRevalidated).get(0); + + Policy policy = PGPainless.getPolicy(); + Date validationDate = new Date(); + String data = "Hello, World"; + + assertThrows(SignatureValidationException.class, () -> SignatureChainValidator.validateSignatureChain( + predatesPrimaryKey, getSignedData(data), publicKeys, policy, validationDate), + "Signature predates primary key"); + assertThrows(SignatureValidationException.class, () -> SignatureChainValidator.validateSignatureChain( + unboundSubkey, getSignedData(data), publicKeys, policy, validationDate), + "Primary key hard revoked"); + assertThrows(SignatureValidationException.class, () -> SignatureChainValidator.validateSignatureChain( + primaryKeyRevoked, getSignedData(data), publicKeys, policy, validationDate), + "Primary key hard revoked"); + assertThrows(SignatureValidationException.class, () -> SignatureChainValidator.validateSignatureChain( + primaryKeyRevalidated, getSignedData(data), publicKeys, policy, validationDate), + "Primary key hard revoked"); + } + + /** + * Subkey signs, primary key is hard revoked with reason: unknown. + * @see Sequoia Test Suite + */ + @Test + public void testSubkeySignsPrimaryKeyIsHardRevokedUnknown() throws IOException { + String key = "-----BEGIN PGP ARMORED FILE-----\n" + + "Comment: ASCII Armor added by openpgp-interoperability-test-suite\n" + + "\n" + + "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\n" + + "Y87zSryajDMFXQS0exdvhN4AXDlPlB3Rrkj7CQ==\n" + + "=MhJL\n" + + "-----END PGP ARMORED FILE-----\n"; + String sigPredatesPrimaryKey = "-----BEGIN PGP ARMORED FILE-----\n" + + "Comment: ASCII Armor added by openpgp-interoperability-test-suite\n" + + "\n" + + "wsBzBAABCgAGBYJYaEaAACEJEEpyNKOhITplFiEEUXksDkji/alOk7kRSnI0o6Eh\n" + + "OmVtqgf/YSG+b8lY01/4oCrHoNMECDDbpI5+8WkeT0CcdlEd5TGj3AHesT6H6XmL\n" + + "ZxaHHOwtkuDh0bIAiYGl36e4ult5XZQhFIwUXGde6myLE+fpCGsBJwNu+TDIrbg3\n" + + "PGqnVZNlcU+2sP5JhJfAn8VtLENuHkbIC3+kH8xBIrkPTc0rbNBgyzX5eFO20U0D\n" + + "bHCCjfjVDpZ8l7N2NlsRYvU0kTzN5GvwbS1HnMOovF9ZKkEpzxxw6IRJIapaE2L9\n" + + "adMKIRAqrIIjfj6Z9nETd1nZE79t1zSw1trfArPaJQr46krgh1ocLQoD/c+PhB9l\n" + + "sRxQBnWERgQaDJByq0kwKSnwWAsyxw==\n" + + "=SDmD\n" + + "-----END PGP ARMORED FILE-----\n"; + String sigSigningKeyUnbound = "-----BEGIN PGP ARMORED FILE-----\n" + + "Comment: ASCII Armor added by openpgp-interoperability-test-suite\n" + + "\n" + + "wsBzBAABCgAGBYJa564AACEJEEpyNKOhITplFiEEUXksDkji/alOk7kRSnI0o6Eh\n" + + "OmWfRgf9ECjIviU1pN0GiMZGci3Vce2b42LIqH0JZApOeRDpcyXgxi3/CmdaewpT\n" + + "w9l18gsXhioRg4xUzMFrYSgyYZ9VajFggMjbeX5dSV3rsOSJSiEEyDbeGi0TcA/Y\n" + + "GUifX4EfKx5X5nI/wevnYjmruDp9SqaPLHIZK1soOoPzueZ8wKyJ9A4vVG4bvxVX\n" + + "FnwBf6mRE/0Z8IoHlRJdq0fSzW4RgX8KAtK8SfyGOnk7LDaevVuL6iE5v0Gsu0oh\n" + + "cHlI6Llm97EVxP93KZ1J7TQIi/a6PUJb5XCIw0K/iyuNuAzETgm8LVJyn6UwL4ym\n" + + "KcNieOK8Qcoivq0kCYuv/0Tbk13jVQ==\n" + + "=5hOz\n" + + "-----END PGP ARMORED FILE-----"; + String sigSubkeyRevoked = "-----BEGIN PGP ARMORED FILE-----\n" + + "Comment: ASCII Armor added by openpgp-interoperability-test-suite\n" + + "\n" + + "wsBzBAABCgAGBYJdP4iAACEJEEpyNKOhITplFiEEUXksDkji/alOk7kRSnI0o6Eh\n" + + "OmUYXQf/dGNZay40bZEpcnxYl+Kq+gRQESeDhg/xOfGfSCLQncMH+UYPaUKANC2g\n" + + "CfMNN1wd8ZWrvgyTVo3TVfK1P1RYa9nrvKoKN3bjsFcY6V7VciPW58xVNsuxsEEC\n" + + "GEH96TQy+FsP680tRnzQ3Dbw/JT6o6Xi+HLf4JVFceapBgyth61E5gN5w3azxVFr\n" + + "GfwIfHvepOjCIq9tRZsRFEBp3XVZ/AF+zQMG5nfIVSm1kVtZjb7KXc3Bj48DVrmb\n" + + "XLxPJz7PLY0cgOsXXxROIdtFT+mbVQg2j247hxnhItwtLeQrafb5T8ibeihRlkhK\n" + + "1tfKv31EP8tAVqgTjw+qD32bH9h77w==\n" + + "=MOaJ\n" + + "-----END PGP ARMORED FILE-----\n"; + String sigSubkeyRevalidated = "-----BEGIN PGP ARMORED FILE-----\n" + + "Comment: ASCII Armor added by openpgp-interoperability-test-suite\n" + + "\n" + + "wsBzBAABCgAGBYJe/cFVACEJEEpyNKOhITplFiEEUXksDkji/alOk7kRSnI0o6Eh\n" + + "OmXmhQf+LjM2vmdKYnxfVy40Vcdoy2Jov/ZD2RSMFff+fIoCXmWfEetila7v5xHj\n" + + "ZXq6aevTEN1vgW3T6Q1OjFhnGpMl9wvya9mszfn5BBKukFtLkHeay0PtYuUcrfJC\n" + + "UIQCx9PFZzgRJFyGsCqrXBc1VIe2DV3d8dq74unTeCEmWdvAZKdjoUYzRohMtcZ+\n" + + "0QctCCJE1kRFJuH/TIdxxwKPtBZfOolSlpS0Z5xxa2sILqUvQ2Dq3hBctUM4g6hB\n" + + "Y8uafI8qIMwWl4DQDzPpQ917d6J+GCdN0Aib6ZOsvmgR5wrBOFiDpRJ/W9W6+rgs\n" + + "I5V/t2y6h6gaHbanggc0cMOaMTtEKQ==\n" + + "=lkHs\n" + + "-----END PGP ARMORED FILE-----\n"; + + PGPPublicKeyRing publicKeys = PGPainless.readKeyRing().publicKeyRing(key); + PGPSignature predatesPrimaryKey = BCUtil.readSignatures(sigPredatesPrimaryKey).get(0); + PGPSignature unboundSubkey = BCUtil.readSignatures(sigSigningKeyUnbound).get(0); + PGPSignature revokedSubkey = BCUtil.readSignatures(sigSubkeyRevoked).get(0); + PGPSignature revalidatedSubkey = BCUtil.readSignatures(sigSubkeyRevalidated).get(0); + + Policy policy = PGPainless.getPolicy(); + Date validationDate = new Date(); + String data = "Hello, World"; + + assertThrows(SignatureValidationException.class, () -> SignatureChainValidator.validateSignatureChain( + predatesPrimaryKey, getSignedData(data), publicKeys, policy, validationDate), + "Signature predates primary key"); + assertThrows(SignatureValidationException.class, () -> SignatureChainValidator.validateSignatureChain( + unboundSubkey, getSignedData(data), publicKeys, policy, validationDate), + "Signing key unbound + hard revocation"); + assertThrows(SignatureValidationException.class, () -> SignatureChainValidator.validateSignatureChain( + revokedSubkey, getSignedData(data), publicKeys, policy, validationDate), + "Primary key is hard revoked"); + assertThrows(SignatureValidationException.class, () -> SignatureChainValidator.validateSignatureChain( + revalidatedSubkey, getSignedData(data), publicKeys, policy, validationDate), + "Primary key is hard revoked"); + } + + /** + * Subkey signs and is hard revoked with reason: unknown. + * + * @see Sequoia Test Suite + */ + @Test + public void testSubkeySignsAndIsHardRevokedUnknown() throws IOException { + String keyWithHardRev = "-----BEGIN PGP ARMORED FILE-----\n" + + "Comment: ASCII Armor added by openpgp-interoperability-test-suite\n" + + "\n" + + "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\n" + + "Y87zSryajDMFXQS0exdvhN4AXDlPlB3Rrkj7CQ==\n" + + "=yTKS\n" + + "-----END PGP ARMORED FILE-----\n"; + PGPPublicKeyRing publicKeys = PGPainless.readKeyRing().publicKeyRing(keyWithHardRev); + String sigPredatesPrimaryKey = "-----BEGIN PGP ARMORED FILE-----\n" + + "Comment: ASCII Armor added by openpgp-interoperability-test-suite\n" + + "\n" + + "wsBzBAABCgAGBYJYaEaAACEJEEpyNKOhITplFiEEUXksDkji/alOk7kRSnI0o6Eh\n" + + "OmVtqgf/YSG+b8lY01/4oCrHoNMECDDbpI5+8WkeT0CcdlEd5TGj3AHesT6H6XmL\n" + + "ZxaHHOwtkuDh0bIAiYGl36e4ult5XZQhFIwUXGde6myLE+fpCGsBJwNu+TDIrbg3\n" + + "PGqnVZNlcU+2sP5JhJfAn8VtLENuHkbIC3+kH8xBIrkPTc0rbNBgyzX5eFO20U0D\n" + + "bHCCjfjVDpZ8l7N2NlsRYvU0kTzN5GvwbS1HnMOovF9ZKkEpzxxw6IRJIapaE2L9\n" + + "adMKIRAqrIIjfj6Z9nETd1nZE79t1zSw1trfArPaJQr46krgh1ocLQoD/c+PhB9l\n" + + "sRxQBnWERgQaDJByq0kwKSnwWAsyxw==\n" + + "=SDmD\n" + + "-----END PGP ARMORED FILE-----\n"; + String sigUnboundBeforeHardRevocation = "-----BEGIN PGP ARMORED FILE-----\n" + + "Comment: ASCII Armor added by openpgp-interoperability-test-suite\n" + + "\n" + + "wsBzBAABCgAGBYJa564AACEJEEpyNKOhITplFiEEUXksDkji/alOk7kRSnI0o6Eh\n" + + "OmWfRgf9ECjIviU1pN0GiMZGci3Vce2b42LIqH0JZApOeRDpcyXgxi3/CmdaewpT\n" + + "w9l18gsXhioRg4xUzMFrYSgyYZ9VajFggMjbeX5dSV3rsOSJSiEEyDbeGi0TcA/Y\n" + + "GUifX4EfKx5X5nI/wevnYjmruDp9SqaPLHIZK1soOoPzueZ8wKyJ9A4vVG4bvxVX\n" + + "FnwBf6mRE/0Z8IoHlRJdq0fSzW4RgX8KAtK8SfyGOnk7LDaevVuL6iE5v0Gsu0oh\n" + + "cHlI6Llm97EVxP93KZ1J7TQIi/a6PUJb5XCIw0K/iyuNuAzETgm8LVJyn6UwL4ym\n" + + "KcNieOK8Qcoivq0kCYuv/0Tbk13jVQ==\n" + + "=5hOz\n" + + "-----END PGP ARMORED FILE-----\n"; + String sigAfterHardRevocation = "-----BEGIN PGP ARMORED FILE-----\n" + + "Comment: ASCII Armor added by openpgp-interoperability-test-suite\n" + + "\n" + + "wsBzBAABCgAGBYJdP4iAACEJEEpyNKOhITplFiEEUXksDkji/alOk7kRSnI0o6Eh\n" + + "OmUYXQf/dGNZay40bZEpcnxYl+Kq+gRQESeDhg/xOfGfSCLQncMH+UYPaUKANC2g\n" + + "CfMNN1wd8ZWrvgyTVo3TVfK1P1RYa9nrvKoKN3bjsFcY6V7VciPW58xVNsuxsEEC\n" + + "GEH96TQy+FsP680tRnzQ3Dbw/JT6o6Xi+HLf4JVFceapBgyth61E5gN5w3azxVFr\n" + + "GfwIfHvepOjCIq9tRZsRFEBp3XVZ/AF+zQMG5nfIVSm1kVtZjb7KXc3Bj48DVrmb\n" + + "XLxPJz7PLY0cgOsXXxROIdtFT+mbVQg2j247hxnhItwtLeQrafb5T8ibeihRlkhK\n" + + "1tfKv31EP8tAVqgTjw+qD32bH9h77w==\n" + + "=MOaJ\n" + + "-----END PGP ARMORED FILE-----\n"; + String sigAfterRevalidation = "-----BEGIN PGP ARMORED FILE-----\n" + + "Comment: ASCII Armor added by openpgp-interoperability-test-suite\n" + + "\n" + + "wsBzBAABCgAGBYJe/cFVACEJEEpyNKOhITplFiEEUXksDkji/alOk7kRSnI0o6Eh\n" + + "OmXmhQf+LjM2vmdKYnxfVy40Vcdoy2Jov/ZD2RSMFff+fIoCXmWfEetila7v5xHj\n" + + "ZXq6aevTEN1vgW3T6Q1OjFhnGpMl9wvya9mszfn5BBKukFtLkHeay0PtYuUcrfJC\n" + + "UIQCx9PFZzgRJFyGsCqrXBc1VIe2DV3d8dq74unTeCEmWdvAZKdjoUYzRohMtcZ+\n" + + "0QctCCJE1kRFJuH/TIdxxwKPtBZfOolSlpS0Z5xxa2sILqUvQ2Dq3hBctUM4g6hB\n" + + "Y8uafI8qIMwWl4DQDzPpQ917d6J+GCdN0Aib6ZOsvmgR5wrBOFiDpRJ/W9W6+rgs\n" + + "I5V/t2y6h6gaHbanggc0cMOaMTtEKQ==\n" + + "=lkHs\n" + + "-----END PGP ARMORED FILE-----\n"; + + PGPSignature predatesPrimaryKey = BCUtil.readSignatures(sigPredatesPrimaryKey).get(0); + PGPSignature unboundKey = BCUtil.readSignatures(sigUnboundBeforeHardRevocation).get(0); + PGPSignature afterHardRevocation = BCUtil.readSignatures(sigAfterHardRevocation).get(0); + PGPSignature afterRevalidation = BCUtil.readSignatures(sigAfterRevalidation).get(0); + + Policy policy = PGPainless.getPolicy(); + Date validationDate = new Date(); + String data = "Hello World :)"; + + assertThrows(SignatureValidationException.class, () -> SignatureChainValidator.validateSignatureChain( + predatesPrimaryKey, getSignedData(data), publicKeys, policy, validationDate), + "Signature predates primary key"); + assertThrows(SignatureValidationException.class, () -> SignatureChainValidator.validateSignatureChain( + unboundKey, getSignedData(data), publicKeys, policy, validationDate), + "Signing key unbound + hard revocation"); + assertThrows(SignatureValidationException.class, () -> SignatureChainValidator.validateSignatureChain( + afterHardRevocation, getSignedData(data), publicKeys, policy, validationDate), + "Hard revocation invalidates key at all times"); + assertThrows(SignatureValidationException.class, () -> SignatureChainValidator.validateSignatureChain( + afterRevalidation, getSignedData(data), publicKeys, policy, validationDate), + "Hard revocation invalidates key at all times"); + } + + /** + * Primary Key signs and is soft revoked with reason: superseded. + * + * @see Sequoia Test Suite + */ + @Test + public void testPrimaryKeySignsAndIsSoftRevokedSuperseded() throws IOException { + String keyWithSoftRev = "-----BEGIN PGP ARMORED FILE-----\n" + + "Comment: ASCII Armor added by openpgp-interoperability-test-suite\n" + + "\n" + + "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\n" + + "exdvhN4AXDlPlB3Rrkj7CQ==\n" + + "=qQpG\n" + + "-----END PGP ARMORED FILE-----\n"; + String sigPredatesPrimaryKey = "-----BEGIN PGP ARMORED FILE-----\n" + + "Comment: ASCII Armor added by openpgp-interoperability-test-suite\n" + + "\n" + + "wsBzBAABCgAGBYJYaEaAACEJEGhPrWLcA4+7FiEE8tFQpP6Ykl1R6RU5aE+tYtwD\n" + + "j7ttqgf9Gp4T5Q19cNL9Eyz1nlw11HDHT1wxfGHU5li76y7oo4Jqim15sEPDJWmc\n" + + "IpYVrczpCI95aCuaE6yfzpjZlXMzftwex3DjM98vyZH4W9teKcOnpAVjn3dLoQJA\n" + + "i4fiq3VaLgl+1OYOwu3DmwGKJZubHM3oPia9pbuyvL5Scvx+QCG0AVnssnt2QswG\n" + + "uU6J35QgBdjG2iC043sUoyxTSk929iamdQnOGchjcaATb4E4+HvtkRy4IirKxiKK\n" + + "c535BHJRLgWQzUcDDZ5kHf3SPLTNsigFFEwUyf5voFcn/DSMWSzPaVecaafTVJW2\n" + + "u8G1R5mjuxDRup8p//X1BSk1FpSmvw==\n" + + "=3/dv\n" + + "-----END PGP ARMORED FILE-----\n"; + String sigKeyIsValid = "-----BEGIN PGP ARMORED FILE-----\n" + + "Comment: ASCII Armor added by openpgp-interoperability-test-suite\n" + + "\n" + + "wsBzBAABCgAGBYJa564AACEJEGhPrWLcA4+7FiEE8tFQpP6Ykl1R6RU5aE+tYtwD\n" + + "j7ufRgf/QOsaJZgQaQ5daQrfBItOEcW+5acgY1TCwMVmc/nzBqC32TOvMaM3dypf\n" + + "wJbqzxHQIes+ivKDF872VWlMA2BErifpdsogbS0pdik/qU+AjMhr188xKpZKG/IY\n" + + "6BtuUPeSpsimx3UeEN3kt79fMtewBo0EXo3ujCyPpIF/9Vpd7L9jlJSvRBuM0/aR\n" + + "gbRsclEw4JZ98B3t7F3rLmx+F57Zre0ctzT4tHE6IaCYpEClr6Tepj/UxExYOt2l\n" + + "hKgVN8Wsuug7XYdOFmxqrV967m3CTnF8AspmxwgKK6NXjVLplfqij7Rp2URPWrWn\n" + + "Pp3CQRGUWJdMeMY9P1MpFq6XiXtftw==\n" + + "=Ld1q\n" + + "-----END PGP ARMORED FILE-----\n"; + String sigKeyIsRevoked = "-----BEGIN PGP ARMORED FILE-----\n" + + "Comment: ASCII Armor added by openpgp-interoperability-test-suite\n" + + "\n" + + "wsBzBAABCgAGBYJdP4iAACEJEGhPrWLcA4+7FiEE8tFQpP6Ykl1R6RU5aE+tYtwD\n" + + "j7sYXQf8CZw6Kx4oyI8ZJ2c9RjVZmUFEirAoXH7oYA+Ye+wSAY9OtqE/x2SOYaC6\n" + + "QHiB93/wkvpqCVkLy2lenzpD7WXLbuFZ+/5jXp1o+sVHXfLSWo6pfIhOjj9FSr8x\n" + + "qqlqUfKwkbA6WYgju+qKC35SYdSptix7unaFkO41UdsM8wGQh880HSRMBMFPzg07\n" + + "3hMNYXoEJjFlIkxJSMu2WL7N0Q/4xE2iJftsQjUYAtJ/C/YK2I6dhW+CZremnv5R\n" + + "/8W+oH5Q63lYU8YL4wYnJQvkHjKs/kjLpoPmqL8kdHjndSpU+KOYr5w61XuEp2hp\n" + + "r8trtljVaVIQX2rYawSlqKkWXt0yag==\n" + + "=xVd8\n" + + "-----END PGP ARMORED FILE-----\n"; + String sigKeyIsRevalidated = "-----BEGIN PGP ARMORED FILE-----\n" + + "Comment: ASCII Armor added by openpgp-interoperability-test-suite\n" + + "\n" + + "wsBzBAABCgAGBYJe/cFVACEJEGhPrWLcA4+7FiEE8tFQpP6Ykl1R6RU5aE+tYtwD\n" + + "j7vmhQf/UB456IXc8ub8HTExab1d5KqOGSUWpwIznTu8Wk8YuzWKEE8ZeZvPmv8K\n" + + "iJfBoOx59YrlOfpLAKcTR9Ql+IFbWsIkqPxX7U1SGldhfQm7iaK5Dn6+mmQFOz/s\n" + + "ZCIavWJ7opsp11JmQAt4FFojv789YswaS7VI1zjDj7EeRiATtzna/GqCYgeCM0cc\n" + + "sIe/1j1H2oh9YvYIpPMSGDJPo7T1Ji4Ie3iEQEYNYPuw1Hb7gWYncHXZGJq1nDf/\n" + + "WAoI9gSFagpsPW0k9cfEAOVNLNYSyi0CSnQWSjq8THbHKiLPFwsP3hvT2oHycWbK\n" + + "u5SfXaTsbMeVQJNdjCNsHq2bOXPGLw==\n" + + "=2BW4\n" + + "-----END PGP ARMORED FILE-----\n"; + + PGPPublicKeyRing publicKeys = PGPainless.readKeyRing().publicKeyRing(keyWithSoftRev); + PGPSignature predatesPrimaryKey = BCUtil.readSignatures(sigPredatesPrimaryKey).get(0); + PGPSignature keyIsValid = BCUtil.readSignatures(sigKeyIsValid).get(0); + PGPSignature keyIsRevoked = BCUtil.readSignatures(sigKeyIsRevoked).get(0); + PGPSignature keyIsRevalidated = BCUtil.readSignatures(sigKeyIsRevalidated).get(0); + Policy policy = PGPainless.getPolicy(); + String data = "Hello, World"; + + // Sig not valid, as it predates the signing key creation time + assertThrows(SignatureValidationException.class, () -> SignatureChainValidator.validateSignatureChain( + predatesPrimaryKey, getSignedData(data), publicKeys, policy, predatesPrimaryKey.getCreationTime()), + "Signature predates primary key creation date"); + + // Sig valid + assertDoesNotThrow(() -> SignatureChainValidator.validateSignatureChain( + keyIsValid, getSignedData(data), publicKeys, policy, keyIsValid.getCreationTime()), + "Signature is valid"); + + // Sig not valid, as the signing key is revoked + assertThrows(SignatureValidationException.class, () -> SignatureChainValidator.validateSignatureChain( + keyIsRevoked, getSignedData(data), publicKeys, policy, keyIsRevoked.getCreationTime()), + "Signing key is revoked at this point"); + + // Sig valid, as the signing key is revalidated + assertDoesNotThrow(() -> SignatureChainValidator.validateSignatureChain( + keyIsRevalidated, getSignedData(data), publicKeys, policy, keyIsRevalidated.getCreationTime()), + "Signature is valid, as signing key is revalidated"); + } + + /** + * Subkey signs, primary key is soft revoked with reason: superseded. + * + * @see Sequoia Test Suite + */ + @Test + public void testSubkeySignsPrimaryKeyIsSoftRevokedSuperseded() throws IOException { + String key = "-----BEGIN PGP ARMORED FILE-----\n" + + "Comment: ASCII Armor added by openpgp-interoperability-test-suite\n" + + "\n" + + "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\n" + + "exdvhN4AXDlPlB3Rrkj7CQ==\n" + + "=qQpG\n" + + "-----END PGP ARMORED FILE-----\n"; + String sigPredatesPrimaryKey = "-----BEGIN PGP ARMORED FILE-----\n" + + "Comment: ASCII Armor added by openpgp-interoperability-test-suite\n" + + "\n" + + "wsBzBAABCgAGBYJYaEaAACEJEEpyNKOhITplFiEEUXksDkji/alOk7kRSnI0o6Eh\n" + + "OmVtqgf/YSG+b8lY01/4oCrHoNMECDDbpI5+8WkeT0CcdlEd5TGj3AHesT6H6XmL\n" + + "ZxaHHOwtkuDh0bIAiYGl36e4ult5XZQhFIwUXGde6myLE+fpCGsBJwNu+TDIrbg3\n" + + "PGqnVZNlcU+2sP5JhJfAn8VtLENuHkbIC3+kH8xBIrkPTc0rbNBgyzX5eFO20U0D\n" + + "bHCCjfjVDpZ8l7N2NlsRYvU0kTzN5GvwbS1HnMOovF9ZKkEpzxxw6IRJIapaE2L9\n" + + "adMKIRAqrIIjfj6Z9nETd1nZE79t1zSw1trfArPaJQr46krgh1ocLQoD/c+PhB9l\n" + + "sRxQBnWERgQaDJByq0kwKSnwWAsyxw==\n" + + "=SDmD\n" + + "-----END PGP ARMORED FILE-----\n"; + String sigSubkeyNotBound = "-----BEGIN PGP ARMORED FILE-----\n" + + "Comment: ASCII Armor added by openpgp-interoperability-test-suite\n" + + "\n" + + "wsBzBAABCgAGBYJa564AACEJEEpyNKOhITplFiEEUXksDkji/alOk7kRSnI0o6Eh\n" + + "OmWfRgf9ECjIviU1pN0GiMZGci3Vce2b42LIqH0JZApOeRDpcyXgxi3/CmdaewpT\n" + + "w9l18gsXhioRg4xUzMFrYSgyYZ9VajFggMjbeX5dSV3rsOSJSiEEyDbeGi0TcA/Y\n" + + "GUifX4EfKx5X5nI/wevnYjmruDp9SqaPLHIZK1soOoPzueZ8wKyJ9A4vVG4bvxVX\n" + + "FnwBf6mRE/0Z8IoHlRJdq0fSzW4RgX8KAtK8SfyGOnk7LDaevVuL6iE5v0Gsu0oh\n" + + "cHlI6Llm97EVxP93KZ1J7TQIi/a6PUJb5XCIw0K/iyuNuAzETgm8LVJyn6UwL4ym\n" + + "KcNieOK8Qcoivq0kCYuv/0Tbk13jVQ==\n" + + "=5hOz\n" + + "-----END PGP ARMORED FILE-----\n"; + String sigKeyRevoked = "-----BEGIN PGP ARMORED FILE-----\n" + + "Comment: ASCII Armor added by openpgp-interoperability-test-suite\n" + + "\n" + + "wsBzBAABCgAGBYJdP4iAACEJEEpyNKOhITplFiEEUXksDkji/alOk7kRSnI0o6Eh\n" + + "OmUYXQf/dGNZay40bZEpcnxYl+Kq+gRQESeDhg/xOfGfSCLQncMH+UYPaUKANC2g\n" + + "CfMNN1wd8ZWrvgyTVo3TVfK1P1RYa9nrvKoKN3bjsFcY6V7VciPW58xVNsuxsEEC\n" + + "GEH96TQy+FsP680tRnzQ3Dbw/JT6o6Xi+HLf4JVFceapBgyth61E5gN5w3azxVFr\n" + + "GfwIfHvepOjCIq9tRZsRFEBp3XVZ/AF+zQMG5nfIVSm1kVtZjb7KXc3Bj48DVrmb\n" + + "XLxPJz7PLY0cgOsXXxROIdtFT+mbVQg2j247hxnhItwtLeQrafb5T8ibeihRlkhK\n" + + "1tfKv31EP8tAVqgTjw+qD32bH9h77w==\n" + + "=MOaJ\n" + + "-----END PGP ARMORED FILE-----\n"; + String sigKeyValid = "-----BEGIN PGP ARMORED FILE-----\n" + + "Comment: ASCII Armor added by openpgp-interoperability-test-suite\n" + + "\n" + + "wsBzBAABCgAGBYJe/cFVACEJEEpyNKOhITplFiEEUXksDkji/alOk7kRSnI0o6Eh\n" + + "OmXmhQf+LjM2vmdKYnxfVy40Vcdoy2Jov/ZD2RSMFff+fIoCXmWfEetila7v5xHj\n" + + "ZXq6aevTEN1vgW3T6Q1OjFhnGpMl9wvya9mszfn5BBKukFtLkHeay0PtYuUcrfJC\n" + + "UIQCx9PFZzgRJFyGsCqrXBc1VIe2DV3d8dq74unTeCEmWdvAZKdjoUYzRohMtcZ+\n" + + "0QctCCJE1kRFJuH/TIdxxwKPtBZfOolSlpS0Z5xxa2sILqUvQ2Dq3hBctUM4g6hB\n" + + "Y8uafI8qIMwWl4DQDzPpQ917d6J+GCdN0Aib6ZOsvmgR5wrBOFiDpRJ/W9W6+rgs\n" + + "I5V/t2y6h6gaHbanggc0cMOaMTtEKQ==\n" + + "=lkHs\n" + + "-----END PGP ARMORED FILE-----\n"; + + PGPPublicKeyRing publicKeys = PGPainless.readKeyRing().publicKeyRing(key); + PGPSignature predatesPrimaryKey = BCUtil.readSignatures(sigPredatesPrimaryKey).get(0); + PGPSignature keyNotBound = BCUtil.readSignatures(sigSubkeyNotBound).get(0); + PGPSignature keyRevoked = BCUtil.readSignatures(sigKeyRevoked).get(0); + PGPSignature valid = BCUtil.readSignatures(sigKeyValid).get(0); + + Policy policy = PGPainless.getPolicy(); + String data = "Hello, World"; + Date validationDate = new Date(); + + assertThrows(SignatureValidationException.class, () -> SignatureChainValidator.validateSignatureChain( + predatesPrimaryKey, getSignedData(data), publicKeys, policy, validationDate), + "Signature predates primary key creation date"); + assertThrows(SignatureValidationException.class, () -> SignatureChainValidator.validateSignatureChain( + keyNotBound, getSignedData(data), publicKeys, policy, validationDate), + "Signing key is not bound at this point"); + assertThrows(SignatureValidationException.class, () -> SignatureChainValidator.validateSignatureChain( + keyRevoked, getSignedData(data), publicKeys, policy, validationDate), + "Signing key is revoked at this point"); + assertDoesNotThrow(() -> + SignatureChainValidator.validateSignatureChain( + valid, getSignedData(data), publicKeys, policy, validationDate), + "Signing key is revalidated"); + } + + /** + * Primary key signs and is soft revoked with reason: retired. + * + * @see Sequoia Test Suite + */ + @Test + public void testPrimaryKeySignsAndIsSoftRevokedRetired() throws IOException { + String key = "-----BEGIN PGP ARMORED FILE-----\n" + + "Comment: ASCII Armor added by openpgp-interoperability-test-suite\n" + + "\n" + + "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\n" + + "=7j+m\n" + + "-----END PGP ARMORED FILE-----\n"; + String sigPredatesPrimaryKey = "-----BEGIN PGP ARMORED FILE-----\n" + + "Comment: ASCII Armor added by openpgp-interoperability-test-suite\n" + + "\n" + + "wsBzBAABCgAGBYJYaEaAACEJEGhPrWLcA4+7FiEE8tFQpP6Ykl1R6RU5aE+tYtwD\n" + + "j7ttqgf9Gp4T5Q19cNL9Eyz1nlw11HDHT1wxfGHU5li76y7oo4Jqim15sEPDJWmc\n" + + "IpYVrczpCI95aCuaE6yfzpjZlXMzftwex3DjM98vyZH4W9teKcOnpAVjn3dLoQJA\n" + + "i4fiq3VaLgl+1OYOwu3DmwGKJZubHM3oPia9pbuyvL5Scvx+QCG0AVnssnt2QswG\n" + + "uU6J35QgBdjG2iC043sUoyxTSk929iamdQnOGchjcaATb4E4+HvtkRy4IirKxiKK\n" + + "c535BHJRLgWQzUcDDZ5kHf3SPLTNsigFFEwUyf5voFcn/DSMWSzPaVecaafTVJW2\n" + + "u8G1R5mjuxDRup8p//X1BSk1FpSmvw==\n" + + "=3/dv\n" + + "-----END PGP ARMORED FILE-----\n"; + String sigValid = "-----BEGIN PGP ARMORED FILE-----\n" + + "Comment: ASCII Armor added by openpgp-interoperability-test-suite\n" + + "\n" + + "wsBzBAABCgAGBYJa564AACEJEGhPrWLcA4+7FiEE8tFQpP6Ykl1R6RU5aE+tYtwD\n" + + "j7ufRgf/QOsaJZgQaQ5daQrfBItOEcW+5acgY1TCwMVmc/nzBqC32TOvMaM3dypf\n" + + "wJbqzxHQIes+ivKDF872VWlMA2BErifpdsogbS0pdik/qU+AjMhr188xKpZKG/IY\n" + + "6BtuUPeSpsimx3UeEN3kt79fMtewBo0EXo3ujCyPpIF/9Vpd7L9jlJSvRBuM0/aR\n" + + "gbRsclEw4JZ98B3t7F3rLmx+F57Zre0ctzT4tHE6IaCYpEClr6Tepj/UxExYOt2l\n" + + "hKgVN8Wsuug7XYdOFmxqrV967m3CTnF8AspmxwgKK6NXjVLplfqij7Rp2URPWrWn\n" + + "Pp3CQRGUWJdMeMY9P1MpFq6XiXtftw==\n" + + "=Ld1q\n" + + "-----END PGP ARMORED FILE-----\n"; + String sigRevoked = "-----BEGIN PGP ARMORED FILE-----\n" + + "Comment: ASCII Armor added by openpgp-interoperability-test-suite\n" + + "\n" + + "wsBzBAABCgAGBYJdP4iAACEJEGhPrWLcA4+7FiEE8tFQpP6Ykl1R6RU5aE+tYtwD\n" + + "j7sYXQf8CZw6Kx4oyI8ZJ2c9RjVZmUFEirAoXH7oYA+Ye+wSAY9OtqE/x2SOYaC6\n" + + "QHiB93/wkvpqCVkLy2lenzpD7WXLbuFZ+/5jXp1o+sVHXfLSWo6pfIhOjj9FSr8x\n" + + "qqlqUfKwkbA6WYgju+qKC35SYdSptix7unaFkO41UdsM8wGQh880HSRMBMFPzg07\n" + + "3hMNYXoEJjFlIkxJSMu2WL7N0Q/4xE2iJftsQjUYAtJ/C/YK2I6dhW+CZremnv5R\n" + + "/8W+oH5Q63lYU8YL4wYnJQvkHjKs/kjLpoPmqL8kdHjndSpU+KOYr5w61XuEp2hp\n" + + "r8trtljVaVIQX2rYawSlqKkWXt0yag==\n" + + "=xVd8\n" + + "-----END PGP ARMORED FILE-----\n"; + String sigReLegitimized = "-----BEGIN PGP ARMORED FILE-----\n" + + "Comment: ASCII Armor added by openpgp-interoperability-test-suite\n" + + "\n" + + "wsBzBAABCgAGBYJe/cFVACEJEGhPrWLcA4+7FiEE8tFQpP6Ykl1R6RU5aE+tYtwD\n" + + "j7vmhQf/UB456IXc8ub8HTExab1d5KqOGSUWpwIznTu8Wk8YuzWKEE8ZeZvPmv8K\n" + + "iJfBoOx59YrlOfpLAKcTR9Ql+IFbWsIkqPxX7U1SGldhfQm7iaK5Dn6+mmQFOz/s\n" + + "ZCIavWJ7opsp11JmQAt4FFojv789YswaS7VI1zjDj7EeRiATtzna/GqCYgeCM0cc\n" + + "sIe/1j1H2oh9YvYIpPMSGDJPo7T1Ji4Ie3iEQEYNYPuw1Hb7gWYncHXZGJq1nDf/\n" + + "WAoI9gSFagpsPW0k9cfEAOVNLNYSyi0CSnQWSjq8THbHKiLPFwsP3hvT2oHycWbK\n" + + "u5SfXaTsbMeVQJNdjCNsHq2bOXPGLw==\n" + + "=2BW4\n" + + "-----END PGP ARMORED FILE-----\n"; + + PGPPublicKeyRing publicKeys = PGPainless.readKeyRing().publicKeyRing(key); + PGPSignature predatesPrimaryKey = BCUtil.readSignatures(sigPredatesPrimaryKey).get(0); + PGPSignature valid = BCUtil.readSignatures(sigValid).get(0); + PGPSignature revoked = BCUtil.readSignatures(sigRevoked).get(0); + PGPSignature revalidated = BCUtil.readSignatures(sigReLegitimized).get(0); + + Policy policy = PGPainless.getPolicy(); + Date validationDate = new Date(); + String data = "Hello, World"; + + assertThrows(SignatureValidationException.class, () -> SignatureChainValidator.validateSignatureChain( + predatesPrimaryKey, getSignedData(data), publicKeys, policy, validationDate), + "Signature predates primary key creation date"); + assertDoesNotThrow(() -> SignatureChainValidator.validateSignatureChain( + valid, getSignedData(data), publicKeys, policy, validationDate), + "Signature is valid"); + assertThrows(SignatureValidationException.class, () -> + SignatureChainValidator.validateSignatureChain( + revoked, getSignedData(data), publicKeys, policy, validationDate), + "Primary key is revoked"); + assertDoesNotThrow(() -> SignatureChainValidator.validateSignatureChain( + revalidated, getSignedData(data), publicKeys, policy, validationDate), + "Primary key is re-legitimized"); + } + + /** + * Keys with temporary validity. + * + * @see Sequoia Test Suite + */ + @Test + public void testTemporaryValidity() throws IOException { + String keyA = "-----BEGIN PGP PUBLIC KEY BLOCK-----\n" + + "Comment: D1A6 6E1A 23B1 82C9 980F 788C FBFC C82A 015E 7330\n" + + "Comment: Bob Babbage \n" + + "\n" + + "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\n" + + "=UNh9\n" + + "-----END PGP PUBLIC KEY BLOCK-----\n"; + String keyB = "-----BEGIN PGP PUBLIC KEY BLOCK-----\n" + + "Comment: D1A6 6E1A 23B1 82C9 980F 788C FBFC C82A 015E 7330\n" + + "Comment: Bob Babbage \n" + + "\n" + + "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\n" + + "iz7OblW3I9Dt\n" + + "=qacX\n" + + "-----END PGP PUBLIC KEY BLOCK-----\n"; + String keyC = "-----BEGIN PGP PUBLIC KEY BLOCK-----\n" + + "Comment: D1A6 6E1A 23B1 82C9 980F 788C FBFC C82A 015E 7330\n" + + "Comment: Bob Babbage \n" + + "\n" + + "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\n" + + "916X5bFdC0Rr\n" + + "=RTZe\n" + + "-----END PGP PUBLIC KEY BLOCK-----\n"; + + String keyASigT0 = "-----BEGIN PGP SIGNATURE-----\n" + + "\n" + + "wsE7BAABCgBvBYJdkyfyCRD7/MgqAV5zMEcUAAAAAAAeACBzYWx0QG5vdGF0aW9u\n" + + "cy5zZXF1b2lhLXBncC5vcmfAtB+VtWWsZc4DiK/QK7LEDG+KAl+ORwXnUahXErz3\n" + + "GhYhBNGmbhojsYLJmA94jPv8yCoBXnMwAABlEAv/fe0No+TMQTq5xkyv2xNgzibB\n" + + "sqeOBbmZL82PfGJTthEtZ+eKi0pNnGPlFH0VIkRskNRKEZqLGJh2d/xDstiPBTfc\n" + + "TzTn66f43h+WnGRqGLYbKHfrJ+Mn/wHub9zxauDvpjcIqOLYscuZ+rvp/OyExoST\n" + + "lOmfLsrpGgS//eMDchgeFTybZy3UP7dw+BHHSlMW/s4QvgqqS8v5x2bNgMcairzN\n" + + "BMxafx6Mp4RSk46tF+INzPgV8W+ImyGXtkZcBmiv9/u36X6GESSyHl/DVf+ZlHm9\n" + + "u35dGn8S2PhyE12eQnLZJoorqcIOtwx1sAD+317Dprv8fwcHBhgyKLVsx2O5NTWn\n" + + "fZNRtdjQiUBt03D4Qdxoru/UzpGZWq4OLLZVB7u2B4tRQohkBCH96s5hvY2GHXSN\n" + + "Md3FCvms+lCEFi70Ae1KZCpD1/DHCFQcT5fYMYVNU1HFB8eQoKMM3kmv0VpJ31CB\n" + + "bJ0esmX6JDukiepzcr09w7bbkhGvKnDgUM3dNxhv\n" + + "=PfHW\n" + + "-----END PGP SIGNATURE-----\n"; + String keyASigT1_T2 = "-----BEGIN PGP SIGNATURE-----\n" + + "\n" + + "wsE7BAABCgBvBYJduBHyCRD7/MgqAV5zMEcUAAAAAAAeACBzYWx0QG5vdGF0aW9u\n" + + "cy5zZXF1b2lhLXBncC5vcmcglTWhGNT4tUfWuGHHpZUQZGDvZ4AKjIM/B97lFgac\n" + + "XBYhBNGmbhojsYLJmA94jPv8yCoBXnMwAADPmAv9EZlRAp65+JLsABQa8sk0YgRU\n" + + "28n6/Wcv2nj0iZMtbnXu2hLJqPj62JihCNbElEgJb7NIYbTPEAiyUisq8sa+E12F\n" + + "PvBsqcNAdLxlDRyPcwlDrbl0fRqLt/fiItMp6lHnfl2SfrSkzWv8t2DswPS+LXpt\n" + + "k5GBWxnJMRQ2Lf1L8rriFY1p6D9XtO8n9wGWMzCoxp3q+diQa8YJbhppPPskU5rT\n" + + "bPYH7f1FnU2OzzxgeqlAjtpjkTmzy3RNRlzGEYk6NnNbHQN0sQoiQdOaHMJ/qKMF\n" + + "wanYX2iatYCCYJGMDv3Ysw6nXLyiZ6dbqFEJwzRCiZQ2ZBYvzRyEnMojIZE3CXOv\n" + + "a+h5qm8Ffc+ZPc0spBoTxFCz8+Rmp5qNT9kFOIQUF9YOs/fo8sCr32ZbLu/bBsy9\n" + + "RhswX42fnAZ4DZJKlAORbtYmM52WsPxArGLnpOpgD8aT4Mt7SsVpAJXnjn43NKTS\n" + + "Qu+KulEt+Sn2/ioiQoKknxJWDkBZxHLkgRC3ejTO\n" + + "=et/I\n" + + "-----END PGP SIGNATURE-----\n"; + String keyASigT2_T3 = "-----BEGIN PGP SIGNATURE-----\n" + + "\n" + + "wsE7BAABCgBvBYJd3PvyCRD7/MgqAV5zMEcUAAAAAAAeACBzYWx0QG5vdGF0aW9u\n" + + "cy5zZXF1b2lhLXBncC5vcmexsHg/HXrdneCOLkWkDj95sRwrSkFKyzFUJtY+9xLv\n" + + "1RYhBNGmbhojsYLJmA94jPv8yCoBXnMwAAAqEQv+NdTRkL4vJA1Gst/LK7f3ytjF\n" + + "H7kkMI9aTQVxnscsNMGZYBjzaT410if/axqCyAmJX3lPWfHIsIZWmglxEPK04K2b\n" + + "Ql04JEpTzEVKIKGLBPhfLT/QamtZJmGpoGw7ba8lZ8Xrarb1Lf9srRBF+WKIzuwd\n" + + "FQAGsPw/57Q4YXNwPnPJwQbOrfuZLLGGZLczGiBW8900NH0Rg82fcMdO2XmuXhMb\n" + + "NnwYoXH7vV+i2uodfdmNhts9ENeiPmPF7DPiKDQtSQ6LLspVE/RoP/lXdd7cGSQR\n" + + "J1IaWX1rN598wBz1wNVwBpxucxxIjm9JYER9+eV3oo08b0+DF4OppDcrJvGnQcdE\n" + + "QuEOAqNeSrFGqXglfZ/Cep5ZkIVPtJA88e9FZYz7xlx49bh1os1jc7jeSA6xG/1O\n" + + "48ZQiPrLkVZp/62j0GvFEZzOj0CVb+/J2gsQ32SYkKeiZfXIdodReQMYAiXHeEj2\n" + + "mtuOFO8yVqWUwnlUwQoVAnr9zMulZB2Np29wvevU\n" + + "=kusD\n" + + "-----END PGP SIGNATURE-----\n"; + String keyASigT3_now = "-----BEGIN PGP SIGNATURE-----\n" + + "\n" + + "wsE7BAABCgBvBYJeAeXyCRD7/MgqAV5zMEcUAAAAAAAeACBzYWx0QG5vdGF0aW9u\n" + + "cy5zZXF1b2lhLXBncC5vcmcElcHwXV0gKjafJ0j2dKF+EeukF5ywMBZMMkEyyjG4\n" + + "jhYhBNGmbhojsYLJmA94jPv8yCoBXnMwAAASuAwAnu1KFMNci1CEkzJb5fkVZRGz\n" + + "Cvo2wC0HXhwMoGm/icuw3qTNWpj3wCA6tOTgIGT6FwfYV42mNkFYpiUSelpIdTP7\n" + + "R5wf7cvu2wS5sZo+q9a3K4T2gWu+hlLO00/q8LJYTAG3dQZd6Mhk3gPUh3qNWHuR\n" + + "zi3gXajxaQ1yJrFHjRt86DBks5vCBWeFkcNQcuIZgoKlHsGxEgfQo0Yej6v4FBrQ\n" + + "xr3iU4GhSAQOFmZQPL2AVOfE8if9CqNRNLGpkloEDAhoSf+TxRyWXFfvXZQGKgSA\n" + + "oKbgQFyUgdybPFXiQa8ezZaO23risIG/7oe+rAM0vOWMA0f2F0d2W5t4UeZLLxsu\n" + + "Gh+7ZYK/MDF1HgFHjYefoW7pSPoNzaSIFv6goCtTr1O2c7BnO9QxU1H1rWgkFUd0\n" + + "NWHrk3H89te7fP94GtBskR1OnT8zxWVMtFx8NEDicrSw/sKqmxkxh0xW434ZtXgi\n" + + "FT2kVzTaUKN+UQ7UIs9wgtqb+s7Dvb7b1bO8wvLg\n" + + "=DeXH\n" + + "-----END PGP SIGNATURE-----\n"; + String keyBSigT0 = "-----BEGIN PGP SIGNATURE-----\n" + + "\n" + + "wsE7BAABCgBvBYJdkyfyCRD7/MgqAV5zMEcUAAAAAAAeACBzYWx0QG5vdGF0aW9u\n" + + "cy5zZXF1b2lhLXBncC5vcmfAtB+VtWWsZc4DiK/QK7LEDG+KAl+ORwXnUahXErz3\n" + + "GhYhBNGmbhojsYLJmA94jPv8yCoBXnMwAABlEAv/fe0No+TMQTq5xkyv2xNgzibB\n" + + "sqeOBbmZL82PfGJTthEtZ+eKi0pNnGPlFH0VIkRskNRKEZqLGJh2d/xDstiPBTfc\n" + + "TzTn66f43h+WnGRqGLYbKHfrJ+Mn/wHub9zxauDvpjcIqOLYscuZ+rvp/OyExoST\n" + + "lOmfLsrpGgS//eMDchgeFTybZy3UP7dw+BHHSlMW/s4QvgqqS8v5x2bNgMcairzN\n" + + "BMxafx6Mp4RSk46tF+INzPgV8W+ImyGXtkZcBmiv9/u36X6GESSyHl/DVf+ZlHm9\n" + + "u35dGn8S2PhyE12eQnLZJoorqcIOtwx1sAD+317Dprv8fwcHBhgyKLVsx2O5NTWn\n" + + "fZNRtdjQiUBt03D4Qdxoru/UzpGZWq4OLLZVB7u2B4tRQohkBCH96s5hvY2GHXSN\n" + + "Md3FCvms+lCEFi70Ae1KZCpD1/DHCFQcT5fYMYVNU1HFB8eQoKMM3kmv0VpJ31CB\n" + + "bJ0esmX6JDukiepzcr09w7bbkhGvKnDgUM3dNxhv\n" + + "=PfHW\n" + + "-----END PGP SIGNATURE-----\n"; + String keyBSigT1_T2 = "-----BEGIN PGP SIGNATURE-----\n" + + "\n" + + "wsE7BAABCgBvBYJduBHyCRD7/MgqAV5zMEcUAAAAAAAeACBzYWx0QG5vdGF0aW9u\n" + + "cy5zZXF1b2lhLXBncC5vcmcglTWhGNT4tUfWuGHHpZUQZGDvZ4AKjIM/B97lFgac\n" + + "XBYhBNGmbhojsYLJmA94jPv8yCoBXnMwAADPmAv9EZlRAp65+JLsABQa8sk0YgRU\n" + + "28n6/Wcv2nj0iZMtbnXu2hLJqPj62JihCNbElEgJb7NIYbTPEAiyUisq8sa+E12F\n" + + "PvBsqcNAdLxlDRyPcwlDrbl0fRqLt/fiItMp6lHnfl2SfrSkzWv8t2DswPS+LXpt\n" + + "k5GBWxnJMRQ2Lf1L8rriFY1p6D9XtO8n9wGWMzCoxp3q+diQa8YJbhppPPskU5rT\n" + + "bPYH7f1FnU2OzzxgeqlAjtpjkTmzy3RNRlzGEYk6NnNbHQN0sQoiQdOaHMJ/qKMF\n" + + "wanYX2iatYCCYJGMDv3Ysw6nXLyiZ6dbqFEJwzRCiZQ2ZBYvzRyEnMojIZE3CXOv\n" + + "a+h5qm8Ffc+ZPc0spBoTxFCz8+Rmp5qNT9kFOIQUF9YOs/fo8sCr32ZbLu/bBsy9\n" + + "RhswX42fnAZ4DZJKlAORbtYmM52WsPxArGLnpOpgD8aT4Mt7SsVpAJXnjn43NKTS\n" + + "Qu+KulEt+Sn2/ioiQoKknxJWDkBZxHLkgRC3ejTO\n" + + "=et/I\n" + + "-----END PGP SIGNATURE-----\n"; + String keyBSigT2_T3 = "-----BEGIN PGP SIGNATURE-----\n" + + "\n" + + "wsE7BAABCgBvBYJd3PvyCRD7/MgqAV5zMEcUAAAAAAAeACBzYWx0QG5vdGF0aW9u\n" + + "cy5zZXF1b2lhLXBncC5vcmexsHg/HXrdneCOLkWkDj95sRwrSkFKyzFUJtY+9xLv\n" + + "1RYhBNGmbhojsYLJmA94jPv8yCoBXnMwAAAqEQv+NdTRkL4vJA1Gst/LK7f3ytjF\n" + + "H7kkMI9aTQVxnscsNMGZYBjzaT410if/axqCyAmJX3lPWfHIsIZWmglxEPK04K2b\n" + + "Ql04JEpTzEVKIKGLBPhfLT/QamtZJmGpoGw7ba8lZ8Xrarb1Lf9srRBF+WKIzuwd\n" + + "FQAGsPw/57Q4YXNwPnPJwQbOrfuZLLGGZLczGiBW8900NH0Rg82fcMdO2XmuXhMb\n" + + "NnwYoXH7vV+i2uodfdmNhts9ENeiPmPF7DPiKDQtSQ6LLspVE/RoP/lXdd7cGSQR\n" + + "J1IaWX1rN598wBz1wNVwBpxucxxIjm9JYER9+eV3oo08b0+DF4OppDcrJvGnQcdE\n" + + "QuEOAqNeSrFGqXglfZ/Cep5ZkIVPtJA88e9FZYz7xlx49bh1os1jc7jeSA6xG/1O\n" + + "48ZQiPrLkVZp/62j0GvFEZzOj0CVb+/J2gsQ32SYkKeiZfXIdodReQMYAiXHeEj2\n" + + "mtuOFO8yVqWUwnlUwQoVAnr9zMulZB2Np29wvevU\n" + + "=kusD\n" + + "-----END PGP SIGNATURE-----\n"; + String keyBSigT3_now = "-----BEGIN PGP SIGNATURE-----\n" + + "\n" + + "wsE7BAABCgBvBYJeAeXyCRD7/MgqAV5zMEcUAAAAAAAeACBzYWx0QG5vdGF0aW9u\n" + + "cy5zZXF1b2lhLXBncC5vcmcElcHwXV0gKjafJ0j2dKF+EeukF5ywMBZMMkEyyjG4\n" + + "jhYhBNGmbhojsYLJmA94jPv8yCoBXnMwAAASuAwAnu1KFMNci1CEkzJb5fkVZRGz\n" + + "Cvo2wC0HXhwMoGm/icuw3qTNWpj3wCA6tOTgIGT6FwfYV42mNkFYpiUSelpIdTP7\n" + + "R5wf7cvu2wS5sZo+q9a3K4T2gWu+hlLO00/q8LJYTAG3dQZd6Mhk3gPUh3qNWHuR\n" + + "zi3gXajxaQ1yJrFHjRt86DBks5vCBWeFkcNQcuIZgoKlHsGxEgfQo0Yej6v4FBrQ\n" + + "xr3iU4GhSAQOFmZQPL2AVOfE8if9CqNRNLGpkloEDAhoSf+TxRyWXFfvXZQGKgSA\n" + + "oKbgQFyUgdybPFXiQa8ezZaO23risIG/7oe+rAM0vOWMA0f2F0d2W5t4UeZLLxsu\n" + + "Gh+7ZYK/MDF1HgFHjYefoW7pSPoNzaSIFv6goCtTr1O2c7BnO9QxU1H1rWgkFUd0\n" + + "NWHrk3H89te7fP94GtBskR1OnT8zxWVMtFx8NEDicrSw/sKqmxkxh0xW434ZtXgi\n" + + "FT2kVzTaUKN+UQ7UIs9wgtqb+s7Dvb7b1bO8wvLg\n" + + "=DeXH\n" + + "-----END PGP SIGNATURE-----\n"; + String keyCSigT0 = "-----BEGIN PGP SIGNATURE-----\n" + + "\n" + + "wsE7BAABCgBvBYJdkyfyCRD7/MgqAV5zMEcUAAAAAAAeACBzYWx0QG5vdGF0aW9u\n" + + "cy5zZXF1b2lhLXBncC5vcmfAtB+VtWWsZc4DiK/QK7LEDG+KAl+ORwXnUahXErz3\n" + + "GhYhBNGmbhojsYLJmA94jPv8yCoBXnMwAABlEAv/fe0No+TMQTq5xkyv2xNgzibB\n" + + "sqeOBbmZL82PfGJTthEtZ+eKi0pNnGPlFH0VIkRskNRKEZqLGJh2d/xDstiPBTfc\n" + + "TzTn66f43h+WnGRqGLYbKHfrJ+Mn/wHub9zxauDvpjcIqOLYscuZ+rvp/OyExoST\n" + + "lOmfLsrpGgS//eMDchgeFTybZy3UP7dw+BHHSlMW/s4QvgqqS8v5x2bNgMcairzN\n" + + "BMxafx6Mp4RSk46tF+INzPgV8W+ImyGXtkZcBmiv9/u36X6GESSyHl/DVf+ZlHm9\n" + + "u35dGn8S2PhyE12eQnLZJoorqcIOtwx1sAD+317Dprv8fwcHBhgyKLVsx2O5NTWn\n" + + "fZNRtdjQiUBt03D4Qdxoru/UzpGZWq4OLLZVB7u2B4tRQohkBCH96s5hvY2GHXSN\n" + + "Md3FCvms+lCEFi70Ae1KZCpD1/DHCFQcT5fYMYVNU1HFB8eQoKMM3kmv0VpJ31CB\n" + + "bJ0esmX6JDukiepzcr09w7bbkhGvKnDgUM3dNxhv\n" + + "=PfHW\n" + + "-----END PGP SIGNATURE-----\n"; + String keyCSigT1_T2 = "-----BEGIN PGP SIGNATURE-----\n" + + "\n" + + "wsE7BAABCgBvBYJduBHyCRD7/MgqAV5zMEcUAAAAAAAeACBzYWx0QG5vdGF0aW9u\n" + + "cy5zZXF1b2lhLXBncC5vcmcglTWhGNT4tUfWuGHHpZUQZGDvZ4AKjIM/B97lFgac\n" + + "XBYhBNGmbhojsYLJmA94jPv8yCoBXnMwAADPmAv9EZlRAp65+JLsABQa8sk0YgRU\n" + + "28n6/Wcv2nj0iZMtbnXu2hLJqPj62JihCNbElEgJb7NIYbTPEAiyUisq8sa+E12F\n" + + "PvBsqcNAdLxlDRyPcwlDrbl0fRqLt/fiItMp6lHnfl2SfrSkzWv8t2DswPS+LXpt\n" + + "k5GBWxnJMRQ2Lf1L8rriFY1p6D9XtO8n9wGWMzCoxp3q+diQa8YJbhppPPskU5rT\n" + + "bPYH7f1FnU2OzzxgeqlAjtpjkTmzy3RNRlzGEYk6NnNbHQN0sQoiQdOaHMJ/qKMF\n" + + "wanYX2iatYCCYJGMDv3Ysw6nXLyiZ6dbqFEJwzRCiZQ2ZBYvzRyEnMojIZE3CXOv\n" + + "a+h5qm8Ffc+ZPc0spBoTxFCz8+Rmp5qNT9kFOIQUF9YOs/fo8sCr32ZbLu/bBsy9\n" + + "RhswX42fnAZ4DZJKlAORbtYmM52WsPxArGLnpOpgD8aT4Mt7SsVpAJXnjn43NKTS\n" + + "Qu+KulEt+Sn2/ioiQoKknxJWDkBZxHLkgRC3ejTO\n" + + "=et/I\n" + + "-----END PGP SIGNATURE-----\n"; + String keyCSigT2_T3 = "-----BEGIN PGP SIGNATURE-----\n" + + "\n" + + "wsE7BAABCgBvBYJd3PvyCRD7/MgqAV5zMEcUAAAAAAAeACBzYWx0QG5vdGF0aW9u\n" + + "cy5zZXF1b2lhLXBncC5vcmexsHg/HXrdneCOLkWkDj95sRwrSkFKyzFUJtY+9xLv\n" + + "1RYhBNGmbhojsYLJmA94jPv8yCoBXnMwAAAqEQv+NdTRkL4vJA1Gst/LK7f3ytjF\n" + + "H7kkMI9aTQVxnscsNMGZYBjzaT410if/axqCyAmJX3lPWfHIsIZWmglxEPK04K2b\n" + + "Ql04JEpTzEVKIKGLBPhfLT/QamtZJmGpoGw7ba8lZ8Xrarb1Lf9srRBF+WKIzuwd\n" + + "FQAGsPw/57Q4YXNwPnPJwQbOrfuZLLGGZLczGiBW8900NH0Rg82fcMdO2XmuXhMb\n" + + "NnwYoXH7vV+i2uodfdmNhts9ENeiPmPF7DPiKDQtSQ6LLspVE/RoP/lXdd7cGSQR\n" + + "J1IaWX1rN598wBz1wNVwBpxucxxIjm9JYER9+eV3oo08b0+DF4OppDcrJvGnQcdE\n" + + "QuEOAqNeSrFGqXglfZ/Cep5ZkIVPtJA88e9FZYz7xlx49bh1os1jc7jeSA6xG/1O\n" + + "48ZQiPrLkVZp/62j0GvFEZzOj0CVb+/J2gsQ32SYkKeiZfXIdodReQMYAiXHeEj2\n" + + "mtuOFO8yVqWUwnlUwQoVAnr9zMulZB2Np29wvevU\n" + + "=kusD\n" + + "-----END PGP SIGNATURE-----\n"; + String keyCSigT3_now = "-----BEGIN PGP SIGNATURE-----\n" + + "\n" + + "wsE7BAABCgBvBYJeAeXyCRD7/MgqAV5zMEcUAAAAAAAeACBzYWx0QG5vdGF0aW9u\n" + + "cy5zZXF1b2lhLXBncC5vcmcElcHwXV0gKjafJ0j2dKF+EeukF5ywMBZMMkEyyjG4\n" + + "jhYhBNGmbhojsYLJmA94jPv8yCoBXnMwAAASuAwAnu1KFMNci1CEkzJb5fkVZRGz\n" + + "Cvo2wC0HXhwMoGm/icuw3qTNWpj3wCA6tOTgIGT6FwfYV42mNkFYpiUSelpIdTP7\n" + + "R5wf7cvu2wS5sZo+q9a3K4T2gWu+hlLO00/q8LJYTAG3dQZd6Mhk3gPUh3qNWHuR\n" + + "zi3gXajxaQ1yJrFHjRt86DBks5vCBWeFkcNQcuIZgoKlHsGxEgfQo0Yej6v4FBrQ\n" + + "xr3iU4GhSAQOFmZQPL2AVOfE8if9CqNRNLGpkloEDAhoSf+TxRyWXFfvXZQGKgSA\n" + + "oKbgQFyUgdybPFXiQa8ezZaO23risIG/7oe+rAM0vOWMA0f2F0d2W5t4UeZLLxsu\n" + + "Gh+7ZYK/MDF1HgFHjYefoW7pSPoNzaSIFv6goCtTr1O2c7BnO9QxU1H1rWgkFUd0\n" + + "NWHrk3H89te7fP94GtBskR1OnT8zxWVMtFx8NEDicrSw/sKqmxkxh0xW434ZtXgi\n" + + "FT2kVzTaUKN+UQ7UIs9wgtqb+s7Dvb7b1bO8wvLg\n" + + "=DeXH\n" + + "-----END PGP SIGNATURE-----\n"; + + PGPPublicKeyRing keysA = PGPainless.readKeyRing().publicKeyRing(keyA); + PGPPublicKeyRing keysB = PGPainless.readKeyRing().publicKeyRing(keyB); + PGPPublicKeyRing keysC = PGPainless.readKeyRing().publicKeyRing(keyC); + + PGPSignature sigAT0 = BCUtil.readSignatures(keyASigT0).get(0); + PGPSignature sigAT1_T2 = BCUtil.readSignatures(keyASigT1_T2).get(0); + PGPSignature sigAT2_T3 = BCUtil.readSignatures(keyASigT2_T3).get(0); + PGPSignature sigAT3_now = BCUtil.readSignatures(keyASigT3_now).get(0); + PGPSignature sigBT0 = BCUtil.readSignatures(keyBSigT0).get(0); + PGPSignature sigBT1_T2 = BCUtil.readSignatures(keyBSigT1_T2).get(0); + PGPSignature sigBT2_T3 = BCUtil.readSignatures(keyBSigT2_T3).get(0); + PGPSignature sigBT3_now = BCUtil.readSignatures(keyBSigT3_now).get(0); + PGPSignature sigCT0 = BCUtil.readSignatures(keyCSigT0).get(0); + PGPSignature sigCT1_T2 = BCUtil.readSignatures(keyCSigT1_T2).get(0); + PGPSignature sigCT2_T3 = BCUtil.readSignatures(keyCSigT2_T3).get(0); + PGPSignature sigCT3_now = BCUtil.readSignatures(keyCSigT3_now).get(0); + + Policy policy = PGPainless.getPolicy(); + Date validationDate = new Date(); + String data = "Hello World :)"; + + assertThrows(SignatureValidationException.class, () -> SignatureChainValidator.validateSignatureChain( + sigAT0, getSignedData(data), keysA, policy, validationDate), + "Signature predates key creation time"); + assertDoesNotThrow(() -> SignatureChainValidator.validateSignatureChain( + sigAT1_T2, getSignedData(data), keysA, policy, validationDate), + "Key valid"); + assertThrows(SignatureValidationException.class, () -> + SignatureChainValidator.validateSignatureChain( + sigAT2_T3, getSignedData(data), keysA, policy, validationDate), + "Key is not valid, as subkey binding expired"); + assertDoesNotThrow(() -> SignatureChainValidator.validateSignatureChain( + sigAT3_now, getSignedData(data), keysA, policy, validationDate), + "Key is valid again"); + + assertThrows(SignatureValidationException.class, () -> SignatureChainValidator.validateSignatureChain( + sigBT0, getSignedData(data), keysB, policy, validationDate), + "Signature predates key creation time"); + assertDoesNotThrow(() -> SignatureChainValidator.validateSignatureChain( + sigBT1_T2, getSignedData(data), keysB, policy, validationDate), + "Key is valid"); + assertThrows(SignatureValidationException.class, () -> SignatureChainValidator.validateSignatureChain( + sigBT2_T3, getSignedData(data), keysB, policy, validationDate), + "Primary key is not signing-capable"); + assertDoesNotThrow(() -> SignatureChainValidator.validateSignatureChain( + sigBT3_now, getSignedData(data), keysB, policy, validationDate), + "Key is valid again"); + + assertThrows(SignatureValidationException.class, () -> SignatureChainValidator.validateSignatureChain( + sigCT0, getSignedData(data), keysC, policy, validationDate), + "Signature predates key creation time"); + assertDoesNotThrow(() -> SignatureChainValidator.validateSignatureChain( + sigCT1_T2, getSignedData(data), keysC, policy, validationDate), + "Key is valid"); + assertThrows(SignatureValidationException.class, () -> SignatureChainValidator.validateSignatureChain( + sigCT2_T3, getSignedData(data), keysC, policy, validationDate), + "Key is revoked"); + assertDoesNotThrow(() -> SignatureChainValidator.validateSignatureChain( + sigCT3_now, getSignedData(data), keysC, policy, validationDate), + "Key is valid again"); + } + + private static InputStream getSignedData(String data) { + return new ByteArrayInputStream(data.getBytes(StandardCharsets.UTF_8)); + } +} diff --git a/pgpainless-core/src/test/java/org/pgpainless/util/NotationRegistryTest.java b/pgpainless-core/src/test/java/org/pgpainless/util/NotationRegistryTest.java index 710b1bce..002b24b8 100644 --- a/pgpainless-core/src/test/java/org/pgpainless/util/NotationRegistryTest.java +++ b/pgpainless-core/src/test/java/org/pgpainless/util/NotationRegistryTest.java @@ -15,22 +15,16 @@ */ package org.pgpainless.util; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; -public class NotationRegistryTest { +import org.junit.jupiter.api.Test; - @BeforeEach - public void setup() { - NotationRegistry.getInstance().clear(); - } +public class NotationRegistryTest { @Test public void notationIsKnownOnceAddedAndUnknownOnceCleared() { - NotationRegistry registry = NotationRegistry.getInstance(); + NotationRegistry registry = new NotationRegistry(); assertFalse(registry.isKnownNotation("proof@metacode.biz"), "Notation is initially not known."); assertFalse(registry.isKnownNotation("unkown@notation.data")); diff --git a/pgpainless-core/src/test/java/org/pgpainless/util/SignatureSubpacketGeneratorUtilTest.java b/pgpainless-core/src/test/java/org/pgpainless/util/SignatureSubpacketGeneratorUtilTest.java index 7ad3e3cd..541a9d94 100644 --- a/pgpainless-core/src/test/java/org/pgpainless/util/SignatureSubpacketGeneratorUtilTest.java +++ b/pgpainless-core/src/test/java/org/pgpainless/util/SignatureSubpacketGeneratorUtilTest.java @@ -26,6 +26,7 @@ import org.bouncycastle.openpgp.PGPSignatureSubpacketGenerator; import org.bouncycastle.openpgp.PGPSignatureSubpacketVector; import org.junit.jupiter.api.Test; import org.pgpainless.algorithm.SignatureSubpacket; +import org.pgpainless.signature.subpackets.SignatureSubpacketGeneratorUtil; public class SignatureSubpacketGeneratorUtilTest { diff --git a/pgpainless-core/src/test/java/org/pgpainless/util/TestUtils.java b/pgpainless-core/src/test/java/org/pgpainless/util/TestUtils.java index 4cda39e0..c513077c 100644 --- a/pgpainless-core/src/test/java/org/pgpainless/util/TestUtils.java +++ b/pgpainless-core/src/test/java/org/pgpainless/util/TestUtils.java @@ -15,10 +15,23 @@ */ package org.pgpainless.util; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Date; import java.util.Iterator; public class TestUtils { + public static SimpleDateFormat UTC_PARSER = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss z"); + + public static Date getUTCDate(String dateString) { + try { + return UTC_PARSER.parse(dateString); + } catch (ParseException e) { + return null; + } + } + public static int getNumberOfItemsInIterator(Iterator iterator) { int num = 0; while (iterator.hasNext()) { diff --git a/pgpainless-core/src/test/java/org/pgpainless/util/selection/signature/SelectSignatureFromKeyTest.java b/pgpainless-core/src/test/java/org/pgpainless/util/selection/signature/SelectSignatureFromKeyTest.java new file mode 100644 index 00000000..ffd1ac9e --- /dev/null +++ b/pgpainless-core/src/test/java/org/pgpainless/util/selection/signature/SelectSignatureFromKeyTest.java @@ -0,0 +1,186 @@ +/* + * Copyright 2021 Paul Schaub. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.pgpainless.util.selection.signature; + +import java.io.IOException; +import java.util.Iterator; + +import org.bouncycastle.openpgp.PGPException; +import org.bouncycastle.openpgp.PGPPublicKey; +import org.bouncycastle.openpgp.PGPPublicKeyRing; +import org.bouncycastle.openpgp.PGPSignature; +import org.junit.jupiter.api.Test; +import org.pgpainless.PGPainless; +import org.pgpainless.signature.SelectSignatureFromKey; + +public class SelectSignatureFromKeyTest { + + @Test + public void validKeyTest() throws IOException, PGPException { + String key = "-----BEGIN PGP PUBLIC KEY BLOCK-----\n" + + "\n" + + "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\n" + + "Db7pzk2vugYKrebvskFQaq0S8TwhHQ4n9GVrUnenFf2OAWYfRYmYbENUv+fQm22+\n" + + "EOxHWSVwB3NWl8albQxs/aPCi3nuPdtdTMU2fHLGDAZ9MGQesb/0tSJLWaqQRvqT\n" + + "k3llI1OqxGbYLaNXSz6nJDLsKK9v+6lFzxA5C8OOxGikHE7b9RJ6SGVNijItXtHo\n" + + "rVuAKayDfMKO+0jc25I+agMbfg6p4Ik5D+1LFzZtsSc6Ib6AKu+FLit6Ik74/nrr\n" + + "/ORSAoTpxnIyJlBu4DS3AUwRd/O7rke8FNVg6EpzaPazrqfY1eZ2YelEE4EO3xXm\n" + + "wcOLSPVwsLNoC3DdRRLtw5EItZy2z0QiARF+NsUYQQM5RCrQizxuzD5+nXg1AcaE\n" + + "ixnbju8StB8jT1m4ccJKHsObgi/cIPPsWm5+BUhV9RDLsMWnaVZ8f3tRAHy2TAld\n" + + "FiEE0aZuGiOxgsmYD3iM+/zIKgFeczAAAAv/DACScy69f/qohzub6e06b3sgmL1K\n" + + "foCMmFRAiEsDHUHunAb/KWBqkbJ8W6wP0COwh4tbmjUzwexMQyI4m58SLRYULcJ7\n" + + "kj3axMV0+JJyFoqUpCT06GpqQQIhZY7Y+AHz9FdVNEDjjUwb3mODx8zVyEg57T9C\n" + + "TfuLrrJDYpycfNJtxYy9qSMPHBiVGqlzqnyETOa312QquZuY6ucfTL8i8kXk5qtL\n" + + "jVHTnKogzrbTCWuKR8fzsxfZ9afdYXI3SMMsip4Ixx2mLM5tN9IeDI/DQnWetwB2\n" + + "Z0PEs7UcYcrn6UWs1X4P7jOmtLH+0d96I9ljd9SSmJ9dTr2cV62J/qtK+75hCBk8\n" + + "Lz+MNWzyAU3sVqGRhsBaLOqvb7K9p3bm6brEmGpBLeKrxuxjBER+7knqkTxSsb+S\n" + + "msO3lGrEnNEQIlcvoxLIGQiv9b0sblGM9lr40C0D84PEvajhuFAUTItoPfCIVVaT\n" + + "7Ry8/ZA6t0uQh9/B0hYblb07mJ92hCacoTx+APM=\n" + + "=yeYe\n" + + "-----END PGP PUBLIC KEY BLOCK-----"; + + PGPPublicKeyRing publicKeys = PGPainless.readKeyRing().publicKeyRing(key); + Iterator keyIt = publicKeys.getPublicKeys(); + PGPPublicKey primaryKey = publicKeys.getPublicKey(); + while (keyIt.hasNext()) { + PGPPublicKey publicKey = keyIt.next(); + // CHECKSTYLE:OFF + System.out.println(publicKey.getKeyID()); + // CHECKSTYLE:ON + + Iterator signatures = publicKey.getSignatures(); + while (signatures.hasNext()) { + PGPSignature signature = signatures.next(); + if (SelectSignatureFromKey.isValidSubkeyBindingSignature(primaryKey, publicKey).accept(signature, publicKey, publicKeys)) { + // CHECKSTYLE:OFF + System.out.println("Valid subkey binding signature"); + // CHECKSTYLE:ON + } + } + } + } + + @Test + public void missingBackSigTest() throws IOException { + String key = "-----BEGIN PGP PUBLIC KEY BLOCK-----\n" + + "\n" + + "xsDNBF2lnPIBDAC5cL9PQoQLTMuhjbYvb4Ncuuo0bfmgPRFywX53jPhoFf4Zg6mv\n" + + "/seOXpgecTdOcVttfzC8ycIKrt3aQTiwOG/ctaR4Bk/t6ayNFfdUNxHWk4WCKzdz\n" + + "/56fW2O0F23qIRd8UUJp5IIlN4RDdRCtdhVQIAuzvp2oVy/LaS2kxQoKvph/5pQ/\n" + + "5whqsyroEWDJoSV0yOb25B/iwk/pLUFoyhDG9bj0kIzDxrEqW+7Ba8nocQlecMF3\n" + + "X5KMN5kp2zraLv9dlBBpWW43XktjcCZgMy20SouraVma8Je/ECwUWYUiAZxLIlMv\n" + + "9CurEOtxUw6N3RdOtLmYZS9uEnn5y1UkF88o8Nku890uk6BrewFzJyLAx5wRZ4F0\n" + + "qV/yq36UWQ0JB/AUGhHVPdFf6pl6eaxBwT5GXvbBUibtf8YI2og5RsgTWtXfU7eb\n" + + "SGXrl5ZMpbA6mbfhd0R8aPxWfmDWiIOhBufhMCvUHh1sApMKVZnvIff9/0Dca3wb\n" + + "vLIwa3T4CyshfT0AEQEAAc0hQm9iIEJhYmJhZ2UgPGJvYkBvcGVucGdwLmV4YW1w\n" + + "bGU+wsFIBBMBCgB8BYJfRGs6AgsJCRD7/MgqAV5zMEcUAAAAAAAeACBzYWx0QG5v\n" + + "dGF0aW9ucy5zZXF1b2lhLXBncC5vcmfG4smOBDeAPqApuhtNx1qTvcbgFVo/gKVD\n" + + "bmy8y8ocOwMVCAoCmwECHgEWIQTRpm4aI7GCyZgPeIz7/MgqAV5zMAAA/zwMAKD9\n" + + "skJhBHzBg0KJKwyaILWlXItDm0Np9GAWTzRa1HWwy4oLzM5tVdi5UiQOO7wsY3r5\n" + + "NMpkwZrlf7xJzn1lXuonUW3GN/L4MlE8SjjXwvwo7HHDijRa3bs6w6xFi4O21WUL\n" + + "mi3cwZU0KvGTygW9iTW4bG92KqdejZzyPnJJlmhqhS0rUFKIwGW9OIvIKUmeeeBH\n" + + "/0zTQBO0zErC73FRekyPTfR3ePuHZ/2VMnd4gI5sBrx9rOLBN/mGU9tBsEAd5Fo0\n" + + "X0Wgdcm1N7NNcseC0rKFfGjvEah9r/U5NryGjseMPRd+HgogGvuCsAfBcQc4EgbP\n" + + "4a0aNlrOqJObyOxkOrYofI2f9l0UgHngskF6bTL+LHQ7H49L+gCzbIXJVytHOh+U\n" + + "7povgQM3OMhG3zNGvxhqgr//k4mDb7G4ygTCOi8lklxkOK/jT3qNHgkoXOWBhKet\n" + + "AH3aeKnfoChPO/YtZvyZWPW8RcgZkDmyvFyuAuee3YeQbMy4nj2hdgaxYgJ4rs7A\n" + + "zQRdpZzyAQwA1jC/XGxjK6ddgrRfW9j+s/U00++EvIsgTs2kr3Rg0GP7FLWV0YNt\n" + + "R1mpl55/bEl7yAxCDTkOgPUMXcaKlnQh6zrlt6H53mF6Bvs3inOHQvOsGtU0dqvb\n" + + "1vkTF0juLiJgPlM7pWv+pNQ6IA39vKoQsTMBv4v5vYNXP9GgKbg8inUNT17BxzZY\n" + + "Hfw5+q63ectgDm2on1e8CIRCZ76oBVwzdkVxoy3gjh1eENlk2D4P0uJNZzF1Q8GV\n" + + "67yLANGMCDICE/OkWn6daipYDzW4iJQtYPUWP4hWhjdm+CK+hg6IQUEn2Vtvi16D\n" + + "2blRP8BpUNNa4fNuylWVuJV76rIHvsLZ1pbM3LHpRgE8s6jivS3Rz3WRs0TmWCNn\n" + + "vHPqWizQ3VTy+r3UQVJ5AmhJDrZdZq9iaUIuZ01PoE1+CHiJwuxPtWvVAxf2POcm\n" + + "1M/F1fK1J0e+lKlQuyonTXqXR22Y41wrfP2aPk3nPSTW2DUAf3vRMZg57ZpRxLEh\n" + + "EMxcM4/LMR+PABEBAAHCwT4EGAEKAHIFgl9EazoJEPv8yCoBXnMwRxQAAAAAAB4A\n" + + "IHNhbHRAbm90YXRpb25zLnNlcXVvaWEtcGdwLm9yZzralcLYPPn2+y5wW/nUhKkM\n" + + "7cEGJPF1O2wGnOpPUWjdApsCFiEE0aZuGiOxgsmYD3iM+/zIKgFeczAAALEgC/wL\n" + + "sBjuZAnyh0Pdz2srlUdsp3UKgLo8d32QC5/6nd7SY4WSlfbtSDxcyXt9qbi6dN85\n" + + "S72cyWfxo2NB8Bi0br/qOuiPcctRxOqrRUye+gQd/9Hd/m/ZmzrTRdqBNAwcQaHE\n" + + "DRauKwFbvmkK5P/r1W6PfmXYxQ7ORbQhdI74sOZsKoqfkfEhQJd7StjFA1Y+90hG\n" + + "VQbNuWfp+xJSKc2rilqAt73yt8VJtO7Z/aF6Pw8CxzR7Jj2GfFmrWrfw7GR+jLll\n" + + "S2QLVQ8/dWfzzv1WTW3c/54dEfz5/vvnLYJB5mUwqXYPF+8gFA0fPA8VdHos/WxL\n" + + "PfmPe8LxOoS5GHhilfCil9OfDWtb+PdSXQnfRobOjOjzocw7F+eQLWbTTc4FGWTF\n" + + "UI4yNTzgCY2xtivxu7UpPY2ooD7JlmuzrO7TdC8fhj+l/TEgH67wbhhJgFLoDbwA\n" + + "+UkgjAOwJ2Rs4Dv77B9o4HUh2Irn72cHy/UsNxkJgoSEkTb30bJJyNlEnds/qyw=\n" + + "=uSRw\n" + + "-----END PGP PUBLIC KEY BLOCK-----\n"; + PGPPublicKeyRing publicKeys = PGPainless.readKeyRing().publicKeyRing(key); + Iterator keyIt = publicKeys.getPublicKeys(); + PGPPublicKey primaryKey = publicKeys.getPublicKey(); + while (keyIt.hasNext()) { + PGPPublicKey publicKey = keyIt.next(); + // CHECKSTYLE:OFF + System.out.println(publicKey.getKeyID()); + // CHECKSTYLE:ON + if (publicKey.isMasterKey()) { + Iterator signatures = publicKey.getSignatures(); + boolean isValidPrimaryKey = false; + boolean isRevokedPrimaryKey = false; + while (signatures.hasNext()) { + PGPSignature signature = signatures.next(); + } + } else { + Iterator signatures = publicKey.getSignatures(); + while (signatures.hasNext()) { + PGPSignature signature = signatures.next(); + + if (SelectSignatureFromKey.isValidSubkeyBindingSignature(primaryKey, publicKey).accept(signature, publicKey, publicKeys)) { + // CHECKSTYLE:OFF + System.out.println("Valid subkey binding signature"); + // CHECKSTYLE:ON + } + } + } + + + + } + } +}