1
0
Fork 0
mirror of https://github.com/pgpainless/pgpainless.git synced 2024-11-25 22:02:05 +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

@ -96,6 +96,10 @@ allprojects {
test {
useJUnitPlatform()
testLogging {
events "passed", "skipped", "failed"
exceptionFormat "full"
}
}
}

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

View file

@ -0,0 +1,60 @@
/*
* Copyright 2021 Paul Schaub.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.bouncycastle;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import java.security.InvalidAlgorithmParameterException;
import java.security.NoSuchAlgorithmException;
import java.util.Iterator;
import org.bouncycastle.openpgp.PGPException;
import org.bouncycastle.openpgp.PGPPublicKey;
import org.bouncycastle.openpgp.PGPPublicKeyRing;
import org.bouncycastle.openpgp.PGPSecretKeyRing;
import org.junit.jupiter.api.Test;
import org.pgpainless.PGPainless;
import org.pgpainless.key.util.KeyRingUtils;
public class PGPPublicKeyRingTest {
/**
* Learning test to see if BC also makes userids available on subkeys.
* It does not.
*
* see also https://security.stackexchange.com/questions/92635/is-it-possible-to-assign-different-uids-to-subkeys-for-the-purpose-of-having-mul
*
* @throws InvalidAlgorithmParameterException
* @throws NoSuchAlgorithmException
* @throws PGPException
*/
@Test
public void subkeysDoNotHaveUserIDsTest() throws InvalidAlgorithmParameterException, NoSuchAlgorithmException, PGPException {
PGPSecretKeyRing secretKeys = PGPainless.generateKeyRing().simpleEcKeyRing("primary@user.id");
PGPPublicKeyRing publicKeys = KeyRingUtils.publicKeyRingFrom(secretKeys);
PGPPublicKey primaryKey = publicKeys.getPublicKey();
for (PGPPublicKey subkey : publicKeys) {
Iterator<String> userIds = subkey.getUserIDs();
if (primaryKey == subkey) {
assertEquals("primary@user.id", userIds.next());
assertFalse(userIds.hasNext());
} else {
assertFalse(userIds.hasNext());
}
}
}
}

View file

@ -17,9 +17,16 @@ package org.junit;
import static org.junit.jupiter.api.Assertions.assertTrue;
import org.junit.jupiter.api.Test;
public class JUtils {
public static void assertEquals(long a, long b, long delta) {
assertTrue(a - delta <= b && a + delta >= b);
}
@Test
public void comparatorLearningTest() {
assertEquals(-1, Integer.compare(5,6), 0);
}
}

View file

@ -18,6 +18,7 @@ package org.pgpainless.encryption_signing;
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.io.ByteArrayInputStream;
@ -36,6 +37,7 @@ import org.bouncycastle.openpgp.PGPPublicKeyRing;
import org.bouncycastle.openpgp.PGPSecretKeyRing;
import org.bouncycastle.openpgp.PGPSignature;
import org.bouncycastle.util.io.Streams;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;
import org.pgpainless.PGPainless;
@ -280,4 +282,68 @@ public class EncryptDecryptTest {
OpenPgpMetadata metadata = verifier.getResult();
assertFalse(metadata.getVerifiedSignatures().isEmpty());
}
@Test
public void expiredSubkeyBacksigTest() throws IOException {
String key = "-----BEGIN PGP PUBLIC KEY BLOCK-----\n" +
"\n" +
"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\n" +
"=j1TE\n" +
"-----END PGP PUBLIC KEY BLOCK-----\n";
PGPPublicKeyRing publicKeys = PGPainless.readKeyRing().publicKeyRing(key);
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
assertThrows(IllegalArgumentException.class, () ->
PGPainless.encryptAndOrSign().onOutputStream(outputStream)
.toRecipients(publicKeys));
}
}

View file

