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