Merge pull request #3115 from mposolda/master

KEYCLOAK-2169 KEYCLOAK-3286 Support for at_hash and c_hash
This commit is contained in:
Marek Posolda 2016-08-08 12:36:33 +02:00 committed by GitHub
commit 64c2077c0b
14 changed files with 471 additions and 93 deletions

View file

@ -108,43 +108,29 @@ public class JWSBuilder {
return encodeAll(buffer, null); return encodeAll(buffer, null);
} }
public String rsa256(PrivateKey privateKey) { public String sign(Algorithm algorithm, PrivateKey privateKey) {
StringBuffer buffer = new StringBuffer(); StringBuffer buffer = new StringBuffer();
byte[] data = marshalContent(); byte[] data = marshalContent();
encode(Algorithm.RS256, data, buffer); encode(algorithm, data, buffer);
byte[] signature = null; byte[] signature = null;
try { try {
signature = RSAProvider.sign(buffer.toString().getBytes("UTF-8"), Algorithm.RS256, privateKey); signature = RSAProvider.sign(buffer.toString().getBytes("UTF-8"), algorithm, privateKey);
} catch (UnsupportedEncodingException e) { } catch (UnsupportedEncodingException e) {
throw new RuntimeException(e); throw new RuntimeException(e);
} }
return encodeAll(buffer, signature); return encodeAll(buffer, signature);
} }
public String rsa256(PrivateKey privateKey) {
return sign(Algorithm.RS256, privateKey);
}
public String rsa384(PrivateKey privateKey) { public String rsa384(PrivateKey privateKey) {
StringBuffer buffer = new StringBuffer(); return sign(Algorithm.RS384, privateKey);
byte[] data = marshalContent();
encode(Algorithm.RS384, data, buffer);
byte[] signature = null;
try {
signature = RSAProvider.sign(buffer.toString().getBytes("UTF-8"), Algorithm.RS384, privateKey);
} catch (UnsupportedEncodingException e) {
throw new RuntimeException(e);
}
return encodeAll(buffer, signature);
} }
public String rsa512(PrivateKey privateKey) { public String rsa512(PrivateKey privateKey) {
StringBuffer buffer = new StringBuffer(); return sign(Algorithm.RS512, privateKey);
byte[] data = marshalContent();
encode(Algorithm.RS512, data, buffer);
byte[] signature = null;
try {
signature = RSAProvider.sign(buffer.toString().getBytes("UTF-8"), Algorithm.RS512, privateKey);
} catch (UnsupportedEncodingException e) {
throw new RuntimeException(e);
}
return encodeAll(buffer, signature);
} }

View file

