From d9c030e474238d9d602c8b7607f9aa1c6f1a557b Mon Sep 17 00:00:00 2001 From: Les Hazlewood <121180+lhazlewood@users.noreply.github.com> Date: Sat, 27 Jan 2024 19:25:07 -0800 Subject: [PATCH 1/2] - Ensured Secret JWK 'k' byte arrays for HMAC-SHA algorithms can be larger than the identified HS* algorithm. This is allowed per https://datatracker.ietf.org/doc/html/rfc7518#section-3.2: "A key of the same size as the hash output ... _or larger_ MUST be used with this algorithm" - Ensured that, when using the JwkBuilder, Secret JWK 'alg' values would automatically be set to 'HS256', 'HS384', or 'HS512' if the specified Java SecretKey algorithm name equals a JCA standard name (HmacSHA256, HmacSHA384, etc) or JCA standard HMAC-SHA OID. Fixes #901. --- .../impl/security/AesAlgorithm.java | 16 ++- .../impl/security/SecretJwkFactory.java | 98 +++++++++++----- .../security/AbstractJwkBuilderTest.groovy | 4 +- .../impl/security/JwkSerializationTest.groovy | 2 +- .../impl/security/JwksTest.groovy | 4 +- .../impl/security/SecretJwkFactoryTest.groovy | 111 +++++++++++++++--- .../impl/security/TestKeys.groovy | 7 ++ 7 files changed, 189 insertions(+), 53 deletions(-) diff --git a/impl/src/main/java/io/jsonwebtoken/impl/security/AesAlgorithm.java b/impl/src/main/java/io/jsonwebtoken/impl/security/AesAlgorithm.java index 0747f8fc7..a7d68117a 100644 --- a/impl/src/main/java/io/jsonwebtoken/impl/security/AesAlgorithm.java +++ b/impl/src/main/java/io/jsonwebtoken/impl/security/AesAlgorithm.java @@ -30,6 +30,7 @@ import javax.crypto.SecretKey; import javax.crypto.spec.GCMParameterSpec; import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.SecretKeySpec; import java.io.InputStream; import java.io.OutputStream; import java.security.SecureRandom; @@ -54,9 +55,22 @@ abstract class AesAlgorithm extends CryptoAlgorithm implements KeyBuilderSupplie protected final int tagBitLength; protected final boolean gcm; + static void assertKeyBitLength(int keyBitLength) { + if (keyBitLength == 128 || keyBitLength == 192 || keyBitLength == 256) return; // valid + String msg = "Invalid AES key length: " + Bytes.bitsMsg(keyBitLength) + ". AES only supports " + + "128, 192, or 256 bit keys."; + throw new IllegalArgumentException(msg); + } + + static SecretKey keyFor(byte[] bytes) { + int bitlen = (int) Bytes.bitLength(bytes); + assertKeyBitLength(bitlen); + return new SecretKeySpec(bytes, KEY_ALG_NAME); + } + AesAlgorithm(String id, final String jcaTransformation, int keyBitLength) { super(id, jcaTransformation); - Assert.isTrue(keyBitLength == 128 || keyBitLength == 192 || keyBitLength == 256, "Invalid AES key length: it must equal 128, 192, or 256."); + assertKeyBitLength(keyBitLength); this.keyBitLength = keyBitLength; this.gcm = jcaTransformation.startsWith("AES/GCM"); this.ivBitLength = jcaTransformation.equals("AESWrap") ? 0 : (this.gcm ? GCM_IV_SIZE : BLOCK_SIZE); diff --git a/impl/src/main/java/io/jsonwebtoken/impl/security/SecretJwkFactory.java b/impl/src/main/java/io/jsonwebtoken/impl/security/SecretJwkFactory.java index e099e32c3..36238cc4e 100644 --- a/impl/src/main/java/io/jsonwebtoken/impl/security/SecretJwkFactory.java +++ b/impl/src/main/java/io/jsonwebtoken/impl/security/SecretJwkFactory.java @@ -15,6 +15,7 @@ */ package io.jsonwebtoken.impl.security; +import io.jsonwebtoken.Identifiable; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.impl.lang.Bytes; import io.jsonwebtoken.impl.lang.ParameterReadable; @@ -22,11 +23,14 @@ import io.jsonwebtoken.io.Encoders; import io.jsonwebtoken.lang.Assert; import io.jsonwebtoken.lang.Strings; +import io.jsonwebtoken.security.AeadAlgorithm; import io.jsonwebtoken.security.InvalidKeyException; +import io.jsonwebtoken.security.Keys; import io.jsonwebtoken.security.MacAlgorithm; import io.jsonwebtoken.security.MalformedKeyException; import io.jsonwebtoken.security.SecretJwk; -import io.jsonwebtoken.security.SecureDigestAlgorithm; +import io.jsonwebtoken.security.SecretKeyAlgorithm; +import io.jsonwebtoken.security.WeakKeyException; import javax.crypto.SecretKey; import javax.crypto.spec.SecretKeySpec; @@ -44,61 +48,97 @@ class SecretJwkFactory extends AbstractFamilyJwkFactory { protected SecretJwk createJwkFromKey(JwkContext ctx) { SecretKey key = Assert.notNull(ctx.getKey(), "JwkContext key cannot be null."); String k; + byte[] encoded = null; try { - byte[] encoded = KeysBridge.getEncoded(key); + encoded = KeysBridge.getEncoded(key); k = Encoders.BASE64URL.encode(encoded); Assert.hasText(k, "k value cannot be null or empty."); } catch (Throwable t) { String msg = "Unable to encode SecretKey to JWK: " + t.getMessage(); throw new InvalidKeyException(msg, t); + } finally { + Bytes.clear(encoded); + } + + MacAlgorithm mac = DefaultMacAlgorithm.findByKey(key); + if (mac != null) { + ctx.put(AbstractJwk.ALG.getId(), mac.getId()); } ctx.put(DefaultSecretJwk.K.getId(), k); - return new DefaultSecretJwk(ctx); + return createJwkFromValues(ctx); } private static void assertKeyBitLength(byte[] bytes, MacAlgorithm alg) { long bitLen = Bytes.bitLength(bytes); long requiredBitLen = alg.getKeyBitLength(); - if (bitLen != requiredBitLen) { + if (bitLen < requiredBitLen) { // Implementors note: Don't print out any information about the `bytes` value itself - size, // content, etc., as it is considered secret material: String msg = "Secret JWK " + AbstractJwk.ALG + " value is '" + alg.getId() + - "', but the " + DefaultSecretJwk.K + " length does not equal the '" + alg.getId() + - "' length requirement of " + Bytes.bitsMsg(requiredBitLen) + - ". This discrepancy could be the result of an algorithm " + - "substitution attack or simply an erroneously constructed JWK. In either case, it is likely " + - "to result in unexpected or undesired security consequences."; - throw new MalformedKeyException(msg); + "', but the " + DefaultSecretJwk.K + " length is smaller than the " + alg.getId() + + " minimum length of " + Bytes.bitsMsg(requiredBitLen) + + " required by " + + "[JWA RFC 7518, Section 3.2](https://www.rfc-editor.org/rfc/rfc7518.html#section-3.2), " + + "2nd paragraph: 'A key of the same size as the hash output or larger MUST be used with this " + + "algorithm.'"; + throw new WeakKeyException(msg); } } + private static void assertSymmetric(Identifiable alg) { + if (alg instanceof MacAlgorithm || alg instanceof SecretKeyAlgorithm || alg instanceof AeadAlgorithm) + return; // valid + String msg = "Invalid Secret JWK " + AbstractJwk.ALG + " value '" + alg.getId() + "'. Secret JWKs " + + "may only be used with symmetric (secret) key algorithms."; + throw new MalformedKeyException(msg); + } + @Override protected SecretJwk createJwkFromValues(JwkContext ctx) { ParameterReadable reader = new RequiredParameterReader(ctx); - byte[] bytes = reader.get(DefaultSecretJwk.K); - String jcaName = null; - - String id = ctx.getAlgorithm(); - if (Strings.hasText(id)) { - SecureDigestAlgorithm alg = Jwts.SIG.get().get(id); - if (alg instanceof MacAlgorithm) { - jcaName = ((CryptoAlgorithm) alg).getJcaName(); // valid for all JJWT alg implementations - Assert.hasText(jcaName, "Algorithm jcaName cannot be null or empty."); - assertKeyBitLength(bytes, (MacAlgorithm) alg); - } - } - if (!Strings.hasText(jcaName)) { - if (ctx.isSigUse()) { + final byte[] bytes = reader.get(DefaultSecretJwk.K); + SecretKey key; + + String algId = ctx.getAlgorithm(); + if (!Strings.hasText(algId)) { // optional per https://www.rfc-editor.org/rfc/rfc7517.html#section-4.4 + + // Here we try to infer the best type of key to create based on siguse and/or key length. + // + // AES requires 128, 192, or 256 bits, so anything larger than 256 cannot be AES, so we'll need to assume + // HMAC. + // + // Also, 256 bits works for either HMAC or AES, so we just have to choose one as there is no other + // RFC-based criteria for determining. Historically, we've chosen AES due to the larger number of + // KeyAlgorithm and AeadAlgorithm use cases, so that's our default. + int kBitLen = (int) Bytes.bitLength(bytes); + + if (ctx.isSigUse() || kBitLen > Jwts.SIG.HS256.getKeyBitLength()) { // The only JWA SecretKey signature algorithms are HS256, HS384, HS512, so choose based on bit length: - jcaName = "HmacSHA" + Bytes.bitLength(bytes); - } else { // not an HS* algorithm, and all standard AeadAlgorithms use AES keys: - jcaName = AesAlgorithm.KEY_ALG_NAME; + key = Keys.hmacShaKeyFor(bytes); + } else { + key = AesAlgorithm.keyFor(bytes); } + ctx.setKey(key); + return new DefaultSecretJwk(ctx); + } + + //otherwise 'alg' was specified, ensure it's valid for secret key use: + Identifiable alg = Jwts.SIG.get().get(algId); + if (alg == null) alg = Jwts.KEY.get().get(algId); + if (alg == null) alg = Jwts.ENC.get().get(algId); + if (alg != null) assertSymmetric(alg); // if we found a standard alg, it must be a symmetric key algorithm + + if (alg instanceof MacAlgorithm) { + assertKeyBitLength(bytes, ((MacAlgorithm) alg)); + String jcaName = ((CryptoAlgorithm) alg).getJcaName(); + Assert.hasText(jcaName, "Algorithm jcaName cannot be null or empty."); + key = new SecretKeySpec(bytes, jcaName); + } else { + // all other remaining JWA-standard symmetric algs use AES: + key = AesAlgorithm.keyFor(bytes); } - Assert.stateNotNull(jcaName, "jcaName cannot be null (invariant)"); - SecretKey key = new SecretKeySpec(bytes, jcaName); ctx.setKey(key); return new DefaultSecretJwk(ctx); } diff --git a/impl/src/test/groovy/io/jsonwebtoken/impl/security/AbstractJwkBuilderTest.groovy b/impl/src/test/groovy/io/jsonwebtoken/impl/security/AbstractJwkBuilderTest.groovy index d54ab0888..ce73f8acf 100644 --- a/impl/src/test/groovy/io/jsonwebtoken/impl/security/AbstractJwkBuilderTest.groovy +++ b/impl/src/test/groovy/io/jsonwebtoken/impl/security/AbstractJwkBuilderTest.groovy @@ -29,10 +29,8 @@ import static org.junit.Assert.* class AbstractJwkBuilderTest { - private static final SecretKey SKEY = TestKeys.A256GCM - private static AbstractJwkBuilder builder() { - return (AbstractJwkBuilder) Jwks.builder().key(SKEY) + return (AbstractJwkBuilder) Jwks.builder().key(TestKeys.NA256) } @Test diff --git a/impl/src/test/groovy/io/jsonwebtoken/impl/security/JwkSerializationTest.groovy b/impl/src/test/groovy/io/jsonwebtoken/impl/security/JwkSerializationTest.groovy index 5ae4212c6..9f95fbc21 100644 --- a/impl/src/test/groovy/io/jsonwebtoken/impl/security/JwkSerializationTest.groovy +++ b/impl/src/test/groovy/io/jsonwebtoken/impl/security/JwkSerializationTest.groovy @@ -97,7 +97,7 @@ class JwkSerializationTest { static void testSecretJwk(Serializer ser, Deserializer des) { - def key = TestKeys.A128GCM + def key = TestKeys.NA256 def jwk = Jwks.builder().key(key).id('id').build() assertWrapped(jwk, ['k']) diff --git a/impl/src/test/groovy/io/jsonwebtoken/impl/security/JwksTest.groovy b/impl/src/test/groovy/io/jsonwebtoken/impl/security/JwksTest.groovy index 16c99f575..30d8d43ef 100644 --- a/impl/src/test/groovy/io/jsonwebtoken/impl/security/JwksTest.groovy +++ b/impl/src/test/groovy/io/jsonwebtoken/impl/security/JwksTest.groovy @@ -40,7 +40,7 @@ import static org.junit.Assert.* class JwksTest { - private static final SecretKey SKEY = Jwts.SIG.HS256.key().build() + private static final SecretKey SKEY = TestKeys.NA256 private static final java.security.KeyPair EC_PAIR = Jwts.SIG.ES256.keyPair().build() private static String srandom() { @@ -172,7 +172,7 @@ class JwksTest { @Test void testOperations() { def val = [Jwks.OP.SIGN, Jwks.OP.VERIFY] as Set - def jwk = Jwks.builder().key(TestKeys.A128GCM).operations().add(val).and().build() + def jwk = Jwks.builder().key(TestKeys.NA256).operations().add(val).and().build() assertEquals val, jwk.getOperations() } diff --git a/impl/src/test/groovy/io/jsonwebtoken/impl/security/SecretJwkFactoryTest.groovy b/impl/src/test/groovy/io/jsonwebtoken/impl/security/SecretJwkFactoryTest.groovy index 336b40c18..6af9c1384 100644 --- a/impl/src/test/groovy/io/jsonwebtoken/impl/security/SecretJwkFactoryTest.groovy +++ b/impl/src/test/groovy/io/jsonwebtoken/impl/security/SecretJwkFactoryTest.groovy @@ -15,9 +15,10 @@ */ package io.jsonwebtoken.impl.security -import io.jsonwebtoken.security.Jwks -import io.jsonwebtoken.security.MalformedKeyException -import io.jsonwebtoken.security.SecretJwk +import io.jsonwebtoken.Jwts +import io.jsonwebtoken.impl.lang.Bytes +import io.jsonwebtoken.io.Encoders +import io.jsonwebtoken.security.* import org.junit.Test import static org.junit.Assert.* @@ -30,10 +31,14 @@ import static org.junit.Assert.* */ class SecretJwkFactoryTest { + private static Set macAlgs() { + return Jwts.SIG.get().values().findAll({ it -> it instanceof MacAlgorithm }) as Collection + } + @Test // if a jwk does not have an 'alg' or 'use' param, we default to an AES key void testNoAlgNoSigJcaName() { - SecretJwk jwk = Jwks.builder().key(TestKeys.HS256).delete('alg').build() + SecretJwk jwk = Jwks.builder().key(TestKeys.NA256).build() SecretJwk result = Jwks.builder().add(jwk).build() as SecretJwk assertEquals 'AES', result.toKey().getAlgorithm() } @@ -47,7 +52,7 @@ class SecretJwkFactoryTest { @Test void testSignOpSetsKeyHmacSHA256() { - SecretJwk jwk = Jwks.builder().key(TestKeys.HS256).delete('alg').build() + SecretJwk jwk = Jwks.builder().key(TestKeys.NA256).build() SecretJwk result = Jwks.builder().add(jwk).operations().add(Jwks.OP.SIGN).and().build() as SecretJwk assertNull result.getAlgorithm() assertNull result.get('use') @@ -63,7 +68,7 @@ class SecretJwkFactoryTest { @Test void testSignOpSetsKeyHmacSHA384() { - SecretJwk jwk = Jwks.builder().key(TestKeys.HS384).delete('alg').build() + SecretJwk jwk = Jwks.builder().key(TestKeys.NA384).build() SecretJwk result = Jwks.builder().add(jwk).operations().add(Jwks.OP.SIGN).and().build() as SecretJwk assertNull result.getAlgorithm() assertNull result.get('use') @@ -79,7 +84,7 @@ class SecretJwkFactoryTest { @Test void testSignOpSetsKeyHmacSHA512() { - SecretJwk jwk = Jwks.builder().key(TestKeys.HS512).delete('alg').build() + SecretJwk jwk = Jwks.builder().key(TestKeys.NA512).build() SecretJwk result = Jwks.builder().add(jwk).operations().add(Jwks.OP.SIGN).and().build() as SecretJwk assertNull result.getAlgorithm() assertNull result.get('use') @@ -89,7 +94,7 @@ class SecretJwkFactoryTest { @Test // no 'alg' jwk property, but 'use' is 'sig', so forces jcaName to be HmacSHA256 void testNoAlgAndSigUseForHS256() { - SecretJwk jwk = Jwks.builder().key(TestKeys.HS256).delete('alg').build() + SecretJwk jwk = Jwks.builder().key(TestKeys.NA256).build() assertFalse jwk.containsKey('alg') assertFalse jwk.containsKey('use') SecretJwk result = Jwks.builder().add(jwk).add('use', 'sig').build() as SecretJwk @@ -99,7 +104,7 @@ class SecretJwkFactoryTest { @Test // no 'alg' jwk property, but 'use' is 'sig', so forces jcaName to be HmacSHA384 void testNoAlgAndSigUseForHS384() { - SecretJwk jwk = Jwks.builder().key(TestKeys.HS384).delete('alg').build() + SecretJwk jwk = Jwks.builder().key(TestKeys.NA384).build() assertFalse jwk.containsKey('alg') assertFalse jwk.containsKey('use') SecretJwk result = Jwks.builder().add(jwk).add('use', 'sig').build() as SecretJwk @@ -109,7 +114,7 @@ class SecretJwkFactoryTest { @Test // no 'alg' jwk property, but 'use' is 'sig', so forces jcaName to be HmacSHA512 void testNoAlgAndSigUseForHS512() { - SecretJwk jwk = Jwks.builder().key(TestKeys.HS512).delete('alg').build() + SecretJwk jwk = Jwks.builder().key(TestKeys.NA512).build() assertFalse jwk.containsKey('alg') assertFalse jwk.containsKey('use') SecretJwk result = Jwks.builder().add(jwk).add('use', 'sig').build() as SecretJwk @@ -119,20 +124,32 @@ class SecretJwkFactoryTest { @Test // no 'alg' jwk property, but 'use' is something other than 'sig', so jcaName should default to AES void testNoAlgAndNonSigUse() { - SecretJwk jwk = Jwks.builder().key(TestKeys.HS256).delete('alg').build() + SecretJwk jwk = Jwks.builder().key(TestKeys.NA256).build() assertFalse jwk.containsKey('alg') assertFalse jwk.containsKey('use') SecretJwk result = Jwks.builder().add(jwk).add('use', 'foo').build() as SecretJwk assertEquals 'AES', result.toKey().getAlgorithm() } + @Test + // 'oct' type, but 'alg' value is not a secret key algorithm (and therefore malformed) + void testMismatchedAlgorithm() { + try { + Jwks.builder().key(TestKeys.NA256).add('alg', Jwts.SIG.RS256.getId()).build() + fail() + } catch (MalformedKeyException expected) { + String msg = "Invalid Secret JWK ${AbstractJwk.ALG} value 'RS256'. Secret JWKs may only be used with " + + "symmetric (secret) key algorithms." + assertEquals msg, expected.message + } + } + /** * Test the case where a jwk `alg` value is present, but the key material doesn't match that algs key length * requirements. This would be a malformed key. */ @Test void testSizeMismatchedSecretJwk() { - //first get a valid HS256 JWK: SecretJwk validJwk = Jwks.builder().key(TestKeys.HS256).build() @@ -142,12 +159,72 @@ class SecretJwkFactoryTest { .add('alg', 'HS384') .build() fail() - } catch (MalformedKeyException expected) { - String msg = "Secret JWK 'alg' (Algorithm) value is 'HS384', but the 'k' (Key Value) length does " + - "not equal the 'HS384' length requirement of 384 bits (48 bytes). This discrepancy could " + - "be the result of an algorithm substitution attack or simply an erroneously constructed " + - "JWK. In either case, it is likely to result in unexpected or undesired security consequences." + } catch (WeakKeyException expected) { + String msg = "Secret JWK 'alg' (Algorithm) value is 'HS384', but the 'k' (Key Value) length is smaller " + + "than the HS384 minimum length of 384 bits (48 bytes) required by " + + "[JWA RFC 7518, Section 3.2](https://www.rfc-editor.org/rfc/rfc7518.html#section-3.2), 2nd " + + "paragraph: 'A key of the same size as the hash output or larger MUST be used with this " + + "algorithm.'" assertEquals msg, expected.getMessage() } } + + /** + * Test when a {@code k} size is smaller, equal to, and larger than the minimum required number of bits/bytes for + * a given HmacSHA* algorithm. The RFCs indicate smaller-than is not allowed, while equal-to and greater-than are + * allowed. + * + * This test asserts this allowed behavior per https://github.com/jwtk/jjwt/issues/905 + * @see JJWT Issue 905 + */ + @Test + void testAllowedKeyLengths() { + + def parser = Jwks.parser().build() + + for (MacAlgorithm alg : macAlgs()) { + + // 3 key length sizes for each alg to test: + // index 0: smaller than minimum required + // index 1: minimum required + // index 2: more than minimum required: + def sizes = [alg.keyBitLength - Byte.SIZE, alg.keyBitLength, alg.keyBitLength + Byte.SIZE] + + for (int i = 0; i < sizes.size(); i++) { + + def kBitLength = sizes.get(i) + def k = Bytes.random(Bytes.length(kBitLength)) + + def jwkJson = """ + { + "kid": "${UUID.randomUUID().toString()}", + "kty": "oct", + "alg": "${alg.getId()}", + "k": "${Encoders.BASE64URL.encode(k)}" + }""".toString() + + def jwk + try { + jwk = parser.parse(jwkJson) + } catch (WeakKeyException expected) { + assertEquals("Should only occur on index 0 with less-than-minimum key length", 0, i) + String msg = "Secret JWK 'alg' (Algorithm) value is '${alg.getId()}', but the 'k' (Key Value) " + + "length is smaller than the ${alg.getId()} minimum length of " + + "${Bytes.bitsMsg(alg.keyBitLength)} required by " + + "[JWA RFC 7518, Section 3.2](https://www.rfc-editor.org/rfc/rfc7518.html#section-3.2), " + + "2nd paragraph: 'A key of the same size as the hash output or larger MUST be used with " + + "this algorithm.'" + assertEquals msg, expected.getMessage() + continue // expected for index 0 (purposefully weak key), so let loop continue + } + + // otherwise not weak, sizes should reflect equal-to or greater-than alg bitlength sizes + assert jwk instanceof SecretJwk + assertEquals alg.getId(), jwk.getAlgorithm() + def bytes = jwk.toKey().getEncoded() + assertTrue Bytes.bitLength(bytes) >= alg.keyBitLength + assertEquals Bytes.length(kBitLength), jwk.toKey().getEncoded().length + } + } + } } diff --git a/impl/src/test/groovy/io/jsonwebtoken/impl/security/TestKeys.groovy b/impl/src/test/groovy/io/jsonwebtoken/impl/security/TestKeys.groovy index 50113765a..95226c9c9 100644 --- a/impl/src/test/groovy/io/jsonwebtoken/impl/security/TestKeys.groovy +++ b/impl/src/test/groovy/io/jsonwebtoken/impl/security/TestKeys.groovy @@ -21,6 +21,7 @@ import io.jsonwebtoken.lang.Collections import io.jsonwebtoken.security.Jwks import javax.crypto.SecretKey +import javax.crypto.spec.SecretKeySpec import java.security.KeyPair import java.security.PrivateKey import java.security.Provider @@ -42,6 +43,11 @@ class TestKeys { static SecretKey HS512 = Jwts.SIG.HS512.key().build() static Collection HS = Collections.setOf(HS256, HS384, HS512) + static SecretKey NA256 = new SecretKeySpec(HS256.encoded, "NONE") + static SecretKey NA384 = new SecretKeySpec(HS384.encoded, "NONE") + static SecretKey NA512 = new SecretKeySpec(HS512.encoded, "NONE") + static Collection NA = [NA256, NA384, NA512] + static SecretKey A128GCM, A192GCM, A256GCM, A128KW, A192KW, A256KW, A128GCMKW, A192GCMKW, A256GCMKW static Collection AGCM static { @@ -59,6 +65,7 @@ class TestKeys { static Collection SECRET = new LinkedHashSet<>() static { SECRET.addAll(HS) + SECRET.addAll(NA) SECRET.addAll(AGCM) SECRET.addAll(ACBC) } From 6ac5cc751bdefe876d33ad8a9c9c05ef1f24c15d Mon Sep 17 00:00:00 2001 From: Les Hazlewood <121180+lhazlewood@users.noreply.github.com> Date: Sat, 27 Jan 2024 19:40:57 -0800 Subject: [PATCH 2/2] - Ensured Secret JWK 'k' byte arrays for HMAC-SHA algorithms can be larger than the identified HS* algorithm. This is allowed per https://datatracker.ietf.org/doc/html/rfc7518#section-3.2: "A key of the same size as the hash output ... _or larger_ MUST be used with this algorithm" - Ensured that, when using the JwkBuilder, Secret JWK 'alg' values would automatically be set to 'HS256', 'HS384', or 'HS512' if the specified Java SecretKey algorithm name equals a JCA standard name (HmacSHA256, HmacSHA384, etc) or JCA standard HMAC-SHA OID. - Updated CHANGELOG.md accordingly. --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5934880eb..8230a11e9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -65,6 +65,8 @@ This release also: [6.2.1.3](https://datatracker.ietf.org/doc/html/rfc7518#section-6.2.1.3), and [6.2.2.1](https://datatracker.ietf.org/doc/html/rfc7518#section-6.2.2.1), respectively. [Issue 901](https://github.com/jwtk/jjwt/issues/901). +* Ensures that Secret JWKs for HMAC-SHA algorithms with `k` sizes larger than the algorithm minimum can + be parsed/used as expected. See [Issue #905](https://github.com/jwtk/jjwt/issues/905) * Fixes various typos in documentation and JavaDoc. Thanks to those contributing pull requests for these! ### 0.12.3