PKCE should return error if code_verifier sent but no code_challenge in the authorization request

Closes #26430

Signed-off-by: rmartinc <rmartinc@redhat.com>
This commit is contained in:
rmartinc 2024-02-05 15:43:32 +01:00 committed by Pedro Igor
parent 5401c63c9e
commit 720c5c6576
3 changed files with 35 additions and 1 deletions

View file

@ -84,6 +84,12 @@ public class PkceUtils {
throw new CorsErrorResponseException(cors, OAuthErrorException.INVALID_GRANT, "PKCE code verifier not specified", Response.Status.BAD_REQUEST);
}
if (codeChallenge == null && codeVerifier != null) {
logger.warnf("PKCE code verifier specified but challenge not present in authorization, authUserId = %s, authUsername = %s", authUserId, authUsername);
event.error(Errors.INVALID_CODE_VERIFIER);
throw new CorsErrorResponseException(cors, OAuthErrorException.INVALID_GRANT, "PKCE code verifier specified but challenge not present in authorization", Response.Status.BAD_REQUEST);
}
if (codeChallenge != null) {
verifyCodeVerifier(codeVerifier, codeChallenge, codeChallengeMethod, authUserId, authUsername, event, cors);
}

View file

@ -194,7 +194,9 @@ public abstract class AbstractFAPITest extends AbstractClientPoliciesTest {
List<NameValuePair> parameters = new LinkedList<>();
parameters.add(new BasicNameValuePair(OAuth2Constants.GRANT_TYPE, OAuth2Constants.AUTHORIZATION_CODE));
parameters.add(new BasicNameValuePair(OAuth2Constants.CODE, code));
parameters.add(new BasicNameValuePair(OAuth2Constants.CODE_VERIFIER, codeVerifier));
if (codeVerifier != null) {
parameters.add(new BasicNameValuePair(OAuth2Constants.CODE_VERIFIER, codeVerifier));
}
parameters.add(new BasicNameValuePair(OAuth2Constants.REDIRECT_URI, oauth.getRedirectUri()));
parameters.add(new BasicNameValuePair(OAuth2Constants.CLIENT_ASSERTION_TYPE, OAuth2Constants.CLIENT_ASSERTION_TYPE_JWT));
parameters.add(new BasicNameValuePair(OAuth2Constants.CLIENT_ASSERTION, signedJwt));

View file

@ -405,6 +405,32 @@ public class OAuthProofKeyForCodeExchangeTest extends AbstractKeycloakTest {
events.expectCodeToToken(codeId, sessionId).error(Errors.INVALID_CODE_VERIFIER).clearDetails().assertEvent();
}
@Test
public void accessTokenRequestInPKCECodeVerifierWithNoCodeChallenge() throws Exception {
String codeVerifier = "12345678e01234567890g2345678h012a4567j90123"; // 43
// send oauth request without code_challenge because intercepted
oauth.codeChallenge(null);
oauth.codeChallengeMethod(null);
oauth.doLogin("test-user@localhost", "password");
EventRepresentation loginEvent = events.expectLogin().assertEvent();
String sessionId = loginEvent.getSessionId();
String codeId = loginEvent.getDetails().get(Details.CODE_ID);
// get the code and add codeVerifier
String code = oauth.getCurrentQuery().get(OAuth2Constants.CODE);
oauth.codeVerifier(codeVerifier);
OAuthClient.AccessTokenResponse response = oauth.doAccessTokenRequest(code, "password");
// assert invalid code because no challenge in authorization
assertEquals(400, response.getStatusCode());
assertEquals(OAuthErrorException.INVALID_GRANT, response.getError());
assertEquals("PKCE code verifier specified but challenge not present in authorization", response.getErrorDescription());
events.expectCodeToToken(codeId, sessionId).error(Errors.INVALID_CODE_VERIFIER).clearDetails().assertEvent();
}
private String generateS256CodeChallenge(String codeVerifier) throws Exception {
MessageDigest md = MessageDigest.getInstance("SHA-256");
md.update(codeVerifier.getBytes("ISO_8859_1"));