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    }