mirror of
https://github.com/keycloak/keycloak.git
synced 2026-01-10 15:32:05 -03:30
Add FAPI 2.0 + DPoP security profile as default profile of client policies
closes #35441 Signed-off-by: Takashi Norimatsu <takashi.norimatsu.ws@hitachi.com>
This commit is contained in:
parent
788e981917
commit
f00cd980c4
@ -74,6 +74,7 @@ test.describe("Realm settings client profiles tab tests", () => {
|
||||
);
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await searchClientProfile(page, editedProfileName);
|
||||
await clickTableRowItem(page, editedProfileName);
|
||||
});
|
||||
|
||||
|
||||
@ -377,6 +377,154 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "fapi-2-dpop-security-profile",
|
||||
"description": "Client profile, which enforce clients to conform 'FAPI 2.0 Security Profile' with DPoP specification.",
|
||||
"executors": [
|
||||
{
|
||||
"executor": "confidential-client",
|
||||
"configuration": {}
|
||||
},
|
||||
{
|
||||
"executor": "secure-client-authenticator",
|
||||
"configuration": {
|
||||
"allowed-client-authenticators": [
|
||||
"client-jwt",
|
||||
"client-x509"
|
||||
],
|
||||
"default-client-authenticator": "client-jwt"
|
||||
}
|
||||
},
|
||||
{
|
||||
"executor": "secure-client-uris",
|
||||
"configuration": {}
|
||||
},
|
||||
{
|
||||
"executor": "secure-signature-algorithm",
|
||||
"configuration": {
|
||||
"default-algorithm": "PS256"
|
||||
}
|
||||
},
|
||||
{
|
||||
"executor": "secure-signature-algorithm-signed-jwt",
|
||||
"configuration": {
|
||||
"require-client-assertion": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"executor": "consent-required",
|
||||
"configuration": {
|
||||
"auto-configure": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"executor": "full-scope-disabled",
|
||||
"configuration": {
|
||||
"auto-configure": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"executor": "reject-implicit-grant",
|
||||
"configuration": {
|
||||
"auto-configure": "true"
|
||||
}
|
||||
},
|
||||
{
|
||||
"executor": "pkce-enforcer",
|
||||
"configuration": {
|
||||
"auto-configure": "true"
|
||||
}
|
||||
},
|
||||
{
|
||||
"executor": "secure-par-content",
|
||||
"configuration": {}
|
||||
},
|
||||
{
|
||||
"executor": "dpop-bind-enforcer",
|
||||
"configuration": {
|
||||
"auto-configure": "true"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "fapi-2-dpop-message-signing",
|
||||
"description": "Client profile, which enforce clients to conform 'FAPI 2.0 Message Signing' with DPoP specification.",
|
||||
"executors": [
|
||||
{
|
||||
"executor": "confidential-client",
|
||||
"configuration": {}
|
||||
},
|
||||
{
|
||||
"executor": "secure-client-authenticator",
|
||||
"configuration": {
|
||||
"allowed-client-authenticators": [
|
||||
"client-jwt",
|
||||
"client-x509"
|
||||
],
|
||||
"default-client-authenticator": "client-jwt"
|
||||
}
|
||||
},
|
||||
{
|
||||
"executor": "secure-client-uris",
|
||||
"configuration": {}
|
||||
},
|
||||
{
|
||||
"executor": "secure-signature-algorithm",
|
||||
"configuration": {
|
||||
"default-algorithm": "PS256"
|
||||
}
|
||||
},
|
||||
{
|
||||
"executor": "secure-signature-algorithm-signed-jwt",
|
||||
"configuration": {
|
||||
"require-client-assertion": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"executor": "consent-required",
|
||||
"configuration": {
|
||||
"auto-configure": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"executor": "full-scope-disabled",
|
||||
"configuration": {
|
||||
"auto-configure": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"executor": "reject-implicit-grant",
|
||||
"configuration": {
|
||||
"auto-configure": "true"
|
||||
}
|
||||
},
|
||||
{
|
||||
"executor": "pkce-enforcer",
|
||||
"configuration": {
|
||||
"auto-configure": "true"
|
||||
}
|
||||
},
|
||||
{
|
||||
"executor" : "secure-par-content",
|
||||
"configuration" : {}
|
||||
},
|
||||
{
|
||||
"executor": "secure-request-object",
|
||||
"configuration": {
|
||||
"verify-nbf": true,
|
||||
"available-period": "3600",
|
||||
"encryption-required": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"executor": "dpop-bind-enforcer",
|
||||
"configuration": {
|
||||
"auto-configure": "true"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "saml-security-profile",
|
||||
"description": "Client profile that enforces SAML clients to be secure.",
|
||||
|
||||
@ -20,6 +20,13 @@ public class AccessTokenRequest extends AbstractHttpPostRequest<AccessTokenReque
|
||||
return client.getEndpoints().getToken();
|
||||
}
|
||||
|
||||
|
||||
public AccessTokenRequest signedJwt(String signedJwt) {
|
||||
parameter(OAuth2Constants.CLIENT_ASSERTION_TYPE, OAuth2Constants.CLIENT_ASSERTION_TYPE_JWT);
|
||||
parameter(OAuth2Constants.CLIENT_ASSERTION, signedJwt);
|
||||
return this;
|
||||
}
|
||||
|
||||
public AccessTokenRequest codeVerifier(PkceGenerator pkceGenerator) {
|
||||
if (pkceGenerator != null) {
|
||||
codeVerifier(pkceGenerator.getCodeVerifier());
|
||||
|
||||
@ -0,0 +1,224 @@
|
||||
/*
|
||||
* Copyright 2024 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
package org.keycloak.testsuite.client;
|
||||
|
||||
import org.keycloak.OAuth2Constants;
|
||||
import org.keycloak.OAuthErrorException;
|
||||
import org.keycloak.authentication.authenticators.client.ClientIdAndSecretAuthenticator;
|
||||
import org.keycloak.authentication.authenticators.client.JWTClientAuthenticator;
|
||||
import org.keycloak.authentication.authenticators.client.JWTClientSecretAuthenticator;
|
||||
import org.keycloak.authentication.authenticators.client.X509ClientAuthenticator;
|
||||
import org.keycloak.client.registration.ClientRegistrationException;
|
||||
import org.keycloak.crypto.Algorithm;
|
||||
import org.keycloak.protocol.oidc.OIDCAdvancedConfigWrapper;
|
||||
import org.keycloak.protocol.oidc.OIDCConfigAttributes;
|
||||
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
|
||||
import org.keycloak.representations.idm.ClientRepresentation;
|
||||
import org.keycloak.representations.oidc.OIDCClientRepresentation;
|
||||
import org.keycloak.services.clientpolicy.ClientPolicyException;
|
||||
import org.keycloak.services.clientpolicy.condition.AnyClientConditionFactory;
|
||||
import org.keycloak.testsuite.util.ClientPoliciesUtil;
|
||||
|
||||
import java.util.Collections;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.fail;
|
||||
import static org.keycloak.testsuite.util.ClientPoliciesUtil.createAnyClientConditionConfig;
|
||||
|
||||
/**
|
||||
* Test for the FAPI 2 specifications (still implementer's draft):
|
||||
* <a href="https://openid.bitbucket.io/fapi/fapi-2_0-security-profile.html">FAPI 2.0 Security Profile</a>
|
||||
* <a href="https://openid.bitbucket.io/fapi/fapi-2_0-message-signing.html">FAPI 2.0 Message Signing</a>
|
||||
* Mostly tests the global FAPI policies work as expected
|
||||
*
|
||||
* @author <a href="mailto:takashi.norimatsu.ws@hitachi.com">Takashi Norimatsu</a>
|
||||
*/
|
||||
public abstract class AbstractFAPI2Test extends AbstractFAPITest {
|
||||
|
||||
protected static final String clientId = "foo";
|
||||
|
||||
protected void testFAPI2ClientRegistration(String profile) throws Exception {
|
||||
setupPolicyFAPI2ForAllClient(profile);
|
||||
|
||||
// Register client with clientIdAndSecret - should fail
|
||||
try {
|
||||
createClientByAdmin("invalid", (ClientRepresentation clientRep) -> clientRep.setClientAuthenticatorType(ClientIdAndSecretAuthenticator.PROVIDER_ID));
|
||||
fail();
|
||||
} catch (ClientPolicyException e) {
|
||||
assertEquals(OAuthErrorException.INVALID_CLIENT_METADATA, e.getMessage());
|
||||
}
|
||||
|
||||
// Register client with signedJWT - should fail
|
||||
try {
|
||||
createClientByAdmin("invalid", (ClientRepresentation clientRep) -> clientRep.setClientAuthenticatorType(JWTClientSecretAuthenticator.PROVIDER_ID));
|
||||
fail();
|
||||
} catch (ClientPolicyException e) {
|
||||
assertEquals(OAuthErrorException.INVALID_CLIENT_METADATA, e.getMessage());
|
||||
}
|
||||
|
||||
// Register client with privateKeyJWT, but unsecured redirectUri - should fail
|
||||
try {
|
||||
createClientByAdmin("invalid", (ClientRepresentation clientRep) -> {
|
||||
clientRep.setClientAuthenticatorType(JWTClientAuthenticator.PROVIDER_ID);
|
||||
clientRep.setRedirectUris(Collections.singletonList("http://foo"));
|
||||
});
|
||||
fail();
|
||||
} catch (ClientPolicyException e) {
|
||||
assertEquals(OAuthErrorException.INVALID_CLIENT_METADATA, e.getMessage());
|
||||
}
|
||||
|
||||
// Try to register client with "client-jwt" - should pass
|
||||
String clientUUID = createClientByAdmin("client-jwt", (ClientRepresentation clientRep) -> clientRep.setClientAuthenticatorType(JWTClientAuthenticator.PROVIDER_ID));
|
||||
ClientRepresentation client = getClientByAdmin(clientUUID);
|
||||
assertEquals(JWTClientAuthenticator.PROVIDER_ID, client.getClientAuthenticatorType());
|
||||
|
||||
// Try to register client with "client-x509" - should pass
|
||||
clientUUID = createClientByAdmin("client-x509", (ClientRepresentation clientRep) -> clientRep.setClientAuthenticatorType(X509ClientAuthenticator.PROVIDER_ID));
|
||||
client = getClientByAdmin(clientUUID);
|
||||
assertEquals(X509ClientAuthenticator.PROVIDER_ID, client.getClientAuthenticatorType());
|
||||
|
||||
// Try to register client with default authenticator - should pass. Client authenticator should be "client-jwt"
|
||||
clientUUID = createClientByAdmin("client-jwt-2", (ClientRepresentation clientRep) -> {
|
||||
});
|
||||
client = getClientByAdmin(clientUUID);
|
||||
assertEquals(JWTClientAuthenticator.PROVIDER_ID, client.getClientAuthenticatorType());
|
||||
|
||||
// Check the Consent is enabled, Holder-of-key is enabled, fullScopeAllowed disabled and default signature algorithm.
|
||||
assertTrue(client.isConsentRequired());
|
||||
OIDCAdvancedConfigWrapper clientConfig = OIDCAdvancedConfigWrapper.fromClientRepresentation(client);
|
||||
assertEquals(Algorithm.PS256, clientConfig.getIdTokenSignedResponseAlg());
|
||||
assertEquals(Algorithm.PS256, clientConfig.getRequestObjectSignatureAlg());
|
||||
assertFalse(client.isFullScopeAllowed());
|
||||
switch (profile) {
|
||||
case FAPI2_SECURITY_PROFILE_NAME:
|
||||
case FAPI2_MESSAGE_SIGNING_PROFILE_NAME:
|
||||
assertTrue(clientConfig.isUseMtlsHokToken());
|
||||
assertFalse(clientConfig.isUseDPoP());
|
||||
break;
|
||||
case FAPI2_DPOP_SECURITY_PROFILE_NAME:
|
||||
case FAPI2_DPOP_MESSAGE_SIGNING_PROFILE_NAME:
|
||||
assertFalse(clientConfig.isUseMtlsHokToken());
|
||||
assertTrue(clientConfig.isUseDPoP());
|
||||
break;
|
||||
default:
|
||||
throw new IllegalStateException("Unexpected value: " + profile);
|
||||
}
|
||||
}
|
||||
|
||||
protected void testFAPI2OIDCClientRegistration(String profile) throws Exception {
|
||||
setupPolicyFAPI2ForAllClient(profile);
|
||||
|
||||
// Try to register client with clientIdAndSecret - should fail
|
||||
try {
|
||||
createClientDynamically(generateSuffixedName(clientId), (OIDCClientRepresentation clientRep) -> clientRep.setTokenEndpointAuthMethod(OIDCLoginProtocol.CLIENT_SECRET_BASIC));
|
||||
fail();
|
||||
} catch (ClientRegistrationException e) {
|
||||
assertEquals(ERR_MSG_CLIENT_REG_FAIL, e.getMessage());
|
||||
}
|
||||
|
||||
// Try to register client with "client-jwt" - should pass
|
||||
String clientUUID = createClientDynamically("client-jwt", (OIDCClientRepresentation clientRep) -> {
|
||||
clientRep.setTokenEndpointAuthMethod(OIDCLoginProtocol.PRIVATE_KEY_JWT);
|
||||
clientRep.setJwksUri("https://foo");
|
||||
});
|
||||
ClientRepresentation client = getClientByAdmin(clientUUID);
|
||||
assertEquals(JWTClientAuthenticator.PROVIDER_ID, client.getClientAuthenticatorType());
|
||||
assertFalse(client.isFullScopeAllowed());
|
||||
|
||||
// Set new initialToken for register new clients
|
||||
setInitialAccessTokenForDynamicClientRegistration();
|
||||
|
||||
// Try to register client with "client-x509" - should pass
|
||||
clientUUID = createClientDynamically("client-x509", (OIDCClientRepresentation clientRep) -> clientRep.setTokenEndpointAuthMethod(OIDCLoginProtocol.TLS_CLIENT_AUTH));
|
||||
client = getClientByAdmin(clientUUID);
|
||||
assertEquals(X509ClientAuthenticator.PROVIDER_ID, client.getClientAuthenticatorType());
|
||||
|
||||
// Check the Consent is enabled, PKCS set to S256
|
||||
OIDCAdvancedConfigWrapper clientConfig = OIDCAdvancedConfigWrapper.fromClientRepresentation(client);
|
||||
assertTrue(client.isConsentRequired());
|
||||
assertEquals(OAuth2Constants.PKCE_METHOD_S256, clientConfig.getPkceCodeChallengeMethod());
|
||||
|
||||
// Check Holder-of-key is enabled
|
||||
switch (profile) {
|
||||
case FAPI2_SECURITY_PROFILE_NAME:
|
||||
case FAPI2_MESSAGE_SIGNING_PROFILE_NAME:
|
||||
assertTrue(clientConfig.isUseMtlsHokToken());
|
||||
assertFalse(clientConfig.isUseDPoP());
|
||||
break;
|
||||
case FAPI2_DPOP_SECURITY_PROFILE_NAME:
|
||||
case FAPI2_DPOP_MESSAGE_SIGNING_PROFILE_NAME:
|
||||
assertFalse(clientConfig.isUseMtlsHokToken());
|
||||
assertTrue(clientConfig.isUseDPoP());
|
||||
break;
|
||||
default:
|
||||
throw new IllegalStateException("Unexpected value: " + profile);
|
||||
}
|
||||
}
|
||||
|
||||
protected void testFAPI2SignatureAlgorithms(String profile) throws Exception {
|
||||
setupPolicyFAPI2ForAllClient(profile);
|
||||
|
||||
// Test that unsecured algorithm (RS256) is not possible
|
||||
try {
|
||||
createClientByAdmin("invalid", (ClientRepresentation clientRep) -> {
|
||||
clientRep.setClientAuthenticatorType(JWTClientAuthenticator.PROVIDER_ID);
|
||||
OIDCAdvancedConfigWrapper clientConfig = OIDCAdvancedConfigWrapper.fromClientRepresentation(clientRep);
|
||||
clientConfig.setIdTokenSignedResponseAlg(Algorithm.RS256);
|
||||
});
|
||||
fail();
|
||||
} catch (ClientPolicyException e) {
|
||||
assertEquals(OAuthErrorException.INVALID_REQUEST, e.getMessage());
|
||||
}
|
||||
|
||||
// Test that secured algorithm is possible to explicitly set
|
||||
String clientUUID = createClientByAdmin("client-jwt", (ClientRepresentation clientRep) -> {
|
||||
clientRep.setClientAuthenticatorType(JWTClientAuthenticator.PROVIDER_ID);
|
||||
OIDCAdvancedConfigWrapper clientCfg = OIDCAdvancedConfigWrapper.fromClientRepresentation(clientRep);
|
||||
clientCfg.setIdTokenSignedResponseAlg(Algorithm.ES256);
|
||||
});
|
||||
ClientRepresentation client = getClientByAdmin(clientUUID);
|
||||
OIDCAdvancedConfigWrapper clientConfig = OIDCAdvancedConfigWrapper.fromClientRepresentation(client);
|
||||
assertEquals(Algorithm.ES256, clientConfig.getIdTokenSignedResponseAlg());
|
||||
assertEquals(Algorithm.PS256, clientConfig.getRequestObjectSignatureAlg());
|
||||
|
||||
// Test default algorithms set everywhere
|
||||
clientUUID = createClientByAdmin("client-jwt-default-alg", (ClientRepresentation clientRep) -> clientRep.setClientAuthenticatorType(JWTClientAuthenticator.PROVIDER_ID));
|
||||
client = getClientByAdmin(clientUUID);
|
||||
clientConfig = OIDCAdvancedConfigWrapper.fromClientRepresentation(client);
|
||||
assertEquals(Algorithm.PS256, clientConfig.getIdTokenSignedResponseAlg());
|
||||
assertEquals(Algorithm.PS256, clientConfig.getRequestObjectSignatureAlg());
|
||||
assertEquals(Algorithm.PS256, clientConfig.getUserInfoSignedResponseAlg());
|
||||
assertEquals(Algorithm.PS256, clientConfig.getTokenEndpointAuthSigningAlg());
|
||||
assertEquals(Algorithm.PS256, client.getAttributes().get(OIDCConfigAttributes.ACCESS_TOKEN_SIGNED_RESPONSE_ALG));
|
||||
|
||||
}
|
||||
|
||||
protected void setupPolicyFAPI2ForAllClient(String profile) throws Exception {
|
||||
String json = (new ClientPoliciesUtil.ClientPoliciesBuilder()).addPolicy(
|
||||
(new ClientPoliciesUtil.ClientPolicyBuilder()).createPolicy("MyPolicy", "Policy for enable FAPI 2.0 Security Profile for all clients", Boolean.TRUE)
|
||||
.addCondition(AnyClientConditionFactory.PROVIDER_ID,
|
||||
createAnyClientConditionConfig())
|
||||
.addProfile(profile)
|
||||
.toRepresentation()
|
||||
).toString();
|
||||
updatePolicies(json);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,503 @@
|
||||
/*
|
||||
* Copyright 2024 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
package org.keycloak.testsuite.client;
|
||||
|
||||
import jakarta.ws.rs.HttpMethod;
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.keycloak.OAuth2Constants;
|
||||
import org.keycloak.OAuthErrorException;
|
||||
import org.keycloak.admin.client.resource.ClientResource;
|
||||
import org.keycloak.authentication.authenticators.client.JWTClientAuthenticator;
|
||||
import org.keycloak.authentication.authenticators.client.X509ClientAuthenticator;
|
||||
import org.keycloak.common.Profile;
|
||||
import org.keycloak.common.util.KeyUtils;
|
||||
import org.keycloak.common.util.Time;
|
||||
import org.keycloak.crypto.Algorithm;
|
||||
import org.keycloak.jose.jwk.ECPublicJWK;
|
||||
import org.keycloak.jose.jwk.JWK;
|
||||
import org.keycloak.jose.jwk.RSAPublicJWK;
|
||||
import org.keycloak.jose.jws.JWSHeader;
|
||||
import org.keycloak.protocol.oidc.OIDCAdvancedConfigWrapper;
|
||||
import org.keycloak.protocol.oidc.utils.OIDCResponseMode;
|
||||
import org.keycloak.protocol.oidc.utils.OIDCResponseType;
|
||||
import org.keycloak.representations.AccessToken;
|
||||
import org.keycloak.representations.RefreshToken;
|
||||
import org.keycloak.representations.idm.ClientRepresentation;
|
||||
import org.keycloak.testsuite.AssertEvents;
|
||||
import org.keycloak.testsuite.arquillian.annotation.EnableFeature;
|
||||
import org.keycloak.testsuite.client.resources.TestApplicationResourceUrls;
|
||||
import org.keycloak.testsuite.rest.resource.TestingOIDCEndpointsApplicationResource;
|
||||
import org.keycloak.testsuite.util.MutualTLSUtils;
|
||||
import org.keycloak.testsuite.util.oauth.AccessTokenResponse;
|
||||
import org.keycloak.testsuite.util.oauth.ParResponse;
|
||||
import org.keycloak.testsuite.util.oauth.PkceGenerator;
|
||||
import org.keycloak.util.JWKSUtils;
|
||||
|
||||
import java.security.KeyPair;
|
||||
import java.util.Collections;
|
||||
import java.util.Random;
|
||||
import java.util.UUID;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.keycloak.testsuite.util.ClientPoliciesUtil.createEcJwk;
|
||||
import static org.keycloak.testsuite.util.ClientPoliciesUtil.createRsaJwk;
|
||||
import static org.keycloak.testsuite.util.ClientPoliciesUtil.generateEcdsaKey;
|
||||
import static org.keycloak.testsuite.util.ClientPoliciesUtil.generateSignedDPoPProof;
|
||||
|
||||
@EnableFeature(value = Profile.Feature.DPOP, skipRestart = true)
|
||||
public class FAPI2DPoPTest extends AbstractFAPI2Test {
|
||||
|
||||
private static final String REALM_NAME = "test";
|
||||
private static final String DPOP_JWT_HEADER_TYPE = "dpop+jwt";
|
||||
private static final String nonce = "123456"; // need to be 123456.
|
||||
|
||||
@Rule
|
||||
public AssertEvents events = new AssertEvents(this);
|
||||
private KeyPair ecKeyPair;
|
||||
private KeyPair rsaKeyPair;
|
||||
private JWSHeader jwsRsaHeader;
|
||||
private JWSHeader jwsEcHeader;
|
||||
private String jktRsa;
|
||||
private String jktEc;
|
||||
|
||||
@Before
|
||||
public void beforeDPoPTest() throws Exception {
|
||||
rsaKeyPair = KeyUtils.generateRsaKeyPair(2048);
|
||||
JWK jwkRsa = createRsaJwk(rsaKeyPair.getPublic());
|
||||
jwkRsa.getOtherClaims().put(RSAPublicJWK.MODULUS, ((RSAPublicJWK) jwkRsa).getModulus());
|
||||
jwkRsa.getOtherClaims().put(RSAPublicJWK.PUBLIC_EXPONENT, ((RSAPublicJWK) jwkRsa).getPublicExponent());
|
||||
jktRsa = JWKSUtils.computeThumbprint(jwkRsa);
|
||||
jwsRsaHeader = new JWSHeader(org.keycloak.jose.jws.Algorithm.PS256, DPOP_JWT_HEADER_TYPE, jwkRsa.getKeyId(), jwkRsa);
|
||||
|
||||
ecKeyPair = generateEcdsaKey("secp256r1");
|
||||
JWK jwkEc = createEcJwk(ecKeyPair.getPublic());
|
||||
jwkEc.getOtherClaims().put(ECPublicJWK.CRV, ((ECPublicJWK) jwkEc).getCrv());
|
||||
jwkEc.getOtherClaims().put(ECPublicJWK.X, ((ECPublicJWK) jwkEc).getX());
|
||||
jwkEc.getOtherClaims().put(ECPublicJWK.Y, ((ECPublicJWK) jwkEc).getY());
|
||||
jktEc = JWKSUtils.computeThumbprint(jwkEc);
|
||||
jwsEcHeader = new JWSHeader(org.keycloak.jose.jws.Algorithm.ES256, DPOP_JWT_HEADER_TYPE, jwkEc.getKeyId(), jwkEc);
|
||||
}
|
||||
|
||||
private final Random rand = new Random(System.currentTimeMillis());
|
||||
|
||||
@Test
|
||||
public void testFAPI2DPoPSecurityProfileClientRegistration() throws Exception {
|
||||
testFAPI2ClientRegistration(FAPI2_DPOP_SECURITY_PROFILE_NAME);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFAPI2DPoPSecurityProfileOIDCClientRegistration() throws Exception {
|
||||
testFAPI2OIDCClientRegistration(FAPI2_DPOP_SECURITY_PROFILE_NAME);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFAPI2DPoPSecurityProfileSignatureAlgorithms() throws Exception {
|
||||
testFAPI2SignatureAlgorithms(FAPI2_DPOP_SECURITY_PROFILE_NAME);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFAPI2DPoPSecurityProfileLoginWithPrivateKeyJWT() throws Exception {
|
||||
// setup client policy
|
||||
setupPolicyFAPI2ForAllClient(FAPI2_DPOP_SECURITY_PROFILE_NAME);
|
||||
|
||||
// Register client with private-key-jwt
|
||||
String clientUUID = createClientByAdmin(clientId, (ClientRepresentation clientRep) -> {
|
||||
clientRep.setClientAuthenticatorType(JWTClientAuthenticator.PROVIDER_ID);
|
||||
OIDCAdvancedConfigWrapper.fromClientRepresentation(clientRep).setRequestUris(Collections.singletonList(TestApplicationResourceUrls.clientRequestUri()));
|
||||
});
|
||||
ClientResource clientResource = adminClient.realm(REALM_NAME).clients().get(clientUUID);
|
||||
ClientRepresentation client = clientResource.toRepresentation();
|
||||
OIDCAdvancedConfigWrapper clientConfig = OIDCAdvancedConfigWrapper.fromClientRepresentation(client);
|
||||
assertEquals(JWTClientAuthenticator.PROVIDER_ID, client.getClientAuthenticatorType());
|
||||
assertEquals(Algorithm.PS256, clientConfig.getTokenEndpointAuthSigningAlg());
|
||||
assertEquals(OAuth2Constants.PKCE_METHOD_S256, clientConfig.getPkceCodeChallengeMethod());
|
||||
assertFalse(client.isImplicitFlowEnabled());
|
||||
assertFalse(client.isFullScopeAllowed());
|
||||
assertFalse(clientConfig.isUseMtlsHokToken());
|
||||
assertTrue(clientConfig.isUseDPoP());
|
||||
assertTrue(client.isConsentRequired());
|
||||
|
||||
// send a pushed authorization request
|
||||
// use EC key for DPoP proof and send dpop_jkt explicitly
|
||||
int clockSkew = rand.nextInt(-10, 10); // acceptable clock skew is +-10sec
|
||||
oauth.client(clientId);
|
||||
pkceGenerator = PkceGenerator.s256();
|
||||
String dpopProofEncoded = generateSignedDPoPProof(UUID.randomUUID().toString(), HttpMethod.POST, oauth.getEndpoints().getPushedAuthorizationRequest(), (long) (Time.currentTime() + clockSkew), Algorithm.ES256, jwsEcHeader, ecKeyPair.getPrivate(), null);
|
||||
|
||||
TestingOIDCEndpointsApplicationResource.AuthorizationEndpointRequestObject requestObject = createValidRequestObjectForSecureRequestObjectExecutor(clientId);
|
||||
requestObject.setNonce(nonce);
|
||||
requestObject.setCodeChallenge(pkceGenerator.getCodeChallenge());
|
||||
requestObject.setCodeChallengeMethod(pkceGenerator.getCodeChallengeMethod());
|
||||
requestObject.setDpopJkt(jktEc);
|
||||
registerRequestObject(requestObject, clientId, Algorithm.PS256, false);
|
||||
|
||||
String signedJwt = createSignedRequestToken(clientId, Algorithm.PS256);
|
||||
ParResponse pResp = oauth
|
||||
.client(clientId)
|
||||
.pushedAuthorizationRequest()
|
||||
.codeChallenge(pkceGenerator)
|
||||
.nonce(nonce)
|
||||
.dpopProof(dpopProofEncoded)
|
||||
.signedJwt(signedJwt)
|
||||
.send();
|
||||
|
||||
assertEquals(201, pResp.getStatusCode());
|
||||
requestUri = pResp.getRequestUri();
|
||||
request = null;
|
||||
|
||||
// send an authorization request
|
||||
String code = loginUserAndGetCode(clientId, nonce,false);
|
||||
|
||||
// send a token request
|
||||
dpopProofEncoded = generateSignedDPoPProof(UUID.randomUUID().toString(), HttpMethod.POST, oauth.getEndpoints().getToken(), (long) (Time.currentTime() + clockSkew), Algorithm.ES256, jwsEcHeader, ecKeyPair.getPrivate(), null);
|
||||
signedJwt = createSignedRequestToken(clientId, Algorithm.PS256);
|
||||
|
||||
oauth.client(clientId).httpClient().set(MutualTLSUtils.newCloseableHttpClientWithDefaultKeyStoreAndTrustStore());
|
||||
AccessTokenResponse tokenResponse = oauth
|
||||
.client(clientId)
|
||||
.accessTokenRequest(code)
|
||||
.codeVerifier(pkceGenerator.getCodeVerifier())
|
||||
.dpopProof(dpopProofEncoded)
|
||||
.signedJwt(signedJwt)
|
||||
.send();
|
||||
assertSuccessfulTokenResponse(tokenResponse);
|
||||
|
||||
// check HoK required
|
||||
// use EC key for DPoP proof
|
||||
AccessToken accessToken = oauth.verifyToken(tokenResponse.getAccessToken());
|
||||
assertEquals(jktEc, accessToken.getConfirmation().getKeyThumbprint());
|
||||
RefreshToken refreshToken = oauth.parseRefreshToken(tokenResponse.getRefreshToken());
|
||||
assertNull(refreshToken.getConfirmation());
|
||||
|
||||
// Logout and remove consent of the user for next logins
|
||||
logoutUserAndRevokeConsent(clientId, TEST_USERNAME);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFAPI2DPoPSecurityProfileLoginWithMTLS() throws Exception {
|
||||
// setup client policy
|
||||
setupPolicyFAPI2ForAllClient(FAPI2_DPOP_SECURITY_PROFILE_NAME);
|
||||
|
||||
// create client with MTLS authentication
|
||||
// Register client with X509
|
||||
String clientUUID = createClientByAdmin(clientId, (ClientRepresentation clientRep) -> {
|
||||
clientRep.setClientAuthenticatorType(X509ClientAuthenticator.PROVIDER_ID);
|
||||
OIDCAdvancedConfigWrapper clientConfig = OIDCAdvancedConfigWrapper.fromClientRepresentation(clientRep);
|
||||
clientConfig.setRequestUris(Collections.singletonList(TestApplicationResourceUrls.clientRequestUri()));
|
||||
clientConfig.setTlsClientAuthSubjectDn(MutualTLSUtils.DEFAULT_KEYSTORE_SUBJECT_DN);
|
||||
clientConfig.setAllowRegexPatternComparison(false);
|
||||
});
|
||||
ClientResource clientResource = adminClient.realm(REALM_NAME).clients().get(clientUUID);
|
||||
ClientRepresentation client = clientResource.toRepresentation();
|
||||
OIDCAdvancedConfigWrapper clientConfig = OIDCAdvancedConfigWrapper.fromClientRepresentation(client);
|
||||
assertEquals(X509ClientAuthenticator.PROVIDER_ID, client.getClientAuthenticatorType());
|
||||
assertEquals(Algorithm.PS256, clientConfig.getTokenEndpointAuthSigningAlg());
|
||||
assertEquals(OAuth2Constants.PKCE_METHOD_S256, clientConfig.getPkceCodeChallengeMethod());
|
||||
assertFalse(client.isImplicitFlowEnabled());
|
||||
assertFalse(client.isFullScopeAllowed());
|
||||
assertFalse(clientConfig.isUseMtlsHokToken());
|
||||
assertTrue(clientConfig.isUseDPoP());
|
||||
assertTrue(client.isConsentRequired());
|
||||
|
||||
oauth.client(clientId);
|
||||
|
||||
// without PAR request - should fail
|
||||
oauth.openLoginForm();
|
||||
assertBrowserWithError("request_uri not included.");
|
||||
|
||||
pkceGenerator = PkceGenerator.s256();
|
||||
|
||||
// requiring hybrid request - should fail
|
||||
ParResponse pResp = oauth
|
||||
.client(clientId)
|
||||
.responseType(OIDCResponseType.CODE + " " + OIDCResponseType.ID_TOKEN + " " + OIDCResponseType.TOKEN)
|
||||
.pushedAuthorizationRequest()
|
||||
.codeChallenge(pkceGenerator)
|
||||
.state(null)
|
||||
.nonce(nonce)
|
||||
.send();
|
||||
|
||||
assertEquals(401, pResp.getStatusCode());
|
||||
assertEquals(OAuthErrorException.UNAUTHORIZED_CLIENT, pResp.getError());
|
||||
|
||||
// an additional parameter in an authorization request that does not exist in a PAR request - should fail
|
||||
pResp = oauth
|
||||
.client(clientId)
|
||||
.responseType(OIDCResponseType.CODE)
|
||||
.pushedAuthorizationRequest()
|
||||
.requestUri(null)
|
||||
.codeChallenge(pkceGenerator)
|
||||
.state(null)
|
||||
.nonce(nonce)
|
||||
.send();
|
||||
assertEquals(201, pResp.getStatusCode());
|
||||
oauth.loginForm().requestUri(pResp.getRequestUri()).param("custom", "value").open();
|
||||
assertBrowserWithError("PAR request did not include necessary parameters");
|
||||
|
||||
// duplicated usage of a PAR request - should fail
|
||||
oauth.loginForm().requestUri(pResp.getRequestUri()).open();
|
||||
assertBrowserWithError("PAR not found. not issued or used multiple times.");
|
||||
|
||||
// send a pushed authorization request
|
||||
// use RSA key for DPoP proof but not send dpop_jkt
|
||||
int clockSkew = rand.nextInt(-10, 10); // acceptable clock skew is +-10sec
|
||||
String dpopProofEncoded = generateSignedDPoPProof(UUID.randomUUID().toString(), HttpMethod.POST, oauth.getEndpoints().getPushedAuthorizationRequest(), (long) (Time.currentTime() + clockSkew), Algorithm.PS256, jwsRsaHeader, rsaKeyPair.getPrivate(), null);
|
||||
pResp = oauth
|
||||
.client(clientId)
|
||||
.responseType(OIDCResponseType.CODE)
|
||||
.pushedAuthorizationRequest()
|
||||
.requestUri(null)
|
||||
.codeChallenge(pkceGenerator)
|
||||
.state(null)
|
||||
.nonce(nonce)
|
||||
.dpopProof(dpopProofEncoded)
|
||||
.send();
|
||||
assertEquals(201, pResp.getStatusCode());
|
||||
requestUri = pResp.getRequestUri();
|
||||
|
||||
// send an authorization request
|
||||
String code = loginUserAndGetCode(clientId, "123456",false);
|
||||
|
||||
// send a token request
|
||||
dpopProofEncoded = generateSignedDPoPProof(UUID.randomUUID().toString(), HttpMethod.POST, oauth.getEndpoints().getToken(), (long) (Time.currentTime() + clockSkew), Algorithm.PS256, jwsRsaHeader, rsaKeyPair.getPrivate(), null);
|
||||
AccessTokenResponse tokenResponse = oauth.
|
||||
client(clientId).
|
||||
accessTokenRequest(code).
|
||||
codeVerifier(pkceGenerator.getCodeVerifier()).
|
||||
dpopProof(dpopProofEncoded).
|
||||
send();
|
||||
|
||||
// check HoK required
|
||||
// use RSA key for DPoP proof
|
||||
AccessToken accessToken = oauth.verifyToken(tokenResponse.getAccessToken());
|
||||
assertEquals(jktRsa, accessToken.getConfirmation().getKeyThumbprint());
|
||||
RefreshToken refreshToken = oauth.parseRefreshToken(tokenResponse.getRefreshToken());
|
||||
assertNull(refreshToken.getConfirmation());
|
||||
|
||||
// Logout and remove consent of the user for next logins
|
||||
logoutUserAndRevokeConsent(clientId, TEST_USERNAME);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFAPI2DPoPMessageSigningClientRegistration() throws Exception {
|
||||
testFAPI2ClientRegistration(FAPI2_DPOP_MESSAGE_SIGNING_PROFILE_NAME);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFAPI2DPoPMessageSigningOIDCClientRegistration() throws Exception {
|
||||
testFAPI2OIDCClientRegistration(FAPI2_DPOP_MESSAGE_SIGNING_PROFILE_NAME);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFAPI2DPoPMessageSigningSignatureAlgorithms() throws Exception {
|
||||
testFAPI2SignatureAlgorithms(FAPI2_DPOP_MESSAGE_SIGNING_PROFILE_NAME);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFAPI2DPoPMessageSigningLoginWithMTLS() throws Exception {
|
||||
// setup client policy
|
||||
setupPolicyFAPI2ForAllClient(FAPI2_DPOP_MESSAGE_SIGNING_PROFILE_NAME);
|
||||
|
||||
// create client with MTLS authentication
|
||||
// Register client with X509
|
||||
String clientUUID = createClientByAdmin(clientId, (ClientRepresentation clientRep) -> {
|
||||
clientRep.setClientAuthenticatorType(X509ClientAuthenticator.PROVIDER_ID);
|
||||
OIDCAdvancedConfigWrapper clientConfig = OIDCAdvancedConfigWrapper.fromClientRepresentation(clientRep);
|
||||
clientConfig.setRequestUris(Collections.singletonList(TestApplicationResourceUrls.clientRequestUri()));
|
||||
clientConfig.setTlsClientAuthSubjectDn(MutualTLSUtils.DEFAULT_KEYSTORE_SUBJECT_DN);
|
||||
clientConfig.setAllowRegexPatternComparison(false);
|
||||
clientConfig.setRequestObjectRequired("request or request_uri");
|
||||
clientConfig.setAuthorizationSignedResponseAlg(Algorithm.PS256);
|
||||
});
|
||||
ClientResource clientResource = adminClient.realm(REALM_NAME).clients().get(clientUUID);
|
||||
ClientRepresentation client = clientResource.toRepresentation();
|
||||
OIDCAdvancedConfigWrapper clientConfig = OIDCAdvancedConfigWrapper.fromClientRepresentation(client);
|
||||
assertEquals(X509ClientAuthenticator.PROVIDER_ID, client.getClientAuthenticatorType());
|
||||
assertEquals(Algorithm.PS256, clientConfig.getTokenEndpointAuthSigningAlg());
|
||||
assertEquals(OAuth2Constants.PKCE_METHOD_S256, clientConfig.getPkceCodeChallengeMethod());
|
||||
assertEquals(Algorithm.PS256, clientConfig.getRequestObjectSignatureAlg());
|
||||
assertFalse(client.isImplicitFlowEnabled());
|
||||
assertFalse(client.isFullScopeAllowed());
|
||||
assertFalse(clientConfig.isUseMtlsHokToken());
|
||||
assertTrue(clientConfig.isUseDPoP());
|
||||
assertTrue(client.isConsentRequired());
|
||||
|
||||
// Set request object and correct responseType
|
||||
// use EC key for DPoP proof and send dpop_jkt explicitly
|
||||
int clockSkew = rand.nextInt(-10, 10); // acceptable clock skew is +-10sec
|
||||
String dpopProofEncoded = generateSignedDPoPProof(UUID.randomUUID().toString(), HttpMethod.POST, oauth.getEndpoints().getPushedAuthorizationRequest(), (long) (Time.currentTime() + clockSkew), Algorithm.ES256, jwsEcHeader, ecKeyPair.getPrivate(), null);
|
||||
oauth.client(clientId);
|
||||
pkceGenerator = PkceGenerator.s256();
|
||||
TestingOIDCEndpointsApplicationResource.AuthorizationEndpointRequestObject requestObject = createValidRequestObjectForSecureRequestObjectExecutor(clientId);
|
||||
requestObject.setNonce(nonce);
|
||||
requestObject.setResponseType(OIDCResponseType.CODE);
|
||||
requestObject.setResponseMode(OIDCResponseMode.QUERY_JWT.value());
|
||||
requestObject.setCodeChallenge(pkceGenerator.getCodeChallenge());
|
||||
requestObject.setCodeChallengeMethod(pkceGenerator.getCodeChallengeMethod());
|
||||
requestObject.setDpopJkt(jktEc);
|
||||
registerRequestObject(requestObject, clientId, Algorithm.PS256, false);
|
||||
|
||||
// send a pushed authorization request
|
||||
ParResponse pResp = oauth
|
||||
.client(clientId)
|
||||
.pushedAuthorizationRequest()
|
||||
.request(request)
|
||||
.requestUri(null)
|
||||
.codeChallenge(pkceGenerator)
|
||||
.nonce(nonce)
|
||||
.dpopProof(dpopProofEncoded)
|
||||
.dpopJkt(jktEc)
|
||||
.send();
|
||||
assertEquals(201, pResp.getStatusCode());
|
||||
|
||||
// send an authorization request
|
||||
oauth.responseType(OIDCResponseType.CODE);
|
||||
oauth.responseMode(OIDCResponseMode.QUERY_JWT.value());
|
||||
requestUri = pResp.getRequestUri();
|
||||
request = null;
|
||||
String code = loginUserAndGetCodeInJwtQueryResponseMode(clientId, nonce);
|
||||
|
||||
// send a token request
|
||||
dpopProofEncoded = generateSignedDPoPProof(UUID.randomUUID().toString(), HttpMethod.POST, oauth.getEndpoints().getToken(), (long) (Time.currentTime() + clockSkew), Algorithm.ES256, jwsEcHeader, ecKeyPair.getPrivate(), null);
|
||||
AccessTokenResponse tokenResponse = oauth
|
||||
.client(clientId)
|
||||
.accessTokenRequest(code)
|
||||
.codeVerifier(pkceGenerator.getCodeVerifier())
|
||||
.dpopProof(dpopProofEncoded)
|
||||
.send();
|
||||
|
||||
// check HoK required
|
||||
// use EC key for DPoP proof
|
||||
AccessToken accessToken = oauth.verifyToken(tokenResponse.getAccessToken());
|
||||
assertEquals(jktEc, accessToken.getConfirmation().getKeyThumbprint());
|
||||
RefreshToken refreshToken = oauth.parseRefreshToken(tokenResponse.getRefreshToken());
|
||||
assertNull(refreshToken.getConfirmation());
|
||||
|
||||
// Logout and remove consent of the user for next logins
|
||||
logoutUserAndRevokeConsent(clientId, TEST_USERNAME);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testFAPI2DPoPMessageSigningLoginWithPrivateKeyJWT() throws Exception {
|
||||
// setup client policy
|
||||
setupPolicyFAPI2ForAllClient(FAPI2_DPOP_MESSAGE_SIGNING_PROFILE_NAME);
|
||||
|
||||
// create client with MTLS authentication
|
||||
// Register client with X509
|
||||
String clientUUID = createClientByAdmin(clientId, (ClientRepresentation clientRep) -> {
|
||||
clientRep.setClientAuthenticatorType(JWTClientAuthenticator.PROVIDER_ID);
|
||||
OIDCAdvancedConfigWrapper clientConfig = OIDCAdvancedConfigWrapper.fromClientRepresentation(clientRep);
|
||||
clientConfig.setRequestUris(Collections.singletonList(TestApplicationResourceUrls.clientRequestUri()));
|
||||
clientConfig.setRequestObjectRequired("request or request_uri");
|
||||
OIDCAdvancedConfigWrapper.fromClientRepresentation(clientRep).setAuthorizationSignedResponseAlg(Algorithm.PS256);
|
||||
});
|
||||
ClientResource clientResource = adminClient.realm(REALM_NAME).clients().get(clientUUID);
|
||||
ClientRepresentation client = clientResource.toRepresentation();
|
||||
OIDCAdvancedConfigWrapper clientConfig = OIDCAdvancedConfigWrapper.fromClientRepresentation(client);
|
||||
assertEquals(JWTClientAuthenticator.PROVIDER_ID, client.getClientAuthenticatorType());
|
||||
assertEquals(Algorithm.PS256, clientConfig.getTokenEndpointAuthSigningAlg());
|
||||
assertEquals(Algorithm.PS256, clientConfig.getRequestObjectSignatureAlg());
|
||||
assertEquals(OAuth2Constants.PKCE_METHOD_S256, clientConfig.getPkceCodeChallengeMethod());
|
||||
assertFalse(client.isImplicitFlowEnabled());
|
||||
assertFalse(client.isFullScopeAllowed());
|
||||
assertFalse(clientConfig.isUseMtlsHokToken());
|
||||
assertTrue(clientConfig.isUseDPoP());
|
||||
assertTrue(client.isConsentRequired());
|
||||
|
||||
oauth.client(clientId);
|
||||
pkceGenerator = PkceGenerator.s256();
|
||||
|
||||
// without a request object - should fail
|
||||
TestingOIDCEndpointsApplicationResource.AuthorizationEndpointRequestObject requestObject = createValidRequestObjectForSecureRequestObjectExecutor(clientId);
|
||||
registerRequestObject(requestObject, clientId, Algorithm.PS256, true);
|
||||
String signedJwt = createSignedRequestToken(clientId, Algorithm.PS256);
|
||||
ParResponse pResp = oauth
|
||||
.client(clientId)
|
||||
.responseType(OIDCResponseType.CODE)
|
||||
.pushedAuthorizationRequest()
|
||||
.request(null)
|
||||
.requestUri(null)
|
||||
.codeChallenge(pkceGenerator)
|
||||
.state(null)
|
||||
.nonce(nonce)
|
||||
.signedJwt(signedJwt)
|
||||
.send();
|
||||
assertEquals(400, pResp.getStatusCode());
|
||||
assertEquals(OAuthErrorException.INVALID_REQUEST_OBJECT, pResp.getError());
|
||||
|
||||
// Set request object and correct responseType
|
||||
requestObject = createValidRequestObjectForSecureRequestObjectExecutor(clientId);
|
||||
requestObject.setNonce(nonce);
|
||||
requestObject.setResponseType(OIDCResponseType.CODE);
|
||||
requestObject.setResponseMode(OIDCResponseMode.QUERY_JWT.value());
|
||||
requestObject.setCodeChallenge(pkceGenerator.getCodeChallenge());
|
||||
requestObject.setCodeChallengeMethod(pkceGenerator.getCodeChallengeMethod());
|
||||
registerRequestObject(requestObject, clientId, Algorithm.PS256, false);
|
||||
|
||||
// send a pushed authorization request
|
||||
// use RSA key for DPoP proof but not send dpop_jkt
|
||||
int clockSkew = rand.nextInt(-10, 10); // acceptable clock skew is +-10sec
|
||||
String dpopProofEncoded = generateSignedDPoPProof(UUID.randomUUID().toString(), HttpMethod.POST, oauth.getEndpoints().getPushedAuthorizationRequest(), (long) (Time.currentTime() + clockSkew), Algorithm.PS256, jwsRsaHeader, rsaKeyPair.getPrivate(), null);
|
||||
signedJwt = createSignedRequestToken(clientId, Algorithm.PS256);
|
||||
pResp = oauth
|
||||
.client(clientId)
|
||||
.pushedAuthorizationRequest()
|
||||
.request(request)
|
||||
.requestUri(null)
|
||||
.codeChallenge(pkceGenerator)
|
||||
.nonce(nonce)
|
||||
.dpopProof(dpopProofEncoded)
|
||||
.signedJwt(signedJwt)
|
||||
.send();
|
||||
assertEquals(201, pResp.getStatusCode());
|
||||
|
||||
// send an authorization request
|
||||
requestUri = pResp.getRequestUri();
|
||||
request = null;
|
||||
String code = loginUserAndGetCodeInJwtQueryResponseMode(clientId, null);
|
||||
|
||||
// send a token request
|
||||
// use RSA key for DPoP proof
|
||||
dpopProofEncoded = generateSignedDPoPProof(UUID.randomUUID().toString(), HttpMethod.POST, oauth.getEndpoints().getToken(), (long) (Time.currentTime() + clockSkew), Algorithm.PS256, jwsRsaHeader, rsaKeyPair.getPrivate(), null);
|
||||
signedJwt = createSignedRequestToken(clientId, Algorithm.PS256);
|
||||
AccessTokenResponse tokenResponse = oauth
|
||||
.client(clientId)
|
||||
.accessTokenRequest(code)
|
||||
.codeVerifier(pkceGenerator.getCodeVerifier())
|
||||
.dpopProof(dpopProofEncoded)
|
||||
.signedJwt(signedJwt)
|
||||
.send();
|
||||
assertSuccessfulTokenResponse(tokenResponse);
|
||||
|
||||
// check HoK required
|
||||
// use RSA key for DPoP proof
|
||||
AccessToken accessToken = oauth.verifyToken(tokenResponse.getAccessToken());
|
||||
assertEquals(jktRsa, accessToken.getConfirmation().getKeyThumbprint());
|
||||
RefreshToken refreshToken = oauth.parseRefreshToken(tokenResponse.getRefreshToken());
|
||||
assertNull(refreshToken.getConfirmation());
|
||||
|
||||
// Logout and remove consent of the user for next logins
|
||||
logoutUserAndRevokeConsent(clientId, TEST_USERNAME);
|
||||
}
|
||||
}
|
||||
@ -209,6 +209,8 @@ public abstract class AbstractClientPoliciesTest extends AbstractKeycloakTest {
|
||||
protected static final String OAUTH2_1_CONFIDENTIAL_CLIENT_PROFILE_NAME = "oauth-2-1-for-confidential-client";
|
||||
protected static final String OAUTH2_1_PUBLIC_CLIENT_PROFILE_NAME = "oauth-2-1-for-public-client";
|
||||
protected static final String SAML_SECURITY_PROFILE_NAME = "saml-security-profile";
|
||||
protected static final String FAPI2_DPOP_SECURITY_PROFILE_NAME = "fapi-2-dpop-security-profile";
|
||||
protected static final String FAPI2_DPOP_MESSAGE_SIGNING_PROFILE_NAME = "fapi-2-dpop-message-signing";
|
||||
|
||||
protected static final String ERR_MSG_MISSING_NONCE = "Missing parameter: nonce";
|
||||
protected static final String ERR_MSG_MISSING_STATE = "Missing parameter: state";
|
||||
@ -348,7 +350,7 @@ public abstract class AbstractClientPoliciesTest extends AbstractKeycloakTest {
|
||||
ClientProfilesRepresentation actualProfilesRep = getProfilesWithGlobals();
|
||||
|
||||
// same profiles
|
||||
assertExpectedProfiles(actualProfilesRep, Arrays.asList(FAPI1_BASELINE_PROFILE_NAME, FAPI1_ADVANCED_PROFILE_NAME, FAPI_CIBA_PROFILE_NAME, FAPI2_SECURITY_PROFILE_NAME, FAPI2_MESSAGE_SIGNING_PROFILE_NAME, OAUTH2_1_CONFIDENTIAL_CLIENT_PROFILE_NAME, OAUTH2_1_PUBLIC_CLIENT_PROFILE_NAME, SAML_SECURITY_PROFILE_NAME), Arrays.asList("ordinal-test-profile", "lack-of-builtin-field-test-profile"));
|
||||
assertExpectedProfiles(actualProfilesRep, Arrays.asList(FAPI1_BASELINE_PROFILE_NAME, FAPI1_ADVANCED_PROFILE_NAME, FAPI_CIBA_PROFILE_NAME, FAPI2_SECURITY_PROFILE_NAME, FAPI2_MESSAGE_SIGNING_PROFILE_NAME, OAUTH2_1_CONFIDENTIAL_CLIENT_PROFILE_NAME, OAUTH2_1_PUBLIC_CLIENT_PROFILE_NAME, SAML_SECURITY_PROFILE_NAME, FAPI2_DPOP_SECURITY_PROFILE_NAME, FAPI2_DPOP_MESSAGE_SIGNING_PROFILE_NAME), Arrays.asList("ordinal-test-profile", "lack-of-builtin-field-test-profile"));
|
||||
|
||||
// each profile - fapi-1-baseline
|
||||
ClientProfileRepresentation actualProfileRep = getProfileRepresentation(actualProfilesRep, FAPI1_BASELINE_PROFILE_NAME, true);
|
||||
|
||||
@ -84,7 +84,7 @@ public class ClientPoliciesLoadUpdateTest extends AbstractClientPoliciesTest {
|
||||
ClientProfilesRepresentation actualProfilesRep = getProfilesWithGlobals();
|
||||
|
||||
// same profiles
|
||||
assertExpectedProfiles(actualProfilesRep, Arrays.asList(FAPI1_BASELINE_PROFILE_NAME, FAPI1_ADVANCED_PROFILE_NAME, FAPI_CIBA_PROFILE_NAME, FAPI2_SECURITY_PROFILE_NAME, FAPI2_MESSAGE_SIGNING_PROFILE_NAME, OAUTH2_1_CONFIDENTIAL_CLIENT_PROFILE_NAME, OAUTH2_1_PUBLIC_CLIENT_PROFILE_NAME, SAML_SECURITY_PROFILE_NAME), Collections.emptyList());
|
||||
assertExpectedProfiles(actualProfilesRep, Arrays.asList(FAPI1_BASELINE_PROFILE_NAME, FAPI1_ADVANCED_PROFILE_NAME, FAPI_CIBA_PROFILE_NAME, FAPI2_SECURITY_PROFILE_NAME, FAPI2_MESSAGE_SIGNING_PROFILE_NAME, OAUTH2_1_CONFIDENTIAL_CLIENT_PROFILE_NAME, OAUTH2_1_PUBLIC_CLIENT_PROFILE_NAME, SAML_SECURITY_PROFILE_NAME, FAPI2_DPOP_SECURITY_PROFILE_NAME, FAPI2_DPOP_MESSAGE_SIGNING_PROFILE_NAME), Collections.emptyList());
|
||||
|
||||
// each profile - fapi-1-baseline
|
||||
ClientProfileRepresentation actualProfileRep = getProfileRepresentation(actualProfilesRep, FAPI1_BASELINE_PROFILE_NAME, true);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user