KEYCLOAK-9635 Add AccessTokenHash to IDToken for OIDC Auth Code flow

Revised tests
This commit is contained in:
Thomas Darimont 2020-05-26 23:55:22 +02:00 committed by Marek Posolda
parent 5a337d0376
commit e825ec24cb
7 changed files with 56 additions and 22 deletions

View file

@ -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 **/

View file

@ -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));
}
}

View file

@ -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);
}

View file

@ -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

View file

@ -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();

View file

@ -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);
}

View file

@ -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);