1
0
Fork 0
mirror of https://github.com/pgpainless/pgpainless.git synced 2025-12-10 14:21:09 +01:00

Proper Signature Verification

This commit is contained in:
Paul Schaub 2021-04-26 13:38:12 +02:00
parent 6ee8a9416f
commit 64cc9ecca4
67 changed files with 7950 additions and 688 deletions

View file

@ -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<Integer, PublicKeyAlgorithm> 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;
}
}

View file

@ -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);
}
}
}

View file

@ -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);
}
}
}

View file

@ -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.

View file

@ -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);
}
}
}

View file

@ -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;
}
}

View file

@ -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 {

View file

@ -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());
}
}
}
}

View file

@ -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()));
}
}

View file

@ -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<PGPPublicKey> encryptionKeys = new HashSet<>();
private final Map<SubkeyIdentifier, PGPPublicKeyRing> encryptionKeys = new ConcurrentHashMap<>();
private final Set<Passphrase> encryptionPassphrases = new HashSet<>();
private boolean detachedSignature = false;
private SignatureType signatureType = SignatureType.BINARY_DOCUMENT;
private final Set<PGPSecretKey> signingKeys = new HashSet<>();
private final Map<SubkeyIdentifier, PGPSecretKeyRing> 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<PGPPublicKey> 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<SubkeyIdentifier, PGPPublicKeyRing> 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<PGPPublicKey> 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<PGPPublicKey> 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 <O> WithAlgorithms toRecipients(@Nonnull PublicKeyRingSelectionStrategy<O> ringSelectionStrategy,
@Nonnull MultiMap<O, PGPPublicKeyRingCollection> keys) {
if (keys.isEmpty()) {
throw new IllegalArgumentException("Recipient map MUST NOT be empty.");
}
MultiMap<O, PGPPublicKeyRing> acceptedKeyRings = ringSelectionStrategy.selectKeyRingsFromCollections(keys);
for (O identifier : acceptedKeyRings.keySet()) {
Set<PGPPublicKeyRing> acceptedSet = acceptedKeyRings.get(identifier);
for (PGPPublicKeyRing ring : acceptedSet) {
for (PGPPublicKeyRingCollection collection : keys) {
for (PGPPublicKeyRing ring : collection) {
Map<SubkeyIdentifier, PGPPublicKeyRing> 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<SubkeyIdentifier, PGPPublicKeyRing> encryptionKeys = new ConcurrentHashMap<>();
for (Iterator<PGPPublicKey> 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<SubkeyIdentifier, PGPPublicKeyRing> encryptionKeys = new ConcurrentHashMap<>();
for (Iterator<PGPPublicKey> 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 <O> WithAlgorithms andToSelf(@Nonnull PublicKeyRingSelectionStrategy<O> ringSelectionStrategy,
@Nonnull MultiMap<O, PGPPublicKeyRingCollection> keys) {
if (keys.isEmpty()) {
throw new IllegalArgumentException("Recipient list MUST NOT be empty.");
}
MultiMap<O, PGPPublicKeyRing> acceptedKeyRings =
ringSelectionStrategy.selectKeyRingsFromCollections(keys);
for (O identifier : acceptedKeyRings.keySet()) {
Set<PGPPublicKeyRing> acceptedSet = acceptedKeyRings.get(identifier);
for (PGPPublicKeyRing k : acceptedSet) {
for (Iterator<PGPPublicKey> 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 <O> DocumentType signWith(@Nonnull SecretKeyRingSelectionStrategy<O> selectionStrategy,
@Nonnull SecretKeyRingProtector decryptor,
@Nonnull MultiMap<O, PGPSecretKeyRingCollection> 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<PGPSecretKey> i = key.getSecretKeys(); i.hasNext(); ) {
for (PGPSecretKeyRing ring : keyRings) {
Map<SubkeyIdentifier, PGPSecretKeyRing> signingKeys = new ConcurrentHashMap<>();
for (Iterator<PGPSecretKey> 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 <O> DocumentType signWith(@Nonnull SecretKeyRingSelectionStrategy<O> ringSelectionStrategy,
@Nonnull SecretKeyRingProtector decryptor,
@Nonnull MultiMap<O, PGPSecretKeyRingCollection> keys) {
if (keys.isEmpty()) {
throw new IllegalArgumentException("Recipient list MUST NOT be empty.");
}
MultiMap<O, PGPSecretKeyRing> acceptedKeyRings =
ringSelectionStrategy.selectKeyRingsFromCollections(keys);
for (O identifier : acceptedKeyRings.keySet()) {
Set<PGPSecretKeyRing> acceptedSet = acceptedKeyRings.get(identifier);
for (PGPSecretKeyRing k : acceptedSet) {
for (Iterator<PGPSecretKey> i = k.getSecretKeys(); i.hasNext(); ) {
PGPSecretKey s = i.next();
if (EncryptionBuilder.this.<O>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<OpenPgpV4Fingerprint, PGPPrivateKey> privateKeys = new ConcurrentHashMap<>();
for (PGPSecretKey secretKey : signingKeys) {
Map<SubkeyIdentifier, Tuple<PGPSecretKeyRing, PGPPrivateKey>> 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(

View file

@ -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 <O> selection criteria type (eg. email address) on which the selection strategy is based
* @return api handle
*/
<O> WithAlgorithms toRecipients(@Nonnull PublicKeyRingSelectionStrategy<O> selectionStrategy,
@Nonnull MultiMap<O, PGPPublicKeyRingCollection> 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 <O> selection criteria type (eg. email address) used by the selection strategy.
* @return api handle
*/
<O> WithAlgorithms andToSelf(@Nonnull PublicKeyRingSelectionStrategy<O> selectionStrategy,
@Nonnull MultiMap<O, PGPPublicKeyRingCollection> 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 <O> selection criteria type (eg. email address)
* @return api handle
*
* @throws SecretKeyNotFoundException in case no suitable secret key can be found
*/
<O> DocumentType signWith(@Nonnull SecretKeyRingSelectionStrategy<O> selectionStrategy,
@Nonnull SecretKeyRingProtector decryptor,
@Nonnull MultiMap<O, PGPSecretKeyRingCollection> keys)
throws SecretKeyNotFoundException;
}
interface DocumentType {

View file

@ -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<PGPPublicKey> encryptionKeys;
private final Map<SubkeyIdentifier, PGPPublicKeyRing> encryptionKeys;
private final Set<Passphrase> encryptionPassphrases;
private final boolean detachedSignature;
private final SignatureType signatureType;
private final Map<OpenPgpV4Fingerprint, PGPPrivateKey> signingKeys;
private final Map<SubkeyIdentifier, Tuple<PGPSecretKeyRing, PGPPrivateKey>> signingKeys;
private final boolean asciiArmor;
private final OpenPgpMetadata.Builder resultBuilder = OpenPgpMetadata.getBuilder();
private Map<OpenPgpV4Fingerprint, PGPSignatureGenerator> signatureGenerators = new ConcurrentHashMap<>();
private Map<SubkeyIdentifier, Tuple<PGPSecretKeyRing, PGPSignatureGenerator>> 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<PGPPublicKey> encryptionKeys,
@Nonnull Map<SubkeyIdentifier, PGPPublicKeyRing> encryptionKeys,
@Nonnull Set<Passphrase> encryptionPassphrases,
boolean detachedSignature,
SignatureType signatureType,
@Nonnull Map<OpenPgpV4Fingerprint, PGPPrivateKey> signingKeys,
@Nonnull Map<SubkeyIdentifier, Tuple<PGPSecretKeyRing, PGPPrivateKey>> 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);
}

View file

@ -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 extends PGPKeyRing> R validate(R keyRing, Policy policy) {
try {
return validate(keyRing, policy, policy.getSignatureValidationDate());
} catch (PGPException e) {
return null;
}
}
public static <R extends PGPKeyRing> R validate(R keyRing, Policy policy, Date validationDate) throws PGPException {
return getKeyRingAtDate(keyRing, policy, validationDate);
}
private static <R extends PGPKeyRing> 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<PGPSignature> directKeyIterator = primaryKey.getSignaturesOfType(SignatureType.DIRECT_KEY.getCode());
List<PGPSignature> 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<PGPSignature> revocationIterator = primaryKey.getSignaturesOfType(SignatureType.KEY_REVOCATION.getCode());
List<PGPSignature> 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<String> userIdIterator = primaryKey.getUserIDs();
while (userIdIterator.hasNext()) {
String userId = userIdIterator.next();
Iterator<PGPSignature> userIdSigs = primaryKey.getSignaturesForID(userId);
List<PGPSignature> 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<PGPUserAttributeSubpacketVector> userAttributes = primaryKey.getUserAttributes();
while (userAttributes.hasNext()) {
PGPUserAttributeSubpacketVector userAttribute = userAttributes.next();
Iterator<PGPSignature> 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 extends PGPKeyRing> R getKeyRingAtDate(R keyRing, KeyRingInfo info) {
Iterator<PGPPublicKey> 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 extends PGPKeyRing> 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<String> userIdIterator = primaryKey.getUserIDs();
while (userIdIterator.hasNext()) {
String userId = userIdIterator.next();
boolean isUserIdBound = false;
Iterator<PGPSignature> 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<PGPSignature> 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 extends PGPKeyRing> 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<PGPSignature> 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<PGPSignature> 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<PGPSignature> 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;
}
}

View file

@ -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<PGPSignature> 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;
}
}

View file

@ -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();
}
}

View file

@ -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 {

View file

@ -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.

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -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<String, PGPSignature> mostRecentUserIdSignatures = new ConcurrentHashMap<>();
private final Map<String, PGPSignature> mostRecentUserIdRevocations = new ConcurrentHashMap<>();
private final Map<Long, PGPSignature> mostRecentSubkeyBindings = new ConcurrentHashMap<>();
private final Map<Long, PGPSignature> 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<String> 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<PGPPublicKey> 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<String> 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<KeyFlag> 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<KeyFlag> 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<PGPSignature> 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<PGPSignature> 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<PGPSignature> 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<PGPSignature> getSelfSignaturesOnKey(long subkeyId) {
PGPPublicKey publicKey = KeyRingUtils.requirePublicKeyFrom(keys, subkeyId);
Iterator<PGPSignature> it = publicKey.getSignaturesForKeyID(keys.getPublicKey().getKeyID());
List<PGPSignature> 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<PGPSignature> signatures = getSelfSignaturesOnKey(keys.getPublicKey().getKeyID());
return getLatestValidSignature(publicKey, signatures, keys);
}
public PGPSignature getLatestValidSignatureOnUserId(String userId) throws PGPException {
PGPPublicKey publicKey = KeyRingUtils.requirePrimaryPublicKeyFrom(keys);
Iterator<PGPSignature> iterator = publicKey.getSignaturesForID(userId);
List<PGPSignature> signatures = iteratorToList(iterator);
return getLatestValidSignature(publicKey, signatures, keys);
}
public List<PGPSignature> getBindingSignaturesOnKey(OpenPgpV4Fingerprint fingerprint) {
return getBindingSignaturesOnKey(fingerprint.getKeyId());
}
public List<PGPSignature> 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<PGPSignature> 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;
}
}

View file

@ -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

View file

@ -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<Byte, Reason> 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();

View file

@ -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;
}
}

View file

@ -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();
}
}

View file

@ -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;
}
}

View file

@ -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<PGPSignature> select(List<PGPSignature> signatures, PGPPublicKey key, PGPKeyRing keyRing) {
List<PGPSignature> 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<KeyFlag> 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);
}
};
}
}

View file

@ -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<PGPSignature, Exception> 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<PGPSignature> directKeySignatures = new ArrayList<>();
Iterator<PGPSignature> 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<PGPSignature> 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<String> userIds = primaryKey.getUserIDs();
Map<String, List<PGPSignature>> userIdSignatures = new ConcurrentHashMap<>();
while (userIds.hasNext()) {
List<PGPSignature> signaturesOnUserId = new ArrayList<>();
String userId = userIds.next();
Iterator<PGPSignature> 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<PGPSignature> subkeySigs = new ArrayList<>();
Iterator<PGPSignature> 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<PGPSignature> 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;
}
}

View file

@ -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<PGPSignature> {
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());
}
}

View file

@ -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<PGPSignature> 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<PGPSignature> 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<PGPSignature> certificationRevocations = primaryKey.getSignaturesOfType(SignatureType.CERTIFICATION_REVOCATION.getCode());
List<PGPSignature> 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<PGPSignature> userIdSigIterator = primaryKey.getSignaturesForID(userId);
List<PGPSignature> 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<PGPSignature> 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<PGPSignature> 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<PGPSignature> getSortedSignaturesOfType(PGPPublicKey key, SignatureType type) {
Iterator<PGPSignature> signaturesOfType = key.getSignaturesOfType(type.getCode());
List<PGPSignature> signatureList = CollectionUtils.iteratorToList(signaturesOfType);
Collections.sort(signatureList, new SignatureCreationDateComparator());
return signatureList;
}
}

View file

@ -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<PGPSignature> 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());
}
}

View file

@ -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<PGPSignature, Exception> rejections) {
super(message + ": " + exceptionMapToString(rejections));
}
private static String exceptionMapToString(Map<PGPSignature, Exception> 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;
};
}

