001/*
002 * nimbus-jose-jwt
003 *
004 * Copyright 2012-2016, Connect2id Ltd.
005 *
006 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use
007 * this file except in compliance with the License. You may obtain a copy of the
008 * License at
009 *
010 *    http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software distributed
013 * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
014 * CONDITIONS OF ANY KIND, either express or implied. See the License for the
015 * specific language governing permissions and limitations under the License.
016 */
017
018package com.nimbusds.jose.jwk;
019
020
021import java.io.Serializable;
022import java.net.URI;
023import java.security.KeyStore;
024import java.security.KeyStoreException;
025import java.security.cert.X509Certificate;
026import java.security.interfaces.ECPublicKey;
027import java.security.interfaces.RSAPublicKey;
028import java.text.ParseException;
029import java.util.*;
030
031import com.nimbusds.jose.Algorithm;
032import com.nimbusds.jose.JOSEException;
033import com.nimbusds.jose.util.Base64;
034import com.nimbusds.jose.util.Base64URL;
035import com.nimbusds.jose.util.JSONObjectUtils;
036import net.minidev.json.JSONAware;
037import net.minidev.json.JSONObject;
038
039
040/**
041 * The base abstract class for JSON Web Keys (JWKs). It serialises to a JSON
042 * object.
043 *
044 * <p>The following JSON object members are common to all JWK types:
045 *
046 * <ul>
047 *     <li>{@link #getKeyType kty} (required)
048 *     <li>{@link #getKeyUse use} (optional)
049 *     <li>{@link #getKeyOperations key_ops} (optional)
050 *     <li>{@link #getKeyID kid} (optional)
051 * </ul>
052 *
053 * <p>Example JWK (of the Elliptic Curve type):
054 *
055 * <pre>
056 * {
057 *   "kty" : "EC",
058 *   "crv" : "P-256",
059 *   "x"   : "MKBCTNIcKUSDii11ySs3526iDZ8AiTo7Tu6KPAqv7D4",
060 *   "y"   : "4Etl6SRW2YiLUrN5vfvVHuhp7x8PxltmWWlbbM4IFyM",
061 *   "use" : "enc",
062 *   "kid" : "1"
063 * }
064 * </pre>
065 *
066 * @author Vladimir Dzhuvinov
067 * @author Justin Richer
068 * @version 2016-12-07
069 */
070public abstract class JWK implements JSONAware, Serializable {
071
072
073        private static final long serialVersionUID = 1L;
074
075
076        /**
077         * The MIME type of JWK objects: 
078         * {@code application/jwk+json; charset=UTF-8}
079         */
080        public static final String MIME_TYPE = "application/jwk+json; charset=UTF-8";
081
082
083        /**
084         * The key type, required.
085         */
086        private final KeyType kty;
087
088
089        /**
090         * The key use, optional.
091         */
092        private final KeyUse use;
093
094
095        /**
096         * The key operations, optional.
097         */
098        private final Set<KeyOperation> ops;
099
100
101        /**
102         * The intended JOSE algorithm for the key, optional.
103         */
104        private final Algorithm alg;
105
106
107        /**
108         * The key ID, optional.
109         */
110        private final String kid;
111
112
113        /**
114         * X.509 certificate URL, optional.
115         */
116        private final URI x5u;
117
118
119        /**
120         * X.509 certificate thumbprint, optional.
121         */
122        private final Base64URL x5t;
123
124
125        /**
126         * The X.509 certificate chain, optional.
127         */
128        private final List<Base64> x5c;
129
130
131        /**
132         * Creates a new JSON Web Key (JWK).
133         *
134         * @param kty The key type. Must not be {@code null}.
135         * @param use The key use, {@code null} if not specified or if the key
136         *            is intended for signing as well as encryption.
137         * @param ops The key operations, {@code null} if not specified.
138         * @param alg The intended JOSE algorithm for the key, {@code null} if
139         *            not specified.
140         * @param kid The key ID, {@code null} if not specified.
141         * @param x5u The X.509 certificate URL, {@code null} if not specified.
142         * @param x5t The X.509 certificate thumbprint, {@code null} if not
143         *            specified.
144         * @param x5c The X.509 certificate chain, {@code null} if not 
145         *            specified.
146         */
147        public JWK(final KeyType kty,
148                   final KeyUse use,
149                   final Set<KeyOperation> ops,
150                   final Algorithm alg,
151                   final String kid,
152                   final URI x5u,
153                   final Base64URL x5t,
154                   final List<Base64> x5c) {
155
156                if (kty == null) {
157                        throw new IllegalArgumentException("The key type \"kty\" parameter must not be null");
158                }
159
160                this.kty = kty;
161
162                if (use != null && ops != null) {
163                        throw new IllegalArgumentException("They key use \"use\" and key options \"key_opts\" parameters cannot be set together");
164                }
165
166                this.use = use;
167                this.ops = ops;
168
169                this.alg = alg;
170                this.kid = kid;
171
172                this.x5u = x5u;
173                this.x5t = x5t;
174                this.x5c = x5c;
175        }
176
177
178        /**
179         * Gets the type ({@code kty}) of this JWK.
180         *
181         * @return The key type.
182         */
183        public KeyType getKeyType() {
184
185                return kty;
186        }
187
188
189        /**
190         * Gets the use ({@code use}) of this JWK.
191         *
192         * @return The key use, {@code null} if not specified or if the key is
193         *         intended for signing as well as encryption.
194         */
195        public KeyUse getKeyUse() {
196
197                return use;
198        }
199
200
201        /**
202         * Gets the operations ({@code key_ops}) for this JWK.
203         *
204         * @return The key operations, {@code null} if not specified.
205         */
206        public Set<KeyOperation> getKeyOperations() {
207
208                return ops;
209        }
210
211
212        /**
213         * Gets the intended JOSE algorithm ({@code alg}) for this JWK.
214         *
215         * @return The intended JOSE algorithm, {@code null} if not specified.
216         */
217        public Algorithm getAlgorithm() {
218
219                return alg;
220        }
221
222
223        /**
224         * Gets the ID ({@code kid}) of this JWK. The key ID can be used to 
225         * match a specific key. This can be used, for instance, to choose a 
226         * key within a {@link JWKSet} during key rollover. The key ID may also 
227         * correspond to a JWS/JWE {@code kid} header parameter value.
228         *
229         * @return The key ID, {@code null} if not specified.
230         */
231        public String getKeyID() {
232
233                return kid;
234        }
235
236
237        /**
238         * Gets the X.509 certificate URL ({@code x5u}) of this JWK.
239         *
240         * @return The X.509 certificate URL, {@code null} if not specified.
241         */
242        public URI getX509CertURL() {
243
244                return x5u;
245        }
246
247
248        /**
249         * Gets the X.509 certificate SHA-1 thumbprint ({@code x5t}) of this
250         * JWK.
251         *
252         * @return The X.509 certificate SHA-1 thumbprint, {@code null} if not
253         *         specified.
254         */
255        public Base64URL getX509CertThumbprint() {
256
257                return x5t;
258        }
259
260
261        /**
262         * Gets the X.509 certificate chain ({@code x5c}) of this JWK.
263         *
264         * @return The X.509 certificate chain as a unmodifiable list,
265         *         {@code null} if not specified.
266         */
267        public List<Base64> getX509CertChain() {
268
269                if (x5c == null) {
270                        return null;
271                }
272
273                return Collections.unmodifiableList(x5c);
274        }
275
276
277        /**
278         * Returns the required JWK parameters. Intended as input for JWK
279         * thumbprint computation. See RFC 7638 for more information.
280         *
281         * @return The required JWK parameters, sorted alphanumerically by key
282         *         name and ready for JSON serialisation.
283         */
284        public abstract LinkedHashMap<String,?> getRequiredParams();
285
286
287        /**
288         * Computes the SHA-256 thumbprint of this JWK. See RFC 7638 for more
289         * information.
290         *
291         * @return The SHA-256 thumbprint.
292         *
293         * @throws JOSEException If the SHA-256 hash algorithm is not
294         *                       supported.
295         */
296        public Base64URL computeThumbprint()
297                throws JOSEException {
298
299                return computeThumbprint("SHA-256");
300        }
301
302
303        /**
304         * Computes the thumbprint of this JWK using the specified hash
305         * algorithm. See RFC 7638 for more information.
306         *
307         * @param hashAlg The hash algorithm. Must not be {@code null}.
308         *
309         * @return The SHA-256 thumbprint.
310         *
311         * @throws JOSEException If the hash algorithm is not supported.
312         */
313        public Base64URL computeThumbprint(final String hashAlg)
314                throws JOSEException {
315
316                return ThumbprintUtils.compute(hashAlg, this);
317        }
318
319
320        /**
321         * Returns {@code true} if this JWK contains private or sensitive
322         * (non-public) parameters.
323         *
324         * @return {@code true} if this JWK contains private parameters, else
325         *         {@code false}.
326         */
327        public abstract boolean isPrivate();
328
329
330        /**
331         * Creates a copy of this JWK with all private or sensitive parameters 
332         * removed.
333         * 
334         * @return The newly created public JWK, or {@code null} if none can be
335         *         created.
336         */
337        public abstract JWK toPublicJWK();
338
339
340        /**
341         * Returns the size of this JWK.
342         *
343         * @return The JWK size, in bits.
344         */
345        public abstract int size();
346
347
348        /**
349         * Returns a JSON object representation of this JWK. This method is 
350         * intended to be called from extending classes.
351         *
352         * <p>Example:
353         *
354         * <pre>
355         * {
356         *   "kty" : "RSA",
357         *   "use" : "sig",
358         *   "kid" : "fd28e025-8d24-48bc-a51a-e2ffc8bc274b"
359         * }
360         * </pre>
361         *
362         * @return The JSON object representation.
363         */
364        public JSONObject toJSONObject() {
365
366                JSONObject o = new JSONObject();
367
368                o.put("kty", kty.getValue());
369
370                if (use != null) {
371                        o.put("use", use.identifier());
372                }
373
374                if (ops != null) {
375
376                        List<String> sl = new ArrayList<>(ops.size());
377
378                        for (KeyOperation op: ops) {
379                                sl.add(op.identifier());
380                        }
381
382                        o.put("key_ops", sl);
383                }
384
385                if (alg != null) {
386                        o.put("alg", alg.getName());
387                }
388
389                if (kid != null) {
390                        o.put("kid", kid);
391                }
392
393                if (x5u != null) {
394                        o.put("x5u", x5u.toString());
395                }
396
397                if (x5t != null) {
398                        o.put("x5t", x5t.toString());
399                }
400
401                if (x5c != null) {
402                        o.put("x5c", x5c);
403                }
404
405                return o;
406        }
407
408
409        /**
410         * Returns the JSON object string representation of this JWK.
411         *
412         * @return The JSON object string representation.
413         */
414        @Override
415        public String toJSONString() {
416
417                return toJSONObject().toString();
418        }
419
420
421        /**
422         * @see #toJSONString
423         */
424        @Override
425        public String toString() {
426
427                return toJSONObject().toString();
428        }
429
430
431        /**
432         * Parses a JWK from the specified JSON object string representation. 
433         * The JWK must be an {@link ECKey}, an {@link RSAKey}, or a 
434         * {@link OctetSequenceKey}.
435         *
436         * @param s The JSON object string to parse. Must not be {@code null}.
437         *
438         * @return The JWK.
439         *
440         * @throws ParseException If the string couldn't be parsed to a
441         *                        supported JWK.
442         */
443        public static JWK parse(final String s)
444                throws ParseException {
445
446                return parse(JSONObjectUtils.parse(s));
447        }
448
449
450        /**
451         * Parses a JWK from the specified JSON object representation. The JWK 
452         * must be an {@link ECKey}, an {@link RSAKey}, or a 
453         * {@link OctetSequenceKey}.
454         *
455         * @param jsonObject The JSON object to parse. Must not be 
456         *                   {@code null}.
457         *
458         * @return The JWK.
459         *
460         * @throws ParseException If the JSON object couldn't be parsed to a 
461         *                        supported JWK.
462         */
463        public static JWK parse(final JSONObject jsonObject)
464                throws ParseException {
465
466                KeyType kty = KeyType.parse(JSONObjectUtils.getString(jsonObject, "kty"));
467
468                if (kty == KeyType.EC) {
469                        
470                        return ECKey.parse(jsonObject);
471
472                } else if (kty == KeyType.RSA) {
473                        
474                        return RSAKey.parse(jsonObject);
475
476                } else if (kty == KeyType.OCT) {
477                        
478                        return OctetSequenceKey.parse(jsonObject);
479
480                } else {
481
482                        throw new ParseException("Unsupported key type \"kty\" parameter: " + kty, 0);
483                }
484        }
485        
486        
487        /**
488         * Parses a public {@link RSAKey RSA} or {@link ECKey EC JWK} from the
489         * specified X.509 certificate. Requires BouncyCastle.
490         *
491         * <p><strong>Important:</strong> The X.509 certificate is not
492         * validated!
493         *
494         * <p>Set the following JWK parameters:
495         *
496         * <ul>
497         *     <li>For an EC key the curve is obtained from the subject public
498         *         key info algorithm parameters.
499         *     <li>The JWK use inferred by {@link KeyUse#from}.
500         *     <li>The JWK ID from the X.509 serial number (in base 10).
501         *     <li>The JWK X.509 certificate chain (this certificate only).
502         *     <li>The JWK X.509 certificate SHA-1 thumbprint.
503         * </ul>
504         *
505         * @param cert The X.509 certificate. Must not be {@code null}.
506         *
507         * @return The public RSA or EC JWK.
508         *
509         * @throws JOSEException If parsing failed.
510         */
511        public static JWK parse(final X509Certificate cert)
512                throws JOSEException {
513                
514                if (cert.getPublicKey() instanceof RSAPublicKey) {
515                        return RSAKey.parse(cert);
516                } else if (cert.getPublicKey() instanceof ECPublicKey) {
517                        return ECKey.parse(cert);
518                } else {
519                        throw new JOSEException("Unsupported public key algorithm: " + cert.getPublicKey().getAlgorithm());
520                }
521        }
522        
523        
524        /**
525         * Loads a JWK from the specified JCE key store. The JWK can be a
526         * public / private {@link RSAKey RSA key}, a public / private
527         * {@link ECKey EC key}, or a {@link OctetSequenceKey secret key}.
528         * Requires BouncyCastle.
529         *
530         * <p><strong>Important:</strong> The X.509 certificate is not
531         * validated!
532         *
533         * @param keyStore The key store. Must not be {@code null}.
534         * @param alias    The alias. Must not be {@code null}.
535         * @param pin      The pin to unlock the private key if any, empty or
536         *                 {@code null} if not required.
537         *
538         * @return The public / private RSA or EC JWK, or secret JWK, or
539         *         {@code null} if no key with the specified alias was found.
540         *
541         * @throws KeyStoreException On a key store exception.
542         * @throws JOSEException     If RSA or EC key loading failed.
543         */
544        public static JWK load(final KeyStore keyStore, final String alias, final char[] pin)
545                throws KeyStoreException, JOSEException {
546                
547                java.security.cert.Certificate cert = keyStore.getCertificate(alias);
548                
549                if (cert == null) {
550                        // Try secret key
551                        return OctetSequenceKey.load(keyStore, alias, pin);
552                }
553                
554                if (cert.getPublicKey() instanceof RSAPublicKey) {
555                        return RSAKey.load(keyStore, alias, pin);
556                } else if (cert.getPublicKey() instanceof ECPublicKey) {
557                        return ECKey.load(keyStore, alias, pin);
558                } else {
559                        throw new JOSEException("Unsupported public key algorithm: " + cert.getPublicKey().getAlgorithm());
560                }
561        }
562}