001// SPDX-FileCopyrightText: 2021 Paul Schaub <vanitasvitae@fsfe.org>
002//
003// SPDX-License-Identifier: Apache-2.0
004
005package org.pgpainless.signature.consumer;
006
007import java.security.NoSuchAlgorithmException;
008import java.util.Arrays;
009import java.util.Date;
010import java.util.Iterator;
011import java.util.List;
012import java.util.Map;
013import java.util.concurrent.ConcurrentHashMap;
014
015import org.bouncycastle.bcpg.sig.KeyFlags;
016import org.bouncycastle.bcpg.sig.NotationData;
017import org.bouncycastle.bcpg.sig.SignatureCreationTime;
018import org.bouncycastle.openpgp.PGPException;
019import org.bouncycastle.openpgp.PGPPublicKey;
020import org.bouncycastle.openpgp.PGPSignature;
021import org.bouncycastle.openpgp.PGPSignatureList;
022import org.bouncycastle.openpgp.PGPSignatureSubpacketVector;
023import org.bouncycastle.openpgp.PGPUserAttributeSubpacketVector;
024import org.pgpainless.algorithm.HashAlgorithm;
025import org.pgpainless.algorithm.KeyFlag;
026import org.pgpainless.algorithm.PublicKeyAlgorithm;
027import org.pgpainless.algorithm.SignatureSubpacket;
028import org.pgpainless.algorithm.SignatureType;
029import org.pgpainless.exception.SignatureValidationException;
030import org.pgpainless.implementation.ImplementationFactory;
031import org.pgpainless.key.OpenPgpFingerprint;
032import org.pgpainless.policy.Policy;
033import org.pgpainless.signature.SignatureUtils;
034import org.pgpainless.signature.subpackets.SignatureSubpacketsUtil;
035import org.pgpainless.util.BCUtil;
036import org.pgpainless.util.DateUtil;
037import org.pgpainless.util.NotationRegistry;
038
039/**
040 * A collection of validators that perform validation steps over signatures.
041 */
042public abstract class SignatureValidator {
043
044    public abstract void verify(PGPSignature signature) throws SignatureValidationException;
045
046    /**
047     * Check, whether there is the possibility that the given signature was created by the given key.
048     * {@link #verify(PGPSignature)} throws a {@link SignatureValidationException} if we can say with certainty that the signature
049     * was not created by the given key (e.g. if the sig carries another issuer, issuer fingerprint packet).
050     *
051     * If there is no information found in the signature about who created it (no issuer, no fingerprint),
052     * {@link #verify(PGPSignature)} will simply return since it is plausible that the given key created the sig.
053     *
054     * @param signingKey signing key
055     * @return validator that throws a {@link SignatureValidationException} if the signature was not possibly made by the given key.
056     */
057    public static SignatureValidator wasPossiblyMadeByKey(PGPPublicKey signingKey) {
058        return new SignatureValidator() {
059            @Override
060            public void verify(PGPSignature signature) throws SignatureValidationException {
061                OpenPgpFingerprint signingKeyFingerprint = OpenPgpFingerprint.of(signingKey);
062
063                Long issuer = SignatureSubpacketsUtil.getIssuerKeyIdAsLong(signature);
064                if (issuer != null) {
065                    if (issuer != signingKey.getKeyID()) {
066                        throw new SignatureValidationException("Signature was not created by " + signingKeyFingerprint + " (signature issuer: " + Long.toHexString(issuer) + ")");
067                    }
068                }
069
070                OpenPgpFingerprint fingerprint = SignatureSubpacketsUtil.getIssuerFingerprintAsOpenPgpFingerprint(signature);
071                if (fingerprint != null) {
072                    if (!fingerprint.equals(signingKeyFingerprint)) {
073                        throw new SignatureValidationException("Signature was not created by " + signingKeyFingerprint + " (signature fingerprint: " + fingerprint + ")");
074                    }
075                }
076
077                // No issuer information found, so we cannot rule out that we did not create the sig
078            }
079        };
080
081    }
082
083    /**
084     * Verify that a subkey binding signature - if the subkey is signing-capable - contains a valid primary key
085     * binding signature.
086     *
087     * @param primaryKey primary key
088     * @param subkey subkey
089     * @param policy policy
090     * @param validationDate reference date for signature verification
091     * @return validator
092     */
093    public static SignatureValidator hasValidPrimaryKeyBindingSignatureIfRequired(PGPPublicKey primaryKey, PGPPublicKey subkey, Policy policy, Date validationDate) {
094        return new SignatureValidator() {
095            @Override
096            public void verify(PGPSignature signature) throws SignatureValidationException {
097                if (!PublicKeyAlgorithm.fromId(signature.getKeyAlgorithm()).isSigningCapable()) {
098                    // subkey is not signing capable -> No need to process embedded sigs
099                    return;
100                }
101
102                KeyFlags keyFlags = SignatureSubpacketsUtil.getKeyFlags(signature);
103                if (keyFlags == null) {
104                    return;
105                }
106                if (!KeyFlag.hasKeyFlag(keyFlags.getFlags(), KeyFlag.SIGN_DATA)
107                        && !KeyFlag.hasKeyFlag(keyFlags.getFlags(), KeyFlag.CERTIFY_OTHER)) {
108                    return;
109                }
110
111                try {
112                    PGPSignatureList embeddedSignatures = SignatureSubpacketsUtil.getEmbeddedSignature(signature);
113                    boolean hasValidPrimaryKeyBinding = false;
114                    Map<PGPSignature, Exception> rejectedEmbeddedSigs = new ConcurrentHashMap<>();
115                    for (PGPSignature embedded : embeddedSignatures) {
116
117                        if (SignatureType.valueOf(embedded.getSignatureType()) == SignatureType.PRIMARYKEY_BINDING) {
118
119                            try {
120                                signatureStructureIsAcceptable(subkey, policy).verify(embedded);
121                                signatureIsEffective(validationDate).verify(embedded);
122                                correctPrimaryKeyBindingSignature(primaryKey, subkey).verify(embedded);
123
124                                hasValidPrimaryKeyBinding = true;
125                                break;
126                            } catch (SignatureValidationException e) {
127                                rejectedEmbeddedSigs.put(embedded, e);
128                            }
129                        }
130                    }
131
132                    if (!hasValidPrimaryKeyBinding) {
133                        throw new SignatureValidationException("Missing primary key binding signature on signing capable subkey " + Long.toHexString(subkey.getKeyID()), rejectedEmbeddedSigs);
134                    }
135                } catch (PGPException e) {
136                    throw new SignatureValidationException("Cannot process list of embedded signatures.", e);
137                }
138            }
139        };
140    }
141
142    /**
143     * Verify that a signature has an acceptable structure.
144     *
145     * @param signingKey signing key
146     * @param policy policy
147     * @return validator
148     */
149    public static SignatureValidator signatureStructureIsAcceptable(PGPPublicKey signingKey, Policy policy) {
150        return new SignatureValidator() {
151            @Override
152            public void verify(PGPSignature signature) throws SignatureValidationException {
153                signatureIsNotMalformed(signingKey).verify(signature);
154                signatureDoesNotHaveCriticalUnknownNotations(policy.getNotationRegistry()).verify(signature);
155                signatureDoesNotHaveCriticalUnknownSubpackets().verify(signature);
156                signatureUsesAcceptableHashAlgorithm(policy).verify(signature);
157                signatureUsesAcceptablePublicKeyAlgorithm(policy, signingKey).verify(signature);
158            }
159        };
160    }
161
162    /**
163     * Verify that a signature was made using an acceptable {@link PublicKeyAlgorithm}.
164     *
165     * @param policy policy
166     * @param signingKey signing key
167     * @return validator
168     */
169    private static SignatureValidator signatureUsesAcceptablePublicKeyAlgorithm(Policy policy, PGPPublicKey signingKey) {
170        return new SignatureValidator() {
171            @Override
172            public void verify(PGPSignature signature) throws SignatureValidationException {
173                PublicKeyAlgorithm algorithm = PublicKeyAlgorithm.fromId(signingKey.getAlgorithm());
174                try {
175                    int bitStrength = BCUtil.getBitStrength(signingKey);
176                    if (!policy.getPublicKeyAlgorithmPolicy().isAcceptable(algorithm, bitStrength)) {
177                        throw new SignatureValidationException("Signature was made using unacceptable key. " +
178                                algorithm + " (" + bitStrength + " bits) is not acceptable according to the public key algorithm policy.");
179                    }
180                } catch (NoSuchAlgorithmException e) {
181                    throw new SignatureValidationException("Cannot determine bit strength of signing key.", e);
182                }
183            }
184        };
185    }
186
187    /**
188     * Verify that a signature uses an acceptable {@link HashAlgorithm}.
189     *
190     * @param policy policy
191     * @return validator
192     */
193    private static SignatureValidator signatureUsesAcceptableHashAlgorithm(Policy policy) {
194        return new SignatureValidator() {
195            @Override
196            public void verify(PGPSignature signature) throws SignatureValidationException {
197                HashAlgorithm hashAlgorithm = HashAlgorithm.fromId(signature.getHashAlgorithm());
198                Policy.HashAlgorithmPolicy hashAlgorithmPolicy = getHashAlgorithmPolicyForSignature(signature, policy);
199
200                if (!hashAlgorithmPolicy.isAcceptable(signature.getHashAlgorithm())) {
201                    throw new SignatureValidationException("Signature uses unacceptable hash algorithm " + hashAlgorithm);
202                }
203            }
204        };
205    }
206
207    /**
208     * Return the applicable {@link Policy.HashAlgorithmPolicy} for the given {@link PGPSignature}.
209     * Revocation signatures are being policed using a different policy than non-revocation signatures.
210     *
211     * @param signature signature
212     * @param policy revocation policy for revocation sigs, normal policy for non-rev sigs
213     * @return policy
214     */
215    private static Policy.HashAlgorithmPolicy getHashAlgorithmPolicyForSignature(PGPSignature signature, Policy policy) {
216        SignatureType type = SignatureType.valueOf(signature.getSignatureType());
217        Policy.HashAlgorithmPolicy hashAlgorithmPolicy;
218        if (type == SignatureType.CERTIFICATION_REVOCATION || type == SignatureType.KEY_REVOCATION || type == SignatureType.SUBKEY_REVOCATION) {
219            hashAlgorithmPolicy = policy.getRevocationSignatureHashAlgorithmPolicy();
220        } else {
221            hashAlgorithmPolicy = policy.getSignatureHashAlgorithmPolicy();
222        }
223        return hashAlgorithmPolicy;
224    }
225
226    /**
227     * Verify that a signature does not carry critical unknown notations.
228     *
229     * @param registry notation registry of known notations
230     * @return validator
231     */
232    public static SignatureValidator signatureDoesNotHaveCriticalUnknownNotations(NotationRegistry registry) {
233        return new SignatureValidator() {
234            @Override
235            public void verify(PGPSignature signature) throws SignatureValidationException {
236                List<NotationData> hashedNotations = SignatureSubpacketsUtil.getHashedNotationData(signature);
237                for (NotationData notation : hashedNotations) {
238                    if (!notation.isCritical()) {
239                        continue;
240                    }
241                    if (!registry.isKnownNotation(notation.getNotationName())) {
242                        throw new SignatureValidationException("Signature contains unknown critical notation '" + notation.getNotationName() + "' in its hashed area.");
243                    }
244                }
245            }
246        };
247    }
248
249    /**
250     * Verify that a signature does not contain critical unknown subpackets.
251     *
252     * @return validator
253     */
254    public static SignatureValidator signatureDoesNotHaveCriticalUnknownSubpackets() {
255        return new SignatureValidator() {
256            @Override
257            public void verify(PGPSignature signature) throws SignatureValidationException {
258                PGPSignatureSubpacketVector hashedSubpackets = signature.getHashedSubPackets();
259                for (int criticalTag : hashedSubpackets.getCriticalTags()) {
260                    try {
261                        SignatureSubpacket.fromCode(criticalTag);
262                    } catch (IllegalArgumentException e) {
263                        throw new SignatureValidationException("Signature contains unknown critical subpacket of type " + Long.toHexString(criticalTag));
264                    }
265                }
266            }
267        };
268    }
269
270    /**
271     * Verify that a signature is effective right now.
272     *
273     * @return validator
274     */
275    public static SignatureValidator signatureIsEffective() {
276        return signatureIsEffective(new Date());
277    }
278
279    /**
280     * Verify that a signature is effective at the given reference date.
281     *
282     * @param validationDate reference date for signature verification
283     * @return validator
284     */
285    public static SignatureValidator signatureIsEffective(Date validationDate) {
286        return new SignatureValidator() {
287            @Override
288            public void verify(PGPSignature signature) throws SignatureValidationException {
289                signatureIsAlreadyEffective(validationDate).verify(signature);
290                signatureIsNotYetExpired(validationDate).verify(signature);
291            }
292        };
293    }
294
295    /**
296     * Verify that a signature was created prior to the given reference date.
297     *
298     * @param validationDate reference date for signature verification
299     * @return validator
300     */
301    public static SignatureValidator signatureIsAlreadyEffective(Date validationDate) {
302        return new SignatureValidator() {
303            @Override
304            public void verify(PGPSignature signature) throws SignatureValidationException {
305                Date signatureCreationTime = SignatureSubpacketsUtil.getSignatureCreationTime(signature).getTime();
306                // Hard revocations are always effective
307                if (SignatureUtils.isHardRevocation(signature)) {
308                    return;
309                }
310
311                if (signatureCreationTime.after(validationDate)) {
312                    throw new SignatureValidationException("Signature was created at " + signatureCreationTime + " and is therefore not yet valid at " + validationDate);
313                }
314            }
315        };
316    }
317
318    /**
319     * Verify that a signature is not yet expired.
320     *
321     * @param validationDate reference date for signature verification
322     * @return validator
323     */
324    public static SignatureValidator signatureIsNotYetExpired(Date validationDate) {
325        return new SignatureValidator() {
326            @Override
327            public void verify(PGPSignature signature) throws SignatureValidationException {
328                // Hard revocations do not expire
329                if (SignatureUtils.isHardRevocation(signature)) {
330                    return;
331                }
332
333                Date signatureExpirationTime = SignatureSubpacketsUtil.getSignatureExpirationTimeAsDate(signature);
334                if (signatureExpirationTime != null && signatureExpirationTime.before(validationDate)) {
335                    throw new SignatureValidationException("Signature is already expired (expiration: " + signatureExpirationTime + ", validation: " + validationDate + ")");
336                }
337            }
338        };
339    }
340
341    /**
342     * Verify that a signature is not malformed.
343     * A signature is malformed if it has no hashed creation time subpacket,
344     * it predates the creation time of the signing key, or it predates the creation date
345     * of the signing key binding signature.
346     *
347     * @param creator signing key
348     * @return validator
349     */
350    public static SignatureValidator signatureIsNotMalformed(PGPPublicKey creator) {
351        return new SignatureValidator() {
352            @Override
353            public void verify(PGPSignature signature) throws SignatureValidationException {
354                signatureHasHashedCreationTime().verify(signature);
355                signatureDoesNotPredateSigningKey(creator).verify(signature);
356                signatureDoesNotPredateSigningKeyBindingDate(creator).verify(signature);
357            }
358        };
359    }
360
361    /**
362     * Verify that a signature has a hashed creation time subpacket.
363     *
364     * @return validator
365     */
366    public static SignatureValidator signatureHasHashedCreationTime() {
367        return new SignatureValidator() {
368            @Override
369            public void verify(PGPSignature signature) throws SignatureValidationException {
370                SignatureCreationTime creationTime = SignatureSubpacketsUtil.getSignatureCreationTime(signature);
371                if (creationTime == null) {
372                    throw new SignatureValidationException("Malformed signature. Signature has no signature creation time subpacket in its hashed area.");
373                }
374            }
375        };
376    }
377
378    /**
379     * Verify that a signature does not predate the creation time of the signing key.
380     *
381     * @param key signing key
382     * @return validator
383     */
384    public static SignatureValidator signatureDoesNotPredateSigningKey(PGPPublicKey key) {
385        return new SignatureValidator() {
386            @Override
387            public void verify(PGPSignature signature) throws SignatureValidationException {
388                Date keyCreationTime = key.getCreationTime();
389                Date signatureCreationTime = signature.getCreationTime();
390
391                if (keyCreationTime.after(signatureCreationTime)) {
392                    throw new SignatureValidationException("Signature predates its signing key (key creation: " + keyCreationTime + ", signature creation: " + signatureCreationTime + ")");
393                }
394            }
395        };
396    }
397
398    /**
399     * Verify that a signature does not predate the binding date of the signing key.
400     *
401     * @param signingKey signing key
402     * @return validator
403     */
404    public static SignatureValidator signatureDoesNotPredateSigningKeyBindingDate(PGPPublicKey signingKey) {
405        return new SignatureValidator() {
406            @Override
407            public void verify(PGPSignature signature) throws SignatureValidationException {
408                if (signingKey.isMasterKey()) {
409                    return;
410                }
411                boolean predatesBindingSig = true;
412                Iterator<PGPSignature> bindingSignatures = signingKey.getSignaturesOfType(SignatureType.SUBKEY_BINDING.getCode());
413                if (!bindingSignatures.hasNext()) {
414                    throw new SignatureValidationException("Signing subkey does not have a subkey binding signature.");
415                }
416                while (bindingSignatures.hasNext()) {
417                    PGPSignature bindingSig = bindingSignatures.next();
418                    if (!bindingSig.getCreationTime().after(signature.getCreationTime())) {
419                        predatesBindingSig = false;
420                    }
421                }
422                if (predatesBindingSig) {
423                    throw new SignatureValidationException("Signature was created before the signing key was bound to the key ring.");
424                }
425            }
426        };
427    }
428
429    /**
430     * Verify that a subkey binding signature is correct.
431     *
432     * @param primaryKey primary key
433     * @param subkey subkey
434     * @return validator
435     */
436    public static SignatureValidator correctSubkeyBindingSignature(PGPPublicKey primaryKey, PGPPublicKey subkey) {
437        return new SignatureValidator() {
438            @Override
439            public void verify(PGPSignature signature) throws SignatureValidationException {
440                if (primaryKey.getKeyID() == subkey.getKeyID()) {
441                    throw new SignatureValidationException("Primary key cannot be its own subkey.");
442                }
443                try {
444                    signature.init(ImplementationFactory.getInstance().getPGPContentVerifierBuilderProvider(), primaryKey);
445                    boolean valid = signature.verifyCertification(primaryKey, subkey);
446                    if (!valid) {
447                        throw new SignatureValidationException("Signature is not correct.");
448                    }
449                } catch (PGPException e) {
450                    throw new SignatureValidationException("Cannot verify subkey binding signature correctness", e);
451                }
452            }
453        };
454    }
455
456    /**
457     * Verify that a primary key binding signature is correct.
458     *
459     * @param primaryKey primary key
460     * @param subkey subkey
461     * @return validator
462     */
463    public static SignatureValidator correctPrimaryKeyBindingSignature(PGPPublicKey primaryKey, PGPPublicKey subkey) {
464        return new SignatureValidator() {
465            @Override
466            public void verify(PGPSignature signature) throws SignatureValidationException {
467                try {
468                    signature.init(ImplementationFactory.getInstance().getPGPContentVerifierBuilderProvider(), subkey);
469                    boolean valid = signature.verifyCertification(primaryKey, subkey);
470                    if (!valid) {
471                        throw new SignatureValidationException("Primary Key Binding Signature is not correct.");
472                    }
473                } catch (PGPException e) {
474                    throw new SignatureValidationException("Cannot verify primary key binding signature correctness", e);
475                }
476            }
477        };
478    }
479
480    /**
481     * Verify that a direct-key signature is correct.
482     *
483     * @param signer signing key
484     * @param signee signed key
485     * @return validator
486     */
487    public static SignatureValidator correctSignatureOverKey(PGPPublicKey signer, PGPPublicKey signee) {
488        return new SignatureValidator() {
489            @Override
490            public void verify(PGPSignature signature) throws SignatureValidationException {
491                try {
492                    signature.init(ImplementationFactory.getInstance().getPGPContentVerifierBuilderProvider(), signer);
493                    boolean valid;
494                    if (signer.getKeyID() != signee.getKeyID()) {
495                        valid = signature.verifyCertification(signer, signee);
496                    } else {
497                        valid = signature.verifyCertification(signee);
498                    }
499                    if (!valid) {
500                        throw new SignatureValidationException("Signature is not correct.");
501                    }
502                } catch (PGPException e) {
503                    throw new SignatureValidationException("Cannot verify direct-key signature correctness", e);
504                }
505            }
506        };
507    }
508
509    /**
510     * Verify that a signature is a certification signature.
511     *
512     * @return validator
513     */
514    public static SignatureValidator signatureIsCertification() {
515        return signatureIsOfType(
516                SignatureType.POSITIVE_CERTIFICATION,
517                SignatureType.CASUAL_CERTIFICATION,
518                SignatureType.GENERIC_CERTIFICATION,
519                SignatureType.NO_CERTIFICATION);
520    }
521
522    /**
523     * Verify that a signature type equals one of the given {@link SignatureType SignatureTypes}.
524     *
525     * @param signatureTypes one or more signature types
526     * @return validator
527     */
528    public static SignatureValidator signatureIsOfType(SignatureType... signatureTypes) {
529        return new SignatureValidator() {
530            @Override
531            public void verify(PGPSignature signature) throws SignatureValidationException {
532                SignatureType type = SignatureType.valueOf(signature.getSignatureType());
533                boolean valid = false;
534                for (SignatureType allowed : signatureTypes) {
535                    if (type == allowed) {
536                        valid = true;
537                        break;
538                    }
539                }
540                if (!valid) {
541                    throw new SignatureValidationException("Signature is of type " + type + " while only " + Arrays.toString(signatureTypes) + " are allowed here.");
542                }
543            }
544        };
545    }
546
547    /**
548     * Verify that a signature over a user-id is correct.
549     *
550     * @param userId user-id
551     * @param certifiedKey key carrying the user-id
552     * @param certifyingKey key that created the signature.
553     * @return validator
554     */
555    public static SignatureValidator correctSignatureOverUserId(String userId, PGPPublicKey certifiedKey, PGPPublicKey certifyingKey) {
556        return new SignatureValidator() {
557            @Override
558            public void verify(PGPSignature signature) throws SignatureValidationException {
559                try {
560                    signature.init(ImplementationFactory.getInstance().getPGPContentVerifierBuilderProvider(), certifyingKey);
561                    boolean valid = signature.verifyCertification(userId, certifiedKey);
562                    if (!valid) {
563                        throw new SignatureValidationException("Signature over user-id '" + userId + "' is not correct.");
564                    }
565                } catch (PGPException e) {
566                    throw new SignatureValidationException("Cannot verify signature over user-id '" + userId + "'.", e);
567                }
568            }
569        };
570    }
571
572    /**
573     * Verify that a signature over a user-attribute packet is correct.
574     *
575     * @param userAttributes user attributes
576     * @param certifiedKey key carrying the user-attributes
577     * @param certifyingKey key that created the certification signature
578     * @return validator
579     */
580    public static SignatureValidator correctSignatureOverUserAttributes(PGPUserAttributeSubpacketVector userAttributes, PGPPublicKey certifiedKey, PGPPublicKey certifyingKey) {
581        return new SignatureValidator() {
582            @Override
583            public void verify(PGPSignature signature) throws SignatureValidationException {
584                try {
585                    signature.init(ImplementationFactory.getInstance().getPGPContentVerifierBuilderProvider(), certifyingKey);
586                    boolean valid = signature.verifyCertification(userAttributes, certifiedKey);
587                    if (!valid) {
588                        throw new SignatureValidationException("Signature over user-attribute vector is not correct.");
589                    }
590                } catch (PGPException e) {
591                    throw new SignatureValidationException("Cannot verify signature over user-attribute vector.", e);
592                }
593            }
594        };
595    }
596
597    public static SignatureValidator signatureWasCreatedInBounds(Date notBefore, Date notAfter) {
598        return new SignatureValidator() {
599            @Override
600            public void verify(PGPSignature signature) throws SignatureValidationException {
601                Date timestamp = signature.getCreationTime();
602                if (notBefore != null && timestamp.before(notBefore)) {
603                    throw new SignatureValidationException("Signature was made before the earliest allowed signature creation time. Created: " +
604                            DateUtil.formatUTCDate(timestamp) + " Earliest allowed: " + DateUtil.formatUTCDate(notBefore));
605                }
606                if (notAfter != null && timestamp.after(notAfter)) {
607                    throw new SignatureValidationException("Signature was made after the latest allowed signature creation time. Created: " +
608                            DateUtil.formatUTCDate(timestamp) + " Latest allowed: " + DateUtil.formatUTCDate(notAfter));
609                }
610            }
611        };
612    }
613
614}