View file

@ -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<PGPSignature, Exception> 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<NotationData> 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<PGPSignature> 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);
}
}
};
}
}

View file

@ -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<PGPSignature> {
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;
}
}

View file

@ -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;

View file

@ -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;

View file

@ -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<NotationData> 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<NotationData> 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<IntendedRecipientFingerprint> getIntendedRecipientFingerprints(PGPSignature signature) {
org.bouncycastle.bcpg.SignatureSubpacket[] subpackets = signature.getHashedSubPackets().getSubpackets(SignatureSubpacket.intendedRecipientFingerprint.getCode());
List<IntendedRecipientFingerprint> 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 extends org.bouncycastle.bcpg.SignatureSubpacket> P hashed(PGPSignature signature, SignatureSubpacket type) {
return getSignatureSubpacket(signature.getHashedSubPackets(), type);
}
private static <P extends org.bouncycastle.bcpg.SignatureSubpacket> P unhashed(PGPSignature signature, SignatureSubpacket type) {
return getSignatureSubpacket(signature.getUnhashedSubPackets(), type);
}
private static <P extends org.bouncycastle.bcpg.SignatureSubpacket> 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 <P> generic return type of the subpacket
* @return last occurrence of the subpacket in the vector
*/
public static <P extends org.bouncycastle.bcpg.SignatureSubpacket> 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
}
}