@ -72,7 +72,7 @@ public class SigningTest {
.toRecipients(keys)
.andToSelf(KeyRingUtils.publicKeyRingFrom(cryptieKeys))
.usingAlgorithms(SymmetricKeyAlgorithm.AES_192, HashAlgorithm.SHA384, CompressionAlgorithm.ZIP)
.signWith(SecretKeyRingProtector.unlockSingleKeyWith(TestKeys.CRYPTIE_PASSPHRASE, cryptieSigningKey), cryptieSigningKey)
.signWith(SecretKeyRingProtector.unlockSingleKeyWith(TestKeys.CRYPTIE_PASSPHRASE, cryptieSigningKey), cryptieKeys)
.signCanonicalText()
.asciiArmor();

View file

@ -0,0 +1,289 @@
/*
* Copyright 2021 Paul Schaub.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.pgpainless.key;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.io.IOException;
import java.util.Date;
import java.util.Iterator;
import org.bouncycastle.openpgp.PGPPublicKey;
import org.bouncycastle.openpgp.PGPPublicKeyRing;
import org.junit.jupiter.api.Test;
import org.pgpainless.PGPainless;
import org.pgpainless.key.info.KeyRingInfo;
import org.pgpainless.util.ArmorUtils;
import org.pgpainless.util.CollectionUtils;
import org.pgpainless.util.TestUtils;
public class KeyRingValidatorTest {
@Test
public void testRevokedSubkey() throws IOException {
String key = "-----BEGIN PGP ARMORED FILE-----\n" +
"Comment: ASCII Armor added by openpgp-interoperability-test-suite\n" +
"\n" +
"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\n" +
"Y87zSryajDMFXQS0exdvhN4AXDlPlB3Rrkj7CQ==\n" +
"=yTKS\n" +
"-----END PGP ARMORED FILE-----\n";
PGPPublicKeyRing keyRing = PGPainless.readKeyRing().publicKeyRing(key);
PGPPublicKeyRing validated = KeyRingValidator.validate(keyRing, PGPainless.getPolicy());
Iterator<PGPPublicKey> keys = validated.getPublicKeys();
assertFalse(keys.next().hasRevocation());
assertTrue(keys.next().hasRevocation());
}
@Test
public void badSignatureTest() throws IOException {
String key = "-----BEGIN PGP PUBLIC KEY BLOCK-----\n" +
"\n" +
"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\n" +
"bVrj5KjhX2PVNEJd3XZRzaXZE2Z/MQ==\n" +
"=6+l9\n" +
"-----END PGP PUBLIC KEY BLOCK-----\n";
PGPPublicKeyRing publicKeys = PGPainless.readKeyRing().publicKeyRing(key);
PGPPublicKeyRing validated = KeyRingValidator.validate(publicKeys, PGPainless.getPolicy());
// CHECKSTYLE:OFF
System.out.println(ArmorUtils.toAsciiArmoredString(validated));
// CHECKSTYLE:ON
}
@Test
public void unboundSubkey() throws IOException {
String key = "-----BEGIN PGP PUBLIC KEY BLOCK-----\n" +
"\n" +
"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\n" +
"=T4QR\n" +
"-----END PGP PUBLIC KEY BLOCK-----\n";
PGPPublicKeyRing publicKeys = PGPainless.readKeyRing().publicKeyRing(key);
PGPPublicKey unbound = CollectionUtils.iteratorToList(publicKeys.getPublicKeys()).get(2);
assertNotNull(unbound);
Date validationDate = TestUtils.getUTCDate("2019-10-15 10:18:26 UTC");
KeyRingInfo info = new KeyRingInfo(publicKeys, validationDate);
for (PGPPublicKey publicKey : publicKeys) {
if (publicKey != unbound) {
assertTrue(info.isKeyValidlyBound(publicKey.getKeyID()));
} else {
assertFalse(info.isKeyValidlyBound(publicKey.getKeyID()));
}
}
}
@Test
public void expired() throws IOException {
String key = "-----BEGIN PGP PUBLIC KEY BLOCK-----\n" +
"\n" +
"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\n" +
"=LxAY\n" +
"-----END PGP PUBLIC KEY BLOCK-----\n";
PGPPublicKeyRing publicKeys = PGPainless.readKeyRing().publicKeyRing(key);
PGPPublicKeyRing validated = KeyRingValidator.validate(publicKeys, PGPainless.getPolicy());
}
}

View file

@ -61,7 +61,7 @@ public class GenerateKeyWithAdditionalUserIdTest {
.build();
PGPPublicKeyRing publicKeys = KeyRingUtils.publicKeyRingFrom(secretKeys);
JUtils.assertEquals(expiration.getTime(), PGPainless.inspectKeyRing(publicKeys).getExpirationDate().getTime(),2000);
JUtils.assertEquals(expiration.getTime(), PGPainless.inspectKeyRing(publicKeys).getPrimaryKeyExpirationDate().getTime(),2000);
Iterator<String> userIds = publicKeys.getPublicKey().getUserIDs();
assertEquals("primary@user.id", userIds.next());

View file

@ -34,6 +34,7 @@ import org.pgpainless.algorithm.PublicKeyAlgorithm;
import org.pgpainless.key.TestKeys;
import org.pgpainless.key.protection.UnprotectedKeysProtector;
import org.pgpainless.key.util.KeyRingUtils;
import org.pgpainless.util.ArmorUtils;
import org.pgpainless.util.Passphrase;
public class KeyRingInfoTest {
@ -67,8 +68,8 @@ public class KeyRingInfoTest {
assertEquals(TestKeys.EMIL_CREATION_DATE, sInfo.getCreationDate());
assertEquals(TestKeys.EMIL_CREATION_DATE, pInfo.getCreationDate());
assertNull(sInfo.getExpirationDate());
assertNull(pInfo.getExpirationDate());
assertNull(sInfo.getPrimaryKeyExpirationDate());
assertNull(pInfo.getPrimaryKeyExpirationDate());
assertEquals(TestKeys.EMIL_CREATION_DATE.getTime(), sInfo.getLastModified().getTime(), 50);
assertEquals(TestKeys.EMIL_CREATION_DATE.getTime(), pInfo.getLastModified().getTime(), 50);
@ -76,6 +77,9 @@ public class KeyRingInfoTest {
assertNull(pInfo.getRevocationDate());
Date revocationDate = new Date();
PGPSecretKeyRing revoked = PGPainless.modifyKeyRing(secretKeys).revoke(new UnprotectedKeysProtector()).done();
// CHECKSTYLE:OFF
System.out.println(ArmorUtils.toAsciiArmoredString(revoked));
// CHECKSTYLE:ON
KeyRingInfo rInfo = PGPainless.inspectKeyRing(revoked);
assertNotNull(rInfo.getRevocationDate());
assertEquals(revocationDate.getTime(), rInfo.getRevocationDate().getTime(), 1000);

View file

@ -45,7 +45,7 @@ import org.pgpainless.key.protection.PasswordBasedSecretKeyRingProtector;
import org.pgpainless.key.protection.SecretKeyRingProtector;
import org.pgpainless.key.protection.UnprotectedKeysProtector;
import org.pgpainless.key.util.RevocationAttributes;
import org.pgpainless.key.util.SignatureUtils;
import org.pgpainless.signature.SignatureUtils;
public class UserIdRevocationTest {

View file

@ -45,17 +45,17 @@ public class ChangeExpirationTest {
PGPSecretKeyRing secretKeys = TestKeys.getEmilSecretKeyRing();
KeyRingInfo sInfo = PGPainless.inspectKeyRing(secretKeys);
assertNull(sInfo.getExpirationDate());
assertNull(sInfo.getExpirationDate(subKeyFingerprint));
assertNull(sInfo.getPrimaryKeyExpirationDate());
assertNull(sInfo.getSubkeyExpirationDate(subKeyFingerprint));
Date date = new Date(1606493432000L);
secretKeys = PGPainless.modifyKeyRing(secretKeys)
.setExpirationDate(date, new UnprotectedKeysProtector()).done();
sInfo = PGPainless.inspectKeyRing(secretKeys);
assertNotNull(sInfo.getExpirationDate());
assertEquals(date.getTime(), sInfo.getExpirationDate().getTime());
assertNotNull(sInfo.getPrimaryKeyExpirationDate());
assertEquals(date.getTime(), sInfo.getPrimaryKeyExpirationDate().getTime());
// subkey unchanged
assertNull(sInfo.getExpirationDate(subKeyFingerprint));
assertNull(sInfo.getSubkeyExpirationDate(subKeyFingerprint));
// We need to wait for one second as OpenPGP signatures have coarse-grained (up to a second)
// accuracy. Creating two signatures within a short amount of time will make the second one
@ -66,8 +66,8 @@ public class ChangeExpirationTest {
.setExpirationDate(null, new UnprotectedKeysProtector()).done();
sInfo = PGPainless.inspectKeyRing(secretKeys);
assertNull(sInfo.getExpirationDate());
assertNull(sInfo.getExpirationDate(subKeyFingerprint));
assertNull(sInfo.getPrimaryKeyExpirationDate());
assertNull(sInfo.getSubkeyExpirationDate(subKeyFingerprint));
}
@Test
@ -75,16 +75,16 @@ public class ChangeExpirationTest {
PGPSecretKeyRing secretKeys = TestKeys.getEmilSecretKeyRing();
KeyRingInfo sInfo = PGPainless.inspectKeyRing(secretKeys);
assertNull(sInfo.getExpirationDate(subKeyFingerprint));
assertNull(sInfo.getExpirationDate());
assertNull(sInfo.getSubkeyExpirationDate(subKeyFingerprint));
assertNull(sInfo.getPrimaryKeyExpirationDate());
Date date = new Date(1606493432000L);
secretKeys = PGPainless.modifyKeyRing(secretKeys)
.setExpirationDate(subKeyFingerprint, date, new UnprotectedKeysProtector()).done();
sInfo = PGPainless.inspectKeyRing(secretKeys);
assertNotNull(sInfo.getExpirationDate(subKeyFingerprint));
assertEquals(date.getTime(), sInfo.getExpirationDate(subKeyFingerprint).getTime());
assertNull(sInfo.getExpirationDate());
assertNotNull(sInfo.getSubkeyExpirationDate(subKeyFingerprint));
assertEquals(date.getTime(), sInfo.getSubkeyExpirationDate(subKeyFingerprint).getTime());
assertNull(sInfo.getPrimaryKeyExpirationDate());
// We need to wait for one second as OpenPGP signatures have coarse-grained (up to a second)
// accuracy. Creating two signatures within a short amount of time will make the second one
@ -95,7 +95,7 @@ public class ChangeExpirationTest {
.setExpirationDate(subKeyFingerprint, null, new UnprotectedKeysProtector()).done();
sInfo = PGPainless.inspectKeyRing(secretKeys);
assertNull(sInfo.getExpirationDate(subKeyFingerprint));
assertNull(sInfo.getExpirationDate());
assertNull(sInfo.getSubkeyExpirationDate(subKeyFingerprint));
assertNull(sInfo.getPrimaryKeyExpirationDate());
}
}

View file

@ -47,7 +47,7 @@ public class OldSignatureSubpacketsArePreservedOnNewSig {
OpenPgpV4Fingerprint subkeyFingerprint = new OpenPgpV4Fingerprint(PGPainless.inspectKeyRing(secretKeys).getPublicKeys().get(1));
PGPSignature oldSignature = PGPainless.inspectKeyRing(secretKeys).getLatestValidSelfOrBindingSignatureOnKey(subkeyFingerprint);
PGPSignature oldSignature = PGPainless.inspectKeyRing(secretKeys).getCurrentSubkeyBindingSignature(subkeyFingerprint.getKeyId());
PGPSignatureSubpacketVector oldPackets = oldSignature.getHashedSubPackets();
assertEquals(0, oldPackets.getKeyExpirationTime());
@ -56,7 +56,7 @@ public class OldSignatureSubpacketsArePreservedOnNewSig {
secretKeys = PGPainless.modifyKeyRing(secretKeys)
.setExpirationDate(subkeyFingerprint, new Date(), new UnprotectedKeysProtector())
.done();
PGPSignature newSignature = PGPainless.inspectKeyRing(secretKeys).getLatestValidSelfOrBindingSignatureOnKey(subkeyFingerprint);
PGPSignature newSignature = PGPainless.inspectKeyRing(secretKeys).getCurrentSubkeyBindingSignature(subkeyFingerprint.getKeyId());
PGPSignatureSubpacketVector newPackets = newSignature.getHashedSubPackets();
assertNotEquals(0, newPackets.getKeyExpirationTime());

View file

@ -131,9 +131,9 @@ public class RevokeKeyWithoutPreferredAlgorithmsOnPrimaryKey {
secretKeys = modify.done();
KeyRingInfo info = PGPainless.inspectKeyRing(secretKeys);
assertEquals(expirationDate.getTime(), info.getExpirationDate().getTime(), 1000);
assertEquals(expirationDate.getTime(), info.getPrimaryKeyExpirationDate().getTime(), 1000);
for (OpenPgpV4Fingerprint fingerprint : fingerprintList) {
assertEquals(expirationDate.getTime(), info.getExpirationDate(fingerprint).getTime(), 1000);
assertEquals(expirationDate.getTime(), info.getSubkeyExpirationDate(fingerprint).getTime(), 1000);
}
}
}

View file

@ -0,0 +1,269 @@
/*
* Copyright 2021 Paul Schaub.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.pgpainless.signature;
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import org.bouncycastle.openpgp.PGPException;
import org.bouncycastle.openpgp.PGPPublicKeyRing;
import org.bouncycastle.openpgp.PGPSecretKeyRing;
import org.bouncycastle.openpgp.PGPSignature;
import org.bouncycastle.util.io.Streams;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.pgpainless.PGPainless;
import org.pgpainless.decryption_verification.DecryptionStream;
import org.pgpainless.decryption_verification.OpenPgpMetadata;
import org.pgpainless.key.OpenPgpV4Fingerprint;
import org.pgpainless.key.protection.SecretKeyRingProtector;
import org.pgpainless.key.util.KeyRingUtils;
import org.pgpainless.util.BCUtil;
/**
* Test if marker packets are being ignored properly.
*
* @see <a href="https://tests.sequoia-pgp.org/#Marker_Packet">Sequoia Test-Suite</a>
*/
public class IgnoreMarkerPackets {
private static final String KEY = "-----BEGIN PGP PRIVATE KEY BLOCK-----\n" +
"Comment: Bob's OpenPGP Transferable Secret Key\n" +
"\n" +
"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\n" +
"=miES\n" +
"-----END PGP PRIVATE KEY BLOCK-----\n";
@Test
public void markerPlusDetachedSignature() throws IOException, PGPException {
String sig = "-----BEGIN PGP SIGNATURE-----\n" +
"\n" +
"ygNQR1DCwTsEAAEKAG8FgmB9Y8YJEPv8yCoBXnMwRxQAAAAAAB4AIHNhbHRAbm90\n" +
"YXRpb25zLnNlcXVvaWEtcGdwLm9yZ1j1pQ8+YA70OJUxn1bZxiCar4WPrLMuM2By\n" +
"IITRjS1OFiEE0aZuGiOxgsmYD3iM+/zIKgFeczAAAOdzDACDhEptUvTFB7gx4YYG\n" +
"fFCaPxpFNo8zKnlcB2g1cFkrKEpZ/2It3ozf0beL81TUaj7G0Z4iJVDR4ei6Zrdt\n" +
"93GZRx+zQ6h3Wpj3TAi9mTHx5VRrMKK32o6VwRPuZy/KYCrst/eaM9LdhvAGsevR\n" +
"aQfopMB1xS+/8ySGimOfD6NwUWuLiOUr9fvAf3UyhpZiHL4UJ2mB1rTbQJtM++yf\n" +
"U48k+YsOVas/7B9qxlw3XsYvjVaFcTrKOj0lBn2uy2NMJje9dG+ll1lfdDkaqFFM\n" +
"FNgiJqGeoQ0whIsURurhzcY5zgujEw0qXRLMblI+g+yw2THrNx07EArnr2WzVzIP\n" +
"ifMu939eqm+mP0NKA1jVAPIIm92ZtIKD3+YzyczIepvLx4FwU1y5eAMotc76JrAg\n" +
"VWR7+FdtSA63VnVvLBR6YX7C0PVGR6BJBLEOFcZjNoW/JhN6gpmUvJLeZkFogC+J\n" +
"+J5EAJeGsE8/f/gi6pLtgAhjCNzH0qltOZsdJAfXqmd0NJ4=\n" +
"=5tQ4\n" +
"-----END PGP SIGNATURE-----\n";
PGPSecretKeyRing secretKeys = PGPainless.readKeyRing().secretKeyRing(KEY);
PGPPublicKeyRing publicKeys = KeyRingUtils.publicKeyRingFrom(secretKeys);
String data = "Marker + Detached signature";
PGPSignature signature = BCUtil.readSignatures(sig).get(0);
DecryptionStream decryptionStream = PGPainless.decryptAndOrVerify().onInputStream(new ByteArrayInputStream(data.getBytes(StandardCharsets.UTF_8)))
.doNotDecrypt()
.verifyDetachedSignature(signature)
.verifyWith(publicKeys)
.ignoreMissingPublicKeys()
.build();
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
Streams.pipeAll(decryptionStream, outputStream);
decryptionStream.close();
OpenPgpMetadata metadata = decryptionStream.getResult();
assertTrue(metadata.getVerifiedSignatures().containsKey(new OpenPgpV4Fingerprint("D1A66E1A23B182C9980F788CFBFCC82A015E7330")));
}
@Test
public void markerPlusEncryptedMessage() throws IOException, PGPException {
String msg = "-----BEGIN PGP MESSAGE-----\n" +
"\n" +
"ygNQR1DBwMwDfC+qTfk8N7IBC/4sXk4Al5Hdl38XCky41A1+HdJcDbXJ70/OY34M\n" +
"QYvco/Yk5Lb5XXg2adtSlwM2C3d6a8JMwmU7qFP/pEDXijJiTD+NabbsWO2+BwGd\n" +
"H9aTzAQhUj8PK4io5Q4SyxELosp9uO5XCkMkps5ev91mACwxm2p79tp7qkj8h4Q/\n" +
"3j8Hc9Ea4o0WCKuTIO1p42nX7gHMaUPkmZqnUxhN7ZkUgC4AHsjDgteK0viUdkmg\n" +
"aLza3TwN/e+vgnO8ypH1wBfdkvW+Ose5WPv587XGHZLsTQI6v2WeJo3K6KeuYXGI\n" +
"lxx0kLnaUL+9TswQAfU1jeh3OEy/eUIsMXc+4miYmE0cg4QxfP3ERyeziJpkzYgJ\n" +
"hGwK6cNfQiUXpWpyaAZ1vkCPEc074HIriGssq/CbkqUyUtbMzRuqfujZfMHiFkRc\n" +
"VvHkR3gzvYlMFhSjHVM2Dx9wJWmQqStdeHKfnZg4fZXDD7/zy+xRPVQgQBWelSTR\n" +
"4etbOP4OKLDX4LttGEPwy45KJvLSwZgBbU64o/RGNEbUt6dAXy3QeU5AkVR3h94f\n" +
"sda0b04cn2ZAIAuvWdF5MsgxxACFYOIHwobHzZGW54TTpTMzLVKJo8XuOuGUQE58\n" +
"jKJ+EiXZ3+TrItbhinUkZBdS59cleq2kNZ2dmTZ/ZkBwcOK1hH//Q8qrdLugm+h0\n" +
"YD/OOKudZTEu93TISGb6VeeWsb8UVjncbb2S/A/9/huFgUNYu7zma4231Y6OFx2J\n" +
"0tMVA+trKRWNhoacGwTl4mZnel42+IlgY6qVU+oTOCOtWNzuAeyj7PjnSMizv14u\n" +
"PvVaWDJv61yxNrsnLRjUX0D+CcrYMn10672ICDKg+L9SiXe87saBmsegDYSQWwE9\n" +
"WOtCWaEnKOSjD/Fewy/547dQJQihb9lG37wdM24t+J4qdkPkYKsUMhE3NcdrtLqJ\n" +
"QO7qDhEpxoX8lloNcEAFo0p+HqgcKX86/AzDYHxoLHKqDOYUQEHcKCfwA7mSXBDV\n" +
"JaOSO5Z2Jz+4HwvnD2ZgHP+qgctx87M5AgUQzlHm5mmBj3U3dvQQr6vIB4xtWKuy\n" +
"DganN7X2Jb5wlODBntdlyoM7FUHE1GYsHI2HkGl6d3bGAcEQdy9NjQyiVuWBqbr8\n" +
"OFDpFglcc70anHB091USc25LO23IhhXQ9ORalzULfoixf5lo6lmW3MPJGuYAhoIp\n" +
"SA8m91mkrqvtBckHA7xE2LdDM5JbAFNPYZzvzz5pAgYTEQp1mJB25Va1G2QHZhos\n" +
"dwvafpzRdOUaHM7lpvzTn3o3rM/Ntqfb6wn7GylsFYNpq+Rgtt6Mea68yTh+AeQ=\n" +
"=VSZ3\n" +
"-----END PGP MESSAGE-----\n";
PGPSecretKeyRing secretKeys = PGPainless.readKeyRing().secretKeyRing(KEY);
PGPPublicKeyRing publicKeys = KeyRingUtils.publicKeyRingFrom(secretKeys);
String data = "Marker + Encrypted Message";
DecryptionStream decryptionStream = PGPainless.decryptAndOrVerify().onInputStream(new ByteArrayInputStream(msg.getBytes(StandardCharsets.UTF_8)))
.decryptWith(SecretKeyRingProtector.unprotectedKeys(), secretKeys)
.verifyWith(publicKeys)
.ignoreMissingPublicKeys()
.build();
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
Streams.pipeAll(decryptionStream, outputStream);
decryptionStream.close();
assertArrayEquals(data.getBytes(StandardCharsets.UTF_8), outputStream.toByteArray());
OpenPgpMetadata metadata = decryptionStream.getResult();
assertTrue(metadata.getVerifiedSignatures().containsKey(new OpenPgpV4Fingerprint("D1A66E1A23B182C9980F788CFBFCC82A015E7330")));
}
@Test
@Disabled // TODO: Fix upstreamed. Enable once BC is bumped
public void markerPlusCertificate() throws IOException {
String pubKeyBlock = "-----BEGIN PGP PUBLIC KEY BLOCK-----\n" +
"\n" +
"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\n" +
"qOFfY9U0Ql3ddlHNpdkTZoAx\n" +
"=TrY7\n" +
"-----END PGP PUBLIC KEY BLOCK-----\n";
PGPPublicKeyRing publicKeys = PGPainless.readKeyRing().publicKeyRing(pubKeyBlock);
assertEquals(new OpenPgpV4Fingerprint("D1A66E1A23B182C9980F788CFBFCC82A015E7330"), new OpenPgpV4Fingerprint(publicKeys));
assertNotNull(publicKeys.getPublicKey(new OpenPgpV4Fingerprint("1DDCE15F09217CEE2F3B37607C2FAA4DF93C37B2").getKeyId()));
}
}

View file

@ -0,0 +1,265 @@
/*
* Copyright 2021 Paul Schaub.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.pgpainless.signature;
import static org.junit.jupiter.api.Assertions.assertThrows;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Date;
import org.bouncycastle.openpgp.PGPPublicKeyRing;
import org.bouncycastle.openpgp.PGPSignature;
import org.junit.jupiter.api.Test;
import org.pgpainless.PGPainless;
import org.pgpainless.util.BCUtil;
public class KeyRevocationTest {
private static final String data = "Hello, World";
@Test
public void subkeySignsPrimaryKeyRevokedNoReason() throws IOException, SignatureValidationException {
String key = "-----BEGIN PGP ARMORED FILE-----\n" +
"Comment: ASCII Armor added by openpgp-interoperability-test-suite\n" +
"\n" +
"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\n" +
"=2oji\n" +
"-----END PGP ARMORED FILE-----\n";
String sigT0 = "-----BEGIN PGP ARMORED FILE-----\n" +
"Comment: ASCII Armor added by openpgp-interoperability-test-suite\n" +
"\n" +
"wsBzBAABCgAGBYJYaEaAACEJEGhPrWLcA4+7FiEE8tFQpP6Ykl1R6RU5aE+tYtwD\n" +
"j7ttqgf9Gp4T5Q19cNL9Eyz1nlw11HDHT1wxfGHU5li76y7oo4Jqim15sEPDJWmc\n" +
"IpYVrczpCI95aCuaE6yfzpjZlXMzftwex3DjM98vyZH4W9teKcOnpAVjn3dLoQJA\n" +
"i4fiq3VaLgl+1OYOwu3DmwGKJZubHM3oPia9pbuyvL5Scvx+QCG0AVnssnt2QswG\n" +
"uU6J35QgBdjG2iC043sUoyxTSk929iamdQnOGchjcaATb4E4+HvtkRy4IirKxiKK\n" +
"c535BHJRLgWQzUcDDZ5kHf3SPLTNsigFFEwUyf5voFcn/DSMWSzPaVecaafTVJW2\n" +
"u8G1R5mjuxDRup8p//X1BSk1FpSmvw==\n" +
"=3/dv\n" +
"-----END PGP ARMORED FILE-----\n";
String sigT1T2 = "-----BEGIN PGP ARMORED FILE-----\n" +
"Comment: ASCII Armor added by openpgp-interoperability-test-suite\n" +
"\n" +
"wsBzBAABCgAGBYJa564AACEJEGhPrWLcA4+7FiEE8tFQpP6Ykl1R6RU5aE+tYtwD\n" +
"j7ufRgf/QOsaJZgQaQ5daQrfBItOEcW+5acgY1TCwMVmc/nzBqC32TOvMaM3dypf\n" +
"wJbqzxHQIes+ivKDF872VWlMA2BErifpdsogbS0pdik/qU+AjMhr188xKpZKG/IY\n" +
"6BtuUPeSpsimx3UeEN3kt79fMtewBo0EXo3ujCyPpIF/9Vpd7L9jlJSvRBuM0/aR\n" +
"gbRsclEw4JZ98B3t7F3rLmx+F57Zre0ctzT4tHE6IaCYpEClr6Tepj/UxExYOt2l\n" +
"hKgVN8Wsuug7XYdOFmxqrV967m3CTnF8AspmxwgKK6NXjVLplfqij7Rp2URPWrWn\n" +
"Pp3CQRGUWJdMeMY9P1MpFq6XiXtftw==\n" +
"=Ld1q\n" +
"-----END PGP ARMORED FILE-----\n";
String sigT2T3 = "-----BEGIN PGP ARMORED FILE-----\n" +
"Comment: ASCII Armor added by openpgp-interoperability-test-suite\n" +
"\n" +
"wsBzBAABCgAGBYJdP4iAACEJEGhPrWLcA4+7FiEE8tFQpP6Ykl1R6RU5aE+tYtwD\n" +
"j7sYXQf8CZw6Kx4oyI8ZJ2c9RjVZmUFEirAoXH7oYA+Ye+wSAY9OtqE/x2SOYaC6\n" +
"QHiB93/wkvpqCVkLy2lenzpD7WXLbuFZ+/5jXp1o+sVHXfLSWo6pfIhOjj9FSr8x\n" +
"qqlqUfKwkbA6WYgju+qKC35SYdSptix7unaFkO41UdsM8wGQh880HSRMBMFPzg07\n" +
"3hMNYXoEJjFlIkxJSMu2WL7N0Q/4xE2iJftsQjUYAtJ/C/YK2I6dhW+CZremnv5R\n" +
"/8W+oH5Q63lYU8YL4wYnJQvkHjKs/kjLpoPmqL8kdHjndSpU+KOYr5w61XuEp2hp\n" +
"r8trtljVaVIQX2rYawSlqKkWXt0yag==\n" +
"=xVd8\n" +
"-----END PGP ARMORED FILE-----\n";
String sigT3Now = "-----BEGIN PGP ARMORED FILE-----\n" +
"Comment: ASCII Armor added by openpgp-interoperability-test-suite\n" +
"\n" +
"wsBzBAABCgAGBYJe/cFVACEJEGhPrWLcA4+7FiEE8tFQpP6Ykl1R6RU5aE+tYtwD\n" +
"j7vmhQf/UB456IXc8ub8HTExab1d5KqOGSUWpwIznTu8Wk8YuzWKEE8ZeZvPmv8K\n" +
"iJfBoOx59YrlOfpLAKcTR9Ql+IFbWsIkqPxX7U1SGldhfQm7iaK5Dn6+mmQFOz/s\n" +
"ZCIavWJ7opsp11JmQAt4FFojv789YswaS7VI1zjDj7EeRiATtzna/GqCYgeCM0cc\n" +
"sIe/1j1H2oh9YvYIpPMSGDJPo7T1Ji4Ie3iEQEYNYPuw1Hb7gWYncHXZGJq1nDf/\n" +
"WAoI9gSFagpsPW0k9cfEAOVNLNYSyi0CSnQWSjq8THbHKiLPFwsP3hvT2oHycWbK\n" +
"u5SfXaTsbMeVQJNdjCNsHq2bOXPGLw==\n" +
"=2BW4\n" +
"-----END PGP ARMORED FILE-----\n";
PGPPublicKeyRing publicKeys = PGPainless.readKeyRing().publicKeyRing(key);
PGPSignature t0 = BCUtil.readSignatures(sigT0).get(0);
PGPSignature t1t2 = BCUtil.readSignatures(sigT1T2).get(0);
PGPSignature t2t3 = BCUtil.readSignatures(sigT2T3).get(0);
PGPSignature t3now = BCUtil.readSignatures(sigT3Now).get(0);
assertThrows(SignatureValidationException.class, () -> SignatureChainValidator.validateSignatureChain(t0,
new ByteArrayInputStream(data.getBytes(StandardCharsets.UTF_8)),
publicKeys, PGPainless.getPolicy(), new Date()));
assertThrows(SignatureValidationException.class, () -> SignatureChainValidator.validateSignatureChain(t1t2,
new ByteArrayInputStream(data.getBytes(StandardCharsets.UTF_8)),
publicKeys, PGPainless.getPolicy(), new Date()));
assertThrows(SignatureValidationException.class, () -> SignatureChainValidator.validateSignatureChain(t2t3,
new ByteArrayInputStream(data.getBytes(StandardCharsets.UTF_8)),
publicKeys, PGPainless.getPolicy(), new Date()));
assertThrows(SignatureValidationException.class, () -> SignatureChainValidator.validateSignatureChain(t3now,
new ByteArrayInputStream(data.getBytes(StandardCharsets.UTF_8)),
publicKeys, PGPainless.getPolicy(), new Date()));
}
/**
* Test signature verification with an evolving signing subkey.
*
* @see <a href="https://tests.sequoia-pgp.org/#Key_revocation_test__subkey_signs__primary_key_is_not_revoked__base_case_">Sequoia Test-Suite</a>
*/
@Test
public void subkeySignsPrimaryKeyNotRevoked() throws IOException, SignatureValidationException {
String key = "-----BEGIN PGP ARMORED FILE-----\n" +
"Comment: ASCII Armor added by openpgp-interoperability-test-suite\n" +
"\n" +
"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\n" +
"jDMFXQS0exdvhN4AXDlPlB3Rrkj7CQ==\n" +
"=+VTZ\n" +
"-----END PGP ARMORED FILE-----\n";
String sig = "-----BEGIN PGP ARMORED FILE-----\n" +
"Comment: ASCII Armor added by openpgp-interoperability-test-suite\n" +
"\n" +
"wsBzBAABCgAGBYJdP4iAACEJEEpyNKOhITplFiEEUXksDkji/alOk7kRSnI0o6Eh\n" +
"OmUYXQf/dGNZay40bZEpcnxYl+Kq+gRQESeDhg/xOfGfSCLQncMH+UYPaUKANC2g\n" +
"CfMNN1wd8ZWrvgyTVo3TVfK1P1RYa9nrvKoKN3bjsFcY6V7VciPW58xVNsuxsEEC\n" +
"GEH96TQy+FsP680tRnzQ3Dbw/JT6o6Xi+HLf4JVFceapBgyth61E5gN5w3azxVFr\n" +
"GfwIfHvepOjCIq9tRZsRFEBp3XVZ/AF+zQMG5nfIVSm1kVtZjb7KXc3Bj48DVrmb\n" +
"XLxPJz7PLY0cgOsXXxROIdtFT+mbVQg2j247hxnhItwtLeQrafb5T8ibeihRlkhK\n" +
"1tfKv31EP8tAVqgTjw+qD32bH9h77w==\n" +
"=MOaJ\n" +
"-----END PGP ARMORED FILE-----\n";
PGPPublicKeyRing publicKeys = PGPainless.readKeyRing().publicKeyRing(key);
PGPSignature signature = BCUtil.readSignatures(sig).get(0);
SignatureChainValidator.validateSignatureChain(signature,
new ByteArrayInputStream(data.getBytes(StandardCharsets.UTF_8)),
publicKeys, PGPainless.getPolicy(), new Date());
}
}