@ -0,0 +1,66 @@
/*
* Copyright 2016 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.jose.jws.crypto;
import java.security.MessageDigest;
import java.util.Arrays;
import org.keycloak.common.util.Base64Url;
import org.keycloak.jose.jws.Algorithm;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class HashProvider {
// See "at_hash" and "c_hash" in OIDC specification
public static String oidcHash(Algorithm jwtAlgorithm, String input) {
byte[] digest = digest(jwtAlgorithm, input);
int hashLength = digest.length / 2;
byte[] hashInput = Arrays.copyOf(digest, hashLength);
return Base64Url.encode(hashInput);
}
private static byte[] digest(Algorithm algorithm, String input) {
String digestAlg = getJavaDigestAlgorithm(algorithm);
try {
MessageDigest md = MessageDigest.getInstance(digestAlg);
md.update(input.getBytes("UTF-8"));
return md.digest();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
private static String getJavaDigestAlgorithm(Algorithm alg) {
switch (alg) {
case RS256:
return "SHA-256";
case RS384:
return "SHA-384";
case RS512:
return "SHA-512";
default:
throw new IllegalArgumentException("Not an RSA Algorithm");
}
}
}

View file

@ -27,6 +27,8 @@ public class IDToken extends JsonWebToken {
public static final String NONCE = "nonce"; public static final String NONCE = "nonce";
public static final String AUTH_TIME = "auth_time"; public static final String AUTH_TIME = "auth_time";
public static final String SESSION_STATE = "session_state"; public static final String SESSION_STATE = "session_state";
public static final String AT_HASH = "at_hash";
public static final String C_HASH = "c_hash";
public static final String NAME = "name"; public static final String NAME = "name";
public static final String GIVEN_NAME = "given_name"; public static final String GIVEN_NAME = "given_name";
public static final String FAMILY_NAME = "family_name"; public static final String FAMILY_NAME = "family_name";
@ -60,6 +62,12 @@ public class IDToken extends JsonWebToken {
@JsonProperty(SESSION_STATE) @JsonProperty(SESSION_STATE)
protected String sessionState; protected String sessionState;
@JsonProperty(AT_HASH)
protected String accessTokenHash;
@JsonProperty(C_HASH)
protected String codeHash;
@JsonProperty(NAME) @JsonProperty(NAME)
protected String name; protected String name;
@ -147,6 +155,22 @@ public class IDToken extends JsonWebToken {
this.sessionState = sessionState; this.sessionState = sessionState;
} }
public String getAccessTokenHash() {
return accessTokenHash;
}
public void setAccessTokenHash(String accessTokenHash) {
this.accessTokenHash = accessTokenHash;
}
public String getCodeHash() {
return codeHash;
}
public void setCodeHash(String codeHash) {
this.codeHash = codeHash;
}
public String getName() { public String getName() {
return this.name; return this.name;
} }

View file

@ -0,0 +1,49 @@
/*
* Copyright 2016 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;
import java.security.Security;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.junit.Assert;
import org.junit.Test;
import org.keycloak.jose.jws.Algorithm;
import org.keycloak.jose.jws.crypto.HashProvider;
/**
* See "at_hash" in OIDC specification
*
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class AtHashTest {
static {
if (Security.getProvider("BC") == null) Security.addProvider(new BouncyCastleProvider());
}
@Test
public void testAtHash() throws Exception {
verifyHash("jHkWEdUXMU1BwAsC4vtUsZwnNvTIxEl0z9K3vx5KF0Y", "77QmUPtjPfzWtF2AnpK9RQ");
verifyHash("ya29.eQETFbFOkAs8nWHcmYXKwEi0Zz46NfsrUU_KuQLOLTwWS40y6Fb99aVzEXC0U14m61lcPMIr1hEIBA", "aUAkJG-u6x4RTWuILWy-CA");
}
private void verifyHash(String accessToken, String expectedAtHash) {
String atHash = HashProvider.oidcHash(Algorithm.RS256, accessToken);
Assert.assertEquals(expectedAtHash, atHash);
}
}

View file

@ -165,10 +165,24 @@ public class OIDCLoginProtocol implements LoginProtocol {
// Implicit or hybrid flow // Implicit or hybrid flow
if (responseType.isImplicitOrHybridFlow()) { if (responseType.isImplicitOrHybridFlow()) {
TokenManager tokenManager = new TokenManager(); TokenManager tokenManager = new TokenManager();
AccessTokenResponse res = tokenManager.responseBuilder(realm, clientSession.getClient(), event, session, userSession, clientSession) TokenManager.AccessTokenResponseBuilder responseBuilder = tokenManager.responseBuilder(realm, clientSession.getClient(), event, session, userSession, clientSession)
.generateAccessToken() .generateAccessToken();
.generateIDToken()
.build(); if (responseType.hasResponseType(OIDCResponseType.ID_TOKEN)) {
responseBuilder.generateIDToken();
if (responseType.hasResponseType(OIDCResponseType.TOKEN)) {
responseBuilder.generateAccessTokenHash();
}
if (responseType.hasResponseType(OIDCResponseType.CODE)) {
responseBuilder.generateCodeHash(accessCode.getCode());
}
}
AccessTokenResponse res = responseBuilder.build();
if (responseType.hasResponseType(OIDCResponseType.ID_TOKEN)) { if (responseType.hasResponseType(OIDCResponseType.ID_TOKEN)) {
redirectUri.addParam(OAuth2Constants.ID_TOKEN, res.getIdToken()); redirectUri.addParam(OAuth2Constants.ID_TOKEN, res.getIdToken());

View file

@ -24,9 +24,11 @@ import org.keycloak.OAuthErrorException;
import org.keycloak.events.Details; import org.keycloak.events.Details;
import org.keycloak.events.Errors; import org.keycloak.events.Errors;
import org.keycloak.events.EventBuilder; import org.keycloak.events.EventBuilder;
import org.keycloak.jose.jws.Algorithm;
import org.keycloak.jose.jws.JWSBuilder; import org.keycloak.jose.jws.JWSBuilder;
import org.keycloak.jose.jws.JWSInput; import org.keycloak.jose.jws.JWSInput;
import org.keycloak.jose.jws.JWSInputException; import org.keycloak.jose.jws.JWSInputException;
import org.keycloak.jose.jws.crypto.HashProvider;
import org.keycloak.jose.jws.crypto.RSAProvider; import org.keycloak.jose.jws.crypto.RSAProvider;
import org.keycloak.models.ClientModel; import org.keycloak.models.ClientModel;
import org.keycloak.models.ClientSessionModel; import org.keycloak.models.ClientSessionModel;
@ -81,6 +83,9 @@ public class TokenManager {
protected static final ServicesLogger logger = ServicesLogger.ROOT_LOGGER; protected static final ServicesLogger logger = ServicesLogger.ROOT_LOGGER;
private static final String JWT = "JWT"; private static final String JWT = "JWT";
// Harcoded for now
Algorithm jwsAlgorithm = Algorithm.RS256;
public static void applyScope(RoleModel role, RoleModel scope, Set<RoleModel> visited, Set<RoleModel> requested) { public static void applyScope(RoleModel role, RoleModel scope, Set<RoleModel> visited, Set<RoleModel> requested) {
if (visited.contains(scope)) return; if (visited.contains(scope)) return;
visited.add(scope); visited.add(scope);
@ -619,7 +624,7 @@ public class TokenManager {
.type(JWT) .type(JWT)
.kid(realm.getKeyId()) .kid(realm.getKeyId())
.jsonContent(token) .jsonContent(token)
.rsa256(realm.getPrivateKey()); .sign(jwsAlgorithm, realm.getPrivateKey());
return encodedToken; return encodedToken;
} }
@ -639,6 +644,9 @@ public class TokenManager {
RefreshToken refreshToken; RefreshToken refreshToken;
IDToken idToken; IDToken idToken;
boolean generateAccessTokenHash = false;
String codeHash;
public AccessTokenResponseBuilder(RealmModel realm, ClientModel client, EventBuilder event, KeycloakSession session, UserSessionModel userSession, ClientSessionModel clientSession) { public AccessTokenResponseBuilder(RealmModel realm, ClientModel client, EventBuilder event, KeycloakSession session, UserSessionModel userSession, ClientSessionModel clientSession) {
this.realm = realm; this.realm = realm;
this.client = client; this.client = client;
@ -712,6 +720,15 @@ public class TokenManager {
return this; return this;
} }
public AccessTokenResponseBuilder generateAccessTokenHash() {
generateAccessTokenHash = true;
return this;
}
public AccessTokenResponseBuilder generateCodeHash(String code) {
codeHash = HashProvider.oidcHash(jwsAlgorithm, code);
return this;
}
public AccessTokenResponse build() { public AccessTokenResponse build() {
@ -729,12 +746,8 @@ public class TokenManager {
} }
AccessTokenResponse res = new AccessTokenResponse(); AccessTokenResponse res = new AccessTokenResponse();
if (idToken != null) {
String encodedToken = new JWSBuilder().type(JWT).kid(realm.getKeyId()).jsonContent(idToken).rsa256(realm.getPrivateKey());
res.setIdToken(encodedToken);
}
if (accessToken != null) { if (accessToken != null) {
String encodedToken = new JWSBuilder().type(JWT).kid(realm.getKeyId()).jsonContent(accessToken).rsa256(realm.getPrivateKey()); String encodedToken = new JWSBuilder().type(JWT).kid(realm.getKeyId()).jsonContent(accessToken).sign(jwsAlgorithm, realm.getPrivateKey());
res.setToken(encodedToken); res.setToken(encodedToken);
res.setTokenType("bearer"); res.setTokenType("bearer");
res.setSessionState(accessToken.getSessionState()); res.setSessionState(accessToken.getSessionState());
@ -742,8 +755,21 @@ public class TokenManager {
res.setExpiresIn(accessToken.getExpiration() - Time.currentTime()); res.setExpiresIn(accessToken.getExpiration() - Time.currentTime());
} }
} }
if (generateAccessTokenHash) {
String atHash = HashProvider.oidcHash(jwsAlgorithm, res.getToken());
idToken.setAccessTokenHash(atHash);
}
if (codeHash != null) {
idToken.setCodeHash(codeHash);
}
if (idToken != null) {
String encodedToken = new JWSBuilder().type(JWT).kid(realm.getKeyId()).jsonContent(idToken).sign(jwsAlgorithm, realm.getPrivateKey());
res.setIdToken(encodedToken);
}
if (refreshToken != null) { if (refreshToken != null) {
String encodedToken = new JWSBuilder().type(JWT).kid(realm.getKeyId()).jsonContent(refreshToken).rsa256(realm.getPrivateKey()); String encodedToken = new JWSBuilder().type(JWT).kid(realm.getKeyId()).jsonContent(refreshToken).sign(jwsAlgorithm, realm.getPrivateKey());
res.setRefreshToken(encodedToken); res.setRefreshToken(encodedToken);
if (refreshToken.getExpiration() != 0) { if (refreshToken.getExpiration() != 0) {
res.setRefreshExpiresIn(refreshToken.getExpiration() - Time.currentTime()); res.setRefreshExpiresIn(refreshToken.getExpiration() - Time.currentTime());

View file

@ -103,6 +103,8 @@ public class OAuthClient {
private String responseMode; private String responseMode;
private String nonce;
private Map<String, PublicKey> publicKeys = new HashMap<>(); private Map<String, PublicKey> publicKeys = new HashMap<>();
public void init(Keycloak adminClient, WebDriver driver) { public void init(Keycloak adminClient, WebDriver driver) {
@ -521,9 +523,12 @@ public class OAuthClient {
if (state != null) { if (state != null) {
b.queryParam(OAuth2Constants.STATE, state); b.queryParam(OAuth2Constants.STATE, state);
} }
if(uiLocales != null){ if (uiLocales != null){
b.queryParam(OAuth2Constants.UI_LOCALES_PARAM, uiLocales); b.queryParam(OAuth2Constants.UI_LOCALES_PARAM, uiLocales);
} }
if (nonce != null){
b.queryParam(OIDCLoginProtocol.NONCE_PARAM, nonce);
}
String scopeParam = TokenUtil.attachOIDCScope(scope); String scopeParam = TokenUtil.attachOIDCScope(scope);
b.queryParam(OAuth2Constants.SCOPE, scopeParam); b.queryParam(OAuth2Constants.SCOPE, scopeParam);
@ -634,6 +639,11 @@ public class OAuthClient {
return this; return this;
} }
public OAuthClient nonce(String nonce) {
this.nonce = nonce;
return this;
}
public String getRealm() { public String getRealm() {
return realm; return realm;
} }

View file

@ -15,14 +15,16 @@
* limitations under the License. * limitations under the License.
*/ */
package org.keycloak.testsuite.oidc.resptype; package org.keycloak.testsuite.oidc.flows;
import java.util.List; import java.util.List;
import org.jboss.arquillian.graphene.page.Page; import org.jboss.arquillian.graphene.page.Page;
import org.junit.Rule; import org.junit.Rule;
import org.junit.Test;
import org.keycloak.OAuthErrorException; import org.keycloak.OAuthErrorException;
import org.keycloak.events.Details; import org.keycloak.events.Details;
import org.keycloak.jose.jws.Algorithm;
import org.keycloak.representations.IDToken; import org.keycloak.representations.IDToken;
import org.keycloak.representations.idm.EventRepresentation; import org.keycloak.representations.idm.EventRepresentation;
import org.keycloak.representations.idm.RealmRepresentation; import org.keycloak.representations.idm.RealmRepresentation;
@ -30,10 +32,8 @@ import org.keycloak.testsuite.Assert;
import org.keycloak.testsuite.AssertEvents; import org.keycloak.testsuite.AssertEvents;
import org.keycloak.testsuite.TestRealmKeycloakTest; import org.keycloak.testsuite.TestRealmKeycloakTest;
import org.keycloak.testsuite.admin.AbstractAdminTest; import org.keycloak.testsuite.admin.AbstractAdminTest;
import org.keycloak.testsuite.pages.AccountUpdateProfilePage;
import org.keycloak.testsuite.pages.AppPage; import org.keycloak.testsuite.pages.AppPage;
import org.keycloak.testsuite.pages.LoginPage; import org.keycloak.testsuite.pages.LoginPage;
import org.keycloak.testsuite.pages.OAuthGrantPage;
import org.keycloak.testsuite.util.OAuthClient; import org.keycloak.testsuite.util.OAuthClient;
import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertFalse;
@ -46,6 +46,9 @@ import static org.junit.Assert.assertTrue;
*/ */
public abstract class AbstractOIDCResponseTypeTest extends TestRealmKeycloakTest { public abstract class AbstractOIDCResponseTypeTest extends TestRealmKeycloakTest {
// Harcoded for now
Algorithm jwsAlgorithm = Algorithm.RS256;
@Rule @Rule
public AssertEvents events = new AssertEvents(this); public AssertEvents events = new AssertEvents(this);
@ -55,12 +58,6 @@ public abstract class AbstractOIDCResponseTypeTest extends TestRealmKeycloakTest
@Page @Page
protected LoginPage loginPage; protected LoginPage loginPage;
@Page
protected AccountUpdateProfilePage profilePage;
@Page
protected OAuthGrantPage grantPage;
@Override @Override
public void configureTestRealm(RealmRepresentation testRealm) { public void configureTestRealm(RealmRepresentation testRealm) {
} }
@ -73,14 +70,9 @@ public abstract class AbstractOIDCResponseTypeTest extends TestRealmKeycloakTest
} }
protected void nonceMatches() { @Test
driver.navigate().to(oauth.getLoginFormUrl() + "&nonce=abcdef123456"); public void nonceMatches() {
EventRepresentation loginEvent = loginUser("abcdef123456");
loginPage.assertCurrent();
loginPage.login("test-user@localhost", "password");
Assert.assertEquals(AppPage.RequestType.AUTH_RESPONSE, appPage.getRequestType());
EventRepresentation loginEvent = events.expectLogin().detail(Details.USERNAME, "test-user@localhost").assertEvent();
List<IDToken> idTokens = retrieveIDTokens(loginEvent); List<IDToken> idTokens = retrieveIDTokens(loginEvent);
for (IDToken idToken : idTokens) { for (IDToken idToken : idTokens) {
@ -89,23 +81,8 @@ public abstract class AbstractOIDCResponseTypeTest extends TestRealmKeycloakTest
} }
protected void nonceNotUsed() { protected void validateNonceNotUsedErrorExpected() {
driver.navigate().to(oauth.getLoginFormUrl()); oauth.nonce(null);
loginPage.assertCurrent();
loginPage.login("test-user@localhost", "password");
Assert.assertEquals(AppPage.RequestType.AUTH_RESPONSE, appPage.getRequestType());
EventRepresentation loginEvent = events.expectLogin().detail(Details.USERNAME, "test-user@localhost").assertEvent();
List<IDToken> idTokens = retrieveIDTokens(loginEvent);
for (IDToken idToken : idTokens) {
Assert.assertNull(idToken.getNonce());
}
}
protected void nonceNotUsedErrorExpected() {
driver.navigate().to(oauth.getLoginFormUrl()); driver.navigate().to(oauth.getLoginFormUrl());
assertFalse(loginPage.isCurrent()); assertFalse(loginPage.isCurrent());
@ -119,5 +96,20 @@ public abstract class AbstractOIDCResponseTypeTest extends TestRealmKeycloakTest
Assert.assertEquals("Missing parameter: nonce", resp.getErrorDescription()); Assert.assertEquals("Missing parameter: nonce", resp.getErrorDescription());
} }
protected EventRepresentation loginUser(String nonce) {
if (nonce != null) {
oauth.nonce(nonce);
}
driver.navigate().to(oauth.getLoginFormUrl());
loginPage.assertCurrent();
loginPage.login("test-user@localhost", "password");
Assert.assertEquals(AppPage.RequestType.AUTH_RESPONSE, appPage.getRequestType());
return events.expectLogin().detail(Details.USERNAME, "test-user@localhost").assertEvent();
}
protected abstract List<IDToken> retrieveIDTokens(EventRepresentation loginEvent); protected abstract List<IDToken> retrieveIDTokens(EventRepresentation loginEvent);
} }

