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