mirror of
https://github.com/pgpainless/pgpainless.git
synced 2024-11-19 02:42:05 +01:00
Kotlin conversion: SignatureValidator
This commit is contained in:
parent
733d5d6b82
commit
e9d8ddc57b
3 changed files with 704 additions and 681 deletions
|
@ -1,681 +0,0 @@
|
|||
// SPDX-FileCopyrightText: 2021 Paul Schaub <vanitasvitae@fsfe.org>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package org.pgpainless.signature.consumer;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import org.bouncycastle.bcpg.sig.KeyFlags;
|
||||
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.KeyFlag;
|
||||
import org.pgpainless.algorithm.PublicKeyAlgorithm;
|
||||
import org.pgpainless.algorithm.SignatureSubpacket;
|
||||
import org.pgpainless.algorithm.SignatureType;
|
||||
import org.pgpainless.exception.SignatureValidationException;
|
||||
import org.pgpainless.implementation.ImplementationFactory;
|
||||
import org.pgpainless.key.OpenPgpFingerprint;
|
||||
import org.pgpainless.policy.Policy;
|
||||
import org.pgpainless.signature.SignatureUtils;
|
||||
import org.pgpainless.signature.subpackets.SignatureSubpacketsUtil;
|
||||
import org.pgpainless.util.DateUtil;
|
||||
import org.pgpainless.util.NotationRegistry;
|
||||
|
||||
/**
|
||||
* A collection of validators that perform validation steps over signatures.
|
||||
*/
|
||||
public abstract class SignatureValidator {
|
||||
|
||||
public abstract void verify(PGPSignature signature) throws SignatureValidationException;
|
||||
|
||||
/**
|
||||
* Check, whether there is the possibility that the given signature was created by the given key.
|
||||
* {@link #verify(PGPSignature)} throws a {@link SignatureValidationException} if we can say with certainty that
|
||||
* the signature was not created by the given key (e.g. if the sig carries another issuer, issuer fingerprint packet).
|
||||
*
|
||||
* If there is no information found in the signature about who created it (no issuer, no fingerprint),
|
||||
* {@link #verify(PGPSignature)} will simply return since it is plausible that the given key created the sig.
|
||||
*
|
||||
* @param signingKey signing key
|
||||
* @return validator that throws a {@link SignatureValidationException} if the signature was not possibly made by
|
||||
* the given key.
|
||||
*/
|
||||
public static SignatureValidator wasPossiblyMadeByKey(PGPPublicKey signingKey) {
|
||||
return new SignatureValidator() {
|
||||
@Override
|
||||
public void verify(PGPSignature signature) throws SignatureValidationException {
|
||||
OpenPgpFingerprint signingKeyFingerprint = OpenPgpFingerprint.of(signingKey);
|
||||
|
||||
Long issuer = SignatureSubpacketsUtil.getIssuerKeyIdAsLong(signature);
|
||||
if (issuer != null) {
|
||||
if (issuer != signingKey.getKeyID()) {
|
||||
throw new SignatureValidationException("Signature was not created by " +
|
||||
signingKeyFingerprint + " (signature issuer: " + Long.toHexString(issuer) + ")");
|
||||
}
|
||||
}
|
||||
|
||||
OpenPgpFingerprint fingerprint =
|
||||
SignatureSubpacketsUtil.getIssuerFingerprintAsOpenPgpFingerprint(signature);
|
||||
if (fingerprint != null) {
|
||||
if (!fingerprint.equals(signingKeyFingerprint)) {
|
||||
throw new SignatureValidationException("Signature was not created by " +
|
||||
signingKeyFingerprint + " (signature fingerprint: " + fingerprint + ")");
|
||||
}
|
||||
}
|
||||
|
||||
// No issuer information found, so we cannot rule out that we did not create the sig
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that a subkey binding signature - if the subkey is signing-capable - contains a valid primary key
|
||||
* binding signature.
|
||||
*
|
||||
* @param primaryKey primary key
|
||||
* @param subkey subkey
|
||||
* @param policy policy
|
||||
* @param referenceDate reference date for signature verification
|
||||
* @return validator
|
||||
*/
|
||||
public static SignatureValidator hasValidPrimaryKeyBindingSignatureIfRequired(PGPPublicKey primaryKey,
|
||||
PGPPublicKey subkey, Policy policy,
|
||||
Date referenceDate) {
|
||||
return new SignatureValidator() {
|
||||
@Override
|
||||
public void verify(PGPSignature signature) throws SignatureValidationException {
|
||||
if (!PublicKeyAlgorithm.requireFromId(signature.getKeyAlgorithm()).isSigningCapable()) {
|
||||
// subkey is not signing capable -> No need to process embedded sigs
|
||||
return;
|
||||
}
|
||||
|
||||
KeyFlags keyFlags = SignatureSubpacketsUtil.getKeyFlags(signature);
|
||||
if (keyFlags == null) {
|
||||
return;
|
||||
}
|
||||
if (!KeyFlag.hasKeyFlag(keyFlags.getFlags(), KeyFlag.SIGN_DATA)
|
||||
&& !KeyFlag.hasKeyFlag(keyFlags.getFlags(), KeyFlag.CERTIFY_OTHER)) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
PGPSignatureList embeddedSignatures = SignatureSubpacketsUtil.getEmbeddedSignature(signature);
|
||||
if (embeddedSignatures == null) {
|
||||
throw new SignatureValidationException(
|
||||
"Missing primary key binding signature on signing capable subkey " +
|
||||
Long.toHexString(subkey.getKeyID()), Collections.emptyMap());
|
||||
}
|
||||
|
||||
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(referenceDate).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);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that a signature has an acceptable structure.
|
||||
*
|
||||
* @param signingKey signing key
|
||||
* @param policy policy
|
||||
* @return validator
|
||||
*/
|
||||
public static SignatureValidator signatureStructureIsAcceptable(PGPPublicKey signingKey, Policy policy) {
|
||||
return new SignatureValidator() {
|
||||
@Override
|
||||
public void verify(PGPSignature signature) throws SignatureValidationException {
|
||||
signatureIsNotMalformed(signingKey).verify(signature);
|
||||
if (signature.getVersion() >= 4) {
|
||||
signatureDoesNotHaveCriticalUnknownNotations(policy.getNotationRegistry()).verify(signature);
|
||||
signatureDoesNotHaveCriticalUnknownSubpackets().verify(signature);
|
||||
}
|
||||
signatureUsesAcceptableHashAlgorithm(policy).verify(signature);
|
||||
signatureUsesAcceptablePublicKeyAlgorithm(policy, signingKey).verify(signature);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that a signature was made using an acceptable {@link PublicKeyAlgorithm}.
|
||||
*
|
||||
* @param policy policy
|
||||
* @param signingKey signing key
|
||||
* @return validator
|
||||
*/
|
||||
public static SignatureValidator signatureUsesAcceptablePublicKeyAlgorithm(Policy policy,
|
||||
PGPPublicKey signingKey) {
|
||||
return new SignatureValidator() {
|
||||
@Override
|
||||
public void verify(PGPSignature signature) throws SignatureValidationException {
|
||||
PublicKeyAlgorithm algorithm = PublicKeyAlgorithm.requireFromId(signingKey.getAlgorithm());
|
||||
int bitStrength = signingKey.getBitStrength();
|
||||
if (bitStrength == -1) {
|
||||
throw new SignatureValidationException("Cannot determine bit strength of signing key.");
|
||||
}
|
||||
if (!policy.getPublicKeyAlgorithmPolicy().isAcceptable(algorithm, bitStrength)) {
|
||||
throw new SignatureValidationException("Signature was made using unacceptable key. " +
|
||||
algorithm + " (" + bitStrength +
|
||||
" bits) is not acceptable according to the public key algorithm policy.");
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that a signature uses an acceptable {@link HashAlgorithm}.
|
||||
*
|
||||
* @param policy policy
|
||||
* @return validator
|
||||
*/
|
||||
public static SignatureValidator signatureUsesAcceptableHashAlgorithm(Policy policy) {
|
||||
return new SignatureValidator() {
|
||||
@Override
|
||||
public void verify(PGPSignature signature) throws SignatureValidationException {
|
||||
try {
|
||||
HashAlgorithm hashAlgorithm = HashAlgorithm.requireFromId(signature.getHashAlgorithm());
|
||||
Policy.HashAlgorithmPolicy hashAlgorithmPolicy =
|
||||
getHashAlgorithmPolicyForSignature(signature, policy);
|
||||
if (!hashAlgorithmPolicy.isAcceptable(signature.getHashAlgorithm(), signature.getCreationTime())) {
|
||||
throw new SignatureValidationException("Signature uses unacceptable hash algorithm " +
|
||||
hashAlgorithm + " (Signature creation time: " +
|
||||
DateUtil.formatUTCDate(signature.getCreationTime()) + ")");
|
||||
}
|
||||
} catch (NoSuchElementException e) {
|
||||
throw new SignatureValidationException("Signature uses unknown hash algorithm " +
|
||||
signature.getHashAlgorithm());
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the applicable {@link Policy.HashAlgorithmPolicy} for the given {@link PGPSignature}.
|
||||
* Revocation signatures are being policed using a different policy than non-revocation signatures.
|
||||
*
|
||||
* @param signature signature
|
||||
* @param policy revocation policy for revocation sigs, normal policy for non-rev sigs
|
||||
* @return policy
|
||||
*/
|
||||
private static Policy.HashAlgorithmPolicy getHashAlgorithmPolicyForSignature(PGPSignature signature,
|
||||
Policy policy) {
|
||||
SignatureType type = SignatureType.valueOf(signature.getSignatureType());
|
||||
Policy.HashAlgorithmPolicy hashAlgorithmPolicy;
|
||||
if (type == SignatureType.CERTIFICATION_REVOCATION || type == SignatureType.KEY_REVOCATION ||
|
||||
type == SignatureType.SUBKEY_REVOCATION) {
|
||||
hashAlgorithmPolicy = policy.getRevocationSignatureHashAlgorithmPolicy();
|
||||
} else {
|
||||
hashAlgorithmPolicy = policy.getSignatureHashAlgorithmPolicy();
|
||||
}
|
||||
return hashAlgorithmPolicy;
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that a signature does not carry critical unknown notations.
|
||||
*
|
||||
* @param registry notation registry of known notations
|
||||
* @return validator
|
||||
*/
|
||||
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.");
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that a signature does not contain critical unknown subpackets.
|
||||
*
|
||||
* @return validator
|
||||
*/
|
||||
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.requireFromCode(criticalTag);
|
||||
} catch (NoSuchElementException e) {
|
||||
throw new SignatureValidationException(
|
||||
"Signature contains unknown critical subpacket of type " +
|
||||
Long.toHexString(criticalTag));
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that a signature is effective right now.
|
||||
*
|
||||
* @return validator
|
||||
*/
|
||||
public static SignatureValidator signatureIsEffective() {
|
||||
return signatureIsEffective(new Date());
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that a signature is effective at the given reference date.
|
||||
*
|
||||
* @param referenceDate reference date for signature verification
|
||||
* @return validator
|
||||
*/
|
||||
public static SignatureValidator signatureIsEffective(Date referenceDate) {
|
||||
return new SignatureValidator() {
|
||||
@Override
|
||||
public void verify(PGPSignature signature) throws SignatureValidationException {
|
||||
signatureIsAlreadyEffective(referenceDate).verify(signature);
|
||||
signatureIsNotYetExpired(referenceDate).verify(signature);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that a signature was created prior to the given reference date.
|
||||
*
|
||||
* @param referenceDate reference date for signature verification
|
||||
* @return validator
|
||||
*/
|
||||
public static SignatureValidator signatureIsAlreadyEffective(Date referenceDate) {
|
||||
return new SignatureValidator() {
|
||||
@Override
|
||||
public void verify(PGPSignature signature) throws SignatureValidationException {
|
||||
Date signatureCreationTime = SignatureSubpacketsUtil.getSignatureCreationTime(signature).getTime();
|
||||
// Hard revocations are always effective
|
||||
if (SignatureUtils.isHardRevocation(signature)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (signatureCreationTime.after(referenceDate)) {
|
||||
throw new SignatureValidationException("Signature was created at " + signatureCreationTime +
|
||||
" and is therefore not yet valid at " + referenceDate);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that a signature is not yet expired.
|
||||
*
|
||||
* @param referenceDate reference date for signature verification
|
||||
* @return validator
|
||||
*/
|
||||
public static SignatureValidator signatureIsNotYetExpired(Date referenceDate) {
|
||||
return new SignatureValidator() {
|
||||
@Override
|
||||
public void verify(PGPSignature signature) throws SignatureValidationException {
|
||||
// Hard revocations do not expire
|
||||
if (SignatureUtils.isHardRevocation(signature)) {
|
||||
return;
|
||||
}
|
||||
|
||||
Date signatureExpirationTime = SignatureSubpacketsUtil.getSignatureExpirationTimeAsDate(signature);
|
||||
if (signatureExpirationTime != null && signatureExpirationTime.before(referenceDate)) {
|
||||
throw new SignatureValidationException("Signature is already expired (expiration: " +
|
||||
signatureExpirationTime + ", validation: " + referenceDate + ")");
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that a signature is not malformed.
|
||||
* A signature is malformed if it has no hashed creation time subpacket,
|
||||
* it predates the creation time of the signing key, or it predates the creation date
|
||||
* of the signing key binding signature.
|
||||
*
|
||||
* @param creator signing key
|
||||
* @return validator
|
||||
*/
|
||||
public static SignatureValidator signatureIsNotMalformed(PGPPublicKey creator) {
|
||||
return new SignatureValidator() {
|
||||
@Override
|
||||
public void verify(PGPSignature signature) throws SignatureValidationException {
|
||||
if (signature.getVersion() >= 4) {
|
||||
signatureHasHashedCreationTime().verify(signature);
|
||||
}
|
||||
signatureDoesNotPredateSigningKey(creator).verify(signature);
|
||||
if (signature.getSignatureType() != SignatureType.PRIMARYKEY_BINDING.getCode()) {
|
||||
signatureDoesNotPredateSigningKeyBindingDate(creator).verify(signature);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static SignatureValidator signatureDoesNotPredateSignee(PGPPublicKey signee) {
|
||||
return signatureDoesNotPredateKeyCreation(signee);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that a signature has a hashed creation time subpacket.
|
||||
*
|
||||
* @return validator
|
||||
*/
|
||||
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.");
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that a signature does not predate the creation time of the signing key.
|
||||
*
|
||||
* @param key signing key
|
||||
* @return validator
|
||||
*/
|
||||
public static SignatureValidator signatureDoesNotPredateSigningKey(PGPPublicKey key) {
|
||||
return signatureDoesNotPredateKeyCreation(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that a signature does not predate the creation time of the given key.
|
||||
*
|
||||
* @param key key
|
||||
* @return validator
|
||||
*/
|
||||
public static SignatureValidator signatureDoesNotPredateKeyCreation(PGPPublicKey key) {
|
||||
return new SignatureValidator() {
|
||||
@Override
|
||||
public void verify(PGPSignature signature) throws SignatureValidationException {
|
||||
Date keyCreationTime = key.getCreationTime();
|
||||
Date signatureCreationTime = signature.getCreationTime();
|
||||
|
||||
if (keyCreationTime.after(signatureCreationTime)) {
|
||||
throw new SignatureValidationException("Signature predates key (key creation: " +
|
||||
keyCreationTime + ", signature creation: " + signatureCreationTime + ")");
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that a signature does not predate the binding date of the signing key.
|
||||
*
|
||||
* @param signingKey signing key
|
||||
* @return validator
|
||||
*/
|
||||
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.");
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that a subkey binding signature is correct.
|
||||
*
|
||||
* @param primaryKey primary key
|
||||
* @param subkey subkey
|
||||
* @return validator
|
||||
*/
|
||||
public static SignatureValidator correctSubkeyBindingSignature(PGPPublicKey primaryKey, PGPPublicKey subkey) {
|
||||
return new SignatureValidator() {
|
||||
@Override
|
||||
public void verify(PGPSignature signature) throws SignatureValidationException {
|
||||
if (primaryKey.getKeyID() == subkey.getKeyID()) {
|
||||
throw new SignatureValidationException("Primary key cannot be its own subkey.");
|
||||
}
|
||||
try {
|
||||
signature.init(ImplementationFactory.getInstance()
|
||||
.getPgpContentVerifierBuilderProvider(), primaryKey);
|
||||
boolean valid = signature.verifyCertification(primaryKey, subkey);
|
||||
if (!valid) {
|
||||
throw new SignatureValidationException("Signature is not correct.");
|
||||
}
|
||||
} catch (PGPException | ClassCastException e) {
|
||||
throw new SignatureValidationException("Cannot verify subkey binding signature correctness", e);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that a primary key binding signature is correct.
|
||||
*
|
||||
* @param primaryKey primary key
|
||||
* @param subkey subkey
|
||||
* @return validator
|
||||
*/
|
||||
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 | ClassCastException e) {
|
||||
throw new SignatureValidationException(
|
||||
"Cannot verify primary key binding signature correctness", e);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that a direct-key signature is correct.
|
||||
*
|
||||
* @param signer signing key
|
||||
* @param signee signed key
|
||||
* @return validator
|
||||
*/
|
||||
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;
|
||||
if (signer.getKeyID() == signee.getKeyID() || signature.getSignatureType() == PGPSignature.DIRECT_KEY) {
|
||||
valid = signature.verifyCertification(signee);
|
||||
} else {
|
||||
valid = signature.verifyCertification(signer, signee);
|
||||
}
|
||||
if (!valid) {
|
||||
throw new SignatureValidationException("Signature is not correct.");
|
||||
}
|
||||
} catch (PGPException | ClassCastException e) {
|
||||
throw new SignatureValidationException("Cannot verify direct-key signature correctness", e);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that a signature is a certification signature.
|
||||
*
|
||||
* @return validator
|
||||
*/
|
||||
public static SignatureValidator signatureIsCertification() {
|
||||
return signatureIsOfType(
|
||||
SignatureType.POSITIVE_CERTIFICATION,
|
||||
SignatureType.CASUAL_CERTIFICATION,
|
||||
SignatureType.GENERIC_CERTIFICATION,
|
||||
SignatureType.NO_CERTIFICATION);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that a signature type equals one of the given {@link SignatureType SignatureTypes}.
|
||||
*
|
||||
* @param signatureTypes one or more signature types
|
||||
* @return validator
|
||||
*/
|
||||
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.");
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that a signature over a user-id is correct.
|
||||
*
|
||||
* @param userId user-id
|
||||
* @param certifiedKey key carrying the user-id
|
||||
* @param certifyingKey key that created the signature.
|
||||
* @return validator
|
||||
*/
|
||||
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 | ClassCastException e) {
|
||||
throw new SignatureValidationException("Cannot verify signature over user-id '" +
|
||||
userId + "'.", e);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that a signature over a user-attribute packet is correct.
|
||||
*
|
||||
* @param userAttributes user attributes
|
||||
* @param certifiedKey key carrying the user-attributes
|
||||
* @param certifyingKey key that created the certification signature
|
||||
* @return validator
|
||||
*/
|
||||
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 | ClassCastException e) {
|
||||
throw new SignatureValidationException("Cannot verify signature over user-attribute vector.", e);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static SignatureValidator signatureWasCreatedInBounds(Date notBefore, Date notAfter) {
|
||||
return new SignatureValidator() {
|
||||
@Override
|
||||
public void verify(PGPSignature signature) throws SignatureValidationException {
|
||||
Date timestamp = signature.getCreationTime();
|
||||
if (notBefore != null && timestamp.before(notBefore)) {
|
||||
throw new SignatureValidationException(
|
||||
"Signature was made before the earliest allowed signature creation time. Created: " +
|
||||
DateUtil.formatUTCDate(timestamp) + " Earliest allowed: " +
|
||||
DateUtil.formatUTCDate(notBefore));
|
||||
}
|
||||
if (notAfter != null && timestamp.after(notAfter)) {
|
||||
throw new SignatureValidationException(
|
||||
"Signature was made after the latest allowed signature creation time. Created: " +
|
||||
DateUtil.formatUTCDate(timestamp) + " Latest allowed: " +
|
||||
DateUtil.formatUTCDate(notAfter));
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
}
|
|
@ -8,6 +8,8 @@ import java.util.*
|
|||
import openpgp.plusSeconds
|
||||
import org.bouncycastle.openpgp.PGPPublicKey
|
||||
import org.bouncycastle.openpgp.PGPSignature
|
||||
import org.pgpainless.algorithm.HashAlgorithm
|
||||
import org.pgpainless.algorithm.PublicKeyAlgorithm
|
||||
import org.pgpainless.algorithm.RevocationState
|
||||
import org.pgpainless.algorithm.SignatureType
|
||||
import org.pgpainless.key.OpenPgpFingerprint
|
||||
|
@ -94,3 +96,12 @@ fun PGPSignature?.toRevocationState() =
|
|||
|
||||
val PGPSignature.fingerprint: OpenPgpFingerprint?
|
||||
get() = SignatureSubpacketsUtil.getIssuerFingerprintAsOpenPgpFingerprint(this)
|
||||
|
||||
val PGPSignature.publicKeyAlgorithm: PublicKeyAlgorithm
|
||||
get() = PublicKeyAlgorithm.requireFromId(keyAlgorithm)
|
||||
|
||||
val PGPSignature.signatureHashAlgorithm: HashAlgorithm
|
||||
get() = HashAlgorithm.requireFromId(hashAlgorithm)
|
||||
|
||||
fun PGPSignature.isOfType(type: SignatureType): Boolean =
|
||||
SignatureType.requireFromCode(signatureType) == type
|
||||
|
|
|
@ -0,0 +1,693 @@
|
|||
// SPDX-FileCopyrightText: 2023 Paul Schaub <vanitasvitae@fsfe.org>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package org.pgpainless.signature.consumer
|
||||
|
||||
import java.lang.Exception
|
||||
import java.util.Date
|
||||
import openpgp.formatUTC
|
||||
import openpgp.openPgpKeyId
|
||||
import org.bouncycastle.extensions.fingerprint
|
||||
import org.bouncycastle.extensions.isHardRevocation
|
||||
import org.bouncycastle.extensions.isOfType
|
||||
import org.bouncycastle.extensions.publicKeyAlgorithm
|
||||
import org.bouncycastle.extensions.signatureExpirationDate
|
||||
import org.bouncycastle.extensions.signatureHashAlgorithm
|
||||
import org.bouncycastle.openpgp.PGPException
|
||||
import org.bouncycastle.openpgp.PGPPublicKey
|
||||
import org.bouncycastle.openpgp.PGPSignature
|
||||
import org.bouncycastle.openpgp.PGPUserAttributeSubpacketVector
|
||||
import org.pgpainless.algorithm.KeyFlag
|
||||
import org.pgpainless.algorithm.SignatureSubpacket
|
||||
import org.pgpainless.algorithm.SignatureType
|
||||
import org.pgpainless.exception.SignatureValidationException
|
||||
import org.pgpainless.implementation.ImplementationFactory
|
||||
import org.pgpainless.key.OpenPgpFingerprint
|
||||
import org.pgpainless.policy.Policy
|
||||
import org.pgpainless.signature.subpackets.SignatureSubpacketsUtil
|
||||
import org.pgpainless.util.NotationRegistry
|
||||
|
||||
abstract class SignatureValidator {
|
||||
|
||||
@Throws(SignatureValidationException::class) abstract fun verify(signature: PGPSignature)
|
||||
|
||||
companion object {
|
||||
|
||||
/**
|
||||
* Check, whether there is the possibility that the given signature was created by the given
|
||||
* key. [verify] throws a [SignatureValidationException] if we can say with certainty that
|
||||
* the signature was not created by the given key (e.g. if the sig carries another issuer,
|
||||
* issuer fingerprint packet).
|
||||
*
|
||||
* If there is no information found in the signature about who created it (no issuer, no
|
||||
* fingerprint), [verify] will simply return since it is plausible that the given key
|
||||
* created the sig.
|
||||
*
|
||||
* @param signingKey signing key
|
||||
* @return validator that throws a [SignatureValidationException] if the signature was not
|
||||
* possibly made by the given key.
|
||||
*/
|
||||
@JvmStatic
|
||||
fun wasPossiblyMadeByKey(signingKey: PGPPublicKey): SignatureValidator {
|
||||
return object : SignatureValidator() {
|
||||
override fun verify(signature: PGPSignature) {
|
||||
val signingKeyFingerprint = OpenPgpFingerprint.of(signingKey)
|
||||
val issuer = SignatureSubpacketsUtil.getIssuerKeyIdAsLong(signature)
|
||||
|
||||
if (issuer != null) {
|
||||
if (issuer != signingKey.keyID) {
|
||||
throw SignatureValidationException(
|
||||
"Signature was not created by" +
|
||||
" $signingKeyFingerprint (signature issuer: ${issuer.openPgpKeyId()})")
|
||||
}
|
||||
}
|
||||
|
||||
if (signature.fingerprint != null &&
|
||||
signature.fingerprint != signingKeyFingerprint) {
|
||||
throw SignatureValidationException(
|
||||
"Signature was not created by" +
|
||||
" $signingKeyFingerprint (signature fingerprint: ${signature.fingerprint})")
|
||||
}
|
||||
}
|
||||
|
||||
// No issuer information found, so we cannot rule out that we did not create the sig
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that a subkey binding signature - if the subkey is signing-capable - contains a
|
||||
* valid primary key binding signature.
|
||||
*
|
||||
* @param primaryKey primary key
|
||||
* @param subkey subkey
|
||||
* @param policy policy
|
||||
* @param referenceDate reference date for signature verification
|
||||
* @return validator
|
||||
*/
|
||||
@JvmStatic
|
||||
fun hasValidPrimaryKeyBindingSignatureIfRequired(
|
||||
primaryKey: PGPPublicKey,
|
||||
subkey: PGPPublicKey,
|
||||
policy: Policy,
|
||||
referenceTime: Date
|
||||
): SignatureValidator {
|
||||
return object : SignatureValidator() {
|
||||
override fun verify(signature: PGPSignature) {
|
||||
if (!signature.publicKeyAlgorithm.isSigningCapable()) {
|
||||
// subkey is not signing capable -> No need to process embedded signatures
|
||||
return
|
||||
}
|
||||
|
||||
// Make sure we have key flags
|
||||
SignatureSubpacketsUtil.getKeyFlags(signature)?.let {
|
||||
if (!KeyFlag.hasKeyFlag(it.flags, KeyFlag.SIGN_DATA) &&
|
||||
!KeyFlag.hasKeyFlag(it.flags, KeyFlag.CERTIFY_OTHER)) {
|
||||
return
|
||||
}
|
||||
}
|
||||
?: return
|
||||
|
||||
try {
|
||||
val embeddedSignatures =
|
||||
SignatureSubpacketsUtil.getEmbeddedSignature(signature)
|
||||
if (embeddedSignatures.isEmpty) {
|
||||
throw SignatureValidationException(
|
||||
"Missing primary key binding" +
|
||||
" signature on signing capable subkey ${subkey.keyID.openPgpKeyId()}",
|
||||
mapOf())
|
||||
}
|
||||
|
||||
val rejectedEmbeddedSignatures = mutableMapOf<PGPSignature, Exception>()
|
||||
if (!embeddedSignatures.any { embedded ->
|
||||
if (embedded.isOfType(SignatureType.PRIMARYKEY_BINDING)) {
|
||||
try {
|
||||
signatureStructureIsAcceptable(subkey, policy).verify(embedded)
|
||||
signatureIsEffective(referenceTime).verify(embedded)
|
||||
correctPrimaryKeyBindingSignature(primaryKey, subkey)
|
||||
.verify(embedded)
|
||||
return@any true
|
||||
} catch (e: SignatureValidationException) {
|
||||
rejectedEmbeddedSignatures[embedded] = e
|
||||
}
|
||||
}
|
||||
false
|
||||
}) {
|
||||
throw SignatureValidationException(
|
||||
"Missing primary key binding signature on signing capable subkey ${subkey.keyID.openPgpKeyId()}",
|
||||
rejectedEmbeddedSignatures)
|
||||
}
|
||||
} catch (e: PGPException) {
|
||||
throw SignatureValidationException(
|
||||
"Cannot process list of embedded signatures.", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that a signature has an acceptable structure.
|
||||
*
|
||||
* @param signingKey signing key
|
||||
* @param policy policy
|
||||
* @return validator
|
||||
*/
|
||||
@JvmStatic
|
||||
fun signatureStructureIsAcceptable(
|
||||
signingKey: PGPPublicKey,
|
||||
policy: Policy
|
||||
): SignatureValidator {
|
||||
return object : SignatureValidator() {
|
||||
override fun verify(signature: PGPSignature) {
|
||||
signatureIsNotMalformed(signingKey).verify(signature)
|
||||
if (signature.version >= 4) {
|
||||
signatureDoesNotHaveCriticalUnknownNotations(policy.notationRegistry)
|
||||
.verify(signature)
|
||||
signatureDoesNotHaveCriticalUnknownSubpackets().verify(signature)
|
||||
}
|
||||
signatureUsesAcceptableHashAlgorithm(policy).verify(signature)
|
||||
signatureUsesAcceptablePublicKeyAlgorithm(policy, signingKey).verify(signature)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that a signature was made using an acceptable [PublicKeyAlgorithm].
|
||||
*
|
||||
* @param policy policy
|
||||
* @param signingKey signing key
|
||||
* @return validator
|
||||
*/
|
||||
@JvmStatic
|
||||
fun signatureUsesAcceptablePublicKeyAlgorithm(
|
||||
policy: Policy,
|
||||
signingKey: PGPPublicKey
|
||||
): SignatureValidator {
|
||||
return object : SignatureValidator() {
|
||||
override fun verify(signature: PGPSignature) {
|
||||
if (signingKey.bitStrength == -1) {
|
||||
throw SignatureValidationException(
|
||||
"Cannot determine bit strength of signing key.")
|
||||
}
|
||||
if (!policy.publicKeyAlgorithmPolicy.isAcceptable(
|
||||
signingKey.publicKeyAlgorithm, signingKey.bitStrength)) {
|
||||
throw SignatureValidationException(
|
||||
"Signature was made using unacceptable key. " +
|
||||
"${signingKey.publicKeyAlgorithm} (${signingKey.bitStrength} bits) is " +
|
||||
"not acceptable according to the public key algorithm policy.")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun signatureUsesAcceptableHashAlgorithm(policy: Policy): SignatureValidator {
|
||||
return object : SignatureValidator() {
|
||||
override fun verify(signature: PGPSignature) {
|
||||
try {
|
||||
val algorithmPolicy = getHashAlgorithmPolicyForSignature(signature, policy)
|
||||
if (!algorithmPolicy.isAcceptable(
|
||||
signature.signatureHashAlgorithm, signature.creationTime)) {
|
||||
throw SignatureValidationException(
|
||||
"Signature uses unacceptable" +
|
||||
" hash algorithm ${signature.signatureHashAlgorithm}" +
|
||||
" (Signature creation time: ${signature.creationTime.formatUTC()})")
|
||||
}
|
||||
} catch (e: NoSuchElementException) {
|
||||
throw SignatureValidationException(
|
||||
"Signature uses unknown hash" + " algorithm ${signature.hashAlgorithm}")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the applicable [Policy.HashAlgorithmPolicy] for the given [PGPSignature].
|
||||
* Revocation signatures are being policed using a different policy than non-revocation
|
||||
* signatures.
|
||||
*
|
||||
* @param signature signature
|
||||
* @param policy revocation policy for revocation sigs, normal policy for non-rev sigs
|
||||
* @return policy
|
||||
*/
|
||||
@JvmStatic
|
||||
private fun getHashAlgorithmPolicyForSignature(
|
||||
signature: PGPSignature,
|
||||
policy: Policy
|
||||
): Policy.HashAlgorithmPolicy {
|
||||
val type = SignatureType.requireFromCode(signature.signatureType)
|
||||
return when (type) {
|
||||
SignatureType.CERTIFICATION_REVOCATION,
|
||||
SignatureType.KEY_REVOCATION,
|
||||
SignatureType.SUBKEY_REVOCATION -> policy.revocationSignatureHashAlgorithmPolicy
|
||||
else -> policy.signatureHashAlgorithmPolicy
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that a signature does not carry critical unknown notations.
|
||||
*
|
||||
* @param registry notation registry of known notations
|
||||
* @return validator
|
||||
*/
|
||||
@JvmStatic
|
||||
fun signatureDoesNotHaveCriticalUnknownNotations(
|
||||
registry: NotationRegistry
|
||||
): SignatureValidator {
|
||||
return object : SignatureValidator() {
|
||||
override fun verify(signature: PGPSignature) {
|
||||
SignatureSubpacketsUtil.getHashedNotationData(signature)
|
||||
.filter { it.isCritical && !registry.isKnownNotation(it.notationName) }
|
||||
.forEach {
|
||||
throw SignatureValidationException(
|
||||
"Signature contains unknown critical notation '${it.notationName}' in its hashed area.")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that a signature does not contain critical unknown subpackets.
|
||||
*
|
||||
* @return validator
|
||||
*/
|
||||
@JvmStatic
|
||||
fun signatureDoesNotHaveCriticalUnknownSubpackets(): SignatureValidator {
|
||||
return object : SignatureValidator() {
|
||||
override fun verify(signature: PGPSignature) {
|
||||
signature.hashedSubPackets.criticalTags.forEach {
|
||||
try {
|
||||
SignatureSubpacket.requireFromCode(it)
|
||||
} catch (e: NoSuchElementException) {
|
||||
throw SignatureValidationException(
|
||||
"Signature contains unknown critical subpacket of type 0x${Integer.toHexString(it)}")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that a signature is effective at the given reference date.
|
||||
*
|
||||
* @param referenceTime reference date for signature verification
|
||||
* @return validator
|
||||
*/
|
||||
@JvmStatic
|
||||
@JvmOverloads
|
||||
fun signatureIsEffective(referenceTime: Date = Date()): SignatureValidator {
|
||||
return object : SignatureValidator() {
|
||||
override fun verify(signature: PGPSignature) {
|
||||
signatureIsAlreadyEffective(referenceTime).verify(signature)
|
||||
signatureIsNotYetExpired(referenceTime).verify(signature)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that a signature was created prior to the given reference date.
|
||||
*
|
||||
* @param referenceTime reference date for signature verification
|
||||
* @return validator
|
||||
*/
|
||||
@JvmStatic
|
||||
fun signatureIsAlreadyEffective(referenceTime: Date): SignatureValidator {
|
||||
return object : SignatureValidator() {
|
||||
override fun verify(signature: PGPSignature) {
|
||||
if (signature.isHardRevocation) {
|
||||
return
|
||||
}
|
||||
if (signature.creationTime > referenceTime) {
|
||||
throw SignatureValidationException(
|
||||
"Signature was created at ${signature.creationTime.formatUTC()} and" +
|
||||
" is therefore not yet valid at ${referenceTime.formatUTC()}")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that a signature is not yet expired.
|
||||
*
|
||||
* @param referenceTime reference date for signature verification
|
||||
* @return validator
|
||||
*/
|
||||
@JvmStatic
|
||||
fun signatureIsNotYetExpired(referenceTime: Date): SignatureValidator {
|
||||
return object : SignatureValidator() {
|
||||
override fun verify(signature: PGPSignature) {
|
||||
if (signature.isHardRevocation) {
|
||||
return
|
||||
}
|
||||
val expirationDate = signature.signatureExpirationDate
|
||||
if (expirationDate != null && expirationDate < referenceTime) {
|
||||
throw SignatureValidationException(
|
||||
"Signature is already expired " +
|
||||
"(expiration: ${expirationDate.formatUTC()}," +
|
||||
" validation: ${referenceTime.formatUTC()})")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that a signature is not malformed. A signature is malformed if it has no hashed
|
||||
* creation time subpacket, it predates the creation time of the signing key, or it predates
|
||||
* the creation date of the signing key binding signature.
|
||||
*
|
||||
* @param signingKey signing key
|
||||
* @return validator
|
||||
*/
|
||||
@JvmStatic
|
||||
fun signatureIsNotMalformed(signingKey: PGPPublicKey): SignatureValidator {
|
||||
return object : SignatureValidator() {
|
||||
override fun verify(signature: PGPSignature) {
|
||||
if (signature.version >= 4) {
|
||||
signatureHasHashedCreationTime().verify(signature)
|
||||
}
|
||||
signatureDoesNotPredateSigningKey(signingKey).verify(signature)
|
||||
if (!signature.isOfType(SignatureType.PRIMARYKEY_BINDING)) {
|
||||
signatureDoesNotPredateSigningKeyBindingDate(signingKey).verify(signature)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun signatureDoesNotPredateSignee(signee: PGPPublicKey): SignatureValidator {
|
||||
return signatureDoesNotPredateKeyCreation(signee)
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that a signature does not predate the creation time of the signing key.
|
||||
*
|
||||
* @param key signing key
|
||||
* @return validator
|
||||
*/
|
||||
@JvmStatic
|
||||
fun signatureDoesNotPredateSigningKey(signingKey: PGPPublicKey): SignatureValidator {
|
||||
return signatureDoesNotPredateKeyCreation(signingKey)
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that a signature does not predate the creation time of the given key.
|
||||
*
|
||||
* @param key key
|
||||
* @return validator
|
||||
*/
|
||||
@JvmStatic
|
||||
fun signatureDoesNotPredateKeyCreation(key: PGPPublicKey): SignatureValidator {
|
||||
return object : SignatureValidator() {
|
||||
override fun verify(signature: PGPSignature) {
|
||||
if (key.creationTime > signature.creationTime) {
|
||||
throw SignatureValidationException(
|
||||
"Signature predates key" +
|
||||
" (key creation: ${key.creationTime.formatUTC()}," +
|
||||
" signature creation: ${signature.creationTime.formatUTC()})")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that a signature has a hashed creation time subpacket.
|
||||
*
|
||||
* @return validator
|
||||
*/
|
||||
@JvmStatic
|
||||
fun signatureHasHashedCreationTime(): SignatureValidator {
|
||||
return object : SignatureValidator() {
|
||||
override fun verify(signature: PGPSignature) {
|
||||
if (SignatureSubpacketsUtil.getSignatureCreationTime(signature) == null) {
|
||||
throw SignatureValidationException(
|
||||
"Malformed signature." +
|
||||
"Signature has no signature creation time subpacket in its hashed area.")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that a signature does not predate the binding date of the signing key.
|
||||
*
|
||||
* @param signingKey signing key
|
||||
* @return validator
|
||||
*/
|
||||
@JvmStatic
|
||||
fun signatureDoesNotPredateSigningKeyBindingDate(
|
||||
signingKey: PGPPublicKey
|
||||
): SignatureValidator {
|
||||
return object : SignatureValidator() {
|
||||
override fun verify(signature: PGPSignature) {
|
||||
if (signingKey.isMasterKey) {
|
||||
return
|
||||
}
|
||||
if (signingKey
|
||||
.getSignaturesOfType(SignatureType.SUBKEY_BINDING.code)
|
||||
.asSequence()
|
||||
.map {
|
||||
if (signature.creationTime < it.creationTime) {
|
||||
throw SignatureValidationException(
|
||||
"Signature was created " +
|
||||
"before the signing key was bound to the certificate.")
|
||||
}
|
||||
}
|
||||
.none()) {
|
||||
throw SignatureValidationException(
|
||||
"Signing subkey does not have a subkey binding signature.")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that a subkey binding signature is correct.
|
||||
*
|
||||
* @param primaryKey primary key
|
||||
* @param subkey subkey
|
||||
* @return validator
|
||||
*/
|
||||
@JvmStatic
|
||||
fun correctSubkeyBindingSignature(
|
||||
primaryKey: PGPPublicKey,
|
||||
subkey: PGPPublicKey
|
||||
): SignatureValidator {
|
||||
return object : SignatureValidator() {
|
||||
override fun verify(signature: PGPSignature) {
|
||||
if (primaryKey.keyID == subkey.keyID) {
|
||||
throw SignatureValidationException("Primary key cannot be its own subkey.")
|
||||
}
|
||||
try {
|
||||
signature.init(
|
||||
ImplementationFactory.getInstance().pgpContentVerifierBuilderProvider,
|
||||
primaryKey)
|
||||
if (!signature.verifyCertification(primaryKey, subkey)) {
|
||||
throw SignatureValidationException("Signature is not correct.")
|
||||
}
|
||||
} catch (e: PGPException) {
|
||||
throw SignatureValidationException(
|
||||
"Cannot verify subkey binding signature correctness", e)
|
||||
} catch (e: ClassCastException) {
|
||||
throw SignatureValidationException(
|
||||
"Cannot verify subkey binding signature correctness", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that a primary key binding signature is correct.
|
||||
*
|
||||
* @param primaryKey primary key
|
||||
* @param subkey subkey
|
||||
* @return validator
|
||||
*/
|
||||
@JvmStatic
|
||||
fun correctPrimaryKeyBindingSignature(
|
||||
primaryKey: PGPPublicKey,
|
||||
subkey: PGPPublicKey
|
||||
): SignatureValidator {
|
||||
return object : SignatureValidator() {
|
||||
override fun verify(signature: PGPSignature) {
|
||||
if (primaryKey.keyID == subkey.keyID) {
|
||||
throw SignatureValidationException("Primary key cannot be its own subkey.")
|
||||
}
|
||||
try {
|
||||
signature.init(
|
||||
ImplementationFactory.getInstance().pgpContentVerifierBuilderProvider,
|
||||
subkey)
|
||||
if (!signature.verifyCertification(primaryKey, subkey)) {
|
||||
throw SignatureValidationException(
|
||||
"Primary Key Binding Signature is not correct.")
|
||||
}
|
||||
} catch (e: PGPException) {
|
||||
throw SignatureValidationException(
|
||||
"Cannot verify primary key binding signature correctness", e)
|
||||
} catch (e: ClassCastException) {
|
||||
throw SignatureValidationException(
|
||||
"Cannot verify primary key binding signature correctness", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that a direct-key signature is correct.
|
||||
*
|
||||
* @param signingKey signing key
|
||||
* @param signedKey signed key
|
||||
* @return validator
|
||||
*/
|
||||
@JvmStatic
|
||||
fun correctSignatureOverKey(
|
||||
signingKey: PGPPublicKey,
|
||||
signedKey: PGPPublicKey
|
||||
): SignatureValidator {
|
||||
return object : SignatureValidator() {
|
||||
override fun verify(signature: PGPSignature) {
|
||||
try {
|
||||
signature.init(
|
||||
ImplementationFactory.getInstance().pgpContentVerifierBuilderProvider,
|
||||
signingKey)
|
||||
val valid =
|
||||
if (signingKey.keyID == signedKey.keyID ||
|
||||
signature.isOfType(SignatureType.DIRECT_KEY)) {
|
||||
signature.verifyCertification(signedKey)
|
||||
} else {
|
||||
signature.verifyCertification(signingKey, signedKey)
|
||||
}
|
||||
if (!valid) {
|
||||
throw SignatureValidationException("Signature is not correct.")
|
||||
}
|
||||
} catch (e: PGPException) {
|
||||
throw SignatureValidationException(
|
||||
"Cannot verify direct-key signature correctness", e)
|
||||
} catch (e: ClassCastException) {
|
||||
throw SignatureValidationException(
|
||||
"Cannot verify direct-key signature correctness", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun signatureIsCertification(): SignatureValidator {
|
||||
return signatureIsOfType(
|
||||
SignatureType.POSITIVE_CERTIFICATION,
|
||||
SignatureType.CASUAL_CERTIFICATION,
|
||||
SignatureType.GENERIC_CERTIFICATION,
|
||||
SignatureType.NO_CERTIFICATION)
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that a signature type equals one of the given [SignatureType].
|
||||
*
|
||||
* @param signatureType one or more signature types
|
||||
* @return validator
|
||||
*/
|
||||
@JvmStatic
|
||||
fun signatureIsOfType(vararg signatureType: SignatureType): SignatureValidator {
|
||||
return object : SignatureValidator() {
|
||||
override fun verify(signature: PGPSignature) {
|
||||
if (signatureType.none { signature.isOfType(it) }) {
|
||||
throw SignatureValidationException(
|
||||
"Signature is of type" +
|
||||
" ${SignatureType.requireFromCode(signature.signatureType)}, " +
|
||||
"while only ${signatureType.contentToString()} are allowed here.")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that a signature over a user-id is correct.
|
||||
*
|
||||
* @param userId user-id
|
||||
* @param certifiedKey key carrying the user-id
|
||||
* @param certifyingKey key that created the signature.
|
||||
* @return validator
|
||||
*/
|
||||
@JvmStatic
|
||||
fun correctSignatureOverUserId(
|
||||
userId: CharSequence,
|
||||
certifiedKey: PGPPublicKey,
|
||||
certifyingKey: PGPPublicKey
|
||||
): SignatureValidator {
|
||||
return object : SignatureValidator() {
|
||||
override fun verify(signature: PGPSignature) {
|
||||
try {
|
||||
signature.init(
|
||||
ImplementationFactory.getInstance().pgpContentVerifierBuilderProvider,
|
||||
certifyingKey)
|
||||
if (!signature.verifyCertification(userId.toString(), certifiedKey)) {
|
||||
throw SignatureValidationException(
|
||||
"Signature over user-id '$userId' is not valid.")
|
||||
}
|
||||
} catch (e: PGPException) {
|
||||
throw SignatureValidationException(
|
||||
"Cannot verify signature over user-id '$userId'.", e)
|
||||
} catch (e: ClassCastException) {
|
||||
throw SignatureValidationException(
|
||||
"Cannot verify signature over user-id '$userId'.", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that a signature over a user-attribute packet is correct.
|
||||
*
|
||||
* @param userAttributes user attributes
|
||||
* @param certifiedKey key carrying the user-attributes
|
||||
* @param certifyingKey key that created the certification signature
|
||||
* @return validator
|
||||
*/
|
||||
@JvmStatic
|
||||
fun correctSignatureOverUserAttributes(
|
||||
userAttributes: PGPUserAttributeSubpacketVector,
|
||||
certifiedKey: PGPPublicKey,
|
||||
certifyingKey: PGPPublicKey
|
||||
): SignatureValidator {
|
||||
return object : SignatureValidator() {
|
||||
override fun verify(signature: PGPSignature) {
|
||||
try {
|
||||
signature.init(
|
||||
ImplementationFactory.getInstance().pgpContentVerifierBuilderProvider,
|
||||
certifyingKey)
|
||||
if (!signature.verifyCertification(userAttributes, certifiedKey)) {
|
||||
throw SignatureValidationException(
|
||||
"Signature over user-attributes is not correct.")
|
||||
}
|
||||
} catch (e: PGPException) {
|
||||
throw SignatureValidationException(
|
||||
"Cannot verify signature over user-attribute vector.", e)
|
||||
} catch (e: ClassCastException) {
|
||||
throw SignatureValidationException(
|
||||
"Cannot verify signature over user-attribute vector.", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun signatureWasCreatedInBounds(notBefore: Date?, notAfter: Date?): SignatureValidator {
|
||||
return object : SignatureValidator() {
|
||||
override fun verify(signature: PGPSignature) {
|
||||
val timestamp = signature.creationTime
|
||||
if (notBefore != null && timestamp < notBefore) {
|
||||
throw SignatureValidationException(
|
||||
"Signature was made before the earliest allowed signature creation time." +
|
||||
" Created: ${timestamp.formatUTC()}," +
|
||||
" earliest allowed: ${notBefore.formatUTC()}")
|
||||
}
|
||||
if (notAfter != null && timestamp > notAfter) {
|
||||
throw SignatureValidationException(
|
||||
"Signature was made before the latest allowed signature creation time." +
|
||||
" Created: ${timestamp.formatUTC()}," +
|
||||
" latest allowed: ${notAfter.formatUTC()}")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue