001 package com.nimbusds.jose.jwk; 002 003 004 import java.security.KeyFactory; 005 import java.security.KeyPair; 006 import java.security.NoSuchAlgorithmException; 007 import java.security.interfaces.ECPrivateKey; 008 import java.security.interfaces.ECPublicKey; 009 import java.security.spec.ECParameterSpec; 010 import java.security.spec.ECPoint; 011 import java.security.spec.ECPrivateKeySpec; 012 import java.security.spec.ECPublicKeySpec; 013 import java.security.spec.InvalidKeySpecException; 014 import java.text.ParseException; 015 016 import net.jcip.annotations.Immutable; 017 018 import net.minidev.json.JSONObject; 019 020 import org.bouncycastle.jce.ECNamedCurveTable; 021 import org.bouncycastle.jce.provider.BouncyCastleProvider; 022 import org.bouncycastle.jce.spec.ECNamedCurveParameterSpec; 023 import org.bouncycastle.jce.spec.ECNamedCurveSpec; 024 025 import com.nimbusds.jose.Algorithm; 026 import com.nimbusds.jose.util.Base64URL; 027 import com.nimbusds.jose.util.JSONObjectUtils; 028 029 030 /** 031 * Public and private {@link KeyType#EC Elliptic Curve} JSON Web Key (JWK). 032 * Uses the BouncyCastle.org provider for EC key import and export. This class 033 * is immutable. 034 * 035 * <p>Example JSON object representation of a public EC JWK: 036 * 037 * <pre> 038 * { 039 * "kty" : "EC", 040 * "crv" : "P-256", 041 * "x" : "MKBCTNIcKUSDii11ySs3526iDZ8AiTo7Tu6KPAqv7D4", 042 * "y" : "4Etl6SRW2YiLUrN5vfvVHuhp7x8PxltmWWlbbM4IFyM", 043 * "use" : "enc", 044 * "kid" : "1" 045 * } 046 * </pre> 047 * 048 * <p>Example JSON object representation of a public and private EC JWK: 049 * 050 * <pre> 051 * { 052 * "kty" : "EC", 053 * "crv" : "P-256", 054 * "x" : "MKBCTNIcKUSDii11ySs3526iDZ8AiTo7Tu6KPAqv7D4", 055 * "y" : "4Etl6SRW2YiLUrN5vfvVHuhp7x8PxltmWWlbbM4IFyM", 056 * "d" : "870MB6gfuTJ4HtUnUvYMyJpr5eUZNP4Bk43bVdj3eAE", 057 * "use" : "enc", 058 * "kid" : "1" 059 * } 060 * </pre> 061 * 062 * <p>See http://en.wikipedia.org/wiki/Elliptic_curve_cryptography 063 * 064 * @author Vladimir Dzhuvinov 065 * @author Justin Richer 066 * @version $version$ (2013-03-28) 067 */ 068 @Immutable 069 public final class ECKey extends JWK { 070 071 072 /** 073 * Cryptographic curve. This class is immutable. 074 * 075 * <p>Includes constants for the following standard cryptographic 076 * curves: 077 * 078 * <ul> 079 * <li>{@link #P_256} 080 * <li>{@link #P_384} 081 * <li>{@link #P_521} 082 * </ul> 083 * 084 * <p>See "Digital Signature Standard (DSS)", FIPS PUB 186-3, June 085 * 2009, National Institute of Standards and Technology (NIST). 086 */ 087 @Immutable 088 public static class Curve { 089 090 091 /** 092 * P-256 curve (secp256r1). 093 */ 094 public static final Curve P_256 = new Curve("P-256", "secp256r1"); 095 096 097 /** 098 * P-384 curve (secp384r1). 099 */ 100 public static final Curve P_384 = new Curve("P-384", "secp384r1"); 101 102 103 /** 104 * P-521 curve (secp521r1). 105 */ 106 public static final Curve P_521 = new Curve("P-521", "secp521r1"); 107 108 109 /** 110 * The JOSE curve name. 111 */ 112 private final String name; 113 114 115 /** 116 * The standard (JCA) curve name, {@code null} if not 117 * specified. 118 */ 119 private final String stdName; 120 121 122 /** 123 * Creates a new cryptographic curve with the specified name. 124 * The standard (JCA) curve name is not unspecified. 125 * 126 * @param name The name of the cryptographic curve. Must not be 127 * {@code null}. 128 */ 129 public Curve(final String name) { 130 131 this(name, null); 132 } 133 134 135 /** 136 * Creates a new cryptographic curve with the specified name. 137 * 138 * @param name The JOSE name of the cryptographic curve. 139 * Must not be {@code null}. 140 * @param stdName The standard (JCA) name of the cryptographic 141 * curve, {@code null} if not specified. 142 */ 143 public Curve(final String name, final String stdName) { 144 145 if (name == null) { 146 147 throw new IllegalArgumentException("The cryptographic curve name must not be null"); 148 } 149 150 this.name = name; 151 152 153 this.stdName = stdName; 154 } 155 156 157 /** 158 * Gets the name of this cryptographic curve. 159 * 160 * @return The name. 161 */ 162 public String getName() { 163 164 return name; 165 } 166 167 168 /** 169 * Gets the standard (JCA) name of this cryptographic curve. 170 * 171 * @return The standard (JCA) name. 172 */ 173 public String getStdName() { 174 175 return stdName; 176 } 177 178 179 /** 180 * Gets the Elliptic Curve parameter specification for this 181 * cryptographic curve. 182 * 183 * @return The EC parameter specification, {@code null} if this 184 * cryptographic curve has no standard (JCA) name 185 * specified or if lookup of the EC parameters failed. 186 */ 187 public ECParameterSpec toECParameterSpec() { 188 189 if (stdName == null) { 190 191 return null; 192 } 193 194 ECNamedCurveParameterSpec curveParams = 195 ECNamedCurveTable.getParameterSpec(stdName); 196 197 if (curveParams == null) { 198 199 return null; 200 } 201 202 return new ECNamedCurveSpec(curveParams.getName(), 203 curveParams.getCurve(), 204 curveParams.getG(), 205 curveParams.getN()); 206 } 207 208 209 /** 210 * @see #getName 211 */ 212 @Override 213 public String toString() { 214 215 return getName(); 216 } 217 218 219 /** 220 * Overrides {@code Object.equals()}. 221 * 222 * @param object The object to compare to. 223 * 224 * @return {@code true} if the objects have the same value, 225 * otherwise {@code false}. 226 */ 227 @Override 228 public boolean equals(final Object object) { 229 230 return object != null && 231 object instanceof Curve && 232 this.toString().equals(object.toString()); 233 } 234 235 236 /** 237 * Parses a cryptographic curve from the specified string. 238 * 239 * @param s The string to parse. Must not be {@code null}. 240 * 241 * @return The cryptographic curve. 242 * 243 * @throws ParseException If the string couldn't be parsed. 244 */ 245 public static Curve parse(final String s) 246 throws ParseException { 247 248 if (s == null) { 249 250 throw new IllegalArgumentException("The cryptographic curve string must not be null"); 251 } 252 253 if (s.equals(P_256.getName())) { 254 255 return P_256; 256 257 } else if (s.equals(P_384.getName())) { 258 259 return P_384; 260 261 } else if (s.equals(P_521.getName())) { 262 263 return P_521; 264 265 } else { 266 267 return new Curve(s); 268 } 269 } 270 271 272 /** 273 * Gets the cryptographic curve for the specified standard 274 * (JCA) name. 275 * 276 * @param stdName The standard (JCA) name. Must not be 277 * {@code null}. 278 * 279 * @throws IllegalArgumentException If no matching JOSE curve 280 * constant could be found. 281 */ 282 public static Curve forStdName(final String stdName) { 283 284 if (stdName.equals("secp256r1")) { 285 286 return P_256; 287 } else if (stdName.equals("secp384r1")) { 288 289 return P_384; 290 291 } else if (stdName.equals("secp521r1")) { 292 293 return P_521; 294 295 } else { 296 297 throw new IllegalArgumentException("No matching curve constant for standard (JCA) name " + stdName); 298 } 299 } 300 } 301 302 303 /** 304 * The curve name. 305 */ 306 private final Curve crv; 307 308 309 /** 310 * The public 'x' EC coordinate. 311 */ 312 private final Base64URL x; 313 314 315 /** 316 * The public 'y' EC coordinate. 317 */ 318 private final Base64URL y; 319 320 321 /** 322 * The private 'd' EC coordinate 323 */ 324 private final Base64URL d; 325 326 327 /** 328 * Creates a new public Elliptic Curve JSON Web Key (JWK) with the 329 * specified parameters. 330 * 331 * @param crv The cryptographic curve. Must not be {@code null}. 332 * @param x The public 'x' coordinate for the elliptic curve point. 333 * It is represented as the Base64URL encoding of the 334 * coordinate's big endian representation. Must not be 335 * {@code null}. 336 * @param y The public 'y' coordinate for the elliptic curve point. 337 * It is represented as the Base64URL encoding of the 338 * coordinate's big endian representation. Must not be 339 * {@code null}. 340 * @param use The key use, {@code null} if not specified. 341 * @param alg The intended JOSE algorithm for the key, {@code null} if 342 * not specified. 343 * @param kid The key ID, {@code null} if not specified. 344 */ 345 public ECKey(final Curve crv, final Base64URL x, final Base64URL y, 346 final Use use, final Algorithm alg, final String kid) { 347 348 this(crv, x, y, null, use, alg, kid); 349 } 350 351 352 /** 353 * Creates a new public / private Elliptic Curve JSON Web Key (JWK) 354 * with the specified parameters. 355 * 356 * @param crv The cryptographic curve. Must not be {@code null}. 357 * @param x The public 'x' coordinate for the elliptic curve point. 358 * It is represented as the Base64URL encoding of the 359 * coordinate's big endian representation. Must not be 360 * {@code null}. 361 * @param y The public 'y' coordinate for the elliptic curve point. 362 * It is represented as the Base64URL encoding of the 363 * coordinate's big endian representation. Must not be 364 * {@code null}. 365 * @param d The private 'd' coordinate for the elliptic curve point. 366 * It is represented as the Base64URL encoding of the 367 * coordinate's big endian representation. May be 368 * {@code null} if this is a public key. 369 * @param use The key use, {@code null} if not specified. 370 * @param alg The intended JOSE algorithm for the key, {@code null} if 371 * not specified. 372 * @param kid The key ID, {@code null} if not specified. 373 */ 374 public ECKey(final Curve crv, final Base64URL x, final Base64URL y, final Base64URL d, 375 final Use use, final Algorithm alg, final String kid) { 376 377 super(KeyType.EC, use, alg, kid); 378 379 if (crv == null) { 380 throw new IllegalArgumentException("The curve must not be null"); 381 } 382 383 this.crv = crv; 384 385 if (x == null) { 386 throw new IllegalArgumentException("The x coordinate must not be null"); 387 } 388 389 this.x = x; 390 391 if (y == null) { 392 throw new IllegalArgumentException("The y coordinate must not be null"); 393 } 394 395 this.y = y; 396 397 this.d = d; 398 } 399 400 401 /** 402 * Creates a new public Elliptic Curve JSON Web Key (JWK) with the 403 * specified parameters. 404 * 405 * @param crv The cryptographic curve. Must not be {@code null}. 406 * @param pub The public EC key to represent. Must not be {@code null}. 407 * @param use The key use, {@code null} if not specified. 408 * @param alg The intended JOSE algorithm for the key, {@code null} if 409 * not specified. 410 * @param kid The key ID, {@code null} if not specified. 411 */ 412 public ECKey(final Curve crv, final ECPublicKey pub, 413 final Use use, final Algorithm alg, final String kid) { 414 415 this(crv, Base64URL.encode(pub.getW().getAffineX()), Base64URL.encode(pub.getW().getAffineY()), 416 use, alg, kid); 417 } 418 419 420 /** 421 * Creates a new public / private Elliptic Curve JSON Web Key (JWK) 422 * with the specified parameters. 423 * 424 * @param crv The cryptographic curve. Must not be {@code null}. 425 * @param pub The public EC key to represent. Must not be 426 * {@code null}. 427 * @param priv The private EC key to represent. Must not be 428 * {@code null}. 429 * @param use The key use, {@code null} if not specified. 430 * @param alg The intended JOSE algorithm for the key, {@code null} if 431 * not specified. 432 * @param kid The key ID, {@code null} if not specified. 433 */ 434 public ECKey(final Curve crv, final ECPublicKey pub, final ECPrivateKey priv, 435 final Use use, final Algorithm alg, final String kid) { 436 437 this(crv, 438 Base64URL.encode(pub.getW().getAffineX()), 439 Base64URL.encode(pub.getW().getAffineY()), 440 Base64URL.encode(priv.getS()), 441 use, alg, kid); 442 } 443 444 445 /** 446 * Gets the cryptographic curve. 447 * 448 * @return The cryptographic curve. 449 */ 450 public Curve getCurve() { 451 452 return crv; 453 } 454 455 456 /** 457 * Gets the public 'x' coordinate for the elliptic curve point. It is 458 * represented as the Base64URL encoding of the coordinate's big endian 459 * representation. 460 * 461 * @return The 'x' coordinate. 462 */ 463 public Base64URL getX() { 464 465 return x; 466 } 467 468 469 /** 470 * Gets the public 'y' coordinate for the elliptic curve point. It is 471 * represented as the Base64URL encoding of the coordinate's big endian 472 * representation. 473 * 474 * @return The 'y' coordinate. 475 */ 476 public Base64URL getY() { 477 478 return y; 479 } 480 481 482 /** 483 * Gets the private 'd' coordinate for the elliptic curve point. It is 484 * represented as the Base64URL encoding of the coordinate's big endian 485 * representation. 486 * 487 * @return The 'd' coordinate, {@code null} if not specified (for a 488 * public key). 489 */ 490 public Base64URL getD() { 491 492 return d; 493 } 494 495 496 /** 497 * Gets a new BouncyCastle.org EC key factory. 498 * 499 * @return The EC key factory. 500 * 501 * @throws NoSuchAlgorithmException If a JCA provider or algorithm 502 * exception was encountered. 503 */ 504 private static KeyFactory getECKeyFactory() 505 throws NoSuchAlgorithmException { 506 507 return KeyFactory.getInstance("EC", new BouncyCastleProvider()); 508 } 509 510 511 /** 512 * Returns a standard {@code java.security.interfaces.ECPublicKey} 513 * representation of this Elliptic Curve JWK. 514 * 515 * @return The public Elliptic Curve key. 516 * 517 * @throws NoSuchAlgorithmException If EC is not supported by the 518 * underlying Java Cryptography (JCA) 519 * provider. 520 * @throws InvalidKeySpecException If the JWK key parameters are 521 * invalid for a public EC key. 522 */ 523 public ECPublicKey toECPublicKey() 524 throws NoSuchAlgorithmException, InvalidKeySpecException { 525 526 ECParameterSpec spec = crv.toECParameterSpec(); 527 528 if (spec == null) { 529 530 throw new NoSuchAlgorithmException("Couldn't get EC parameter spec for curve " + crv); 531 } 532 533 ECPoint w = new ECPoint(x.decodeToBigInteger(), y.decodeToBigInteger()); 534 535 ECPublicKeySpec publicKeySpec = new ECPublicKeySpec(w, spec); 536 537 KeyFactory keyFactory = getECKeyFactory(); 538 539 return (ECPublicKey)keyFactory.generatePublic(publicKeySpec); 540 } 541 542 543 /** 544 * Returns a standard {@code java.security.interfaces.ECPrivateKey} 545 * representation of this Elliptic Curve JWK. 546 * 547 * @return The private Elliptic Curve key, {@code null} if not 548 * specified by this JWK. 549 * 550 * @throws NoSuchAlgorithmException If EC is not supported by the 551 * underlying Java Cryptography (JCA) 552 * provider. 553 * @throws InvalidKeySpecException If the JWK key parameters are 554 * invalid for a private EC key. 555 */ 556 public ECPrivateKey toECPrivateKey() 557 throws NoSuchAlgorithmException, InvalidKeySpecException { 558 559 if (d == null) { 560 561 // No private 'd' param 562 return null; 563 } 564 565 ECParameterSpec spec = crv.toECParameterSpec(); 566 567 if (spec == null) { 568 569 throw new NoSuchAlgorithmException("Couldn't get EC parameter spec for curve " + crv); 570 } 571 572 ECPrivateKeySpec privateKeySpec = new ECPrivateKeySpec(d.decodeToBigInteger(), spec); 573 574 KeyFactory keyFactory = getECKeyFactory(); 575 576 return (ECPrivateKey)keyFactory.generatePrivate(privateKeySpec); 577 } 578 579 580 /** 581 * Returns a standard {@code java.security.KeyPair} representation of 582 * this Elliptic Curve JWK. 583 * 584 * @return The Elliptic Curve key pair. The private Elliptic Curve key 585 * will be {@code null} if not specified. 586 * 587 * @throws NoSuchAlgorithmException If EC is not supported by the 588 * underlying Java Cryptography (JCA) 589 * provider. 590 * @throws InvalidKeySpecException If the JWK key parameters are 591 * invalid for a public and / or 592 * private EC key. 593 */ 594 public KeyPair toKeyPair() 595 throws NoSuchAlgorithmException, InvalidKeySpecException { 596 597 return new KeyPair(toECPublicKey(), toECPrivateKey()); 598 } 599 600 601 @Override 602 public boolean isPrivate() { 603 604 if (d != null) { 605 606 return true; 607 608 } else { 609 610 return false; 611 } 612 } 613 614 615 /** 616 * Returns a copy of this Elliptic Curve JWK with any private values 617 * removed. 618 * 619 * @return The copied public Elliptic Curve JWK. 620 */ 621 @Override 622 public ECKey toPublicJWK() { 623 624 return new ECKey(getCurve(), getX(), getY(), getKeyUse(), getAlgorithm(), getKeyID()); 625 } 626 627 628 @Override 629 public JSONObject toJSONObject() { 630 631 JSONObject o = super.toJSONObject(); 632 633 // Append EC specific attributes 634 o.put("crv", crv.toString()); 635 o.put("x", x.toString()); 636 o.put("y", y.toString()); 637 638 if (d != null) { 639 o.put("d", d.toString()); 640 } 641 642 return o; 643 } 644 645 646 /** 647 * Parses a public / private Elliptic Curve JWK from the specified JSON 648 * object string representation. 649 * 650 * @param s The JSON object string to parse. Must not be {@code null}. 651 * 652 * @return The public / private Elliptic Curve JWK. 653 * 654 * @throws ParseException If the string couldn't be parsed to an 655 * Elliptic Curve JWK. 656 */ 657 public static ECKey parse(final String s) 658 throws ParseException { 659 660 return parse(JSONObjectUtils.parseJSONObject(s)); 661 } 662 663 664 /** 665 * Parses a public / private Elliptic Curve JWK from the specified JSON 666 * object representation. 667 * 668 * @param jsonObject The JSON object to parse. Must not be 669 * {@code null}. 670 * 671 * @return The public / private Elliptic Curve JWK. 672 * 673 * @throws ParseException If the JSON object couldn't be parsed to an 674 * Elliptic Curve JWK. 675 */ 676 public static ECKey parse(final JSONObject jsonObject) 677 throws ParseException { 678 679 // Parse the mandatory parameters first 680 Curve crv = Curve.parse(JSONObjectUtils.getString(jsonObject, "crv")); 681 Base64URL x = new Base64URL(JSONObjectUtils.getString(jsonObject, "x")); 682 Base64URL y = new Base64URL(JSONObjectUtils.getString(jsonObject, "y")); 683 684 // Check key type 685 KeyType kty = KeyType.parse(JSONObjectUtils.getString(jsonObject, "kty")); 686 if (kty != KeyType.EC) { 687 throw new ParseException("The key type \"kty\" must be EC", 0); 688 } 689 690 // optional private key 691 Base64URL d = null; 692 if (jsonObject.get("d") != null) { 693 d = new Base64URL(JSONObjectUtils.getString(jsonObject, "d")); 694 } 695 696 // Get optional key use 697 Use use = JWK.parseKeyUse(jsonObject); 698 699 // Get optional intended algorithm 700 Algorithm alg = JWK.parseAlgorithm(jsonObject); 701 702 // Get optional key ID 703 String kid = JWK.parseKeyID(jsonObject); 704 705 return new ECKey(crv, x, y, d, use, alg, kid); 706 } 707 }