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