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.util.Collections;
008import java.util.Date;
009import java.util.Iterator;
010import java.util.List;
011
012import org.bouncycastle.openpgp.PGPKeyRing;
013import org.bouncycastle.openpgp.PGPPublicKey;
014import org.bouncycastle.openpgp.PGPSignature;
015import org.pgpainless.algorithm.SignatureType;
016import org.pgpainless.exception.SignatureValidationException;
017import org.pgpainless.policy.Policy;
018import org.pgpainless.signature.SignatureUtils;
019import org.pgpainless.util.CollectionUtils;
020
021/**
022 * Pick signatures from keys.
023 *
024 * The format of a V4 OpenPGP key is:
025 *
026 * Primary-Key
027 *    [Revocation Self Signature]
028 *    [Direct Key Signature...]
029 *     User ID [Signature ...]
030 *    [User ID [Signature ...] ...]
031 *    [User Attribute [Signature ...] ...]
032 *    [[Subkey [Binding-Signature-Revocation] Primary-Key-Binding-Signature] ...]
033 */
034public final class SignaturePicker {
035
036    private SignaturePicker() {
037
038    }
039
040    /**
041     * Pick the at validation date most recent valid key revocation signature.
042     * If there are hard revocation signatures, the latest hard revocation sig is picked, even if it was created after
043     * validationDate or if it is already expired.
044     *
045     * @param keyRing key ring
046     * @return most recent, valid key revocation signature
047     */
048    public static PGPSignature pickCurrentRevocationSelfSignature(PGPKeyRing keyRing, Policy policy, Date validationDate) {
049        PGPPublicKey primaryKey = keyRing.getPublicKey();
050
051        List<PGPSignature> signatures = getSortedSignaturesOfType(primaryKey, SignatureType.KEY_REVOCATION);
052        PGPSignature mostCurrentValidSig = null;
053
054        for (PGPSignature signature : signatures) {
055            try {
056                SignatureVerifier.verifyKeyRevocationSignature(signature, primaryKey, policy, validationDate);
057            } catch (SignatureValidationException e) {
058                // Signature is not valid
059                continue;
060            }
061            mostCurrentValidSig = signature;
062        }
063
064        return mostCurrentValidSig;
065    }
066
067    /**
068     * Pick the at validationDate most recent, valid direct key signature.
069     * This method might return null, if there is no direct key self-signature which is valid at validationDate.
070     *
071     * @param keyRing key ring
072     * @param validationDate validation date
073     * @return direct-key self-signature
074     */
075    public static PGPSignature pickCurrentDirectKeySelfSignature(PGPKeyRing keyRing, Policy policy, Date validationDate) {
076        PGPPublicKey primaryKey = keyRing.getPublicKey();
077        return pickCurrentDirectKeySignature(primaryKey, primaryKey, policy, validationDate);
078    }
079
080    /**
081     * Pick the at validationDate, latest, valid direct key signature made by signingKey on signedKey.
082     * This method might return null, if there is no direct key self signature which is valid at validationDate.
083     *
084     * @param signingKey key that created the signature
085     * @param signedKey key that carries the signature
086     * @param validationDate validation date
087     * @return direct key sig
088     */
089    public static PGPSignature pickCurrentDirectKeySignature(PGPPublicKey signingKey, PGPPublicKey signedKey, Policy policy, Date validationDate) {
090        List<PGPSignature> directKeySignatures = getSortedSignaturesOfType(signedKey, SignatureType.DIRECT_KEY);
091
092        PGPSignature mostRecentDirectKeySigBySigningKey = null;
093        for (PGPSignature signature : directKeySignatures) {
094            try {
095                SignatureVerifier.verifyDirectKeySignature(signature, signingKey, signedKey, policy, validationDate);
096            } catch (SignatureValidationException e) {
097                // Direct key sig is not valid
098                continue;
099            }
100            mostRecentDirectKeySigBySigningKey = signature;
101        }
102
103        return mostRecentDirectKeySigBySigningKey;
104    }
105
106    /**
107     * Pick the at validationDate latest direct key signature.
108     * This method might return an expired signature.
109     * If there are more than one direct-key signature, and some of those are not expired, the latest non-expired
110     * yet already effective direct-key signature will be returned.
111     *
112     * @param keyRing key ring
113     * @param validationDate validation date
114     * @return latest direct key signature
115     */
116    public static PGPSignature pickLatestDirectKeySignature(PGPKeyRing keyRing, Policy policy, Date validationDate) {
117        PGPPublicKey primaryKey = keyRing.getPublicKey();
118        return pickLatestDirectKeySignature(primaryKey, primaryKey, policy, validationDate);
119    }
120
121    /**
122     * Pick the at validationDate latest direct key signature made by signingKey on signedKey.
123     * This method might return an expired signature.
124     * If a non-expired direct-key signature exists, the latest non-expired yet already effective direct-key
125     * signature will be returned.
126     *
127     * @param signingKey signing key (key that made the sig)
128     * @param signedKey signed key (key that carries the sig)
129     * @param validationDate date of validation
130     * @return latest direct key sig
131     */
132    public static PGPSignature pickLatestDirectKeySignature(PGPPublicKey signingKey, PGPPublicKey signedKey, Policy policy, Date validationDate) {
133        List<PGPSignature> signatures = getSortedSignaturesOfType(signedKey, SignatureType.DIRECT_KEY);
134
135        PGPSignature latestDirectKeySignature = null;
136        for (PGPSignature signature : signatures) {
137            try {
138                SignatureValidator.signatureIsOfType(SignatureType.DIRECT_KEY).verify(signature);
139                SignatureValidator.signatureStructureIsAcceptable(signingKey, policy).verify(signature);
140                SignatureValidator.signatureIsAlreadyEffective(validationDate).verify(signature);
141                // if the currently latest signature is not yet expired, check if the next candidate is not yet expired
142                if (latestDirectKeySignature != null && !SignatureUtils.isSignatureExpired(latestDirectKeySignature, validationDate)) {
143                    SignatureValidator.signatureIsNotYetExpired(validationDate).verify(signature);
144                }
145                SignatureValidator.correctSignatureOverKey(signingKey, signedKey).verify(signature);
146            } catch (SignatureValidationException e) {
147                // Direct key signature is not valid
148                continue;
149            }
150            latestDirectKeySignature = signature;
151        }
152
153        return latestDirectKeySignature;
154    }
155
156    /**
157     * Pick the at validationDate most recent, valid user-id revocation signature.
158     * If there are hard revocation signatures, the latest hard revocation sig is picked, even if it was created after
159     * validationDate or if it is already expired.
160     *
161     * @param keyRing key ring
162     * @param userId user-Id that gets revoked
163     * @param validationDate validation date
164     * @return revocation signature
165     */
166    public static PGPSignature pickCurrentUserIdRevocationSignature(PGPKeyRing keyRing, String userId, Policy policy, Date validationDate) {
167        PGPPublicKey primaryKey = keyRing.getPublicKey();
168        List<PGPSignature> signatures = getSortedSignaturesOfType(primaryKey, SignatureType.CERTIFICATION_REVOCATION);
169
170        PGPSignature latestUserIdRevocation = null;
171        for (PGPSignature signature : signatures) {
172            try {
173                SignatureVerifier.verifyUserIdRevocation(userId, signature, primaryKey, policy, validationDate);
174            } catch (SignatureValidationException e) {
175                // User-id revocation is not valid
176                continue;
177            }
178            latestUserIdRevocation = signature;
179        }
180
181        return latestUserIdRevocation;
182    }
183
184    /**
185     * Pick the at validationDate latest, valid certification self-signature for the given user-id.
186     * This method might return null, if there is no certification self signature for that user-id which is valid
187     * at validationDate.
188     *
189     * @param keyRing keyring
190     * @param userId userid
191     * @param validationDate validation date
192     * @return user-id certification
193     */
194    public static PGPSignature pickCurrentUserIdCertificationSignature(PGPKeyRing keyRing, String userId, Policy policy, Date validationDate) {
195        PGPPublicKey primaryKey = keyRing.getPublicKey();
196
197        Iterator<PGPSignature> userIdSigIterator = primaryKey.getSignaturesForID(userId);
198        List<PGPSignature> signatures = CollectionUtils.iteratorToList(userIdSigIterator);
199        Collections.sort(signatures, new SignatureCreationDateComparator());
200
201        PGPSignature mostRecentUserIdCertification = null;
202        for (PGPSignature signature : signatures) {
203            try {
204                SignatureVerifier.verifyUserIdCertification(userId, signature, primaryKey, policy, validationDate);
205            } catch (SignatureValidationException e) {
206                // User-id certification is not valid
207                continue;
208            }
209            mostRecentUserIdCertification = signature;
210        }
211
212        return mostRecentUserIdCertification;
213    }
214
215    /**
216     * Pick the at validationDate latest certification self-signature for the given user-id.
217     * This method might return an expired signature.
218     * If a non-expired user-id certification signature exists, the latest non-expired yet already effective
219     * user-id certification signature for the given user-id will be returned.
220     *
221     * @param keyRing keyring
222     * @param userId userid
223     * @param validationDate validation date
224     * @return user-id certification
225     */
226    public static PGPSignature pickLatestUserIdCertificationSignature(PGPKeyRing keyRing, String userId, Policy policy, Date validationDate) {
227        PGPPublicKey primaryKey = keyRing.getPublicKey();
228
229        Iterator<PGPSignature> userIdSigIterator = primaryKey.getSignaturesForID(userId);
230        List<PGPSignature> signatures = CollectionUtils.iteratorToList(userIdSigIterator);
231        Collections.sort(signatures, new SignatureCreationDateComparator());
232
233        PGPSignature latestUserIdCert = null;
234        for (PGPSignature signature : signatures) {
235            try {
236                SignatureValidator.wasPossiblyMadeByKey(primaryKey).verify(signature);
237                SignatureValidator.signatureIsCertification().verify(signature);
238                SignatureValidator.signatureStructureIsAcceptable(primaryKey, policy).verify(signature);
239                SignatureValidator.signatureIsAlreadyEffective(validationDate).verify(signature);
240                SignatureValidator.correctSignatureOverUserId(userId, primaryKey, primaryKey).verify(signature);
241            } catch (SignatureValidationException e) {
242                // User-id certification is not valid
243                continue;
244            }
245
246            latestUserIdCert = signature;
247        }
248
249        return latestUserIdCert;
250    }
251
252    /**
253     * Pick the at validationDate most recent, valid subkey revocation signature.
254     * If there are hard revocation signatures, the latest hard revocation sig is picked, even if it was created after
255     * validationDate or if it is already expired.
256     *
257     * @param keyRing keyring
258     * @param subkey subkey
259     * @param validationDate validation date
260     * @return subkey revocation signature
261     */
262    public static PGPSignature pickCurrentSubkeyBindingRevocationSignature(PGPKeyRing keyRing, PGPPublicKey subkey, Policy policy, Date validationDate) {
263        PGPPublicKey primaryKey = keyRing.getPublicKey();
264        if (primaryKey.getKeyID() == subkey.getKeyID()) {
265            throw new IllegalArgumentException("Primary key cannot have subkey binding revocations.");
266        }
267
268        List<PGPSignature> signatures = getSortedSignaturesOfType(subkey, SignatureType.SUBKEY_REVOCATION);
269        PGPSignature latestSubkeyRevocation = null;
270
271        for (PGPSignature signature : signatures) {
272            try {
273                SignatureVerifier.verifySubkeyBindingRevocation(signature, primaryKey, subkey, policy, validationDate);
274            } catch (SignatureValidationException e) {
275                // subkey binding revocation is not valid
276                continue;
277            }
278            latestSubkeyRevocation = signature;
279        }
280
281        return latestSubkeyRevocation;
282    }
283
284    /**
285     * Pick the at validationDate latest, valid subkey binding signature for the given subkey.
286     * This method might return null, if there is no subkey binding signature which is valid
287     * at validationDate.
288     *
289     * @param keyRing key ring
290     * @param subkey subkey
291     * @param validationDate date of validation
292     * @return most recent valid subkey binding signature
293     */
294    public static PGPSignature pickCurrentSubkeyBindingSignature(PGPKeyRing keyRing, PGPPublicKey subkey, Policy policy, Date validationDate) {
295        PGPPublicKey primaryKey = keyRing.getPublicKey();
296        if (primaryKey.getKeyID() == subkey.getKeyID()) {
297            throw new IllegalArgumentException("Primary key cannot have subkey binding signature.");
298        }
299
300        List<PGPSignature> subkeyBindingSigs = getSortedSignaturesOfType(subkey, SignatureType.SUBKEY_BINDING);
301        PGPSignature mostCurrentValidSig = null;
302
303        for (PGPSignature signature : subkeyBindingSigs) {
304            try {
305                SignatureVerifier.verifySubkeyBindingSignature(signature, primaryKey, subkey, policy, validationDate);
306            } catch (SignatureValidationException validationException) {
307                // Subkey binding sig is not valid
308                continue;
309            }
310            mostCurrentValidSig = signature;
311        }
312
313        return mostCurrentValidSig;
314    }
315
316    /**
317     * Pick the at validationDate latest subkey binding signature for the given subkey.
318     * This method might return an expired signature.
319     * If a non-expired subkey binding signature exists, the latest non-expired yet already effective
320     * subkey binding signature for the given subkey will be returned.
321     *
322     * @param keyRing key ring
323     * @param subkey subkey
324     * @param validationDate validationDate
325     * @return subkey binding signature
326     */
327    public static PGPSignature pickLatestSubkeyBindingSignature(PGPKeyRing keyRing, PGPPublicKey subkey, Policy policy, Date validationDate) {
328        PGPPublicKey primaryKey = keyRing.getPublicKey();
329        if (primaryKey.getKeyID() == subkey.getKeyID()) {
330            throw new IllegalArgumentException("Primary key cannot have subkey binding signature.");
331        }
332
333        List<PGPSignature> signatures = getSortedSignaturesOfType(subkey, SignatureType.SUBKEY_BINDING);
334        PGPSignature latestSubkeyBinding = null;
335
336        for (PGPSignature signature : signatures) {
337            try {
338                SignatureValidator.signatureIsOfType(SignatureType.SUBKEY_BINDING).verify(signature);
339                SignatureValidator.signatureStructureIsAcceptable(primaryKey, policy).verify(signature);
340                SignatureValidator.signatureIsAlreadyEffective(validationDate).verify(signature);
341                // if the currently latest signature is not yet expired, check if the next candidate is not yet expired
342                if (latestSubkeyBinding != null && !SignatureUtils.isSignatureExpired(latestSubkeyBinding, validationDate)) {
343                    SignatureValidator.signatureIsNotYetExpired(validationDate).verify(signature);
344                }
345                SignatureValidator.correctSubkeyBindingSignature(primaryKey, subkey).verify(signature);
346            } catch (SignatureValidationException e) {
347                // Subkey binding sig is not valid
348                continue;
349            }
350            latestSubkeyBinding = signature;
351        }
352
353        return latestSubkeyBinding;
354    }
355
356    /**
357     * Return a list of all signatures of the given {@link SignatureType} on the given key, sorted using a
358     * {@link SignatureCreationDateComparator}.
359     *
360     * The returned list will be sorted first by ascending signature creation time.
361     *
362     * @param key key
363     * @param type type of signatures which shall be collected and sorted
364     * @return sorted list of signatures
365     */
366    private static List<PGPSignature> getSortedSignaturesOfType(PGPPublicKey key, SignatureType type) {
367        Iterator<PGPSignature> signaturesOfType = key.getSignaturesOfType(type.getCode());
368        List<PGPSignature> signatureList = CollectionUtils.iteratorToList(signaturesOfType);
369        Collections.sort(signatureList, new SignatureCreationDateComparator());
370        return signatureList;
371    }
372}