From 099b160656677d2d5ccf4208ef1e79919e9f02d6 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 17 Aug 2021 14:47:07 +0200 Subject: [PATCH] Native support for notBefore and notAfter signature creation time constraints --- .../ConsumerOptions.java | 36 +++++++++++-------- .../DecryptionStreamFactory.java | 2 +- .../SignatureVerifyingInputStream.java | 8 +++-- .../signature/SignatureValidator.java | 27 ++++++++++++++ .../java/org/pgpainless/sop/DecryptImpl.java | 21 ++++------- .../java/org/pgpainless/sop/VerifyImpl.java | 19 ++++------ 6 files changed, 68 insertions(+), 45 deletions(-) diff --git a/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/ConsumerOptions.java b/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/ConsumerOptions.java index aec2766a..5e07e380 100644 --- a/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/ConsumerOptions.java +++ b/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/ConsumerOptions.java @@ -43,8 +43,8 @@ import org.pgpainless.util.Passphrase; */ public class ConsumerOptions { - private Date verifyNotBefore; - private Date verifyNotAfter; + private Date verifyNotBefore = null; + private Date verifyNotAfter = new Date(); // Set of verification keys private final Set certificates = new HashSet<>(); @@ -59,39 +59,45 @@ public class ConsumerOptions { /** - * Consider signatures made before the given timestamp invalid. - * - * Note: This method does not have any effect yet. - * TODO: Add support for custom signature validity date ranges + * Consider signatures on the message made before the given timestamp invalid. + * Null means no limitation. * * @param timestamp timestamp * @return options */ public ConsumerOptions verifyNotBefore(Date timestamp) { this.verifyNotBefore = timestamp; - throw new NotYetImplementedException(); - // return this; + return this; } - public Date getVerifyNotBefore() { + /** + * Return the earliest creation date on which signatures on the message are considered valid. + * Signatures made earlier than this date are considered invalid. + * + * @return earliest allowed signature creation date or null + */ + public @Nullable Date getVerifyNotBefore() { return verifyNotBefore; } /** - * Consider signatures made after the given timestamp invalid. - * - * Note: This method does not have any effect yet. - * TODO: Add support for custom signature validity date ranges + * Consider signatures on the message made after the given timestamp invalid. + * Null means no limitation. * * @param timestamp timestamp * @return options */ public ConsumerOptions verifyNotAfter(Date timestamp) { this.verifyNotAfter = timestamp; - throw new NotYetImplementedException(); - // return this; + return this; } + /** + * Return the latest possible creation date on which signatures made on the message are considered valid. + * Signatures made later than this date are considered invalid. + * + * @return Latest possible creation date or null. + */ public Date getVerifyNotAfter() { return verifyNotAfter; } 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 7a47846e..d3044d42 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 @@ -213,7 +213,7 @@ public final class DecryptionStreamFactory { } return new SignatureVerifyingInputStream(literalDataInputStream, - objectFactory, verifiableOnePassSignatures, resultBuilder); + objectFactory, verifiableOnePassSignatures, options, resultBuilder); } private InputStream decrypt(@Nonnull PGPEncryptedDataList encryptedDataList) 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 3df518ab..d6e7e91b 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 @@ -17,12 +17,12 @@ package org.pgpainless.decryption_verification; import static org.pgpainless.signature.SignatureValidator.signatureIsEffective; import static org.pgpainless.signature.SignatureValidator.signatureStructureIsAcceptable; +import static org.pgpainless.signature.SignatureValidator.verifySignatureCreationTimeIsInBounds; 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; @@ -47,6 +47,7 @@ public class SignatureVerifyingInputStream extends FilterInputStream { private final PGPObjectFactory objectFactory; private final Map onePassSignatures; + private final ConsumerOptions options; private final OpenPgpMetadata.Builder resultBuilder; private boolean validated = false; @@ -54,9 +55,11 @@ public class SignatureVerifyingInputStream extends FilterInputStream { protected SignatureVerifyingInputStream(@Nonnull InputStream inputStream, @Nonnull PGPObjectFactory objectFactory, @Nonnull Map onePassSignatures, + @Nonnull ConsumerOptions options, @Nonnull OpenPgpMetadata.Builder resultBuilder) { super(inputStream); this.objectFactory = objectFactory; + this.options = options; this.resultBuilder = resultBuilder; this.onePassSignatures = onePassSignatures; @@ -116,7 +119,8 @@ public class SignatureVerifyingInputStream extends FilterInputStream { try { PGPPublicKey signingKey = onePassSignature.getVerificationKeys().getPublicKey(signature.getKeyID()); signatureStructureIsAcceptable(signingKey, policy).verify(signature); - signatureIsEffective(new Date()).verify(signature); + verifySignatureCreationTimeIsInBounds(options.getVerifyNotBefore(), options.getVerifyNotAfter()).verify(signature); + signatureIsEffective().verify(signature); SignatureChainValidator.validateSigningKey(signature, onePassSignature.getVerificationKeys(), PGPainless.getPolicy()); diff --git a/pgpainless-core/src/main/java/org/pgpainless/signature/SignatureValidator.java b/pgpainless-core/src/main/java/org/pgpainless/signature/SignatureValidator.java index 30b069be..18003077 100644 --- a/pgpainless-core/src/main/java/org/pgpainless/signature/SignatureValidator.java +++ b/pgpainless-core/src/main/java/org/pgpainless/signature/SignatureValidator.java @@ -45,6 +45,7 @@ import org.pgpainless.key.OpenPgpV4Fingerprint; import org.pgpainless.policy.Policy; import org.pgpainless.signature.subpackets.SignatureSubpacketsUtil; import org.pgpainless.util.BCUtil; +import org.pgpainless.util.DateUtil; import org.pgpainless.util.NotationRegistry; public abstract class SignatureValidator { @@ -664,6 +665,15 @@ public abstract class SignatureValidator { }; } + /** + * Verify that a signature is effective right now. + * + * @return validator + */ + public static SignatureValidator signatureIsEffective() { + return signatureIsEffective(new Date()); + } + /** * Verify that a signature is effective at the given reference date. * @@ -997,4 +1007,21 @@ public abstract class SignatureValidator { }; } + public static SignatureValidator verifySignatureCreationTimeIsInBounds(Date notBefore, Date notAfter) { + return new SignatureValidator() { + @Override + public void verify(PGPSignature signature) throws SignatureValidationException { + Date timestamp = signature.getCreationTime(); + if (notBefore != null && timestamp.before(notBefore)) { + throw new SignatureValidationException("Signature was made before the earliest allowed signature creation time. Created: " + + DateUtil.formatUTCDate(timestamp) + " Earliest allowed: " + DateUtil.formatUTCDate(notBefore)); + } + if (notAfter != null && timestamp.after(notAfter)) { + throw new SignatureValidationException("Signature was made after the latest allowed signature creation time. Created: " + + DateUtil.formatUTCDate(timestamp) + " Latest allowed: " + DateUtil.formatUTCDate(notAfter)); + } + } + }; + } + } diff --git a/pgpainless-sop/src/main/java/org/pgpainless/sop/DecryptImpl.java b/pgpainless-sop/src/main/java/org/pgpainless/sop/DecryptImpl.java index 94535e90..baae4ad6 100644 --- a/pgpainless-sop/src/main/java/org/pgpainless/sop/DecryptImpl.java +++ b/pgpainless-sop/src/main/java/org/pgpainless/sop/DecryptImpl.java @@ -53,7 +53,7 @@ public class DecryptImpl implements Decrypt { try { consumerOptions.verifyNotBefore(timestamp); } catch (NotYetImplementedException e) { - // throw new SOPGPException.UnsupportedOption(); + throw new SOPGPException.UnsupportedOption(); } return this; } @@ -63,7 +63,7 @@ public class DecryptImpl implements Decrypt { try { consumerOptions.verifyNotAfter(timestamp); } catch (NotYetImplementedException e) { - // throw new SOPGPException.UnsupportedOption(); + throw new SOPGPException.UnsupportedOption(); } return this; } @@ -91,7 +91,7 @@ public class DecryptImpl implements Decrypt { } @Override - public DecryptImpl withPassword(String password) throws SOPGPException.PasswordNotHumanReadable, SOPGPException.UnsupportedOption { + public DecryptImpl withPassword(String password) { consumerOptions.addDecryptionPassphrase(Passphrase.fromPassword(password)); String withoutTrailingWhitespace = removeTrailingWhitespace(password); if (!password.equals(withoutTrailingWhitespace)) { @@ -158,17 +158,10 @@ public class DecryptImpl implements Decrypt { List verificationList = new ArrayList<>(); for (SubkeyIdentifier verifiedSigningKey : metadata.getVerifiedSignatures().keySet()) { PGPSignature signature = metadata.getVerifiedSignatures().get(verifiedSigningKey); - Date verifyNotBefore = consumerOptions.getVerifyNotBefore(); - Date verifyNotAfter = consumerOptions.getVerifyNotAfter(); - - if (verifyNotAfter == null || !signature.getCreationTime().after(verifyNotAfter)) { - if (verifyNotBefore == null || !signature.getCreationTime().before(verifyNotBefore)) { - verificationList.add(new Verification( - signature.getCreationTime(), - verifiedSigningKey.getSubkeyFingerprint().toString(), - verifiedSigningKey.getPrimaryKeyFingerprint().toString())); - } - } + verificationList.add(new Verification( + signature.getCreationTime(), + verifiedSigningKey.getSubkeyFingerprint().toString(), + verifiedSigningKey.getPrimaryKeyFingerprint().toString())); } if (!consumerOptions.getCertificates().isEmpty()) { diff --git a/pgpainless-sop/src/main/java/org/pgpainless/sop/VerifyImpl.java b/pgpainless-sop/src/main/java/org/pgpainless/sop/VerifyImpl.java index 047a7ab1..64aa41f8 100644 --- a/pgpainless-sop/src/main/java/org/pgpainless/sop/VerifyImpl.java +++ b/pgpainless-sop/src/main/java/org/pgpainless/sop/VerifyImpl.java @@ -44,7 +44,7 @@ public class VerifyImpl implements Verify { try { options.verifyNotBefore(timestamp); } catch (NotYetImplementedException e) { - // throw new SOPGPException.UnsupportedOption(); + throw new SOPGPException.UnsupportedOption(); } return this; } @@ -54,7 +54,7 @@ public class VerifyImpl implements Verify { try { options.verifyNotAfter(timestamp); } catch (NotYetImplementedException e) { - // throw new SOPGPException.UnsupportedOption(); + throw new SOPGPException.UnsupportedOption(); } return this; } @@ -97,17 +97,10 @@ public class VerifyImpl implements Verify { for (SubkeyIdentifier verifiedSigningKey : metadata.getVerifiedSignatures().keySet()) { PGPSignature signature = metadata.getVerifiedSignatures().get(verifiedSigningKey); - Date verifyNotBefore = options.getVerifyNotBefore(); - Date verifyNotAfter = options.getVerifyNotAfter(); - - if (verifyNotAfter == null || !signature.getCreationTime().after(verifyNotAfter)) { - if (verifyNotBefore == null || !signature.getCreationTime().before(verifyNotBefore)) { - verificationList.add(new Verification( - signature.getCreationTime(), - verifiedSigningKey.getSubkeyFingerprint().toString(), - verifiedSigningKey.getPrimaryKeyFingerprint().toString())); - } - } + verificationList.add(new Verification( + signature.getCreationTime(), + verifiedSigningKey.getSubkeyFingerprint().toString(), + verifiedSigningKey.getPrimaryKeyFingerprint().toString())); } if (!options.getCertificates().isEmpty()) {