Skip to content

NimbusJwtEncoder produces JWT with wrong "typ" header value #18269

@ziqin

Description

@ziqin

Describe the bug

In Spring Security 7, a NimbusJwtEncoder constructed by private NimbusJwtEncoder(JWK jwk) produces Jwt objects with incorrent "typ" parameter values in headers. The "typ" header parameter should declare the media type of the complete JWT and the value should be "JWT" in most cases, as is defined in RFC 7519. However, the value emitted by NimbusJwtEncoder is acutally the type of the signing JWK, i.e. the "kty" value defined in RFC 7517, whose common values include "RSA", "EC", "oct", etc.

Such an incorrect "typ" value is rejected by JwtTypeValidator.jwt(), which is employed by NimbusJwtDeocder by default in Spring Security 7, causing a JWT verification error.

This bug was introduced in Line 139 below, where jwk.getKeyType() was passed to JwsHeader.Builder::type(String type):

private NimbusJwtEncoder(JWK jwk) {
Assert.notNull(jwk, "jwk cannot be null");
this.jwkSource = new ImmutableJWKSet<>(new JWKSet(jwk));
JwsAlgorithm algorithm = SignatureAlgorithm.from(jwk.getAlgorithm().getName());
if (algorithm == null) {
algorithm = MacAlgorithm.from(jwk.getAlgorithm().getName());
}
Assert.notNull(algorithm, "Failed to derive supported algorithm from " + jwk.getAlgorithm());
JwsHeader.Builder builder = JwsHeader.with(algorithm).type(jwk.getKeyType().getValue()).keyId(jwk.getKeyID());

To Reproduce

  1. Create a NimbusJwtEncoder instance using SecretKeyJwtEncoderBuilder (You may also use RsaKeyPairJwtEncoderBuilder or EcKeyPairJwtEncoderBuilder, as long as the private constructor is invoked):

    SecretKey secretKey = KeyGenerator.getInstance("HmacSHA256").generateKey();
    NimbusJwtEncoder jwtEncoder = NimbusJwtEncoder.withSecretkey(secretKey).build();
  2. Encode a JWT with the NimbusJwtEncoder instance:

    JwtClaimsSet claims = TestJwtClaimsSets.jwtClaimsSet().build();
    Jwt jwt = jwtEncoder.encode(JwtEncoderParamaters.from(claims));
  3. Print out the "typ" parameter value, and you'll see oct —— the "kty" of a HS256 JWK.

    System.out.println(jwt.getHeaders().get(JoseHeaderNames.TYP));  // It prints: oct

Expected behavior

The expected value of the "typ" header parameter is often "JWT". If RFC 9068 is anticipated it may be "at+jwt".

Sample

I was surprised by the fact that this bug hasn't been caught by existing tests, so I created a symmetric test NimbusJwtEncoderDecoderTests.java where a JWT encoded with NimbusJwtEncoder is then decoded with NimbusJwtDecoder. With a JwtTypeValidator included in JwtValidators.createDefault(), NimbusJwtDecoder throws a JwtValidationException, saying:

An error occurred while attempting to decode the Jwt: the given typ value needs to be one of [JWT]

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions