KEYCLOAK-18828 FAPI-CIBA-ID1 conformance test : Additional checks of signed authentication request

This commit is contained in:
Takashi Norimatsu 2021-07-21 10:09:32 +09:00 committed by Marek Posolda
parent 61fcbb307b
commit 8df36fbf28
2 changed files with 154 additions and 0 deletions

View file

@ -17,6 +17,8 @@
package org.keycloak.protocol.oidc.grants.ciba.clientpolicy.executor;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import javax.ws.rs.core.MultivaluedMap;
@ -24,12 +26,14 @@ import javax.ws.rs.core.MultivaluedMap;
import org.jboss.logging.Logger;
import org.keycloak.OAuthErrorException;
import org.keycloak.common.util.Time;
import org.keycloak.models.ClientModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
import org.keycloak.protocol.oidc.grants.ciba.clientpolicy.context.BackchannelAuthenticationRequestContext;
import org.keycloak.protocol.oidc.grants.ciba.endpoints.request.BackchannelAuthenticationEndpointRequest;
import org.keycloak.protocol.oidc.grants.ciba.endpoints.request.BackchannelAuthenticationEndpointRequestParser;
import org.keycloak.representations.idm.ClientPolicyExecutorConfigurationRepresentation;
import org.keycloak.services.Urls;
import org.keycloak.services.clientpolicy.ClientPolicyContext;
import org.keycloak.services.clientpolicy.ClientPolicyException;
import org.keycloak.services.clientpolicy.executor.ClientPolicyExecutorProvider;
@ -163,6 +167,55 @@ public class SecureCibaSignedAuthenticationRequestExecutor implements ClientPoli
throw new ClientPolicyException(OAuthErrorException.INVALID_REQUEST, "signed authentication request's available period is long");
}
// check whether "aud" claim exists
List<String> aud = new ArrayList<String>();
JsonNode audience = signedAuthReq.get("aud");
if (audience == null) {
logger.trace("aud claim not incuded.");
throw new ClientPolicyException(OAuthErrorException.INVALID_REQUEST, "Missing parameter in the 'request' object: aud");
}
if (audience.isArray()) {
for (JsonNode node : audience) aud.add(node.asText());
} else {
aud.add(audience.asText());
}
if (aud.isEmpty()) {
logger.trace("aud claim not incuded.");
throw new ClientPolicyException(OAuthErrorException.INVALID_REQUEST, "Missing parameter value in the 'request' object: aud");
}
// check whether "aud" claim points to this keycloak as authz server
String authzServerIss = Urls.realmIssuer(session.getContext().getUri().getBaseUri(), session.getContext().getRealm().getName());
if (!aud.contains(authzServerIss)) {
logger.trace("aud not points to the intended realm.");
throw new ClientPolicyException(OAuthErrorException.INVALID_REQUEST, "Invalid parameter in the 'request' object: aud");
}
// check whether "iss" claim exists
if (signedAuthReq.get("iss") == null) {
logger.trace("iss claim not incuded.");
throw new ClientPolicyException(OAuthErrorException.INVALID_REQUEST, "Missing parameter in the 'request' object: iss");
}
ClientModel client = session.getContext().getClient();
String iss = signedAuthReq.get("iss").asText();
if (!iss.equals(client.getClientId())) {
logger.trace("iss claim not match client's identity.");
throw new ClientPolicyException(OAuthErrorException.INVALID_REQUEST, "Invalid parameter in the 'request' object: iss");
}
// check whether "iat" claim exists
if (signedAuthReq.get("iat") == null) {
logger.trace("iat claim not incuded.");
throw new ClientPolicyException(OAuthErrorException.INVALID_REQUEST, "Missing parameter in the signed authentication request: iat");
}
// check whether "jti" claim exists
if (signedAuthReq.get("jti") == null) {
logger.trace("jti claim not incuded.");
throw new ClientPolicyException(OAuthErrorException.INVALID_REQUEST, "Missing parameter in the signed authentication request: jti");
}
logger.trace("Passed.");
}

View file

