001/* 002 * nimbus-jose-jwt 003 * 004 * Copyright 2012-2016, Connect2id Ltd. 005 * 006 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use 007 * this file except in compliance with the License. You may obtain a copy of the 008 * License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, software distributed 013 * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 014 * CONDITIONS OF ANY KIND, either express or implied. See the License for the 015 * specific language governing permissions and limitations under the License. 016 */ 017 018package com.nimbusds.jose.jwk; 019 020 021import com.nimbusds.jose.Algorithm; 022import com.nimbusds.jose.JOSEException; 023import com.nimbusds.jose.util.Base64; 024import com.nimbusds.jose.util.*; 025import com.nimbusds.jwt.util.DateUtils; 026 027import java.io.Serializable; 028import java.net.URI; 029import java.security.*; 030import java.security.cert.X509Certificate; 031import java.security.interfaces.ECPrivateKey; 032import java.security.interfaces.ECPublicKey; 033import java.security.interfaces.RSAPrivateKey; 034import java.security.interfaces.RSAPublicKey; 035import java.security.spec.ECParameterSpec; 036import java.text.ParseException; 037import java.util.*; 038 039 040/** 041 * The base abstract class for JSON Web Keys (JWKs). It serialises to a JSON 042 * object. 043 * 044 * <p>The following JSON object members are common to all JWK types: 045 * 046 * <ul> 047 * <li>{@link #getKeyType kty} (required) 048 * <li>{@link #getKeyUse use} (optional) 049 * <li>{@link #getKeyOperations key_ops} (optional) 050 * <li>{@link #getKeyID kid} (optional) 051 * <li>{@link #getX509CertURL() x5u} (optional) 052 * <li>{@link #getX509CertThumbprint() x5t} (optional) 053 * <li>{@link #getX509CertSHA256Thumbprint() x5t#S256} (optional) 054 * <li>{@link #getX509CertChain() x5c} (optional) 055 * <li>{@link #getExpirationTime() exp} (optional) 056 * <li>{@link #getNotBeforeTime() nbf} (optional) 057 * <li>{@link #getIssueTime() iat} (optional) 058 * <li>{@link #getKeyRevocation() revoked} (optional) 059 * <li>{@link #getKeyStore()} 060 * </ul> 061 * 062 * <p>Example JWK (of the Elliptic Curve type): 063 * 064 * <pre> 065 * { 066 * "kty" : "EC", 067 * "crv" : "P-256", 068 * "x" : "MKBCTNIcKUSDii11ySs3526iDZ8AiTo7Tu6KPAqv7D4", 069 * "y" : "4Etl6SRW2YiLUrN5vfvVHuhp7x8PxltmWWlbbM4IFyM", 070 * "use" : "enc", 071 * "kid" : "1" 072 * } 073 * </pre> 074 * 075 * @author Vladimir Dzhuvinov 076 * @author Justin Richer 077 * @author Stefan Larsson 078 * @version 2024-10-31 079 */ 080public abstract class JWK implements Serializable { 081 082 083 private static final long serialVersionUID = 1L; 084 085 086 /** 087 * The MIME type of JWK objects: 088 * {@code application/jwk+json; charset=UTF-8} 089 */ 090 public static final String MIME_TYPE = "application/jwk+json; charset=UTF-8"; 091 092 093 /** 094 * The key type, required. 095 */ 096 private final KeyType kty; 097 098 099 /** 100 * The key use, optional. 101 */ 102 private final KeyUse use; 103 104 105 /** 106 * The key operations, optional. 107 */ 108 private final Set<KeyOperation> ops; 109 110 111 /** 112 * The intended JOSE algorithm for the key, optional. 113 */ 114 private final Algorithm alg; 115 116 117 /** 118 * The key ID, optional. 119 */ 120 private final String kid; 121 122 123 /** 124 * X.509 certificate URL, optional. 125 */ 126 private final URI x5u; 127 128 129 /** 130 * X.509 certificate SHA-1 thumbprint, optional. 131 */ 132 @Deprecated 133 private final Base64URL x5t; 134 135 136 /** 137 * X.509 certificate SHA-256 thumbprint, optional. 138 */ 139 private final Base64URL x5t256; 140 141 142 /** 143 * The X.509 certificate chain, optional. 144 */ 145 private final List<Base64> x5c; 146 147 148 /** 149 * The key expiration time, optional. 150 */ 151 private final Date exp; 152 153 154 /** 155 * The key not-before time, optional. 156 */ 157 private final Date nbf; 158 159 160 /** 161 * The key issued-at time, optional. 162 */ 163 private final Date iat; 164 165 166 /** 167 * The key revocation, optional. 168 */ 169 private final KeyRevocation revocation; 170 171 172 /** 173 * The parsed X.509 certificate chain, optional. 174 */ 175 private final List<X509Certificate> parsedX5c; 176 177 178 /** 179 * Reference to the underlying key store, {@code null} if none. 180 */ 181 private final KeyStore keyStore; 182 183 184 /** 185 * Creates a new JSON Web Key (JWK). 186 * 187 * @param kty The key type. Must not be {@code null}. 188 * @param use The key use, {@code null} if not specified or if the 189 * key is intended for signing as well as encryption. 190 * @param ops The key operations, {@code null} if not specified. 191 * @param alg The intended JOSE algorithm for the key, {@code null} 192 * if not specified. 193 * @param kid The key ID, {@code null} if not specified. 194 * @param x5u The X.509 certificate URL, {@code null} if not 195 * specified. 196 * @param x5t The X.509 certificate thumbprint, {@code null} if not 197 * specified. 198 * @param x5t256 The X.509 certificate SHA-256 thumbprint, {@code null} 199 * if not specified. 200 * @param x5c The X.509 certificate chain, {@code null} if not 201 * specified. 202 * @param ks Reference to the underlying key store, {@code null} if 203 * none. 204 */ 205 @Deprecated 206 protected JWK(final KeyType kty, 207 final KeyUse use, 208 final Set<KeyOperation> ops, 209 final Algorithm alg, 210 final String kid, 211 final URI x5u, 212 final Base64URL x5t, 213 final Base64URL x5t256, 214 final List<Base64> x5c, 215 final KeyStore ks) { 216 217 this(kty, use, ops, alg, kid, x5u, x5t, x5t256, x5c, null, null, null, ks); 218 } 219 220 221 /** 222 * Creates a new JSON Web Key (JWK). 223 * 224 * @param kty The key type. Must not be {@code null}. 225 * @param use The key use, {@code null} if not specified or if the 226 * key is intended for signing as well as encryption. 227 * @param ops The key operations, {@code null} if not specified. 228 * @param alg The intended JOSE algorithm for the key, {@code null} 229 * if not specified. 230 * @param kid The key ID, {@code null} if not specified. 231 * @param x5u The X.509 certificate URL, {@code null} if not 232 * specified. 233 * @param x5t The X.509 certificate thumbprint, {@code null} if not 234 * specified. 235 * @param x5t256 The X.509 certificate SHA-256 thumbprint, {@code null} 236 * if not specified. 237 * @param x5c The X.509 certificate chain, {@code null} if not 238 * specified. 239 * @param exp The key expiration time, {@code null} if not 240 * specified. 241 * @param nbf The key not-before time, {@code null} if not 242 * specified. 243 * @param iat The key issued-at time, {@code null} if not specified. 244 * @param ks Reference to the underlying key store, {@code null} if 245 * none. 246 */ 247 @Deprecated 248 protected JWK(final KeyType kty, 249 final KeyUse use, 250 final Set<KeyOperation> ops, 251 final Algorithm alg, 252 final String kid, 253 final URI x5u, 254 final Base64URL x5t, 255 final Base64URL x5t256, 256 final List<Base64> x5c, 257 final Date exp, 258 final Date nbf, 259 final Date iat, 260 final KeyStore ks) { 261 262 this(kty, use, ops, alg, kid, x5u, x5t, x5t256, x5c, exp, nbf, iat, null, ks); 263 } 264 265 266 /** 267 * Creates a new JSON Web Key (JWK). 268 * 269 * @param kty The key type. Must not be {@code null}. 270 * @param use The key use, {@code null} if not specified or if 271 * the key is intended for signing as well as 272 * encryption. 273 * @param ops The key operations, {@code null} if not specified. 274 * @param alg The intended JOSE algorithm for the key, 275 * {@code null} if not specified. 276 * @param kid The key ID, {@code null} if not specified. 277 * @param x5u The X.509 certificate URL, {@code null} if not 278 * specified. 279 * @param x5t The X.509 certificate thumbprint, {@code null} if 280 * not specified. 281 * @param x5t256 The X.509 certificate SHA-256 thumbprint, 282 * {@code null} if not specified. 283 * @param x5c The X.509 certificate chain, {@code null} if not 284 * specified. 285 * @param exp The key expiration time, {@code null} if not 286 * specified. 287 * @param nbf The key not-before time, {@code null} if not 288 * specified. 289 * @param iat The key issued-at time, {@code null} if not 290 * specified. 291 * @param revocation The key revocation, {@code null} if not specified. 292 * @param ks Reference to the underlying key store, 293 * {@code null} if none. 294 */ 295 protected JWK(final KeyType kty, 296 final KeyUse use, 297 final Set<KeyOperation> ops, 298 final Algorithm alg, 299 final String kid, 300 final URI x5u, 301 final Base64URL x5t, 302 final Base64URL x5t256, 303 final List<Base64> x5c, 304 final Date exp, 305 final Date nbf, 306 final Date iat, 307 final KeyRevocation revocation, 308 final KeyStore ks) { 309 310 this.kty = Objects.requireNonNull(kty, "The key type \"" + JWKParameterNames.KEY_TYPE + "\" parameter must not be null"); 311 312 if (! KeyUseAndOpsConsistency.areConsistent(use, ops)) { 313 throw new IllegalArgumentException("The key use \"" + JWKParameterNames.PUBLIC_KEY_USE + "\" and key options \"" + JWKParameterNames.KEY_OPS + "\" parameters are not consistent, " + 314 "see RFC 7517, section 4.3"); 315 } 316 317 this.use = use; 318 this.ops = ops; 319 320 this.alg = alg; 321 this.kid = kid; 322 323 this.x5u = x5u; 324 this.x5t = x5t; 325 this.x5t256 = x5t256; 326 327 if (x5c != null && x5c.isEmpty()) { 328 throw new IllegalArgumentException("The X.509 certificate chain \"" + JWKParameterNames.X_509_CERT_CHAIN + "\" must not be empty"); 329 } 330 this.x5c = x5c; 331 332 try { 333 parsedX5c = X509CertChainUtils.parse(x5c); 334 } catch (ParseException e) { 335 throw new IllegalArgumentException("Invalid X.509 certificate chain \"" + JWKParameterNames.X_509_CERT_CHAIN + "\": " + e.getMessage(), e); 336 } 337 338 this.exp = exp; 339 this.nbf = nbf; 340 this.iat = iat; 341 this.revocation = revocation; 342 343 this.keyStore = ks; 344 } 345 346 347 /** 348 * Returns the type ({@code kty}) of this JWK. 349 * 350 * @return The key type. 351 */ 352 public KeyType getKeyType() { 353 354 return kty; 355 } 356 357 358 /** 359 * Returns the use ({@code use}) of this JWK. 360 * 361 * @return The key use, {@code null} if not specified or if the key is 362 * intended for signing as well as encryption. 363 */ 364 public KeyUse getKeyUse() { 365 366 return use; 367 } 368 369 370 /** 371 * Returns the operations ({@code key_ops}) for this JWK. 372 * 373 * @return The key operations, {@code null} if not specified. 374 */ 375 public Set<KeyOperation> getKeyOperations() { 376 377 return ops; 378 } 379 380 381 /** 382 * Returns the intended JOSE algorithm ({@code alg}) for this JWK. 383 * 384 * @return The intended JOSE algorithm, {@code null} if not specified. 385 */ 386 public Algorithm getAlgorithm() { 387 388 return alg; 389 } 390 391 392 /** 393 * Returns the ID ({@code kid}) of this JWK. The key ID can be used to 394 * match a specific key. This can be used, for instance, to choose a 395 * key within a {@link JWKSet} during key rollover. The key ID may also 396 * correspond to a JWS/JWE {@code kid} header parameter value. 397 * 398 * @return The key ID, {@code null} if not specified. 399 */ 400 public String getKeyID() { 401 402 return kid; 403 } 404 405 406 /** 407 * Returns the X.509 certificate URL ({@code x5u}) of this JWK. 408 * 409 * @return The X.509 certificate URL, {@code null} if not specified. 410 */ 411 public URI getX509CertURL() { 412 413 return x5u; 414 } 415 416 417 /** 418 * Returns the X.509 certificate SHA-1 thumbprint ({@code x5t}) of this 419 * JWK. 420 * 421 * @return The X.509 certificate SHA-1 thumbprint, {@code null} if not 422 * specified. 423 */ 424 @Deprecated 425 public Base64URL getX509CertThumbprint() { 426 427 return x5t; 428 } 429 430 431 /** 432 * Returns the X.509 certificate SHA-256 thumbprint ({@code x5t#S256}) 433 * of this JWK. 434 * 435 * @return The X.509 certificate SHA-256 thumbprint, {@code null} if 436 * not specified. 437 */ 438 public Base64URL getX509CertSHA256Thumbprint() { 439 440 return x5t256; 441 } 442 443 444 /** 445 * Returns the X.509 certificate chain ({@code x5c}) of this JWK. 446 * 447 * @return The X.509 certificate chain as a unmodifiable list, 448 * {@code null} if not specified. 449 */ 450 public List<Base64> getX509CertChain() { 451 452 if (x5c == null) { 453 return null; 454 } 455 456 return Collections.unmodifiableList(x5c); 457 } 458 459 460 /** 461 * Returns the parsed X.509 certificate chain ({@code x5c}) of this 462 * JWK. 463 * 464 * @return The X.509 certificate chain as a unmodifiable list, 465 * {@code null} if not specified. 466 */ 467 public List<X509Certificate> getParsedX509CertChain() { 468 469 if (parsedX5c == null) { 470 return null; 471 } 472 473 return Collections.unmodifiableList(parsedX5c); 474 } 475 476 477 /** 478 * Returns the expiration time ({@code exp}) if this JWK. 479 * 480 * @return The expiration time, {@code null} if not specified. 481 */ 482 public Date getExpirationTime() { 483 484 return exp; 485 } 486 487 488 /** 489 * Returns the not-before ({@code nbf}) of this JWK. 490 * 491 * @return The not-before time, {@code null} if not specified. 492 */ 493 public Date getNotBeforeTime() { 494 495 return nbf; 496 } 497 498 499 /** 500 * Returns the issued-at ({@code iat}) time of this JWK. 501 * 502 * @return The issued-at time, {@code null} if not specified. 503 */ 504 public Date getIssueTime() { 505 506 return iat; 507 } 508 509 510 /** 511 * Returns the key revocation ({@code revoked}) of this JWK. 512 * 513 * @return The key revocation, {@code null} if not specified. 514 */ 515 public KeyRevocation getKeyRevocation() { 516 return revocation; 517 } 518 519 520 /** 521 * Returns a reference to the underlying key store. 522 * 523 * @return The underlying key store, {@code null} if none. 524 */ 525 public KeyStore getKeyStore() { 526 527 return keyStore; 528 } 529 530 531 /** 532 * Returns the required JWK parameters. Intended as input for JWK 533 * thumbprint computation. See RFC 7638 for more information. 534 * 535 * @return The required JWK parameters, sorted alphanumerically by key 536 * name and ready for JSON serialisation. 537 */ 538 public abstract LinkedHashMap<String,?> getRequiredParams(); 539 540 541 /** 542 * Computes the SHA-256 thumbprint of this JWK. See RFC 7638 for more 543 * information. 544 * 545 * @return The SHA-256 thumbprint. 546 * 547 * @throws JOSEException If the SHA-256 hash algorithm is not 548 * supported. 549 */ 550 public Base64URL computeThumbprint() 551 throws JOSEException { 552 553 return computeThumbprint("SHA-256"); 554 555 } 556 557 558 /** 559 * Computes the thumbprint of this JWK using the specified hash 560 * algorithm. See RFC 7638 for more information. 561 * 562 * @param hashAlg The hash algorithm. Must not be {@code null}. 563 * 564 * @return The SHA-256 thumbprint. 565 * 566 * @throws JOSEException If the hash algorithm is not supported. 567 */ 568 public Base64URL computeThumbprint(final String hashAlg) 569 throws JOSEException { 570 571 return ThumbprintUtils.compute(hashAlg, this); 572 } 573 574 575 /** 576 * Computes the SHA-256 thumbprint URI of this JWK. See RFC 7638 and 577 * draft-ietf-oauth-jwk-thumbprint-uri for more information. 578 * 579 * @return The SHA-256 thumbprint URI. 580 * 581 * @throws JOSEException If the SHA-256 hash algorithm is not 582 * supported. 583 */ 584 public ThumbprintURI computeThumbprintURI() 585 throws JOSEException { 586 587 return new ThumbprintURI("sha-256", computeThumbprint("SHA-256")); 588 } 589 590 591 /** 592 * Returns {@code true} if this JWK contains private or sensitive 593 * (non-public) parameters. 594 * 595 * @return {@code true} if this JWK contains private parameters, else 596 * {@code false}. 597 */ 598 public abstract boolean isPrivate(); 599 600 601 /** 602 * Creates a copy of this JWK with all private or sensitive parameters 603 * removed. 604 * 605 * @return The newly created public JWK, or {@code null} if none can be 606 * created. 607 */ 608 public abstract JWK toPublicJWK(); 609 610 611 /** 612 * Creates a copy of this JWK with the specified key revocation. 613 * 614 * @param keyRevocation The key revocation. Must not be {@code null}. 615 * 616 * @return The new JWK with the specified revocation. 617 * 618 * @throws IllegalStateException If the JWK is already revoked. 619 */ 620 public abstract JWK toRevokedJWK(final KeyRevocation keyRevocation); 621 622 623 /** 624 * Returns the size of this JWK. 625 * 626 * @return The JWK size, in bits. 627 */ 628 public abstract int size(); 629 630 631 /** 632 * Casts this JWK to an RSA JWK. 633 * 634 * @return The RSA JWK. 635 */ 636 public RSAKey toRSAKey() { 637 return (RSAKey)this; 638 } 639 640 641 /** 642 * Casts this JWK to an EC JWK. 643 * 644 * @return The EC JWK. 645 */ 646 public ECKey toECKey() { 647 return (ECKey)this; 648 } 649 650 651 /** 652 * Casts this JWK to an octet sequence JWK. 653 * 654 * @return The octet sequence JWK. 655 */ 656 public OctetSequenceKey toOctetSequenceKey() { 657 return (OctetSequenceKey)this; 658 } 659 660 661 /** 662 * Casts this JWK to an octet key pair JWK. 663 * 664 * @return The octet key pair JWK. 665 */ 666 public OctetKeyPair toOctetKeyPair() { 667 return (OctetKeyPair)this; 668 } 669 670 671 /** 672 * Returns a JSON object representation of this JWK. This method is 673 * intended to be called from extending classes. 674 * 675 * <p>Example: 676 * 677 * <pre> 678 * { 679 * "kty" : "RSA", 680 * "use" : "sig", 681 * "kid" : "fd28e025-8d24-48bc-a51a-e2ffc8bc274b" 682 * } 683 * </pre> 684 * 685 * @return The JSON object representation. 686 */ 687 public Map<String, Object> toJSONObject() { 688 689 Map<String, Object> o = JSONObjectUtils.newJSONObject(); 690 691 o.put(JWKParameterNames.KEY_TYPE, kty.getValue()); 692 693 if (use != null) { 694 o.put(JWKParameterNames.PUBLIC_KEY_USE, use.identifier()); 695 } 696 697 if (ops != null) { 698 List<Object> stringValues = JSONArrayUtils.newJSONArray(); 699 for (KeyOperation op: ops) { 700 stringValues.add(op.identifier()); 701 } 702 o.put(JWKParameterNames.KEY_OPS, stringValues); 703 } 704 705 if (alg != null) { 706 o.put(JWKParameterNames.ALGORITHM, alg.getName()); 707 } 708 709 if (kid != null) { 710 o.put(JWKParameterNames.KEY_ID, kid); 711 } 712 713 if (x5u != null) { 714 o.put(JWKParameterNames.X_509_CERT_URL, x5u.toString()); 715 } 716 717 if (x5t != null) { 718 o.put(JWKParameterNames.X_509_CERT_SHA_1_THUMBPRINT, x5t.toString()); 719 } 720 721 if (x5t256 != null) { 722 o.put(JWKParameterNames.X_509_CERT_SHA_256_THUMBPRINT, x5t256.toString()); 723 } 724 725 if (x5c != null) { 726 List<Object> stringValues = JSONArrayUtils.newJSONArray(); 727 for (Base64 base64: x5c) { 728 stringValues.add(base64.toString()); 729 } 730 o.put(JWKParameterNames.X_509_CERT_CHAIN, stringValues); 731 } 732 733 if (exp != null) { 734 o.put(JWKParameterNames.EXPIRATION_TIME, DateUtils.toSecondsSinceEpoch(exp)); 735 } 736 737 if (nbf != null) { 738 o.put(JWKParameterNames.NOT_BEFORE, DateUtils.toSecondsSinceEpoch(nbf)); 739 } 740 741 if (iat != null) { 742 o.put(JWKParameterNames.ISSUED_AT, DateUtils.toSecondsSinceEpoch(iat)); 743 } 744 745 if (revocation != null) { 746 o.put(JWKParameterNames.REVOKED, revocation.toJSONObject()); 747 } 748 749 return o; 750 } 751 752 753 /** 754 * Returns the JSON object string representation of this JWK. 755 * 756 * @return The JSON object string representation. 757 */ 758 public String toJSONString() { 759 return JSONObjectUtils.toJSONString(toJSONObject()); 760 } 761 762 763 /** 764 * @see #toJSONString 765 */ 766 @Override 767 public String toString() { 768 769 return JSONObjectUtils.toJSONString(toJSONObject()); 770 } 771 772 773 /** 774 * Parses a JWK from the specified JSON object string representation. 775 * The JWK must be an {@link ECKey}, an {@link RSAKey}, or a 776 * {@link OctetSequenceKey}. 777 * 778 * @param s The JSON object string to parse. Must not be {@code null}. 779 * 780 * @return The JWK. 781 * 782 * @throws ParseException If the string couldn't be parsed to a 783 * supported JWK. 784 */ 785 public static JWK parse(final String s) 786 throws ParseException { 787 788 return parse(JSONObjectUtils.parse(s)); 789 } 790 791 792 /** 793 * Parses a JWK from the specified JSON object representation. The JWK 794 * must be an {@link ECKey}, an {@link RSAKey}, or a 795 * {@link OctetSequenceKey}. 796 * 797 * @param jsonObject The JSON object to parse. Must not be 798 * {@code null}. 799 * 800 * @return The JWK. 801 * 802 * @throws ParseException If the JSON object couldn't be parsed to a 803 * supported JWK. 804 */ 805 public static JWK parse(final Map<String, Object> jsonObject) 806 throws ParseException { 807 808 String ktyString = JSONObjectUtils.getString(jsonObject, JWKParameterNames.KEY_TYPE); 809 810 if (ktyString == null) { 811 throw new ParseException("Missing key type \"kty\" parameter", 0); 812 } 813 814 KeyType kty = KeyType.parse(ktyString); 815 816 if (kty == KeyType.EC) { 817 818 return ECKey.parse(jsonObject); 819 820 } else if (kty == KeyType.RSA) { 821 822 return RSAKey.parse(jsonObject); 823 824 } else if (kty == KeyType.OCT) { 825 826 return OctetSequenceKey.parse(jsonObject); 827 828 } else if (kty == KeyType.OKP) { 829 830 return OctetKeyPair.parse(jsonObject); 831 832 } else { 833 834 throw new ParseException("Unsupported key type \"" + JWKParameterNames.KEY_TYPE + "\" parameter: " + kty, 0); 835 } 836 } 837 838 839 /** 840 * Parses a public {@link RSAKey RSA} or {@link ECKey EC JWK} from the 841 * specified X.509 certificate. Requires BouncyCastle. 842 * 843 * <p><strong>Important:</strong> The X.509 certificate is not 844 * validated! 845 * 846 * <p>Sets the following JWK parameters: 847 * 848 * <ul> 849 * <li>For an EC key the curve is obtained from the subject public 850 * key info algorithm parameters. 851 * <li>The JWK use inferred by {@link KeyUse#from}. 852 * <li>The JWK ID from the X.509 serial number (in base 10). 853 * <li>The JWK X.509 certificate chain (this certificate only). 854 * <li>The JWK X.509 certificate SHA-256 thumbprint. 855 * </ul> 856 * 857 * @param cert The X.509 certificate. Must not be {@code null}. 858 * 859 * @return The public RSA or EC JWK. 860 * 861 * @throws JOSEException If parsing failed. 862 */ 863 public static JWK parse(final X509Certificate cert) 864 throws JOSEException { 865 866 if (cert.getPublicKey() instanceof RSAPublicKey) { 867 return RSAKey.parse(cert); 868 } else if (cert.getPublicKey() instanceof ECPublicKey) { 869 return ECKey.parse(cert); 870 } else { 871 throw new JOSEException("Unsupported public key algorithm: " + cert.getPublicKey().getAlgorithm()); 872 } 873 } 874 875 876 /** 877 * Parses a public {@link RSAKey RSA} or {@link ECKey EC JWK} from the 878 * specified PEM-encoded X.509 certificate. Requires BouncyCastle. 879 * 880 * <p><strong>Important:</strong> The X.509 certificate is not 881 * validated! 882 * 883 * <p>Sets the following JWK parameters: 884 * 885 * <ul> 886 * <li>For an EC key the curve is obtained from the subject public 887 * key info algorithm parameters. 888 * <li>The JWK use inferred by {@link KeyUse#from}. 889 * <li>The JWK ID from the X.509 serial number (in base 10). 890 * <li>The JWK X.509 certificate chain (this certificate only). 891 * <li>The JWK X.509 certificate SHA-256 thumbprint. 892 * </ul> 893 * 894 * @param pemEncodedCert The PEM-encoded X.509 certificate. Must not be 895 * {@code null}. 896 * 897 * @return The public RSA or EC JWK. 898 * 899 * @throws JOSEException If parsing failed. 900 */ 901 public static JWK parseFromPEMEncodedX509Cert(final String pemEncodedCert) 902 throws JOSEException { 903 904 X509Certificate cert = X509CertUtils.parse(pemEncodedCert); 905 906 if (cert == null) { 907 throw new JOSEException("Couldn't parse PEM-encoded X.509 certificate"); 908 } 909 910 return parse(cert); 911 } 912 913 914 /** 915 * Loads a JWK from the specified JCE key store. The JWK can be a 916 * public / private {@link RSAKey RSA key}, a public / private 917 * {@link ECKey EC key}, or a {@link OctetSequenceKey secret key}. 918 * Requires BouncyCastle. 919 * 920 * <p><strong>Important:</strong> The X.509 certificate is not 921 * validated! 922 * 923 * @param keyStore The key store. Must not be {@code null}. 924 * @param alias The alias. Must not be {@code null}. 925 * @param pin The pin to unlock the private key if any, empty or 926 * {@code null} if not required. 927 * 928 * @return The public / private RSA or EC JWK, or secret JWK, or 929 * {@code null} if no key with the specified alias was found. 930 * 931 * @throws KeyStoreException On a key store exception. 932 * @throws JOSEException If RSA or EC key loading failed. 933 */ 934 public static JWK load(final KeyStore keyStore, final String alias, final char[] pin) 935 throws KeyStoreException, JOSEException { 936 937 java.security.cert.Certificate cert = keyStore.getCertificate(alias); 938 939 if (cert == null) { 940 // Try secret key 941 return OctetSequenceKey.load(keyStore, alias, pin); 942 } 943 944 if (cert.getPublicKey() instanceof RSAPublicKey) { 945 return RSAKey.load(keyStore, alias, pin); 946 } else if (cert.getPublicKey() instanceof ECPublicKey) { 947 return ECKey.load(keyStore, alias, pin); 948 } else { 949 throw new JOSEException("Unsupported public key algorithm: " + cert.getPublicKey().getAlgorithm()); 950 } 951 } 952 953 /** 954 * Parses an RSA or EC JWK from the specified string of one or more 955 * PEM-encoded object(s): 956 * 957 * <ul> 958 * <li>X.509 certificate (PEM header: BEGIN CERTIFICATE) 959 * <li>PKCS#1 RSAPublicKey (PEM header: BEGIN RSA PUBLIC KEY) 960 * <li>X.509 SubjectPublicKeyInfo (PEM header: BEGIN PUBLIC KEY) 961 * <li>PKCS#1 RSAPrivateKey (PEM header: BEGIN RSA PRIVATE KEY) 962 * <li>PKCS#8 PrivateKeyInfo (PEM header: BEGIN PRIVATE KEY) 963 * <li>matching pair of the above 964 * </ul> 965 * 966 * <p>Requires BouncyCastle. 967 * 968 * @param pemEncodedObjects The string of PEM-encoded object(s). 969 * 970 * @return The public / (private) RSA or EC JWK. 971 * 972 * @throws JOSEException If RSA or EC key parsing failed. 973 */ 974 public static JWK parseFromPEMEncodedObjects(final String pemEncodedObjects) 975 throws JOSEException { 976 977 final List<KeyPair> keys = PEMEncodedKeyParser.parseKeys(pemEncodedObjects); 978 if (keys.isEmpty()) { 979 throw new JOSEException("No PEM-encoded keys found"); 980 } 981 982 final KeyPair pair = mergeKeyPairs(keys); 983 984 final PublicKey publicKey = pair.getPublic(); 985 final PrivateKey privateKey = pair.getPrivate(); 986 987 if (publicKey == null) { 988 // For EC keys, for RSA the public can be reconstructed 989 throw new JOSEException("Missing PEM-encoded public key to construct JWK"); 990 } 991 992 if (publicKey instanceof ECPublicKey) { 993 final ECPublicKey ecPubKey = (ECPublicKey) publicKey; 994 final ECParameterSpec pubParams = ecPubKey.getParams(); 995 996 if (privateKey instanceof ECPrivateKey) { 997 validateEcCurves(ecPubKey, (ECPrivateKey) privateKey); 998 } 999 if (privateKey != null && !(privateKey instanceof ECPrivateKey)) { 1000 throw new JOSEException("Unsupported " + KeyType.EC.getValue() + " private key type: " + privateKey); 1001 } 1002 1003 final Curve curve = Curve.forECParameterSpec(pubParams); 1004 final ECKey.Builder builder = new ECKey.Builder(curve, (ECPublicKey) publicKey); 1005 1006 if (privateKey != null) { 1007 builder.privateKey((ECPrivateKey) privateKey); 1008 } 1009 return builder.build(); 1010 } 1011 1012 if (publicKey instanceof RSAPublicKey) { 1013 final RSAKey.Builder builder = new RSAKey.Builder((RSAPublicKey) publicKey); 1014 if (privateKey instanceof RSAPrivateKey) { 1015 builder.privateKey((RSAPrivateKey) privateKey); 1016 } else if (privateKey != null) { 1017 throw new JOSEException("Unsupported " + KeyType.RSA.getValue() + " private key type: " + privateKey); 1018 } 1019 return builder.build(); 1020 } 1021 1022 throw new JOSEException("Unsupported algorithm of PEM-encoded key: " + publicKey.getAlgorithm()); 1023 } 1024 1025 1026 private static void validateEcCurves(ECPublicKey publicKey, ECPrivateKey privateKey) throws JOSEException { 1027 final ECParameterSpec pubParams = publicKey.getParams(); 1028 final ECParameterSpec privParams = privateKey.getParams(); 1029 if (!pubParams.getCurve().equals(privParams.getCurve())) { 1030 throw new JOSEException("Public/private " + KeyType.EC.getValue() + " key curve mismatch: " + publicKey); 1031 } 1032 if (pubParams.getCofactor() != privParams.getCofactor()) { 1033 throw new JOSEException("Public/private " + KeyType.EC.getValue() + " key cofactor mismatch: " + publicKey); 1034 } 1035 if (!pubParams.getGenerator().equals(privParams.getGenerator())) { 1036 throw new JOSEException("Public/private " + KeyType.EC.getValue() + " key generator mismatch: " + publicKey); 1037 } 1038 if (!pubParams.getOrder().equals(privParams.getOrder())) { 1039 throw new JOSEException("Public/private " + KeyType.EC.getValue() + " key order mismatch: " + publicKey); 1040 } 1041 } 1042 1043 1044 private static KeyPair mergeKeyPairs(final List<KeyPair> keys) throws JOSEException { 1045 final KeyPair pair; 1046 if (keys.size() == 1) { 1047 // Assume public key, or private key easy to convert to public, 1048 // otherwise not representable as a JWK 1049 pair = keys.get(0); 1050 } else if (keys.size() == 2) { 1051 // If two keys, assume public + private keys separated 1052 pair = twoKeysToKeyPair(keys); 1053 } else { 1054 throw new JOSEException("Expected key or pair of PEM-encoded keys"); 1055 } 1056 return pair; 1057 } 1058 1059 1060 private static KeyPair twoKeysToKeyPair(final List<? extends KeyPair> keys) throws JOSEException { 1061 final KeyPair key1 = keys.get(0); 1062 final KeyPair key2 = keys.get(1); 1063 if (key1.getPublic() != null && key2.getPrivate() != null) { 1064 return new KeyPair(key1.getPublic(), key2.getPrivate()); 1065 } else if (key1.getPrivate() != null && key2.getPublic() != null) { 1066 return new KeyPair(key2.getPublic(), key1.getPrivate()); 1067 } else { 1068 throw new JOSEException("Not a public/private key pair"); 1069 } 1070 } 1071 1072 1073 @Override 1074 public boolean equals(Object o) { 1075 if (this == o) return true; 1076 if (!(o instanceof JWK)) return false; 1077 JWK jwk = (JWK) o; 1078 return Objects.equals(kty, jwk.kty) && 1079 Objects.equals(use, jwk.use) && 1080 Objects.equals(ops, jwk.ops) && 1081 Objects.equals(alg, jwk.alg) && 1082 Objects.equals(kid, jwk.kid) && 1083 Objects.equals(x5u, jwk.x5u) && 1084 Objects.equals(x5t, jwk.x5t) && 1085 Objects.equals(x5t256, jwk.x5t256) && 1086 Objects.equals(x5c, jwk.x5c) && 1087 Objects.equals(exp, jwk.exp) && 1088 Objects.equals(nbf, jwk.nbf) && 1089 Objects.equals(iat, jwk.iat) && 1090 Objects.equals(revocation, jwk.revocation) && 1091 Objects.equals(keyStore, jwk.keyStore); 1092 } 1093 1094 1095 @Override 1096 public int hashCode() { 1097 return Objects.hash(kty, use, ops, alg, kid, x5u, x5t, x5t256, x5c, exp, nbf, iat, revocation, keyStore); 1098 } 1099}