mirror of
https://github.com/keycloak/keycloak.git
synced 2026-05-26 13:50:48 +00:00
[OID4VCI-HAIP] Pass oid4vci-1_0-issuer-metadata_test
Signed-off-by: Thomas Diesler <tdiesler@proton.me>
This commit is contained in:
committed by
Marek Posolda
parent
29aeb894fc
commit
22e018cfdf
@@ -98,6 +98,7 @@ public class Profile {
|
||||
STEP_UP_AUTHENTICATION_SAML("Step-up Authentication Saml", Type.PREVIEW, Feature.STEP_UP_AUTHENTICATION),
|
||||
|
||||
CLIENT_AUTH_FEDERATED("Authenticates client based on assertions issued by identity provider", Type.DEFAULT),
|
||||
CLIENT_AUTH_ABCA("Attestation-Based Client Authentication", Type.EXPERIMENTAL),
|
||||
|
||||
SPIFFE("SPIFFE trust relationship provider", Type.PREVIEW),
|
||||
|
||||
|
||||
+22
@@ -115,6 +115,12 @@ public class OIDCConfigurationRepresentation {
|
||||
@JsonProperty("token_endpoint_auth_signing_alg_values_supported")
|
||||
private List<String> tokenEndpointAuthSigningAlgValuesSupported;
|
||||
|
||||
@JsonProperty("client_attestation_signing_alg_values_supported")
|
||||
private List<String> clientAttestationSigningAlgValuesSupported;
|
||||
|
||||
@JsonProperty("client_attestation_pop_signing_alg_values_supported")
|
||||
private List<String> clientAttestationPopSigningAlgValuesSupported;
|
||||
|
||||
@JsonProperty("introspection_endpoint_auth_methods_supported")
|
||||
private List<String> introspectionEndpointAuthMethodsSupported;
|
||||
|
||||
@@ -410,6 +416,22 @@ public class OIDCConfigurationRepresentation {
|
||||
this.tokenEndpointAuthSigningAlgValuesSupported = tokenEndpointAuthSigningAlgValuesSupported;
|
||||
}
|
||||
|
||||
public List<String> getClientAttestationSigningAlgValuesSupported() {
|
||||
return clientAttestationSigningAlgValuesSupported;
|
||||
}
|
||||
|
||||
public void setClientAttestationSigningAlgValuesSupported(List<String> clientAttestationSigningAlgValuesSupported) {
|
||||
this.clientAttestationSigningAlgValuesSupported = clientAttestationSigningAlgValuesSupported;
|
||||
}
|
||||
|
||||
public List<String> getClientAttestationPopSigningAlgValuesSupported() {
|
||||
return clientAttestationPopSigningAlgValuesSupported;
|
||||
}
|
||||
|
||||
public void setClientAttestationPopSigningAlgValuesSupported(List<String> clientAttestationPopSigningAlgValuesSupported) {
|
||||
this.clientAttestationPopSigningAlgValuesSupported = clientAttestationPopSigningAlgValuesSupported;
|
||||
}
|
||||
|
||||
public List<String> getIntrospectionEndpointAuthMethodsSupported() {
|
||||
return introspectionEndpointAuthMethodsSupported;
|
||||
}
|
||||
|
||||
+1
@@ -47,5 +47,6 @@ public enum AuthenticationFlowError {
|
||||
DISPLAY_NOT_SUPPORTED,
|
||||
|
||||
ACCESS_DENIED,
|
||||
UNAUTHORIZED_CLIENT,
|
||||
GENERIC_AUTHENTICATION_ERROR
|
||||
}
|
||||
|
||||
+4
@@ -23,11 +23,15 @@ import org.keycloak.authentication.ClientAuthenticatorFactory;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.KeycloakSessionFactory;
|
||||
|
||||
import org.jboss.logging.Logger;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public abstract class AbstractClientAuthenticator implements ClientAuthenticator, ClientAuthenticatorFactory {
|
||||
|
||||
protected final Logger logger = Logger.getLogger(getClass());
|
||||
|
||||
@Override
|
||||
public ClientAuthenticator create() {
|
||||
return this;
|
||||
|
||||
+111
@@ -0,0 +1,111 @@
|
||||
/*
|
||||
* Copyright 2026 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.authentication.authenticators.client;
|
||||
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import jakarta.ws.rs.core.Response;
|
||||
|
||||
import org.keycloak.Config;
|
||||
import org.keycloak.OAuthErrorException;
|
||||
import org.keycloak.authentication.AuthenticationFlowError;
|
||||
import org.keycloak.authentication.ClientAuthenticationFlowContext;
|
||||
import org.keycloak.common.Profile;
|
||||
import org.keycloak.models.AuthenticationExecutionModel;
|
||||
import org.keycloak.models.ClientModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
|
||||
import org.keycloak.provider.EnvironmentDependentProviderFactory;
|
||||
import org.keycloak.provider.ProviderConfigProperty;
|
||||
|
||||
import static jakarta.ws.rs.core.Response.Status.NOT_IMPLEMENTED;
|
||||
|
||||
/**
|
||||
* Attestation-Based Client Authentication based on Client Attestation JWT and PoP.
|
||||
* See <a href="https://datatracker.ietf.org/doc/draft-ietf-oauth-attestation-based-client-auth">specs</a> for more details.
|
||||
*
|
||||
* @author <a href="mailto:tdiesler@proton.me">Thomas Diesler</a>
|
||||
*/
|
||||
public class AttestationBasedClientAuthenticator extends AbstractClientAuthenticator implements EnvironmentDependentProviderFactory {
|
||||
|
||||
public static final String PROVIDER_ID = "client-attestation";
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return PROVIDER_ID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void authenticateClient(ClientAuthenticationFlowContext context) {
|
||||
Response errorResponse = ClientAuthUtil.errorResponse(NOT_IMPLEMENTED.getStatusCode(), OAuthErrorException.UNAUTHORIZED_CLIENT,
|
||||
"Attestation-Based Client Authentication not (yet) supported");
|
||||
context.failure(AuthenticationFlowError.UNAUTHORIZED_CLIENT, errorResponse);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDisplayType() {
|
||||
return "Attestation-Based";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isConfigurable() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AuthenticationExecutionModel.Requirement[] getRequirementChoices() {
|
||||
return REQUIREMENT_CHOICES;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getHelpText() {
|
||||
return "Validates client based on a Client Attestation JWT and a PoP JWT which proves possession of the private key";
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ProviderConfigProperty> getConfigProperties() {
|
||||
return List.of();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ProviderConfigProperty> getConfigPropertiesPerClient() {
|
||||
return List.of();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> getAdapterConfiguration(KeycloakSession session, ClientModel client) {
|
||||
return Map.of();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSupported(Config.Scope config) {
|
||||
return Profile.isFeatureEnabled(Profile.Feature.CLIENT_AUTH_ABCA);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> getProtocolAuthenticatorMethods(String loginProtocol) {
|
||||
if (loginProtocol.equals(OIDCLoginProtocol.LOGIN_PROTOCOL)) {
|
||||
return Set.of(OIDCLoginProtocol.ATTEST_JWT_CLIENT_AUTH);
|
||||
} else {
|
||||
return Set.of();
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
-4
@@ -26,12 +26,9 @@ import org.keycloak.provider.ProviderConfigurationBuilder;
|
||||
import org.keycloak.representations.JsonWebToken;
|
||||
import org.keycloak.services.resources.IdentityBrokerService;
|
||||
|
||||
import org.jboss.logging.Logger;
|
||||
|
||||
public class FederatedJWTClientAuthenticator extends AbstractClientAuthenticator implements EnvironmentDependentProviderFactory {
|
||||
|
||||
private static final Logger LOGGER = Logger.getLogger(FederatedJWTClientAuthenticator.class);
|
||||
|
||||
public static final String PROVIDER_ID = "federated-jwt";
|
||||
|
||||
public static final String JWT_CREDENTIAL_ISSUER_KEY = "jwt.credential.issuer";
|
||||
@@ -113,7 +110,7 @@ public class FederatedJWTClientAuthenticator extends AbstractClientAuthenticator
|
||||
context.failure(AuthenticationFlowError.INVALID_CLIENT_CREDENTIALS);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
LOGGER.warn("Authentication failed", e);
|
||||
logger.warn("Authentication failed", e);
|
||||
context.failure(AuthenticationFlowError.INVALID_CLIENT_CREDENTIALS);
|
||||
}
|
||||
}
|
||||
|
||||
-4
@@ -28,8 +28,6 @@ import org.keycloak.protocol.oidc.OIDCLoginProtocol;
|
||||
import org.keycloak.provider.ProviderConfigProperty;
|
||||
import org.keycloak.services.x509.X509ClientCertificateLookup;
|
||||
|
||||
import org.jboss.logging.Logger;
|
||||
|
||||
public class X509ClientAuthenticator extends AbstractClientAuthenticator {
|
||||
|
||||
public static final String PROVIDER_ID = "client-x509";
|
||||
@@ -56,8 +54,6 @@ public class X509ClientAuthenticator extends AbstractClientAuthenticator {
|
||||
CUSTOM_OIDS_REVERSED.put("E", "1.2.840.113549.1.9.1"); // Another synonym for "EMAILADDRESS"
|
||||
}
|
||||
|
||||
private final static Logger logger = Logger.getLogger(X509ClientAuthenticator.class);
|
||||
|
||||
@Override
|
||||
public void authenticateClient(ClientAuthenticationFlowContext context) {
|
||||
|
||||
|
||||
@@ -121,6 +121,7 @@ public class OIDCLoginProtocol implements LoginProtocol {
|
||||
public static final String CLIENT_SECRET_JWT = "client_secret_jwt";
|
||||
public static final String PRIVATE_KEY_JWT = "private_key_jwt";
|
||||
public static final String TLS_CLIENT_AUTH = "tls_client_auth";
|
||||
public static final String ATTEST_JWT_CLIENT_AUTH = "attest_jwt_client_auth";
|
||||
|
||||
/**
|
||||
* This is just for legacy setups which expect an unencoded, non-RFC6749 compliant client secret send from Keycloak to an IdP.
|
||||
|
||||
@@ -69,6 +69,8 @@ import org.keycloak.urls.UrlType;
|
||||
import org.keycloak.util.JsonSerialization;
|
||||
import org.keycloak.wellknown.WellKnownProvider;
|
||||
|
||||
import static org.keycloak.protocol.oidc.OIDCLoginProtocol.ATTEST_JWT_CLIENT_AUTH;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
@@ -153,10 +155,18 @@ public class OIDCWellKnownProvider implements WellKnownProvider {
|
||||
|
||||
config.setPromptValuesSupported(getPromptValuesSupported(realm));
|
||||
|
||||
config.setTokenEndpointAuthMethodsSupported(getClientAuthMethodsSupported());
|
||||
config.setTokenEndpointAuthSigningAlgValuesSupported(getSupportedClientSigningAlgorithms(false));
|
||||
config.setIntrospectionEndpointAuthMethodsSupported(getClientAuthMethodsSupported());
|
||||
config.setIntrospectionEndpointAuthSigningAlgValuesSupported(getSupportedClientSigningAlgorithms(false));
|
||||
List<String> clientAuthMethodsSupported = getClientAuthMethodsSupported();
|
||||
List<String> supportedClientSigningAlgorithms = getSupportedClientSigningAlgorithms(false);
|
||||
|
||||
config.setTokenEndpointAuthMethodsSupported(clientAuthMethodsSupported);
|
||||
config.setTokenEndpointAuthSigningAlgValuesSupported(supportedClientSigningAlgorithms);
|
||||
config.setIntrospectionEndpointAuthMethodsSupported(clientAuthMethodsSupported);
|
||||
config.setIntrospectionEndpointAuthSigningAlgValuesSupported(supportedClientSigningAlgorithms);
|
||||
|
||||
if (clientAuthMethodsSupported.contains(ATTEST_JWT_CLIENT_AUTH)) {
|
||||
config.setClientAttestationSigningAlgValuesSupported(getSupportedSigningAlgorithms(false));
|
||||
config.setClientAttestationPopSigningAlgValuesSupported(getSupportedSigningAlgorithms(false));
|
||||
}
|
||||
|
||||
config.setAuthorizationSigningAlgValuesSupported(getSupportedSigningAlgorithms(false));
|
||||
config.setAuthorizationEncryptionAlgValuesSupported(getSupportedEncryptionAlg(false));
|
||||
@@ -199,8 +209,8 @@ public class OIDCWellKnownProvider implements WellKnownProvider {
|
||||
// NOTE: Don't hardcode HTTPS checks here. JWKS URI is exposed just in the development/testing environment. For the production environment, the OIDCWellKnownProvider
|
||||
// is not exposed over "http" at all.
|
||||
config.setRevocationEndpoint(revocationEndpoint.toString());
|
||||
config.setRevocationEndpointAuthMethodsSupported(getClientAuthMethodsSupported());
|
||||
config.setRevocationEndpointAuthSigningAlgValuesSupported(getSupportedClientSigningAlgorithms(false));
|
||||
config.setRevocationEndpointAuthMethodsSupported(clientAuthMethodsSupported);
|
||||
config.setRevocationEndpointAuthSigningAlgValuesSupported(supportedClientSigningAlgorithms);
|
||||
|
||||
config.setBackchannelLogoutSupported(true);
|
||||
config.setBackchannelLogoutSessionSupported(true);
|
||||
@@ -249,11 +259,12 @@ public class OIDCWellKnownProvider implements WellKnownProvider {
|
||||
}
|
||||
|
||||
private List<String> getClientAuthMethodsSupported() {
|
||||
return session.getKeycloakSessionFactory().getProviderFactoriesStream(ClientAuthenticator.class)
|
||||
List<String> clientAuthMethods = session.getKeycloakSessionFactory().getProviderFactoriesStream(ClientAuthenticator.class)
|
||||
.map(ClientAuthenticatorFactory.class::cast)
|
||||
.map(caf -> caf.getProtocolAuthenticatorMethods(OIDCLoginProtocol.LOGIN_PROTOCOL))
|
||||
.flatMap(Collection::stream)
|
||||
.collect(Collectors.toList());
|
||||
return clientAuthMethods;
|
||||
}
|
||||
|
||||
private List<String> getSupportedAlgorithms(Class<? extends Provider> clazz, boolean includeNone) {
|
||||
|
||||
+2
-1
@@ -19,4 +19,5 @@ org.keycloak.authentication.authenticators.client.ClientIdAndSecretAuthenticator
|
||||
org.keycloak.authentication.authenticators.client.JWTClientAuthenticator
|
||||
org.keycloak.authentication.authenticators.client.JWTClientSecretAuthenticator
|
||||
org.keycloak.authentication.authenticators.client.X509ClientAuthenticator
|
||||
org.keycloak.authentication.authenticators.client.FederatedJWTClientAuthenticator
|
||||
org.keycloak.authentication.authenticators.client.FederatedJWTClientAuthenticator
|
||||
org.keycloak.authentication.authenticators.client.AttestationBasedClientAuthenticator
|
||||
|
||||
@@ -404,6 +404,13 @@ public abstract class OID4VCIssuerTestBase {
|
||||
}
|
||||
}
|
||||
|
||||
public static class VCTestServerWithABCAEnabled implements KeycloakServerConfig {
|
||||
@Override
|
||||
public KeycloakServerConfigBuilder configure(KeycloakServerConfigBuilder config) {
|
||||
return config.features(Profile.Feature.OID4VC_VCI, Profile.Feature.CLIENT_AUTH_ABCA);
|
||||
}
|
||||
}
|
||||
|
||||
public static class VCTestRealmConfig implements RealmConfig {
|
||||
|
||||
public static final String TEST_REALM_NAME = "test";
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* 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.tests.oid4vc;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.keycloak.protocol.oidc.representations.OIDCConfigurationRepresentation;
|
||||
import org.keycloak.testframework.annotations.KeycloakIntegrationTest;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.keycloak.protocol.oidc.OIDCLoginProtocol.ATTEST_JWT_CLIENT_AUTH;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
|
||||
|
||||
@KeycloakIntegrationTest(config = OID4VCIssuerTestBase.VCTestServerConfig.class)
|
||||
public class OIDCAuthorizationMetadataTest extends OID4VCIssuerTestBase {
|
||||
|
||||
@Test
|
||||
public void testTokenEndpointAuthMethods() {
|
||||
OIDCConfigurationRepresentation oidcConfiguration = oauth.doWellKnownRequest();
|
||||
List<String> tokenAuthMethodsSupported = oidcConfiguration.getTokenEndpointAuthMethodsSupported();
|
||||
assertFalse(tokenAuthMethodsSupported.contains(ATTEST_JWT_CLIENT_AUTH), "Should not contain: " + ATTEST_JWT_CLIENT_AUTH);
|
||||
}
|
||||
}
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* 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.tests.oid4vc.abca;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.keycloak.protocol.oidc.representations.OIDCConfigurationRepresentation;
|
||||
import org.keycloak.testframework.annotations.KeycloakIntegrationTest;
|
||||
import org.keycloak.tests.oid4vc.OID4VCIssuerTestBase;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.keycloak.protocol.oidc.OIDCLoginProtocol.ATTEST_JWT_CLIENT_AUTH;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
@KeycloakIntegrationTest(config = OID4VCIssuerTestBase.VCTestServerWithABCAEnabled.class)
|
||||
public class OIDCAuthorizationMetadataABCATest extends OID4VCIssuerTestBase {
|
||||
|
||||
@Test
|
||||
public void testTokenEndpointAuthMethods() {
|
||||
OIDCConfigurationRepresentation oidcConfiguration = oauth.doWellKnownRequest();
|
||||
List<String> tokenAuthMethodsSupported = oidcConfiguration.getTokenEndpointAuthMethodsSupported();
|
||||
assertTrue(tokenAuthMethodsSupported.contains(ATTEST_JWT_CLIENT_AUTH), "Should contain: " + ATTEST_JWT_CLIENT_AUTH);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user