Search in sources :

Example 6 with OIDCAdvancedConfigWrapper

use of org.keycloak.protocol.oidc.OIDCAdvancedConfigWrapper in project keycloak by keycloak.

the class DescriptionConverter method toExternalResponse.

public static OIDCClientRepresentation toExternalResponse(KeycloakSession session, ClientRepresentation client, URI uri) {
    OIDCClientRepresentation response = new OIDCClientRepresentation();
    response.setClientId(client.getClientId());
    if ("none".equals(client.getClientAuthenticatorType())) {
        response.setTokenEndpointAuthMethod("none");
    } else {
        ClientAuthenticatorFactory clientAuth = (ClientAuthenticatorFactory) session.getKeycloakSessionFactory().getProviderFactory(ClientAuthenticator.class, client.getClientAuthenticatorType());
        Set<String> oidcClientAuthMethods = clientAuth.getProtocolAuthenticatorMethods(OIDCLoginProtocol.LOGIN_PROTOCOL);
        if (oidcClientAuthMethods != null && !oidcClientAuthMethods.isEmpty()) {
            response.setTokenEndpointAuthMethod(oidcClientAuthMethods.iterator().next());
        }
    }
    if (client.getClientAuthenticatorType().equals(ClientIdAndSecretAuthenticator.PROVIDER_ID)) {
        response.setClientSecret(client.getSecret());
        response.setClientSecretExpiresAt(0);
    }
    response.setClientName(client.getName());
    response.setClientUri(client.getBaseUrl());
    response.setRedirectUris(client.getRedirectUris());
    response.setRegistrationAccessToken(client.getRegistrationAccessToken());
    response.setRegistrationClientUri(uri.toString());
    response.setResponseTypes(getOIDCResponseTypes(client));
    response.setGrantTypes(getOIDCGrantTypes(client));
    List<String> scopes = client.getOptionalClientScopes();
    if (scopes != null)
        response.setScope(scopes.stream().collect(Collectors.joining(" ")));
    OIDCAdvancedConfigWrapper config = OIDCAdvancedConfigWrapper.fromClientRepresentation(client);
    if (config.isUserInfoSignatureRequired()) {
        response.setUserinfoSignedResponseAlg(config.getUserInfoSignedResponseAlg().toString());
    }
    if (config.getRequestObjectSignatureAlg() != null) {
        response.setRequestObjectSigningAlg(config.getRequestObjectSignatureAlg().toString());
    }
    if (config.getRequestObjectEncryptionAlg() != null) {
        response.setRequestObjectEncryptionAlg(config.getRequestObjectEncryptionAlg());
    }
    if (config.getRequestObjectEncryptionEnc() != null) {
        response.setRequestObjectEncryptionEnc(config.getRequestObjectEncryptionEnc());
    }
    if (config.isUseJwksUrl()) {
        response.setJwksUri(config.getJwksUrl());
    }
    if (config.isUseJwksString()) {
        try {
            response.setJwks(JsonSerialization.readValue(config.getJwksString(), JSONWebKeySet.class));
        } catch (IOException e) {
            throw new ClientRegistrationException("Illegal jwks format");
        }
    }
    // https://tools.ietf.org/html/draft-ietf-oauth-mtls-08#section-6.5
    if (config.isUseMtlsHokToken()) {
        response.setTlsClientCertificateBoundAccessTokens(Boolean.TRUE);
    } else {
        response.setTlsClientCertificateBoundAccessTokens(Boolean.FALSE);
    }
    if (config.getTlsClientAuthSubjectDn() != null) {
        response.setTlsClientAuthSubjectDn(config.getTlsClientAuthSubjectDn());
    }
    if (config.getIdTokenSignedResponseAlg() != null) {
        response.setIdTokenSignedResponseAlg(config.getIdTokenSignedResponseAlg());
    }
    if (config.getIdTokenEncryptedResponseAlg() != null) {
        response.setIdTokenEncryptedResponseAlg(config.getIdTokenEncryptedResponseAlg());
    }
    if (config.getIdTokenEncryptedResponseEnc() != null) {
        response.setIdTokenEncryptedResponseEnc(config.getIdTokenEncryptedResponseEnc());
    }
    if (config.getAuthorizationSignedResponseAlg() != null) {
        response.setAuthorizationSignedResponseAlg(config.getAuthorizationSignedResponseAlg());
    }
    if (config.getAuthorizationEncryptedResponseAlg() != null) {
        response.setAuthorizationEncryptedResponseAlg(config.getAuthorizationEncryptedResponseAlg());
    }
    if (config.getAuthorizationEncryptedResponseEnc() != null) {
        response.setAuthorizationEncryptedResponseEnc(config.getAuthorizationEncryptedResponseEnc());
    }
    if (config.getRequestUris() != null) {
        response.setRequestUris(config.getRequestUris());
    }
    if (config.getTokenEndpointAuthSigningAlg() != null) {
        response.setTokenEndpointAuthSigningAlg(config.getTokenEndpointAuthSigningAlg());
    }
    response.setBackchannelLogoutUri(config.getBackchannelLogoutUrl());
    response.setBackchannelLogoutSessionRequired(config.isBackchannelLogoutSessionRequired());
    response.setBackchannelLogoutSessionRequired(config.getBackchannelLogoutRevokeOfflineTokens());
    if (client.getAttributes() != null) {
        String mode = client.getAttributes().get(CibaConfig.CIBA_BACKCHANNEL_TOKEN_DELIVERY_MODE_PER_CLIENT);
        if (StringUtil.isNotBlank(mode)) {
            response.setBackchannelTokenDeliveryMode(mode);
        }
        String clientNotificationEndpoint = client.getAttributes().get(CibaConfig.CIBA_BACKCHANNEL_CLIENT_NOTIFICATION_ENDPOINT);
        if (StringUtil.isNotBlank(clientNotificationEndpoint)) {
            response.setBackchannelClientNotificationEndpoint(clientNotificationEndpoint);
        }
        String alg = client.getAttributes().get(CibaConfig.CIBA_BACKCHANNEL_AUTH_REQUEST_SIGNING_ALG);
        if (StringUtil.isNotBlank(alg)) {
            response.setBackchannelAuthenticationRequestSigningAlg(alg);
        }
        Boolean requirePushedAuthorizationRequests = Boolean.valueOf(client.getAttributes().get(ParConfig.REQUIRE_PUSHED_AUTHORIZATION_REQUESTS));
        response.setRequirePushedAuthorizationRequests(requirePushedAuthorizationRequests.booleanValue());
    }
    List<ProtocolMapperRepresentation> foundPairwiseMappers = PairwiseSubMapperUtils.getPairwiseSubMappers(client);
    SubjectType subjectType = foundPairwiseMappers.isEmpty() ? SubjectType.PUBLIC : SubjectType.PAIRWISE;
    response.setSubjectType(subjectType.toString().toLowerCase());
    if (subjectType.equals(SubjectType.PAIRWISE)) {
        // Get sectorIdentifier from 1st found
        String sectorIdentifierUri = PairwiseSubMapperHelper.getSectorIdentifierUri(foundPairwiseMappers.get(0));
        response.setSectorIdentifierUri(sectorIdentifierUri);
    }
    response.setFrontChannelLogoutUri(config.getFrontChannelLogoutUrl());
    List<String> defaultAcrValues = config.getAttributeMultivalued(Constants.DEFAULT_ACR_VALUES);
    if (!defaultAcrValues.isEmpty()) {
        response.setDefaultAcrValues(defaultAcrValues);
    }
    return response;
}
Also used : OIDCAdvancedConfigWrapper(org.keycloak.protocol.oidc.OIDCAdvancedConfigWrapper) JSONWebKeySet(org.keycloak.jose.jwk.JSONWebKeySet) IOException(java.io.IOException) SubjectType(org.keycloak.protocol.oidc.utils.SubjectType) OIDCClientRepresentation(org.keycloak.representations.oidc.OIDCClientRepresentation) ClientAuthenticatorFactory(org.keycloak.authentication.ClientAuthenticatorFactory) ProtocolMapperRepresentation(org.keycloak.representations.idm.ProtocolMapperRepresentation) JWTClientAuthenticator(org.keycloak.authentication.authenticators.client.JWTClientAuthenticator) X509ClientAuthenticator(org.keycloak.authentication.authenticators.client.X509ClientAuthenticator) ClientAuthenticator(org.keycloak.authentication.ClientAuthenticator) ClientRegistrationException(org.keycloak.services.clientregistration.ClientRegistrationException)

Example 7 with OIDCAdvancedConfigWrapper

use of org.keycloak.protocol.oidc.OIDCAdvancedConfigWrapper in project keycloak by keycloak.

the class DescriptionConverter method toInternal.

public static ClientRepresentation toInternal(KeycloakSession session, OIDCClientRepresentation clientOIDC) throws ClientRegistrationException {
    ClientRepresentation client = new ClientRepresentation();
    client.setClientId(clientOIDC.getClientId());
    client.setName(clientOIDC.getClientName());
    client.setRedirectUris(clientOIDC.getRedirectUris());
    client.setBaseUrl(clientOIDC.getClientUri());
    client.setProtocol(OIDCLoginProtocol.LOGIN_PROTOCOL);
    String scopeParam = clientOIDC.getScope();
    if (scopeParam != null)
        client.setOptionalClientScopes(new ArrayList<>(Arrays.asList(scopeParam.split(" "))));
    List<String> oidcResponseTypes = clientOIDC.getResponseTypes();
    if (oidcResponseTypes == null || oidcResponseTypes.isEmpty()) {
        oidcResponseTypes = Collections.singletonList(OIDCResponseType.CODE);
    }
    List<String> oidcGrantTypes = clientOIDC.getGrantTypes();
    try {
        OIDCResponseType responseType = OIDCResponseType.parse(oidcResponseTypes);
        client.setStandardFlowEnabled(responseType.hasResponseType(OIDCResponseType.CODE));
        client.setImplicitFlowEnabled(responseType.isImplicitOrHybridFlow());
        if (oidcGrantTypes != null) {
            client.setDirectAccessGrantsEnabled(oidcGrantTypes.contains(OAuth2Constants.PASSWORD));
            client.setServiceAccountsEnabled(oidcGrantTypes.contains(OAuth2Constants.CLIENT_CREDENTIALS));
            setOidcCibaGrantEnabled(client, oidcGrantTypes.contains(OAuth2Constants.CIBA_GRANT_TYPE));
        }
    } catch (IllegalArgumentException iae) {
        throw new ClientRegistrationException(iae.getMessage(), iae);
    }
    String authMethod = clientOIDC.getTokenEndpointAuthMethod();
    client.setPublicClient(Boolean.FALSE);
    if ("none".equals(authMethod)) {
        client.setClientAuthenticatorType("none");
        client.setPublicClient(Boolean.TRUE);
    } else {
        ClientAuthenticatorFactory clientAuthFactory;
        if (authMethod == null) {
            clientAuthFactory = (ClientAuthenticatorFactory) session.getKeycloakSessionFactory().getProviderFactory(ClientAuthenticator.class, KeycloakModelUtils.getDefaultClientAuthenticatorType());
        } else {
            clientAuthFactory = AuthorizeClientUtil.findClientAuthenticatorForOIDCAuthMethod(session, authMethod);
        }
        if (clientAuthFactory == null) {
            throw new ClientRegistrationException("Not found clientAuthenticator for requested token_endpoint_auth_method");
        }
        client.setClientAuthenticatorType(clientAuthFactory.getId());
    }
    boolean publicKeySet = setPublicKey(clientOIDC, client);
    if (authMethod != null && authMethod.equals(OIDCLoginProtocol.PRIVATE_KEY_JWT) && !publicKeySet) {
        throw new ClientRegistrationException("Didn't find key of supported keyType for use " + JWK.Use.SIG.asString());
    }
    OIDCAdvancedConfigWrapper configWrapper = OIDCAdvancedConfigWrapper.fromClientRepresentation(client);
    if (clientOIDC.getUserinfoSignedResponseAlg() != null) {
        Algorithm algorithm = Enum.valueOf(Algorithm.class, clientOIDC.getUserinfoSignedResponseAlg());
        configWrapper.setUserInfoSignedResponseAlg(algorithm);
    }
    if (clientOIDC.getRequestObjectSigningAlg() != null) {
        Algorithm algorithm = Enum.valueOf(Algorithm.class, clientOIDC.getRequestObjectSigningAlg());
        configWrapper.setRequestObjectSignatureAlg(algorithm);
    }
    // KEYCLOAK-6771 Certificate Bound Token
    // https://tools.ietf.org/html/draft-ietf-oauth-mtls-08#section-6.5
    Boolean tlsClientCertificateBoundAccessTokens = clientOIDC.getTlsClientCertificateBoundAccessTokens();
    if (tlsClientCertificateBoundAccessTokens != null) {
        if (tlsClientCertificateBoundAccessTokens.booleanValue())
            configWrapper.setUseMtlsHoKToken(true);
        else
            configWrapper.setUseMtlsHoKToken(false);
    }
    if (clientOIDC.getTlsClientAuthSubjectDn() != null) {
        configWrapper.setTlsClientAuthSubjectDn(clientOIDC.getTlsClientAuthSubjectDn());
        // According to specification, attribute tls_client_auth_subject_dn has subject DN in the exact expected format. There is no reason for support regex comparisons
        configWrapper.setAllowRegexPatternComparison(false);
    }
    if (clientOIDC.getIdTokenSignedResponseAlg() != null) {
        configWrapper.setIdTokenSignedResponseAlg(clientOIDC.getIdTokenSignedResponseAlg());
    }
    if (clientOIDC.getIdTokenEncryptedResponseAlg() != null) {
        configWrapper.setIdTokenEncryptedResponseAlg(clientOIDC.getIdTokenEncryptedResponseAlg());
    }
    if (clientOIDC.getIdTokenEncryptedResponseEnc() != null) {
        configWrapper.setIdTokenEncryptedResponseEnc(clientOIDC.getIdTokenEncryptedResponseEnc());
    }
    configWrapper.setAuthorizationSignedResponseAlg(clientOIDC.getAuthorizationSignedResponseAlg());
    configWrapper.setAuthorizationEncryptedResponseAlg(clientOIDC.getAuthorizationEncryptedResponseAlg());
    configWrapper.setAuthorizationEncryptedResponseEnc(clientOIDC.getAuthorizationEncryptedResponseEnc());
    if (clientOIDC.getRequestUris() != null) {
        configWrapper.setRequestUris(clientOIDC.getRequestUris());
    }
    configWrapper.setTokenEndpointAuthSigningAlg(clientOIDC.getTokenEndpointAuthSigningAlg());
    configWrapper.setBackchannelLogoutUrl(clientOIDC.getBackchannelLogoutUri());
    if (clientOIDC.getBackchannelLogoutSessionRequired() == null) {
        configWrapper.setBackchannelLogoutSessionRequired(true);
    } else {
        configWrapper.setBackchannelLogoutSessionRequired(clientOIDC.getBackchannelLogoutSessionRequired());
    }
    if (clientOIDC.getBackchannelLogoutRevokeOfflineTokens() == null) {
        configWrapper.setBackchannelLogoutRevokeOfflineTokens(false);
    } else {
        configWrapper.setBackchannelLogoutRevokeOfflineTokens(clientOIDC.getBackchannelLogoutRevokeOfflineTokens());
    }
    if (clientOIDC.getLogoUri() != null) {
        configWrapper.setLogoUri(clientOIDC.getLogoUri());
    }
    if (clientOIDC.getPolicyUri() != null) {
        configWrapper.setPolicyUri(clientOIDC.getPolicyUri());
    }
    if (clientOIDC.getTosUri() != null) {
        configWrapper.setTosUri(clientOIDC.getTosUri());
    }
    // CIBA
    String backchannelTokenDeliveryMode = clientOIDC.getBackchannelTokenDeliveryMode();
    if (backchannelTokenDeliveryMode != null) {
        Map<String, String> attr = Optional.ofNullable(client.getAttributes()).orElse(new HashMap<>());
        attr.put(CibaConfig.CIBA_BACKCHANNEL_TOKEN_DELIVERY_MODE_PER_CLIENT, backchannelTokenDeliveryMode);
        client.setAttributes(attr);
    }
    String backchannelClientNotificationEndpoint = clientOIDC.getBackchannelClientNotificationEndpoint();
    if (backchannelClientNotificationEndpoint != null) {
        Map<String, String> attr = Optional.ofNullable(client.getAttributes()).orElse(new HashMap<>());
        attr.put(CibaConfig.CIBA_BACKCHANNEL_CLIENT_NOTIFICATION_ENDPOINT, backchannelClientNotificationEndpoint);
        client.setAttributes(attr);
    }
    String backchannelAuthenticationRequestSigningAlg = clientOIDC.getBackchannelAuthenticationRequestSigningAlg();
    if (backchannelAuthenticationRequestSigningAlg != null) {
        Map<String, String> attr = Optional.ofNullable(client.getAttributes()).orElse(new HashMap<>());
        attr.put(CibaConfig.CIBA_BACKCHANNEL_AUTH_REQUEST_SIGNING_ALG, backchannelAuthenticationRequestSigningAlg);
        client.setAttributes(attr);
    }
    // PAR
    Boolean requirePushedAuthorizationRequests = clientOIDC.getRequirePushedAuthorizationRequests();
    if (requirePushedAuthorizationRequests != null) {
        Map<String, String> attr = Optional.ofNullable(client.getAttributes()).orElse(new HashMap<>());
        attr.put(ParConfig.REQUIRE_PUSHED_AUTHORIZATION_REQUESTS, requirePushedAuthorizationRequests.toString());
        client.setAttributes(attr);
    }
    configWrapper.setFrontChannelLogoutUrl(Optional.ofNullable(clientOIDC.getFrontChannelLogoutUri()).orElse(null));
    if (clientOIDC.getDefaultAcrValues() != null) {
        configWrapper.setAttributeMultivalued(Constants.DEFAULT_ACR_VALUES, clientOIDC.getDefaultAcrValues());
    }
    return client;
}
Also used : OIDCAdvancedConfigWrapper(org.keycloak.protocol.oidc.OIDCAdvancedConfigWrapper) ArrayList(java.util.ArrayList) OIDCResponseType(org.keycloak.protocol.oidc.utils.OIDCResponseType) Algorithm(org.keycloak.jose.jws.Algorithm) OIDCClientRepresentation(org.keycloak.representations.oidc.OIDCClientRepresentation) ClientRepresentation(org.keycloak.representations.idm.ClientRepresentation) ClientAuthenticatorFactory(org.keycloak.authentication.ClientAuthenticatorFactory) ClientRegistrationException(org.keycloak.services.clientregistration.ClientRegistrationException)

