diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/AbstractTestRealmKeycloakTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/AbstractTestRealmKeycloakTest.java index 7f11e48ced..6e53e8a263 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/AbstractTestRealmKeycloakTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/AbstractTestRealmKeycloakTest.java @@ -93,6 +93,13 @@ public abstract class AbstractTestRealmKeycloakTest extends AbstractKeycloakTest protected IDToken sendTokenRequestAndGetIDToken(EventRepresentation loginEvent) { + + OAuthClient.AccessTokenResponse response = sendTokenRequestAndGetResponse(loginEvent); + return oauth.verifyIDToken(response.getIdToken()); + } + + protected OAuthClient.AccessTokenResponse sendTokenRequestAndGetResponse(EventRepresentation loginEvent) { + String sessionId = loginEvent.getSessionId(); String codeId = loginEvent.getDetails().get(Details.CODE_ID); @@ -100,14 +107,15 @@ public abstract class AbstractTestRealmKeycloakTest extends AbstractKeycloakTest OAuthClient.AccessTokenResponse response = oauth.doAccessTokenRequest(code, "password"); Assert.assertEquals(200, response.getStatusCode()); - IDToken idToken = oauth.verifyIDToken(response.getIdToken()); + Field eventsField = Reflections.findDeclaredField(this.getClass(), "events"); if (eventsField != null) { AssertEvents events = Reflections.getFieldValue(eventsField, this, AssertEvents.class); events.expectCodeToToken(codeId, sessionId).assertEvent(); } - return idToken; + + return response; } /** KEYCLOAK-12065 Inherit Client Connection from parent session **/ diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/flows/AbstractOIDCResponseTypeTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/flows/AbstractOIDCResponseTypeTest.java index 8576e77ed3..a90e8385e0 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/flows/AbstractOIDCResponseTypeTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/flows/AbstractOIDCResponseTypeTest.java @@ -28,6 +28,7 @@ import org.keycloak.events.Details; import org.keycloak.events.Errors; import org.keycloak.jose.jws.JWSHeader; import org.keycloak.jose.jws.JWSInput; +import org.keycloak.jose.jws.crypto.HashUtils; import org.keycloak.representations.IDToken; import org.keycloak.representations.idm.EventRepresentation; import org.keycloak.representations.idm.RealmRepresentation; @@ -253,11 +254,6 @@ public abstract class AbstractOIDCResponseTypeTest extends AbstractTestRealmKeyc for (IDToken idt : idTokens) { Assert.assertEquals("abcdef123456", idt.getNonce()); Assert.assertEquals(authzResponse.getSessionState(), idt.getSessionState()); - // see KEYCLOAK-9635 - if (authzResponse.getCode() != null && authzResponse.getAccessToken() != null) { - // we have an IDToken that was obtained via auth code flow alongside an AccessToken - Assert.assertNotNull("claim at_hash should be present in IDToken for OIDC auth code flow requests", idt.getAccessTokenHash()); - } } } @@ -302,4 +298,29 @@ public abstract class AbstractOIDCResponseTypeTest extends AbstractTestRealmKeyc protected String getIdTokenSignatureAlgorithm() { return this.idTokenSigAlgName; } + + /** + * Validate "at_hash" claim in IDToken. + * see KEYCLOAK-9635 + * @param accessTokenHash + * @param accessToken + */ + protected void assertValidAccessTokenHash(String accessTokenHash, String accessToken) { + + Assert.assertNotNull(accessTokenHash); + Assert.assertNotNull(accessToken); + assertEquals(accessTokenHash, HashUtils.oidcHash(getIdTokenSignatureAlgorithm(), accessToken)); + } + + /** + * Validate "c_hash" claim in IDToken. + * @param codeHash + * @param code + */ + protected void assertValidCodeHash(String codeHash, String code) { + + Assert.assertNotNull(codeHash); + Assert.assertNotNull(code); + Assert.assertEquals(codeHash, HashUtils.oidcHash(getIdTokenSignatureAlgorithm(), code)); + } } diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/flows/OIDCBasicResponseTypeCodeTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/flows/OIDCBasicResponseTypeCodeTest.java index d4b4f9e3a1..44ad850275 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/flows/OIDCBasicResponseTypeCodeTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/flows/OIDCBasicResponseTypeCodeTest.java @@ -57,9 +57,13 @@ public class OIDCBasicResponseTypeCodeTest extends AbstractOIDCResponseTypeTest Assert.assertNull(authzResponse.getAccessToken()); Assert.assertNull(authzResponse.getIdToken()); - IDToken idToken = sendTokenRequestAndGetIDToken(loginEvent); + OAuthClient.AccessTokenResponse authzResponse2 = sendTokenRequestAndGetResponse(loginEvent); + IDToken idToken2 = oauth.verifyIDToken(authzResponse2.getIdToken()); - return Collections.singletonList(idToken); + // Validate "at_hash" + assertValidAccessTokenHash(idToken2.getAccessTokenHash(), authzResponse2.getAccessToken()); + + return Collections.singletonList(idToken2); } diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/flows/OIDCHybridResponseTypeCodeIDTokenTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/flows/OIDCHybridResponseTypeCodeIDTokenTest.java index 2afc4c20ff..6000ce3850 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/flows/OIDCHybridResponseTypeCodeIDTokenTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/flows/OIDCHybridResponseTypeCodeIDTokenTest.java @@ -60,11 +60,11 @@ public class OIDCHybridResponseTypeCodeIDTokenTest extends AbstractOIDCResponseT String idTokenStr = authzResponse.getIdToken(); IDToken idToken = oauth.verifyIDToken(idTokenStr); - // Validate "c_hash" + // Validate "at_hash" Assert.assertNull(idToken.getAccessTokenHash()); - Assert.assertNotNull(idToken.getCodeHash()); - Assert.assertEquals(idToken.getCodeHash(), HashUtils.oidcHash(getIdTokenSignatureAlgorithm(), authzResponse.getCode())); + // Validate "c_hash" + assertValidCodeHash(idToken.getCodeHash(), authzResponse.getCode()); // Financial API - Part 2: Read and Write API Security Profile // http://openid.net/specs/openid-financial-api-part-2.html#authorization-server diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/flows/OIDCHybridResponseTypeCodeIDTokenTokenTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/flows/OIDCHybridResponseTypeCodeIDTokenTokenTest.java index 4f2f557c18..233e2bb84b 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/flows/OIDCHybridResponseTypeCodeIDTokenTokenTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/flows/OIDCHybridResponseTypeCodeIDTokenTokenTest.java @@ -60,13 +60,11 @@ public class OIDCHybridResponseTypeCodeIDTokenTokenTest extends AbstractOIDCResp String idTokenStr = authzResponse.getIdToken(); IDToken idToken = oauth.verifyIDToken(idTokenStr); - // Validate "at_hash" and "c_hash" - Assert.assertNotNull(idToken.getAccessTokenHash()); + // Validate "at_hash" + assertValidAccessTokenHash(idToken.getAccessTokenHash(), authzResponse.getAccessToken()); - Assert.assertEquals(idToken.getAccessTokenHash(), HashUtils.oidcHash(getIdTokenSignatureAlgorithm(), authzResponse.getAccessToken())); - Assert.assertNotNull(idToken.getCodeHash()); - - Assert.assertEquals(idToken.getCodeHash(), HashUtils.oidcHash(getIdTokenSignatureAlgorithm(), authzResponse.getCode())); + // Validate "c_hash" + assertValidCodeHash(idToken.getCodeHash(), authzResponse.getCode()); // Financial API - Part 2: Read and Write API Security Profile // http://openid.net/specs/openid-financial-api-part-2.html#authorization-server @@ -81,7 +79,6 @@ public class OIDCHybridResponseTypeCodeIDTokenTokenTest extends AbstractOIDCResp return Arrays.asList(idToken, idToken2); } - @Test public void nonceNotUsedErrorExpected() { super.validateNonceNotUsedErrorExpected(); diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/flows/OIDCHybridResponseTypeCodeTokenTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/flows/OIDCHybridResponseTypeCodeTokenTest.java index 13e43d265a..a7c7dca076 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/flows/OIDCHybridResponseTypeCodeTokenTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/flows/OIDCHybridResponseTypeCodeTokenTest.java @@ -58,7 +58,11 @@ public class OIDCHybridResponseTypeCodeTokenTest extends AbstractOIDCResponseTyp Assert.assertNull(authzResponse.getIdToken()); // IDToken exchanged for the code - IDToken idToken2 = sendTokenRequestAndGetIDToken(loginEvent); + OAuthClient.AccessTokenResponse authzResponse2 = sendTokenRequestAndGetResponse(loginEvent); + IDToken idToken2 = oauth.verifyIDToken(authzResponse2.getIdToken()); + + // Validate "at_hash" + assertValidAccessTokenHash(idToken2.getAccessTokenHash(), authzResponse2.getAccessToken()); return Collections.singletonList(idToken2); } diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/flows/OIDCImplicitResponseTypeIDTokenTokenTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/flows/OIDCImplicitResponseTypeIDTokenTokenTest.java index ec560ce2da..c397494f9c 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/flows/OIDCImplicitResponseTypeIDTokenTokenTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/flows/OIDCImplicitResponseTypeIDTokenTokenTest.java @@ -60,9 +60,9 @@ public class OIDCImplicitResponseTypeIDTokenTokenTest extends AbstractOIDCRespon IDToken idToken = oauth.verifyIDToken(idTokenStr); // Validate "at_hash" - Assert.assertNotNull(idToken.getAccessTokenHash()); + assertValidAccessTokenHash(idToken.getAccessTokenHash(), authzResponse.getAccessToken()); - Assert.assertEquals(idToken.getAccessTokenHash(), HashUtils.oidcHash(getIdTokenSignatureAlgorithm(), authzResponse.getAccessToken())); + // Validate "c_hash" Assert.assertNull(idToken.getCodeHash()); return Collections.singletonList(idToken);