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