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}