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