From 8c1ea4b47cec939b447a6bb6ba966fd5379a54e5 Mon Sep 17 00:00:00 2001 From: Takashi Norimatsu Date: Wed, 17 Aug 2022 12:55:06 +0900 Subject: [PATCH] mTLS binding support for password grant Closes #13662 --- .../oidc/endpoints/TokenEndpoint.java | 10 ++++-- .../org/keycloak/testsuite/hok/HoKTest.java | 31 +++++++++++++++++++ 2 files changed, 38 insertions(+), 3 deletions(-) diff --git a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/TokenEndpoint.java b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/TokenEndpoint.java index 6e612b5703..e147e7ec9f 100644 --- a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/TokenEndpoint.java +++ b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/TokenEndpoint.java @@ -440,11 +440,12 @@ public class TokenEndpoint { TokenManager.AccessTokenResponseBuilder responseBuilder = tokenManager .responseBuilder(realm, client, event, session, userSession, clientSessionCtx).accessToken(token); - if (OIDCAdvancedConfigWrapper.fromClientModel(client).isUseRefreshToken()) { + boolean useRefreshToken = OIDCAdvancedConfigWrapper.fromClientModel(client).isUseRefreshToken(); + if (useRefreshToken) { responseBuilder.generateRefreshToken(); } - checkMtlsHoKToken(responseBuilder, OIDCAdvancedConfigWrapper.fromClientModel(client).isUseRefreshToken()); + checkMtlsHoKToken(responseBuilder, useRefreshToken); if (TokenUtil.isOIDCRequest(scopeParam)) { responseBuilder.generateIDToken().generateAccessTokenHash(); @@ -627,7 +628,8 @@ public class TokenEndpoint { TokenManager.AccessTokenResponseBuilder responseBuilder = tokenManager .responseBuilder(realm, client, event, session, userSession, clientSessionCtx).generateAccessToken(); - if (OIDCAdvancedConfigWrapper.fromClientModel(client).isUseRefreshToken()) { + boolean useRefreshToken = OIDCAdvancedConfigWrapper.fromClientModel(client).isUseRefreshToken(); + if (useRefreshToken) { responseBuilder.generateRefreshToken(); } @@ -636,6 +638,8 @@ public class TokenEndpoint { responseBuilder.generateIDToken().generateAccessTokenHash(); } + checkMtlsHoKToken(responseBuilder, useRefreshToken); + // TODO : do the same as codeToToken() AccessTokenResponse res = responseBuilder.build(); diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/hok/HoKTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/hok/HoKTest.java index 3ce8116dda..b484ec12b5 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/hok/HoKTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/hok/HoKTest.java @@ -56,6 +56,7 @@ import org.keycloak.representations.idm.UserRepresentation; import org.keycloak.representations.oidc.TokenMetadataRepresentation; import org.keycloak.testsuite.AbstractKeycloakTest; import org.keycloak.testsuite.AbstractTestRealmKeycloakTest; +import org.keycloak.testsuite.Assert; import org.keycloak.testsuite.AssertEvents; import org.keycloak.testsuite.admin.ApiUtil; import org.keycloak.testsuite.drone.Different; @@ -138,6 +139,7 @@ public class HoKTest extends AbstractTestRealmKeycloakTest { ClientRepresentation serviceAccountApp = KeycloakModelUtils.createClient(testRealm, "service-account-client"); serviceAccountApp.setSecret("secret1"); serviceAccountApp.setServiceAccountsEnabled(Boolean.TRUE); + serviceAccountApp.setDirectAccessGrantsEnabled(Boolean.TRUE); ClientRepresentation pubApp = KeycloakModelUtils.createClient(testRealm, "public-cli"); pubApp.setPublicClient(Boolean.TRUE); @@ -670,6 +672,35 @@ public class HoKTest extends AbstractTestRealmKeycloakTest { } } + @Test + public void resourceOwnerPasswordCredentialsGrantWithClientCertificate() throws Exception { + oauth.clientId("service-account-client"); + + AccessTokenResponse response; + + Supplier previous = oauth.getHttpClient(); + + try { + // Request without HoK should fail + oauth.httpClient(MutualTLSUtils::newCloseableHttpClientWithoutKeyStoreAndTrustStore); + response = oauth.doGrantAccessTokenRequest("secret1", "test-user@localhost", "password", null); + assertEquals(400, response.getStatusCode()); + assertEquals(OAuthErrorException.INVALID_REQUEST, response.getError()); + assertEquals("Client Certification missing for MTLS HoK Token Binding", response.getErrorDescription()); + + // Request with HoK - success + oauth.httpClient(MutualTLSUtils::newCloseableHttpClientWithDefaultKeyStoreAndTrustStore); + response = oauth.doGrantAccessTokenRequest("secret1", "test-user@localhost", "password", null); + assertEquals(200, response.getStatusCode()); + + // Success Pattern + verifyHoKTokenCertThumbPrint(response, MutualTLSUtils.getThumbprintFromDefaultClientCert(), false); + } catch (IOException ioe) { + throw new RuntimeException(ioe); + } finally { + oauth.httpClient(previous); + } + } private void verifyHoKTokenDefaultCertThumbPrint(AccessTokenResponse response) throws Exception { verifyHoKTokenCertThumbPrint(response, MutualTLSUtils.getThumbprintFromDefaultClientCert(), true);