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