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