View file

@ -0,0 +1,136 @@
/*
* Copyright 2021 Paul Schaub.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.pgpainless.signature;
import java.io.IOException;
import java.util.Collections;
import java.util.Date;
import org.bouncycastle.openpgp.PGPException;
import org.bouncycastle.openpgp.PGPPublicKeyRing;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.pgpainless.PGPainless;
import org.pgpainless.algorithm.HashAlgorithm;
import org.pgpainless.key.KeyRingValidator;
import org.pgpainless.policy.Policy;
import org.pgpainless.util.ArmorUtils;
import org.pgpainless.util.TestUtils;
public class KeyRingValidationTest {
private static Policy.HashAlgorithmPolicy defaultSignatureHashAlgorithmPolicy;
@BeforeAll
public static void setCustomPolicy() {
Policy policy = PGPainless.getPolicy();
defaultSignatureHashAlgorithmPolicy = policy.getSignatureHashAlgorithmPolicy();
policy.setSignatureHashAlgorithmPolicy(new Policy.HashAlgorithmPolicy(HashAlgorithm.SHA256, Collections.singletonList(HashAlgorithm.SHA256)));
}
@AfterAll
public static void resetCustomPolicy() {
PGPainless.getPolicy().setSignatureHashAlgorithmPolicy(defaultSignatureHashAlgorithmPolicy);
}
@Test
public void testSignatureValidationOnPrimaryKey() throws IOException, PGPException {
String key = "-----BEGIN PGP ARMORED FILE-----\n" +
"Comment: ASCII Armor added by openpgp-interoperability-test-suite\n" +
"\n" +
"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\n" +
"exdvhN4AXDlPlB3Rrkj7CQ==\n" +
"=qQpG\n" +
"-----END PGP ARMORED FILE-----\n";
PGPPublicKeyRing publicKeys = PGPainless.readKeyRing().publicKeyRing(key);
Date validationDate = TestUtils.getUTCDate("2019-05-01 00:00:00 UTC");
Policy policy = PGPainless.getPolicy();
PGPPublicKeyRing evaluated = KeyRingValidator.validate(publicKeys, policy, validationDate);
// CHECKSTYLE:OFF
System.out.println(ArmorUtils.toAsciiArmoredString(evaluated));
// CHECKSTYLE:ON
}
}

View file

@ -15,22 +15,16 @@
*/
package org.pgpainless.util;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
public class NotationRegistryTest {
import org.junit.jupiter.api.Test;
@BeforeEach
public void setup() {
NotationRegistry.getInstance().clear();
}
public class NotationRegistryTest {
@Test
public void notationIsKnownOnceAddedAndUnknownOnceCleared() {
NotationRegistry registry = NotationRegistry.getInstance();
NotationRegistry registry = new NotationRegistry();
assertFalse(registry.isKnownNotation("proof@metacode.biz"), "Notation is initially not known.");
assertFalse(registry.isKnownNotation("unkown@notation.data"));

View file

@ -26,6 +26,7 @@ import org.bouncycastle.openpgp.PGPSignatureSubpacketGenerator;
import org.bouncycastle.openpgp.PGPSignatureSubpacketVector;
import org.junit.jupiter.api.Test;
import org.pgpainless.algorithm.SignatureSubpacket;
import org.pgpainless.signature.subpackets.SignatureSubpacketGeneratorUtil;
public class SignatureSubpacketGeneratorUtilTest {

View file

@ -15,10 +15,23 @@
*/
package org.pgpainless.util;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Iterator;
public class TestUtils {
public static SimpleDateFormat UTC_PARSER = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss z");
public static Date getUTCDate(String dateString) {
try {
return UTC_PARSER.parse(dateString);
} catch (ParseException e) {
return null;
}
}
public static int getNumberOfItemsInIterator(Iterator<?> iterator) {
int num = 0;
while (iterator.hasNext()) {

View file

@ -0,0 +1,186 @@
/*
* Copyright 2021 Paul Schaub.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.pgpainless.util.selection.signature;
import java.io.IOException;
import java.util.Iterator;
import org.bouncycastle.openpgp.PGPException;
import org.bouncycastle.openpgp.PGPPublicKey;
import org.bouncycastle.openpgp.PGPPublicKeyRing;
import org.bouncycastle.openpgp.PGPSignature;
import org.junit.jupiter.api.Test;
import org.pgpainless.PGPainless;
import org.pgpainless.signature.SelectSignatureFromKey;
public class SelectSignatureFromKeyTest {
@Test
public void validKeyTest() throws IOException, PGPException {
String key = "-----BEGIN PGP PUBLIC KEY BLOCK-----\n" +
"\n" +
"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\n" +
"Db7pzk2vugYKrebvskFQaq0S8TwhHQ4n9GVrUnenFf2OAWYfRYmYbENUv+fQm22+\n" +
"EOxHWSVwB3NWl8albQxs/aPCi3nuPdtdTMU2fHLGDAZ9MGQesb/0tSJLWaqQRvqT\n" +
"k3llI1OqxGbYLaNXSz6nJDLsKK9v+6lFzxA5C8OOxGikHE7b9RJ6SGVNijItXtHo\n" +
"rVuAKayDfMKO+0jc25I+agMbfg6p4Ik5D+1LFzZtsSc6Ib6AKu+FLit6Ik74/nrr\n" +
"/ORSAoTpxnIyJlBu4DS3AUwRd/O7rke8FNVg6EpzaPazrqfY1eZ2YelEE4EO3xXm\n" +
"wcOLSPVwsLNoC3DdRRLtw5EItZy2z0QiARF+NsUYQQM5RCrQizxuzD5+nXg1AcaE\n" +
"ixnbju8StB8jT1m4ccJKHsObgi/cIPPsWm5+BUhV9RDLsMWnaVZ8f3tRAHy2TAld\n" +
"FiEE0aZuGiOxgsmYD3iM+/zIKgFeczAAAAv/DACScy69f/qohzub6e06b3sgmL1K\n" +
"foCMmFRAiEsDHUHunAb/KWBqkbJ8W6wP0COwh4tbmjUzwexMQyI4m58SLRYULcJ7\n" +
"kj3axMV0+JJyFoqUpCT06GpqQQIhZY7Y+AHz9FdVNEDjjUwb3mODx8zVyEg57T9C\n" +
"TfuLrrJDYpycfNJtxYy9qSMPHBiVGqlzqnyETOa312QquZuY6ucfTL8i8kXk5qtL\n" +
"jVHTnKogzrbTCWuKR8fzsxfZ9afdYXI3SMMsip4Ixx2mLM5tN9IeDI/DQnWetwB2\n" +
"Z0PEs7UcYcrn6UWs1X4P7jOmtLH+0d96I9ljd9SSmJ9dTr2cV62J/qtK+75hCBk8\n" +
"Lz+MNWzyAU3sVqGRhsBaLOqvb7K9p3bm6brEmGpBLeKrxuxjBER+7knqkTxSsb+S\n" +
"msO3lGrEnNEQIlcvoxLIGQiv9b0sblGM9lr40C0D84PEvajhuFAUTItoPfCIVVaT\n" +
"7Ry8/ZA6t0uQh9/B0hYblb07mJ92hCacoTx+APM=\n" +
"=yeYe\n" +
"-----END PGP PUBLIC KEY BLOCK-----";
PGPPublicKeyRing publicKeys = PGPainless.readKeyRing().publicKeyRing(key);
Iterator<PGPPublicKey> keyIt = publicKeys.getPublicKeys();
PGPPublicKey primaryKey = publicKeys.getPublicKey();
while (keyIt.hasNext()) {
PGPPublicKey publicKey = keyIt.next();
// CHECKSTYLE:OFF
System.out.println(publicKey.getKeyID());
// CHECKSTYLE:ON
Iterator<PGPSignature> signatures = publicKey.getSignatures();
while (signatures.hasNext()) {
PGPSignature signature = signatures.next();
if (SelectSignatureFromKey.isValidSubkeyBindingSignature(primaryKey, publicKey).accept(signature, publicKey, publicKeys)) {
// CHECKSTYLE:OFF
System.out.println("Valid subkey binding signature");
// CHECKSTYLE:ON
}
}
}
}
@Test
public void missingBackSigTest() throws IOException {
String key = "-----BEGIN PGP PUBLIC KEY BLOCK-----\n" +
"\n" +
"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\n" +
"=uSRw\n" +
"-----END PGP PUBLIC KEY BLOCK-----\n";
PGPPublicKeyRing publicKeys = PGPainless.readKeyRing().publicKeyRing(key);
Iterator<PGPPublicKey> keyIt = publicKeys.getPublicKeys();
PGPPublicKey primaryKey = publicKeys.getPublicKey();
while (keyIt.hasNext()) {
PGPPublicKey publicKey = keyIt.next();
// CHECKSTYLE:OFF
System.out.println(publicKey.getKeyID());
// CHECKSTYLE:ON
if (publicKey.isMasterKey()) {
Iterator<PGPSignature> signatures = publicKey.getSignatures();
boolean isValidPrimaryKey = false;
boolean isRevokedPrimaryKey = false;
while (signatures.hasNext()) {
PGPSignature signature = signatures.next();
}
} else {
Iterator<PGPSignature> signatures = publicKey.getSignatures();
while (signatures.hasNext()) {
PGPSignature signature = signatures.next();
if (SelectSignatureFromKey.isValidSubkeyBindingSignature(primaryKey, publicKey).accept(signature, publicKey, publicKeys)) {
// CHECKSTYLE:OFF
System.out.println("Valid subkey binding signature");
// CHECKSTYLE:ON
}
}
}
}
}
}