001// SPDX-FileCopyrightText: 2020 Paul Schaub <vanitasvitae@fsfe.org>, 2021 Flowcrypt a.s. 002// 003// SPDX-License-Identifier: Apache-2.0 004 005package org.pgpainless.key.info; 006 007import static org.pgpainless.util.CollectionUtils.iteratorToList; 008 009import java.util.ArrayList; 010import java.util.Collections; 011import java.util.Date; 012import java.util.HashMap; 013import java.util.HashSet; 014import java.util.Iterator; 015import java.util.List; 016import java.util.Map; 017import java.util.NoSuchElementException; 018import java.util.Set; 019import java.util.regex.Matcher; 020import java.util.regex.Pattern; 021import javax.annotation.Nonnull; 022import javax.annotation.Nullable; 023 024import org.bouncycastle.bcpg.sig.PrimaryUserID; 025import org.bouncycastle.bcpg.sig.RevocationReason; 026import org.bouncycastle.openpgp.PGPKeyRing; 027import org.bouncycastle.openpgp.PGPPublicKey; 028import org.bouncycastle.openpgp.PGPPublicKeyRing; 029import org.bouncycastle.openpgp.PGPSecretKey; 030import org.bouncycastle.openpgp.PGPSecretKeyRing; 031import org.bouncycastle.openpgp.PGPSignature; 032import org.pgpainless.PGPainless; 033import org.pgpainless.algorithm.CompressionAlgorithm; 034import org.pgpainless.algorithm.EncryptionPurpose; 035import org.pgpainless.algorithm.HashAlgorithm; 036import org.pgpainless.algorithm.KeyFlag; 037import org.pgpainless.algorithm.PublicKeyAlgorithm; 038import org.pgpainless.algorithm.SymmetricKeyAlgorithm; 039import org.pgpainless.exception.KeyValidationError; 040import org.pgpainless.key.OpenPgpFingerprint; 041import org.pgpainless.key.SubkeyIdentifier; 042import org.pgpainless.key.util.RevocationAttributes; 043import org.pgpainless.policy.Policy; 044import org.pgpainless.signature.SignatureUtils; 045import org.pgpainless.signature.consumer.SignaturePicker; 046import org.pgpainless.signature.subpackets.SignatureSubpacketsUtil; 047 048/** 049 * Utility class to quickly extract certain information from a {@link PGPPublicKeyRing}/{@link PGPSecretKeyRing}. 050 */ 051public class KeyRingInfo { 052 053 private static final Pattern PATTERN_EMAIL = Pattern.compile("[A-Z0-9a-z._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,6}"); 054 055 private final PGPKeyRing keys; 056 private final Signatures signatures; 057 private final Date evaluationDate; 058 private final String primaryUserId; 059 060 /** 061 * Evaluate the key ring at creation time of the given signature. 062 * 063 * @param keyRing key ring 064 * @param signature signature 065 * @return info of key ring at signature creation time 066 */ 067 public static KeyRingInfo evaluateForSignature(PGPKeyRing keyRing, PGPSignature signature) { 068 return new KeyRingInfo(keyRing, signature.getCreationTime()); 069 } 070 071 /** 072 * Evaluate the key ring right now. 073 * 074 * @param keys key ring 075 */ 076 public KeyRingInfo(PGPKeyRing keys) { 077 this(keys, new Date()); 078 } 079 080 /** 081 * Evaluate the key ring at the provided validation date. 082 * 083 * @param keys key ring 084 * @param validationDate date of validation 085 */ 086 public KeyRingInfo(PGPKeyRing keys, Date validationDate) { 087 this.keys = keys; 088 this.signatures = new Signatures(keys, validationDate, PGPainless.getPolicy()); 089 this.evaluationDate = validationDate; 090 this.primaryUserId = findPrimaryUserId(); 091 } 092 093 /** 094 * Return the first {@link PGPPublicKey} of this key ring. 095 * 096 * @return public key 097 */ 098 public PGPPublicKey getPublicKey() { 099 return keys.getPublicKey(); 100 } 101 102 /** 103 * Return the public key with the given fingerprint. 104 * 105 * @param fingerprint fingerprint 106 * @return public key or null 107 */ 108 public @Nullable PGPPublicKey getPublicKey(OpenPgpFingerprint fingerprint) { 109 return getPublicKey(fingerprint.getKeyId()); 110 } 111 112 /** 113 * Return the public key with the given key id. 114 * 115 * @param keyId key id 116 * @return public key or null 117 */ 118 public @Nullable PGPPublicKey getPublicKey(long keyId) { 119 return getPublicKey(keys, keyId); 120 } 121 122 /** 123 * Return the public key with the given key id from the provided key ring. 124 * 125 * @param keyRing key ring 126 * @param keyId key id 127 * @return public key or null 128 */ 129 public static @Nullable PGPPublicKey getPublicKey(PGPKeyRing keyRing, long keyId) { 130 return keyRing.getPublicKey(keyId); 131 } 132 133 /** 134 * Return true if the public key with the given key id is bound to the key ring properly. 135 * 136 * @param keyId key id 137 * @return true if key is bound validly 138 */ 139 public boolean isKeyValidlyBound(long keyId) { 140 PGPPublicKey publicKey = keys.getPublicKey(keyId); 141 if (publicKey == null) { 142 return false; 143 } 144 145 if (publicKey == getPublicKey()) { 146 if (signatures.primaryKeyRevocation != null && SignatureUtils.isHardRevocation(signatures.primaryKeyRevocation)) { 147 return false; 148 } 149 return signatures.primaryKeyRevocation == null; 150 } 151 152 PGPSignature binding = signatures.subkeyBindings.get(keyId); 153 PGPSignature revocation = signatures.subkeyRevocations.get(keyId); 154 155 // No valid binding 156 if (binding == null || SignatureUtils.isSignatureExpired(binding)) { 157 return false; 158 } 159 160 // Revocation 161 if (revocation != null) { 162 if (SignatureUtils.isHardRevocation(revocation)) { 163 // Subkey is hard revoked 164 return false; 165 } else { 166 // Key is soft-revoked, not yet re-bound 167 return SignatureUtils.isSignatureExpired(revocation) 168 || !revocation.getCreationTime().after(binding.getCreationTime()); 169 } 170 } 171 172 return true; 173 } 174 175 /** 176 * Return all {@link PGPPublicKey PGPPublicKeys} of this key ring. 177 * The first key in the list being the primary key. 178 * Note that the list is unmodifiable. 179 * 180 * @return list of public keys 181 */ 182 public List<PGPPublicKey> getPublicKeys() { 183 Iterator<PGPPublicKey> iterator = keys.getPublicKeys(); 184 List<PGPPublicKey> list = iteratorToList(iterator); 185 return Collections.unmodifiableList(list); 186 } 187 188 /** 189 * Return the primary {@link PGPSecretKey} of this key ring or null if the key ring is not a {@link PGPSecretKeyRing}. 190 * 191 * @return primary secret key or null if the key ring is public 192 */ 193 public @Nullable PGPSecretKey getSecretKey() { 194 if (keys instanceof PGPSecretKeyRing) { 195 PGPSecretKeyRing secretKeys = (PGPSecretKeyRing) keys; 196 return secretKeys.getSecretKey(); 197 } 198 return null; 199 } 200 201 /** 202 * Return the secret key with the given fingerprint. 203 * 204 * @param fingerprint fingerprint 205 * @return secret key or null 206 */ 207 public @Nullable PGPSecretKey getSecretKey(OpenPgpFingerprint fingerprint) { 208 return getSecretKey(fingerprint.getKeyId()); 209 } 210 211 /** 212 * Return the secret key with the given key id. 213 * 214 * @param keyId key id 215 * @return secret key or null 216 */ 217 public @Nullable PGPSecretKey getSecretKey(long keyId) { 218 if (keys instanceof PGPSecretKeyRing) { 219 return ((PGPSecretKeyRing) keys).getSecretKey(keyId); 220 } 221 return null; 222 } 223 224 /** 225 * Return all secret keys of the key ring. 226 * If the key ring is a {@link PGPPublicKeyRing}, then return an empty list. 227 * Note that the list is unmodifiable. 228 * 229 * @return list of secret keys 230 */ 231 public List<PGPSecretKey> getSecretKeys() { 232 if (keys instanceof PGPSecretKeyRing) { 233 PGPSecretKeyRing secretKeys = (PGPSecretKeyRing) keys; 234 Iterator<PGPSecretKey> iterator = secretKeys.getSecretKeys(); 235 return Collections.unmodifiableList(iteratorToList(iterator)); 236 } 237 return Collections.emptyList(); 238 } 239 240 /** 241 * Return the key id of the primary key of this key ring. 242 * 243 * @return key id 244 */ 245 public long getKeyId() { 246 return getPublicKey().getKeyID(); 247 } 248 249 /** 250 * Return the {@link OpenPgpFingerprint} of this key ring. 251 * 252 * @return fingerprint 253 */ 254 public OpenPgpFingerprint getFingerprint() { 255 return OpenPgpFingerprint.of(getPublicKey()); 256 } 257 258 public @Nullable String getPrimaryUserId() { 259 return primaryUserId; 260 } 261 262 /** 263 * Return the current primary user-id of the key ring. 264 * 265 * Note: If no user-id is marked as primary key using a {@link PrimaryUserID} packet, 266 * this method returns the first user-id on the key, otherwise null. 267 * 268 * @return primary user-id or null 269 */ 270 private String findPrimaryUserId() { 271 String primaryUserId = null; 272 Date currentModificationDate = null; 273 274 List<String> userIds = getUserIds(); 275 if (userIds.isEmpty()) { 276 return null; 277 } 278 279 String firstUserId = userIds.get(0); 280 if (userIds.size() == 1) { 281 return firstUserId; 282 } 283 284 for (String userId : userIds) { 285 PGPSignature certification = signatures.userIdCertifications.get(userId); 286 if (certification == null) { 287 continue; 288 } 289 Date creationTime = certification.getCreationTime(); 290 291 if (certification.getHashedSubPackets().isPrimaryUserID()) { 292 if (currentModificationDate == null || creationTime.after(currentModificationDate)) { 293 primaryUserId = userId; 294 currentModificationDate = creationTime; 295 } 296 297 } 298 } 299 300 if (primaryUserId != null) { 301 return primaryUserId; 302 } 303 304 return firstUserId; 305 } 306 307 /** 308 * Return a list of all user-ids of the primary key. 309 * Note: This list might also contain expired / revoked user-ids. 310 * Consider using {@link #getValidUserIds()} instead. 311 * 312 * @return list of user-ids 313 */ 314 public List<String> getUserIds() { 315 Iterator<String> iterator = getPublicKey().getUserIDs(); 316 List<String> userIds = iteratorToList(iterator); 317 return userIds; 318 } 319 320 /** 321 * Return a list of valid user-ids. 322 * 323 * @return valid user-ids 324 */ 325 public List<String> getValidUserIds() { 326 List<String> valid = new ArrayList<>(); 327 List<String> userIds = getUserIds(); 328 for (String userId : userIds) { 329 if (isUserIdBound(userId)) { 330 valid.add(userId); 331 } 332 } 333 return valid; 334 } 335 336 /** 337 * Return a list of all user-ids that were valid at some point, but might be expired by now. 338 * 339 * @return bound user-ids 340 */ 341 public List<String> getValidAndExpiredUserIds() { 342 List<String> probablyExpired = new ArrayList<>(); 343 List<String> userIds = getUserIds(); 344 345 for (String userId : userIds) { 346 PGPSignature certification = signatures.userIdCertifications.get(userId); 347 PGPSignature revocation = signatures.userIdRevocations.get(userId); 348 349 // Not revoked -> valid 350 if (revocation == null) { 351 probablyExpired.add(userId); 352 continue; 353 } 354 355 // Hard revocation -> invalid 356 if (SignatureUtils.isHardRevocation(revocation)) { 357 continue; 358 } 359 360 // Soft revocation -> valid if certification is newer than revocation (revalidation) 361 if (certification.getCreationTime().after(revocation.getCreationTime())) { 362 probablyExpired.add(userId); 363 } 364 } 365 return probablyExpired; 366 } 367 368 /** 369 * Return true if the provided user-id is valid. 370 * 371 * @param userId user-id 372 * @return true if user-id is valid 373 */ 374 public boolean isUserIdValid(String userId) { 375 if (!userId.equals(primaryUserId)) { 376 if (!isUserIdBound(primaryUserId)) { 377 // primary user-id not valid 378 return false; 379 } 380 } 381 return isUserIdBound(userId); 382 } 383 384 385 private boolean isUserIdBound(String userId) { 386 387 PGPSignature certification = signatures.userIdCertifications.get(userId); 388 PGPSignature revocation = signatures.userIdRevocations.get(userId); 389 390 if (certification == null) { 391 return false; 392 } 393 if (SignatureUtils.isSignatureExpired(certification)) { 394 return false; 395 } 396 if (certification.getHashedSubPackets().isPrimaryUserID()) { 397 Date keyExpiration = SignatureSubpacketsUtil.getKeyExpirationTimeAsDate(certification, keys.getPublicKey()); 398 if (keyExpiration != null && evaluationDate.after(keyExpiration)) { 399 return false; 400 } 401 } 402 // Not revoked -> valid 403 if (revocation == null) { 404 return true; 405 } 406 // Hard revocation -> invalid 407 if (SignatureUtils.isHardRevocation(revocation)) { 408 return false; 409 } 410 // Soft revocation -> valid if certification is newer than revocation (revalidation) 411 return certification.getCreationTime().after(revocation.getCreationTime()); 412 } 413 414 /** 415 * Return a list of all user-ids of the primary key that appear to be email-addresses. 416 * Note: This list might contain expired / revoked user-ids. 417 * 418 * @return email addresses 419 */ 420 public List<String> getEmailAddresses() { 421 List<String> userIds = getUserIds(); 422 List<String> emails = new ArrayList<>(); 423 for (String userId : userIds) { 424 Matcher matcher = PATTERN_EMAIL.matcher(userId); 425 if (matcher.find()) { 426 emails.add(matcher.group()); 427 } 428 } 429 return emails; 430 } 431 432 /** 433 * Return the latest direct-key self signature. 434 * 435 * Note: This signature might be expired (check with {@link SignatureUtils#isSignatureExpired(PGPSignature)}). 436 * 437 * @return latest direct key self-signature or null 438 */ 439 public @Nullable PGPSignature getLatestDirectKeySelfSignature() { 440 return signatures.primaryKeySelfSignature; 441 } 442 443 /** 444 * Return the latest revocation self-signature on the primary key. 445 * 446 * @return revocation or null 447 */ 448 public @Nullable PGPSignature getRevocationSelfSignature() { 449 return signatures.primaryKeyRevocation; 450 } 451 452 /** 453 * Return the latest certification self-signature on the provided user-id. 454 * 455 * @param userId user-id 456 * @return certification signature or null 457 */ 458 public @Nullable PGPSignature getLatestUserIdCertification(String userId) { 459 return signatures.userIdCertifications.get(userId); 460 } 461 462 /** 463 * Return the latest user-id revocation signature for the provided user-id. 464 * 465 * @param userId user-id 466 * @return revocation or null 467 */ 468 public @Nullable PGPSignature getUserIdRevocation(String userId) { 469 return signatures.userIdRevocations.get(userId); 470 } 471 472 /** 473 * Return the currently active subkey binding signature for the subkey with the provided key-id. 474 * 475 * @param keyId subkey id 476 * @return subkey binding signature or null 477 */ 478 public @Nullable PGPSignature getCurrentSubkeyBindingSignature(long keyId) { 479 return signatures.subkeyBindings.get(keyId); 480 } 481 482 /** 483 * Return the latest subkey binding revocation signature for the subkey with the given key-id. 484 * 485 * @param keyId subkey id 486 * @return subkey binding revocation or null 487 */ 488 public @Nullable PGPSignature getSubkeyRevocationSignature(long keyId) { 489 return signatures.subkeyRevocations.get(keyId); 490 } 491 492 /** 493 * Return a list of {@link KeyFlag KeyFlags} that apply to the subkey with the provided key id. 494 * @param keyId key-id 495 * @return list of key flags 496 */ 497 public @Nonnull List<KeyFlag> getKeyFlagsOf(long keyId) { 498 // key is primary key 499 if (getPublicKey().getKeyID() == keyId) { 500 501 PGPSignature directKeySignature = getLatestDirectKeySelfSignature(); 502 if (directKeySignature != null) { 503 List<KeyFlag> keyFlags = SignatureSubpacketsUtil.parseKeyFlags(directKeySignature); 504 if (keyFlags != null) { 505 return keyFlags; 506 } 507 } 508 509 String primaryUserId = getPrimaryUserId(); 510 if (primaryUserId != null) { 511 PGPSignature userIdSignature = getLatestUserIdCertification(primaryUserId); 512 List<KeyFlag> keyFlags = SignatureSubpacketsUtil.parseKeyFlags(userIdSignature); 513 if (keyFlags != null) { 514 return keyFlags; 515 } 516 } 517 } 518 // Key is subkey 519 else { 520 PGPSignature bindingSignature = getCurrentSubkeyBindingSignature(keyId); 521 if (bindingSignature != null) { 522 List<KeyFlag> keyFlags = SignatureSubpacketsUtil.parseKeyFlags(bindingSignature); 523 if (keyFlags != null) { 524 return keyFlags; 525 } 526 } 527 } 528 return Collections.emptyList(); 529 } 530 531 /** 532 * Return a list of {@link KeyFlag KeyFlags} that apply to the given user-id. 533 * 534 * @param userId user-id 535 * @return key flags 536 */ 537 public @Nonnull List<KeyFlag> getKeyFlagsOf(String userId) { 538 if (!isUserIdValid(userId)) { 539 return Collections.emptyList(); 540 } 541 542 PGPSignature userIdCertification = getLatestUserIdCertification(userId); 543 if (userIdCertification == null) { 544 throw new AssertionError("While user-id '" + userId + "' was reported as valid, there appears to be no certification for it."); 545 } 546 547 List<KeyFlag> keyFlags = SignatureSubpacketsUtil.parseKeyFlags(userIdCertification); 548 if (keyFlags != null) { 549 return keyFlags; 550 } 551 return Collections.emptyList(); 552 } 553 554 /** 555 * Return the algorithm of the primary key. 556 * 557 * @return public key algorithm 558 */ 559 public PublicKeyAlgorithm getAlgorithm() { 560 return PublicKeyAlgorithm.fromId(getPublicKey().getAlgorithm()); 561 } 562 563 /** 564 * Return the creation date of the primary key. 565 * 566 * @return creation date 567 */ 568 public Date getCreationDate() { 569 return getPublicKey().getCreationTime(); 570 } 571 572 /** 573 * Return the date on which the key ring was last modified. 574 * This date corresponds to the date of the last signature that was made on this key ring by the primary key. 575 * 576 * @return last modification date. 577 */ 578 public @Nullable Date getLastModified() { 579 PGPSignature mostRecent = getMostRecentSignature(); 580 if (mostRecent == null) { 581 // No sigs found. Return public key creation date instead. 582 return getLatestKeyCreationDate(); 583 } 584 return mostRecent.getCreationTime(); 585 } 586 587 /** 588 * Return the creation time of the latest added subkey. 589 * 590 * @return latest key creation time 591 */ 592 public @Nonnull Date getLatestKeyCreationDate() { 593 Date latestCreation = null; 594 for (PGPPublicKey key : getPublicKeys()) { 595 if (!isKeyValidlyBound(key.getKeyID())) { 596 continue; 597 } 598 Date keyCreation = key.getCreationTime(); 599 if (latestCreation == null || latestCreation.before(keyCreation)) { 600 latestCreation = keyCreation; 601 } 602 } 603 if (latestCreation == null) { 604 throw new AssertionError("Apparently there is no validly bound key in this key ring."); 605 } 606 return latestCreation; 607 } 608 609 private @Nullable PGPSignature getMostRecentSignature() { 610 Set<PGPSignature> allSignatures = new HashSet<>(); 611 PGPSignature mostRecentSelfSignature = getLatestDirectKeySelfSignature(); 612 PGPSignature revocationSelfSignature = getRevocationSelfSignature(); 613 if (mostRecentSelfSignature != null) allSignatures.add(mostRecentSelfSignature); 614 if (revocationSelfSignature != null) allSignatures.add(revocationSelfSignature); 615 allSignatures.addAll(signatures.userIdCertifications.values()); 616 allSignatures.addAll(signatures.userIdRevocations.values()); 617 allSignatures.addAll(signatures.subkeyBindings.values()); 618 allSignatures.addAll(signatures.subkeyRevocations.values()); 619 620 PGPSignature mostRecent = null; 621 for (PGPSignature signature : allSignatures) { 622 if (mostRecent == null || signature.getCreationTime().after(mostRecent.getCreationTime())) { 623 mostRecent = signature; 624 } 625 } 626 return mostRecent; 627 } 628 629 /** 630 * Return the date on which the primary key was revoked, or null if it has not yet been revoked. 631 * 632 * @return revocation date or null 633 */ 634 public @Nullable Date getRevocationDate() { 635 return getRevocationSelfSignature() == null ? null : getRevocationSelfSignature().getCreationTime(); 636 } 637 638 /** 639 * Return the date of expiration of the primary key or null if the key has no expiration date. 640 * 641 * @return expiration date 642 */ 643 public @Nullable Date getPrimaryKeyExpirationDate() { 644 PGPSignature directKeySig = getLatestDirectKeySelfSignature(); 645 Date directKeyExpirationDate = null; 646 if (directKeySig != null) { 647 directKeyExpirationDate = SignatureSubpacketsUtil.getKeyExpirationTimeAsDate(directKeySig, getPublicKey()); 648 } 649 650 PGPSignature primaryUserIdCertification = null; 651 Date userIdExpirationDate = null; 652 String possiblyExpiredPrimaryUserId = getPossiblyExpiredPrimaryUserId(); 653 if (possiblyExpiredPrimaryUserId != null) { 654 primaryUserIdCertification = getLatestUserIdCertification(possiblyExpiredPrimaryUserId); 655 if (primaryUserIdCertification != null) { 656 userIdExpirationDate = SignatureSubpacketsUtil.getKeyExpirationTimeAsDate(primaryUserIdCertification, getPublicKey()); 657 } 658 } 659 660 if (directKeySig == null && primaryUserIdCertification == null) { 661 throw new NoSuchElementException("No direct-key signature and no user-id signature found."); 662 } 663 664 if (directKeyExpirationDate != null && userIdExpirationDate == null) { 665 return directKeyExpirationDate; 666 } 667 668 if (directKeyExpirationDate == null) { 669 return userIdExpirationDate; 670 } 671 672 if (directKeyExpirationDate.before(userIdExpirationDate)) { 673 return directKeyExpirationDate; 674 } 675 676 return userIdExpirationDate; 677 } 678 679 public String getPossiblyExpiredPrimaryUserId() { 680 String validPrimaryUserId = getPrimaryUserId(); 681 if (validPrimaryUserId != null) { 682 return validPrimaryUserId; 683 } 684 685 Date latestCreationTime = null; 686 String primaryUserId = null; 687 boolean foundPrimary = false; 688 for (String userId : getUserIds()) { 689 PGPSignature signature = getLatestUserIdCertification(userId); 690 if (signature == null) { 691 continue; 692 } 693 694 boolean isPrimary = signature.getHashedSubPackets().isPrimaryUserID(); 695 if (foundPrimary && !isPrimary) { 696 continue; 697 } 698 699 Date creationTime = signature.getCreationTime(); 700 if (latestCreationTime == null || creationTime.after(latestCreationTime) || isPrimary && !foundPrimary) { 701 latestCreationTime = creationTime; 702 primaryUserId = userId; 703 } 704 705 foundPrimary |= isPrimary; 706 } 707 708 return primaryUserId; 709 } 710 711 /** 712 * Return the expiration date of the subkey with the provided fingerprint. 713 * 714 * @param fingerprint subkey fingerprint 715 * @return expiration date or null 716 */ 717 public @Nullable Date getSubkeyExpirationDate(OpenPgpFingerprint fingerprint) { 718 if (getPublicKey().getKeyID() == fingerprint.getKeyId()) { 719 return getPrimaryKeyExpirationDate(); 720 } 721 722 PGPPublicKey subkey = getPublicKey(fingerprint.getKeyId()); 723 if (subkey == null) { 724 throw new NoSuchElementException("No subkey with fingerprint " + fingerprint + " found."); 725 } 726 727 PGPSignature bindingSig = getCurrentSubkeyBindingSignature(fingerprint.getKeyId()); 728 if (bindingSig == null) { 729 throw new AssertionError("Subkey has no valid binding signature."); 730 } 731 732 return SignatureUtils.getKeyExpirationDate(subkey.getCreationTime(), bindingSig); 733 } 734 735 /** 736 * Return the latest date on which the key ring is still usable for the given key flag. 737 * If only a subkey is carrying the required flag and the primary key expires earlier than the subkey, 738 * the expiry date of the primary key is returned. 739 * 740 * This method might return null, if the primary key and a subkey with the required flag does not expire. 741 * @param use key flag representing the use case, e.g. {@link KeyFlag#SIGN_DATA} or 742 * {@link KeyFlag#ENCRYPT_COMMS}/{@link KeyFlag#ENCRYPT_STORAGE}. 743 * @return latest date on which the key ring can be used for the given use case, or null if it can be used indefinitely. 744 */ 745 public Date getExpirationDateForUse(KeyFlag use) { 746 if (use == KeyFlag.SPLIT || use == KeyFlag.SHARED) { 747 throw new IllegalArgumentException("SPLIT and SHARED are not uses, but properties."); 748 } 749 750 Date primaryExpiration = getPrimaryKeyExpirationDate(); 751 List<PGPPublicKey> nonExpiringSubkeys = new ArrayList<>(); 752 Date latestSubkeyExpirationDate = null; 753 754 List<PGPPublicKey> keysWithFlag = getKeysWithKeyFlag(use); 755 if (keysWithFlag.isEmpty()) { 756 throw new NoSuchElementException("No key with the required key flag found."); 757 } 758 759 for (PGPPublicKey key : keysWithFlag) { 760 Date subkeyExpirationDate = getSubkeyExpirationDate(OpenPgpFingerprint.of(key)); 761 if (subkeyExpirationDate == null) { 762 nonExpiringSubkeys.add(key); 763 } else { 764 if (latestSubkeyExpirationDate == null || subkeyExpirationDate.after(latestSubkeyExpirationDate)) { 765 latestSubkeyExpirationDate = subkeyExpirationDate; 766 } 767 } 768 } 769 770 if (nonExpiringSubkeys.isEmpty()) { 771 if (latestSubkeyExpirationDate != null) { 772 if (primaryExpiration == null) { 773 return latestSubkeyExpirationDate; 774 } 775 if (latestSubkeyExpirationDate.before(primaryExpiration)) { 776 return latestSubkeyExpirationDate; 777 } 778 } 779 } 780 return primaryExpiration; 781 } 782 783 public boolean isHardRevoked(String userId) { 784 PGPSignature revocation = signatures.userIdRevocations.get(userId); 785 if (revocation == null) { 786 return false; 787 } 788 RevocationReason revocationReason = revocation.getHashedSubPackets().getRevocationReason(); 789 return revocationReason == null || RevocationAttributes.Reason.isHardRevocation(revocationReason.getRevocationReason()); 790 } 791 792 /** 793 * Return true if the key ring is a {@link PGPSecretKeyRing}. 794 * If it is a {@link PGPPublicKeyRing} return false and if it is neither, throw an {@link AssertionError}. 795 * 796 * @return true if the key ring is a secret key ring. 797 */ 798 public boolean isSecretKey() { 799 if (keys instanceof PGPSecretKeyRing) { 800 return true; 801 } else if (keys instanceof PGPPublicKeyRing) { 802 return false; 803 } else { 804 throw new AssertionError("Expected PGPKeyRing to be either PGPPublicKeyRing or PGPSecretKeyRing, but got " + keys.getClass().getName() + " instead."); 805 } 806 } 807 808 /** 809 * Returns true when every secret key on the key ring is not encrypted. 810 * If there is at least one encrypted secret key on the key ring, returns false. 811 * If the key ring is a {@link PGPPublicKeyRing}, returns true. 812 * Sub-keys with S2K of a type GNU_DUMMY_S2K do not affect the result. 813 * 814 * @return true if all secret keys are unencrypted. 815 */ 816 public boolean isFullyDecrypted() { 817 if (!isSecretKey()) { 818 return true; 819 } 820 for (PGPSecretKey secretKey : getSecretKeys()) { 821 if (!KeyInfo.hasDummyS2K(secretKey) && KeyInfo.isEncrypted(secretKey)) { 822 return false; 823 } 824 } 825 return true; 826 } 827 828 /** 829 * Returns true when every secret key on the key ring is encrypted. 830 * If there is at least one not encrypted secret key on the key ring, returns false. 831 * If the key ring is a {@link PGPPublicKeyRing}, returns false. 832 * Sub-keys with S2K of a type GNU_DUMMY_S2K do not affect a result. 833 * 834 * @return true if all secret keys are encrypted. 835 */ 836 public boolean isFullyEncrypted() { 837 if (!isSecretKey()) { 838 return false; 839 } 840 for (PGPSecretKey secretKey : getSecretKeys()) { 841 if (!KeyInfo.hasDummyS2K(secretKey) && KeyInfo.isDecrypted(secretKey)) { 842 return false; 843 } 844 } 845 return true; 846 } 847 848 /** 849 * Return the version number of the public keys format. 850 * 851 * @return version 852 */ 853 public int getVersion() { 854 return keys.getPublicKey().getVersion(); 855 } 856 857 /** 858 * Return a list of all subkeys which can be used for encryption of the given purpose. 859 * This list does not include expired or revoked keys. 860 * 861 * @param purpose purpose (encrypt data at rest / communications) 862 * @return encryption subkeys 863 */ 864 public @Nonnull List<PGPPublicKey> getEncryptionSubkeys(EncryptionPurpose purpose) { 865 Date primaryExpiration = getPrimaryKeyExpirationDate(); 866 if (primaryExpiration != null && primaryExpiration.before(new Date())) { 867 return Collections.emptyList(); 868 } 869 870 Iterator<PGPPublicKey> subkeys = keys.getPublicKeys(); 871 List<PGPPublicKey> encryptionKeys = new ArrayList<>(); 872 while (subkeys.hasNext()) { 873 PGPPublicKey subKey = subkeys.next(); 874 875 if (!isKeyValidlyBound(subKey.getKeyID())) { 876 continue; 877 } 878 879 Date subkeyExpiration = getSubkeyExpirationDate(OpenPgpFingerprint.of(subKey)); 880 if (subkeyExpiration != null && subkeyExpiration.before(new Date())) { 881 continue; 882 } 883 884 if (!subKey.isEncryptionKey()) { 885 continue; 886 } 887 888 List<KeyFlag> keyFlags = getKeyFlagsOf(subKey.getKeyID()); 889 switch (purpose) { 890 case COMMUNICATIONS: 891 if (keyFlags.contains(KeyFlag.ENCRYPT_COMMS)) { 892 encryptionKeys.add(subKey); 893 } 894 break; 895 case STORAGE: 896 if (keyFlags.contains(KeyFlag.ENCRYPT_STORAGE)) { 897 encryptionKeys.add(subKey); 898 } 899 break; 900 case ANY: 901 if (keyFlags.contains(KeyFlag.ENCRYPT_COMMS) || keyFlags.contains(KeyFlag.ENCRYPT_STORAGE)) { 902 encryptionKeys.add(subKey); 903 } 904 break; 905 } 906 } 907 return encryptionKeys; 908 } 909 910 /** 911 * Return a list of all keys which carry the provided key flag in their signature. 912 * 913 * @param flag flag 914 * @return keys with flag 915 */ 916 public List<PGPPublicKey> getKeysWithKeyFlag(KeyFlag flag) { 917 List<PGPPublicKey> keysWithFlag = new ArrayList<>(); 918 for (PGPPublicKey key : getPublicKeys()) { 919 List<KeyFlag> keyFlags = getKeyFlagsOf(key.getKeyID()); 920 if (keyFlags.contains(flag)) { 921 keysWithFlag.add(key); 922 } 923 } 924 925 return keysWithFlag; 926 } 927 928 /** 929 * Return a list of all subkeys that can be used for encryption with the given user-id. 930 * This list does not include expired or revoked keys. 931 * TODO: Does it make sense to pass in a user-id? 932 * Aren't the encryption subkeys the same, regardless of which user-id is used? 933 * 934 * @param userId user-id 935 * @param purpose encryption purpose 936 * @return encryption subkeys 937 */ 938 public @Nonnull List<PGPPublicKey> getEncryptionSubkeys(String userId, EncryptionPurpose purpose) { 939 if (userId != null && !isUserIdValid(userId)) { 940 throw new KeyValidationError(userId, getLatestUserIdCertification(userId), getUserIdRevocation(userId)); 941 } 942 943 return getEncryptionSubkeys(purpose); 944 } 945 946 /** 947 * Return a list of all subkeys which can be used to sign data. 948 * 949 * @return signing keys 950 */ 951 public @Nonnull List<PGPPublicKey> getSigningSubkeys() { 952 Iterator<PGPPublicKey> subkeys = keys.getPublicKeys(); 953 List<PGPPublicKey> signingKeys = new ArrayList<>(); 954 while (subkeys.hasNext()) { 955 PGPPublicKey subKey = subkeys.next(); 956 957 if (!isKeyValidlyBound(subKey.getKeyID())) { 958 continue; 959 } 960 961 List<KeyFlag> keyFlags = getKeyFlagsOf(subKey.getKeyID()); 962 if (keyFlags.contains(KeyFlag.SIGN_DATA)) { 963 signingKeys.add(subKey); 964 } 965 } 966 return signingKeys; 967 } 968 969 public Set<HashAlgorithm> getPreferredHashAlgorithms() { 970 return getPreferredHashAlgorithms(getPrimaryUserId()); 971 } 972 973 public Set<HashAlgorithm> getPreferredHashAlgorithms(String userId) { 974 return getKeyAccessor(userId, getKeyId()).getPreferredHashAlgorithms(); 975 } 976 977 public Set<HashAlgorithm> getPreferredHashAlgorithms(long keyId) { 978 return getKeyAccessor(null, keyId).getPreferredHashAlgorithms(); 979 } 980 981 public Set<SymmetricKeyAlgorithm> getPreferredSymmetricKeyAlgorithms() { 982 return getPreferredSymmetricKeyAlgorithms(getPrimaryUserId()); 983 } 984 985 public Set<SymmetricKeyAlgorithm> getPreferredSymmetricKeyAlgorithms(String userId) { 986 return getKeyAccessor(userId, getKeyId()).getPreferredSymmetricKeyAlgorithms(); 987 } 988 989 public Set<SymmetricKeyAlgorithm> getPreferredSymmetricKeyAlgorithms(long keyId) { 990 return getKeyAccessor(null, keyId).getPreferredSymmetricKeyAlgorithms(); 991 } 992 993 public Set<CompressionAlgorithm> getPreferredCompressionAlgorithms() { 994 return getPreferredCompressionAlgorithms(getPrimaryUserId()); 995 } 996 997 public Set<CompressionAlgorithm> getPreferredCompressionAlgorithms(String userId) { 998 return getKeyAccessor(userId, getKeyId()).getPreferredCompressionAlgorithms(); 999 } 1000 1001 public Set<CompressionAlgorithm> getPreferredCompressionAlgorithms(long keyId) { 1002 return getKeyAccessor(null, keyId).getPreferredCompressionAlgorithms(); 1003 } 1004 1005 private KeyAccessor getKeyAccessor(@Nullable String userId, long keyID) { 1006 if (getPublicKey(keyID) == null) { 1007 throw new IllegalArgumentException("No subkey with key id " + Long.toHexString(keyID) + " found on this key."); 1008 } 1009 if (userId != null && !getUserIds().contains(userId)) { 1010 throw new IllegalArgumentException("No user-id '" + userId + "' found on this key."); 1011 } 1012 return userId == null ? new KeyAccessor.ViaKeyId(this, new SubkeyIdentifier(keys, keyID)) 1013 : new KeyAccessor.ViaUserId(this, new SubkeyIdentifier(keys, keyID), userId); 1014 } 1015 1016 public static class Signatures { 1017 1018 private final PGPSignature primaryKeyRevocation; 1019 private final PGPSignature primaryKeySelfSignature; 1020 private final Map<String, PGPSignature> userIdRevocations; 1021 private final Map<String, PGPSignature> userIdCertifications; 1022 private final Map<Long, PGPSignature> subkeyRevocations; 1023 private final Map<Long, PGPSignature> subkeyBindings; 1024 1025 public Signatures(PGPKeyRing keyRing, Date evaluationDate, Policy policy) { 1026 primaryKeyRevocation = SignaturePicker.pickCurrentRevocationSelfSignature(keyRing, policy, evaluationDate); 1027 primaryKeySelfSignature = SignaturePicker.pickLatestDirectKeySignature(keyRing, policy, evaluationDate); 1028 userIdRevocations = new HashMap<>(); 1029 userIdCertifications = new HashMap<>(); 1030 subkeyRevocations = new HashMap<>(); 1031 subkeyBindings = new HashMap<>(); 1032 1033 for (Iterator<String> it = keyRing.getPublicKey().getUserIDs(); it.hasNext(); ) { 1034 String userId = it.next(); 1035 PGPSignature revocation = SignaturePicker.pickCurrentUserIdRevocationSignature(keyRing, userId, policy, evaluationDate); 1036 if (revocation != null) { 1037 userIdRevocations.put(userId, revocation); 1038 } 1039 PGPSignature certification = SignaturePicker.pickLatestUserIdCertificationSignature(keyRing, userId, policy, evaluationDate); 1040 if (certification != null) { 1041 userIdCertifications.put(userId, certification); 1042 } 1043 } 1044 1045 Iterator<PGPPublicKey> keys = keyRing.getPublicKeys(); 1046 keys.next(); // Skip primary key 1047 while (keys.hasNext()) { 1048 PGPPublicKey subkey = keys.next(); 1049 PGPSignature subkeyRevocation = SignaturePicker.pickCurrentSubkeyBindingRevocationSignature(keyRing, subkey, policy, evaluationDate); 1050 if (subkeyRevocation != null) { 1051 subkeyRevocations.put(subkey.getKeyID(), subkeyRevocation); 1052 } 1053 PGPSignature subkeyBinding = SignaturePicker.pickLatestSubkeyBindingSignature(keyRing, subkey, policy, evaluationDate); 1054 if (subkeyBinding != null) { 1055 subkeyBindings.put(subkey.getKeyID(), subkeyBinding); 1056 } 1057 } 1058 } 1059 } 1060}