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}