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.io.IOException;
008import java.io.InputStream;
009import java.util.Date;
010
011import org.bouncycastle.openpgp.PGPException;
012import org.bouncycastle.openpgp.PGPPublicKey;
013import org.bouncycastle.openpgp.PGPSignature;
014import org.bouncycastle.openpgp.PGPUserAttributeSubpacketVector;
015import org.pgpainless.algorithm.SignatureType;
016import org.pgpainless.exception.SignatureValidationException;
017import org.pgpainless.implementation.ImplementationFactory;
018import org.pgpainless.policy.Policy;
019
020/**
021 * Collection of static methods for signature verification.
022 * Signature verification entails validation of certain criteria (see {@link SignatureValidator}), as well as
023 * cryptographic verification of signature correctness.
024 */
025public final class SignatureVerifier {
026
027    private SignatureVerifier() {
028
029    }
030
031    /**
032     * Verify a signature (certification or revocation) over a user-id.
033     *
034     * @param userId user-id
035     * @param signature certification signature
036     * @param signingKey key that created the certification
037     * @param keyWithUserId key carrying the user-id
038     * @param policy policy
039     * @param validationDate reference date for signature verification
040     * @return true if signature verification is successful
041     *
042     * @throws SignatureValidationException if signature verification fails for some reason
043     */
044    public static boolean verifySignatureOverUserId(String userId, PGPSignature signature, PGPPublicKey signingKey, PGPPublicKey keyWithUserId, Policy policy, Date validationDate)
045            throws SignatureValidationException {
046        SignatureType type = SignatureType.valueOf(signature.getSignatureType());
047        switch (type) {
048            case GENERIC_CERTIFICATION:
049            case NO_CERTIFICATION:
050            case CASUAL_CERTIFICATION:
051            case POSITIVE_CERTIFICATION:
052                return verifyUserIdCertification(userId, signature, signingKey, keyWithUserId, policy, validationDate);
053            case CERTIFICATION_REVOCATION:
054                return verifyUserIdRevocation(userId, signature, signingKey, keyWithUserId, policy, validationDate);
055            default:
056                throw new SignatureValidationException("Signature is not a valid user-id certification/revocation signature: " + type);
057        }
058    }
059
060    /**
061     * Verify a certification self-signature over a user-id.
062     *
063     * @param userId user-id
064     * @param signature certification signature
065     * @param primaryKey primary key
066     * @param policy policy
067     * @param validationDate reference date for signature verification
068     * @return true if the self-signature is verified successfully
069     *
070     * @throws SignatureValidationException if signature verification fails for some reason
071     */
072    public static boolean verifyUserIdCertification(String userId, PGPSignature signature, PGPPublicKey primaryKey, Policy policy, Date validationDate)
073            throws SignatureValidationException {
074        return verifyUserIdCertification(userId, signature, primaryKey, primaryKey, policy, validationDate);
075    }
076
077    /**
078     * Verify a user-id certification.
079     *
080     * @param userId user-id
081     * @param signature certification signature
082     * @param signingKey key that created the certification
083     * @param keyWithUserId primary key that carries the user-id
084     * @param policy policy
085     * @param validationDate reference date for signature verification
086     * @return true if signature verification is successful
087     *
088     * @throws SignatureValidationException if signature verification fails for some reason
089     */
090    public static boolean verifyUserIdCertification(String userId, PGPSignature signature, PGPPublicKey signingKey, PGPPublicKey keyWithUserId, Policy policy, Date validationDate)
091            throws SignatureValidationException {
092        SignatureValidator.signatureIsCertification().verify(signature);
093        SignatureValidator.signatureStructureIsAcceptable(signingKey, policy).verify(signature);
094        SignatureValidator.signatureIsEffective(validationDate).verify(signature);
095        SignatureValidator.correctSignatureOverUserId(userId, keyWithUserId, signingKey).verify(signature);
096
097        return true;
098    }
099
100    /**
101     * Verify a user-id revocation self-signature.
102     *
103     * @param userId user-id
104     * @param signature user-id revocation signature
105     * @param primaryKey primary key
106     * @param policy policy
107     * @param validationDate reference date for signature verification
108     * @return true if the user-id revocation signature is successfully verified
109     *
110     * @throws SignatureValidationException if signature verification fails for some reason
111     */
112    public static boolean verifyUserIdRevocation(String userId, PGPSignature signature, PGPPublicKey primaryKey, Policy policy, Date validationDate)
113            throws SignatureValidationException {
114        return verifyUserIdRevocation(userId, signature, primaryKey, primaryKey, policy, validationDate);
115    }
116
117    /**
118     * Verify a user-id revocation signature.
119     *
120     * @param userId user-id
121     * @param signature revocation signature
122     * @param signingKey key that created the revocation signature
123     * @param keyWithUserId primary key carrying the user-id
124     * @param policy policy
125     * @param validationDate reference date for signature verification
126     * @return true if the user-id revocation signature is successfully verified
127     *
128     * @throws SignatureValidationException if signature verification fails for some reason
129     */
130    public static boolean verifyUserIdRevocation(String userId, PGPSignature signature, PGPPublicKey signingKey, PGPPublicKey keyWithUserId, Policy policy, Date validationDate)
131            throws SignatureValidationException {
132        SignatureValidator.signatureIsOfType(SignatureType.CERTIFICATION_REVOCATION).verify(signature);
133        SignatureValidator.signatureStructureIsAcceptable(signingKey, policy).verify(signature);
134        SignatureValidator.signatureIsEffective(validationDate).verify(signature);
135        SignatureValidator.correctSignatureOverUserId(userId, keyWithUserId, signingKey).verify(signature);
136
137        return true;
138    }
139
140    /**
141     * Verify a certification self-signature over a user-attributes packet.
142     *
143     * @param userAttributes user attributes
144     * @param signature certification self-signature
145     * @param primaryKey primary key that carries the user-attributes
146     * @param policy policy
147     * @param validationDate reference date for signature verification
148     * @return true if the signature can be verified successfully
149     *
150     * @throws SignatureValidationException if signature verification fails for some reason
151     */
152    public static boolean verifyUserAttributesCertification(PGPUserAttributeSubpacketVector userAttributes,
153                                                            PGPSignature signature, PGPPublicKey primaryKey,
154                                                            Policy policy, Date validationDate)
155            throws SignatureValidationException {
156        return verifyUserAttributesCertification(userAttributes, signature, primaryKey, primaryKey, policy, validationDate);
157    }
158
159    /**
160     * Verify a certification signature over a user-attributes packet.
161     *
162     * @param userAttributes user attributes
163     * @param signature certification signature
164     * @param signingKey key that created the user-attributes certification
165     * @param keyWithUserAttributes key that carries the user-attributes certification
166     * @param policy policy
167     * @param validationDate reference date for signature verification
168     * @return true if the signature can be verified successfully
169     *
170     * @throws SignatureValidationException if signature verification fails for some reason
171     */
172    public static boolean verifyUserAttributesCertification(PGPUserAttributeSubpacketVector userAttributes,
173                                                            PGPSignature signature, PGPPublicKey signingKey,
174                                                            PGPPublicKey keyWithUserAttributes, Policy policy,
175                                                            Date validationDate)
176            throws SignatureValidationException {
177        SignatureValidator.signatureIsCertification().verify(signature);
178        SignatureValidator.signatureStructureIsAcceptable(signingKey, policy).verify(signature);
179        SignatureValidator.signatureIsEffective(validationDate).verify(signature);
180        SignatureValidator.correctSignatureOverUserAttributes(userAttributes, keyWithUserAttributes, signingKey).verify(signature);
181
182        return true;
183    }
184
185    /**
186     * Verify a user-attributes revocation self-signature.
187     *
188     * @param userAttributes user-attributes
189     * @param signature user-attributes revocation signature
190     * @param primaryKey primary key that carries the user-attributes
191     * @param policy policy
192     * @param validationDate reference date for signature verification
193     * @return true if the revocation signature can be verified successfully
194     *
195     * @throws SignatureValidationException if signature verification fails for some reason
196     */
197    public static boolean verifyUserAttributesRevocation(PGPUserAttributeSubpacketVector userAttributes,
198                                                         PGPSignature signature, PGPPublicKey primaryKey,
199                                                         Policy policy, Date validationDate)
200            throws SignatureValidationException {
201        return verifyUserAttributesRevocation(userAttributes, signature, primaryKey, primaryKey, policy, validationDate);
202    }
203
204    /**
205     * Verify a user-attributes revocation signature.
206     *
207     * @param userAttributes user-attributes
208     * @param signature revocation signature
209     * @param signingKey revocation key
210     * @param keyWithUserAttributes key that carries the user-attributes
211     * @param policy policy
212     * @param validationDate reference date for signature verification
213     * @return true if the revocation signature can be verified successfully
214     *
215     * @throws SignatureValidationException if signature verification fails for some reason
216     */
217    public static boolean verifyUserAttributesRevocation(PGPUserAttributeSubpacketVector userAttributes,
218                                                         PGPSignature signature, PGPPublicKey signingKey,
219                                                         PGPPublicKey keyWithUserAttributes, Policy policy,
220                                                         Date validationDate)
221            throws SignatureValidationException {
222        SignatureValidator.signatureIsOfType(SignatureType.CERTIFICATION_REVOCATION).verify(signature);
223        SignatureValidator.signatureStructureIsAcceptable(signingKey, policy).verify(signature);
224        SignatureValidator.signatureIsEffective(validationDate).verify(signature);
225        SignatureValidator.correctSignatureOverUserAttributes(userAttributes, keyWithUserAttributes, signingKey).verify(signature);
226
227        return true;
228    }
229
230    /**
231     * Verify a subkey binding signature.
232     *
233     * @param signature binding signature
234     * @param primaryKey primary key
235     * @param subkey subkey
236     * @param policy policy
237     * @param validationDate reference date for signature verification
238     * @return true if the binding signature can be verified successfully
239     *
240     * @throws SignatureValidationException if signature verification fails for some reason
241     */
242    public static boolean verifySubkeyBindingSignature(PGPSignature signature, PGPPublicKey primaryKey, PGPPublicKey subkey, Policy policy, Date validationDate)
243            throws SignatureValidationException {
244        SignatureValidator.signatureIsOfType(SignatureType.SUBKEY_BINDING).verify(signature);
245        SignatureValidator.signatureStructureIsAcceptable(primaryKey, policy).verify(signature);
246        SignatureValidator.signatureIsEffective(validationDate).verify(signature);
247        SignatureValidator.hasValidPrimaryKeyBindingSignatureIfRequired(primaryKey, subkey, policy, validationDate).verify(signature);
248        SignatureValidator.correctSubkeyBindingSignature(primaryKey, subkey).verify(signature);
249
250        return true;
251    }
252
253    /**
254     * Verify a subkey revocation signature.
255     *
256     * @param signature subkey revocation signature
257     * @param primaryKey primary key
258     * @param subkey subkey
259     * @param policy policy
260     * @param validationDate reference date for signature verification
261     * @return true if the subkey revocation signature can be verified successfully
262     *
263     * @throws SignatureValidationException if signature verification fails for some reason
264     */
265    public static boolean verifySubkeyBindingRevocation(PGPSignature signature, PGPPublicKey primaryKey, PGPPublicKey subkey, Policy policy, Date validationDate) throws SignatureValidationException {
266        SignatureValidator.signatureIsOfType(SignatureType.SUBKEY_REVOCATION).verify(signature);
267        SignatureValidator.signatureStructureIsAcceptable(primaryKey, policy).verify(signature);
268        SignatureValidator.signatureIsEffective(validationDate).verify(signature);
269        SignatureValidator.correctSignatureOverKey(primaryKey, subkey).verify(signature);
270
271        return true;
272    }
273
274    /**
275     * Verify a direct-key self-signature.
276     *
277     * @param signature signature
278     * @param primaryKey primary key
279     * @param policy policy
280     * @param validationDate reference date for signature verification
281     * @return true if the signature can be verified successfully
282     *
283     * @throws SignatureValidationException if signature verification fails for some reason
284     */
285    public static boolean verifyDirectKeySignature(PGPSignature signature, PGPPublicKey primaryKey, Policy policy, Date validationDate)
286            throws SignatureValidationException {
287        return verifyDirectKeySignature(signature, primaryKey, primaryKey, policy, validationDate);
288    }
289
290    /**
291     * Verify a direct-key signature.
292     *
293     * @param signature signature
294     * @param signingKey signing key
295     * @param signedKey signed key
296     * @param policy policy
297     * @param validationDate reference date for signature verification
298     * @return true if signature verification is successful
299     *
300     * @throws SignatureValidationException if signature verification fails for some reason
301     */
302    public static boolean verifyDirectKeySignature(PGPSignature signature, PGPPublicKey signingKey, PGPPublicKey signedKey, Policy policy, Date validationDate)
303            throws SignatureValidationException {
304        SignatureValidator.signatureIsOfType(SignatureType.DIRECT_KEY).verify(signature);
305        SignatureValidator.signatureStructureIsAcceptable(signingKey, policy).verify(signature);
306        SignatureValidator.signatureIsEffective(validationDate).verify(signature);
307        SignatureValidator.correctSignatureOverKey(signingKey, signedKey).verify(signature);
308
309        return true;
310    }
311
312    /**
313     * Verify a key revocation signature.
314     *
315     * @param signature signature
316     * @param primaryKey primary key
317     * @param policy policy
318     * @param validationDate reference date for signature verification
319     * @return true if signature verification is successful
320     *
321     * @throws SignatureValidationException if signature verification fails for some reason
322     */
323    public static boolean verifyKeyRevocationSignature(PGPSignature signature, PGPPublicKey primaryKey, Policy policy, Date validationDate)
324            throws SignatureValidationException {
325        SignatureValidator.signatureIsOfType(SignatureType.KEY_REVOCATION).verify(signature);
326        SignatureValidator.signatureStructureIsAcceptable(primaryKey, policy).verify(signature);
327        SignatureValidator.signatureIsEffective(validationDate).verify(signature);
328        SignatureValidator.correctSignatureOverKey(primaryKey, primaryKey).verify(signature);
329
330        return true;
331    }
332
333    /**
334     * Initialize a signature and verify it afterwards by updating it with the signed data.
335     *
336     * @param signature OpenPGP signature
337     * @param signedData input stream containing the signed data
338     * @param signingKey the key that created the signature
339     * @param policy policy
340     * @param validationDate reference date of signature verification
341     * @return true if the signature is successfully verified
342     *
343     * @throws SignatureValidationException if the signature verification fails for some reason
344     */
345    public static boolean verifyUninitializedSignature(PGPSignature signature, InputStream signedData, PGPPublicKey signingKey, Policy policy, Date validationDate) throws SignatureValidationException {
346        initializeSignatureAndUpdateWithSignedData(signature, signedData, signingKey);
347        return verifyInitializedSignature(signature, signingKey, policy, validationDate);
348    }
349
350    /**
351     * Initialize a signature and then update it with the signed data from the given {@link InputStream}.
352     *
353     * @param signature OpenPGP signature
354     * @param signedData input stream containing signed data
355     * @param signingKey key that created the signature
356     *
357     * @throws SignatureValidationException in case the signature cannot be verified for some reason
358     */
359    public static void initializeSignatureAndUpdateWithSignedData(PGPSignature signature, InputStream signedData, PGPPublicKey signingKey)
360            throws SignatureValidationException {
361        try {
362            signature.init(ImplementationFactory.getInstance().getPGPContentVerifierBuilderProvider(), signingKey);
363            int read;
364            byte[] buf = new byte[8192];
365            byte lastByte = -1;
366            while ((read = signedData.read(buf)) != -1) {
367                // If we previously omitted a newline, but the stream is not yet empty, add it now
368                if (lastByte == (byte) '\n') {
369                    signature.update(lastByte);
370                }
371                lastByte = buf[read - 1];
372
373                if (lastByte == (byte) '\n') {
374                    // if last byte in buffer is newline, omit it for now
375                    signature.update(buf, 0, read - 1);
376                } else {
377                    // otherwise, write buffer as usual
378                    signature.update(buf, 0, read);
379                }
380            }
381        } catch (PGPException e) {
382            throw new SignatureValidationException("Cannot init signature.", e);
383        } catch (IOException e) {
384            throw new SignatureValidationException("Cannot update signature.", e);
385        }
386    }
387
388    /**
389     * Verify an initialized signature.
390     * An initialized signature was already updated with the signed data.
391     *
392     * @param signature OpenPGP signature
393     * @param signingKey key that created the signature
394     * @param policy policy
395     * @param validationDate reference date for signature verification
396     * @return true if signature is verified successfully
397     *
398     * @throws SignatureValidationException if signature verification fails for some reason
399     */
400    public static boolean verifyInitializedSignature(PGPSignature signature, PGPPublicKey signingKey, Policy policy, Date validationDate)
401            throws SignatureValidationException {
402        SignatureValidator.wasPossiblyMadeByKey(signingKey).verify(signature);
403        SignatureValidator.signatureStructureIsAcceptable(signingKey, policy).verify(signature);
404        SignatureValidator.signatureIsEffective(validationDate).verify(signature);
405
406        try {
407            if (!signature.verify()) {
408                throw new SignatureValidationException("Signature is not correct.");
409            }
410            return true;
411        } catch (PGPException e) {
412            throw new SignatureValidationException("Could not verify signature correctness.", e);
413        }
414    }
415
416    public static boolean verifyOnePassSignature(PGPSignature signature, PGPPublicKey signingKey, OnePassSignatureCheck onePassSignature, Policy policy)
417            throws SignatureValidationException {
418        try {
419            SignatureValidator.wasPossiblyMadeByKey(signingKey).verify(signature);
420            SignatureValidator.signatureStructureIsAcceptable(signingKey, policy).verify(signature);
421            SignatureValidator.signatureIsEffective().verify(signature);
422        } catch (SignatureValidationException e) {
423            throw new SignatureValidationException("Signature is not valid: " + e.getMessage(), e);
424        }
425
426        try {
427            if (onePassSignature.getSignature() == null) {
428                throw new IllegalStateException("No comparison signature provided.");
429            }
430            if (!onePassSignature.getOnePassSignature().verify(signature)) {
431                throw new SignatureValidationException("Bad signature of key " + Long.toHexString(signingKey.getKeyID()));
432            }
433        } catch (PGPException e) {
434            throw new SignatureValidationException("Could not verify correctness of One-Pass-Signature: " + e.getMessage(), e);
435        }
436
437        return true;
438    }
439
440    /**
441     * Verify a signature (certification or revocation) over a user-id.
442     *
443     * @param userId user-id
444     * @param signature self-signature
445     * @param primaryKey primary key that created the signature
446     * @param policy policy
447     * @param validationDate reference date for signature verification
448     * @return true if the signature is successfully verified
449     *
450     * @throws SignatureValidationException if signature verification fails for some reason
451     */
452    public static boolean verifySignatureOverUserId(String userId, PGPSignature signature, PGPPublicKey primaryKey, Policy policy, Date validationDate)
453            throws SignatureValidationException {
454        return verifySignatureOverUserId(userId, signature, primaryKey, primaryKey, policy, validationDate);
455    }
456}