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}