Example 8 with OIDCAdvancedConfigWrapper

use of org.keycloak.protocol.oidc.OIDCAdvancedConfigWrapper in project keycloak by keycloak.

the class DescriptionConverter method setPublicKey.

private static boolean setPublicKey(OIDCClientRepresentation clientOIDC, ClientRepresentation clientRep) {
    OIDCAdvancedConfigWrapper configWrapper = OIDCAdvancedConfigWrapper.fromClientRepresentation(clientRep);
    if (clientOIDC.getJwks() != null) {
        if (clientOIDC.getJwksUri() != null) {
            throw new ClientRegistrationException("Illegal to use both jwks_uri and jwks");
        }
        JSONWebKeySet keySet = clientOIDC.getJwks();
        JWK publicKeyJWk = JWKSUtils.getKeyForUse(keySet, JWK.Use.SIG);
        try {
            configWrapper.setJwksString(JsonSerialization.writeValueAsPrettyString(clientOIDC.getJwks()));
        } catch (IOException e) {
            throw new ClientRegistrationException("Illegal jwks format");
        }
        configWrapper.setUseJwksString(true);
        configWrapper.setUseJwksUrl(false);
        if (publicKeyJWk == null) {
            return false;
        }
        PublicKey publicKey = JWKParser.create(publicKeyJWk).toPublicKey();
        String publicKeyPem = KeycloakModelUtils.getPemFromKey(publicKey);
        CertificateRepresentation rep = new CertificateRepresentation();
        rep.setPublicKey(publicKeyPem);
        rep.setKid(publicKeyJWk.getKeyId());
        CertificateInfoHelper.updateClientRepresentationCertificateInfo(clientRep, rep, JWTClientAuthenticator.ATTR_PREFIX);
        return true;
    } else if (clientOIDC.getJwksUri() != null) {
        configWrapper.setUseJwksUrl(true);
        configWrapper.setJwksUrl(clientOIDC.getJwksUri());
        configWrapper.setUseJwksString(false);
        return true;
    }
    return false;
}
Also used : OIDCAdvancedConfigWrapper(org.keycloak.protocol.oidc.OIDCAdvancedConfigWrapper) JSONWebKeySet(org.keycloak.jose.jwk.JSONWebKeySet) PublicKey(java.security.PublicKey) CertificateRepresentation(org.keycloak.representations.idm.CertificateRepresentation) ClientRegistrationException(org.keycloak.services.clientregistration.ClientRegistrationException) IOException(java.io.IOException) JWK(org.keycloak.jose.jwk.JWK)

