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 net.minidev.json.JSONArray; 034import net.minidev.json.JSONAware; 035import net.minidev.json.JSONObject; 036 037import com.nimbusds.jose.Algorithm; 038import com.nimbusds.jose.JOSEException; 039import com.nimbusds.jose.util.Base64; 040import com.nimbusds.jose.util.*; 041 042 043/** 044 * The base abstract class for JSON Web Keys (JWKs). It serialises to a JSON 045 * object. 046 * 047 * <p>The following JSON object members are common to all JWK types: 048 * 049 * <ul> 050 * <li>{@link #getKeyType kty} (required) 051 * <li>{@link #getKeyUse use} (optional) 052 * <li>{@link #getKeyOperations key_ops} (optional) 053 * <li>{@link #getKeyID kid} (optional) 054 * <li>{@link #getX509CertURL() x5u} (optional) 055 * <li>{@link #getX509CertThumbprint() x5t} (optional) 056 * <li>{@link #getX509CertSHA256Thumbprint() x5t#S256} (optional) 057 * <li>{@link #getX509CertChain() x5c} (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 2020-02-09 078 */ 079public abstract class JWK implements JSONAware, 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 Base64URL x5t256; 139 140 141 /** 142 * The X.509 certificate chain, optional. 143 */ 144 private final List<Base64> x5c; 145 146 147 /** 148 * The parsed X.509 certificate chain, optional. 149 */ 150 private final List<X509Certificate> parsedX5c; 151 152 153 /** 154 * Reference to the underlying key store, {@code null} if none. 155 */ 156 private final KeyStore keyStore; 157 158 159 /** 160 * Creates a new JSON Web Key (JWK). 161 * 162 * @param kty The key type. Must not be {@code null}. 163 * @param use The key use, {@code null} if not specified or if the 164 * key is intended for signing as well as encryption. 165 * @param ops The key operations, {@code null} if not specified. 166 * @param alg The intended JOSE algorithm for the key, {@code null} 167 * if not specified. 168 * @param kid The key ID, {@code null} if not specified. 169 * @param x5u The X.509 certificate URL, {@code null} if not 170 * specified. 171 * @param x5t The X.509 certificate thumbprint, {@code null} if not 172 * specified. 173 * @param x5t256 The X.509 certificate SHA-256 thumbprint, {@code null} 174 * if not specified. 175 * @param x5c The X.509 certificate chain, {@code null} if not 176 * specified. 177 * @param ks Reference to the underlying key store, {@code null} if 178 * none. 179 */ 180 protected JWK(final KeyType kty, 181 final KeyUse use, 182 final Set<KeyOperation> ops, 183 final Algorithm alg, 184 final String kid, 185 final URI x5u, 186 final Base64URL x5t, 187 final Base64URL x5t256, 188 final List<Base64> x5c, 189 final KeyStore ks) { 190 191 if (kty == null) { 192 throw new IllegalArgumentException("The key type \"kty\" parameter must not be null"); 193 } 194 195 this.kty = kty; 196 197 if (! KeyUseAndOpsConsistency.areConsistent(use, ops)) { 198 throw new IllegalArgumentException("The key use \"use\" and key options \"key_opts\" parameters are not consistent, " + 199 "see RFC 7517, section 4.3"); 200 } 201 202 this.use = use; 203 this.ops = ops; 204 205 this.alg = alg; 206 this.kid = kid; 207 208 this.x5u = x5u; 209 this.x5t = x5t; 210 this.x5t256 = x5t256; 211 212 if (x5c != null && x5c.isEmpty()) { 213 throw new IllegalArgumentException("The X.509 certificate chain \"x5c\" must not be empty"); 214 } 215 this.x5c = x5c; 216 217 try { 218 parsedX5c = X509CertChainUtils.parse(x5c); 219 } catch (ParseException e) { 220 throw new IllegalArgumentException("Invalid X.509 certificate chain \"x5c\": " + e.getMessage(), e); 221 } 222 223 this.keyStore = ks; 224 } 225 226 227 /** 228 * Gets the type ({@code kty}) of this JWK. 229 * 230 * @return The key type. 231 */ 232 public KeyType getKeyType() { 233 234 return kty; 235 } 236 237 238 /** 239 * Gets the use ({@code use}) of this JWK. 240 * 241 * @return The key use, {@code null} if not specified or if the key is 242 * intended for signing as well as encryption. 243 */ 244 public KeyUse getKeyUse() { 245 246 return use; 247 } 248 249 250 /** 251 * Gets the operations ({@code key_ops}) for this JWK. 252 * 253 * @return The key operations, {@code null} if not specified. 254 */ 255 public Set<KeyOperation> getKeyOperations() { 256 257 return ops; 258 } 259 260 261 /** 262 * Gets the intended JOSE algorithm ({@code alg}) for this JWK. 263 * 264 * @return The intended JOSE algorithm, {@code null} if not specified. 265 */ 266 public Algorithm getAlgorithm() { 267 268 return alg; 269 } 270 271 272 /** 273 * Gets the ID ({@code kid}) of this JWK. The key ID can be used to 274 * match a specific key. This can be used, for instance, to choose a 275 * key within a {@link JWKSet} during key rollover. The key ID may also 276 * correspond to a JWS/JWE {@code kid} header parameter value. 277 * 278 * @return The key ID, {@code null} if not specified. 279 */ 280 public String getKeyID() { 281 282 return kid; 283 } 284 285 286 /** 287 * Gets the X.509 certificate URL ({@code x5u}) of this JWK. 288 * 289 * @return The X.509 certificate URL, {@code null} if not specified. 290 */ 291 public URI getX509CertURL() { 292 293 return x5u; 294 } 295 296 297 /** 298 * Gets the X.509 certificate SHA-1 thumbprint ({@code x5t}) of this 299 * JWK. 300 * 301 * @return The X.509 certificate SHA-1 thumbprint, {@code null} if not 302 * specified. 303 */ 304 @Deprecated 305 public Base64URL getX509CertThumbprint() { 306 307 return x5t; 308 } 309 310 311 /** 312 * Gets the X.509 certificate SHA-256 thumbprint ({@code x5t#S256}) of 313 * this JWK. 314 * 315 * @return The X.509 certificate SHA-256 thumbprint, {@code null} if 316 * not specified. 317 */ 318 public Base64URL getX509CertSHA256Thumbprint() { 319 320 return x5t256; 321 } 322 323 324 /** 325 * Gets the X.509 certificate chain ({@code x5c}) of this JWK. 326 * 327 * @return The X.509 certificate chain as a unmodifiable list, 328 * {@code null} if not specified. 329 */ 330 public List<Base64> getX509CertChain() { 331 332 if (x5c == null) { 333 return null; 334 } 335 336 return Collections.unmodifiableList(x5c); 337 } 338 339 340 /** 341 * Gets the parsed X.509 certificate chain ({@code x5c}) of this JWK. 342 * 343 * @return The X.509 certificate chain as a unmodifiable list, 344 * {@code null} if not specified. 345 */ 346 public List<X509Certificate> getParsedX509CertChain() { 347 348 if (parsedX5c == null) { 349 return null; 350 } 351 352 return Collections.unmodifiableList(parsedX5c); 353 } 354 355 356 /** 357 * Returns a reference to the underlying key store. 358 * 359 * @return The underlying key store, {@code null} if none. 360 */ 361 public KeyStore getKeyStore() { 362 363 return keyStore; 364 } 365 366 367 /** 368 * Returns the required JWK parameters. Intended as input for JWK 369 * thumbprint computation. See RFC 7638 for more information. 370 * 371 * @return The required JWK parameters, sorted alphanumerically by key 372 * name and ready for JSON serialisation. 373 */ 374 public abstract LinkedHashMap<String,?> getRequiredParams(); 375 376 377 /** 378 * Computes the SHA-256 thumbprint of this JWK. See RFC 7638 for more 379 * information. 380 * 381 * @return The SHA-256 thumbprint. 382 * 383 * @throws JOSEException If the SHA-256 hash algorithm is not 384 * supported. 385 */ 386 public Base64URL computeThumbprint() 387 throws JOSEException { 388 389 return computeThumbprint("SHA-256"); 390 } 391 392 393 /** 394 * Computes the thumbprint of this JWK using the specified hash 395 * algorithm. See RFC 7638 for more information. 396 * 397 * @param hashAlg The hash algorithm. Must not be {@code null}. 398 * 399 * @return The SHA-256 thumbprint. 400 * 401 * @throws JOSEException If the hash algorithm is not supported. 402 */ 403 public Base64URL computeThumbprint(final String hashAlg) 404 throws JOSEException { 405 406 return ThumbprintUtils.compute(hashAlg, this); 407 } 408 409 410 /** 411 * Returns {@code true} if this JWK contains private or sensitive 412 * (non-public) parameters. 413 * 414 * @return {@code true} if this JWK contains private parameters, else 415 * {@code false}. 416 */ 417 public abstract boolean isPrivate(); 418 419 420 /** 421 * Creates a copy of this JWK with all private or sensitive parameters 422 * removed. 423 * 424 * @return The newly created public JWK, or {@code null} if none can be 425 * created. 426 */ 427 public abstract JWK toPublicJWK(); 428 429 430 /** 431 * Returns the size of this JWK. 432 * 433 * @return The JWK size, in bits. 434 */ 435 public abstract int size(); 436 437 438 /** 439 * Casts this JWK to an RSA JWK. 440 * 441 * @return The RSA JWK. 442 */ 443 public RSAKey toRSAKey() { 444 return (RSAKey)this; 445 } 446 447 448 /** 449 * Casts this JWK to an EC JWK. 450 * 451 * @return The EC JWK. 452 */ 453 public ECKey toECKey() { 454 return (ECKey)this; 455 } 456 457 458 /** 459 * Casts this JWK to an octet sequence JWK. 460 * 461 * @return The octet sequence JWK. 462 */ 463 public OctetSequenceKey toOctetSequenceKey() { 464 return (OctetSequenceKey)this; 465 } 466 467 468 /** 469 * Casts this JWK to an octet key pair JWK. 470 * 471 * @return The octet key pair JWK. 472 */ 473 public OctetKeyPair toOctetKeyPair() { 474 return (OctetKeyPair)this; 475 } 476 477 478 /** 479 * Returns a JSON object representation of this JWK. This method is 480 * intended to be called from extending classes. 481 * 482 * <p>Example: 483 * 484 * <pre> 485 * { 486 * "kty" : "RSA", 487 * "use" : "sig", 488 * "kid" : "fd28e025-8d24-48bc-a51a-e2ffc8bc274b" 489 * } 490 * </pre> 491 * 492 * @return The JSON object representation. 493 */ 494 public JSONObject toJSONObject() { 495 496 JSONObject o = new JSONObject(); 497 498 o.put("kty", kty.getValue()); 499 500 if (use != null) { 501 o.put("use", use.identifier()); 502 } 503 504 if (ops != null) { 505 506 List<String> sl = new ArrayList<>(ops.size()); 507 508 for (KeyOperation op: ops) { 509 sl.add(op.identifier()); 510 } 511 512 o.put("key_ops", sl); 513 } 514 515 if (alg != null) { 516 o.put("alg", alg.getName()); 517 } 518 519 if (kid != null) { 520 o.put("kid", kid); 521 } 522 523 if (x5u != null) { 524 o.put("x5u", x5u.toString()); 525 } 526 527 if (x5t != null) { 528 o.put("x5t", x5t.toString()); 529 } 530 531 if (x5t256 != null) { 532 o.put("x5t#S256", x5t256.toString()); 533 } 534 535 if (x5c != null) { 536 JSONArray stringValues = new JSONArray(); 537 for (Base64 base64: x5c) { 538 stringValues.add(base64.toString()); 539 } 540 o.put("x5c", stringValues); 541 } 542 543 return o; 544 } 545 546 547 /** 548 * Returns the JSON object string representation of this JWK. 549 * 550 * @return The JSON object string representation. 551 */ 552 @Override 553 public String toJSONString() { 554 555 return toJSONObject().toString(); 556 } 557 558 559 /** 560 * @see #toJSONString 561 */ 562 @Override 563 public String toString() { 564 565 return toJSONObject().toString(); 566 } 567 568 569 /** 570 * Parses a JWK from the specified JSON object string representation. 571 * The JWK must be an {@link ECKey}, an {@link RSAKey}, or a 572 * {@link OctetSequenceKey}. 573 * 574 * @param s The JSON object string to parse. Must not be {@code null}. 575 * 576 * @return The JWK. 577 * 578 * @throws ParseException If the string couldn't be parsed to a 579 * supported JWK. 580 */ 581 public static JWK parse(final String s) 582 throws ParseException { 583 584 return parse(JSONObjectUtils.parse(s)); 585 } 586 587 588 /** 589 * Parses a JWK from the specified JSON object representation. The JWK 590 * must be an {@link ECKey}, an {@link RSAKey}, or a 591 * {@link OctetSequenceKey}. 592 * 593 * @param jsonObject The JSON object to parse. Must not be 594 * {@code null}. 595 * 596 * @return The JWK. 597 * 598 * @throws ParseException If the JSON object couldn't be parsed to a 599 * supported JWK. 600 */ 601 public static JWK parse(final JSONObject jsonObject) 602 throws ParseException { 603 604 KeyType kty = KeyType.parse(JSONObjectUtils.getString(jsonObject, "kty")); 605 606 if (kty == KeyType.EC) { 607 608 return ECKey.parse(jsonObject); 609 610 } else if (kty == KeyType.RSA) { 611 612 return RSAKey.parse(jsonObject); 613 614 } else if (kty == KeyType.OCT) { 615 616 return OctetSequenceKey.parse(jsonObject); 617 618 } else if (kty == KeyType.OKP) { 619 620 return OctetKeyPair.parse(jsonObject); 621 622 } else { 623 624 throw new ParseException("Unsupported key type \"kty\" parameter: " + kty, 0); 625 } 626 } 627 628 629 /** 630 * Parses a public {@link RSAKey RSA} or {@link ECKey EC JWK} from the 631 * specified X.509 certificate. Requires BouncyCastle. 632 * 633 * <p><strong>Important:</strong> The X.509 certificate is not 634 * validated! 635 * 636 * <p>Sets the following JWK parameters: 637 * 638 * <ul> 639 * <li>For an EC key the curve is obtained from the subject public 640 * key info algorithm parameters. 641 * <li>The JWK use inferred by {@link KeyUse#from}. 642 * <li>The JWK ID from the X.509 serial number (in base 10). 643 * <li>The JWK X.509 certificate chain (this certificate only). 644 * <li>The JWK X.509 certificate SHA-256 thumbprint. 645 * </ul> 646 * 647 * @param cert The X.509 certificate. Must not be {@code null}. 648 * 649 * @return The public RSA or EC JWK. 650 * 651 * @throws JOSEException If parsing failed. 652 */ 653 public static JWK parse(final X509Certificate cert) 654 throws JOSEException { 655 656 if (cert.getPublicKey() instanceof RSAPublicKey) { 657 return RSAKey.parse(cert); 658 } else if (cert.getPublicKey() instanceof ECPublicKey) { 659 return ECKey.parse(cert); 660 } else { 661 throw new JOSEException("Unsupported public key algorithm: " + cert.getPublicKey().getAlgorithm()); 662 } 663 } 664 665 666 /** 667 * Parses a public {@link RSAKey RSA} or {@link ECKey EC JWK} from the 668 * specified PEM-encoded X.509 certificate. Requires BouncyCastle. 669 * 670 * <p><strong>Important:</strong> The X.509 certificate is not 671 * validated! 672 * 673 * <p>Sets the following JWK parameters: 674 * 675 * <ul> 676 * <li>For an EC key the curve is obtained from the subject public 677 * key info algorithm parameters. 678 * <li>The JWK use inferred by {@link KeyUse#from}. 679 * <li>The JWK ID from the X.509 serial number (in base 10). 680 * <li>The JWK X.509 certificate chain (this certificate only). 681 * <li>The JWK X.509 certificate SHA-256 thumbprint. 682 * </ul> 683 * 684 * @param pemEncodedCert The PEM-encoded X.509 certificate. Must not be 685 * {@code null}. 686 * 687 * @return The public RSA or EC JWK. 688 * 689 * @throws JOSEException If parsing failed. 690 */ 691 public static JWK parseFromPEMEncodedX509Cert(final String pemEncodedCert) 692 throws JOSEException { 693 694 X509Certificate cert = X509CertUtils.parse(pemEncodedCert); 695 696 if (cert == null) { 697 throw new JOSEException("Couldn't parse PEM-encoded X.509 certificate"); 698 } 699 700 return parse(cert); 701 } 702 703 704 /** 705 * Loads a JWK from the specified JCE key store. The JWK can be a 706 * public / private {@link RSAKey RSA key}, a public / private 707 * {@link ECKey EC key}, or a {@link OctetSequenceKey secret key}. 708 * Requires BouncyCastle. 709 * 710 * <p><strong>Important:</strong> The X.509 certificate is not 711 * validated! 712 * 713 * @param keyStore The key store. Must not be {@code null}. 714 * @param alias The alias. Must not be {@code null}. 715 * @param pin The pin to unlock the private key if any, empty or 716 * {@code null} if not required. 717 * 718 * @return The public / private RSA or EC JWK, or secret JWK, or 719 * {@code null} if no key with the specified alias was found. 720 * 721 * @throws KeyStoreException On a key store exception. 722 * @throws JOSEException If RSA or EC key loading failed. 723 */ 724 public static JWK load(final KeyStore keyStore, final String alias, final char[] pin) 725 throws KeyStoreException, JOSEException { 726 727 java.security.cert.Certificate cert = keyStore.getCertificate(alias); 728 729 if (cert == null) { 730 // Try secret key 731 return OctetSequenceKey.load(keyStore, alias, pin); 732 } 733 734 if (cert.getPublicKey() instanceof RSAPublicKey) { 735 return RSAKey.load(keyStore, alias, pin); 736 } else if (cert.getPublicKey() instanceof ECPublicKey) { 737 return ECKey.load(keyStore, alias, pin); 738 } else { 739 throw new JOSEException("Unsupported public key algorithm: " + cert.getPublicKey().getAlgorithm()); 740 } 741 } 742 743 /** 744 * Parses an RSA or EC JWK from the specified string of one or more 745 * PEM-encoded object(s): 746 * 747 * <ul> 748 * <li>X.509 certificate (PEM header: BEGIN CERTIFICATE) 749 * <li>PKCS#1 RSAPublicKey (PEM header: BEGIN RSA PUBLIC KEY) 750 * <li>X.509 SubjectPublicKeyInfo (PEM header: BEGIN PUBLIC KEY) 751 * <li>PKCS#1 RSAPrivateKey (PEM header: BEGIN RSA PRIVATE KEY) 752 * <li>PKCS#8 PrivateKeyInfo (PEM header: BEGIN PRIVATE KEY) 753 * <li>matching pair of the above 754 * </ul> 755 * 756 * <p>Requires BouncyCastle. 757 * 758 * @param pemEncodedObjects The string of PEM-encoded object(s). 759 * 760 * @return The public / (private) RSA or EC JWK. 761 * 762 * @throws JOSEException If RSA or EC key parsing failed. 763 */ 764 public static JWK parseFromPEMEncodedObjects(final String pemEncodedObjects) 765 throws JOSEException { 766 767 final List<KeyPair> keys = PEMEncodedKeyParser.parseKeys(pemEncodedObjects); 768 if (keys.isEmpty()) { 769 throw new JOSEException("No PEM-encoded keys found"); 770 } 771 772 final KeyPair pair = mergeKeyPairs(keys); 773 774 final PublicKey publicKey = pair.getPublic(); 775 final PrivateKey privateKey = pair.getPrivate(); 776 777 if (publicKey == null) { 778 // For EC keys, for RSA the public can be reconstructed 779 throw new JOSEException("Missing PEM-encoded public key to construct JWK"); 780 } 781 782 if (publicKey instanceof ECPublicKey) { 783 final ECPublicKey ecPubKey = (ECPublicKey) publicKey; 784 final ECParameterSpec pubParams = ecPubKey.getParams(); 785 786 if (privateKey instanceof ECPrivateKey) { 787 validateEcCurves(ecPubKey, (ECPrivateKey) privateKey); 788 } 789 if (privateKey != null && !(privateKey instanceof ECPrivateKey)) { 790 throw new JOSEException("Unsupported EC private key type: " + privateKey); 791 } 792 793 final Curve curve = Curve.forECParameterSpec(pubParams); 794 final ECKey.Builder builder = new ECKey.Builder(curve, (ECPublicKey) publicKey); 795 796 if (privateKey != null) { 797 builder.privateKey((ECPrivateKey) privateKey); 798 } 799 return builder.build(); 800 } 801 802 if (publicKey instanceof RSAPublicKey) { 803 final RSAKey.Builder builder = new RSAKey.Builder((RSAPublicKey) publicKey); 804 if (privateKey instanceof RSAPrivateKey) { 805 builder.privateKey((RSAPrivateKey) privateKey); 806 } else if (privateKey != null) { 807 throw new JOSEException("Unsupported RSA private key type: " + privateKey); 808 } 809 return builder.build(); 810 } 811 812 throw new JOSEException("Unsupported algorithm of PEM-encoded key: " + publicKey.getAlgorithm()); 813 } 814 815 816 private static void validateEcCurves(ECPublicKey publicKey, ECPrivateKey privateKey) throws JOSEException { 817 final ECParameterSpec pubParams = publicKey.getParams(); 818 final ECParameterSpec privParams = privateKey.getParams(); 819 if (!pubParams.getCurve().equals(privParams.getCurve())) { 820 throw new JOSEException("Public/private EC key curve mismatch: " + publicKey); 821 } 822 if (pubParams.getCofactor() != privParams.getCofactor()) { 823 throw new JOSEException("Public/private EC key cofactor mismatch: " + publicKey); 824 } 825 if (!pubParams.getGenerator().equals(privParams.getGenerator())) { 826 throw new JOSEException("Public/private EC key generator mismatch: " + publicKey); 827 } 828 if (!pubParams.getOrder().equals(privParams.getOrder())) { 829 throw new JOSEException("Public/private EC key order mismatch: " + publicKey); 830 } 831 } 832 833 834 private static KeyPair mergeKeyPairs(final List<KeyPair> keys) throws JOSEException { 835 final KeyPair pair; 836 if (keys.size() == 1) { 837 // Assume public key, or private key easy to convert to public, 838 // otherwise not representable as a JWK 839 pair = keys.get(0); 840 } else if (keys.size() == 2) { 841 // If two keys, assume public + private keys separated 842 pair = twoKeysToKeyPair(keys); 843 } else { 844 throw new JOSEException("Expected key or pair of PEM-encoded keys"); 845 } 846 return pair; 847 } 848 849 850 private static KeyPair twoKeysToKeyPair(final List<? extends KeyPair> keys) throws JOSEException { 851 final KeyPair key1 = keys.get(0); 852 final KeyPair key2 = keys.get(1); 853 if (key1.getPublic() != null && key2.getPrivate() != null) { 854 return new KeyPair(key1.getPublic(), key2.getPrivate()); 855 } else if (key1.getPrivate() != null && key2.getPublic() != null) { 856 return new KeyPair(key2.getPublic(), key1.getPrivate()); 857 } else { 858 throw new JOSEException("Not a public/private key pair"); 859 } 860 } 861 862 863 @Override 864 public boolean equals(Object o) { 865 if (this == o) return true; 866 if (!(o instanceof JWK)) return false; 867 JWK jwk = (JWK) o; 868 return Objects.equals(kty, jwk.kty) && 869 Objects.equals(use, jwk.use) && 870 Objects.equals(ops, jwk.ops) && 871 Objects.equals(alg, jwk.alg) && 872 Objects.equals(kid, jwk.kid) && 873 Objects.equals(x5u, jwk.x5u) && 874 Objects.equals(x5t, jwk.x5t) && 875 Objects.equals(x5t256, jwk.x5t256) && 876 Objects.equals(x5c, jwk.x5c) && 877 Objects.equals(keyStore, jwk.keyStore); 878 } 879 880 881 @Override 882 public int hashCode() { 883 return Objects.hash(kty, use, ops, alg, kid, x5u, x5t, x5t256, x5c, keyStore); 884 } 885}