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