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