001/*
002 * nimbus-jose-jwt
003 *
004 * Copyright 2012-2023, Connect2id Ltd and contributors.
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.crypto.impl;
019
020
021import com.nimbusds.jose.JOSEException;
022import com.nimbusds.jose.JWSAlgorithm;
023import com.nimbusds.jose.KeyLengthException;
024import com.nimbusds.jose.util.ByteUtils;
025import com.nimbusds.jose.util.StandardCharset;
026
027import javax.crypto.SecretKey;
028import javax.crypto.spec.SecretKeySpec;
029import java.util.Collections;
030import java.util.LinkedHashSet;
031import java.util.Set;
032
033
034/**
035 * The base abstract class for Message Authentication Code (MAC) signers and
036 * verifiers of {@link com.nimbusds.jose.JWSObject JWS objects}.
037 *
038 * <p>Supports the following algorithms:
039 *
040 * <ul>
041 *     <li>{@link com.nimbusds.jose.JWSAlgorithm#HS256}
042 *     <li>{@link com.nimbusds.jose.JWSAlgorithm#HS384}
043 *     <li>{@link com.nimbusds.jose.JWSAlgorithm#HS512}
044 * </ul>
045 * 
046 * @author Vladimir Dzhuvinov
047 * @author Ulrich Winter
048 * @version 2024-10-28
049 */
050public abstract class MACProvider extends BaseJWSProvider {
051
052
053        /**
054         * The supported JWS algorithms by the MAC provider class.
055         */
056        public static final Set<JWSAlgorithm> SUPPORTED_ALGORITHMS;
057
058
059        static {
060                Set<JWSAlgorithm> algs = new LinkedHashSet<>();
061                algs.add(JWSAlgorithm.HS256);
062                algs.add(JWSAlgorithm.HS384);
063                algs.add(JWSAlgorithm.HS512);
064                SUPPORTED_ALGORITHMS = Collections.unmodifiableSet(algs);
065        }
066
067
068        /**
069         * Returns the compatible JWS HMAC algorithms for the specified secret
070         * length.
071         *
072         * @param secretLength The secret length in bits. Must not be negative.
073         *
074         * @return The compatible HMAC algorithms, empty set if the secret
075         *         length is too short for any algorithm.
076         */
077        public static Set<JWSAlgorithm> getCompatibleAlgorithms(final int secretLength) {
078
079                Set<JWSAlgorithm> hmacAlgs = new LinkedHashSet<>();
080
081                if (secretLength >= 256)
082                        hmacAlgs.add(JWSAlgorithm.HS256);
083
084                if (secretLength >= 384)
085                        hmacAlgs.add(JWSAlgorithm.HS384);
086
087                if (secretLength >= 512)
088                        hmacAlgs.add(JWSAlgorithm.HS512);
089
090                return Collections.unmodifiableSet(hmacAlgs);
091        }
092
093
094        /**
095         * Returns the minimal required secret length for the specified HMAC
096         * JWS algorithm.
097         *
098         * @param alg The HMAC JWS algorithm. Must be
099         *            {@link #SUPPORTED_ALGORITHMS supported} and not
100         *            {@code null}.
101         *
102         * @return The minimal required secret length, in bits.
103         *
104         * @throws JOSEException If the algorithm is not supported.
105         */
106        public static int getMinRequiredSecretLength(final JWSAlgorithm alg)
107                throws JOSEException {
108
109                if (JWSAlgorithm.HS256.equals(alg)) {
110                        return 256;
111                } else if (JWSAlgorithm.HS384.equals(alg)) {
112                        return 384;
113                } else if (JWSAlgorithm.HS512.equals(alg)) {
114                        return 512;
115                } else {
116                        throw new JOSEException(AlgorithmSupportMessage.unsupportedJWSAlgorithm(
117                                alg,
118                                SUPPORTED_ALGORITHMS));
119                }
120        }
121
122
123        /**
124         * Gets the matching Java Cryptography Architecture (JCA) algorithm 
125         * name for the specified HMAC-based JSON Web Algorithm (JWA).
126         *
127         * @param alg The JSON Web Algorithm (JWA). Must be supported and not
128         *            {@code null}.
129         *
130         * @return The matching JCA algorithm name.
131         *
132         * @throws JOSEException If the algorithm is not supported.
133         */
134        protected static String getJCAAlgorithmName(final JWSAlgorithm alg)
135                throws JOSEException {
136
137                if (alg.equals(JWSAlgorithm.HS256)) {
138                        return "HMACSHA256";
139                } else if (alg.equals(JWSAlgorithm.HS384)) {
140                        return "HMACSHA384";
141                } else if (alg.equals(JWSAlgorithm.HS512)) {
142                        return "HMACSHA512";
143                } else {
144                        throw new JOSEException(AlgorithmSupportMessage.unsupportedJWSAlgorithm(
145                                alg,
146                                SUPPORTED_ALGORITHMS));
147                }
148        }
149
150
151        /**
152         * The secret, {@code null} if specified as {@link SecretKey}.
153         */
154        private final byte[] secret;
155
156
157        /**
158         * The secret key, {@code null} if specified as byte array.
159         */
160        private final SecretKey secretKey;
161
162
163        /**
164         * Creates a new Message Authentication (MAC) provider.
165         *
166         * @param secret The secret. Must be at least 256 bits long and not
167         *               {@code null}.
168         *
169         * @throws KeyLengthException If the secret length is shorter than the
170         *                            minimum 256-bit requirement.
171         */
172        protected MACProvider(final byte[] secret)
173                throws KeyLengthException {
174
175                super(getCompatibleAlgorithms(ByteUtils.bitLength(secret.length)));
176
177                if (ByteUtils.bitLength(secret) < 256) {
178                        // First minimum check. The sign and verify methods
179                        // check whether the secret matches the concrete HMAC
180                        // algorithm secret length requirement
181                        throw new KeyLengthException("The secret length must be at least 256 bits");
182                }
183
184                this.secret = secret;
185                this.secretKey = null;
186        }
187
188
189        /**
190         * Creates a new Message Authentication (MAC) provider.
191         *
192         * @param secretKey The secret key. Must be at least 256 bits long and
193         *                  not {@code null}.S algorithms. Must not be
194         *                  {@code null}.
195         *
196         * @throws KeyLengthException If the secret length is shorter than the
197         *                            minimum 256-bit requirement.
198         */
199        protected MACProvider(final SecretKey secretKey)
200                throws KeyLengthException {
201
202                super(
203                        secretKey.getEncoded() != null ?
204                        // Get the compatible HSxxx algs for the secret key length
205                        getCompatibleAlgorithms(ByteUtils.bitLength(secretKey.getEncoded()))
206                        :
207                        // HSM-based SecretKey will not expose its key material, assume support for all algs
208                        SUPPORTED_ALGORITHMS
209                );
210
211                // An HSM based key will not expose its material and return null
212                if (secretKey.getEncoded() != null && ByteUtils.bitLength(secretKey.getEncoded()) < 256) {
213                        // First minimum check. The sign and verify methods
214                        // check whether the secret matches the concrete HMAC
215                        // algorithm secret length requirement
216                        throw new KeyLengthException("The secret length must be at least 256 bits");
217                }
218
219                this.secretKey = secretKey;
220                this.secret = null;
221        }
222
223
224        /**
225         * Gets the secret key.
226         *
227         * @return The secret key.
228         */
229        public SecretKey getSecretKey() {
230                if(this.secretKey != null) {
231                        return secretKey;
232                } else if (secret != null){
233                        return new SecretKeySpec(secret, "MAC");
234                } else {
235                        throw new IllegalStateException("Unexpected state");
236                }
237        }
238
239
240        /**
241         * Gets the secret bytes.
242         *
243         * @return The secret bytes, {@code null} if this provider was
244         *         constructed with a {@link SecretKey} that doesn't expose the
245         *         key material.
246         */
247        public byte[] getSecret() {
248                if(this.secretKey != null) {
249                        return secretKey.getEncoded();
250                } else if (secret != null){
251                        return secret;
252                } else {
253                        throw new IllegalStateException("Unexpected state");
254                }
255        }
256
257
258        /**
259         * Gets the secret as a UTF-8 encoded string.
260         *
261         * @return The secret as a UTF-8 encoded string, {@code null} if this
262         *         provider was constructed with a {@link SecretKey} that
263         *         doesn't expose the key material.
264         */
265        public String getSecretString() {
266
267                byte[] secret = getSecret();
268
269                if (secret == null) {
270                        return null;
271                }
272
273                return new String(secret, StandardCharset.UTF_8);
274        }
275
276
277        /**
278         * Ensures the secret length satisfies the minimum required for the
279         * specified HMAC JWS algorithm.
280         *
281         * @param alg The HMAC JWS algorithm. Must be
282         *            {@link #SUPPORTED_ALGORITHMS supported} and not
283         *            {@code null}.
284         *
285         * @throws JOSEException      If the algorithm is not supported.
286         * @throws KeyLengthException If the secret length is shorter than the
287         *                            minimum required.
288         */
289        protected void ensureSecretLengthSatisfiesAlgorithm(final JWSAlgorithm alg)
290                throws JOSEException {
291
292                if (getSecret() == null) {
293                        // Secret not available (HSM)
294                        return;
295                }
296
297                final int minRequiredBitLength = getMinRequiredSecretLength(alg);
298
299                if (ByteUtils.bitLength(getSecret()) < minRequiredBitLength) {
300                        throw new KeyLengthException("The secret length for " + alg + " must be at least " + minRequiredBitLength + " bits");
301                }
302        }
303}