mirror of
https://github.com/pgpainless/pgpainless.git
synced 2025-12-10 14:21:09 +01:00
Proper Signature Verification
This commit is contained in:
parent
6ee8a9416f
commit
64cc9ecca4
67 changed files with 7950 additions and 688 deletions
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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()));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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 {
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
};
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
|
@ -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;
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
|
@ -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;
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
34
pgpainless-core/src/main/java/org/pgpainless/util/Tuple.java
Normal file
34
pgpainless-core/src/main/java/org/pgpainless/util/Tuple.java
Normal 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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue