001/* 002 * nimbus-jose-jwt 003 * 004 * Copyright 2012-2023, Connect2id Ltd and contributors. 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.impl; 019 020 021import com.nimbusds.jose.JOSEException; 022import com.nimbusds.jose.JWSAlgorithm; 023import com.nimbusds.jose.KeyLengthException; 024import com.nimbusds.jose.util.ByteUtils; 025import com.nimbusds.jose.util.StandardCharset; 026 027import javax.crypto.SecretKey; 028import javax.crypto.spec.SecretKeySpec; 029import java.util.Collections; 030import java.util.LinkedHashSet; 031import java.util.Set; 032 033 034/** 035 * The base abstract class for Message Authentication Code (MAC) signers and 036 * verifiers of {@link com.nimbusds.jose.JWSObject JWS objects}. 037 * 038 * <p>Supports the following algorithms: 039 * 040 * <ul> 041 * <li>{@link com.nimbusds.jose.JWSAlgorithm#HS256} 042 * <li>{@link com.nimbusds.jose.JWSAlgorithm#HS384} 043 * <li>{@link com.nimbusds.jose.JWSAlgorithm#HS512} 044 * </ul> 045 * 046 * @author Vladimir Dzhuvinov 047 * @author Ulrich Winter 048 * @version 2024-10-28 049 */ 050public abstract class MACProvider extends BaseJWSProvider { 051 052 053 /** 054 * The supported JWS algorithms by the MAC provider class. 055 */ 056 public static final Set<JWSAlgorithm> SUPPORTED_ALGORITHMS; 057 058 059 static { 060 Set<JWSAlgorithm> algs = new LinkedHashSet<>(); 061 algs.add(JWSAlgorithm.HS256); 062 algs.add(JWSAlgorithm.HS384); 063 algs.add(JWSAlgorithm.HS512); 064 SUPPORTED_ALGORITHMS = Collections.unmodifiableSet(algs); 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 * Returns the minimal required secret length for the specified HMAC 096 * JWS algorithm. 097 * 098 * @param alg The HMAC JWS algorithm. Must be 099 * {@link #SUPPORTED_ALGORITHMS supported} and not 100 * {@code null}. 101 * 102 * @return The minimal required secret length, in bits. 103 * 104 * @throws JOSEException If the algorithm is not supported. 105 */ 106 public static int getMinRequiredSecretLength(final JWSAlgorithm alg) 107 throws JOSEException { 108 109 if (JWSAlgorithm.HS256.equals(alg)) { 110 return 256; 111 } else if (JWSAlgorithm.HS384.equals(alg)) { 112 return 384; 113 } else if (JWSAlgorithm.HS512.equals(alg)) { 114 return 512; 115 } else { 116 throw new JOSEException(AlgorithmSupportMessage.unsupportedJWSAlgorithm( 117 alg, 118 SUPPORTED_ALGORITHMS)); 119 } 120 } 121 122 123 /** 124 * Gets the matching Java Cryptography Architecture (JCA) algorithm 125 * name for the specified HMAC-based JSON Web Algorithm (JWA). 126 * 127 * @param alg The JSON Web Algorithm (JWA). Must be supported and not 128 * {@code null}. 129 * 130 * @return The matching JCA algorithm name. 131 * 132 * @throws JOSEException If the algorithm is not supported. 133 */ 134 protected static String getJCAAlgorithmName(final JWSAlgorithm alg) 135 throws JOSEException { 136 137 if (alg.equals(JWSAlgorithm.HS256)) { 138 return "HMACSHA256"; 139 } else if (alg.equals(JWSAlgorithm.HS384)) { 140 return "HMACSHA384"; 141 } else if (alg.equals(JWSAlgorithm.HS512)) { 142 return "HMACSHA512"; 143 } else { 144 throw new JOSEException(AlgorithmSupportMessage.unsupportedJWSAlgorithm( 145 alg, 146 SUPPORTED_ALGORITHMS)); 147 } 148 } 149 150 151 /** 152 * The secret, {@code null} if specified as {@link SecretKey}. 153 */ 154 private final byte[] secret; 155 156 157 /** 158 * The secret key, {@code null} if specified as byte array. 159 */ 160 private final SecretKey secretKey; 161 162 163 /** 164 * Creates a new Message Authentication (MAC) provider. 165 * 166 * @param secret The secret. Must be at least 256 bits long and not 167 * {@code null}. 168 * 169 * @throws KeyLengthException If the secret length is shorter than the 170 * minimum 256-bit requirement. 171 */ 172 protected MACProvider(final byte[] secret) 173 throws KeyLengthException { 174 175 super(getCompatibleAlgorithms(ByteUtils.bitLength(secret.length))); 176 177 if (ByteUtils.bitLength(secret) < 256) { 178 // First minimum check. The sign and verify methods 179 // check whether the secret matches the concrete HMAC 180 // algorithm secret length requirement 181 throw new KeyLengthException("The secret length must be at least 256 bits"); 182 } 183 184 this.secret = secret; 185 this.secretKey = null; 186 } 187 188 189 /** 190 * Creates a new Message Authentication (MAC) provider. 191 * 192 * @param secretKey The secret key. Must be at least 256 bits long and 193 * not {@code null}.S algorithms. Must not be 194 * {@code null}. 195 * 196 * @throws KeyLengthException If the secret length is shorter than the 197 * minimum 256-bit requirement. 198 */ 199 protected MACProvider(final SecretKey secretKey) 200 throws KeyLengthException { 201 202 super( 203 secretKey.getEncoded() != null ? 204 // Get the compatible HSxxx algs for the secret key length 205 getCompatibleAlgorithms(ByteUtils.bitLength(secretKey.getEncoded())) 206 : 207 // HSM-based SecretKey will not expose its key material, assume support for all algs 208 SUPPORTED_ALGORITHMS 209 ); 210 211 // An HSM based key will not expose its material and return null 212 if (secretKey.getEncoded() != null && ByteUtils.bitLength(secretKey.getEncoded()) < 256) { 213 // First minimum check. The sign and verify methods 214 // check whether the secret matches the concrete HMAC 215 // algorithm secret length requirement 216 throw new KeyLengthException("The secret length must be at least 256 bits"); 217 } 218 219 this.secretKey = secretKey; 220 this.secret = null; 221 } 222 223 224 /** 225 * Gets the secret key. 226 * 227 * @return The secret key. 228 */ 229 public SecretKey getSecretKey() { 230 if(this.secretKey != null) { 231 return secretKey; 232 } else if (secret != null){ 233 return new SecretKeySpec(secret, "MAC"); 234 } else { 235 throw new IllegalStateException("Unexpected state"); 236 } 237 } 238 239 240 /** 241 * Gets the secret bytes. 242 * 243 * @return The secret bytes, {@code null} if this provider was 244 * constructed with a {@link SecretKey} that doesn't expose the 245 * key material. 246 */ 247 public byte[] getSecret() { 248 if(this.secretKey != null) { 249 return secretKey.getEncoded(); 250 } else if (secret != null){ 251 return secret; 252 } else { 253 throw new IllegalStateException("Unexpected state"); 254 } 255 } 256 257 258 /** 259 * Gets the secret as a UTF-8 encoded string. 260 * 261 * @return The secret as a UTF-8 encoded string, {@code null} if this 262 * provider was constructed with a {@link SecretKey} that 263 * doesn't expose the key material. 264 */ 265 public String getSecretString() { 266 267 byte[] secret = getSecret(); 268 269 if (secret == null) { 270 return null; 271 } 272 273 return new String(secret, StandardCharset.UTF_8); 274 } 275 276 277 /** 278 * Ensures the secret length satisfies the minimum required for the 279 * specified HMAC JWS algorithm. 280 * 281 * @param alg The HMAC JWS algorithm. Must be 282 * {@link #SUPPORTED_ALGORITHMS supported} and not 283 * {@code null}. 284 * 285 * @throws JOSEException If the algorithm is not supported. 286 * @throws KeyLengthException If the secret length is shorter than the 287 * minimum required. 288 */ 289 protected void ensureSecretLengthSatisfiesAlgorithm(final JWSAlgorithm alg) 290 throws JOSEException { 291 292 if (getSecret() == null) { 293 // Secret not available (HSM) 294 return; 295 } 296 297 final int minRequiredBitLength = getMinRequiredSecretLength(alg); 298 299 if (ByteUtils.bitLength(getSecret()) < minRequiredBitLength) { 300 throw new KeyLengthException("The secret length for " + alg + " must be at least " + minRequiredBitLength + " bits"); 301 } 302 } 303}