001package com.nimbusds.jose.jwk; 002 003 004import java.math.BigInteger; 005import java.security.KeyPair; 006import java.security.interfaces.ECPrivateKey; 007import java.security.interfaces.ECPublicKey; 008import java.security.spec.ECParameterSpec; 009import java.security.spec.ECPrivateKeySpec; 010import java.security.spec.ECPublicKeySpec; 011import java.security.spec.EllipticCurve; 012import java.text.ParseException; 013 014import net.jcip.annotations.Immutable; 015 016import net.minidev.json.JSONObject; 017 018import com.nimbusds.jose.Algorithm; 019import com.nimbusds.jose.util.Base64URL; 020import com.nimbusds.jose.util.JSONObjectUtils; 021 022 023/** 024 * Public and private {@link KeyType#EC Elliptic Curve} JSON Web Key (JWK). 025 * This class is immutable. 026 * 027 * <p>Example JSON object representation of a public EC JWK: 028 * 029 * <pre> 030 * { 031 * "kty" : "EC", 032 * "crv" : "P-256", 033 * "x" : "MKBCTNIcKUSDii11ySs3526iDZ8AiTo7Tu6KPAqv7D4", 034 * "y" : "4Etl6SRW2YiLUrN5vfvVHuhp7x8PxltmWWlbbM4IFyM", 035 * "use" : "enc", 036 * "kid" : "1" 037 * } 038 * </pre> 039 * 040 * <p>Example JSON object representation of a public and private EC JWK: 041 * 042 * <pre> 043 * { 044 * "kty" : "EC", 045 * "crv" : "P-256", 046 * "x" : "MKBCTNIcKUSDii11ySs3526iDZ8AiTo7Tu6KPAqv7D4", 047 * "y" : "4Etl6SRW2YiLUrN5vfvVHuhp7x8PxltmWWlbbM4IFyM", 048 * "d" : "870MB6gfuTJ4HtUnUvYMyJpr5eUZNP4Bk43bVdj3eAE", 049 * "use" : "enc", 050 * "kid" : "1" 051 * } 052 * </pre> 053 * 054 * <p>See http://en.wikipedia.org/wiki/Elliptic_curve_cryptography 055 * 056 * @author Vladimir Dzhuvinov 057 * @author Justin Richer 058 * @version $version$ (2013-03-20) 059 */ 060@Immutable 061public final class ECKey extends JWK { 062 063 064 /** 065 * Cryptographic curve. This class is immutable. 066 * 067 * <p>Includes constants for the following standard cryptographic 068 * curves: 069 * 070 * <ul> 071 * <li>{@link #P_256} 072 * <li>{@link #P_384} 073 * <li>{@link #P_521} 074 * </ul> 075 * 076 * <p>See "Digital Signature Standard (DSS)", FIPS PUB 186-3, June 077 * 2009, National Institute of Standards and Technology (NIST). 078 */ 079 @Immutable 080 public static class Curve { 081 082 083 /** 084 * P-256 curve. 085 */ 086 public static final Curve P_256 = new Curve("P-256"); 087 088 089 /** 090 * P-384 curve. 091 */ 092 public static final Curve P_384 = new Curve("P-384"); 093 094 095 /** 096 * P-521 curve. 097 */ 098 public static final Curve P_521 = new Curve("P-521"); 099 100 101 /** 102 * The curve name. 103 */ 104 private final String name; 105 106 107 /** 108 * Creates a new cryptographic curve with the specified name. 109 * 110 * @param name The name of the cryptographic curve. Must not be 111 * {@code null}. 112 */ 113 public Curve(final String name) { 114 115 if (name == null) { 116 throw new IllegalArgumentException("The cryptographic curve name must not be null"); 117 } 118 119 this.name = name; 120 } 121 122 123 /** 124 * Gets the name of this cryptographic curve. 125 * 126 * @return The name. 127 */ 128 public String getName() { 129 130 return name; 131 } 132 133 134 /** 135 * @see #getName 136 */ 137 @Override 138 public String toString() { 139 140 return getName(); 141 } 142 143 144 /** 145 * Overrides {@code Object.equals()}. 146 * 147 * @param object The object to compare to. 148 * 149 * @return {@code true} if the objects have the same value, 150 * otherwise {@code false}. 151 */ 152 @Override 153 public boolean equals(final Object object) { 154 155 return object != null && 156 object instanceof Curve && 157 this.toString().equals(object.toString()); 158 } 159 160 161 /** 162 * Parses a cryptographic curve from the specified string. 163 * 164 * @param s The string to parse. Must not be {@code null}. 165 * 166 * @return The cryptographic curve. 167 * 168 * @throws ParseException If the string couldn't be parsed. 169 */ 170 public static Curve parse(final String s) 171 throws ParseException { 172 173 if (s == null) { 174 175 throw new IllegalArgumentException("The cryptographic curve string must not be null"); 176 } 177 178 if (s.equals(P_256.getName())) { 179 180 return P_256; 181 182 } else if (s.equals(P_384.getName())) { 183 184 return P_384; 185 186 } else if (s.equals(P_521.getName())) { 187 188 return P_521; 189 190 } else { 191 192 return new Curve(s); 193 } 194 } 195 } 196 197 198 /** 199 * The curve name. 200 */ 201 private final Curve crv; 202 203 204 /** 205 * The public 'x' EC coordinate. 206 */ 207 private final Base64URL x; 208 209 210 /** 211 * The public 'y' EC coordinate. 212 */ 213 private final Base64URL y; 214 215 216 /** 217 * The private 'd' EC coordinate 218 */ 219 private final Base64URL d; 220 221 222 /** 223 * Creates a new public Elliptic Curve JSON Web Key (JWK) with the 224 * specified parameters. 225 * 226 * @param crv The cryptographic curve. Must not be {@code null}. 227 * @param x The public 'x' coordinate for the elliptic curve point. 228 * It is represented as the Base64URL encoding of the 229 * coordinate's big endian representation. Must not be 230 * {@code null}. 231 * @param y The public 'y' coordinate for the elliptic curve point. 232 * It is represented as the Base64URL encoding of the 233 * coordinate's big endian representation. Must not be 234 * {@code null}. 235 * @param use The key use, {@code null} if not specified. 236 * @param alg The intended JOSE algorithm for the key, {@code null} if 237 * not specified. 238 * @param kid The key ID, {@code null} if not specified. 239 */ 240 public ECKey(final Curve crv, final Base64URL x, final Base64URL y, 241 final Use use, final Algorithm alg, final String kid) { 242 243 this(crv, x, y, null, use, alg, kid); 244 } 245 246 247 /** 248 * Creates a new public / private Elliptic Curve JSON Web Key (JWK) 249 * with the specified parameters. 250 * 251 * @param crv The cryptographic curve. Must not be {@code null}. 252 * @param x The public 'x' coordinate for the elliptic curve point. 253 * It is represented as the Base64URL encoding of the 254 * coordinate's big endian representation. Must not be 255 * {@code null}. 256 * @param y The public 'y' coordinate for the elliptic curve point. 257 * It is represented as the Base64URL encoding of the 258 * coordinate's big endian representation. Must not be 259 * {@code null}. 260 * @param d The private 'd' coordinate for the elliptic curve point. 261 * It is represented as the Base64URL encoding of the 262 * coordinate's big endian representation. May be 263 * {@code null} if this is a public key. 264 * @param use The key use, {@code null} if not specified. 265 * @param alg The intended JOSE algorithm for the key, {@code null} if 266 * not specified. 267 * @param kid The key ID, {@code null} if not specified. 268 */ 269 public ECKey(final Curve crv, final Base64URL x, final Base64URL y, final Base64URL d, 270 final Use use, final Algorithm alg, final String kid) { 271 272 super(KeyType.EC, use, alg, kid); 273 274 if (crv == null) { 275 throw new IllegalArgumentException("The curve must not be null"); 276 } 277 278 this.crv = crv; 279 280 if (x == null) { 281 throw new IllegalArgumentException("The x coordinate must not be null"); 282 } 283 284 this.x = x; 285 286 if (y == null) { 287 throw new IllegalArgumentException("The y coordinate must not be null"); 288 } 289 290 this.y = y; 291 292 this.d = d; 293 } 294 295 296 /** 297 * Gets the cryptographic curve. 298 * 299 * @return The cryptographic curve. 300 */ 301 public Curve getCurve() { 302 303 return crv; 304 } 305 306 307 /** 308 * Gets the public 'x' coordinate for the elliptic curve point. It is 309 * represented as the Base64URL encoding of the coordinate's big endian 310 * representation. 311 * 312 * @return The 'x' coordinate. 313 */ 314 public Base64URL getX() { 315 316 return x; 317 } 318 319 320 /** 321 * Gets the public 'y' coordinate for the elliptic curve point. It is 322 * represented as the Base64URL encoding of the coordinate's big endian 323 * representation. 324 * 325 * @return The 'y' coordinate. 326 */ 327 public Base64URL getY() { 328 329 return y; 330 } 331 332 333 /** 334 * Gets the private 'd' coordinate for the elliptic curve point. It is 335 * represented as the Base64URL encoding of the coordinate's big endian 336 * representation. 337 * 338 * @return The 'd' coordinate, {@code null} if not specified (for a 339 * public key). 340 */ 341 public Base64URL getD() { 342 343 return d; 344 } 345 346 347 /** 348 * Returns a standard {@code java.security.interfaces.ECPublicKey} 349 * representation of this Elliptic Curve JWK. 350 * 351 * @return The public Elliptic Curve key. 352 * 353 * @throws UnsupportedOperationException Not yet implemented. 354 */ 355 public ECPublicKey toECPublicKey() { 356 357 // TODO 358 throw new UnsupportedOperationException("Not yet implemented"); 359 } 360 361 362 /** 363 * Returns a standard {@code java.security.interfaces.ECPrivateKey} 364 * representation of this Elliptic Curve JWK. 365 * 366 * @return The private Elliptic Curve key, {@code null} if not 367 * specified by this JWK. 368 * 369 * @throws UnsupportedOperationException Not yet implemented. 370 */ 371 public ECPrivateKey toECPrivateKey() { 372 373 if (d == null) { 374 375 // No private 'd' param 376 return null; 377 } 378 379 BigInteger privateValue = d.decodeToBigInteger(); 380 381 // See fips_186-3.pdf, p. 89 for EC curve parameter constants 382 383 ECPrivateKeySpec spec = new ECPrivateKeySpec(privateValue, null); 384 385 386 // TODO 387 throw new UnsupportedOperationException("Not yet implemented"); 388 } 389 390 391 /** 392 * Returns a standard {@code java.security.KeyPair} representation of 393 * this Elliptic Curve JWK. 394 * 395 * @return The Elliptic Curve key pair. The private Elliptic Curve key 396 * will be {@code null} if not specified. 397 */ 398 public KeyPair toKeyPair() { 399 400 return new KeyPair(toECPublicKey(), toECPrivateKey()); 401 } 402 403 404 @Override 405 public boolean isPrivate() { 406 407 if (d != null) { 408 409 return true; 410 411 } else { 412 413 return false; 414 } 415 } 416 417 418 /** 419 * Returns a copy of this Elliptic Curve JWK with any private values 420 * removed. 421 * 422 * @return The copied public Elliptic Curve JWK. 423 */ 424 @Override 425 public ECKey toPublicJWK() { 426 427 return new ECKey(getCurve(), getX(), getY(), getKeyUse(), getAlgorithm(), getKeyID()); 428 } 429 430 431 @Override 432 public JSONObject toJSONObject() { 433 434 JSONObject o = super.toJSONObject(); 435 436 // Append EC specific attributes 437 o.put("crv", crv.toString()); 438 o.put("x", x.toString()); 439 o.put("y", y.toString()); 440 441 if (d != null) { 442 o.put("d", d.toString()); 443 } 444 445 return o; 446 } 447 448 449 /** 450 * Parses a public / private Elliptic Curve JWK from the specified JSON 451 * object string representation. 452 * 453 * @param s The JSON object string to parse. Must not be {@code null}. 454 * 455 * @return The public / private Elliptic Curve JWK. 456 * 457 * @throws ParseException If the string couldn't be parsed to an 458 * Elliptic Curve JWK. 459 */ 460 public static ECKey parse(final String s) 461 throws ParseException { 462 463 return parse(JSONObjectUtils.parseJSONObject(s)); 464 } 465 466 467 /** 468 * Parses a public / private Elliptic Curve JWK from the specified JSON 469 * object representation. 470 * 471 * @param jsonObject The JSON object to parse. Must not be 472 * {@code null}. 473 * 474 * @return The public / private Elliptic Curve JWK. 475 * 476 * @throws ParseException If the JSON object couldn't be parsed to an 477 * Elliptic Curve JWK. 478 */ 479 public static ECKey parse(final JSONObject jsonObject) 480 throws ParseException { 481 482 // Parse the mandatory parameters first 483 Curve crv = Curve.parse(JSONObjectUtils.getString(jsonObject, "crv")); 484 Base64URL x = new Base64URL(JSONObjectUtils.getString(jsonObject, "x")); 485 Base64URL y = new Base64URL(JSONObjectUtils.getString(jsonObject, "y")); 486 487 // Check key type 488 KeyType kty = KeyType.parse(JSONObjectUtils.getString(jsonObject, "kty")); 489 if (kty != KeyType.EC) { 490 throw new ParseException("The key type \"kty\" must be EC", 0); 491 } 492 493 // optional private key 494 Base64URL d = null; 495 if (jsonObject.get("d") != null) { 496 d = new Base64URL(JSONObjectUtils.getString(jsonObject, "d")); 497 } 498 499 // Get optional key use 500 Use use = JWK.parseKeyUse(jsonObject); 501 502 // Get optional intended algorithm 503 Algorithm alg = JWK.parseAlgorithm(jsonObject); 504 505 // Get optional key ID 506 String kid = JWK.parseKeyID(jsonObject); 507 508 return new ECKey(crv, x, y, d, use, alg, kid); 509 } 510}