001package com.nimbusds.jose.crypto;
002
003
004import java.nio.charset.Charset;
005import java.util.Collections;
006import java.util.LinkedHashSet;
007import java.util.Set;
008
009import javax.crypto.SecretKey;
010
011import net.jcip.annotations.ThreadSafe;
012
013import com.nimbusds.jose.*;
014import com.nimbusds.jose.jwk.OctetSequenceKey;
015import com.nimbusds.jose.util.Base64URL;
016import com.nimbusds.jose.util.ByteUtils;
017
018
019
020/**
021 * Message Authentication Code (MAC) signer of 
022 * {@link com.nimbusds.jose.JWSObject JWS objects}. This class is thread-safe.
023 *
024 * <p>Supports the following algorithms:
025 *
026 * <ul>
027 *     <li>{@link com.nimbusds.jose.JWSAlgorithm#HS256}
028 *     <li>{@link com.nimbusds.jose.JWSAlgorithm#HS384}
029 *     <li>{@link com.nimbusds.jose.JWSAlgorithm#HS512}
030 * </ul>
031 * 
032 * @author Vladimir Dzhuvinov
033 * @version 2015-06-29
034 */
035@ThreadSafe
036public class MACSigner extends MACProvider implements JWSSigner {
037
038
039        /**
040         * Returns the minimal required secret length for the specified HMAC
041         * JWS algorithm.
042         *
043         * @param alg The HMAC JWS algorithm. Must be
044         *            {@link #SUPPORTED_ALGORITHMS supported} and not
045         *            {@code null}.
046         *
047         * @return The minimal required secret length, in bits.
048         *
049         * @throws JOSEException If the algorithm is not supported.
050         */
051        public static int getMinRequiredSecretLength(final JWSAlgorithm alg)
052                throws JOSEException {
053
054                if (JWSAlgorithm.HS256.equals(alg)) {
055                        return 256;
056                } else if (JWSAlgorithm.HS384.equals(alg)) {
057                        return 384;
058                } else if (JWSAlgorithm.HS512.equals(alg)) {
059                        return 512;
060                } else {
061                        throw new JOSEException(AlgorithmSupportMessage.unsupportedJWSAlgorithm(
062                                alg,
063                                SUPPORTED_ALGORITHMS));
064                }
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         * Creates a new Message Authentication (MAC) signer.
096         *
097         * @param secret The secret. Must be at least 256 bits long and not
098         *               {@code null}.
099         *
100         * @throws KeyLengthException If the secret length is shorter than the
101         *                            minimum 256-bit requirement.
102         */
103        public MACSigner(final byte[] secret)
104                throws KeyLengthException {
105
106                super(secret, getCompatibleAlgorithms(ByteUtils.bitLength(secret.length)));
107        }
108
109
110        /**
111         * Creates a new Message Authentication (MAC) signer.
112         *
113         * @param secretString The secret as a UTF-8 encoded string. Must be at
114         *                     least 256 bits long and not {@code null}.
115         *
116         * @throws KeyLengthException If the secret length is shorter than the
117         *                            minimum 256-bit requirement.
118         */
119        public MACSigner(final String secretString)
120                throws KeyLengthException {
121
122                this(secretString.getBytes(Charset.forName("UTF-8")));
123        }
124
125
126        /**
127         * Creates a new Message Authentication (MAC) signer.
128         *
129         * @param secretKey The secret key. Must be at least 256 bits long and
130         *                  not {@code null}.
131         *
132         * @throws KeyLengthException If the secret length is shorter than the
133         *                            minimum 256-bit requirement.
134         */
135        public MACSigner(final SecretKey secretKey)
136                throws KeyLengthException {
137
138                this(secretKey.getEncoded());
139        }
140
141
142        /**
143         * Creates a new Message Authentication (MAC) signer.
144         *
145         * @param jwk The secret as a JWK. Must be at least 256 bits long and
146         *            not {@code null}.
147         *
148         * @throws KeyLengthException If the secret length is shorter than the
149         *                            minimum 256-bit requirement.
150         */
151        public MACSigner(final OctetSequenceKey jwk)
152                throws KeyLengthException {
153
154                this(jwk.toByteArray());
155        }
156
157
158        @Override
159        public Base64URL sign(final JWSHeader header, final byte[] signingInput)
160                throws JOSEException {
161
162                final int minRequiredLength = getMinRequiredSecretLength(header.getAlgorithm());
163
164                if (getSecret().length < ByteUtils.byteLength(minRequiredLength)) {
165                        throw new KeyLengthException("The secret length for " + header.getAlgorithm() + " must be at least " + minRequiredLength + " bits");
166                }
167
168                String jcaAlg = getJCAAlgorithmName(header.getAlgorithm());
169                byte[] hmac = HMAC.compute(jcaAlg, getSecret(), signingInput, getJCAContext().getProvider());
170                return Base64URL.encode(hmac);
171        }
172}