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