001package com.nimbusds.jose.jwk;
002
003
004import java.security.KeyFactory;
005import java.security.KeyPair;
006import java.security.NoSuchAlgorithmException;
007import java.security.interfaces.ECPrivateKey;
008import java.security.interfaces.ECPublicKey;
009import java.security.spec.ECParameterSpec;
010import java.security.spec.ECPoint;
011import java.security.spec.ECPrivateKeySpec;
012import java.security.spec.ECPublicKeySpec;
013import java.security.spec.InvalidKeySpecException;
014import java.text.ParseException;
015
016import net.jcip.annotations.Immutable;
017
018import net.minidev.json.JSONObject;
019
020import org.bouncycastle.jce.ECNamedCurveTable;
021import org.bouncycastle.jce.provider.BouncyCastleProvider;
022import org.bouncycastle.jce.spec.ECNamedCurveParameterSpec;
023import org.bouncycastle.jce.spec.ECNamedCurveSpec;
024
025import com.nimbusds.jose.Algorithm;
026import com.nimbusds.jose.util.Base64URL;
027import 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
069public 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}