@ -1302,11 +1302,112 @@ public class CIBATest extends AbstractClientPoliciesTest {
assertThat(response.getError(), is(OAuthErrorException.INVALID_REQUEST));
assertThat(response.getErrorDescription(), is("signed authentication request's available period is long"));
useRequestUri = true;
bindingMessage = "Flughafen-Wien-Schwechat";
requestObject = createPartialAuthorizationEndpointRequestObject(username, bindingMessage);
requestObject.exp(requestObject.getIat() + Long.valueOf(300));
requestObject.nbf(requestObject.getIat());
requestObject.audience(null);
registerSharedAuthenticationRequest(requestObject, TEST_CLIENT_NAME, sigAlg, useRequestUri);
// user Backchannel Authentication Request
response = oauth.doBackchannelAuthenticationRequest(TEST_CLIENT_NAME, TEST_CLIENT_PASSWORD, username, bindingMessage, null, null);
assertThat(response.getStatusCode(), is(equalTo(400)));
assertThat(response.getError(), is(OAuthErrorException.INVALID_REQUEST));
assertThat(response.getErrorDescription(), is("Missing parameter in the 'request' object: aud"));
useRequestUri = false;
bindingMessage = "Stuttgart-Hauptbahnhof";
requestObject = createPartialAuthorizationEndpointRequestObject(username, bindingMessage);
requestObject.exp(requestObject.getIat() + Long.valueOf(300));
requestObject.nbf(requestObject.getIat());
requestObject.audience("https://example.com");
registerSharedAuthenticationRequest(requestObject, TEST_CLIENT_NAME, sigAlg, useRequestUri);
// user Backchannel Authentication Request
response = oauth.doBackchannelAuthenticationRequest(TEST_CLIENT_NAME, TEST_CLIENT_PASSWORD, username, bindingMessage, null, null);
assertThat(response.getStatusCode(), is(equalTo(400)));
assertThat(response.getError(), is(OAuthErrorException.INVALID_REQUEST));
assertThat(response.getErrorDescription(), is("Invalid parameter in the 'request' object: aud"));
useRequestUri = true;
bindingMessage = "Flughafen-Wien-Schwechat";
requestObject = createPartialAuthorizationEndpointRequestObject(username, bindingMessage);
requestObject.exp(requestObject.getIat() + Long.valueOf(300));
requestObject.nbf(requestObject.getIat());
requestObject.audience(Urls.realmIssuer(new URI(suiteContext.getAuthServerInfo().getContextRoot().toString() + "/auth"), REALM_NAME), "https://example.com");
registerSharedAuthenticationRequest(requestObject, TEST_CLIENT_NAME, sigAlg, useRequestUri);
// user Backchannel Authentication Request
response = oauth.doBackchannelAuthenticationRequest(TEST_CLIENT_NAME, TEST_CLIENT_PASSWORD, username, bindingMessage, null, null);
assertThat(response.getStatusCode(), is(equalTo(400)));
assertThat(response.getError(), is(OAuthErrorException.INVALID_REQUEST));
assertThat(response.getErrorDescription(), is("Missing parameter in the 'request' object: iss"));
useRequestUri = false;
bindingMessage = "Stuttgart-Hauptbahnhof";
requestObject = createPartialAuthorizationEndpointRequestObject(username, bindingMessage);
requestObject.exp(requestObject.getIat() + Long.valueOf(300));
requestObject.nbf(requestObject.getIat());
requestObject.audience(Urls.realmIssuer(new URI(suiteContext.getAuthServerInfo().getContextRoot().toString() + "/auth"), REALM_NAME), "https://example.com");
requestObject.issuer(TEST_CLIENT_NAME + TEST_CLIENT_NAME);
registerSharedAuthenticationRequest(requestObject, TEST_CLIENT_NAME, sigAlg, useRequestUri);
// user Backchannel Authentication Request
response = oauth.doBackchannelAuthenticationRequest(TEST_CLIENT_NAME, TEST_CLIENT_PASSWORD, username, bindingMessage, null, null);
assertThat(response.getStatusCode(), is(equalTo(400)));
assertThat(response.getError(), is(OAuthErrorException.INVALID_REQUEST));
assertThat(response.getErrorDescription(), is("Invalid parameter in the 'request' object: iss"));
useRequestUri = true;
bindingMessage = "Flughafen-Wien-Schwechat";
requestObject = createPartialAuthorizationEndpointRequestObject(username, bindingMessage);
requestObject.exp(requestObject.getIat() + Long.valueOf(300));
requestObject.nbf(requestObject.getIat());
requestObject.audience(Urls.realmIssuer(new URI(suiteContext.getAuthServerInfo().getContextRoot().toString() + "/auth"), REALM_NAME), "https://example.com");
requestObject.issuer(TEST_CLIENT_NAME);
requestObject.iat(null);
requestObject.id(null);
registerSharedAuthenticationRequest(requestObject, TEST_CLIENT_NAME, sigAlg, useRequestUri);
// user Backchannel Authentication Request
response = oauth.doBackchannelAuthenticationRequest(TEST_CLIENT_NAME, TEST_CLIENT_PASSWORD, username, bindingMessage, null, null);
assertThat(response.getStatusCode(), is(equalTo(400)));
assertThat(response.getError(), is(OAuthErrorException.INVALID_REQUEST));
assertThat(response.getErrorDescription(), is("Missing parameter in the signed authentication request: iat"));
useRequestUri = false;
bindingMessage = "Stuttgart-Hauptbahnhof";
requestObject = createPartialAuthorizationEndpointRequestObject(username, bindingMessage);
requestObject.exp(requestObject.getIat() + Long.valueOf(300));
requestObject.nbf(requestObject.getIat());
requestObject.audience(Urls.realmIssuer(new URI(suiteContext.getAuthServerInfo().getContextRoot().toString() + "/auth"), REALM_NAME), "https://example.com");
requestObject.issuer(TEST_CLIENT_NAME);
requestObject.iat(Long.valueOf(Time.currentTime()));
requestObject.id(null);
registerSharedAuthenticationRequest(requestObject, TEST_CLIENT_NAME, sigAlg, useRequestUri);
// user Backchannel Authentication Request
response = oauth.doBackchannelAuthenticationRequest(TEST_CLIENT_NAME, TEST_CLIENT_PASSWORD, username, bindingMessage, null, null);
assertThat(response.getStatusCode(), is(equalTo(400)));
assertThat(response.getError(), is(OAuthErrorException.INVALID_REQUEST));
assertThat(response.getErrorDescription(), is("Missing parameter in the signed authentication request: jti"));
useRequestUri = true;
bindingMessage = "Brno-hlavni-nadrazif";
requestObject = createPartialAuthorizationEndpointRequestObject(username, bindingMessage);
requestObject.exp(requestObject.getIat() + Long.valueOf(300));
requestObject.nbf(requestObject.getIat());
requestObject.audience(Urls.realmIssuer(new URI(suiteContext.getAuthServerInfo().getContextRoot().toString() + "/auth"), REALM_NAME), "https://example.com");
requestObject.issuer(TEST_CLIENT_NAME);
requestObject.id(org.keycloak.models.utils.KeycloakModelUtils.generateId());
requestObject.iat(Long.valueOf(Time.currentTime()));
registerSharedAuthenticationRequest(requestObject, TEST_CLIENT_NAME, sigAlg, useRequestUri);