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.StandardCharset;
025
026import javax.crypto.SecretKey;
027import javax.crypto.spec.SecretKeySpec;
028import java.util.Collections;
029import java.util.LinkedHashSet;
030import java.util.Set;
031
032
033/**
034 * The base abstract class for Message Authentication Code (MAC) signers and
035 * verifiers of {@link com.nimbusds.jose.JWSObject JWS objects}.
036 *
037 * <p>Supports the following algorithms:
038 *
039 * <ul>
040 *     <li>{@link com.nimbusds.jose.JWSAlgorithm#HS256}
041 *     <li>{@link com.nimbusds.jose.JWSAlgorithm#HS384}
042 *     <li>{@link com.nimbusds.jose.JWSAlgorithm#HS512}
043 * </ul>
044 * 
045 * @author Vladimir Dzhuvinov
046 * @author Ulrich Winter
047 * @version 2019-09-14
048 */
049public abstract class MACProvider extends BaseJWSProvider {
050
051
052        /**
053         * The supported JWS algorithms by the MAC provider class.
054         */
055        public static final Set<JWSAlgorithm> SUPPORTED_ALGORITHMS;
056
057
058        static {
059                Set<JWSAlgorithm> algs = new LinkedHashSet<>();
060                algs.add(JWSAlgorithm.HS256);
061                algs.add(JWSAlgorithm.HS384);
062                algs.add(JWSAlgorithm.HS512);
063                SUPPORTED_ALGORITHMS = Collections.unmodifiableSet(algs);
064        }
065
066
067        /**
068         * Gets the matching Java Cryptography Architecture (JCA) algorithm 
069         * name for the specified HMAC-based JSON Web Algorithm (JWA).
070         *
071         * @param alg The JSON Web Algorithm (JWA). Must be supported and not
072         *            {@code null}.
073         *
074         * @return The matching JCA algorithm name.
075         *
076         * @throws JOSEException If the algorithm is not supported.
077         */
078        protected static String getJCAAlgorithmName(final JWSAlgorithm alg)
079                throws JOSEException {
080
081                if (alg.equals(JWSAlgorithm.HS256)) {
082                        return "HMACSHA256";
083                } else if (alg.equals(JWSAlgorithm.HS384)) {
084                        return "HMACSHA384";
085                } else if (alg.equals(JWSAlgorithm.HS512)) {
086                        return "HMACSHA512";
087                } else {
088                        throw new JOSEException(AlgorithmSupportMessage.unsupportedJWSAlgorithm(
089                                alg,
090                                SUPPORTED_ALGORITHMS));
091                }
092        }
093
094
095        /**
096         * The secret, {@code null} if specified as {@link SecretKey}.
097         */
098        private final byte[] secret;
099
100
101        /**
102         * The secret key, {@code null} if specified as byte array.
103         */
104        private final SecretKey secretKey;
105
106
107        /**
108         * Creates a new Message Authentication (MAC) provider.
109         *
110         * @param secret        The secret. Must be at least 256 bits long and
111         *                      not {@code null}.
112         * @param supportedAlgs The supported HMAC algorithms. Must not be
113         *                      {@code null}.
114         *
115         * @throws KeyLengthException If the secret length is shorter than the
116         *                            minimum 256-bit requirement.
117         */
118        protected MACProvider(final byte[] secret,
119                              final Set<JWSAlgorithm> supportedAlgs)
120                throws KeyLengthException {
121
122                super(supportedAlgs);
123
124                if (secret.length < 256 / 8) {
125                        throw new KeyLengthException("The secret length must be at least 256 bits");
126                }
127
128                this.secret = secret;
129                this.secretKey = null;
130        }
131
132
133        /**
134         * Creates a new Message Authentication (MAC) provider.
135         *
136         * @param secretKey     The secret key. Must be at least 256 bits long
137         *                      and not {@code null}.
138         * @param supportedAlgs The supported HMAC algorithms. Must not be
139         *                      {@code null}.
140         *
141         * @throws KeyLengthException If the secret length is shorter than the
142         *                            minimum 256-bit requirement.
143         */
144        protected MACProvider(final SecretKey secretKey,
145                              final Set<JWSAlgorithm> supportedAlgs)
146                throws KeyLengthException {
147
148                super(supportedAlgs);
149
150                // An HSM based key will not expose its material and return null
151                if (secretKey.getEncoded() != null && secretKey.getEncoded().length < 256 / 8) {
152                        throw new KeyLengthException("The secret length must be at least 256 bits");
153                }
154
155                this.secretKey = secretKey;
156                this.secret = null;
157        }
158
159
160        /**
161         * Gets the secret key.
162         *
163         * @return The secret key.
164         */
165        public SecretKey getSecretKey() {
166                if(this.secretKey != null) {
167                        return secretKey;
168                } else if (secret != null){
169                        return new SecretKeySpec(secret, "MAC");
170                } else {
171                        throw new IllegalStateException("Unexpected state");
172                }
173        }
174
175
176        /**
177         * Gets the secret bytes.
178         *
179         * @return The secret bytes, {@code null} if this provider was
180         *         constructed with a {@link SecretKey} that doesn't expose the
181         *         key material.
182         */
183        public byte[] getSecret() {
184                if(this.secretKey != null) {
185                        return secretKey.getEncoded();
186                } else if (secret != null){
187                        return secret;
188                } else {
189                        throw new IllegalStateException("Unexpected state");
190                }
191        }
192
193
194        /**
195         * Gets the secret as a UTF-8 encoded string.
196         *
197         * @return The secret as a UTF-8 encoded string, {@code null} if this
198         *         provider was constructed with a {@link SecretKey} that
199         *         doesn't expose the key material.
200         */
201        public String getSecretString() {
202
203                byte[] secret = getSecret();
204
205                if (secret == null) {
206                        return null;
207                }
208
209                return new String(secret, StandardCharset.UTF_8);
210        }
211}