ndtreviv
ndtreviv

Reputation: 3624

Correctly compare code_verifier with code_challenge in Java

I'm using passport-oauth2 (passportjs.org and https://github.com/jaredhanson/passport-oauth2/blob/master/lib/strategy.js) for OAuth2+PKCE integration in a nodejs application.

The backend it's authenticating against is written in Java.

The problem is that I can't seem to decode->hash the code_verifier to correctly match the code_challenge that comes from passport-oauth2.

I know that the Base64 encoding that comes from passport has been generated to be URL safe (no padding, no wrapping, replacements for + or /), so I'm using a Url Decoder:

Base64.getUrlDecoder().decode(...)

Then I'm using commons DigestUtils to generate a SHA256 of the decoded verifier and comparing it with the challenge. So the whole thing looks something like this:

    java.util.Base64.Decoder decoder = java.util.Base64.getUrlDecoder();
    String codeChallenge = // get the code challenge from my cache
    byte[] decodedCodeChallenge = decoder.decode(codeChallenge);
    byte[] decodedCodeVerifier = decoder.decode(codeVerifier);
    if (!Arrays.equals(sha256(decodedCodeVerifier), decodedCodeChallenge)) {
        return Response.status(400).entity(ERROR_INVALID_CHALLENGE_VERIFIER).build();
    }

Example:

This code verifier: 5CFCAiZC0g0OA-jmBmmjTBZiyPCQsnq_2q5k9fD-aAY should match this code challenge: Fw7s3XHRVb2m1nT7s646UrYiYLMJ54as0ZIU_injyqw once both have been Base64-url-decoded and the verifier has been SHA256 hashed, but it doesn't.

What am I doing wrong?

Upvotes: 3

Views: 4601

Answers (1)

ndtreviv
ndtreviv

Reputation: 3624

Just 5 minutes later I figured it out.

In passport-oauth2, the code verifier is Base64-url-encoded(random bytes):

verifier = base64url(crypto.pseudoRandomBytes(32))

See: https://github.com/jaredhanson/passport-oauth2/blob/master/lib/strategy.js#L236

The challenge is then Base64-url-encoded(sha256(verifier)), which expands to Base64-url-encoded(sha256(Base64-url-encoded(random bytes))):

challenge = base64url(crypto.createHash('sha256').update(verifier).digest());

See: https://github.com/jaredhanson/passport-oauth2/blob/master/lib/strategy.js#L242

So to do the verification, I don't need to decode anything. It was sha256-d in it's encoded state.

This worked in the end:

    java.util.Base64.Encoder encoder = java.util.Base64.getUrlEncoder();
    String codeChallenge = // get code challenge from my cache;
    String encodedVerifier = new String(encoder.encode(sha256(codeVerifier))).split("=")[0]; // Remember to remove padding
    if (!encodedVerifier.equals(codeChallenge)) {
        return Response.status(400).entity(ERROR_INVALID_CHALLENGE_VERIFIER).build();
    }

Upvotes: 1

Related Questions