Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow using GenericSecret for HmacSHA* #935

Merged
merged 2 commits into from Apr 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Expand Up @@ -135,12 +135,12 @@ private void assertAlgorithmName(SecretKey key, boolean signing) {
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() + ".");
Expand Down
16 changes: 10 additions & 6 deletions impl/src/main/java/io/jsonwebtoken/impl/security/KeysBridge.java
Expand Up @@ -34,8 +34,9 @@
@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() {
Expand Down Expand Up @@ -95,10 +96,13 @@ public static byte[] findEncoded(Key key) {
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) {
lhazlewood marked this conversation as resolved.
Show resolved Hide resolved
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);
}

/**
Expand Down
Expand Up @@ -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
Expand Down Expand Up @@ -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)
}
}
Expand Up @@ -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 {

Expand Down Expand Up @@ -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)
}
}