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