001package com.nimbusds.jose.jwk; 002 003 004import java.math.BigInteger; 005import java.net.URL; 006import java.util.List; 007import java.security.KeyFactory; 008import java.security.KeyPair; 009import java.security.NoSuchAlgorithmException; 010import java.security.interfaces.ECPrivateKey; 011import java.security.interfaces.ECPublicKey; 012import java.security.spec.ECParameterSpec; 013import java.security.spec.ECPoint; 014import java.security.spec.ECPrivateKeySpec; 015import java.security.spec.ECPublicKeySpec; 016import java.security.spec.InvalidKeySpecException; 017import java.text.ParseException; 018import java.util.Set; 019 020import net.jcip.annotations.Immutable; 021 022import net.minidev.json.JSONObject; 023 024import org.bouncycastle.jce.ECNamedCurveTable; 025import org.bouncycastle.jce.spec.ECNamedCurveParameterSpec; 026import org.bouncycastle.jce.spec.ECNamedCurveSpec; 027 028import com.nimbusds.jose.Algorithm; 029import com.nimbusds.jose.crypto.BouncyCastleProviderSingleton; 030import com.nimbusds.jose.util.*; 031 032 033/** 034 * Public and private {@link KeyType#EC Elliptic Curve} JSON Web Key (JWK). 035 * Uses the BouncyCastle.org provider for EC key import and export. This class 036 * is immutable. 037 * 038 * <p>Example JSON object representation of a public EC JWK: 039 * 040 * <pre> 041 * { 042 * "kty" : "EC", 043 * "crv" : "P-256", 044 * "x" : "MKBCTNIcKUSDii11ySs3526iDZ8AiTo7Tu6KPAqv7D4", 045 * "y" : "4Etl6SRW2YiLUrN5vfvVHuhp7x8PxltmWWlbbM4IFyM", 046 * "use" : "enc", 047 * "kid" : "1" 048 * } 049 * </pre> 050 * 051 * <p>Example JSON object representation of a public and private EC JWK: 052 * 053 * <pre> 054 * { 055 * "kty" : "EC", 056 * "crv" : "P-256", 057 * "x" : "MKBCTNIcKUSDii11ySs3526iDZ8AiTo7Tu6KPAqv7D4", 058 * "y" : "4Etl6SRW2YiLUrN5vfvVHuhp7x8PxltmWWlbbM4IFyM", 059 * "d" : "870MB6gfuTJ4HtUnUvYMyJpr5eUZNP4Bk43bVdj3eAE", 060 * "use" : "enc", 061 * "kid" : "1" 062 * } 063 * </pre> 064 * 065 * <p>See http://en.wikipedia.org/wiki/Elliptic_curve_cryptography 066 * 067 * @author Vladimir Dzhuvinov 068 * @author Justin Richer 069 * @version $version$ (2014-04-02) 070 */ 071@Immutable 072public final class ECKey extends JWK { 073 074 075 /** 076 * Cryptographic curve. This class is immutable. 077 * 078 * <p>Includes constants for the following standard cryptographic 079 * curves: 080 * 081 * <ul> 082 * <li>{@link #P_256} 083 * <li>{@link #P_384} 084 * <li>{@link #P_521} 085 * </ul> 086 * 087 * <p>See "Digital Signature Standard (DSS)", FIPS PUB 186-3, June 088 * 2009, National Institute of Standards and Technology (NIST). 089 */ 090 @Immutable 091 public static class Curve { 092 093 094 /** 095 * P-256 curve (secp256r1). 096 */ 097 public static final Curve P_256 = new Curve("P-256", "secp256r1"); 098 099 100 /** 101 * P-384 curve (secp384r1). 102 */ 103 public static final Curve P_384 = new Curve("P-384", "secp384r1"); 104 105 106 /** 107 * P-521 curve (secp521r1). 108 */ 109 public static final Curve P_521 = new Curve("P-521", "secp521r1"); 110 111 112 /** 113 * The JOSE curve name. 114 */ 115 private final String name; 116 117 118 /** 119 * The standard (JCA) curve name, {@code null} if not 120 * specified. 121 */ 122 private final String stdName; 123 124 125 /** 126 * Creates a new cryptographic curve with the specified name. 127 * The standard (JCA) curve name is not unspecified. 128 * 129 * @param name The name of the cryptographic curve. Must not be 130 * {@code null}. 131 */ 132 public Curve(final String name) { 133 134 this(name, null); 135 } 136 137 138 /** 139 * Creates a new cryptographic curve with the specified name. 140 * 141 * @param name The JOSE name of the cryptographic curve. 142 * Must not be {@code null}. 143 * @param stdName The standard (JCA) name of the cryptographic 144 * curve, {@code null} if not specified. 145 */ 146 public Curve(final String name, final String stdName) { 147 148 if (name == null) { 149 throw new IllegalArgumentException("The cryptographic curve name must not be null"); 150 } 151 152 this.name = name; 153 154 this.stdName = stdName; 155 } 156 157 158 /** 159 * Gets the name of this cryptographic curve. 160 * 161 * @return The name. 162 */ 163 public String getName() { 164 165 return name; 166 } 167 168 169 /** 170 * Gets the standard (JCA) name of this cryptographic curve. 171 * 172 * @return The standard (JCA) name. 173 */ 174 public String getStdName() { 175 176 return stdName; 177 } 178 179 180 /** 181 * Gets the Elliptic Curve parameter specification for this 182 * cryptographic curve. 183 * 184 * @return The EC parameter specification, {@code null} if this 185 * cryptographic curve has no standard (JCA) name 186 * specified or if lookup of the EC parameters failed. 187 */ 188 public ECParameterSpec toECParameterSpec() { 189 190 if (stdName == null) { 191 return null; 192 } 193 194 ECNamedCurveParameterSpec curveParams = 195 ECNamedCurveTable.getParameterSpec(stdName); 196 197 if (curveParams == null) { 198 return null; 199 } 200 201 return new ECNamedCurveSpec(curveParams.getName(), 202 curveParams.getCurve(), 203 curveParams.getG(), 204 curveParams.getN()); 205 } 206 207 208 /** 209 * @see #getName 210 */ 211 @Override 212 public String toString() { 213 214 return getName(); 215 } 216 217 218 /** 219 * Overrides {@code Object.equals()}. 220 * 221 * @param object The object to compare to. 222 * 223 * @return {@code true} if the objects have the same value, 224 * otherwise {@code false}. 225 */ 226 @Override 227 public boolean equals(final Object object) { 228 229 return object instanceof Curve && 230 this.toString().equals(object.toString()); 231 } 232 233 234 /** 235 * Parses a cryptographic curve from the specified string. 236 * 237 * @param s The string to parse. Must not be {@code null} or 238 * empty. 239 * 240 * @return The cryptographic curve. 241 */ 242 public static Curve parse(final String s) { 243 244 if (s == null || s.trim().isEmpty()) { 245 throw new IllegalArgumentException("The cryptographic curve string must not be null or empty"); 246 } 247 248 if (s.equals(P_256.getName())) { 249 return P_256; 250 251 } else if (s.equals(P_384.getName())) { 252 return P_384; 253 254 } else if (s.equals(P_521.getName())) { 255 return P_521; 256 257 } else { 258 return new Curve(s); 259 } 260 } 261 262 263 /** 264 * Gets the cryptographic curve for the specified standard 265 * (JCA) name. 266 * 267 * @param stdName The standard (JCA) name. Must not be 268 * {@code null}. 269 * 270 * @throws IllegalArgumentException If no matching JOSE curve 271 * constant could be found. 272 */ 273 public static Curve forStdName(final String stdName) { 274 275 switch (stdName) { 276 case "secp256r1": return P_256; 277 case "secp384r1": return P_384; 278 case "secp521r1": return P_521; 279 default: throw new IllegalArgumentException("No matching curve constant for standard (JCA) name " + stdName); 280 } 281 } 282 } 283 284 285 /** 286 * Builder for constructing Elliptic Curve JWKs. 287 * 288 * <p>Example use: 289 * 290 * <pre> 291 * ECKey key = new ECKey.Builder(Curve.P521, x, y). 292 * d(d). 293 * algorithm(JWSAlgorithm.ES512). 294 * keyID("789"). 295 * build(); 296 * </pre> 297 */ 298 public static class Builder { 299 300 301 /** 302 * The curve name. 303 */ 304 private final Curve crv; 305 306 307 /** 308 * The public 'x' EC coordinate. 309 */ 310 private final Base64URL x; 311 312 313 /** 314 * The public 'y' EC coordinate. 315 */ 316 private final Base64URL y; 317 318 319 /** 320 * The private 'd' EC coordinate, optional. 321 */ 322 private Base64URL d; 323 324 325 /** 326 * The key use, optional. 327 */ 328 private KeyUse use; 329 330 331 /** 332 * The key operations, optional. 333 */ 334 private Set<KeyOperation> ops; 335 336 337 /** 338 * The intended JOSE algorithm for the key, optional. 339 */ 340 private Algorithm alg; 341 342 343 /** 344 * The key ID, optional. 345 */ 346 private String kid; 347 348 349 /** 350 * X.509 certificate URL, optional. 351 */ 352 private URL x5u; 353 354 355 /** 356 * X.509 certificate thumbprint, optional. 357 */ 358 private Base64URL x5t; 359 360 361 /** 362 * The X.509 certificate chain, optional. 363 */ 364 private List<Base64> x5c; 365 366 367 /** 368 * Creates a new Elliptic Curve JWK builder. 369 * 370 * @param crv The cryptographic curve. Must not be 371 * {@code null}. 372 * @param x The public 'x' coordinate for the elliptic curve 373 * point. It is represented as the Base64URL 374 * encoding of the coordinate's big endian 375 * representation. Must not be {@code null}. 376 * @param y The public 'y' coordinate for the elliptic curve 377 * point. It is represented as the Base64URL 378 * encoding of the coordinate's big endian 379 * representation. Must not be {@code null}. 380 */ 381 public Builder(final Curve crv, final Base64URL x, final Base64URL y) { 382 383 if (crv == null) { 384 throw new IllegalArgumentException("The curve must not be null"); 385 } 386 387 this.crv = crv; 388 389 if (x == null) { 390 throw new IllegalArgumentException("The 'x' coordinate must not be null"); 391 } 392 393 this.x = x; 394 395 if (y == null) { 396 throw new IllegalArgumentException("The 'y' coordinate must not be null"); 397 } 398 399 this.y = y; 400 } 401 402 403 /** 404 * Creates a new Elliptic Curve JWK builder. 405 * 406 * @param crv The cryptographic curve. Must not be 407 * {@code null}. 408 * @param pub The public EC key to represent. Must not be 409 * {@code null}. 410 */ 411 public Builder(final Curve crv, final ECPublicKey pub) { 412 413 this(crv, 414 encodeCoordinate(pub.getParams().getCurve().getField().getFieldSize(), pub.getW().getAffineX()), 415 encodeCoordinate(pub.getParams().getCurve().getField().getFieldSize(), pub.getW().getAffineY())); 416 } 417 418 419 /** 420 * Sets the private 'd' coordinate for the elliptic curve 421 * point. The alternative method is {@link #privateKey}. 422 * 423 * @param d The 'd' coordinate. It is represented as the 424 * Base64URL encoding of the coordinate's big endian 425 * representation. {@code null} if not specified (for 426 * a public key). 427 * 428 * @return This builder. 429 */ 430 public Builder d(final Base64URL d) { 431 432 this.d = d; 433 return this; 434 } 435 436 437 /** 438 * Sets the private Elliptic Curve key. The alternative method 439 * is {@link #d}. 440 * 441 * @param priv The private EC key, used to obtain the private 442 * 'd' coordinate for the elliptic curve point. 443 * {@code null} if not specified (for a public 444 * key). 445 * 446 * @return This builder. 447 */ 448 public Builder privateKey(final ECPrivateKey priv) { 449 450 if (priv != null) { 451 this.d = encodeCoordinate(priv.getParams().getCurve().getField().getFieldSize(), priv.getS()); 452 } 453 454 return this; 455 } 456 457 458 /** 459 * Sets the use ({@code use}) of the JWK. 460 * 461 * @param use The key use, {@code null} if not specified or if 462 * the key is intended for signing as well as 463 * encryption. 464 * 465 * @return This builder. 466 */ 467 public Builder keyUse(final KeyUse use) { 468 469 this.use = use; 470 return this; 471 } 472 473 474 /** 475 * Sets the operations ({@code key_ops}) of the JWK. 476 * 477 * @param ops The key operations, {@code null} if not 478 * specified. 479 * 480 * @return This builder. 481 */ 482 public Builder keyOperations(final Set<KeyOperation> ops) { 483 484 this.ops = ops; 485 return this; 486 } 487 488 489 /** 490 * Sets the intended JOSE algorithm ({@code alg}) for the JWK. 491 * 492 * @param alg The intended JOSE algorithm, {@code null} if not 493 * specified. 494 * 495 * @return This builder. 496 */ 497 public Builder algorithm(final Algorithm alg) { 498 499 this.alg = alg; 500 return this; 501 } 502 503 /** 504 * Sets the ID ({@code kid}) of the JWK. The key ID can be used 505 * to match a specific key. This can be used, for instance, to 506 * choose a key within a {@link JWKSet} during key rollover. 507 * The key ID may also correspond to a JWS/JWE {@code kid} 508 * header parameter value. 509 * 510 * @param kid The key ID, {@code null} if not specified. 511 * 512 * @return This builder. 513 */ 514 public Builder keyID(final String kid) { 515 516 this.kid = kid; 517 return this; 518 } 519 520 521 /** 522 * Sets the X.509 certificate URL ({@code x5u}) of the JWK. 523 * 524 * @param x5u The X.509 certificate URL, {@code null} if not 525 * specified. 526 * 527 * @return This builder. 528 */ 529 public Builder x509CertURL(final URL x5u) { 530 531 this.x5u = x5u; 532 return this; 533 } 534 535 536 /** 537 * Sets the X.509 certificate thumbprint ({@code x5t}) of the 538 * JWK. 539 * 540 * @param x5t The X.509 certificate thumbprint, {@code null} if 541 * not specified. 542 * 543 * @return This builder. 544 */ 545 public Builder x509CertThumbprint(final Base64URL x5t) { 546 547 this.x5t = x5t; 548 return this; 549 } 550 551 552 /** 553 * Sets the X.509 certificate chain ({@code x5c}) of the JWK. 554 * 555 * @param x5c The X.509 certificate chain as a unmodifiable 556 * list, {@code null} if not specified. 557 * 558 * @return This builder. 559 */ 560 public Builder x509CertChain(final List<Base64> x5c) { 561 562 this.x5c = x5c; 563 return this; 564 } 565 566 567 /** 568 * Builds a new octet sequence JWK. 569 * 570 * @return The octet sequence JWK. 571 * 572 * @throws IllegalStateException If the JWK parameters were 573 * inconsistently specified. 574 */ 575 public ECKey build() { 576 577 try { 578 if (d == null) { 579 // Public key 580 return new ECKey(crv, x, y, use, ops, alg, kid, x5u, x5t, x5c); 581 } 582 583 // Pair 584 return new ECKey(crv, x, y, d, use, ops, alg, kid, x5u, x5t, x5c); 585 586 } catch (IllegalArgumentException e) { 587 588 throw new IllegalStateException(e.getMessage(), e); 589 } 590 } 591 } 592 593 594 /** 595 * Returns the Base64URL encoding of the specified elliptic curve 'x', 596 * 'y' or 'd' coordinate, with leading zero padding up to the specified 597 * field size in bits. 598 * 599 * @param fieldSize The field size in bits. 600 * @param coordinate The elliptic curve coordinate. Must not be 601 * {@code null}. 602 * 603 * @return The Base64URL-encoded coordinate, with leading zero padding 604 * up to the curve's field size. 605 */ 606 public static Base64URL encodeCoordinate(final int fieldSize, final BigInteger coordinate) { 607 608 byte[] unpadded = BigIntegerUtils.toBytesUnsigned(coordinate); 609 610 int bytesToOutput = (fieldSize + 7)/8; 611 612 if (unpadded.length >= bytesToOutput) { 613 // Greater-than check to prevent exception on malformed 614 // key below 615 return Base64URL.encode(unpadded); 616 } 617 618 byte[] padded = new byte[bytesToOutput]; 619 620 System.arraycopy(unpadded, 0, padded, bytesToOutput - unpadded.length, unpadded.length); 621 622 return Base64URL.encode(padded); 623 } 624 625 626 /** 627 * The curve name. 628 */ 629 private final Curve crv; 630 631 632 /** 633 * The public 'x' EC coordinate. 634 */ 635 private final Base64URL x; 636 637 638 /** 639 * The public 'y' EC coordinate. 640 */ 641 private final Base64URL y; 642 643 644 /** 645 * The private 'd' EC coordinate 646 */ 647 private final Base64URL d; 648 649 650 /** 651 * Creates a new public Elliptic Curve JSON Web Key (JWK) with the 652 * specified parameters. 653 * 654 * @param crv The cryptographic curve. Must not be {@code null}. 655 * @param x The public 'x' coordinate for the elliptic curve point. 656 * It is represented as the Base64URL encoding of the 657 * coordinate's big endian representation. Must not be 658 * {@code null}. 659 * @param y The public 'y' coordinate for the elliptic curve point. 660 * It is represented as the Base64URL encoding of the 661 * coordinate's big endian representation. Must not be 662 * {@code null}. 663 * @param use The key use, {@code null} if not specified or if the key 664 * is intended for signing as well as encryption. 665 * @param ops The key operations, {@code null} if not specified. 666 * @param alg The intended JOSE algorithm for the key, {@code null} if 667 * not specified. 668 * @param kid The key ID, {@code null} if not specified. 669 * @param x5u The X.509 certificate URL, {@code null} if not specified. 670 * @param x5t The X.509 certificate thumbprint, {@code null} if not 671 * specified. 672 * @param x5c The X.509 certificate chain, {@code null} if not 673 * specified. 674 */ 675 public ECKey(final Curve crv, final Base64URL x, final Base64URL y, 676 final KeyUse use, final Set<KeyOperation> ops, final Algorithm alg, final String kid, 677 final URL x5u, final Base64URL x5t, final List<Base64> x5c) { 678 679 super(KeyType.EC, use, ops, alg, kid, x5u, x5t, x5c); 680 681 if (crv == null) { 682 throw new IllegalArgumentException("The curve must not be null"); 683 } 684 685 this.crv = crv; 686 687 if (x == null) { 688 throw new IllegalArgumentException("The 'x' coordinate must not be null"); 689 } 690 691 this.x = x; 692 693 if (y == null) { 694 throw new IllegalArgumentException("The 'y' coordinate must not be null"); 695 } 696 697 this.y = y; 698 699 this.d = null; 700 } 701 702 703 /** 704 * Creates a new public / private Elliptic Curve JSON Web Key (JWK) 705 * with the specified parameters. 706 * 707 * @param crv The cryptographic curve. Must not be {@code null}. 708 * @param x The public 'x' coordinate for the elliptic curve point. 709 * It is represented as the Base64URL encoding of the 710 * coordinate's big endian representation. Must not be 711 * {@code null}. 712 * @param y The public 'y' coordinate for the elliptic curve point. 713 * It is represented as the Base64URL encoding of the 714 * coordinate's big endian representation. Must not be 715 * {@code null}. 716 * @param d The private 'd' coordinate for the elliptic curve point. 717 * It is represented as the Base64URL encoding of the 718 * coordinate's big endian representation. Must not be 719 * {@code null}. 720 * @param use The key use, {@code null} if not specified or if the key 721 * is intended for signing as well as encryption. 722 * @param ops The key operations, {@code null} if not specified. 723 * @param alg The intended JOSE algorithm for the key, {@code null} if 724 * not specified. 725 * @param kid The key ID, {@code null} if not specified. 726 * @param x5u The X.509 certificate URL, {@code null} if not specified. 727 * @param x5t The X.509 certificate thumbprint, {@code null} if not 728 * specified. 729 * @param x5c The X.509 certificate chain, {@code null} if not 730 * specified. 731 */ 732 public ECKey(final Curve crv, final Base64URL x, final Base64URL y, final Base64URL d, 733 final KeyUse use, final Set<KeyOperation> ops, final Algorithm alg, final String kid, 734 final URL x5u, final Base64URL x5t, final List<Base64> x5c) { 735 736 super(KeyType.EC, use, ops, alg, kid, x5u, x5t, x5c); 737 738 if (crv == null) { 739 throw new IllegalArgumentException("The curve must not be null"); 740 } 741 742 this.crv = crv; 743 744 if (x == null) { 745 throw new IllegalArgumentException("The 'x' coordinate must not be null"); 746 } 747 748 this.x = x; 749 750 if (y == null) { 751 throw new IllegalArgumentException("The 'y' coordinate must not be null"); 752 } 753 754 this.y = y; 755 756 if (d == null) { 757 throw new IllegalArgumentException("The 'd' coordinate must not be null"); 758 } 759 760 this.d = d; 761 } 762 763 764 /** 765 * Creates a new public Elliptic Curve JSON Web Key (JWK) with the 766 * specified parameters. 767 * 768 * @param crv The cryptographic curve. Must not be {@code null}. 769 * @param pub The public EC key to represent. Must not be {@code null}. 770 * @param use The key use, {@code null} if not specified or if the key 771 * is intended for signing as well as encryption. 772 * @param ops The key operations, {@code null} if not specified. 773 * @param alg The intended JOSE algorithm for the key, {@code null} if 774 * not specified. 775 * @param kid The key ID, {@code null} if not specified. 776 * @param x5u The X.509 certificate URL, {@code null} if not specified. 777 * @param x5t The X.509 certificate thumbprint, {@code null} if not 778 * specified. 779 * @param x5c The X.509 certificate chain, {@code null} if not 780 * specified. 781 */ 782 public ECKey(final Curve crv, final ECPublicKey pub, 783 final KeyUse use, final Set<KeyOperation> ops, final Algorithm alg, final String kid, 784 final URL x5u, final Base64URL x5t, final List<Base64> x5c) { 785 786 this(crv, 787 encodeCoordinate(pub.getParams().getCurve().getField().getFieldSize(), pub.getW().getAffineX()), 788 encodeCoordinate(pub.getParams().getCurve().getField().getFieldSize(), pub.getW().getAffineY()), 789 use, ops, alg, kid, 790 x5u, x5t, x5c); 791 } 792 793 794 /** 795 * Creates a new public / private Elliptic Curve JSON Web Key (JWK) 796 * with the specified parameters. 797 * 798 * @param crv The cryptographic curve. Must not be {@code null}. 799 * @param pub The public EC key to represent. Must not be 800 * {@code null}. 801 * @param priv The private EC key to represent. Must not be 802 * {@code null}. 803 * @param use The key use, {@code null} if not specified or if the key 804 * is intended for signing as well as encryption. 805 * @param ops The key operations, {@code null} if not specified. 806 * @param alg The intended JOSE algorithm for the key, {@code null} if 807 * not specified. 808 * @param kid The key ID, {@code null} if not specified. 809 * @param x5u The X.509 certificate URL, {@code null} if not 810 * specified. 811 * @param x5t The X.509 certificate thumbprint, {@code null} if not 812 * specified. 813 * @param x5c The X.509 certificate chain, {@code null} if not 814 * specified. 815 */ 816 public ECKey(final Curve crv, final ECPublicKey pub, final ECPrivateKey priv, 817 final KeyUse use, final Set<KeyOperation> ops, final Algorithm alg, final String kid, 818 final URL x5u, final Base64URL x5t, final List<Base64> x5c) { 819 820 this(crv, 821 encodeCoordinate(pub.getParams().getCurve().getField().getFieldSize(), pub.getW().getAffineX()), 822 encodeCoordinate(pub.getParams().getCurve().getField().getFieldSize(), pub.getW().getAffineY()), 823 encodeCoordinate(priv.getParams().getCurve().getField().getFieldSize(), priv.getS()), 824 use, ops, alg, kid, 825 x5u, x5t, x5c); 826 } 827 828 829 /** 830 * Gets the cryptographic curve. 831 * 832 * @return The cryptographic curve. 833 */ 834 public Curve getCurve() { 835 836 return crv; 837 } 838 839 840 /** 841 * Gets the public 'x' coordinate for the elliptic curve point. 842 * 843 * @return The 'x' coordinate. It is represented as the Base64URL 844 * encoding of the coordinate's big endian representation. 845 */ 846 public Base64URL getX() { 847 848 return x; 849 } 850 851 852 /** 853 * Gets the public 'y' coordinate for the elliptic curve point. 854 * 855 * @return The 'y' coordinate. It is represented as the Base64URL 856 * encoding of the coordinate's big endian representation. 857 */ 858 public Base64URL getY() { 859 860 return y; 861 } 862 863 864 /** 865 * Gets the private 'd' coordinate for the elliptic curve point. It is 866 * represented as the Base64URL encoding of the coordinate's big endian 867 * representation. 868 * 869 * @return The 'd' coordinate. It is represented as the Base64URL 870 * encoding of the coordinate's big endian representation. 871 * {@code null} if not specified (for a public key). 872 */ 873 public Base64URL getD() { 874 875 return d; 876 } 877 878 879 /** 880 * Gets a new BouncyCastle.org EC key factory. 881 * 882 * @return The EC key factory. 883 * 884 * @throws NoSuchAlgorithmException If a JCA provider or algorithm 885 * exception was encountered. 886 */ 887 private static KeyFactory getECKeyFactory() 888 throws NoSuchAlgorithmException { 889 890 return KeyFactory.getInstance("EC", BouncyCastleProviderSingleton.getInstance()); 891 } 892 893 894 /** 895 * Returns a standard {@code java.security.interfaces.ECPublicKey} 896 * representation of this Elliptic Curve JWK. 897 * 898 * @return The public Elliptic Curve key. 899 * 900 * @throws NoSuchAlgorithmException If EC is not supported by the 901 * underlying Java Cryptography (JCA) 902 * provider. 903 * @throws InvalidKeySpecException If the JWK key parameters are 904 * invalid for a public EC key. 905 */ 906 public ECPublicKey toECPublicKey() 907 throws NoSuchAlgorithmException, InvalidKeySpecException { 908 909 ECParameterSpec spec = crv.toECParameterSpec(); 910 911 if (spec == null) { 912 throw new NoSuchAlgorithmException("Couldn't get EC parameter spec for curve " + crv); 913 } 914 915 ECPoint w = new ECPoint(x.decodeToBigInteger(), y.decodeToBigInteger()); 916 917 ECPublicKeySpec publicKeySpec = new ECPublicKeySpec(w, spec); 918 919 KeyFactory keyFactory = getECKeyFactory(); 920 921 return (ECPublicKey)keyFactory.generatePublic(publicKeySpec); 922 } 923 924 925 /** 926 * Returns a standard {@code java.security.interfaces.ECPrivateKey} 927 * representation of this Elliptic Curve JWK. 928 * 929 * @return The private Elliptic Curve key, {@code null} if not 930 * specified by this JWK. 931 * 932 * @throws NoSuchAlgorithmException If EC is not supported by the 933 * underlying Java Cryptography (JCA) 934 * provider. 935 * @throws InvalidKeySpecException If the JWK key parameters are 936 * invalid for a private EC key. 937 */ 938 public ECPrivateKey toECPrivateKey() 939 throws NoSuchAlgorithmException, InvalidKeySpecException { 940 941 if (d == null) { 942 // No private 'd' param 943 return null; 944 } 945 946 ECParameterSpec spec = crv.toECParameterSpec(); 947 948 if (spec == null) { 949 throw new NoSuchAlgorithmException("Couldn't get EC parameter spec for curve " + crv); 950 } 951 952 ECPrivateKeySpec privateKeySpec = new ECPrivateKeySpec(d.decodeToBigInteger(), spec); 953 954 KeyFactory keyFactory = getECKeyFactory(); 955 956 return (ECPrivateKey)keyFactory.generatePrivate(privateKeySpec); 957 } 958 959 960 /** 961 * Returns a standard {@code java.security.KeyPair} representation of 962 * this Elliptic Curve JWK. 963 * 964 * @return The Elliptic Curve key pair. The private Elliptic Curve key 965 * will be {@code null} if not specified. 966 * 967 * @throws NoSuchAlgorithmException If EC is not supported by the 968 * underlying Java Cryptography (JCA) 969 * provider. 970 * @throws InvalidKeySpecException If the JWK key parameters are 971 * invalid for a public and / or 972 * private EC key. 973 */ 974 public KeyPair toKeyPair() 975 throws NoSuchAlgorithmException, InvalidKeySpecException { 976 977 return new KeyPair(toECPublicKey(), toECPrivateKey()); 978 } 979 980 981 @Override 982 public boolean isPrivate() { 983 984 return d != null; 985 } 986 987 988 /** 989 * Returns a copy of this Elliptic Curve JWK with any private values 990 * removed. 991 * 992 * @return The copied public Elliptic Curve JWK. 993 */ 994 @Override 995 public ECKey toPublicJWK() { 996 997 return new ECKey(getCurve(), getX(), getY(), 998 getKeyUse(), getKeyOperations(), getAlgorithm(), getKeyID(), 999 getX509CertURL(), getX509CertThumbprint(), getX509CertChain()); 1000 } 1001 1002 1003 @Override 1004 public JSONObject toJSONObject() { 1005 1006 JSONObject o = super.toJSONObject(); 1007 1008 // Append EC specific attributes 1009 o.put("crv", crv.toString()); 1010 o.put("x", x.toString()); 1011 o.put("y", y.toString()); 1012 1013 if (d != null) { 1014 o.put("d", d.toString()); 1015 } 1016 1017 return o; 1018 } 1019 1020 1021 /** 1022 * Parses a public / private Elliptic Curve JWK from the specified JSON 1023 * object string representation. 1024 * 1025 * @param s The JSON object string to parse. Must not be {@code null}. 1026 * 1027 * @return The public / private Elliptic Curve JWK. 1028 * 1029 * @throws ParseException If the string couldn't be parsed to an 1030 * Elliptic Curve JWK. 1031 */ 1032 public static ECKey parse(final String s) 1033 throws ParseException { 1034 1035 return parse(JSONObjectUtils.parseJSONObject(s)); 1036 } 1037 1038 1039 /** 1040 * Parses a public / private Elliptic Curve JWK from the specified JSON 1041 * object representation. 1042 * 1043 * @param jsonObject The JSON object to parse. Must not be 1044 * {@code null}. 1045 * 1046 * @return The public / private Elliptic Curve JWK. 1047 * 1048 * @throws ParseException If the JSON object couldn't be parsed to an 1049 * Elliptic Curve JWK. 1050 */ 1051 public static ECKey parse(final JSONObject jsonObject) 1052 throws ParseException { 1053 1054 // Parse the mandatory parameters first 1055 Curve crv = Curve.parse(JSONObjectUtils.getString(jsonObject, "crv")); 1056 Base64URL x = new Base64URL(JSONObjectUtils.getString(jsonObject, "x")); 1057 Base64URL y = new Base64URL(JSONObjectUtils.getString(jsonObject, "y")); 1058 1059 // Check key type 1060 KeyType kty = KeyType.parse(JSONObjectUtils.getString(jsonObject, "kty")); 1061 1062 if (kty != KeyType.EC) { 1063 throw new ParseException("The key type \"kty\" must be EC", 0); 1064 } 1065 1066 // Get optional private key 1067 Base64URL d = null; 1068 if (jsonObject.get("d") != null) { 1069 d = new Base64URL(JSONObjectUtils.getString(jsonObject, "d")); 1070 } 1071 1072 // Get optional key use 1073 KeyUse use = null; 1074 1075 if (jsonObject.containsKey("use")) { 1076 use = KeyUse.parse(JSONObjectUtils.getString(jsonObject, "use")); 1077 } 1078 1079 // Get optional key operations 1080 Set<KeyOperation> ops = null; 1081 1082 if (jsonObject.containsKey("key_ops")) { 1083 ops = KeyOperation.parse(JSONObjectUtils.getStringList(jsonObject, "key_ops")); 1084 } 1085 1086 // Get optional intended algorithm 1087 Algorithm alg = null; 1088 1089 if (jsonObject.containsKey("alg")) { 1090 alg = new Algorithm(JSONObjectUtils.getString(jsonObject, "alg")); 1091 } 1092 1093 // Get optional key ID 1094 String kid = null; 1095 1096 if (jsonObject.containsKey("kid")) { 1097 kid = JSONObjectUtils.getString(jsonObject, "kid"); 1098 } 1099 1100 // Get optional X.509 cert URL 1101 URL x5u = null; 1102 1103 if (jsonObject.containsKey("x5u")) { 1104 x5u = JSONObjectUtils.getURL(jsonObject, "x5u"); 1105 } 1106 1107 // Get optional X.509 cert thumbprint 1108 Base64URL x5t = null; 1109 1110 if (jsonObject.containsKey("x5t")) { 1111 x5t = new Base64URL(JSONObjectUtils.getString(jsonObject, "x5t")); 1112 } 1113 1114 // Get optional X.509 cert chain 1115 List<Base64> x5c = null; 1116 1117 if (jsonObject.containsKey("x5c")) { 1118 x5c = X509CertChainUtils.parseX509CertChain(JSONObjectUtils.getJSONArray(jsonObject, "x5c")); 1119 } 1120 1121 try { 1122 if (d == null) { 1123 // Public key 1124 return new ECKey(crv, x, y, use, ops, alg, kid, x5u, x5t, x5c); 1125 1126 } else { 1127 // Key pair 1128 return new ECKey(crv, x, y, d, use, ops, alg, kid, x5u, x5t, x5c); 1129 } 1130 1131 } catch (IllegalArgumentException ex) { 1132 1133 // Conflicting 'use' and 'key_ops' 1134 throw new ParseException(ex.getMessage(), 0); 1135 } 1136 } 1137}