001package com.nimbusds.jose.crypto;
002
003
004import java.util.Collections;
005import java.util.LinkedHashSet;
006import java.util.Set;
007import javax.crypto.SecretKey;
008
009import com.nimbusds.jose.*;
010import com.nimbusds.jose.jwk.OctetSequenceKey;
011import com.nimbusds.jose.util.Base64URL;
012import com.nimbusds.jose.util.ByteUtils;
013import com.nimbusds.jose.util.StandardCharset;
014import net.jcip.annotations.ThreadSafe;
015
016
017
018/**
019 * Message Authentication Code (MAC) signer of 
020 * {@link com.nimbusds.jose.JWSObject JWS objects}. Expects a secret key.
021 *
022 * <p>See RFC 7518
023 * <a href="https://tools.ietf.org/html/rfc7518#section-3.2">section 3.2</a>
024 * for more information.
025 *
026 * <p>This class is thread-safe.
027 *
028 * <p>Supports the following algorithms:
029 *
030 * <ul>
031 *     <li>{@link com.nimbusds.jose.JWSAlgorithm#HS256}
032 *     <li>{@link com.nimbusds.jose.JWSAlgorithm#HS384}
033 *     <li>{@link com.nimbusds.jose.JWSAlgorithm#HS512}
034 * </ul>
035 * 
036 * @author Vladimir Dzhuvinov
037 * @version 2016-07-27
038 */
039@ThreadSafe
040public class MACSigner extends MACProvider implements JWSSigner {
041
042
043        /**
044         * Returns the minimal required secret length for the specified HMAC
045         * JWS algorithm.
046         *
047         * @param alg The HMAC JWS algorithm. Must be
048         *            {@link #SUPPORTED_ALGORITHMS supported} and not
049         *            {@code null}.
050         *
051         * @return The minimal required secret length, in bits.
052         *
053         * @throws JOSEException If the algorithm is not supported.
054         */
055        public static int getMinRequiredSecretLength(final JWSAlgorithm alg)
056                throws JOSEException {
057
058                if (JWSAlgorithm.HS256.equals(alg)) {
059                        return 256;
060                } else if (JWSAlgorithm.HS384.equals(alg)) {
061                        return 384;
062                } else if (JWSAlgorithm.HS512.equals(alg)) {
063                        return 512;
064                } else {
065                        throw new JOSEException(AlgorithmSupportMessage.unsupportedJWSAlgorithm(
066                                alg,
067                                SUPPORTED_ALGORITHMS));
068                }
069        }
070
071
072        /**
073         * Returns the compatible JWS HMAC algorithms for the specified secret
074         * length.
075         *
076         * @param secretLength The secret length in bits. Must not be negative.
077         *
078         * @return The compatible HMAC algorithms, empty set if the secret
079         *         length is too short for any algorithm.
080         */
081        public static Set<JWSAlgorithm> getCompatibleAlgorithms(final int secretLength) {
082
083                Set<JWSAlgorithm> hmacAlgs = new LinkedHashSet<>();
084
085                if (secretLength >= 256)
086                        hmacAlgs.add(JWSAlgorithm.HS256);
087
088                if (secretLength >= 384)
089                        hmacAlgs.add(JWSAlgorithm.HS384);
090
091                if (secretLength >= 512)
092                        hmacAlgs.add(JWSAlgorithm.HS512);
093
094                return Collections.unmodifiableSet(hmacAlgs);
095        }
096
097
098        /**
099         * Creates a new Message Authentication (MAC) signer.
100         *
101         * @param secret The secret. Must be at least 256 bits long and not
102         *               {@code null}.
103         *
104         * @throws KeyLengthException If the secret length is shorter than the
105         *                            minimum 256-bit requirement.
106         */
107        public MACSigner(final byte[] secret)
108                throws KeyLengthException {
109
110                super(secret, getCompatibleAlgorithms(ByteUtils.bitLength(secret.length)));
111        }
112
113
114        /**
115         * Creates a new Message Authentication (MAC) signer.
116         *
117         * @param secretString The secret as a UTF-8 encoded string. Must be at
118         *                     least 256 bits long and not {@code null}.
119         *
120         * @throws KeyLengthException If the secret length is shorter than the
121         *                            minimum 256-bit requirement.
122         */
123        public MACSigner(final String secretString)
124                throws KeyLengthException {
125
126                this(secretString.getBytes(StandardCharset.UTF_8));
127        }
128
129
130        /**
131         * Creates a new Message Authentication (MAC) signer.
132         *
133         * @param secretKey The secret key. Must be at least 256 bits long and
134         *                  not {@code null}.
135         *
136         * @throws KeyLengthException If the secret length is shorter than the
137         *                            minimum 256-bit requirement.
138         */
139        public MACSigner(final SecretKey secretKey)
140                throws KeyLengthException {
141
142                this(secretKey.getEncoded());
143        }
144
145
146        /**
147         * Creates a new Message Authentication (MAC) signer.
148         *
149         * @param jwk The secret as a JWK. Must be at least 256 bits long and
150         *            not {@code null}.
151         *
152         * @throws KeyLengthException If the secret length is shorter than the
153         *                            minimum 256-bit requirement.
154         */
155        public MACSigner(final OctetSequenceKey jwk)
156                throws KeyLengthException {
157
158                this(jwk.toByteArray());
159        }
160
161
162        @Override
163        public Base64URL sign(final JWSHeader header, final byte[] signingInput)
164                throws JOSEException {
165
166                final int minRequiredLength = getMinRequiredSecretLength(header.getAlgorithm());
167
168                if (getSecret().length < ByteUtils.byteLength(minRequiredLength)) {
169                        throw new KeyLengthException("The secret length for " + header.getAlgorithm() + " must be at least " + minRequiredLength + " bits");
170                }
171
172                String jcaAlg = getJCAAlgorithmName(header.getAlgorithm());
173                byte[] hmac = HMAC.compute(jcaAlg, getSecret(), signingInput, getJCAContext().getProvider());
174                return Base64URL.encode(hmac);
175        }
176}