mirror of https://github.com/jwtk/jjwt.git
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:
parent
c673b76ef5
commit
23d9a33ff6
|
@ -135,12 +135,12 @@ final class DefaultMacAlgorithm extends AbstractSecureDigestAlgorithm<SecretKey,
|
||||||
throw new InvalidKeyException(msg);
|
throw new InvalidKeyException(msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
// We can ignore PKCS11 key name assertions because HSM module key algorithm names don't always align with
|
// We can ignore key name assertions for generic secrets, because HSM module key algorithm names
|
||||||
// JCA standard algorithm names:
|
// don't always align with JCA standard algorithm names
|
||||||
boolean pkcs11Key = KeysBridge.isSunPkcs11GenericSecret(key);
|
boolean generic = KeysBridge.isGenericSecret(key);
|
||||||
|
|
||||||
//assert key's jca name is valid if it's a JWA standard algorithm:
|
//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 +
|
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 " +
|
"' does not equal a valid HmacSHA* algorithm name or PKCS12 OID and cannot be used with " +
|
||||||
getId() + ".");
|
getId() + ".");
|
||||||
|
|
|
@ -34,8 +34,9 @@ import java.security.interfaces.RSAKey;
|
||||||
@SuppressWarnings({"unused"}) // reflection bridge class for the io.jsonwebtoken.security.Keys implementation
|
@SuppressWarnings({"unused"}) // reflection bridge class for the io.jsonwebtoken.security.Keys implementation
|
||||||
public final class KeysBridge {
|
public final class KeysBridge {
|
||||||
|
|
||||||
private static final String SUNPKCS11_GENERIC_SECRET_CLASSNAME = "sun.security.pkcs11.P11Key$P11SecretKey";
|
// Some HSMs use generic secrets. This prefix matches the generic secret algorithm name
|
||||||
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
|
// 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
|
// prevent instantiation
|
||||||
private KeysBridge() {
|
private KeysBridge() {
|
||||||
|
@ -95,10 +96,13 @@ public final class KeysBridge {
|
||||||
return encoded;
|
return encoded;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean isSunPkcs11GenericSecret(Key key) {
|
public static boolean isGenericSecret(Key key) {
|
||||||
return key instanceof SecretKey &&
|
if (!(key instanceof SecretKey)) {
|
||||||
key.getClass().getName().equals(SUNPKCS11_GENERIC_SECRET_CLASSNAME) &&
|
return false;
|
||||||
SUNPKCS11_GENERIC_SECRET_ALGNAME.equals(key.getAlgorithm());
|
}
|
||||||
|
|
||||||
|
String algName = Assert.hasText(key.getAlgorithm(), "Key algorithm cannot be null or empty.");
|
||||||
|
return algName.startsWith(GENERIC_SECRET_ALG_PREFIX);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -19,6 +19,7 @@ import io.jsonwebtoken.impl.io.Streams
|
||||||
import io.jsonwebtoken.security.*
|
import io.jsonwebtoken.security.*
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
|
||||||
|
import javax.crypto.SecretKey
|
||||||
import javax.crypto.spec.SecretKeySpec
|
import javax.crypto.spec.SecretKeySpec
|
||||||
import java.nio.charset.StandardCharsets
|
import java.nio.charset.StandardCharsets
|
||||||
import java.security.Key
|
import java.security.Key
|
||||||
|
@ -232,4 +233,29 @@ class DefaultMacAlgorithmTest {
|
||||||
assertSame mac, DefaultMacAlgorithm.findByKey(oidKey)
|
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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,9 +17,13 @@ package io.jsonwebtoken.impl.security
|
||||||
|
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
|
||||||
|
import javax.crypto.SecretKey
|
||||||
import java.security.Key
|
import java.security.Key
|
||||||
|
import java.security.PrivateKey
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals
|
import static org.junit.Assert.assertEquals
|
||||||
|
import static org.junit.Assert.assertFalse
|
||||||
|
import static org.junit.Assert.assertTrue
|
||||||
|
|
||||||
class KeysBridgeTest {
|
class KeysBridgeTest {
|
||||||
|
|
||||||
|
@ -56,4 +60,50 @@ class KeysBridgeTest {
|
||||||
void testToStringPassword() {
|
void testToStringPassword() {
|
||||||
testFormattedOutput(new PasswordSpec("foo".toCharArray()))
|
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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue