Allow using GenericSecret for HmacSHA* (#935)

* Extended the pre-existing check for SUN PKCS11 generic secret to allow `SecretKey`s with `getAlgorithm()` names starting with `Generic` (e.g. `GenericSecret` and `Generic Secret`) as valid keys for `HmacSHA*` algorithm name checks in `DefaultMacAlgorithm`.  This matches at least with the Sun PKCS11 and AWS CloudHSM JCE providers, but likely others as well.
This commit is contained in:
Mikko Nylén 2024-04-22 22:49:06 +03:00 committed by GitHub
parent c673b76ef5
commit 23d9a33ff6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 90 additions and 10 deletions

View File

@ -135,12 +135,12 @@ final class DefaultMacAlgorithm extends AbstractSecureDigestAlgorithm<SecretKey,
throw new InvalidKeyException(msg);
}
// We can ignore PKCS11 key name assertions because HSM module key algorithm names don't always align with
// JCA standard algorithm names:
boolean pkcs11Key = KeysBridge.isSunPkcs11GenericSecret(key);
// We can ignore key name assertions for generic secrets, because HSM module key algorithm names
// don't always align with JCA standard algorithm names
boolean generic = KeysBridge.isGenericSecret(key);
//assert key's jca name is valid if it's a JWA standard algorithm:
if (!pkcs11Key && isJwaStandard() && !isJwaStandardJcaName(name)) {
if (!generic && isJwaStandard() && !isJwaStandardJcaName(name)) {
throw new InvalidKeyException("The " + keyType(signing) + " key's algorithm '" + name +
"' does not equal a valid HmacSHA* algorithm name or PKCS12 OID and cannot be used with " +
getId() + ".");

View File

@ -34,8 +34,9 @@ import java.security.interfaces.RSAKey;
@SuppressWarnings({"unused"}) // reflection bridge class for the io.jsonwebtoken.security.Keys implementation
public final class KeysBridge {
private static final String SUNPKCS11_GENERIC_SECRET_CLASSNAME = "sun.security.pkcs11.P11Key$P11SecretKey";
private static final String SUNPKCS11_GENERIC_SECRET_ALGNAME = "Generic Secret"; // https://github.com/openjdk/jdk/blob/4f90abaf17716493bad740dcef76d49f16d69379/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/P11KeyStore.java#L1292
// Some HSMs use generic secrets. This prefix matches the generic secret algorithm name
// used by SUN PKCS#11 provider, AWS CloudHSM JCE provider and possibly other HSMs
private static final String GENERIC_SECRET_ALG_PREFIX = "Generic";
// prevent instantiation
private KeysBridge() {
@ -95,10 +96,13 @@ public final class KeysBridge {
return encoded;
}
public static boolean isSunPkcs11GenericSecret(Key key) {
return key instanceof SecretKey &&
key.getClass().getName().equals(SUNPKCS11_GENERIC_SECRET_CLASSNAME) &&
SUNPKCS11_GENERIC_SECRET_ALGNAME.equals(key.getAlgorithm());
public static boolean isGenericSecret(Key key) {
if (!(key instanceof SecretKey)) {
return false;
}
String algName = Assert.hasText(key.getAlgorithm(), "Key algorithm cannot be null or empty.");
return algName.startsWith(GENERIC_SECRET_ALG_PREFIX);
}
/**

View File

@ -19,6 +19,7 @@ import io.jsonwebtoken.impl.io.Streams
import io.jsonwebtoken.security.*
import org.junit.Test
import javax.crypto.SecretKey
import javax.crypto.spec.SecretKeySpec
import java.nio.charset.StandardCharsets
import java.security.Key
@ -232,4 +233,29 @@ class DefaultMacAlgorithmTest {
assertSame mac, DefaultMacAlgorithm.findByKey(oidKey)
}
}
/**
* Asserts that generic secrets are accepted
*/
@Test
void testValidateKeyAcceptsGenericSecret() {
def genericSecret = new SecretKey() {
@Override
String getAlgorithm() {
return 'GenericSecret'
}
@Override
String getFormat() {
return "RAW"
}
@Override
byte[] getEncoded() {
return Randoms.secureRandom().nextBytes(new byte[32])
}
}
newAlg().validateKey(genericSecret, true)
}
}

View File

@ -17,9 +17,13 @@ package io.jsonwebtoken.impl.security
import org.junit.Test
import javax.crypto.SecretKey
import java.security.Key
import java.security.PrivateKey
import static org.junit.Assert.assertEquals
import static org.junit.Assert.assertFalse
import static org.junit.Assert.assertTrue
class KeysBridgeTest {
@ -56,4 +60,50 @@ class KeysBridgeTest {
void testToStringPassword() {
testFormattedOutput(new PasswordSpec("foo".toCharArray()))
}
@Test
void testIsGenericSecret() {
def secretKeyWithAlg = { alg ->
new SecretKey() {
@Override
String getAlgorithm() {
return alg
}
@Override
String getFormat() {
return 'RAW'
}
@Override
byte[] getEncoded() {
return new byte[0]
}
}
}
PrivateKey genericPrivateKey = new PrivateKey() {
@Override
String getAlgorithm() {
return "Generic"
}
@Override
String getFormat() {
return "RAW"
}
@Override
byte[] getEncoded() {
return new byte[0]
}
}
assertTrue KeysBridge.isGenericSecret(secretKeyWithAlg("GenericSecret"))
assertTrue KeysBridge.isGenericSecret(secretKeyWithAlg("Generic Secret"))
assertFalse KeysBridge.isGenericSecret(secretKeyWithAlg(" Generic"))
assertFalse KeysBridge.isGenericSecret(TestKeys.HS256)
assertFalse KeysBridge.isGenericSecret(TestKeys.A256GCM)
assertFalse KeysBridge.isGenericSecret(genericPrivateKey)
}
}