Example 9 with OIDCAdvancedConfigWrapper

use of org.keycloak.protocol.oidc.OIDCAdvancedConfigWrapper in project keycloak by keycloak.

the class OIDCBackwardsCompatibilityTest method testExcludeSessionStateParameter.

// KEYCLOAK-6286
@Test
public void testExcludeSessionStateParameter() {
    // Open login form and login successfully. Assert session_state is present
    OAuthClient.AuthorizationEndpointResponse authzResponse = oauth.doLogin("test-user@localhost", "password");
    EventRepresentation loginEvent = events.expectLogin().assertEvent();
    Assert.assertEquals(AppPage.RequestType.AUTH_RESPONSE, appPage.getRequestType());
    Assert.assertNotNull(authzResponse.getSessionState());
    // Switch "exclude session_state" to on
    ClientResource client = ApiUtil.findClientByClientId(adminClient.realm("test"), "test-app");
    ClientRepresentation clientRep = client.toRepresentation();
    OIDCAdvancedConfigWrapper config = OIDCAdvancedConfigWrapper.fromClientRepresentation(clientRep);
    config.setExcludeSessionStateFromAuthResponse(true);
    client.update(clientRep);
    // Open login again and assert session_state not present
    driver.navigate().to(oauth.getLoginFormUrl());
    org.keycloak.testsuite.Assert.assertEquals(AppPage.RequestType.AUTH_RESPONSE, appPage.getRequestType());
    loginEvent = events.expectLogin().detail(Details.USERNAME, "test-user@localhost").assertEvent();
    authzResponse = new OAuthClient.AuthorizationEndpointResponse(oauth);
    Assert.assertNull(authzResponse.getSessionState());
    // Revert
    config.setExcludeSessionStateFromAuthResponse(false);
    client.update(clientRep);
}
Also used : OAuthClient(org.keycloak.testsuite.util.OAuthClient) OIDCAdvancedConfigWrapper(org.keycloak.protocol.oidc.OIDCAdvancedConfigWrapper) EventRepresentation(org.keycloak.representations.idm.EventRepresentation) ClientResource(org.keycloak.admin.client.resource.ClientResource) ClientRepresentation(org.keycloak.representations.idm.ClientRepresentation) Test(org.junit.Test) AbstractTestRealmKeycloakTest(org.keycloak.testsuite.AbstractTestRealmKeycloakTest)

