BCFIPS approved mode: Some tests failing due the short secret for client-secret-jwt client authentication

Closes #16678
This commit is contained in:
mposolda 2023-01-27 15:30:39 +01:00 committed by Marek Posolda
parent d2ef774788
commit 7f017f540e
8 changed files with 56 additions and 37 deletions

View file

@ -17,16 +17,20 @@
package org.keycloak.jose;
import org.jboss.logging.Logger;
import org.junit.Assert;
import org.junit.ClassRule;
import org.junit.Test;
import org.keycloak.common.util.BouncyIntegration;
import org.keycloak.jose.jws.JWSBuilder;
import org.keycloak.jose.jws.JWSInput;
import org.keycloak.jose.jws.crypto.HMACProvider;
import org.keycloak.rule.CryptoInitRule;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.SecretKeySpec;
import java.util.UUID;
/**
@ -35,17 +39,29 @@ import java.util.UUID;
*/
public abstract class HmacTest {
private final Logger logger = Logger.getLogger(getClass().getName());
@ClassRule
public static CryptoInitRule cryptoInitRule = new CryptoInitRule();
@Test
public void testHmacSignatures() throws Exception {
SecretKey secret = new SecretKeySpec(UUID.randomUUID().toString().getBytes(), "HmacSHA256");
public void testHmacSignaturesWithRandomSecretKey() throws Exception {
SecretKey secretKey = new SecretKeySpec(UUID.randomUUID().toString().getBytes(), "HmacSHA256");
testHMACSignAndVerify(secretKey, "testHmacSignaturesWithRandomSecretKey");
}
@Test
public void testHmacSignaturesWithShortSecretKey() throws Exception {
SecretKey secretKey = new SecretKeySpec("secret".getBytes(), "HmacSHA256");
testHMACSignAndVerify(secretKey, "testHmacSignaturesWithShortSecretKey");
}
protected void testHMACSignAndVerify(SecretKey secretKey, String test) throws Exception {
String encoded = new JWSBuilder().content("12345678901234567890".getBytes())
.hmac256(secret);
System.out.println("length: " + encoded.length());
.hmac256(secretKey);
logger.infof("%s: Length of encoded content: %d, Length of secret key: %d", test, encoded.length(), secretKey.getEncoded().length);
JWSInput input = new JWSInput(encoded);
Assert.assertTrue(HMACProvider.verify(input, secret));
Assert.assertTrue(HMACProvider.verify(input, secretKey));
}
}

View file

@ -42,13 +42,9 @@ public class ElytronHmacTest extends HmacTest {
SecureRandom random = isWindows() ? SecureRandom.getInstance("Windows-PRNG") : SecureRandom.getInstance("NativePRNG");
random.setSeed(UUID.randomUUID().toString().getBytes());
keygen.init(random);
SecretKey secret = keygen.generateKey();
SecretKey secretKey = keygen.generateKey();
String encoded = new JWSBuilder().content("12345678901234567890".getBytes())
.hmac256(secret);
System.out.println("length: " + encoded.length());
JWSInput input = new JWSInput(encoded);
Assert.assertTrue(HMACProvider.verify(input, secret));
testHMACSignAndVerify(secretKey, "testHmacSignaturesUsingKeyGen");
}
private boolean isWindows(){
return System.getProperty("os.name").startsWith("Windows");

View file

@ -1,21 +1,19 @@
package org.keycloak.crypto.fips.test;
import java.util.UUID;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.SecretKeySpec;
import org.junit.Assert;
import org.bouncycastle.crypto.CryptoServicesRegistrar;
import org.junit.Assume;
import org.junit.Before;
import org.junit.Test;
import org.keycloak.common.util.BouncyIntegration;
import org.keycloak.common.util.Environment;
import org.keycloak.jose.HmacTest;
import org.keycloak.jose.jws.JWSBuilder;
import org.keycloak.jose.jws.JWSInput;
import org.keycloak.jose.jws.crypto.HMACProvider;
/**
@ -31,15 +29,16 @@ public class FIPS1402HmacTest extends HmacTest {
}
@Test
public void testHmacSignaturesFIPS() throws Exception {
//
public void testHmacSignaturesWithRandomSecretKeyCreatedByFactory() throws Exception {
SecretKeyFactory skFact = SecretKeyFactory.getInstance("HmacSHA256", BouncyIntegration.PROVIDER );
SecretKey secret = skFact.generateSecret(new SecretKeySpec(UUID.randomUUID().toString().getBytes(), "HmacSHA256"));
String encoded = new JWSBuilder().content("12345678901234567890".getBytes())
.hmac256(secret);
System.out.println("length: " + encoded.length());
JWSInput input = new JWSInput(encoded);
Assert.assertTrue(HMACProvider.verify(input, secret));
SecretKey secretKey = skFact.generateSecret(new SecretKeySpec(UUID.randomUUID().toString().getBytes(), "HmacSHA256"));
testHMACSignAndVerify(secretKey, "testHmacSignaturesWithRandomSecretKeyCreatedByFactory");
}
@Override
public void testHmacSignaturesWithShortSecretKey() throws Exception {
// With BCFIPS approved mode, secret key used for HmacSHA256 must be at least 112 bits long (14 characters). Short key won't work
Assume.assumeFalse(CryptoServicesRegistrar.isInApprovedOnlyMode());
super.testHmacSignaturesWithShortSecretKey();
}
}

View file

@ -89,7 +89,10 @@ which means even stricter security requirements on cryptography and security alg
```
--spi-password-hashing-pbkdf2-sha256-max-padding-length=14
```
- RSA keys of 1024 bits don't work (2048 is the minimum)
- RSA keys of 1024 bits don't work (2048 is the minimum). This applies for keys used by Keycloak realm itself (Realm keys from the `Keys` tab), but also client keys and IDP keys
- HMAC SHA-XXX keys must be at least 112 bits (or 14 characters long). For example if you use OIDC clients with the client
authentication `Signed Jwt with Client Secret` (aka `client-secret-jwt`), then your client secrets should be at least 14 characters long.
But anyway, it is recommended to use client secrets generated by Keycloak server, which always matches this requirement.
- Also `jks` and `pkcs12` keystores/trustores are not supported.
When starting server at startup, you can check that startup log contains `KC` provider contains KC provider with the note about `Approved Mode` like this:

View file

@ -1,6 +1,5 @@
package org.keycloak.testsuite.broker;
import static org.keycloak.testsuite.broker.BrokerTestConstants.CLIENT_SECRET;
import static org.keycloak.testsuite.broker.BrokerTestConstants.IDP_OIDC_ALIAS;
import static org.keycloak.testsuite.broker.BrokerTestConstants.IDP_OIDC_PROVIDER_ID;
import static org.keycloak.testsuite.broker.BrokerTestTools.createIdentityProvider;
@ -16,6 +15,9 @@ import org.keycloak.representations.idm.IdentityProviderRepresentation;
public class KcOidcBrokerClientSecretJwtTest extends AbstractBrokerTest {
// BCFIPS approved mode requires at least 112 bits (14 characters) long SecretKey for "client-secret-jwt" authentication
private static final String CLIENT_SECRET = "atleast-14chars-password";
@Override
protected BrokerConfiguration getBrokerConfiguration() {
return new KcOidcBrokerConfigurationWithJWTAuthentication();
@ -39,6 +41,7 @@ public class KcOidcBrokerClientSecretJwtTest extends AbstractBrokerTest {
IdentityProviderRepresentation idp = createIdentityProvider(IDP_OIDC_ALIAS, IDP_OIDC_PROVIDER_ID);
Map<String, String> config = idp.getConfig();
applyDefaultConfiguration(config, syncMode);
config.put("clientSecret", CLIENT_SECRET);
config.put("clientAuthMethod", OIDCLoginProtocol.CLIENT_SECRET_JWT);
return idp;
}

View file

@ -316,7 +316,7 @@ public class FAPI1Test extends AbstractClientPoliciesTest {
// Register client (default authenticator)
String clientUUID = createClientByAdmin("foo", (ClientRepresentation clientRep) -> {
clientRep.setClientAuthenticatorType(JWTClientSecretAuthenticator.PROVIDER_ID);
clientRep.setSecret("secret");
clientRep.setSecret("atleast-14chars-password");
});
ClientRepresentation client = getClientByAdmin(clientUUID);
Assert.assertFalse(client.isPublicClient());
@ -336,7 +336,7 @@ public class FAPI1Test extends AbstractClientPoliciesTest {
// Check PKCE with S256, redirectUri and nonce/state set. Login should be successful
successfulLoginAndLogout("foo", false, (String code) -> {
String signedJwt = getClientSecretSignedJWT("secret", Algorithm.HS256);
String signedJwt = getClientSecretSignedJWT("atleast-14chars-password", Algorithm.HS256);
return doAccessTokenRequestWithClientSignedJWT(code, signedJwt, codeVerifier, DefaultHttpClient::new);
});
}

View file

@ -92,6 +92,9 @@ public class ClientAuthSecretSignedJWTTest extends AbstractKeycloakTest {
private static final String PROFILE_NAME = "ClientSecretRotationProfile";
private static final String POLICY_NAME = "ClientSecretRotationPolicy";
private static final String OIDC = "openid-connect";
// BCFIPS approved mode requires at least 112 bits (14 characters) long SecretKey for "client-secret-jwt" authentication
private static final String CLIENT_SECRET = "atleast-14chars-password";
private static final ObjectMapper objectMapper = new ObjectMapper();
@Rule
@ -142,7 +145,7 @@ public class ClientAuthSecretSignedJWTTest extends AbstractKeycloakTest {
}
};
String algorithm = Algorithm.HS256;
jwtProvider.setClientSecret("password", algorithm);
jwtProvider.setClientSecret(CLIENT_SECRET, algorithm);
String jwt = jwtProvider.createSignedRequestToken(oauth.getClientId(), getRealmInfoUrl(), algorithm);
OAuthClient.AccessTokenResponse response = doAccessTokenRequest(code,
jwt);
@ -180,7 +183,6 @@ public class ClientAuthSecretSignedJWTTest extends AbstractKeycloakTest {
ClientRepresentation clientRep = null;
final String realmName = "test";
final String clientId = "test-app";
final String clientSecret = "password";
try {
clientResource = ApiUtil.findClientByClientId(adminClient.realm(realmName), clientId);
clientRep = clientResource.toRepresentation();
@ -188,11 +190,11 @@ public class ClientAuthSecretSignedJWTTest extends AbstractKeycloakTest {
clientResource.update(clientRep);
oauth.clientId(clientId);
oauth.doLogin("test-user@localhost", clientSecret);
oauth.doLogin("test-user@localhost", "password");
events.expectLogin().client(clientId).assertEvent();
String code = oauth.getCurrentQuery().get(OAuth2Constants.CODE);
OAuthClient.AccessTokenResponse response = doAccessTokenRequest(code, getClientSignedJWT(clientSecret, 20, Algorithm.HS256));
OAuthClient.AccessTokenResponse response = doAccessTokenRequest(code, getClientSignedJWT(CLIENT_SECRET, 20, Algorithm.HS256));
assertEquals(400, response.getStatusCode());
assertEquals("invalid_client", response.getError());
} catch (Exception e) {
@ -213,7 +215,7 @@ public class ClientAuthSecretSignedJWTTest extends AbstractKeycloakTest {
.assertEvent();
String code = oauth.getCurrentQuery().get(OAuth2Constants.CODE);
OAuthClient.AccessTokenResponse response = doAccessTokenRequest(code, getClientSignedJWT("password", 20, algorithm));
OAuthClient.AccessTokenResponse response = doAccessTokenRequest(code, getClientSignedJWT(CLIENT_SECRET, 20, algorithm));
assertEquals(200, response.getStatusCode());
oauth.verifyToken(response.getAccessToken());
@ -245,7 +247,7 @@ public class ClientAuthSecretSignedJWTTest extends AbstractKeycloakTest {
}
private void processAuthenticateWithAlgorithm(String algorithm, Integer secretLength) throws Exception{
String cidConfidential= createClientByAdmin("jwt-client","jwt-client","password",algorithm);
String cidConfidential= createClientByAdmin("jwt-client","jwt-client",CLIENT_SECRET,algorithm);
ClientResource clientResource = adminClient.realm(REALM_NAME).clients().get(cidConfidential);
configureDefaultProfileAndPolicy();
@ -292,7 +294,7 @@ public class ClientAuthSecretSignedJWTTest extends AbstractKeycloakTest {
.assertEvent();
String code = oauth.getCurrentQuery().get(OAuth2Constants.CODE);
String clientSignedJWT = getClientSignedJWT("password", 20);
String clientSignedJWT = getClientSignedJWT(CLIENT_SECRET, 20);
OAuthClient.AccessTokenResponse response = doAccessTokenRequest(code, clientSignedJWT);
assertEquals(200, response.getStatusCode());
@ -329,7 +331,7 @@ public class ClientAuthSecretSignedJWTTest extends AbstractKeycloakTest {
*/
@Test
public void authenticateWithInvalidRotatedClientSecretPolicyIsEnable() throws Exception {
String cidConfidential= createClientByAdmin("jwt-client","jwt-client","password",Algorithm.HS256);
String cidConfidential= createClientByAdmin("jwt-client","jwt-client",CLIENT_SECRET,Algorithm.HS256);
ClientResource clientResource = adminClient.realm(REALM_NAME).clients().get(cidConfidential);
configureDefaultProfileAndPolicy();
String firstSecret = clientResource.getSecret().getValue();

View file

@ -29,7 +29,7 @@
],
"adminUrl": "http://localhost:8180/auth/realms/master/app/admin",
"clientAuthenticatorType": "client-secret-jwt",
"secret": "password"
"secret": "atleast-14chars-password"
}
],
"roles" : {