From 6cb9091b2a8f3a6c439d5fbe8d42a36a17215842 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Fri, 21 May 2021 16:30:48 +0200 Subject: [PATCH] Work on signaturePicker --- .../org/pgpainless/key/EvaluatedKeyRing.java | 119 ++++++- .../org/pgpainless/key/KeyRingValidator.java | 121 +------ .../org/pgpainless/key/info/KeyRingInfo.java | 12 +- .../pgpainless/signature/SignaturePicker.java | 313 +++++++++++------- .../signature/SignatureValidator.java | 86 ++++- .../org/pgpainless/util/NonEmptyList.java | 88 +++++ .../org/pgpainless/util/SignatureTree.java | 35 ++ .../org/pgpainless/util/NonEmptyListTest.java | 62 ++++ .../org/pgpainless/sop/commands/Encrypt.java | 115 ++----- 9 files changed, 614 insertions(+), 337 deletions(-) create mode 100644 pgpainless-core/src/main/java/org/pgpainless/util/NonEmptyList.java create mode 100644 pgpainless-core/src/main/java/org/pgpainless/util/SignatureTree.java create mode 100644 pgpainless-core/src/test/java/org/pgpainless/util/NonEmptyListTest.java diff --git a/pgpainless-core/src/main/java/org/pgpainless/key/EvaluatedKeyRing.java b/pgpainless-core/src/main/java/org/pgpainless/key/EvaluatedKeyRing.java index 34d9314c..1023edee 100644 --- a/pgpainless-core/src/main/java/org/pgpainless/key/EvaluatedKeyRing.java +++ b/pgpainless-core/src/main/java/org/pgpainless/key/EvaluatedKeyRing.java @@ -16,17 +16,132 @@ package org.pgpainless.key; import java.util.List; +import java.util.Map; import javax.annotation.Nullable; +import org.bouncycastle.bcpg.UserAttributePacket; import org.bouncycastle.openpgp.PGPSignature; import org.pgpainless.algorithm.KeyFlag; +import org.pgpainless.exception.SignatureValidationException; +import org.pgpainless.signature.SignatureUtils; +import org.pgpainless.util.NonEmptyList; import org.pgpainless.signature.subpackets.SignatureSubpacketsUtil; public interface EvaluatedKeyRing { - PGPSignature getUserIdCertification(String userId); + class EvaluatedSignature { + private final PGPSignature signature; + private final SignatureValidationException exception; - PGPSignature getUserIdRevocation(String userId); + PGPSignature getValidSignature() throws SignatureValidationException { + if (getException() != null) { + throw new SignatureValidationException("Signature is not valid.", getException()); + } + return signature; + } + + SignatureValidationException getException() { + return exception; + } + + public EvaluatedSignature(PGPSignature signature, SignatureValidationException exception) { + this.signature = signature; + this.exception = exception; + } + } + + /** + * Return a {@link Map} of user-ids and associated user-id certification signatures. + * Each map entry consists of a user-id and a {@link NonEmptyList} of associated certification signatures which + * contains the latest non-revoking certification signature as its first element. + * + * @return map of user-ids and certifications + */ + Map> getUserIdCertifications(); + + /** + * Return the latest user-id certification signature associated to the provided user-id. + * + * @param userId user-id + * @return latest user-id certification signature + * @throws IllegalArgumentException if the key doesn't have at least one certification signature for the + * provided user-id. + */ + default PGPSignature getUserIdCertification(String userId) { + NonEmptyList userIdCerts = getUserIdCertifications().get(userId); + if (userIdCerts == null) { + throw new IllegalArgumentException("No user-id '" + userId + "' found on the key."); + } + return userIdCerts.get(); + } + + /** + * Return a {@link Map} of user-ids and associated user-id revocation signatures. + * + * @return map of user-ids and revocations + */ + Map> getUserIdRevocations(); + + /** + * Return the latest, hardest revocation signature for the passed in user-id. + * + * @param userId user-id + * @return latest hardest revocation signature + * @throws IllegalArgumentException if the key doesn't have at least one certification signature for the given user-id. + */ + default PGPSignature getUserIdRevocation(String userId) { + List userIdRevs = getUserIdRevocations().get(userId); + if (userIdRevs == null) { + throw new IllegalArgumentException("No user-id '" + userId + "' found on the key."); + } + return userIdRevs.isEmpty() ? null : userIdRevs.get(0); + } + + default boolean isRevoked(String userId) { + PGPSignature latestCertification = getUserIdCertification(userId); + PGPSignature latestRevocation = getUserIdRevocation(userId); + + if (latestRevocation == null) { + return false; + } + + return latestRevocation.getCreationTime().after(latestCertification.getCreationTime()) + || SignatureUtils.isHardRevocation(latestRevocation); + } + + /** + * Return a {@link Map} of {@link UserAttributePacket UserAttributePackets} and associated certification signatures. + * Each map entry consists of a {@link UserAttributePacket} and a {@link NonEmptyList} of associated certification + * signatures which contains the latest non-revoking certification signtaure as its first element. + * + * @return map of user-attributes and certifications + */ + Map> getUserAttributeCertifications(); + + /** + * Return the latest certification signature for the provided {@link UserAttributePacket}. + * + * @param userAttribute user attribute + * @return latest certification signature + * @throws IllegalArgumentException if the key doesn't carry such user-attribute + */ + default PGPSignature getUserAttributeCertification(UserAttributePacket userAttribute) { + NonEmptyList userAttrCerts = getUserAttributeCertifications().get(userAttribute); + if (userAttrCerts == null) { + throw new IllegalArgumentException("No such user-attribute found on the key."); + } + return userAttrCerts.get(); + } + + Map> getUserAttributeRevocations(); + + default PGPSignature getUserAttributeRevocation(UserAttributePacket userAttribute) { + List userAttrRevs = getUserAttributeRevocations().get(userAttribute); + if (userAttrRevs == null) { + throw new IllegalArgumentException("No such user-attribute found on the key."); + } + return userAttrRevs.isEmpty() ? null : userAttrRevs.get(0); + } PGPSignature getSubkeyBinding(long subkeyId); diff --git a/pgpainless-core/src/main/java/org/pgpainless/key/KeyRingValidator.java b/pgpainless-core/src/main/java/org/pgpainless/key/KeyRingValidator.java index 35a7176a..2a8f0ac3 100644 --- a/pgpainless-core/src/main/java/org/pgpainless/key/KeyRingValidator.java +++ b/pgpainless-core/src/main/java/org/pgpainless/key/KeyRingValidator.java @@ -26,17 +26,13 @@ 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.exception.SignatureValidationException; +import org.pgpainless.implementation.ImplementationFactory; +import org.pgpainless.policy.Policy; +import org.pgpainless.signature.SignatureCreationDateComparator; import org.pgpainless.signature.SignatureValidator; import org.pgpainless.util.CollectionUtils; @@ -146,115 +142,4 @@ public class KeyRingValidator { 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/info/KeyRingInfo.java b/pgpainless-core/src/main/java/org/pgpainless/key/info/KeyRingInfo.java index c73091f0..b4d1c9bf 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 @@ -629,8 +629,8 @@ public class KeyRingInfo { private final Map subkeyBindings; public Signatures(PGPKeyRing keyRing, Date evaluationDate, Policy policy) { - primaryKeyRevocation = SignaturePicker.pickCurrentRevocationSelfSignature(keyRing, evaluationDate); - primaryKeySelfSignature = SignaturePicker.pickCurrentDirectKeySelfSignature(keyRing, evaluationDate); + primaryKeyRevocation = SignaturePicker.pickCurrentRevocationSelfSignature(keyRing, policy, evaluationDate); + primaryKeySelfSignature = SignaturePicker.pickLatestDirectKeySignature(keyRing, policy, evaluationDate); userIdRevocations = new HashMap<>(); userIdCertifications = new HashMap<>(); subkeyRevocations = new HashMap<>(); @@ -638,11 +638,11 @@ public class KeyRingInfo { for (Iterator it = keyRing.getPublicKey().getUserIDs(); it.hasNext(); ) { String userId = it.next(); - PGPSignature revocation = SignaturePicker.pickCurrentUserIdRevocationSignature(keyRing, userId, evaluationDate); + PGPSignature revocation = SignaturePicker.pickCurrentUserIdRevocationSignature(keyRing, userId, policy, evaluationDate); if (revocation != null) { userIdRevocations.put(userId, revocation); } - PGPSignature certification = SignaturePicker.pickCurrentUserIdCertificationSignature(keyRing, userId, evaluationDate); + PGPSignature certification = SignaturePicker.pickLatestUserIdCertificationSignature(keyRing, userId, policy, evaluationDate); if (certification != null) { userIdCertifications.put(userId, certification); } @@ -652,11 +652,11 @@ public class KeyRingInfo { keys.next(); // Skip primary key while (keys.hasNext()) { PGPPublicKey subkey = keys.next(); - PGPSignature subkeyRevocation = SignaturePicker.pickCurrentSubkeyBindingRevocationSignature(keyRing, subkey, evaluationDate); + PGPSignature subkeyRevocation = SignaturePicker.pickCurrentSubkeyBindingRevocationSignature(keyRing, subkey, policy, evaluationDate); if (subkeyRevocation != null) { subkeyRevocations.put(subkey.getKeyID(), subkeyRevocation); } - PGPSignature subkeyBinding = SignaturePicker.pickCurrentSubkeyBindingSignature(keyRing, subkey, evaluationDate); + PGPSignature subkeyBinding = SignaturePicker.pickLatestSubkeyBindingSignature(keyRing, subkey, policy, evaluationDate); if (subkeyBinding != null) { subkeyBindings.put(subkey.getKeyID(), subkeyBinding); } diff --git a/pgpainless-core/src/main/java/org/pgpainless/signature/SignaturePicker.java b/pgpainless-core/src/main/java/org/pgpainless/signature/SignaturePicker.java index b90702c5..514cd9fc 100644 --- a/pgpainless-core/src/main/java/org/pgpainless/signature/SignaturePicker.java +++ b/pgpainless-core/src/main/java/org/pgpainless/signature/SignaturePicker.java @@ -20,17 +20,12 @@ 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.PGPainless; import org.pgpainless.algorithm.SignatureType; import org.pgpainless.exception.SignatureValidationException; -import org.pgpainless.key.util.RevocationAttributes; import org.pgpainless.policy.Policy; -import org.pgpainless.signature.subpackets.SignatureSubpacketsUtil; import org.pgpainless.util.CollectionUtils; /** @@ -49,14 +44,14 @@ import org.pgpainless.util.CollectionUtils; 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. + * Pick the, at validation date most recent valid key revocation signature. + * If there are hard revocation signatures, the latest hard revocation sig is picked, even if it was created after + * validationDate or if it is already expired. * * @param keyRing key ring * @return most recent, valid key revocation signature */ - public static PGPSignature pickCurrentRevocationSelfSignature(PGPKeyRing keyRing, Date validationDate) { - Policy policy = PGPainless.getPolicy(); + public static PGPSignature pickCurrentRevocationSelfSignature(PGPKeyRing keyRing, Policy policy, Date validationDate) { PGPPublicKey primaryKey = keyRing.getPublicKey(); List signatures = getSortedSignaturesOfType(primaryKey, SignatureType.KEY_REVOCATION); @@ -76,42 +71,36 @@ public class SignaturePicker { } /** - * Pick the current direct key self-signature on the primary key. + * Pick the, at validationDate most recent, valid direct key signature. + * This method might return null, if there is no direct key self-signature which is valid at validationDate. + * * @param keyRing key ring * @param validationDate validation date * @return direct-key self-signature */ - public static PGPSignature pickCurrentDirectKeySelfSignature(PGPKeyRing keyRing, Date validationDate) { + public static PGPSignature pickCurrentDirectKeySelfSignature(PGPKeyRing keyRing, Policy policy, Date validationDate) { PGPPublicKey primaryKey = keyRing.getPublicKey(); - return pickCurrentDirectKeySignature(primaryKey, primaryKey, keyRing, validationDate); + return pickCurrentDirectKeySignature(primaryKey, primaryKey, policy, validationDate); } /** - * Pick the current direct-key signature made by the signing key on the signed key. + * Pick the, at validationDate, latest, valid direct key signature made by signingKey on signedKey. + * This method might return null, if there is no direct key self signature which is valid at validationDate. * * @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) { + public static PGPSignature pickCurrentDirectKeySignature(PGPPublicKey signingKey, PGPPublicKey signedKey, Policy policy, 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. + try { + SignatureValidator.verifyDirectKeySignature(signature, signingKey, signedKey, policy, validationDate); + } catch (SignatureValidationException e) { + // Direct key sig is not valid continue; } mostRecentDirectKeySigBySigningKey = signature; @@ -121,57 +110,94 @@ public class SignaturePicker { } /** - * Pick the most recent user-id revocation signature. + * Pick the, at validationDate, latest direct key signature. + * This method might return an expired signature. + * If there are more than one direct-key signature, and some of those are not expired, the latest non-expired + * yet already effective direct-key signature will be returned. + * + * @param keyRing key ring + * @param validationDate validation date + * @return latest direct key signature + */ + public static PGPSignature pickLatestDirectKeySignature(PGPKeyRing keyRing, Policy policy, Date validationDate) { + PGPPublicKey primaryKey = keyRing.getPublicKey(); + return pickLatestDirectKeySignature(primaryKey, primaryKey, policy, validationDate); + } + + /** + * Pick the, at validationDate, latest direct key signature made by signingKey on signedKey. + * This method might return an expired signature. + * If a non-expired direct-key signature exists, the latest non-expired yet already effective direct-key + * signature will be returned. + * + * @param signingKey signing key (key that made the sig) + * @param signedKey signed key (key that carries the sig) + * @param validationDate date of validation + * @return latest direct key sig + */ + public static PGPSignature pickLatestDirectKeySignature(PGPPublicKey signingKey, PGPPublicKey signedKey, Policy policy, Date validationDate) { + List signatures = getSortedSignaturesOfType(signedKey, SignatureType.DIRECT_KEY); + + PGPSignature latestDirectKeySignature = null; + for (PGPSignature signature : signatures) { + try { + SignatureValidator.signatureIsOfType(SignatureType.DIRECT_KEY).verify(signature); + SignatureValidator.signatureStructureIsAcceptable(signingKey, policy).verify(signature); + SignatureValidator.signatureIsAlreadyEffective(validationDate).verify(signature); + // if the currently latest signature is not yet expired, check if the next candidate is not yet expired + if (latestDirectKeySignature != null && !SignatureUtils.isSignatureExpired(latestDirectKeySignature, validationDate)) { + SignatureValidator.signatureIsNotYetExpired(validationDate).verify(signature); + } + SignatureValidator.correctSignatureOverKey(signingKey, signedKey).verify(signature); + } catch (SignatureValidationException e) { + // Direct key signature is not valid + continue; + } + latestDirectKeySignature = signature; + } + + return latestDirectKeySignature; + } + + /** + * Pick the, at validationDate most recent, valid user-id revocation signature. + * If there are hard revocation signatures, the latest hard revocation sig is picked, even if it was created after + * validationDate or if it is already expired. * * @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) { + public static PGPSignature pickCurrentUserIdRevocationSignature(PGPKeyRing keyRing, String userId, Policy policy, Date validationDate) { PGPPublicKey primaryKey = keyRing.getPublicKey(); + List signatures = getSortedSignaturesOfType(primaryKey, SignatureType.CERTIFICATION_REVOCATION); - Iterator certificationRevocations = primaryKey.getSignaturesOfType(SignatureType.CERTIFICATION_REVOCATION.getCode()); - List signatures = CollectionUtils.iteratorToList(certificationRevocations); - Collections.sort(signatures, new SignatureCreationDateComparator()); - - PGPSignature mostRecentUserIdRevocation = null; + PGPSignature latestUserIdRevocation = null; for (PGPSignature signature : signatures) { - if (!SelectSignatureFromKey.isWellFormed().accept(signature, primaryKey, keyRing)) { - // Sig is not well formed. + try { + SignatureValidator.verifyUserIdRevocation(userId, signature, primaryKey, policy, validationDate); + } catch (SignatureValidationException e) { + // User-id revocation is not valid 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; + latestUserIdRevocation = signature; } - return mostRecentUserIdRevocation; + return latestUserIdRevocation; } /** - * Pick the most current certification self-signature for the given user-id. + * Pick the, at validationDate latest, valid certification self-signature for the given user-id. + * This method might return null, if there is no certification self signature for that user-id which is valid + * at validationDate. * * @param keyRing keyring * @param userId userid * @param validationDate validation date * @return user-id certification */ - public static PGPSignature pickCurrentUserIdCertificationSignature(PGPKeyRing keyRing, String userId, Date validationDate) { + public static PGPSignature pickCurrentUserIdCertificationSignature(PGPKeyRing keyRing, String userId, Policy policy, Date validationDate) { PGPPublicKey primaryKey = keyRing.getPublicKey(); Iterator userIdSigIterator = primaryKey.getSignaturesForID(userId); @@ -180,21 +206,12 @@ public class SignaturePicker { PGPSignature mostRecentUserIdCertification = null; for (PGPSignature signature : signatures) { - if (!SelectSignatureFromKey.isWellFormed().accept(signature, primaryKey, keyRing)) { - // Sig not well formed + try { + SignatureValidator.verifyUserIdCertification(userId, signature, primaryKey, policy, validationDate); + } catch (SignatureValidationException e) { + // User-id certification is not valid 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; } @@ -202,57 +219,88 @@ public class SignaturePicker { } /** - * Return the current subkey binding revocation signature for the given subkey. + * Pick the, at validationDate latest certification self-signature for the given user-id. + * This method might return an expired signature. + * If a non-expired user-id certification signature exists, the latest non-expired yet already effective + * user-id certification signature for the given user-id will be returned. + * + * @param keyRing keyring + * @param userId userid + * @param validationDate validation date + * @return user-id certification + */ + public static PGPSignature pickLatestUserIdCertificationSignature(PGPKeyRing keyRing, String userId, Policy policy, Date validationDate) { + PGPPublicKey primaryKey = keyRing.getPublicKey(); + + Iterator userIdSigIterator = primaryKey.getSignaturesForID(userId); + List signatures = CollectionUtils.iteratorToList(userIdSigIterator); + Collections.sort(signatures, new SignatureCreationDateComparator()); + + PGPSignature latestUserIdCert = null; + for (PGPSignature signature : signatures) { + try { + SignatureValidator.signatureIsCertification().verify(signature); + SignatureValidator.signatureStructureIsAcceptable(primaryKey, policy).verify(signature); + SignatureValidator.signatureIsAlreadyEffective(validationDate).verify(signature); + // if the currently latest signature is not yet expired, check if the next candidate is not yet expired + if (latestUserIdCert != null && !SignatureUtils.isSignatureExpired(latestUserIdCert, validationDate)) { + SignatureValidator.signatureIsNotYetExpired(validationDate).verify(signature); + } + SignatureValidator.correctSignatureOverUserId(userId, primaryKey, primaryKey).verify(signature); + } catch (SignatureValidationException e) { + // User-id certification is not valid + continue; + } + + latestUserIdCert = signature; + } + + return latestUserIdCert; + } + + /** + * Pick the, at validationDate most recent, valid subkey revocation signature. + * If there are hard revocation signatures, the latest hard revocation sig is picked, even if it was created after + * validationDate or if it is already expired. * * @param keyRing keyring * @param subkey subkey * @param validationDate validation date * @return subkey revocation signature */ - public static PGPSignature pickCurrentSubkeyBindingRevocationSignature(PGPKeyRing keyRing, PGPPublicKey subkey, Date validationDate) { + public static PGPSignature pickCurrentSubkeyBindingRevocationSignature(PGPKeyRing keyRing, PGPPublicKey subkey, Policy policy, 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; + List signatures = getSortedSignaturesOfType(subkey, SignatureType.SUBKEY_BINDING); + PGPSignature latestSubkeyRevocation = null; - for (PGPSignature signature : subkeyRevocationSigs) { - if (!SelectSignatureFromKey.isWellFormed().accept(signature, primaryKey, keyRing)) { - // Signature is not well formed + for (PGPSignature signature : signatures) { + try { + SignatureValidator.verifySubkeyBindingRevocation(signature, primaryKey, subkey, policy, validationDate); + } catch (SignatureValidationException e) { + // subkey binding revocation is not valid 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; + latestSubkeyRevocation = signature; } - return mostRecentSubkeyRevocation; + return latestSubkeyRevocation; } /** - * 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. + * Pick the, at validationDate latest, valid subkey binding signature for the given subkey. + * This method might return null, if there is no subkey binding signature which is valid + * at validationDate. * * @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) { + public static PGPSignature pickCurrentSubkeyBindingSignature(PGPKeyRing keyRing, PGPPublicKey subkey, Policy policy, Date validationDate) { PGPPublicKey primaryKey = keyRing.getPublicKey(); if (primaryKey.getKeyID() == subkey.getKeyID()) { throw new IllegalArgumentException("Primary key cannot have subkey binding signature."); @@ -262,35 +310,68 @@ public class SignaturePicker { 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. + try { + SignatureValidator.verifySubkeyBindingSignature(signature, primaryKey, subkey, policy, validationDate); + } catch (SignatureValidationException validationException) { + // Subkey binding sig is not valid 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; } + /** + * Pick the, at validationDate latest subkey binding signature for the given subkey. + * This method might return an expired signature. + * If a non-expired subkey binding signature exists, the latest non-expired yet already effective + * subkey binding signature for the given subkey will be returned. + * + * @param keyRing key ring + * @param subkey subkey + * @param validationDate validationDate + * @return subkey binding signature + */ + public static PGPSignature pickLatestSubkeyBindingSignature(PGPKeyRing keyRing, PGPPublicKey subkey, Policy policy, Date validationDate) { + PGPPublicKey primaryKey = keyRing.getPublicKey(); + if (primaryKey.getKeyID() == subkey.getKeyID()) { + throw new IllegalArgumentException("Primary key cannot have subkey binding signature."); + } + + List signatures = getSortedSignaturesOfType(subkey, SignatureType.SUBKEY_BINDING); + PGPSignature latestSubkeyBinding = null; + + for (PGPSignature signature : signatures) { + try { + SignatureValidator.signatureIsOfType(SignatureType.SUBKEY_BINDING).verify(signature); + SignatureValidator.signatureStructureIsAcceptable(primaryKey, policy).verify(signature); + SignatureValidator.signatureIsAlreadyEffective(validationDate).verify(signature); + // if the currently latest signature is not yet expired, check if the next candidate is not yet expired + if (latestSubkeyBinding != null && !SignatureUtils.isSignatureExpired(latestSubkeyBinding, validationDate)) { + SignatureValidator.signatureIsNotYetExpired(validationDate).verify(signature); + } + SignatureValidator.correctSubkeyBindingSignature(primaryKey, subkey).verify(signature); + } catch (SignatureValidationException e) { + // Subkey binding sig is not valid + continue; + } + latestSubkeyBinding = signature; + } + + return latestSubkeyBinding; + } + + /** + * Return a list of all signatures of the given {@link SignatureType} on the given key, sorted using a + * {@link SignatureCreationDateComparator}. + * + * The returned list will be sorted first by ascending signature creation time. + * + * @param key key + * @param type type of signatures which shall be collected and sorted + * @return sorted list of signatures + */ private static List getSortedSignaturesOfType(PGPPublicKey key, SignatureType type) { Iterator signaturesOfType = key.getSignaturesOfType(type.getCode()); List signatureList = CollectionUtils.iteratorToList(signaturesOfType); 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 7b3dfd12..61000219 100644 --- a/pgpainless-core/src/main/java/org/pgpainless/signature/SignatureValidator.java +++ b/pgpainless-core/src/main/java/org/pgpainless/signature/SignatureValidator.java @@ -178,7 +178,7 @@ public abstract class SignatureValidator { signatureStructureIsAcceptable(primaryKey, policy).verify(signature); signatureIsEffective(validationDate).verify(signature); hasValidPrimaryKeyBindingSignatureIfRequired(primaryKey, subkey, policy, validationDate).verify(signature); - correctSignatureOverKey(primaryKey, subkey).verify(signature); + correctSubkeyBindingSignature(primaryKey, subkey).verify(signature); return true; } @@ -274,22 +274,34 @@ public abstract class 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(); - } + Policy.HashAlgorithmPolicy hashAlgorithmPolicy = getHashAlgorithmPolicyForSignature(signature, policy); if (!hashAlgorithmPolicy.isAcceptable(signature.getHashAlgorithm())) { - throw new SignatureValidationException("Signature uses inacceptable hash algorithm " + hashAlgorithm); + throw new SignatureValidationException("Signature uses unacceptable hash algorithm " + hashAlgorithm); } } }; } + /** + * Return the applicable {@link Policy.HashAlgorithmPolicy} for the given {@link PGPSignature}. + * Revocation signatures are being policed using a different policy than non-revocation signatures. + * + * @param signature signature + * @param policy revocation policy for revocation sigs, normal policy for non-rev sigs + * @return policy + */ + private static Policy.HashAlgorithmPolicy getHashAlgorithmPolicyForSignature(PGPSignature signature, Policy policy) { + 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(); + } + return hashAlgorithmPolicy; + } + public static SignatureValidator signatureDoesNotHaveCriticalUnknownNotations(NotationRegistry registry) { return new SignatureValidator() { @Override @@ -324,15 +336,39 @@ public abstract class SignatureValidator { } public static SignatureValidator signatureIsEffective(Date validationDate) { + return new SignatureValidator() { + @Override + public void verify(PGPSignature signature) throws SignatureValidationException { + signatureIsAlreadyEffective(validationDate).verify(signature); + signatureIsNotYetExpired(validationDate).verify(signature); + } + }; + } + + public static SignatureValidator signatureIsAlreadyEffective(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); - } + // Hard revocations are always effective + if (SignatureUtils.isHardRevocation(signature)) { + return; + } + + if (signatureCreationTime.after(validationDate)) { + throw new SignatureValidationException("Signature was created at " + signatureCreationTime + " and is therefore not yet valid at " + validationDate); + } + } + }; + } + + public static SignatureValidator signatureIsNotYetExpired(Date validationDate) { + return new SignatureValidator() { + @Override + public void verify(PGPSignature signature) throws SignatureValidationException { + // Hard revocations do not expire + if (SignatureUtils.isHardRevocation(signature)) { + return; } Date signatureExpirationTime = SignatureSubpacketsUtil.getSignatureExpirationTimeAsDate(signature); @@ -420,6 +456,26 @@ public abstract class SignatureValidator { }; } + public static SignatureValidator correctSubkeyBindingSignature(PGPPublicKey primaryKey, PGPPublicKey subkey) { + return new SignatureValidator() { + @Override + public void verify(PGPSignature signature) throws SignatureValidationException { + if (primaryKey.getKeyID() == subkey.getKeyID()) { + throw new SignatureValidationException("Primary key cannot be its own subkey."); + } + try { + signature.init(ImplementationFactory.getInstance().getPGPContentVerifierBuilderProvider(), primaryKey); + boolean valid = signature.verifyCertification(primaryKey, subkey); + if (!valid) { + throw new SignatureValidationException("Signature is not correct."); + } + } catch (PGPException e) { + throw new SignatureValidationException("Cannot verify subkey binding signature correctness", e); + } + } + }; + } + public static SignatureValidator correctPrimaryKeyBindingSignature(PGPPublicKey primaryKey, PGPPublicKey subkey) { return new SignatureValidator() { @Override diff --git a/pgpainless-core/src/main/java/org/pgpainless/util/NonEmptyList.java b/pgpainless-core/src/main/java/org/pgpainless/util/NonEmptyList.java new file mode 100644 index 00000000..cc56baed --- /dev/null +++ b/pgpainless-core/src/main/java/org/pgpainless/util/NonEmptyList.java @@ -0,0 +1,88 @@ +/* + * 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; + +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; +import javax.annotation.Nonnull; + +/** + * Utility class of an immutable list which cannot be empty. + * The first element can be accessed via {@link #get()} which is guaranteed to return a non-null value. + * The rest of the list can be accessed via {@link #getOthers()}, which is guaranteed to return a non-null list which is possibly empty. + * Lastly, the whole list can be accessed via {@link #getAll()}, which is guaranteed to return a non-empty list. + * + * @param element type + */ +public class NonEmptyList { + + private final List elements; + + /** + * Create a singleton list from the given element. + * + * @param element element + */ + public NonEmptyList(E element) { + if (element == null) { + throw new IllegalArgumentException("Singleton element cannot be null."); + } + this.elements = Collections.singletonList(element); + } + + /** + * Create a non-empty list from the given list of elements. + * + * @param elements elements + * @throws IllegalArgumentException if the provided list of elements is empty. + */ + public NonEmptyList(List elements) { + if (elements.isEmpty()) { + throw new IllegalArgumentException("Underlying list cannot be empty."); + } + this.elements = Collections.unmodifiableList(elements); + } + + /** + * Return the first element of the list. + * + * @return first + */ + public @Nonnull E get() { + return elements.get(0); + } + + /** + * Return a list of all elements of the list except the first. + * + * @return list of all but the first element + */ + public @Nonnull List getOthers() { + List others = new LinkedList<>(elements); + others.remove(0); + return Collections.unmodifiableList(others); + } + + /** + * Return a non-empty list of all elements of this list. + * + * @return all elements + */ + public List getAll() { + return elements; + } +} diff --git a/pgpainless-core/src/main/java/org/pgpainless/util/SignatureTree.java b/pgpainless-core/src/main/java/org/pgpainless/util/SignatureTree.java new file mode 100644 index 00000000..d1476156 --- /dev/null +++ b/pgpainless-core/src/main/java/org/pgpainless/util/SignatureTree.java @@ -0,0 +1,35 @@ +/* + * 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 SignatureTree { + + public interface Node { + long getKeyId(); + } + + public interface PrimaryKeyNode extends Node { + + } + + public interface SubkeyNode extends Node { + + } + + public interface SignatureNode extends Node { + + } +} diff --git a/pgpainless-core/src/test/java/org/pgpainless/util/NonEmptyListTest.java b/pgpainless-core/src/test/java/org/pgpainless/util/NonEmptyListTest.java new file mode 100644 index 00000000..1280f72e --- /dev/null +++ b/pgpainless-core/src/test/java/org/pgpainless/util/NonEmptyListTest.java @@ -0,0 +1,62 @@ +/* + * 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; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import org.junit.jupiter.api.Test; + +public class NonEmptyListTest { + + @Test + public void testEmptyListThrows() { + assertThrows(IllegalArgumentException.class, () -> new NonEmptyList<>(Collections.emptyList())); + } + + @Test + public void testSingleElementList() { + List singleElement = Collections.singletonList("Hello"); + NonEmptyList nonEmpty = new NonEmptyList<>(singleElement); + assertEquals("Hello", nonEmpty.get()); + assertTrue(nonEmpty.getOthers().isEmpty()); + assertEquals(1, nonEmpty.getAll().size()); + assertTrue(nonEmpty.getAll().contains("Hello")); + } + + @Test + public void testSingletonElement() { + assertThrows(IllegalArgumentException.class, () -> new NonEmptyList<>((String) null)); + NonEmptyList nonEmpty = new NonEmptyList<>("Foo"); + assertEquals("Foo", nonEmpty.get()); + assertTrue(nonEmpty.getOthers().isEmpty()); + assertEquals(Collections.singletonList("Foo"), nonEmpty.getAll()); + } + + @Test + public void testMultipleElements() { + List multipleElements = Arrays.asList("Foo", "Bar", "Baz"); + NonEmptyList nonEmpty = new NonEmptyList<>(multipleElements); + assertEquals("Foo", nonEmpty.get()); + assertEquals(Arrays.asList("Bar", "Baz"), nonEmpty.getOthers()); + assertEquals(multipleElements, nonEmpty.getAll()); + } +} diff --git a/pgpainless-sop/src/main/java/org/pgpainless/sop/commands/Encrypt.java b/pgpainless-sop/src/main/java/org/pgpainless/sop/commands/Encrypt.java index db092dcd..2cb35ebf 100644 --- a/pgpainless-sop/src/main/java/org/pgpainless/sop/commands/Encrypt.java +++ b/pgpainless-sop/src/main/java/org/pgpainless/sop/commands/Encrypt.java @@ -22,28 +22,22 @@ import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; -import java.util.Arrays; -import java.util.HashMap; -import java.util.Map; import java.util.Scanner; +import javax.annotation.Nullable; import org.bouncycastle.openpgp.PGPException; import org.bouncycastle.openpgp.PGPPublicKeyRing; -import org.bouncycastle.openpgp.PGPPublicKeyRingCollection; -import org.bouncycastle.openpgp.PGPSecretKey; import org.bouncycastle.openpgp.PGPSecretKeyRing; -import org.bouncycastle.openpgp.PGPSignature; import org.bouncycastle.util.io.Streams; import org.pgpainless.PGPainless; import org.pgpainless.algorithm.DocumentSignatureType; -import org.pgpainless.algorithm.KeyFlag; -import org.pgpainless.algorithm.SymmetricKeyAlgorithm; -import org.pgpainless.encryption_signing.EncryptionBuilderInterface; +import org.pgpainless.encryption_signing.EncryptionOptions; import org.pgpainless.encryption_signing.EncryptionStream; +import org.pgpainless.encryption_signing.ProducerOptions; +import org.pgpainless.encryption_signing.SigningOptions; import org.pgpainless.key.OpenPgpV4Fingerprint; -import org.pgpainless.key.protection.KeyRingProtectionSettings; -import org.pgpainless.key.protection.CachingSecretKeyRingProtector; import org.pgpainless.key.protection.SecretKeyRingProtector; +import org.pgpainless.key.protection.passphrase_provider.SecretKeyPassphraseProvider; import org.pgpainless.util.Passphrase; import picocli.CommandLine; @@ -90,96 +84,57 @@ public class Encrypt implements Runnable { System.exit(19); } - PGPPublicKeyRing[] pubKeysArray = new PGPPublicKeyRing[certs.length]; + EncryptionOptions encOpt = new EncryptionOptions(); + SigningOptions signOpt = new SigningOptions(); + for (int i = 0 ; i < certs.length; i++) { try (InputStream fileIn = new FileInputStream(certs[i])) { PGPPublicKeyRing publicKey = PGPainless.readKeyRing().publicKeyRing(fileIn); - pubKeysArray[i] = publicKey; + encOpt.addRecipient(publicKey); } catch (IOException e) { err_ln("Cannot read certificate " + certs[i].getName()); err_ln(e.getMessage()); System.exit(1); } } - PGPPublicKeyRingCollection publicKeys; - try { - publicKeys = new PGPPublicKeyRingCollection(Arrays.asList(pubKeysArray)); - } catch (IOException | PGPException e) { - err_ln("Cannot construct public key collection."); - err_ln(e.getMessage()); - System.exit(1); - return; + + for (int i = 0; i < withPassword.length; i++) { + Passphrase passphrase = Passphrase.fromPassword(withPassword[i]); + encOpt.addPassphrase(passphrase); } - PGPSecretKeyRing[] secretKeys = new PGPSecretKeyRing[signWith.length]; + + final Scanner scanner = new Scanner(System.in); for (int i = 0; i < signWith.length; i++) { try (FileInputStream fileIn = new FileInputStream(signWith[i])) { PGPSecretKeyRing secretKey = PGPainless.readKeyRing().secretKeyRing(fileIn); - secretKeys[i] = secretKey; + SecretKeyRingProtector protector = SecretKeyRingProtector.defaultSecretKeyRingProtector( + new SecretKeyPassphraseProvider() { + @Nullable + @Override + public Passphrase getPassphraseFor(Long keyId) { + print_ln("Please provide the passphrase for key " + new OpenPgpV4Fingerprint(secretKey)); + String password = scanner.nextLine(); + Passphrase passphrase = Passphrase.fromPassword(password.trim()); + return passphrase; + } + } + ); + signOpt.addInlineSignature(protector, secretKey, + type == Type.text || type == Type.mime ? + DocumentSignatureType.CANONICAL_TEXT_DOCUMENT : DocumentSignatureType.BINARY_DOCUMENT); } catch (IOException | PGPException e) { err_ln("Cannot read secret key from file " + signWith[i].getName()); err_ln(e.getMessage()); System.exit(1); } } - Passphrase[] passphraseArray = new Passphrase[withPassword.length]; - for (int i = 0; i < withPassword.length; i++) { - String password = withPassword[i]; - passphraseArray[i] = Passphrase.fromPassword(password); - } - Map passphraseMap = new HashMap<>(); - Scanner scanner = null; - for (PGPSecretKeyRing ring : secretKeys) { - for (PGPSecretKey key : ring) { - // Skip non-signing keys - PGPSignature signature = (PGPSignature) key.getPublicKey().getSignatures().next(); - int flags = signature.getHashedSubPackets().getKeyFlags(); - if (!key.isSigningKey() || !KeyFlag.hasKeyFlag(flags, KeyFlag.SIGN_DATA)) { - // Key cannot sign - continue; - } - - if (key.getKeyEncryptionAlgorithm() == SymmetricKeyAlgorithm.NULL.getAlgorithmId()) { - passphraseMap.put(key.getKeyID(), Passphrase.emptyPassphrase()); - } else { - print_ln("Please provide the passphrase for key " + new OpenPgpV4Fingerprint(key)); - if (scanner == null) { - scanner = new Scanner(System.in); - } - String password = scanner.nextLine(); - Passphrase passphrase = Passphrase.fromPassword(password.trim()); - passphraseMap.put(key.getKeyID(), passphrase); - } - } - } - - EncryptionBuilderInterface.ToRecipientsOrSign builder = PGPainless.encryptAndOrSign() - .onOutputStream(System.out) - .toRecipients(publicKeys) - .and(); - for (Passphrase passphrase : passphraseArray) { - builder = builder.forPassphrase(passphrase).and(); - } - EncryptionBuilderInterface.Armor builder_armor = null; - EncryptionBuilderInterface.SignWith builder1 = builder; try { - if (signWith.length != 0) { - for (int i = 0; i < signWith.length; i++) { - PGPSecretKeyRing secretKeyRing = secretKeys[i]; - EncryptionBuilderInterface.AdditionalSignWith additionalSignWith = builder1.signInlineWith( - SecretKeyRingProtector.unlockAllKeysWith( - passphraseMap.get(secretKeyRing.getPublicKey().getKeyID()), - secretKeyRing), - secretKeyRing, null, - type == Type.text || type == Type.mime ? - DocumentSignatureType.CANONICAL_TEXT_DOCUMENT : DocumentSignatureType.BINARY_DOCUMENT); - builder_armor = additionalSignWith; - builder1 = additionalSignWith.and(); - } - } else { - builder_armor = builder.doNotSign(); - } - EncryptionStream encryptionStream = !armor ? builder_armor.noArmor() : builder_armor.asciiArmor(); + EncryptionStream encryptionStream = PGPainless.encryptAndOrSign() + .onOutputStream(System.out) + .withOptions(ProducerOptions + .signAndEncrypt(encOpt, signOpt) + .setAsciiArmor(armor)); Streams.pipeAll(System.in, encryptionStream);