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