001package com.nimbusds.jose.jwk;
002
003
004import java.net.URI;
005import java.util.LinkedHashMap;
006import java.util.List;
007import java.text.ParseException;
008import java.util.Set;
009
010import javax.crypto.SecretKey;
011import javax.crypto.spec.SecretKeySpec;
012
013import net.jcip.annotations.Immutable;
014
015import net.minidev.json.JSONObject;
016
017import com.nimbusds.jose.Algorithm;
018import com.nimbusds.jose.JOSEException;
019import com.nimbusds.jose.util.Base64;
020import com.nimbusds.jose.util.Base64URL;
021import com.nimbusds.jose.util.JSONObjectUtils;
022
023
024/**
025 * {@link KeyType#OCT Octet sequence} JSON Web Key (JWK), used to represent
026 * symmetric keys. This class is immutable.
027 *
028 * <p>Octet sequence JWKs should specify the algorithm intended to be used with
029 * the key, unless the application uses other means or convention to determine
030 * the algorithm used.
031 *
032 * <p>Example JSON object representation of an octet sequence JWK:
033 *
034 * <pre>
035 * {
036 *   "kty" : "oct",
037 *   "alg" : "A128KW",
038 *   "k"   : "GawgguFyGrWKav7AX4VKUg"
039 * }
040 * </pre>
041 * 
042 * @author Justin Richer
043 * @author Vladimir Dzhuvinov
044 * @version 2015-12-08
045 */
046@Immutable
047public final class OctetSequenceKey extends JWK implements SecretJWK {
048
049
050        private static final long serialVersionUID = 1L;
051
052
053        /**
054         * The key value.
055         */
056        private final Base64URL k;
057
058
059        /**
060         * Builder for constructing octet sequence JWKs.
061         *
062         * <p>Example usage:
063         *
064         * <pre>
065         * OctetSequenceKey key = new OctetSequenceKey.Builder(k).
066         *                        algorithm(JWSAlgorithm.HS512).
067         *                        keyID("123").
068         *                        build();
069         * </pre>
070         */
071        public static class Builder {
072
073
074                /**
075                 * The key value.
076                 */
077                private final Base64URL k;
078
079
080                /**
081                 * The public key use, optional.
082                 */
083                private KeyUse use;
084
085
086                /**
087                 * The key operations, optional.
088                 */
089                private Set<KeyOperation> ops;
090
091
092                /**
093                 * The intended JOSE algorithm for the key, optional.
094                 */
095                private Algorithm alg;
096
097
098                /**
099                 * The key ID, optional.
100                 */
101                private String kid;
102
103
104                /**
105                 * X.509 certificate URL, optional.
106                 */
107                private URI x5u;
108
109
110                /**
111                 * X.509 certificate thumbprint, optional.
112                 */
113                private Base64URL x5t;
114
115
116                /**
117                 * The X.509 certificate chain, optional.
118                 */
119                private List<Base64> x5c;
120
121
122                /**
123                 * Creates a new octet sequence JWK builder.
124                 *
125                 * @param k The key value. It is represented as the Base64URL 
126                 *          encoding of value's big endian representation. Must
127                 *          not be {@code null}.
128                 */
129                public Builder(final Base64URL k) {
130
131                        if (k == null) {
132                                throw new IllegalArgumentException("The key value must not be null");
133                        }
134
135                        this.k = k;
136                }
137
138
139                /**
140                 * Creates a new octet sequence JWK builder.
141                 *
142                 * @param key The key value. Must not be empty byte array or
143                 *            {@code null}.
144                 */
145                public Builder(final byte[] key) {
146
147                        this(Base64URL.encode(key));
148
149                        if (key.length == 0) {
150                                throw new IllegalArgumentException("The key must have a positive length");
151                        }
152                }
153
154
155                /**
156                 * Creates a new octet sequence JWK builder.
157                 *
158                 * @param secretKey The secret key to represent. Must not be
159                 *                  {@code null}.
160                 */
161                public Builder(final SecretKey secretKey) {
162
163                        this(secretKey.getEncoded());
164                }
165
166
167                /**
168                 * Sets the use ({@code use}) of the JWK.
169                 *
170                 * @param use The key use, {@code null} if not specified or if
171                 *            the key is intended for signing as well as
172                 *            encryption.
173                 *
174                 * @return This builder.
175                 */
176                public Builder keyUse(final KeyUse use) {
177
178                        this.use = use;
179                        return this;
180                }
181
182
183                /**
184                 * Sets the operations ({@code key_ops}) of the JWK (for a
185                 * non-public key).
186                 *
187                 * @param ops The key operations, {@code null} if not
188                 *            specified.
189                 *
190                 * @return This builder.
191                 */
192                public Builder keyOperations(final Set<KeyOperation> ops) {
193
194                        this.ops = ops;
195                        return this;
196                }
197
198
199                /**
200                 * Sets the intended JOSE algorithm ({@code alg}) for the JWK.
201                 *
202                 * @param alg The intended JOSE algorithm, {@code null} if not 
203                 *            specified.
204                 *
205                 * @return This builder.
206                 */
207                public Builder algorithm(final Algorithm alg) {
208
209                        this.alg = alg;
210                        return this;
211                }
212
213                /**
214                 * Sets the ID ({@code kid}) of the JWK. The key ID can be used 
215                 * to match a specific key. This can be used, for instance, to 
216                 * choose a key within a {@link JWKSet} during key rollover. 
217                 * The key ID may also correspond to a JWS/JWE {@code kid} 
218                 * header parameter value.
219                 *
220                 * @param kid The key ID, {@code null} if not specified.
221                 *
222                 * @return This builder.
223                 */
224                public Builder keyID(final String kid) {
225
226                        this.kid = kid;
227                        return this;
228                }
229
230
231                /**
232                 * Sets the ID ({@code kid}) of the JWK to its SHA-256 JWK
233                 * thumbprint (RFC 7638). The key ID can be used to match a
234                 * specific key. This can be used, for instance, to choose a
235                 * key within a {@link JWKSet} during key rollover. The key ID
236                 * may also correspond to a JWS/JWE {@code kid} header
237                 * parameter value.
238                 *
239                 * @return This builder.
240                 *
241                 * @throws JOSEException If the SHA-256 hash algorithm is not
242                 *                       supported.
243                 */
244                public Builder keyIDFromThumbprint()
245                        throws JOSEException {
246
247                        return keyIDFromThumbprint("SHA-256");
248                }
249
250
251                /**
252                 * Sets the ID ({@code kid}) of the JWK to its JWK thumbprint
253                 * (RFC 7638). The key ID can be used to match a specific key.
254                 * This can be used, for instance, to choose a key within a
255                 * {@link JWKSet} during key rollover. The key ID may also
256                 * correspond to a JWS/JWE {@code kid} header parameter value.
257                 *
258                 * @param hashAlg The hash algorithm for the JWK thumbprint
259                 *                computation. Must not be {@code null}.
260                 *
261                 * @return This builder.
262                 *
263                 * @throws JOSEException If the hash algorithm is not
264                 *                       supported.
265                 */
266                public Builder keyIDFromThumbprint(final String hashAlg)
267                        throws JOSEException {
268
269                        // Put mandatory params in sorted order
270                        LinkedHashMap<String,String> requiredParams = new LinkedHashMap<>();
271                        requiredParams.put("k", k.toString());
272                        requiredParams.put("kty", KeyType.OCT.getValue());
273                        this.kid = ThumbprintUtils.compute(hashAlg, requiredParams).toString();
274                        return this;
275                }
276
277
278                /**
279                 * Sets the X.509 certificate URL ({@code x5u}) of the JWK.
280                 *
281                 * @param x5u The X.509 certificate URL, {@code null} if not 
282                 *            specified.
283                 *
284                 * @return This builder.
285                 */
286                public Builder x509CertURL(final URI x5u) {
287
288                        this.x5u = x5u;
289                        return this;
290                }
291
292
293                /**
294                 * Sets the X.509 certificate thumbprint ({@code x5t}) of the
295                 * JWK.
296                 *
297                 * @param x5t The X.509 certificate thumbprint, {@code null} if 
298                 *            not specified.
299                 *
300                 * @return This builder.
301                 */
302                public Builder x509CertThumbprint(final Base64URL x5t) {
303
304                        this.x5t = x5t;
305                        return this;
306                }
307
308                /**
309                 * Sets the X.509 certificate chain ({@code x5c}) of the JWK.
310                 *
311                 * @param x5c The X.509 certificate chain as a unmodifiable 
312                 *            list, {@code null} if not specified.
313                 *
314                 * @return This builder.
315                 */
316                public Builder x509CertChain(final List<Base64> x5c) {
317
318                        this.x5c = x5c;
319                        return this;
320                }
321
322                /**
323                 * Builds a new octet sequence JWK.
324                 *
325                 * @return The octet sequence JWK.
326                 *
327                 * @throws IllegalStateException If the JWK parameters were
328                 *                               inconsistently specified.
329                 */
330                public OctetSequenceKey build() {
331
332                        try {
333                                return new OctetSequenceKey(k, use, ops, alg, kid, x5u, x5t, x5c);
334
335                        } catch (IllegalArgumentException e) {
336
337                                throw new IllegalStateException(e.getMessage(), e);
338                        }
339                }
340        }
341
342        
343        /**
344         * Creates a new octet sequence JSON Web Key (JWK) with the specified
345         * parameters.
346         *
347         * @param k   The key value. It is represented as the Base64URL 
348         *            encoding of the value's big endian representation. Must
349         *            not be {@code null}.
350         * @param use The key use, {@code null} if not specified or if the key
351         *            is intended for signing as well as encryption.
352         * @param ops The key operations, {@code null} if not specified.
353         * @param alg The intended JOSE algorithm for the key, {@code null} if
354         *            not specified.
355         * @param kid The key ID. {@code null} if not specified.
356         * @param x5u The X.509 certificate URL, {@code null} if not specified.
357         * @param x5t The X.509 certificate thumbprint, {@code null} if not
358         *            specified.
359         * @param x5c The X.509 certificate chain, {@code null} if not 
360         *            specified.
361         */
362        public OctetSequenceKey(final Base64URL k,
363                                final KeyUse use, final Set<KeyOperation> ops, final Algorithm alg, final String kid,
364                                final URI x5u, final Base64URL x5t, final List<Base64> x5c) {
365        
366                super(KeyType.OCT, use, ops, alg, kid, x5u, x5t, x5c);
367
368                if (k == null) {
369                        throw new IllegalArgumentException("The key value must not be null");
370                }
371
372                this.k = k;
373        }
374    
375
376        /**
377         * Returns the value of this octet sequence key. 
378         *
379         * @return The key value. It is represented as the Base64URL encoding
380         *         of the value's big endian representation.
381         */
382        public Base64URL getKeyValue() {
383
384                return k;
385        }
386        
387        
388        /**
389         * Returns a copy of this octet sequence key value as a byte array.
390         * 
391         * @return The key value as a byte array.
392         */
393        public byte[] toByteArray() {
394
395                return getKeyValue().decode();
396        }
397
398
399        /**
400         * Returns a secret key representation of this octet sequence key.
401         *
402         * @return The secret key representation, with an algorithm set to
403         *         {@code NONE}.
404         */
405        @Override
406        public SecretKey toSecretKey() {
407
408                return toSecretKey("NONE");
409        }
410
411
412        /**
413         * Returns a secret key representation of this octet sequence key with
414         * the specified Java Cryptography Architecture (JCA) algorithm.
415         *
416         * @param jcaAlg The JCA algorithm. Must not be {@code null}.
417         *
418         * @return The secret key representation.
419         */
420        public SecretKey toSecretKey(final String jcaAlg) {
421
422                return new SecretKeySpec(toByteArray(), jcaAlg);
423        }
424
425
426        @Override
427        public LinkedHashMap<String,?> getRequiredParams() {
428
429                // Put mandatory params in sorted order
430                LinkedHashMap<String,String> requiredParams = new LinkedHashMap<>();
431                requiredParams.put("k", k.toString());
432                requiredParams.put("kty", getKeyType().toString());
433                return requiredParams;
434        }
435
436
437        /**
438         * Octet sequence (symmetric) keys are never considered public, this 
439         * method always returns {@code true}.
440         *
441         * @return {@code true}
442         */
443        @Override
444        public boolean isPrivate() {
445
446                return true;
447        }
448
449
450        /**
451         * Octet sequence (symmetric) keys are never considered public, this 
452         * method always returns {@code null}.
453         *
454         * @return {@code null}
455         */
456        @Override
457        public OctetSequenceKey toPublicJWK() {
458
459                return null;
460        }
461        
462
463        @Override
464        public JSONObject toJSONObject() {
465
466                JSONObject o = super.toJSONObject();
467
468                // Append key value
469                o.put("k", k.toString());
470                
471                return o;
472        }
473
474
475        /**
476         * Parses an octet sequence JWK from the specified JSON object string 
477         * representation.
478         *
479         * @param s The JSON object string to parse. Must not be {@code null}.
480         *
481         * @return The octet sequence JWK.
482         *
483         * @throws ParseException If the string couldn't be parsed to an octet
484         *                        sequence JWK.
485         */
486        public static OctetSequenceKey parse(final String s)
487                throws ParseException {
488
489                return parse(JSONObjectUtils.parse(s));
490        }
491
492        
493        /**
494         * Parses an octet sequence JWK from the specified JSON object 
495         * representation.
496         *
497         * @param jsonObject The JSON object to parse. Must not be 
498         *                   {@code null}.
499         *
500         * @return The octet sequence JWK.
501         *
502         * @throws ParseException If the JSON object couldn't be parsed to an
503         *                        octet sequence JWK.
504         */
505        public static OctetSequenceKey parse(final JSONObject jsonObject) 
506                throws ParseException {
507
508                // Parse the mandatory parameters first
509                Base64URL k = new Base64URL(JSONObjectUtils.getString(jsonObject, "k"));
510
511                // Check key type
512                KeyType kty = JWKMetadata.parseKeyType(jsonObject);
513
514                if (kty != KeyType.OCT) {
515
516                        throw new ParseException("The key type \"kty\" must be oct", 0);
517                }
518
519                return new OctetSequenceKey(k,
520                        JWKMetadata.parseKeyUse(jsonObject),
521                        JWKMetadata.parseKeyOperations(jsonObject),
522                        JWKMetadata.parseAlgorithm(jsonObject),
523                        JWKMetadata.parseKeyID(jsonObject),
524                        JWKMetadata.parseX509CertURL(jsonObject),
525                        JWKMetadata.parseX509CertThumbprint(jsonObject),
526                        JWKMetadata.parseX509CertChain(jsonObject));
527        }
528}