001package com.nimbusds.jose.jwk;
002
003
004import java.net.URI;
005import java.text.ParseException;
006import java.util.ArrayList;
007import java.util.Collections;
008import java.util.List;
009import java.util.Set;
010
011import com.nimbusds.jose.JOSEException;
012import net.minidev.json.JSONAware;
013import net.minidev.json.JSONObject;
014
015import com.nimbusds.jose.Algorithm;
016import com.nimbusds.jose.util.Base64;
017import com.nimbusds.jose.util.Base64URL;
018import com.nimbusds.jose.util.JSONObjectUtils;
019
020
021/**
022 * The base abstract class for JSON Web Keys (JWKs). It serialises to a JSON
023 * object.
024 *
025 * <p>The following JSON object members are common to all JWK types:
026 *
027 * <ul>
028 *     <li>{@link #getKeyType kty} (required)
029 *     <li>{@link #getKeyUse use} (optional)
030 *     <li>{@link #getKeyOperations key_ops} (optional)
031 *     <li>{@link #getKeyID kid} (optional)
032 * </ul>
033 *
034 * <p>Example JWK (of the Elliptic Curve type):
035 *
036 * <pre>
037 * {
038 *   "kty" : "EC",
039 *   "crv" : "P-256",
040 *   "x"   : "MKBCTNIcKUSDii11ySs3526iDZ8AiTo7Tu6KPAqv7D4",
041 *   "y"   : "4Etl6SRW2YiLUrN5vfvVHuhp7x8PxltmWWlbbM4IFyM",
042 *   "use" : "enc",
043 *   "kid" : "1"
044 * }
045 * </pre>
046 *
047 * @author Vladimir Dzhuvinov
048 * @author Justin Richer
049 * @version 2015-09-21
050 */
051public abstract class JWK implements JSONAware {
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         * Computes the SHA-256 thumbprint of this JWK. See RFC 7638 for more
256         * information.
257         *
258         * @return The SHA-256 thumbprint.
259         *
260         * @throws JOSEException If the SHA-256 hash algorithm is not
261         *                       supported.
262         */
263        public Base64URL computeThumbprint()
264                throws JOSEException {
265
266                return computeThumbprint("SHA-256");
267        }
268
269
270        /**
271         * Computes the thumbprint of this JWK using the specified hash
272         * algorithm. See RFC 7638 for more information.
273         *
274         * @param hashAlg The hash algorithm. Must not be {@code null}.
275         *
276         * @return The SHA-256 thumbprint.
277         *
278         * @throws JOSEException If the hash algorithm is not supported.
279         */
280        public abstract Base64URL computeThumbprint(final String hashAlg)
281                throws JOSEException;
282
283
284        /**
285         * Returns {@code true} if this JWK contains private or sensitive
286         * (non-public) parameters.
287         *
288         * @return {@code true} if this JWK contains private parameters, else
289         *         {@code false}.
290         */
291        public abstract boolean isPrivate();
292
293
294        /**
295         * Creates a copy of this JWK with all private or sensitive parameters 
296         * removed.
297         * 
298         * @return The newly created public JWK, or {@code null} if none can be
299         *         created.
300         */
301        public abstract JWK toPublicJWK();
302
303
304        /**
305         * Returns a JSON object representation of this JWK. This method is 
306         * intended to be called from extending classes.
307         *
308         * <p>Example:
309         *
310         * <pre>
311         * {
312         *   "kty" : "RSA",
313         *   "use" : "sig",
314         *   "kid" : "fd28e025-8d24-48bc-a51a-e2ffc8bc274b"
315         * }
316         * </pre>
317         *
318         * @return The JSON object representation.
319         */
320        public JSONObject toJSONObject() {
321
322                JSONObject o = new JSONObject();
323
324                o.put("kty", kty.getValue());
325
326                if (use != null) {
327                        o.put("use", use.identifier());
328                }
329
330                if (ops != null) {
331
332                        List<String> sl = new ArrayList<>(ops.size());
333
334                        for (KeyOperation op: ops) {
335                                sl.add(op.identifier());
336                        }
337
338                        o.put("key_ops", sl);
339                }
340
341                if (alg != null) {
342                        o.put("alg", alg.getName());
343                }
344
345                if (kid != null) {
346                        o.put("kid", kid);
347                }
348
349                if (x5u != null) {
350                        o.put("x5u", x5u.toString());
351                }
352
353                if (x5t != null) {
354                        o.put("x5t", x5t.toString());
355                }
356
357                if (x5c != null) {
358                        o.put("x5c", x5c);
359                }
360
361                return o;
362        }
363
364
365        /**
366         * Returns the JSON object string representation of this JWK.
367         *
368         * @return The JSON object string representation.
369         */
370        @Override
371        public String toJSONString() {
372
373                return toJSONObject().toString();
374        }
375
376
377        /**
378         * @see #toJSONString
379         */
380        @Override
381        public String toString() {
382
383                return toJSONObject().toString();
384        }
385
386
387        /**
388         * Parses a JWK from the specified JSON object string representation. 
389         * The JWK must be an {@link ECKey}, an {@link RSAKey}, or a 
390         * {@link OctetSequenceKey}.
391         *
392         * @param s The JSON object string to parse. Must not be {@code null}.
393         *
394         * @return The JWK.
395         *
396         * @throws ParseException If the string couldn't be parsed to a
397         *                        supported JWK.
398         */
399        public static JWK parse(final String s)
400                throws ParseException {
401
402                return parse(JSONObjectUtils.parseJSONObject(s));
403        }
404
405
406        /**
407         * Parses a JWK from the specified JSON object representation. The JWK 
408         * must be an {@link ECKey}, an {@link RSAKey}, or a 
409         * {@link OctetSequenceKey}.
410         *
411         * @param jsonObject The JSON object to parse. Must not be 
412         *                   {@code null}.
413         *
414         * @return The JWK.
415         *
416         * @throws ParseException If the JSON object couldn't be parsed to a 
417         *                        supported JWK.
418         */
419        public static JWK parse(final JSONObject jsonObject)
420                throws ParseException {
421
422                KeyType kty = KeyType.parse(JSONObjectUtils.getString(jsonObject, "kty"));
423
424                if (kty == KeyType.EC) {
425                        
426                        return ECKey.parse(jsonObject);
427
428                } else if (kty == KeyType.RSA) {
429                        
430                        return RSAKey.parse(jsonObject);
431
432                } else if (kty == KeyType.OCT) {
433                        
434                        return OctetSequenceKey.parse(jsonObject);
435
436                } else {
437
438                        throw new ParseException("Unsupported key type \"kty\" parameter: " + kty, 0);
439                }
440        }
441}