001/* 002 * nimbus-jose-jwt 003 * 004 * Copyright 2012-2023, Connect2id Ltd. 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; 019 020 021import com.nimbusds.jose.*; 022import com.nimbusds.jose.crypto.impl.AlgorithmSupportMessage; 023import com.nimbusds.jose.crypto.impl.HMAC; 024import com.nimbusds.jose.crypto.impl.MACProvider; 025import com.nimbusds.jose.jwk.OctetSequenceKey; 026import com.nimbusds.jose.util.Base64URL; 027import com.nimbusds.jose.util.ByteUtils; 028import com.nimbusds.jose.util.StandardCharset; 029import net.jcip.annotations.ThreadSafe; 030 031import javax.crypto.SecretKey; 032import java.util.Collections; 033import java.util.LinkedHashSet; 034import java.util.Set; 035 036 037 038/** 039 * Message Authentication Code (MAC) signer of 040 * {@link com.nimbusds.jose.JWSObject JWS objects}. Expects a secret key. 041 * 042 * <p>See RFC 7518 043 * <a href="https://tools.ietf.org/html/rfc7518#section-3.2">section 3.2</a> 044 * for more information. 045 * 046 * <p>This class is thread-safe. 047 * 048 * <p>Supports the following algorithms: 049 * 050 * <ul> 051 * <li>{@link com.nimbusds.jose.JWSAlgorithm#HS256} 052 * <li>{@link com.nimbusds.jose.JWSAlgorithm#HS384} 053 * <li>{@link com.nimbusds.jose.JWSAlgorithm#HS512} 054 * </ul> 055 * 056 * <p>Tested with the AWS CloudHSM JCE provider. 057 * 058 * @author Vladimir Dzhuvinov 059 * @author Ulrich Winter 060 * @version 2023-09-14 061 */ 062@ThreadSafe 063public class MACSigner extends MACProvider implements JWSSigner { 064 065 066 /** 067 * Returns the minimal required secret length for the specified HMAC 068 * JWS algorithm. 069 * 070 * @param alg The HMAC JWS algorithm. Must be 071 * {@link #SUPPORTED_ALGORITHMS supported} and not 072 * {@code null}. 073 * 074 * @return The minimal required secret length, in bits. 075 * 076 * @throws JOSEException If the algorithm is not supported. 077 */ 078 public static int getMinRequiredSecretLength(final JWSAlgorithm alg) 079 throws JOSEException { 080 081 if (JWSAlgorithm.HS256.equals(alg)) { 082 return 256; 083 } else if (JWSAlgorithm.HS384.equals(alg)) { 084 return 384; 085 } else if (JWSAlgorithm.HS512.equals(alg)) { 086 return 512; 087 } else { 088 throw new JOSEException(AlgorithmSupportMessage.unsupportedJWSAlgorithm( 089 alg, 090 SUPPORTED_ALGORITHMS)); 091 } 092 } 093 094 095 /** 096 * Returns the compatible JWS HMAC algorithms for the specified secret 097 * length. 098 * 099 * @param secretLength The secret length in bits. Must not be negative. 100 * 101 * @return The compatible HMAC algorithms, empty set if the secret 102 * length is too short for any algorithm. 103 */ 104 public static Set<JWSAlgorithm> getCompatibleAlgorithms(final int secretLength) { 105 106 Set<JWSAlgorithm> hmacAlgs = new LinkedHashSet<>(); 107 108 if (secretLength >= 256) 109 hmacAlgs.add(JWSAlgorithm.HS256); 110 111 if (secretLength >= 384) 112 hmacAlgs.add(JWSAlgorithm.HS384); 113 114 if (secretLength >= 512) 115 hmacAlgs.add(JWSAlgorithm.HS512); 116 117 return Collections.unmodifiableSet(hmacAlgs); 118 } 119 120 121 /** 122 * Creates a new Message Authentication (MAC) signer. 123 * 124 * @param secret The secret. Must be at least 256 bits long and not 125 * {@code null}. 126 * 127 * @throws KeyLengthException If the secret length is shorter than the 128 * minimum 256-bit requirement. 129 */ 130 public MACSigner(final byte[] secret) 131 throws KeyLengthException { 132 133 super(secret, getCompatibleAlgorithms(ByteUtils.bitLength(secret.length))); 134 } 135 136 137 /** 138 * Creates a new Message Authentication (MAC) signer. 139 * 140 * @param secretString The secret as a UTF-8 encoded string. Must be at 141 * least 256 bits long and not {@code null}. 142 * 143 * @throws KeyLengthException If the secret length is shorter than the 144 * minimum 256-bit requirement. 145 */ 146 public MACSigner(final String secretString) 147 throws KeyLengthException { 148 149 this(secretString.getBytes(StandardCharset.UTF_8)); 150 } 151 152 153 /** 154 * Creates a new Message Authentication (MAC) signer. 155 * 156 * @param secretKey The secret key. Must be at least 256 bits long and 157 * not {@code null}. 158 * 159 * @throws KeyLengthException If the secret length is shorter than the 160 * minimum 256-bit requirement. 161 */ 162 public MACSigner(final SecretKey secretKey) 163 throws KeyLengthException { 164 165 super( 166 secretKey, 167 secretKey.getEncoded() != null ? 168 // Get the compatible HSxxx algs for the secret key length 169 getCompatibleAlgorithms(ByteUtils.bitLength(secretKey.getEncoded())) 170 : 171 // HSM-based SecretKey will not expose its key material, assume support for all algs 172 SUPPORTED_ALGORITHMS 173 ); 174 } 175 176 177 /** 178 * Creates a new Message Authentication (MAC) signer. 179 * 180 * @param jwk The secret as a JWK. Must be at least 256 bits long and 181 * not {@code null}. 182 * 183 * @throws KeyLengthException If the secret length is shorter than the 184 * minimum 256-bit requirement. 185 */ 186 public MACSigner(final OctetSequenceKey jwk) 187 throws KeyLengthException { 188 189 this(jwk.toByteArray()); 190 } 191 192 193 @Override 194 public Base64URL sign(final JWSHeader header, final byte[] signingInput) 195 throws JOSEException { 196 197 if (getSecret() != null) { 198 199 final int minRequiredLength = getMinRequiredSecretLength(header.getAlgorithm()); 200 201 if (getSecret().length < ByteUtils.byteLength(minRequiredLength)) { 202 throw new KeyLengthException("The secret length for " + header.getAlgorithm() + " must be at least " + minRequiredLength + " bits"); 203 } 204 } 205 206 String jcaAlg = getJCAAlgorithmName(header.getAlgorithm()); 207 byte[] hmac = HMAC.compute(jcaAlg, getSecretKey(), signingInput, getJCAContext().getProvider()); 208 return Base64URL.encode(hmac); 209 } 210}