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}