View file

@ -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;

View file

@ -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;
};
}

View file

@ -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<String> 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.

View file

@ -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<A, B> {
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;
}
}

View file

@ -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<PGPPublicKey> selectPublicKeys(PGPKeyRing keyRing) {
List<PGPPublicKey> selected = new ArrayList<>();
List<PGPPublicKey> publicKeys = CollectionUtils.iteratorToList(keyRing.getPublicKeys());
for (PGPPublicKey publicKey : publicKeys) {
if (accept(publicKey, keyRing)) {
selected.add(publicKey);
}
}
return selected;
}
public PGPPublicKey firstMatch(PGPKeyRing keyRing) {
List<PGPPublicKey> 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<PGPSignature> 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<String> 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<PGPSignature> revSigIt = publicKey.getSignaturesOfType(SignatureType.KEY_REVOCATION.getCode());
List<PGPSignature> revSigs = CollectionUtils.iteratorToList(revSigIt);
List<PGPSignature> 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<PGPSignature> 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<PGPSignature> 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);
}
};
}
}

View file

@ -44,7 +44,10 @@ public class HasAnyKeyFlagSelectionStrategy {
@Override
public boolean accept(PGPPublicKey key) {
Iterator<PGPSignature> signatures = key.getSignatures();
int flags = signatures.next().getHashedSubPackets().getKeyFlags();
int flags = 0;
while (signatures.hasNext()) {
flags = signatures.next().getHashedSubPackets().getKeyFlags();
}
return (keyFlagMask & flags) != 0;
}
}