001package com.nimbusds.jose.jwk;
002
003
004import java.io.Serializable;
005
006import java.net.URI;
007import java.text.ParseException;
008import java.util.*;
009
010import net.minidev.json.JSONAware;
011import net.minidev.json.JSONObject;
012
013import com.nimbusds.jose.Algorithm;
014import com.nimbusds.jose.JOSEException;
015import com.nimbusds.jose.util.Base64;
016import com.nimbusds.jose.util.Base64URL;
017import com.nimbusds.jose.util.JSONObjectUtils;
018
019
020/**
021 * The base abstract class for JSON Web Keys (JWKs). It serialises to a JSON
022 * object.
023 *
024 * <p>The following JSON object members are common to all JWK types:
025 *
026 * <ul>
027 *     <li>{@link #getKeyType kty} (required)
028 *     <li>{@link #getKeyUse use} (optional)
029 *     <li>{@link #getKeyOperations key_ops} (optional)
030 *     <li>{@link #getKeyID kid} (optional)
031 * </ul>
032 *
033 * <p>Example JWK (of the Elliptic Curve type):
034 *
035 * <pre>
036 * {
037 *   "kty" : "EC",
038 *   "crv" : "P-256",
039 *   "x"   : "MKBCTNIcKUSDii11ySs3526iDZ8AiTo7Tu6KPAqv7D4",
040 *   "y"   : "4Etl6SRW2YiLUrN5vfvVHuhp7x8PxltmWWlbbM4IFyM",
041 *   "use" : "enc",
042 *   "kid" : "1"
043 * }
044 * </pre>
045 *
046 * @author Vladimir Dzhuvinov
047 * @author Justin Richer
048 * @version 2015-09-28
049 */
050public abstract class JWK implements JSONAware, Serializable {
051
052
053        private static final long serialVersionUID = 1L;
054
055
056        /**
057         * The MIME type of JWK objects: 
058         * {@code application/jwk+json; charset=UTF-8}
059         */
060        public static final String MIME_TYPE = "application/jwk+json; charset=UTF-8";
061
062
063        /**
064         * The key type, required.
065         */
066        private final KeyType kty;
067
068
069        /**
070         * The key use, optional.
071         */
072        private final KeyUse use;
073
074
075        /**
076         * The key operations, optional.
077         */
078        private final Set<KeyOperation> ops;
079
080
081        /**
082         * The intended JOSE algorithm for the key, optional.
083         */
084        private final Algorithm alg;
085
086
087        /**
088         * The key ID, optional.
089         */
090        private final String kid;
091
092
093        /**
094         * X.509 certificate URL, optional.
095         */
096        private final URI x5u;
097
098
099        /**
100         * X.509 certificate thumbprint, optional.
101         */
102        private final Base64URL x5t;
103
104
105        /**
106         * The X.509 certificate chain, optional.
107         */
108        private final List<Base64> x5c;
109
110
111        /**
112         * Creates a new JSON Web Key (JWK).
113         *
114         * @param kty The key type. Must not be {@code null}.
115         * @param use The key use, {@code null} if not specified or if the key
116         *            is intended for signing as well as encryption.
117         * @param ops The key operations, {@code null} if not specified.
118         * @param alg The intended JOSE algorithm for the key, {@code null} if
119         *            not specified.
120         * @param kid The key ID, {@code null} if not specified.
121         * @param x5u The X.509 certificate URL, {@code null} if not specified.
122         * @param x5t The X.509 certificate thumbprint, {@code null} if not
123         *            specified.
124         * @param x5c The X.509 certificate chain, {@code null} if not 
125         *            specified.
126         */
127        public JWK(final KeyType kty,
128                   final KeyUse use,
129                   final Set<KeyOperation> ops,
130                   final Algorithm alg,
131                   final String kid,
132                   final URI x5u,
133                   final Base64URL x5t,
134                   final List<Base64> x5c) {
135
136                if (kty == null) {
137                        throw new IllegalArgumentException("The key type \"kty\" parameter must not be null");
138                }
139
140                this.kty = kty;
141
142                if (use != null && ops != null) {
143                        throw new IllegalArgumentException("They key use \"use\" and key options \"key_opts\" parameters cannot be set together");
144                }
145
146                this.use = use;
147                this.ops = ops;
148
149                this.alg = alg;
150                this.kid = kid;
151
152                this.x5u = x5u;
153                this.x5t = x5t;
154                this.x5c = x5c;
155        }
156
157
158        /**
159         * Gets the type ({@code kty}) of this JWK.
160         *
161         * @return The key type.
162         */
163        public KeyType getKeyType() {
164
165                return kty;
166        }
167
168
169        /**
170         * Gets the use ({@code use}) of this JWK.
171         *
172         * @return The key use, {@code null} if not specified or if the key is
173         *         intended for signing as well as encryption.
174         */
175        public KeyUse getKeyUse() {
176
177                return use;
178        }
179
180
181        /**
182         * Gets the operations ({@code key_ops}) for this JWK.
183         *
184         * @return The key operations, {@code null} if not specified.
185         */
186        public Set<KeyOperation> getKeyOperations() {
187
188                return ops;
189        }
190
191
192        /**
193         * Gets the intended JOSE algorithm ({@code alg}) for this JWK.
194         *
195         * @return The intended JOSE algorithm, {@code null} if not specified.
196         */
197        public Algorithm getAlgorithm() {
198
199                return alg;
200        }
201
202
203        /**
204         * Gets the ID ({@code kid}) of this JWK. The key ID can be used to 
205         * match a specific key. This can be used, for instance, to choose a 
206         * key within a {@link JWKSet} during key rollover. The key ID may also 
207         * correspond to a JWS/JWE {@code kid} header parameter value.
208         *
209         * @return The key ID, {@code null} if not specified.
210         */
211        public String getKeyID() {
212
213                return kid;
214        }
215
216
217        /**
218         * Gets the X.509 certificate URL ({@code x5u}) of this JWK.
219         *
220         * @return The X.509 certificate URL, {@code null} if not specified.
221         */
222        public URI getX509CertURL() {
223
224                return x5u;
225        }
226
227
228        /**
229         * Gets the X.509 certificate thumbprint ({@code x5t}) of this JWK.
230         *
231         * @return The X.509 certificate thumbprint, {@code null} if not
232         *         specified.
233         */
234        public Base64URL getX509CertThumbprint() {
235
236                return x5t;
237        }
238
239
240        /**
241         * Gets the X.509 certificate chain ({@code x5c}) of this JWK.
242         *
243         * @return The X.509 certificate chain as a unmodifiable list,
244         *         {@code null} if not specified.
245         */
246        public List<Base64> getX509CertChain() {
247
248                if (x5c == null) {
249                        return null;
250                }
251
252                return Collections.unmodifiableList(x5c);
253        }
254
255
256        /**
257         * Returns the required JWK parameters. Intended as input for JWK
258         * thumbprint computation. See RFC 7638 for more information.
259         *
260         * @return The required JWK parameters, sorted alphanumerically by key
261         *         name and ready for JSON serialisation.
262         */
263        public abstract LinkedHashMap<String,?> getRequiredParams();
264
265
266        /**
267         * Computes the SHA-256 thumbprint of this JWK. See RFC 7638 for more
268         * information.
269         *
270         * @return The SHA-256 thumbprint.
271         *
272         * @throws JOSEException If the SHA-256 hash algorithm is not
273         *                       supported.
274         */
275        public Base64URL computeThumbprint()
276                throws JOSEException {
277
278                return computeThumbprint("SHA-256");
279        }
280
281
282        /**
283         * Computes the thumbprint of this JWK using the specified hash
284         * algorithm. See RFC 7638 for more information.
285         *
286         * @param hashAlg The hash algorithm. Must not be {@code null}.
287         *
288         * @return The SHA-256 thumbprint.
289         *
290         * @throws JOSEException If the hash algorithm is not supported.
291         */
292        public Base64URL computeThumbprint(final String hashAlg)
293                throws JOSEException {
294
295                return ThumbprintUtils.compute(hashAlg, this);
296        }
297
298
299        /**
300         * Returns {@code true} if this JWK contains private or sensitive
301         * (non-public) parameters.
302         *
303         * @return {@code true} if this JWK contains private parameters, else
304         *         {@code false}.
305         */
306        public abstract boolean isPrivate();
307
308
309        /**
310         * Creates a copy of this JWK with all private or sensitive parameters 
311         * removed.
312         * 
313         * @return The newly created public JWK, or {@code null} if none can be
314         *         created.
315         */
316        public abstract JWK toPublicJWK();
317
318
319        /**
320         * Returns a JSON object representation of this JWK. This method is 
321         * intended to be called from extending classes.
322         *
323         * <p>Example:
324         *
325         * <pre>
326         * {
327         *   "kty" : "RSA",
328         *   "use" : "sig",
329         *   "kid" : "fd28e025-8d24-48bc-a51a-e2ffc8bc274b"
330         * }
331         * </pre>
332         *
333         * @return The JSON object representation.
334         */
335        public JSONObject toJSONObject() {
336
337                JSONObject o = new JSONObject();
338
339                o.put("kty", kty.getValue());
340
341                if (use != null) {
342                        o.put("use", use.identifier());
343                }
344
345                if (ops != null) {
346
347                        List<String> sl = new ArrayList<>(ops.size());
348
349                        for (KeyOperation op: ops) {
350                                sl.add(op.identifier());
351                        }
352
353                        o.put("key_ops", sl);
354                }
355
356                if (alg != null) {
357                        o.put("alg", alg.getName());
358                }
359
360                if (kid != null) {
361                        o.put("kid", kid);
362                }
363
364                if (x5u != null) {
365                        o.put("x5u", x5u.toString());
366                }
367
368                if (x5t != null) {
369                        o.put("x5t", x5t.toString());
370                }
371
372                if (x5c != null) {
373                        o.put("x5c", x5c);
374                }
375
376                return o;
377        }
378
379
380        /**
381         * Returns the JSON object string representation of this JWK.
382         *
383         * @return The JSON object string representation.
384         */
385        @Override
386        public String toJSONString() {
387
388                return toJSONObject().toString();
389        }
390
391
392        /**
393         * @see #toJSONString
394         */
395        @Override
396        public String toString() {
397
398                return toJSONObject().toString();
399        }
400
401
402        /**
403         * Parses a JWK from the specified JSON object string representation. 
404         * The JWK must be an {@link ECKey}, an {@link RSAKey}, or a 
405         * {@link OctetSequenceKey}.
406         *
407         * @param s The JSON object string to parse. Must not be {@code null}.
408         *
409         * @return The JWK.
410         *
411         * @throws ParseException If the string couldn't be parsed to a
412         *                        supported JWK.
413         */
414        public static JWK parse(final String s)
415                throws ParseException {
416
417                return parse(JSONObjectUtils.parse(s));
418        }
419
420
421        /**
422         * Parses a JWK from the specified JSON object representation. The JWK 
423         * must be an {@link ECKey}, an {@link RSAKey}, or a 
424         * {@link OctetSequenceKey}.
425         *
426         * @param jsonObject The JSON object to parse. Must not be 
427         *                   {@code null}.
428         *
429         * @return The JWK.
430         *
431         * @throws ParseException If the JSON object couldn't be parsed to a 
432         *                        supported JWK.
433         */
434        public static JWK parse(final JSONObject jsonObject)
435                throws ParseException {
436
437                KeyType kty = KeyType.parse(JSONObjectUtils.getString(jsonObject, "kty"));
438
439                if (kty == KeyType.EC) {
440                        
441                        return ECKey.parse(jsonObject);
442
443                } else if (kty == KeyType.RSA) {
444                        
445                        return RSAKey.parse(jsonObject);
446
447                } else if (kty == KeyType.OCT) {
448                        
449                        return OctetSequenceKey.parse(jsonObject);
450
451                } else {
452
453                        throw new ParseException("Unsupported key type \"kty\" parameter: " + kty, 0);
454                }
455        }
456}