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.math.BigInteger; 022import java.net.URI; 023import java.security.*; 024import java.security.cert.Certificate; 025import java.security.cert.CertificateEncodingException; 026import java.security.cert.X509Certificate; 027import java.security.interfaces.ECPrivateKey; 028import java.security.interfaces.ECPublicKey; 029import java.security.spec.*; 030import java.text.ParseException; 031import java.util.*; 032 033import net.jcip.annotations.Immutable; 034import org.bouncycastle.cert.jcajce.JcaX509CertificateHolder; 035 036import com.nimbusds.jose.Algorithm; 037import com.nimbusds.jose.JOSEException; 038import com.nimbusds.jose.crypto.utils.ECChecks; 039import com.nimbusds.jose.util.Base64; 040import com.nimbusds.jose.util.Base64URL; 041import com.nimbusds.jose.util.BigIntegerUtils; 042import com.nimbusds.jose.util.JSONObjectUtils; 043 044 045/** 046 * Public and private {@link KeyType#EC Elliptic Curve} JSON Web Key (JWK). 047 * This class is immutable. 048 * 049 * <p>Supported curves: 050 * 051 * <ul> 052 * <li>{@link Curve#P_256 P-256} 053 * <li>{@link Curve#SECP256K1 secp256k1} 054 * <li>{@link Curve#P_384 P-384} 055 * <li>{@link Curve#P_521 P-512} 056 * </ul> 057 * 058 * <p>Provides EC JWK import from / export to the following standard Java 059 * interfaces and classes: 060 * 061 * <ul> 062 * <li>{@link java.security.interfaces.ECPublicKey} 063 * <li>{@link java.security.interfaces.ECPrivateKey} 064 * <li>{@link java.security.PrivateKey} for an EC key in a PKCS#11 store 065 * <li>{@link java.security.KeyPair} 066 * </ul> 067 * 068 * <p>Example JSON object representation of a public EC JWK: 069 * 070 * <pre> 071 * { 072 * "kty" : "EC", 073 * "crv" : "P-256", 074 * "x" : "MKBCTNIcKUSDii11ySs3526iDZ8AiTo7Tu6KPAqv7D4", 075 * "y" : "4Etl6SRW2YiLUrN5vfvVHuhp7x8PxltmWWlbbM4IFyM", 076 * "use" : "enc", 077 * "kid" : "1" 078 * } 079 * </pre> 080 * 081 * <p>Example JSON object representation of a private EC JWK: 082 * 083 * <pre> 084 * { 085 * "kty" : "EC", 086 * "crv" : "P-256", 087 * "x" : "MKBCTNIcKUSDii11ySs3526iDZ8AiTo7Tu6KPAqv7D4", 088 * "y" : "4Etl6SRW2YiLUrN5vfvVHuhp7x8PxltmWWlbbM4IFyM", 089 * "d" : "870MB6gfuTJ4HtUnUvYMyJpr5eUZNP4Bk43bVdj3eAE", 090 * "use" : "enc", 091 * "kid" : "1" 092 * } 093 * </pre> 094 * 095 * <p>Use the builder to create a new EC JWK: 096 * 097 * <pre> 098 * ECKey key = new ECKey.Builder(Curve.P_256, x, y) 099 * .keyUse(KeyUse.SIGNATURE) 100 * .keyID("1") 101 * .build(); 102 * </pre> 103 * 104 * <p>See http://en.wikipedia.org/wiki/Elliptic_curve_cryptography 105 * 106 * @author Vladimir Dzhuvinov 107 * @author Justin Richer 108 * @version 2020-06-03 109 */ 110@Immutable 111public final class ECKey extends JWK implements AsymmetricJWK, CurveBasedJWK { 112 113 114 private static final long serialVersionUID = 1L; 115 116 117 /** 118 * Supported EC curves. 119 */ 120 public static final Set<Curve> SUPPORTED_CURVES = Collections.unmodifiableSet( 121 new HashSet<>(Arrays.asList(Curve.P_256, Curve.SECP256K1, Curve.P_384, Curve.P_521)) 122 ); 123 124 125 /** 126 * Builder for constructing Elliptic Curve JWKs. 127 * 128 * <p>Example usage: 129 * 130 * <pre> 131 * ECKey key = new ECKey.Builder(Curve.P521, x, y) 132 * .d(d) 133 * .algorithm(JWSAlgorithm.ES512) 134 * .keyID("1") 135 * .build(); 136 * </pre> 137 */ 138 public static class Builder { 139 140 141 /** 142 * The curve name. 143 */ 144 private final Curve crv; 145 146 147 /** 148 * The public 'x' EC coordinate. 149 */ 150 private final Base64URL x; 151 152 153 /** 154 * The public 'y' EC coordinate. 155 */ 156 private final Base64URL y; 157 158 159 /** 160 * The private 'd' EC coordinate, optional. 161 */ 162 private Base64URL d; 163 164 165 /** 166 * The private EC key, as PKCS#11 handle, optional. 167 */ 168 private PrivateKey priv; 169 170 171 /** 172 * The key use, optional. 173 */ 174 private KeyUse use; 175 176 177 /** 178 * The key operations, optional. 179 */ 180 private Set<KeyOperation> ops; 181 182 183 /** 184 * The intended JOSE algorithm for the key, optional. 185 */ 186 private Algorithm alg; 187 188 189 /** 190 * The key ID, optional. 191 */ 192 private String kid; 193 194 195 /** 196 * X.509 certificate URL, optional. 197 */ 198 private URI x5u; 199 200 201 /** 202 * X.509 certificate SHA-1 thumbprint, optional. 203 */ 204 @Deprecated 205 private Base64URL x5t; 206 207 208 /** 209 * X.509 certificate SHA-256 thumbprint, optional. 210 */ 211 private Base64URL x5t256; 212 213 214 /** 215 * The X.509 certificate chain, optional. 216 */ 217 private List<Base64> x5c; 218 219 220 /** 221 * Reference to the underlying key store, {@code null} if none. 222 */ 223 private KeyStore ks; 224 225 226 /** 227 * Creates a new Elliptic Curve JWK builder. 228 * 229 * @param crv The cryptographic curve. Must not be 230 * {@code null}. 231 * @param x The public 'x' coordinate for the elliptic curve 232 * point. It is represented as the Base64URL 233 * encoding of the coordinate's big endian 234 * representation. Must not be {@code null}. 235 * @param y The public 'y' coordinate for the elliptic curve 236 * point. It is represented as the Base64URL 237 * encoding of the coordinate's big endian 238 * representation. Must not be {@code null}. 239 */ 240 public Builder(final Curve crv, final Base64URL x, final Base64URL y) { 241 242 if (crv == null) { 243 throw new IllegalArgumentException("The curve must not be null"); 244 } 245 246 this.crv = crv; 247 248 if (x == null) { 249 throw new IllegalArgumentException("The 'x' coordinate must not be null"); 250 } 251 252 this.x = x; 253 254 if (y == null) { 255 throw new IllegalArgumentException("The 'y' coordinate must not be null"); 256 } 257 258 this.y = y; 259 } 260 261 262 /** 263 * Creates a new Elliptic Curve JWK builder. 264 * 265 * @param crv The cryptographic curve. Must not be 266 * {@code null}. 267 * @param pub The public EC key to represent. Must not be 268 * {@code null}. 269 */ 270 public Builder(final Curve crv, final ECPublicKey pub) { 271 272 this(crv, 273 encodeCoordinate(pub.getParams().getCurve().getField().getFieldSize(), pub.getW().getAffineX()), 274 encodeCoordinate(pub.getParams().getCurve().getField().getFieldSize(), pub.getW().getAffineY())); 275 } 276 277 278 /** 279 * Creates a new Elliptic Curve JWK builder. 280 * 281 * @param ecJWK The EC JWK to start with. Must not be 282 * {@code null}. 283 */ 284 public Builder(final ECKey ecJWK) { 285 286 crv = ecJWK.crv; 287 x = ecJWK.x; 288 y = ecJWK.y; 289 d = ecJWK.d; 290 priv = ecJWK.privateKey; 291 use = ecJWK.getKeyUse(); 292 ops = ecJWK.getKeyOperations(); 293 alg = ecJWK.getAlgorithm(); 294 kid = ecJWK.getKeyID(); 295 x5u = ecJWK.getX509CertURL(); 296 x5t = ecJWK.getX509CertThumbprint(); 297 x5t256 = ecJWK.getX509CertSHA256Thumbprint(); 298 x5c = ecJWK.getX509CertChain(); 299 ks = ecJWK.getKeyStore(); 300 } 301 302 303 /** 304 * Sets the private 'd' coordinate for the elliptic curve 305 * point. The alternative method is {@link #privateKey}. 306 * 307 * @param d The private 'd' coordinate. It is represented as 308 * the Base64URL encoding of the coordinate's big 309 * endian representation. {@code null} if not 310 * specified (for a public key). 311 * 312 * @return This builder. 313 */ 314 public Builder d(final Base64URL d) { 315 316 this.d = d; 317 return this; 318 } 319 320 321 /** 322 * Sets the private Elliptic Curve key. The alternative method 323 * is {@link #d}. 324 * 325 * @param priv The private EC key, used to obtain the private 326 * 'd' coordinate for the elliptic curve point. 327 * {@code null} if not specified (for a public 328 * key). 329 * 330 * @return This builder. 331 */ 332 public Builder privateKey(final ECPrivateKey priv) { 333 334 if (priv != null) { 335 this.d = encodeCoordinate(priv.getParams().getCurve().getField().getFieldSize(), priv.getS()); 336 } 337 338 return this; 339 } 340 341 342 /** 343 * Sets the private EC key, typically for a key located in a 344 * PKCS#11 store that doesn't expose the private key parameters 345 * (such as a smart card or HSM). 346 * 347 * @param priv The private EC key reference. Its algorithm must 348 * be "EC". Must not be {@code null}. 349 * 350 * @return This builder. 351 */ 352 public Builder privateKey(final PrivateKey priv) { 353 354 if (priv instanceof ECPrivateKey) { 355 return privateKey((ECPrivateKey) priv); 356 } 357 358 if (! "EC".equalsIgnoreCase(priv.getAlgorithm())) { 359 throw new IllegalArgumentException("The private key algorithm must be EC"); 360 } 361 362 this.priv = priv; 363 return this; 364 } 365 366 367 /** 368 * Sets the use ({@code use}) of the JWK. 369 * 370 * @param use The key use, {@code null} if not specified or if 371 * the key is intended for signing as well as 372 * encryption. 373 * 374 * @return This builder. 375 */ 376 public Builder keyUse(final KeyUse use) { 377 378 this.use = use; 379 return this; 380 } 381 382 383 /** 384 * Sets the operations ({@code key_ops}) of the JWK. 385 * 386 * @param ops The key operations, {@code null} if not 387 * specified. 388 * 389 * @return This builder. 390 */ 391 public Builder keyOperations(final Set<KeyOperation> ops) { 392 393 this.ops = ops; 394 return this; 395 } 396 397 398 /** 399 * Sets the intended JOSE algorithm ({@code alg}) for the JWK. 400 * 401 * @param alg The intended JOSE algorithm, {@code null} if not 402 * specified. 403 * 404 * @return This builder. 405 */ 406 public Builder algorithm(final Algorithm alg) { 407 408 this.alg = alg; 409 return this; 410 } 411 412 /** 413 * Sets the ID ({@code kid}) of the JWK. The key ID can be used 414 * to match a specific key. This can be used, for instance, to 415 * choose a key within a {@link JWKSet} during key rollover. 416 * The key ID may also correspond to a JWS/JWE {@code kid} 417 * header parameter value. 418 * 419 * @param kid The key ID, {@code null} if not specified. 420 * 421 * @return This builder. 422 */ 423 public Builder keyID(final String kid) { 424 425 this.kid = kid; 426 return this; 427 } 428 429 430 /** 431 * Sets the ID ({@code kid}) of the JWK to its SHA-256 JWK 432 * thumbprint (RFC 7638). The key ID can be used to match a 433 * specific key. This can be used, for instance, to choose a 434 * key within a {@link JWKSet} during key rollover. The key ID 435 * may also correspond to a JWS/JWE {@code kid} header 436 * parameter value. 437 * 438 * @return This builder. 439 * 440 * @throws JOSEException If the SHA-256 hash algorithm is not 441 * supported. 442 */ 443 public Builder keyIDFromThumbprint() 444 throws JOSEException { 445 446 return keyIDFromThumbprint("SHA-256"); 447 } 448 449 450 /** 451 * Sets the ID ({@code kid}) of the JWK to its JWK thumbprint 452 * (RFC 7638). The key ID can be used to match a specific key. 453 * This can be used, for instance, to choose a key within a 454 * {@link JWKSet} during key rollover. The key ID may also 455 * correspond to a JWS/JWE {@code kid} header parameter value. 456 * 457 * @param hashAlg The hash algorithm for the JWK thumbprint 458 * computation. Must not be {@code null}. 459 * 460 * @return This builder. 461 * 462 * @throws JOSEException If the hash algorithm is not 463 * supported. 464 */ 465 public Builder keyIDFromThumbprint(final String hashAlg) 466 throws JOSEException { 467 468 // Put mandatory params in sorted order 469 LinkedHashMap<String,String> requiredParams = new LinkedHashMap<>(); 470 requiredParams.put(JWKParameterNames.ELLIPTIC_CURVE, crv.toString()); 471 requiredParams.put(JWKParameterNames.KEY_TYPE, KeyType.EC.getValue()); 472 requiredParams.put(JWKParameterNames.ELLIPTIC_CURVE_X_COORDINATE, x.toString()); 473 requiredParams.put(JWKParameterNames.ELLIPTIC_CURVE_Y_COORDINATE, y.toString()); 474 this.kid = ThumbprintUtils.compute(hashAlg, requiredParams).toString(); 475 return this; 476 } 477 478 479 /** 480 * Sets the X.509 certificate URL ({@code x5u}) of the JWK. 481 * 482 * @param x5u The X.509 certificate URL, {@code null} if not 483 * specified. 484 * 485 * @return This builder. 486 */ 487 public Builder x509CertURL(final URI x5u) { 488 489 this.x5u = x5u; 490 return this; 491 } 492 493 494 /** 495 * Sets the X.509 certificate SHA-1 thumbprint ({@code x5t}) of 496 * the JWK. 497 * 498 * @param x5t The X.509 certificate SHA-1 thumbprint, 499 * {@code null} if not specified. 500 * 501 * @return This builder. 502 */ 503 @Deprecated 504 public Builder x509CertThumbprint(final Base64URL x5t) { 505 506 this.x5t = x5t; 507 return this; 508 } 509 510 511 /** 512 * Sets the X.509 certificate SHA-256 thumbprint 513 * ({@code x5t#S256}) of the JWK. 514 * 515 * @param x5t256 The X.509 certificate SHA-256 thumbprint, 516 * {@code null} if not specified. 517 * 518 * @return This builder. 519 */ 520 public Builder x509CertSHA256Thumbprint(final Base64URL x5t256) { 521 522 this.x5t256 = x5t256; 523 return this; 524 } 525 526 527 /** 528 * Sets the X.509 certificate chain ({@code x5c}) of the JWK. 529 * 530 * @param x5c The X.509 certificate chain as a unmodifiable 531 * list, {@code null} if not specified. 532 * 533 * @return This builder. 534 */ 535 public Builder x509CertChain(final List<Base64> x5c) { 536 537 this.x5c = x5c; 538 return this; 539 } 540 541 542 /** 543 * Sets the underlying key store. 544 * 545 * @param keyStore Reference to the underlying key store, 546 * {@code null} if none. 547 * 548 * @return This builder. 549 */ 550 public Builder keyStore(final KeyStore keyStore) { 551 552 this.ks = keyStore; 553 return this; 554 } 555 556 557 /** 558 * Builds a new Elliptic Curve JWK. 559 * 560 * @return The Elliptic Curve JWK. 561 * 562 * @throws IllegalStateException If the JWK parameters were 563 * inconsistently specified. 564 */ 565 public ECKey build() { 566 567 try { 568 if (d == null && priv == null) { 569 // Public key 570 return new ECKey(crv, x, y, use, ops, alg, kid, x5u, x5t, x5t256, x5c, ks); 571 } 572 573 if (priv != null) { 574 // PKCS#11 reference to private key 575 return new ECKey(crv, x, y, priv, use, ops, alg, kid, x5u, x5t, x5t256, x5c, ks); 576 } 577 578 // Public / private key pair with 'd' 579 return new ECKey(crv, x, y, d, use, ops, alg, kid, x5u, x5t, x5t256, x5c, ks); 580 581 } catch (IllegalArgumentException e) { 582 throw new IllegalStateException(e.getMessage(), e); 583 } 584 } 585 } 586 587 588 /** 589 * Returns the Base64URL encoding of the specified elliptic curve 'x', 590 * 'y' or 'd' coordinate, with leading zero padding up to the specified 591 * field size in bits. 592 * 593 * @param fieldSize The field size in bits. 594 * @param coordinate The elliptic curve coordinate. Must not be 595 * {@code null}. 596 * 597 * @return The Base64URL-encoded coordinate, with leading zero padding 598 * up to the curve's field size. 599 */ 600 public static Base64URL encodeCoordinate(final int fieldSize, final BigInteger coordinate) { 601 602 final byte[] notPadded = BigIntegerUtils.toBytesUnsigned(coordinate); 603 604 int bytesToOutput = (fieldSize + 7)/8; 605 606 if (notPadded.length >= bytesToOutput) { 607 // Greater-than check to prevent exception on malformed 608 // key below 609 return Base64URL.encode(notPadded); 610 } 611 612 final byte[] padded = new byte[bytesToOutput]; 613 614 System.arraycopy(notPadded, 0, padded, bytesToOutput - notPadded.length, notPadded.length); 615 616 return Base64URL.encode(padded); 617 } 618 619 620 /** 621 * The curve name. 622 */ 623 private final Curve crv; 624 625 626 /** 627 * The public 'x' EC coordinate. 628 */ 629 private final Base64URL x; 630 631 632 /** 633 * The public 'y' EC coordinate. 634 */ 635 private final Base64URL y; 636 637 638 /** 639 * The private 'd' EC coordinate. 640 */ 641 private final Base64URL d; 642 643 644 /** 645 * Private PKCS#11 key handle. 646 */ 647 private final PrivateKey privateKey; 648 649 650 /** 651 * Ensures the specified 'x' and 'y' public coordinates are on the 652 * given curve. 653 * 654 * @param crv The curve. Must not be {@code null}. 655 * @param x The public 'x' coordinate. Must not be {@code null}. 656 * @param y The public 'y' coordinate. Must not be {@code null}. 657 */ 658 private static void ensurePublicCoordinatesOnCurve(final Curve crv, final Base64URL x, final Base64URL y) { 659 660 if (! SUPPORTED_CURVES.contains(crv)) { 661 throw new IllegalArgumentException("Unknown / unsupported curve: " + crv); 662 } 663 664 if (! ECChecks.isPointOnCurve(x.decodeToBigInteger(), y.decodeToBigInteger(), crv.toECParameterSpec())) { 665 throw new IllegalArgumentException("Invalid EC JWK: The 'x' and 'y' public coordinates are not on the " + crv + " curve"); 666 } 667 } 668 669 670 /** 671 * Creates a new public Elliptic Curve JSON Web Key (JWK) with the 672 * specified parameters. 673 * 674 * @param crv The cryptographic curve. Must not be {@code null}. 675 * @param x The public 'x' coordinate for the elliptic curve 676 * point. It is represented as the Base64URL encoding of 677 * the coordinate's big endian representation. Must not 678 * be {@code null}. 679 * @param y The public 'y' coordinate for the elliptic curve 680 * point. It is represented as the Base64URL encoding of 681 * the coordinate's big endian representation. Must not 682 * be {@code null}. 683 * @param use The key use, {@code null} if not specified or if the 684 * key is intended for signing as well as encryption. 685 * @param ops The key operations, {@code null} if not specified. 686 * @param alg The intended JOSE algorithm for the key, {@code null} 687 * if not specified. 688 * @param kid The key ID, {@code null} if not specified. 689 * @param x5u The X.509 certificate URL, {@code null} if not 690 * specified. 691 * @param x5t The X.509 certificate SHA-1 thumbprint, {@code null} 692 * if not specified. 693 * @param x5t256 The X.509 certificate SHA-256 thumbprint, {@code null} 694 * if not specified. 695 * @param x5c The X.509 certificate chain, {@code null} if not 696 * specified. 697 * @param ks Reference to the underlying key store, {@code null} if 698 * not specified. 699 */ 700 public ECKey(final Curve crv, final Base64URL x, final Base64URL y, 701 final KeyUse use, final Set<KeyOperation> ops, final Algorithm alg, final String kid, 702 final URI x5u, final Base64URL x5t, final Base64URL x5t256, final List<Base64> x5c, 703 final KeyStore ks) { 704 705 super(KeyType.EC, use, ops, alg, kid, x5u, x5t, x5t256, x5c, ks); 706 707 if (crv == null) { 708 throw new IllegalArgumentException("The curve must not be null"); 709 } 710 711 this.crv = crv; 712 713 if (x == null) { 714 throw new IllegalArgumentException("The 'x' coordinate must not be null"); 715 } 716 717 this.x = x; 718 719 if (y == null) { 720 throw new IllegalArgumentException("The 'y' coordinate must not be null"); 721 } 722 723 this.y = y; 724 725 ensurePublicCoordinatesOnCurve(crv, x, y); 726 727 ensureMatches(getParsedX509CertChain()); 728 729 this.d = null; 730 731 this.privateKey = null; 732 } 733 734 735 /** 736 * Creates a new public / private Elliptic Curve JSON Web Key (JWK) 737 * with the specified parameters. 738 * 739 * @param crv The cryptographic curve. Must not be {@code null}. 740 * @param x The public 'x' coordinate for the elliptic curve 741 * point. It is represented as the Base64URL encoding of 742 * the coordinate's big endian representation. Must not 743 * be {@code null}. 744 * @param y The public 'y' coordinate for the elliptic curve 745 * point. It is represented as the Base64URL encoding of 746 * the coordinate's big endian representation. Must not 747 * be {@code null}. 748 * @param d The private 'd' coordinate for the elliptic curve 749 * point. It is represented as the Base64URL encoding of 750 * the coordinate's big endian representation. Must not 751 * be {@code null}. 752 * @param use The key use, {@code null} if not specified or if the 753 * key is intended for signing as well as encryption. 754 * @param ops The key operations, {@code null} if not specified. 755 * @param alg The intended JOSE algorithm for the key, {@code null} 756 * if not specified. 757 * @param kid The key ID, {@code null} if not specified. 758 * @param x5u The X.509 certificate URL, {@code null} if not 759 * specified. 760 * @param x5t The X.509 certificate SHA-1 thumbprint, {@code null} 761 * if not specified. 762 * @param x5t256 The X.509 certificate SHA-256 thumbprint, {@code null} 763 * if not specified. 764 * @param x5c The X.509 certificate chain, {@code null} if not 765 * specified. 766 * @param ks Reference to the underlying key store, {@code null} if 767 * not specified. 768 */ 769 public ECKey(final Curve crv, final Base64URL x, final Base64URL y, final Base64URL d, 770 final KeyUse use, final Set<KeyOperation> ops, final Algorithm alg, final String kid, 771 final URI x5u, final Base64URL x5t, final Base64URL x5t256, final List<Base64> x5c, 772 final KeyStore ks) { 773 774 super(KeyType.EC, use, ops, alg, kid, x5u, x5t, x5t256, x5c, ks); 775 776 if (crv == null) { 777 throw new IllegalArgumentException("The curve must not be null"); 778 } 779 780 this.crv = crv; 781 782 if (x == null) { 783 throw new IllegalArgumentException("The 'x' coordinate must not be null"); 784 } 785 786 this.x = x; 787 788 if (y == null) { 789 throw new IllegalArgumentException("The 'y' coordinate must not be null"); 790 } 791 792 this.y = y; 793 794 ensurePublicCoordinatesOnCurve(crv, x, y); 795 796 ensureMatches(getParsedX509CertChain()); 797 798 if (d == null) { 799 throw new IllegalArgumentException("The 'd' coordinate must not be null"); 800 } 801 802 this.d = d; 803 804 this.privateKey = null; 805 } 806 807 808 /** 809 * Creates a new public / private Elliptic Curve JSON Web Key (JWK) 810 * with the specified parameters. The private key is specified by its 811 * PKCS#11 handle. 812 * 813 * @param crv The cryptographic curve. Must not be {@code null}. 814 * @param x The public 'x' coordinate for the elliptic curve 815 * point. It is represented as the Base64URL encoding of 816 * the coordinate's big endian representation. Must not 817 * be {@code null}. 818 * @param y The public 'y' coordinate for the elliptic curve 819 * point. It is represented as the Base64URL encoding of 820 * the coordinate's big endian representation. Must not 821 * be {@code null}. 822 * @param priv The private key as a PKCS#11 handle, {@code null} if 823 * not specified. 824 * @param use The key use, {@code null} if not specified or if the 825 * key is intended for signing as well as encryption. 826 * @param ops The key operations, {@code null} if not specified. 827 * @param alg The intended JOSE algorithm for the key, {@code null} 828 * if not specified. 829 * @param kid The key ID, {@code null} if not specified. 830 * @param x5u The X.509 certificate URL, {@code null} if not 831 * specified. 832 * @param x5t The X.509 certificate SHA-1 thumbprint, {@code null} 833 * if not specified. 834 * @param x5t256 The X.509 certificate SHA-256 thumbprint, {@code null} 835 * if not specified. 836 * @param x5c The X.509 certificate chain, {@code null} if not 837 * specified. 838 */ 839 public ECKey(final Curve crv, final Base64URL x, final Base64URL y, final PrivateKey priv, 840 final KeyUse use, final Set<KeyOperation> ops, final Algorithm alg, final String kid, 841 final URI x5u, final Base64URL x5t, final Base64URL x5t256, final List<Base64> x5c, 842 final KeyStore ks) { 843 844 super(KeyType.EC, use, ops, alg, kid, x5u, x5t, x5t256, x5c, ks); 845 846 if (crv == null) { 847 throw new IllegalArgumentException("The curve must not be null"); 848 } 849 850 this.crv = crv; 851 852 if (x == null) { 853 throw new IllegalArgumentException("The 'x' coordinate must not be null"); 854 } 855 856 this.x = x; 857 858 if (y == null) { 859 throw new IllegalArgumentException("The 'y' coordinate must not be null"); 860 } 861 862 this.y = y; 863 864 ensurePublicCoordinatesOnCurve(crv, x, y); 865 866 ensureMatches(getParsedX509CertChain()); 867 868 d = null; 869 870 this.privateKey = priv; 871 } 872 873 874 /** 875 * Creates a new public Elliptic Curve JSON Web Key (JWK) with the 876 * specified parameters. 877 * 878 * @param crv The cryptographic curve. Must not be {@code null}. 879 * @param pub The public EC key to represent. Must not be 880 * {@code null}. 881 * @param use The key use, {@code null} if not specified or if the 882 * key is intended for signing as well as encryption. 883 * @param ops The key operations, {@code null} if not specified. 884 * @param alg The intended JOSE algorithm for the key, {@code null} 885 * if not specified. 886 * @param kid The key ID, {@code null} if not specified. 887 * @param x5u The X.509 certificate URL, {@code null} if not 888 * specified. 889 * @param x5t The X.509 certificate SHA-1 thumbprint, {@code null} 890 * if not specified. 891 * @param x5t256 The X.509 certificate SHA-256 thumbprint, {@code null} 892 * if not specified. 893 * @param x5c The X.509 certificate chain, {@code null} if not 894 * specified. 895 * @param ks Reference to the underlying key store, {@code null} if 896 * not specified. 897 */ 898 public ECKey(final Curve crv, final ECPublicKey pub, 899 final KeyUse use, final Set<KeyOperation> ops, final Algorithm alg, final String kid, 900 final URI x5u, final Base64URL x5t, final Base64URL x5t256, final List<Base64> x5c, 901 final KeyStore ks) { 902 903 this(crv, 904 encodeCoordinate(pub.getParams().getCurve().getField().getFieldSize(), pub.getW().getAffineX()), 905 encodeCoordinate(pub.getParams().getCurve().getField().getFieldSize(), pub.getW().getAffineY()), 906 use, ops, alg, kid, 907 x5u, x5t, x5t256, x5c, 908 ks); 909 } 910 911 912 /** 913 * Creates a new public / private Elliptic Curve JSON Web Key (JWK) 914 * with the specified parameters. 915 * 916 * @param crv The cryptographic curve. Must not be {@code null}. 917 * @param pub The public EC key to represent. Must not be 918 * {@code null}. 919 * @param priv The private EC key to represent. Must not be 920 * {@code null}. 921 * @param use The key use, {@code null} if not specified or if the 922 * key is intended for signing as well as encryption. 923 * @param ops The key operations, {@code null} if not specified. 924 * @param alg The intended JOSE algorithm for the key, {@code null} 925 * if not specified. 926 * @param kid The key ID, {@code null} if not specified. 927 * @param x5u The X.509 certificate URL, {@code null} if not 928 * specified. 929 * @param x5t The X.509 certificate SHA-1 thumbprint, {@code null} 930 * if not specified. 931 * @param x5t256 The X.509 certificate SHA-256 thumbprint, {@code null} 932 * if not specified. 933 * @param x5c The X.509 certificate chain, {@code null} if not 934 * specified. 935 * @param ks Reference to the underlying key store, {@code null} if 936 * not specified. 937 */ 938 public ECKey(final Curve crv, final ECPublicKey pub, final ECPrivateKey priv, 939 final KeyUse use, final Set<KeyOperation> ops, final Algorithm alg, final String kid, 940 final URI x5u, final Base64URL x5t, final Base64URL x5t256, final List<Base64> x5c, 941 final KeyStore ks) { 942 943 this(crv, 944 encodeCoordinate(pub.getParams().getCurve().getField().getFieldSize(), pub.getW().getAffineX()), 945 encodeCoordinate(pub.getParams().getCurve().getField().getFieldSize(), pub.getW().getAffineY()), 946 encodeCoordinate(priv.getParams().getCurve().getField().getFieldSize(), priv.getS()), 947 use, ops, alg, kid, 948 x5u, x5t, x5t256, x5c, 949 ks); 950 } 951 952 953 /** 954 * Creates a new public / private Elliptic Curve JSON Web Key (JWK) 955 * with the specified parameters. The private key is specified by its 956 * PKCS#11 handle. 957 * 958 * @param crv The cryptographic curve. Must not be {@code null}. 959 * @param pub The public EC key to represent. Must not be 960 * {@code null}. 961 * @param priv The private key as a PKCS#11 handle, {@code null} if 962 * not specified. 963 * @param use The key use, {@code null} if not specified or if the 964 * key is intended for signing as well as encryption. 965 * @param ops The key operations, {@code null} if not specified. 966 * @param alg The intended JOSE algorithm for the key, {@code null} 967 * if not specified. 968 * @param kid The key ID, {@code null} if not specified. 969 * @param x5u The X.509 certificate URL, {@code null} if not 970 * specified. 971 * @param x5t The X.509 certificate SHA-1 thumbprint, {@code null} 972 * if not specified. 973 * @param x5t256 The X.509 certificate SHA-256 thumbprint, {@code null} 974 * if not specified. 975 * @param x5c The X.509 certificate chain, {@code null} if not 976 * specified. 977 * @param ks Reference to the underlying key store, {@code null} if 978 * not specified. 979 */ 980 public ECKey(final Curve crv, final ECPublicKey pub, final PrivateKey priv, 981 final KeyUse use, final Set<KeyOperation> ops, final Algorithm alg, final String kid, 982 final URI x5u, final Base64URL x5t, final Base64URL x5t256, final List<Base64> x5c, 983 final KeyStore ks) { 984 985 this( 986 crv, 987 encodeCoordinate(pub.getParams().getCurve().getField().getFieldSize(), pub.getW().getAffineX()), 988 encodeCoordinate(pub.getParams().getCurve().getField().getFieldSize(), pub.getW().getAffineY()), 989 priv, 990 use, ops, alg, kid, x5u, x5t, x5t256, x5c, 991 ks); 992 } 993 994 995 @Override 996 public Curve getCurve() { 997 998 return crv; 999 } 1000 1001 1002 /** 1003 * Gets the public 'x' coordinate for the elliptic curve point. 1004 * 1005 * @return The 'x' coordinate. It is represented as the Base64URL 1006 * encoding of the coordinate's big endian representation. 1007 */ 1008 public Base64URL getX() { 1009 1010 return x; 1011 } 1012 1013 1014 /** 1015 * Gets the public 'y' coordinate for the elliptic curve point. 1016 * 1017 * @return The 'y' coordinate. It is represented as the Base64URL 1018 * encoding of the coordinate's big endian representation. 1019 */ 1020 public Base64URL getY() { 1021 1022 return y; 1023 } 1024 1025 1026 /** 1027 * Gets the private 'd' coordinate for the elliptic curve point. It is 1028 * represented as the Base64URL encoding of the coordinate's big endian 1029 * representation. 1030 * 1031 * @return The 'd' coordinate. It is represented as the Base64URL 1032 * encoding of the coordinate's big endian representation. 1033 * {@code null} if not specified (for a public key). 1034 */ 1035 public Base64URL getD() { 1036 1037 return d; 1038 } 1039 1040 1041 /** 1042 * Returns a standard {@code java.security.interfaces.ECPublicKey} 1043 * representation of this Elliptic Curve JWK. Uses the default JCA 1044 * provider. 1045 * 1046 * @return The public Elliptic Curve key. 1047 * 1048 * @throws JOSEException If EC is not supported by the underlying Java 1049 * Cryptography (JCA) provider or if the JWK 1050 * parameters are invalid for a public EC key. 1051 */ 1052 public ECPublicKey toECPublicKey() 1053 throws JOSEException { 1054 1055 return toECPublicKey(null); 1056 } 1057 1058 1059 /** 1060 * Returns a standard {@code java.security.interfaces.ECPublicKey} 1061 * representation of this Elliptic Curve JWK. 1062 * 1063 * @param provider The specific JCA provider to use, {@code null} 1064 * implies the default one. 1065 * 1066 * @return The public Elliptic Curve key. 1067 * 1068 * @throws JOSEException If EC is not supported by the underlying Java 1069 * Cryptography (JCA) provider or if the JWK 1070 * parameters are invalid for a public EC key. 1071 */ 1072 public ECPublicKey toECPublicKey(final Provider provider) 1073 throws JOSEException { 1074 1075 ECParameterSpec spec = crv.toECParameterSpec(); 1076 1077 if (spec == null) { 1078 throw new JOSEException("Couldn't get EC parameter spec for curve " + crv); 1079 } 1080 1081 ECPoint w = new ECPoint(x.decodeToBigInteger(), y.decodeToBigInteger()); 1082 1083 ECPublicKeySpec publicKeySpec = new ECPublicKeySpec(w, spec); 1084 1085 try { 1086 KeyFactory keyFactory; 1087 1088 if (provider == null) { 1089 keyFactory = KeyFactory.getInstance("EC"); 1090 } else { 1091 keyFactory = KeyFactory.getInstance("EC", provider); 1092 } 1093 1094 return (ECPublicKey) keyFactory.generatePublic(publicKeySpec); 1095 1096 } catch (NoSuchAlgorithmException | InvalidKeySpecException e) { 1097 1098 throw new JOSEException(e.getMessage(), e); 1099 } 1100 } 1101 1102 1103 /** 1104 * Returns a standard {@code java.security.interfaces.ECPrivateKey} 1105 * representation of this Elliptic Curve JWK. Uses the default JCA 1106 * provider. 1107 * 1108 * @return The private Elliptic Curve key, {@code null} if not 1109 * specified by this JWK. 1110 * 1111 * @throws JOSEException If EC is not supported by the underlying Java 1112 * Cryptography (JCA) provider or if the JWK 1113 * parameters are invalid for a private EC key. 1114 */ 1115 public ECPrivateKey toECPrivateKey() 1116 throws JOSEException { 1117 1118 return toECPrivateKey(null); 1119 } 1120 1121 1122 /** 1123 * Returns a standard {@code java.security.interfaces.ECPrivateKey} 1124 * representation of this Elliptic Curve JWK. 1125 * 1126 * @param provider The specific JCA provider to use, {@code null} 1127 * implies the default one. 1128 * 1129 * @return The private Elliptic Curve key, {@code null} if not 1130 * specified by this JWK. 1131 * 1132 * @throws JOSEException If EC is not supported by the underlying Java 1133 * Cryptography (JCA) provider or if the JWK 1134 * parameters are invalid for a private EC key. 1135 */ 1136 public ECPrivateKey toECPrivateKey(final Provider provider) 1137 throws JOSEException { 1138 1139 if (d == null) { 1140 // No private 'd' param 1141 return null; 1142 } 1143 1144 ECParameterSpec spec = crv.toECParameterSpec(); 1145 1146 if (spec == null) { 1147 throw new JOSEException("Couldn't get EC parameter spec for curve " + crv); 1148 } 1149 1150 ECPrivateKeySpec privateKeySpec = new ECPrivateKeySpec(d.decodeToBigInteger(), spec); 1151 1152 try { 1153 KeyFactory keyFactory; 1154 1155 if (provider == null) { 1156 keyFactory = KeyFactory.getInstance("EC"); 1157 } else { 1158 keyFactory = KeyFactory.getInstance("EC", provider); 1159 } 1160 1161 return (ECPrivateKey) keyFactory.generatePrivate(privateKeySpec); 1162 1163 } catch (NoSuchAlgorithmException | InvalidKeySpecException e) { 1164 1165 throw new JOSEException(e.getMessage(), e); 1166 } 1167 } 1168 1169 1170 @Override 1171 public PublicKey toPublicKey() 1172 throws JOSEException { 1173 1174 return toECPublicKey(); 1175 } 1176 1177 1178 @Override 1179 public PrivateKey toPrivateKey() 1180 throws JOSEException { 1181 1182 PrivateKey prv = toECPrivateKey(); 1183 1184 if (prv != null) { 1185 // Return private EC key with key material 1186 return prv; 1187 } 1188 1189 // Return private EC key as PKCS#11 handle, or null 1190 return privateKey; 1191 } 1192 1193 1194 /** 1195 * Returns a standard {@code java.security.KeyPair} representation of 1196 * this Elliptic Curve JWK. Uses the default JCA provider. 1197 * 1198 * @return The Elliptic Curve key pair. The private Elliptic Curve key 1199 * will be {@code null} if not specified. 1200 * 1201 * @throws JOSEException If EC is not supported by the underlying Java 1202 * Cryptography (JCA) provider or if the JWK 1203 * parameters are invalid for a public and / or 1204 * private EC key. 1205 */ 1206 @Override 1207 public KeyPair toKeyPair() 1208 throws JOSEException { 1209 1210 return toKeyPair(null); 1211 } 1212 1213 1214 /** 1215 * Returns a standard {@code java.security.KeyPair} representation of 1216 * this Elliptic Curve JWK. 1217 * 1218 * @param provider The specific JCA provider to use, {@code null} 1219 * implies the default one. 1220 * 1221 * @return The Elliptic Curve key pair. The private Elliptic Curve key 1222 * will be {@code null} if not specified. 1223 * 1224 * @throws JOSEException If EC is not supported by the underlying Java 1225 * Cryptography (JCA) provider or if the JWK 1226 * parameters are invalid for a public and / or 1227 * private EC key. 1228 */ 1229 public KeyPair toKeyPair(final Provider provider) 1230 throws JOSEException { 1231 1232 if (privateKey != null) { 1233 // Private key as PKCS#11 handle 1234 return new KeyPair(toECPublicKey(provider), privateKey); 1235 } else { 1236 return new KeyPair(toECPublicKey(provider), toECPrivateKey(provider)); 1237 } 1238 } 1239 1240 1241 @Override 1242 public boolean matches(final X509Certificate cert) { 1243 1244 ECPublicKey certECKey; 1245 try { 1246 certECKey = (ECPublicKey) getParsedX509CertChain().get(0).getPublicKey(); 1247 } catch (ClassCastException ex) { 1248 return false; 1249 } 1250 // Compare Big Ints, base64url encoding may have padding! 1251 // https://tools.ietf.org/html/rfc7518#section-6.2.1.2 1252 if (! getX().decodeToBigInteger().equals(certECKey.getW().getAffineX())) { 1253 return false; 1254 } 1255 return getY().decodeToBigInteger().equals(certECKey.getW().getAffineY()); 1256 } 1257 1258 1259 /** 1260 * Calls {@link #matches(X509Certificate)} for the first X.509 1261 * certificate in the specified chain. 1262 * 1263 * @param chain The X.509 certificate chain, {@code null} if not 1264 * specified. 1265 * 1266 * @throws IllegalArgumentException If a certificate chain is specified 1267 * and the first certificate in it 1268 * doesn't match. 1269 */ 1270 private void ensureMatches(final List<X509Certificate> chain) { 1271 1272 if (chain == null) 1273 return; 1274 1275 if (! matches(chain.get(0))) 1276 throw new IllegalArgumentException("The public subject key info of the first X.509 certificate in the chain must match the JWK type and public parameters"); 1277 } 1278 1279 1280 @Override 1281 public LinkedHashMap<String,?> getRequiredParams() { 1282 1283 // Put mandatory params in sorted order 1284 LinkedHashMap<String,String> requiredParams = new LinkedHashMap<>(); 1285 requiredParams.put(JWKParameterNames.ELLIPTIC_CURVE, crv.toString()); 1286 requiredParams.put(JWKParameterNames.KEY_TYPE, getKeyType().getValue()); 1287 requiredParams.put(JWKParameterNames.ELLIPTIC_CURVE_X_COORDINATE, x.toString()); 1288 requiredParams.put(JWKParameterNames.ELLIPTIC_CURVE_Y_COORDINATE, y.toString()); 1289 return requiredParams; 1290 } 1291 1292 1293 @Override 1294 public boolean isPrivate() { 1295 1296 return d != null || privateKey != null; 1297 } 1298 1299 1300 @Override 1301 public int size() { 1302 1303 ECParameterSpec ecParameterSpec = crv.toECParameterSpec(); 1304 1305 if (ecParameterSpec == null) { 1306 throw new UnsupportedOperationException("Couldn't determine field size for curve " + crv.getName()); 1307 } 1308 1309 return ecParameterSpec.getCurve().getField().getFieldSize(); 1310 } 1311 1312 1313 /** 1314 * Returns a copy of this Elliptic Curve JWK with any private values 1315 * removed. 1316 * 1317 * @return The copied public Elliptic Curve JWK. 1318 */ 1319 @Override 1320 public ECKey toPublicJWK() { 1321 1322 return new ECKey( 1323 getCurve(), getX(), getY(), 1324 getKeyUse(), getKeyOperations(), getAlgorithm(), getKeyID(), 1325 getX509CertURL(), getX509CertThumbprint(), getX509CertSHA256Thumbprint(), getX509CertChain(), 1326 getKeyStore()); 1327 } 1328 1329 1330 @Override 1331 public Map<String, Object> toJSONObject() { 1332 1333 Map<String, Object> o = super.toJSONObject(); 1334 1335 // Append EC specific attributes 1336 o.put(JWKParameterNames.ELLIPTIC_CURVE, crv.toString()); 1337 o.put(JWKParameterNames.ELLIPTIC_CURVE_X_COORDINATE, x.toString()); 1338 o.put(JWKParameterNames.ELLIPTIC_CURVE_Y_COORDINATE, y.toString()); 1339 1340 if (d != null) { 1341 o.put(JWKParameterNames.ELLIPTIC_CURVE_PRIVATE_KEY, d.toString()); 1342 } 1343 1344 return o; 1345 } 1346 1347 1348 /** 1349 * Parses a public / private Elliptic Curve JWK from the specified JSON 1350 * object string representation. 1351 * 1352 * @param s The JSON object string to parse. Must not be {@code null}. 1353 * 1354 * @return The public / private Elliptic Curve JWK. 1355 * 1356 * @throws ParseException If the string couldn't be parsed to an 1357 * Elliptic Curve JWK. 1358 */ 1359 public static ECKey parse(final String s) 1360 throws ParseException { 1361 1362 return parse(JSONObjectUtils.parse(s)); 1363 } 1364 1365 1366 /** 1367 * Parses a public / private Elliptic Curve JWK from the specified JSON 1368 * object representation. 1369 * 1370 * @param jsonObject The JSON object to parse. Must not be 1371 * {@code null}. 1372 * 1373 * @return The public / private Elliptic Curve JWK. 1374 * 1375 * @throws ParseException If the JSON object couldn't be parsed to an 1376 * Elliptic Curve JWK. 1377 */ 1378 public static ECKey parse(final Map<String, Object> jsonObject) 1379 throws ParseException { 1380 1381 // Check key type 1382 if (! KeyType.EC.equals(JWKMetadata.parseKeyType(jsonObject))) { 1383 throw new ParseException("The key type \"kty\" must be EC", 0); 1384 } 1385 1386 // Parse the mandatory public key parameters 1387 Curve crv; 1388 try { 1389 crv = Curve.parse(JSONObjectUtils.getString(jsonObject, JWKParameterNames.ELLIPTIC_CURVE)); 1390 } catch (IllegalArgumentException e) { 1391 throw new ParseException(e.getMessage(), 0); 1392 } 1393 1394 Base64URL x = JSONObjectUtils.getBase64URL(jsonObject, JWKParameterNames.ELLIPTIC_CURVE_X_COORDINATE); 1395 Base64URL y = JSONObjectUtils.getBase64URL(jsonObject, JWKParameterNames.ELLIPTIC_CURVE_Y_COORDINATE); 1396 1397 // Get optional private key 1398 Base64URL d = JSONObjectUtils.getBase64URL(jsonObject, JWKParameterNames.ELLIPTIC_CURVE_PRIVATE_KEY); 1399 1400 try { 1401 if (d == null) { 1402 // Public key 1403 return new ECKey(crv, x, y, 1404 JWKMetadata.parseKeyUse(jsonObject), 1405 JWKMetadata.parseKeyOperations(jsonObject), 1406 JWKMetadata.parseAlgorithm(jsonObject), 1407 JWKMetadata.parseKeyID(jsonObject), 1408 JWKMetadata.parseX509CertURL(jsonObject), 1409 JWKMetadata.parseX509CertThumbprint(jsonObject), 1410 JWKMetadata.parseX509CertSHA256Thumbprint(jsonObject), 1411 JWKMetadata.parseX509CertChain(jsonObject), 1412 null); 1413 1414 } else { 1415 // Key pair 1416 return new ECKey(crv, x, y, d, 1417 JWKMetadata.parseKeyUse(jsonObject), 1418 JWKMetadata.parseKeyOperations(jsonObject), 1419 JWKMetadata.parseAlgorithm(jsonObject), 1420 JWKMetadata.parseKeyID(jsonObject), 1421 JWKMetadata.parseX509CertURL(jsonObject), 1422 JWKMetadata.parseX509CertThumbprint(jsonObject), 1423 JWKMetadata.parseX509CertSHA256Thumbprint(jsonObject), 1424 JWKMetadata.parseX509CertChain(jsonObject), 1425 null); 1426 } 1427 1428 } catch (IllegalArgumentException ex) { 1429 1430 // Missing x or y, conflicting 'use' and 'key_ops' 1431 throw new ParseException(ex.getMessage(), 0); 1432 } 1433 } 1434 1435 1436 /** 1437 * Parses a public Elliptic Curve JWK from the specified X.509 1438 * certificate. Requires BouncyCastle. 1439 * 1440 * <p><strong>Important:</strong> The X.509 certificate is not 1441 * validated! 1442 * 1443 * <p>Sets the following JWK parameters: 1444 * 1445 * <ul> 1446 * <li>The curve is obtained from the subject public key info 1447 * algorithm parameters. 1448 * <li>The JWK use inferred by {@link KeyUse#from}. 1449 * <li>The JWK ID from the X.509 serial number (in base 10). 1450 * <li>The JWK X.509 certificate chain (this certificate only). 1451 * <li>The JWK X.509 certificate SHA-256 thumbprint. 1452 * </ul> 1453 * 1454 * @param cert The X.509 certificate. Must not be {@code null}. 1455 * 1456 * @return The public Elliptic Curve JWK. 1457 * 1458 * @throws JOSEException If parsing failed. 1459 */ 1460 public static ECKey parse(final X509Certificate cert) 1461 throws JOSEException { 1462 1463 if (! (cert.getPublicKey() instanceof ECPublicKey)) { 1464 throw new JOSEException("The public key of the X.509 certificate is not EC"); 1465 } 1466 1467 ECPublicKey publicKey = (ECPublicKey) cert.getPublicKey(); 1468 1469 try { 1470 JcaX509CertificateHolder certHolder = new JcaX509CertificateHolder(cert); 1471 1472 String oid = certHolder.getSubjectPublicKeyInfo().getAlgorithm().getParameters().toString(); 1473 1474 Curve crv = Curve.forOID(oid); 1475 1476 if (crv == null) { 1477 throw new JOSEException("Couldn't determine EC JWK curve for OID " + oid); 1478 } 1479 1480 MessageDigest sha256 = MessageDigest.getInstance("SHA-256"); 1481 1482 return new ECKey.Builder(crv, publicKey) 1483 .keyUse(KeyUse.from(cert)) 1484 .keyID(cert.getSerialNumber().toString(10)) 1485 .x509CertChain(Collections.singletonList(Base64.encode(cert.getEncoded()))) 1486 .x509CertSHA256Thumbprint(Base64URL.encode(sha256.digest(cert.getEncoded()))) 1487 .build(); 1488 } catch (NoSuchAlgorithmException e) { 1489 throw new JOSEException("Couldn't encode x5t parameter: " + e.getMessage(), e); 1490 } catch (CertificateEncodingException e) { 1491 throw new JOSEException("Couldn't encode x5c parameter: " + e.getMessage(), e); 1492 } 1493 } 1494 1495 1496 /** 1497 * Loads a public / private Elliptic Curve JWK from the specified JCA 1498 * key store. Requires BouncyCastle. 1499 * 1500 * <p><strong>Important:</strong> The X.509 certificate is not 1501 * validated! 1502 * 1503 * @param keyStore The key store. Must not be {@code null}. 1504 * @param alias The alias. Must not be {@code null}. 1505 * @param pin The pin to unlock the private key if any, empty or 1506 * {@code null} if not required. 1507 * 1508 * @return The public / private Elliptic Curve JWK., {@code null} if no 1509 * key with the specified alias was found. 1510 * 1511 * @throws KeyStoreException On a key store exception. 1512 * @throws JOSEException If EC key loading failed. 1513 */ 1514 public static ECKey load(final KeyStore keyStore, 1515 final String alias, 1516 final char[] pin) 1517 throws KeyStoreException, JOSEException { 1518 1519 Certificate cert = keyStore.getCertificate(alias); 1520 1521 if (!(cert instanceof X509Certificate)) { 1522 return null; 1523 } 1524 1525 X509Certificate x509Cert = (X509Certificate)cert; 1526 1527 if (! (x509Cert.getPublicKey() instanceof ECPublicKey)) { 1528 throw new JOSEException("Couldn't load EC JWK: The key algorithm is not EC"); 1529 } 1530 1531 ECKey ecJWK = ECKey.parse(x509Cert); 1532 1533 // Let kid=alias 1534 ecJWK = new ECKey.Builder(ecJWK).keyID(alias).keyStore(keyStore).build(); 1535 1536 // Check for private counterpart 1537 Key key; 1538 try { 1539 key = keyStore.getKey(alias, pin); 1540 } catch (UnrecoverableKeyException | NoSuchAlgorithmException e) { 1541 throw new JOSEException("Couldn't retrieve private EC key (bad pin?): " + e.getMessage(), e); 1542 } 1543 1544 if (key instanceof ECPrivateKey) { 1545 // Simple file based key store 1546 return new ECKey.Builder(ecJWK) 1547 .privateKey((ECPrivateKey)key) 1548 .build(); 1549 } else if (key instanceof PrivateKey && "EC".equalsIgnoreCase(key.getAlgorithm())) { 1550 // PKCS#11 store 1551 return new ECKey.Builder(ecJWK) 1552 .privateKey((PrivateKey)key) 1553 .build(); 1554 } else { 1555 return ecJWK; 1556 } 1557 } 1558 1559 1560 @Override 1561 public boolean equals(Object o) { 1562 if (this == o) return true; 1563 if (!(o instanceof ECKey)) return false; 1564 if (!super.equals(o)) return false; 1565 ECKey ecKey = (ECKey) o; 1566 return Objects.equals(crv, ecKey.crv) && 1567 Objects.equals(x, ecKey.x) && 1568 Objects.equals(y, ecKey.y) && 1569 Objects.equals(d, ecKey.d) && 1570 Objects.equals(privateKey, ecKey.privateKey); 1571 } 1572 1573 1574 @Override 1575 public int hashCode() { 1576 return Objects.hash(super.hashCode(), crv, x, y, d, privateKey); 1577 } 1578}