Example 10 with OIDCAdvancedConfigWrapper

use of org.keycloak.protocol.oidc.OIDCAdvancedConfigWrapper in project keycloak by keycloak.

the class X509ClientAuthenticator method authenticateClient.

@Override
public void authenticateClient(ClientAuthenticationFlowContext context) {
    X509ClientCertificateLookup provider = context.getSession().getProvider(X509ClientCertificateLookup.class);
    if (provider == null) {
        logger.errorv("\"{0}\" Spi is not available, did you forget to update the configuration?", X509ClientCertificateLookup.class);
        return;
    }
    X509Certificate[] certs = null;
    ClientModel client = null;
    try {
        certs = provider.getCertificateChain(context.getHttpRequest());
        String client_id = null;
        MediaType mediaType = context.getHttpRequest().getHttpHeaders().getMediaType();
        boolean hasFormData = mediaType != null && mediaType.isCompatible(MediaType.APPLICATION_FORM_URLENCODED_TYPE);
        MultivaluedMap<String, String> formData = hasFormData ? context.getHttpRequest().getDecodedFormParameters() : null;
        MultivaluedMap<String, String> queryParams = context.getSession().getContext().getUri().getQueryParameters();
        if (formData != null) {
            client_id = formData.getFirst(OAuth2Constants.CLIENT_ID);
        }
        if (client_id == null && queryParams != null) {
            client_id = queryParams.getFirst(OAuth2Constants.CLIENT_ID);
        }
        if (client_id == null) {
            client_id = context.getSession().getAttribute("client_id", String.class);
        }
        if (client_id == null) {
            Response challengeResponse = ClientAuthUtil.errorResponse(Response.Status.BAD_REQUEST.getStatusCode(), "invalid_client", "Missing client_id parameter");
            context.challenge(challengeResponse);
            return;
        }
        client = context.getRealm().getClientByClientId(client_id);
        if (client == null) {
            context.failure(AuthenticationFlowError.CLIENT_NOT_FOUND, null);
            return;
        }
        context.getEvent().client(client_id);
        context.setClient(client);
        if (!client.isEnabled()) {
            context.failure(AuthenticationFlowError.CLIENT_DISABLED, null);
            return;
        }
    } catch (GeneralSecurityException e) {
        logger.errorf("[X509ClientCertificateAuthenticator:authenticate] Exception: %s", e.getMessage());
        context.attempted();
        return;
    }
    if (certs == null || certs.length == 0) {
        // No x509 client cert, fall through and
        // continue processing the rest of the authentication flow
        logger.debug("[X509ClientCertificateAuthenticator:authenticate] x509 client certificate is not available for mutual SSL.");
        context.attempted();
        return;
    }
    OIDCAdvancedConfigWrapper clientCfg = OIDCAdvancedConfigWrapper.fromClientModel(client);
    String subjectDNRegexp = client.getAttribute(ATTR_SUBJECT_DN);
    if (subjectDNRegexp == null || subjectDNRegexp.length() == 0) {
        logger.errorf("[X509ClientCertificateAuthenticator:authenticate] " + ATTR_SUBJECT_DN + " is null or empty");
        context.attempted();
        return;
    }
    Optional<String> matchedCertificate;
    if (clientCfg.getAllowRegexPatternComparison()) {
        Pattern subjectDNPattern = Pattern.compile(subjectDNRegexp);
        matchedCertificate = Arrays.stream(certs).map(certificate -> certificate.getSubjectDN().getName()).filter(subjectdn -> subjectDNPattern.matcher(subjectdn).matches()).findFirst();
    } else {
        // OIDC/OAuth2 does not use regex comparison as it expects exact DN given in the format according to RFC4514. See RFC8705 for the details.
        // We allow custom OIDs attributes to be "expanded" or not expanded in the given Subject DN
        X500Principal expectedDNPrincipal = new X500Principal(subjectDNRegexp, CUSTOM_OIDS_REVERSED);
        matchedCertificate = Arrays.stream(certs).filter(certificate -> expectedDNPrincipal.getName(X500Principal.RFC2253, CUSTOM_OIDS).equals(certificate.getSubjectX500Principal().getName(X500Principal.RFC2253, CUSTOM_OIDS))).map(certificate -> certificate.getSubjectDN().getName()).findFirst();
    }
    if (!matchedCertificate.isPresent()) {
        // We do quite expensive operation here, so better check the logging level beforehand.
        if (logger.isDebugEnabled()) {
            logger.debug("[X509ClientCertificateAuthenticator:authenticate] Couldn't match any certificate for expected Subject DN '" + subjectDNRegexp + "' with allow regex pattern '" + clientCfg.getAllowRegexPatternComparison() + "'.");
            logger.debug("[X509ClientCertificateAuthenticator:authenticate] Available SubjectDNs: " + Arrays.stream(certs).map(cert -> cert.getSubjectDN().getName()).collect(Collectors.toList()));
        }
        context.attempted();
        return;
    } else {
        logger.debug("[X509ClientCertificateAuthenticator:authenticate] Matched " + matchedCertificate.get() + " certificate.");
    }
    context.success();
}
Also used : ClientModel(org.keycloak.models.ClientModel) X509Certificate(java.security.cert.X509Certificate) Arrays(java.util.Arrays) X500Principal(javax.security.auth.x500.X500Principal) ProviderConfigProperty(org.keycloak.provider.ProviderConfigProperty) HashMap(java.util.HashMap) ServicesLogger(org.keycloak.services.ServicesLogger) ClientAuthenticationFlowContext(org.keycloak.authentication.ClientAuthenticationFlowContext) Function(java.util.function.Function) HashSet(java.util.HashSet) MediaType(javax.ws.rs.core.MediaType) GeneralSecurityException(java.security.GeneralSecurityException) AuthenticationExecutionModel(org.keycloak.models.AuthenticationExecutionModel) OIDCAdvancedConfigWrapper(org.keycloak.protocol.oidc.OIDCAdvancedConfigWrapper) Map(java.util.Map) AuthenticationFlowError(org.keycloak.authentication.AuthenticationFlowError) Set(java.util.Set) Collectors(java.util.stream.Collectors) X509ClientCertificateLookup(org.keycloak.services.x509.X509ClientCertificateLookup) MultivaluedMap(javax.ws.rs.core.MultivaluedMap) List(java.util.List) Response(javax.ws.rs.core.Response) OIDCLoginProtocol(org.keycloak.protocol.oidc.OIDCLoginProtocol) Optional(java.util.Optional) Pattern(java.util.regex.Pattern) Collections(java.util.Collections) OAuth2Constants(org.keycloak.OAuth2Constants) Pattern(java.util.regex.Pattern) OIDCAdvancedConfigWrapper(org.keycloak.protocol.oidc.OIDCAdvancedConfigWrapper) GeneralSecurityException(java.security.GeneralSecurityException) X509ClientCertificateLookup(org.keycloak.services.x509.X509ClientCertificateLookup) X509Certificate(java.security.cert.X509Certificate) Response(javax.ws.rs.core.Response) ClientModel(org.keycloak.models.ClientModel) MediaType(javax.ws.rs.core.MediaType) X500Principal(javax.security.auth.x500.X500Principal)

