javajose4j

Unexpected "InvalidJwtSignatureException: JWT rejected due to invalid signature"


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;

}


Solution

  • 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)