View file

@ -15,7 +15,7 @@
* limitations under the License. * limitations under the License.
*/ */
package org.keycloak.testsuite.oidc.resptype; package org.keycloak.testsuite.oidc.flows;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
@ -61,12 +61,11 @@ public class OIDCBasicResponseTypeCodeTest extends AbstractOIDCResponseTypeTest
@Test @Test
public void nonceNotUsed() { public void nonceNotUsed() {
super.nonceNotUsed(); EventRepresentation loginEvent = loginUser(null);
}
List<IDToken> idTokens = retrieveIDTokens(loginEvent);
@Test for (IDToken idToken : idTokens) {
public void nonceMatches() { Assert.assertNull(idToken.getNonce());
super.nonceMatches(); }
} }
} }

View file

@ -15,15 +15,15 @@
* limitations under the License. * limitations under the License.
*/ */
package org.keycloak.testsuite.oidc.resptype; package org.keycloak.testsuite.oidc.flows;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections;
import java.util.List; import java.util.List;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.keycloak.events.Details; import org.keycloak.events.Details;
import org.keycloak.jose.jws.crypto.HashProvider;
import org.keycloak.protocol.oidc.utils.OIDCResponseType; import org.keycloak.protocol.oidc.utils.OIDCResponseType;
import org.keycloak.representations.IDToken; import org.keycloak.representations.IDToken;
import org.keycloak.representations.idm.EventRepresentation; import org.keycloak.representations.idm.EventRepresentation;
@ -56,6 +56,11 @@ public class OIDCHybridResponseTypeCodeIDTokenTest extends AbstractOIDCResponseT
String idTokenStr = authzResponse.getIdToken(); String idTokenStr = authzResponse.getIdToken();
IDToken idToken = oauth.verifyIDToken(idTokenStr); IDToken idToken = oauth.verifyIDToken(idTokenStr);
// Validate "c_hash"
Assert.assertNull(idToken.getAccessTokenHash());
Assert.assertNotNull(idToken.getCodeHash());
Assert.assertEquals(idToken.getCodeHash(), HashProvider.oidcHash(jwsAlgorithm, authzResponse.getCode()));
// IDToken exchanged for the code // IDToken exchanged for the code
IDToken idToken2 = sendTokenRequestAndGetIDToken(loginEvent); IDToken idToken2 = sendTokenRequestAndGetIDToken(loginEvent);
@ -65,12 +70,6 @@ public class OIDCHybridResponseTypeCodeIDTokenTest extends AbstractOIDCResponseT
@Test @Test
public void nonceNotUsedErrorExpected() { public void nonceNotUsedErrorExpected() {
super.nonceNotUsedErrorExpected(); super.validateNonceNotUsedErrorExpected();
}
@Test
public void nonceMatches() {
super.nonceMatches();
} }
} }