Aggregations

OIDCAdvancedConfigWrapper (org.keycloak.protocol.oidc.OIDCAdvancedConfigWrapper)33 ClientRepresentation (org.keycloak.representations.idm.ClientRepresentation)27 Test (org.junit.Test)22 OIDCClientRepresentation (org.keycloak.representations.oidc.OIDCClientRepresentation)17 ClientResource (org.keycloak.admin.client.resource.ClientResource)7 Matchers.containsString (org.hamcrest.Matchers.containsString)5 ClientPolicyException (org.keycloak.services.clientpolicy.ClientPolicyException)5 OAuthClient (org.keycloak.testsuite.util.OAuthClient)4 JSONWebKeySet (org.keycloak.jose.jwk.JSONWebKeySet)3 ClientRegistrationException (org.keycloak.services.clientregistration.ClientRegistrationException)3 AuthenticationRequestAcknowledgement (org.keycloak.testsuite.util.OAuthClient.AuthenticationRequestAcknowledgement)3 IOException (java.io.IOException)2 Response (javax.ws.rs.core.Response)2 ClientAuthenticatorFactory (org.keycloak.authentication.ClientAuthenticatorFactory)2 ClientModel (org.keycloak.models.ClientModel)2 AccessToken (org.keycloak.representations.AccessToken)2 CertificateRepresentation (org.keycloak.representations.idm.CertificateRepresentation)2 AuthorizationEndpointRequestObject (org.keycloak.testsuite.rest.resource.TestingOIDCEndpointsApplicationResource.AuthorizationEndpointRequestObject)2 GeneralSecurityException (java.security.GeneralSecurityException)1 PublicKey (java.security.PublicKey)1