2

I have a JWT that looks like this (I had to hide some values):

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJhdWQiOiJ4eHgiLCJpc3MiOiJ4eHgiLCJpYXQiOjE2MTIzNDkwMTEsIm5iZiI6MCwiZXhwIjoxNjEyMzUyNjExLCJhdXRoX3RpbWUiOjE2MTEwNDU5MjgsIm5vbmNlIjoieHh4Iiwic3ViIjoieHh4IiwidXBuIjoieHh4IiwidW5pcXVlX25hbWUiOiJ4eHgiLCJwd2RfdXJsIjoieHh4IiwicHdkX2V4cCI6Inh4eCIsInNpZCI6Inh4eCIsImp0aSI6ImZmYWQ0NjM1LTU3MmItNGUyYi04ZGRhLTAxNmEzNDRlYzY4ZiJ9.nW5xTs6IbEkIFTZ_9PJZBpZAHXqG2HeU6y0XJwmQZiM

or simply:

{
 "typ": "JWT",
 "alg": "RS256",
 "x5t": "8Q3reRBv6jj6FyxBo5phA1yKzYg",
 "kid": "8Q3reRBv6jj6FyxBo5phA1yKzYg"
}

and

{
 "aud": "xxx",
 "iss": "xxx",
 "iat": 0,
 "nbf": 0,
 "exp": 1611049528,
 "auth_time": 1611045928,
 "nonce": "xxx",
 "sub": "xxx",
 "upn": "xxx",
 "unique_name": "xxx",
 "pwd_url": "xxx",
 "pwd_exp": "xxx",
 "sid": "xxx"
}

This is the code I wrote to verify it (starting from the examples in the website).

   @SneakyThrows
    public boolean isValid(String extractedToken) {
        log.info("Validating JWT");

        // Generate an RSA key pair, which will be used for signing and verification of the JWT, wrapped in a JWK
        RsaJsonWebKey rsaJsonWebKey = RsaJwkGenerator.generateJwk(2048);

        // Give the JWK a Key ID (kid), which is just the polite thing to do
        rsaJsonWebKey.setKeyId("8Q3reRBv6jj6FyxBo5phA1yKzYg");
        rsaJsonWebKey.setX509CertificateSha256Thumbprint("8Q3reRBv6jj6FyxBo5phA1yKzYg");

        // Use JwtConsumerBuilder to construct an appropriate JwtConsumer, which will
        // be used to validate and process the JWT.
        // The specific validation requirements for a JWT are context dependent, however,
        // it typically advisable to require a (reasonable) expiration time, a trusted issuer, and
        // and audience that identifies your system as the intended recipient.
        // If the JWT is encrypted too, you need only provide a decryption key or
        // decryption key resolver to the builder.
        JwtConsumer jwtConsumer = new JwtConsumerBuilder()
                .setRequireExpirationTime() // the JWT must have an expiration time
                .setAllowedClockSkewInSeconds(30) // allow some leeway in validating time based claims to account for clock skew
                .setVerificationKey(rsaJsonWebKey.getKey()) // verify the signature with the public key
                .setJwsAlgorithmConstraints( // only allow the expected signature algorithm(s) in the given context
                        AlgorithmConstraints.ConstraintType.PERMIT, AlgorithmIdentifiers.RSA_USING_SHA256) // which is only RS256 here
                .build(); // create the JwtConsumer instance

        try
        {
            //  Validate the JWT and process it to the Claims
            JwtClaims jwtClaims = jwtConsumer.processToClaims(extractedToken);
            System.out.println("JWT validation succeeded! " + jwtClaims);
            log.info("JTW validated");
            return true;
        }
        catch (InvalidJwtException e)
        {
            // InvalidJwtException will be thrown, if the JWT failed processing or validation in anyway.
            // Hopefully with meaningful explanations(s) about what went wrong.
            System.out.println("Invalid JWT! " + e);

            // Programmatic access to (some) specific reasons for JWT invalidity is also possible
            // should you want different error handling behavior for certain conditions.

            // Whether or not the JWT has expired being one common reason for invalidity
            if (e.hasExpired())
            {
                System.out.println("JWT expired at " + e.getJwtContext().getJwtClaims().getExpirationTime());
            }

            // Or maybe the audience was invalid
            if (e.hasErrorCode(ErrorCodes.AUDIENCE_INVALID))
            {
                System.out.println("JWT had wrong audience: " + e.getJwtContext().getJwtClaims().getAudience());
            }
        }

        log.info("JTW validated");
        return false;
    }

My objective is to verify only the signature and no other key-pair value at the moment.

However, when I run the code, I get:

Invalid JWT! org.jose4j.jwt.consumer.InvalidJwtSignatureException: JWT rejected due to invalid signature. Additional details: [[9] Invalid JWS Signature: JsonWebSignature{"typ":"JWT","alg":"RS256","x5t":"8Q3reRBv6jj6FyxBo5phA1yKzYg","kid":"8Q3reRBv6jj6FyxBo5phA1yKzYg"}->eyJ0eXAiOi.....]

And the token in the right part of the "->" is indeed my token.

So I have the suspicion that I'm not properly setting up the JWTConsumer, but I cannot see where the error is.

WORKING SOLUTION:

@SneakyThrows
public boolean isValid(String extractedToken) {
    log.info("Validating JWT");

    String pem = "MIIBIj........DAQAB";

    X509EncodedKeySpec pubKeySpec = new X509EncodedKeySpec(Base64.getMimeDecoder().decode(pem));
    KeyFactory keyFactory = KeyFactory.getInstance("RSA");
    PublicKey publicKey = keyFactory.generatePublic(pubKeySpec);

    // Use JwtConsumerBuilder to construct an appropriate JwtConsumer, which will
    // be used to validate and process the JWT.
    // The specific validation requirements for a JWT are context dependent, however,
    // it typically advisable to require a (reasonable) expiration time, a trusted issuer, and
    // and audience that identifies your system as the intended recipient.
    // If the JWT is encrypted too, you need only provide a decryption key or
    // decryption key resolver to the builder.
    JwtConsumer jwtConsumer = new JwtConsumerBuilder()
            .setVerificationKey(publicKey) // verify the signature with the public key
            .setJwsAlgorithmConstraints( // only allow the expected signature algorithm(s) in the given context
                    AlgorithmConstraints.ConstraintType.PERMIT, AlgorithmIdentifiers.RSA_USING_SHA256) // which is only RS256 here
            .setSkipAllDefaultValidators()
            .build(); // create the JwtConsumer instance

    try
    {
        //  Validate the JWT and process it to the Claims
        JwtClaims jwtClaims = jwtConsumer.processToClaims(extractedToken);
        System.out.println("JWT validation succeeded! " + jwtClaims);
        log.info("JTW validated");
        return true;
    }
    catch (InvalidJwtException e)
    {
        // InvalidJwtException will be thrown, if the JWT failed processing or validation in anyway.
        // Hopefully with meaningful explanations(s) about what went wrong.
        System.out.println("Invalid JWT! " + e);

        // Programmatic access to (some) specific reasons for JWT invalidity is also possible
        // should you want different error handling behavior for certain conditions.

        // Whether or not the JWT has expired being one common reason for invalidity
        if (e.hasExpired())
        {
            System.out.println("JWT expired at " + e.getJwtContext().getJwtClaims().getExpirationTime());
        }

        // Or maybe the audience was invalid
        if (e.hasErrorCode(ErrorCodes.AUDIENCE_INVALID))
        {
            System.out.println("JWT had wrong audience: " + e.getJwtContext().getJwtClaims().getAudience());
        }
    }

log.info("JTW validated");
return false;

}

  • I believe the key must be encoded in base64.. – aran Feb 03 '21 at 11:07
  • The aim of RsaJwkGenerator is to generate a random RSA key pair, so if you want de validate a JWT token. You have to sign this token with the RSA private key (see here https://bitbucket.org/b_c/jose4j/wiki/JWT%20Examples) The key id and the thumbprint are only there to link the correct public key to the token using JWK – vmargerin Feb 03 '21 at 11:44
  • @aran I tried to encode the string "8Q3reRBv6jj6FyxBo5phA1yKzYg" to base64 and base64url and paste the result into the code, but it doesn't work. Did I misunderstand you comment? – Capitano Giovarco Feb 03 '21 at 13:03
  • @vmargerin I used exactly that code as example. Could you maybe more specific and tell me what I should change? – Capitano Giovarco Feb 03 '21 at 13:05

1 Answers1

1

The extractedToken signature is coming from a different RSA pair than the one that is generated here:

RsaJsonWebKey rsaJsonWebKey = RsaJwkGenerator.generateJwk(2048);

So when you do that:

JwtConsumer jwtConsumer = new JwtConsumerBuilder()
            .setRequireExpirationTime() // the JWT must have an expiration time
            .setAllowedClockSkewInSeconds(30) // allow some leeway in validating time based claims to account for clock skew
            .setVerificationKey(rsaJsonWebKey.getKey()) // verify the signature with the public key
            .setJwsAlgorithmConstraints( // only allow the expected signature algorithm(s) in the given context
                    AlgorithmConstraints.ConstraintType.PERMIT, AlgorithmIdentifiers.RSA_USING_SHA256) // which is only RS256 here
            .build();

And then:

JwtClaims jwtClaims = jwtConsumer.processToClaims(extractedToken);

You are validating your token against an other public key (not the one which have been used originally)

In order to valid the signature of the JWT token, you have to keep the original RSA pair and then put the public key in the JWTConsumer like this:

.setVerificationKey(originalPublicKey)
vmargerin
  • 94
  • 2
  • Hi Vmargerin, thanks for the answer! I have the public key as a String tho and the code doesn't compile. How can I convert this to a Key properly? – Capitano Giovarco Feb 03 '21 at 13:52
  • It depends of how the String key has been encoded.The problem has been mentioned here: https://bitbucket.org/b_c/jose4j/issues/13/how-can-i-convert-a-publickey-string-into – vmargerin Feb 03 '21 at 14:12
  • Hey, thanks for the very precious hints. One more point before I mark this as resolved. Now I get two errors: expired and wrong audience. I haven't set any requirements in my code for those two checks tho. If I use .setSkipAllDefaultValidators(), do I also skip the validation of the signature too? – Capitano Giovarco Feb 03 '21 at 14:55
  • For reference: I ask this question to understand if my code is actually working or I'm skipping the signature check – Capitano Giovarco Feb 03 '21 at 14:58
  • If you don't expected specific audience, you can use setSkipDefaultAudienceValidation(). But it's generally recommended to validate audience is possible (using setExpectedAudience(audience)). I'm not totally sure but setSkipAllDefaultValidators should remove Claims validation (not signature) https://www.javadoc.io/static/org.bitbucket.b_c/jose4j/0.6.2/org/jose4j/jwt/consumer/JwtConsumerBuilder.html#setSkipAllDefaultValidators() – vmargerin Feb 03 '21 at 15:31
  • Access token lasts generally 1 hour, that's why it is expired. – vmargerin Feb 03 '21 at 15:43
  • For everyone's information: I actually tried to change the public key without changing the token and it indeed fails even when you use setSkipDefaultAudienceValidation(). For the rest, thanks for the help!! Will mark this as resolved and update the post with my last code version. – Capitano Giovarco Feb 04 '21 at 08:38