View file

@ -0,0 +1,76 @@
/*
* Copyright 2016 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.testsuite.oidc.flows;
import java.util.Arrays;
import java.util.List;
import org.junit.Before;
import org.junit.Test;
import org.keycloak.events.Details;
import org.keycloak.jose.jws.crypto.HashProvider;
import org.keycloak.protocol.oidc.utils.OIDCResponseType;
import org.keycloak.representations.IDToken;
import org.keycloak.representations.idm.EventRepresentation;
import org.keycloak.testsuite.Assert;
import org.keycloak.testsuite.util.ClientManager;
import org.keycloak.testsuite.util.OAuthClient;
/**
* Tests with response_type=code id_token token
*
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class OIDCHybridResponseTypeCodeIDTokenTokenTest extends AbstractOIDCResponseTypeTest {
@Before
public void clientConfiguration() {
ClientManager.realm(adminClient.realm("test")).clientId("test-app").standardFlow(true).implicitFlow(true);
oauth.clientId("test-app");
oauth.responseType(OIDCResponseType.CODE + " " + OIDCResponseType.ID_TOKEN + " " + OIDCResponseType.TOKEN);
}
protected List<IDToken> retrieveIDTokens(EventRepresentation loginEvent) {
Assert.assertEquals(OIDCResponseType.CODE + " " + OIDCResponseType.ID_TOKEN + " " + OIDCResponseType.TOKEN, loginEvent.getDetails().get(Details.RESPONSE_TYPE));
// IDToken from the authorization response
OAuthClient.AuthorizationEndpointResponse authzResponse = new OAuthClient.AuthorizationEndpointResponse(oauth, true);
Assert.assertNotNull(authzResponse.getAccessToken());
String idTokenStr = authzResponse.getIdToken();
IDToken idToken = oauth.verifyIDToken(idTokenStr);
// Validate "at_hash" and "c_hash"
Assert.assertNotNull(idToken.getAccessTokenHash());
Assert.assertEquals(idToken.getAccessTokenHash(), HashProvider.oidcHash(jwsAlgorithm, authzResponse.getAccessToken()));
Assert.assertNotNull(idToken.getCodeHash());
Assert.assertEquals(idToken.getCodeHash(), HashProvider.oidcHash(jwsAlgorithm, authzResponse.getCode()));
// IDToken exchanged for the code
IDToken idToken2 = sendTokenRequestAndGetIDToken(loginEvent);
return Arrays.asList(idToken, idToken2);
}
@Test
public void nonceNotUsedErrorExpected() {
super.validateNonceNotUsedErrorExpected();
}
}

View file

@ -0,0 +1,69 @@
/*
* Copyright 2016 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.testsuite.oidc.flows;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import org.junit.Before;
import org.junit.Test;
import org.keycloak.events.Details;
import org.keycloak.protocol.oidc.utils.OIDCResponseType;
import org.keycloak.representations.IDToken;
import org.keycloak.representations.idm.EventRepresentation;
import org.keycloak.testsuite.Assert;
import org.keycloak.testsuite.util.ClientManager;
import org.keycloak.testsuite.util.OAuthClient;
/**
* Tests with response_type=code token
*
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class OIDCHybridResponseTypeCodeTokenTest extends AbstractOIDCResponseTypeTest {
@Before
public void clientConfiguration() {
ClientManager.realm(adminClient.realm("test")).clientId("test-app").standardFlow(true).implicitFlow(true);
oauth.clientId("test-app");
oauth.responseType(OIDCResponseType.CODE + " " + OIDCResponseType.TOKEN);
}
protected List<IDToken> retrieveIDTokens(EventRepresentation loginEvent) {
Assert.assertEquals(OIDCResponseType.CODE + " " + OIDCResponseType.TOKEN, loginEvent.getDetails().get(Details.RESPONSE_TYPE));
OAuthClient.AuthorizationEndpointResponse authzResponse = new OAuthClient.AuthorizationEndpointResponse(oauth, true);
Assert.assertNotNull(authzResponse.getAccessToken());
Assert.assertNull(authzResponse.getIdToken());
// IDToken exchanged for the code
IDToken idToken2 = sendTokenRequestAndGetIDToken(loginEvent);
return Collections.singletonList(idToken2);
}
@Test
public void nonceNotUsedErrorExpected() {
super.validateNonceNotUsedErrorExpected();
}
}

View file

@ -15,7 +15,7 @@
* limitations under the License. * limitations under the License.
*/ */
package org.keycloak.testsuite.oidc.resptype; package org.keycloak.testsuite.oidc.flows;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
@ -54,19 +54,16 @@ public class OIDCImplicitResponseTypeIDTokenTest extends AbstractOIDCResponseTyp
String idTokenStr = authzResponse.getIdToken(); String idTokenStr = authzResponse.getIdToken();
IDToken idToken = oauth.verifyIDToken(idTokenStr); IDToken idToken = oauth.verifyIDToken(idTokenStr);
Assert.assertNull(idToken.getAccessTokenHash());
Assert.assertNull(idToken.getCodeHash());
return Collections.singletonList(idToken); return Collections.singletonList(idToken);
} }
@Test @Test
public void nonceNotUsedErrorExpected() { public void nonceNotUsedErrorExpected() {
super.nonceNotUsedErrorExpected(); super.validateNonceNotUsedErrorExpected();
}
@Test
public void nonceMatches() {
super.nonceMatches();
} }
} }

