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