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 2017-01-10
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         * Reference to the underlying key store, {@code null} if none.
133         */
134        private final KeyStore keyStore;
135
136
137        /**
138         * Creates a new JSON Web Key (JWK).
139         *
140         * @param kty The key type. Must not be {@code null}.
141         * @param use The key use, {@code null} if not specified or if the key
142         *            is intended for signing as well as encryption.
143         * @param ops The key operations, {@code null} if not specified.
144         * @param alg The intended JOSE algorithm for the key, {@code null} if
145         *            not specified.
146         * @param kid The key ID, {@code null} if not specified.
147         * @param x5u The X.509 certificate URL, {@code null} if not specified.
148         * @param x5t The X.509 certificate thumbprint, {@code null} if not
149         *            specified.
150         * @param x5c The X.509 certificate chain, {@code null} if not 
151         *            specified.
152         * @param ks  Reference to the underlying key store, {@code null} if
153         *            none.
154         */
155        protected JWK(final KeyType kty,
156                      final KeyUse use,
157                      final Set<KeyOperation> ops,
158                      final Algorithm alg,
159                      final String kid,
160                      final URI x5u,
161                      final Base64URL x5t,
162                      final List<Base64> x5c,
163                      final KeyStore ks) {
164
165                if (kty == null) {
166                        throw new IllegalArgumentException("The key type \"kty\" parameter must not be null");
167                }
168
169                this.kty = kty;
170
171                if (use != null && ops != null) {
172                        throw new IllegalArgumentException("They key use \"use\" and key options \"key_opts\" parameters cannot be set together");
173                }
174
175                this.use = use;
176                this.ops = ops;
177
178                this.alg = alg;
179                this.kid = kid;
180
181                this.x5u = x5u;
182                this.x5t = x5t;
183                this.x5c = x5c;
184                
185                this.keyStore = ks;
186        }
187
188
189        /**
190         * Gets the type ({@code kty}) of this JWK.
191         *
192         * @return The key type.
193         */
194        public KeyType getKeyType() {
195
196                return kty;
197        }
198
199
200        /**
201         * Gets the use ({@code use}) of this JWK.
202         *
203         * @return The key use, {@code null} if not specified or if the key is
204         *         intended for signing as well as encryption.
205         */
206        public KeyUse getKeyUse() {
207
208                return use;
209        }
210
211
212        /**
213         * Gets the operations ({@code key_ops}) for this JWK.
214         *
215         * @return The key operations, {@code null} if not specified.
216         */
217        public Set<KeyOperation> getKeyOperations() {
218
219                return ops;
220        }
221
222
223        /**
224         * Gets the intended JOSE algorithm ({@code alg}) for this JWK.
225         *
226         * @return The intended JOSE algorithm, {@code null} if not specified.
227         */
228        public Algorithm getAlgorithm() {
229
230                return alg;
231        }
232
233
234        /**
235         * Gets the ID ({@code kid}) of this JWK. The key ID can be used to 
236         * match a specific key. This can be used, for instance, to choose a 
237         * key within a {@link JWKSet} during key rollover. The key ID may also 
238         * correspond to a JWS/JWE {@code kid} header parameter value.
239         *
240         * @return The key ID, {@code null} if not specified.
241         */
242        public String getKeyID() {
243
244                return kid;
245        }
246
247
248        /**
249         * Gets the X.509 certificate URL ({@code x5u}) of this JWK.
250         *
251         * @return The X.509 certificate URL, {@code null} if not specified.
252         */
253        public URI getX509CertURL() {
254
255                return x5u;
256        }
257
258
259        /**
260         * Gets the X.509 certificate SHA-1 thumbprint ({@code x5t}) of this
261         * JWK.
262         *
263         * @return The X.509 certificate SHA-1 thumbprint, {@code null} if not
264         *         specified.
265         */
266        public Base64URL getX509CertThumbprint() {
267
268                return x5t;
269        }
270
271
272        /**
273         * Gets the X.509 certificate chain ({@code x5c}) of this JWK.
274         *
275         * @return The X.509 certificate chain as a unmodifiable list,
276         *         {@code null} if not specified.
277         */
278        public List<Base64> getX509CertChain() {
279
280                if (x5c == null) {
281                        return null;
282                }
283
284                return Collections.unmodifiableList(x5c);
285        }
286        
287        
288        /**
289         * Returns a reference to the underlying key store.
290         *
291         * @return The underlying key store, {@code null} if none.
292         */
293        public KeyStore getKeyStore() {
294                
295                return keyStore;
296        }
297
298
299        /**
300         * Returns the required JWK parameters. Intended as input for JWK
301         * thumbprint computation. See RFC 7638 for more information.
302         *
303         * @return The required JWK parameters, sorted alphanumerically by key
304         *         name and ready for JSON serialisation.
305         */
306        public abstract LinkedHashMap<String,?> getRequiredParams();
307
308
309        /**
310         * Computes the SHA-256 thumbprint of this JWK. See RFC 7638 for more
311         * information.
312         *
313         * @return The SHA-256 thumbprint.
314         *
315         * @throws JOSEException If the SHA-256 hash algorithm is not
316         *                       supported.
317         */
318        public Base64URL computeThumbprint()
319                throws JOSEException {
320
321                return computeThumbprint("SHA-256");
322        }
323
324
325        /**
326         * Computes the thumbprint of this JWK using the specified hash
327         * algorithm. See RFC 7638 for more information.
328         *
329         * @param hashAlg The hash algorithm. Must not be {@code null}.
330         *
331         * @return The SHA-256 thumbprint.
332         *
333         * @throws JOSEException If the hash algorithm is not supported.
334         */
335        public Base64URL computeThumbprint(final String hashAlg)
336                throws JOSEException {
337
338                return ThumbprintUtils.compute(hashAlg, this);
339        }
340
341
342        /**
343         * Returns {@code true} if this JWK contains private or sensitive
344         * (non-public) parameters.
345         *
346         * @return {@code true} if this JWK contains private parameters, else
347         *         {@code false}.
348         */
349        public abstract boolean isPrivate();
350
351
352        /**
353         * Creates a copy of this JWK with all private or sensitive parameters 
354         * removed.
355         * 
356         * @return The newly created public JWK, or {@code null} if none can be
357         *         created.
358         */
359        public abstract JWK toPublicJWK();
360
361
362        /**
363         * Returns the size of this JWK.
364         *
365         * @return The JWK size, in bits.
366         */
367        public abstract int size();
368
369
370        /**
371         * Returns a JSON object representation of this JWK. This method is 
372         * intended to be called from extending classes.
373         *
374         * <p>Example:
375         *
376         * <pre>
377         * {
378         *   "kty" : "RSA",
379         *   "use" : "sig",
380         *   "kid" : "fd28e025-8d24-48bc-a51a-e2ffc8bc274b"
381         * }
382         * </pre>
383         *
384         * @return The JSON object representation.
385         */
386        public JSONObject toJSONObject() {
387
388                JSONObject o = new JSONObject();
389
390                o.put("kty", kty.getValue());
391
392                if (use != null) {
393                        o.put("use", use.identifier());
394                }
395
396                if (ops != null) {
397
398                        List<String> sl = new ArrayList<>(ops.size());
399
400                        for (KeyOperation op: ops) {
401                                sl.add(op.identifier());
402                        }
403
404                        o.put("key_ops", sl);
405                }
406
407                if (alg != null) {
408                        o.put("alg", alg.getName());
409                }
410
411                if (kid != null) {
412                        o.put("kid", kid);
413                }
414
415                if (x5u != null) {
416                        o.put("x5u", x5u.toString());
417                }
418
419                if (x5t != null) {
420                        o.put("x5t", x5t.toString());
421                }
422
423                if (x5c != null) {
424                        o.put("x5c", x5c);
425                }
426
427                return o;
428        }
429
430
431        /**
432         * Returns the JSON object string representation of this JWK.
433         *
434         * @return The JSON object string representation.
435         */
436        @Override
437        public String toJSONString() {
438
439                return toJSONObject().toString();
440        }
441
442
443        /**
444         * @see #toJSONString
445         */
446        @Override
447        public String toString() {
448
449                return toJSONObject().toString();
450        }
451
452
453        /**
454         * Parses a JWK from the specified JSON object string representation. 
455         * The JWK must be an {@link ECKey}, an {@link RSAKey}, or a 
456         * {@link OctetSequenceKey}.
457         *
458         * @param s The JSON object string to parse. Must not be {@code null}.
459         *
460         * @return The JWK.
461         *
462         * @throws ParseException If the string couldn't be parsed to a
463         *                        supported JWK.
464         */
465        public static JWK parse(final String s)
466                throws ParseException {
467
468                return parse(JSONObjectUtils.parse(s));
469        }
470
471
472        /**
473         * Parses a JWK from the specified JSON object representation. The JWK 
474         * must be an {@link ECKey}, an {@link RSAKey}, or a 
475         * {@link OctetSequenceKey}.
476         *
477         * @param jsonObject The JSON object to parse. Must not be 
478         *                   {@code null}.
479         *
480         * @return The JWK.
481         *
482         * @throws ParseException If the JSON object couldn't be parsed to a 
483         *                        supported JWK.
484         */
485        public static JWK parse(final JSONObject jsonObject)
486                throws ParseException {
487
488                KeyType kty = KeyType.parse(JSONObjectUtils.getString(jsonObject, "kty"));
489
490                if (kty == KeyType.EC) {
491                        
492                        return ECKey.parse(jsonObject);
493
494                } else if (kty == KeyType.RSA) {
495                        
496                        return RSAKey.parse(jsonObject);
497
498                } else if (kty == KeyType.OCT) {
499                        
500                        return OctetSequenceKey.parse(jsonObject);
501
502                } else {
503
504                        throw new ParseException("Unsupported key type \"kty\" parameter: " + kty, 0);
505                }
506        }
507        
508        
509        /**
510         * Parses a public {@link RSAKey RSA} or {@link ECKey EC JWK} from the
511         * specified X.509 certificate. Requires BouncyCastle.
512         *
513         * <p><strong>Important:</strong> The X.509 certificate is not
514         * validated!
515         *
516         * <p>Set the following JWK parameters:
517         *
518         * <ul>
519         *     <li>For an EC key the curve is obtained from the subject public
520         *         key info algorithm parameters.
521         *     <li>The JWK use inferred by {@link KeyUse#from}.
522         *     <li>The JWK ID from the X.509 serial number (in base 10).
523         *     <li>The JWK X.509 certificate chain (this certificate only).
524         *     <li>The JWK X.509 certificate SHA-1 thumbprint.
525         * </ul>
526         *
527         * @param cert The X.509 certificate. Must not be {@code null}.
528         *
529         * @return The public RSA or EC JWK.
530         *
531         * @throws JOSEException If parsing failed.
532         */
533        public static JWK parse(final X509Certificate cert)
534                throws JOSEException {
535                
536                if (cert.getPublicKey() instanceof RSAPublicKey) {
537                        return RSAKey.parse(cert);
538                } else if (cert.getPublicKey() instanceof ECPublicKey) {
539                        return ECKey.parse(cert);
540                } else {
541                        throw new JOSEException("Unsupported public key algorithm: " + cert.getPublicKey().getAlgorithm());
542                }
543        }
544        
545        
546        /**
547         * Loads a JWK from the specified JCE key store. The JWK can be a
548         * public / private {@link RSAKey RSA key}, a public / private
549         * {@link ECKey EC key}, or a {@link OctetSequenceKey secret key}.
550         * Requires BouncyCastle.
551         *
552         * <p><strong>Important:</strong> The X.509 certificate is not
553         * validated!
554         *
555         * @param keyStore The key store. Must not be {@code null}.
556         * @param alias    The alias. Must not be {@code null}.
557         * @param pin      The pin to unlock the private key if any, empty or
558         *                 {@code null} if not required.
559         *
560         * @return The public / private RSA or EC JWK, or secret JWK, or
561         *         {@code null} if no key with the specified alias was found.
562         *
563         * @throws KeyStoreException On a key store exception.
564         * @throws JOSEException     If RSA or EC key loading failed.
565         */
566        public static JWK load(final KeyStore keyStore, final String alias, final char[] pin)
567                throws KeyStoreException, JOSEException {
568                
569                java.security.cert.Certificate cert = keyStore.getCertificate(alias);
570                
571                if (cert == null) {
572                        // Try secret key
573                        return OctetSequenceKey.load(keyStore, alias, pin);
574                }
575                
576                if (cert.getPublicKey() instanceof RSAPublicKey) {
577                        return RSAKey.load(keyStore, alias, pin);
578                } else if (cert.getPublicKey() instanceof ECPublicKey) {
579                        return ECKey.load(keyStore, alias, pin);
580                } else {
581                        throw new JOSEException("Unsupported public key algorithm: " + cert.getPublicKey().getAlgorithm());
582                }
583        }
584}