001// SPDX-FileCopyrightText: 2021 Paul Schaub <vanitasvitae@fsfe.org>
002//
003// SPDX-License-Identifier: Apache-2.0
004
005package org.pgpainless.key;
006
007import java.util.Collections;
008import java.util.Date;
009import java.util.Iterator;
010import java.util.List;
011
012import org.bouncycastle.openpgp.PGPException;
013import org.bouncycastle.openpgp.PGPKeyRing;
014import org.bouncycastle.openpgp.PGPPublicKey;
015import org.bouncycastle.openpgp.PGPPublicKeyRing;
016import org.bouncycastle.openpgp.PGPSignature;
017import org.bouncycastle.openpgp.PGPUserAttributeSubpacketVector;
018import org.pgpainless.algorithm.SignatureType;
019import org.pgpainless.exception.SignatureValidationException;
020import org.pgpainless.implementation.ImplementationFactory;
021import org.pgpainless.policy.Policy;
022import org.pgpainless.signature.consumer.SignatureCreationDateComparator;
023import org.pgpainless.signature.consumer.SignatureVerifier;
024import org.pgpainless.util.CollectionUtils;
025import org.slf4j.Logger;
026import org.slf4j.LoggerFactory;
027
028public final class KeyRingValidator {
029
030    private KeyRingValidator() {
031
032    }
033
034    private static final Logger LOGGER = LoggerFactory.getLogger(KeyRingValidator.class);
035
036    public static <R extends PGPKeyRing> R validate(R keyRing, Policy policy) {
037        try {
038            return validate(keyRing, policy, new Date());
039        } catch (PGPException e) {
040            return null;
041        }
042    }
043
044    public static <R extends PGPKeyRing> R validate(R keyRing, Policy policy, Date validationDate) throws PGPException {
045        return getKeyRingAtDate(keyRing, policy, validationDate);
046    }
047
048    private static <R extends PGPKeyRing> R getKeyRingAtDate(R keyRing, Policy policy, Date validationDate) throws PGPException {
049        PGPPublicKey primaryKey = keyRing.getPublicKey();
050        primaryKey = evaluatePrimaryKey(primaryKey, policy, validationDate);
051        if (keyRing instanceof PGPPublicKeyRing) {
052            PGPPublicKeyRing publicKeys = (PGPPublicKeyRing) keyRing;
053            publicKeys = PGPPublicKeyRing.insertPublicKey(publicKeys, primaryKey);
054            keyRing = (R) publicKeys;
055        }
056
057        return keyRing;
058    }
059
060    private static PGPPublicKey evaluatePrimaryKey(PGPPublicKey primaryKey, Policy policy, Date validationDate) throws PGPException {
061
062        PGPPublicKey blank = new PGPPublicKey(primaryKey.getPublicKeyPacket(), ImplementationFactory.getInstance().getKeyFingerprintCalculator());
063
064        Iterator<PGPSignature> directKeyIterator = primaryKey.getSignaturesOfType(SignatureType.DIRECT_KEY.getCode());
065        List<PGPSignature> directKeyCertifications = CollectionUtils.iteratorToList(directKeyIterator);
066        Collections.sort(directKeyCertifications, new SignatureCreationDateComparator(SignatureCreationDateComparator.Order.NEW_TO_OLD));
067        for (PGPSignature signature : directKeyCertifications) {
068            try {
069                if (SignatureVerifier.verifyDirectKeySignature(signature, blank, policy, validationDate)) {
070                    blank = PGPPublicKey.addCertification(blank, signature);
071                }
072            } catch (SignatureValidationException e) {
073                LOGGER.debug("Rejecting direct key signature: {}", e.getMessage(), e);
074            }
075        }
076
077        Iterator<PGPSignature> revocationIterator = primaryKey.getSignaturesOfType(SignatureType.KEY_REVOCATION.getCode());
078        List<PGPSignature> directKeyRevocations = CollectionUtils.iteratorToList(revocationIterator);
079        Collections.sort(directKeyRevocations, new SignatureCreationDateComparator(SignatureCreationDateComparator.Order.NEW_TO_OLD));
080        for (PGPSignature signature : directKeyRevocations) {
081            try {
082                if (SignatureVerifier.verifyKeyRevocationSignature(signature, primaryKey, policy, validationDate)) {
083                    blank = PGPPublicKey.addCertification(blank, signature);
084                }
085            } catch (SignatureValidationException e) {
086                LOGGER.debug("Rejecting key revocation signature: {}", e.getMessage(), e);
087            }
088        }
089
090        Iterator<String> userIdIterator = primaryKey.getUserIDs();
091        while (userIdIterator.hasNext()) {
092            String userId = userIdIterator.next();
093            Iterator<PGPSignature> userIdSigs = primaryKey.getSignaturesForID(userId);
094            List<PGPSignature> signatures = CollectionUtils.iteratorToList(userIdSigs);
095            Collections.sort(signatures, new SignatureCreationDateComparator(SignatureCreationDateComparator.Order.NEW_TO_OLD));
096            for (PGPSignature signature : signatures) {
097                try {
098                    if (SignatureType.valueOf(signature.getSignatureType()) == SignatureType.CERTIFICATION_REVOCATION) {
099                        if (SignatureVerifier.verifyUserIdRevocation(userId, signature, primaryKey, policy, validationDate)) {
100                            blank = PGPPublicKey.addCertification(blank, userId, signature);
101                        }
102                    } else {
103                        if (SignatureVerifier.verifyUserIdCertification(userId, signature, primaryKey, policy, validationDate)) {
104                            blank = PGPPublicKey.addCertification(blank, userId, signature);
105                        }
106                    }
107                } catch (SignatureValidationException e) {
108                    LOGGER.debug("Rejecting user-id certification for user-id {}: {}", userId, e.getMessage(), e);
109                }
110            }
111        }
112
113        Iterator<PGPUserAttributeSubpacketVector> userAttributes = primaryKey.getUserAttributes();
114        while (userAttributes.hasNext()) {
115            PGPUserAttributeSubpacketVector userAttribute = userAttributes.next();
116            Iterator<PGPSignature> userAttributeSignatureIterator = primaryKey.getSignaturesForUserAttribute(userAttribute);
117            while (userAttributeSignatureIterator.hasNext()) {
118                PGPSignature signature = userAttributeSignatureIterator.next();
119                try {
120                    if (SignatureType.valueOf(signature.getSignatureType()) == SignatureType.CERTIFICATION_REVOCATION) {
121                        if (SignatureVerifier.verifyUserAttributesRevocation(userAttribute, signature, primaryKey, policy, validationDate)) {
122                            blank = PGPPublicKey.addCertification(blank, userAttribute, signature);
123                        }
124                    } else {
125                        if (SignatureVerifier.verifyUserAttributesCertification(userAttribute, signature, primaryKey, policy, validationDate)) {
126                            blank = PGPPublicKey.addCertification(blank, userAttribute, signature);
127                        }
128                    }
129                } catch (SignatureValidationException e) {
130                    LOGGER.debug("Rejecting user-attribute signature: {}", e.getMessage(), e);
131                }
132            }
133        }
134
135        return blank;
136    }
137
138}