View file

@ -0,0 +1,71 @@
/*
* Copyright 2016 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.testsuite.oidc.flows;
import java.util.Collections;
import java.util.List;
import org.junit.Before;
import org.junit.Test;
import org.keycloak.events.Details;
import org.keycloak.jose.jws.crypto.HashProvider;
import org.keycloak.protocol.oidc.utils.OIDCResponseType;
import org.keycloak.representations.IDToken;
import org.keycloak.representations.idm.EventRepresentation;
import org.keycloak.testsuite.Assert;
import org.keycloak.testsuite.util.ClientManager;
import org.keycloak.testsuite.util.OAuthClient;
/**
* Tests with response_type=id_token token
*
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class OIDCImplicitResponseTypeIDTokenTokenTest extends AbstractOIDCResponseTypeTest {
@Before
public void clientConfiguration() {
ClientManager.realm(adminClient.realm("test")).clientId("test-app").standardFlow(false).implicitFlow(true);
oauth.clientId("test-app");
oauth.responseType(OIDCResponseType.ID_TOKEN + " " + OIDCResponseType.TOKEN);
}
protected List<IDToken> retrieveIDTokens(EventRepresentation loginEvent) {
Assert.assertEquals(OIDCResponseType.ID_TOKEN + " " + OIDCResponseType.TOKEN, loginEvent.getDetails().get(Details.RESPONSE_TYPE));
OAuthClient.AuthorizationEndpointResponse authzResponse = new OAuthClient.AuthorizationEndpointResponse(oauth, true);
Assert.assertNotNull(authzResponse.getAccessToken());
String idTokenStr = authzResponse.getIdToken();
IDToken idToken = oauth.verifyIDToken(idTokenStr);
// Validate "at_hash"
Assert.assertNotNull(idToken.getAccessTokenHash());
Assert.assertEquals(idToken.getAccessTokenHash(), HashProvider.oidcHash(jwsAlgorithm, authzResponse.getAccessToken()));
Assert.assertNull(idToken.getCodeHash());
return Collections.singletonList(idToken);
}
@Test
public void nonceNotUsedErrorExpected() {
super.validateNonceNotUsedErrorExpected();
}
}