Skip to content

Out-of-range NumericDate claims can throw unchecked DateTimeException during verification #782

Description

@Str1ckl4nd

Checklist

  • I have looked into the Readme and Examples, and have not found a suitable solution or answer.
  • I have looked into the API documentation and have not found a suitable solution or answer.
  • I have searched the issues and have not found a suitable solution or answer.
  • I have searched the Auth0 Community forums and have not found a suitable solution or answer.
  • I agree to the terms within the Auth0 Code of Conduct.

Description

JWTVerifier.verify(String) can throw java.time.DateTimeException when a registered NumericDate claim (exp, nbf, or iat) is a JSON number that fits in long but is outside the range supported by java.time.Instant.

The README verification example documents handling invalid tokens through JWTVerificationException:

try {
    decodedJWT = verifier.verify(token);
} catch (JWTVerificationException exception) {
    // Invalid signature/claims
}

For the out-of-range NumericDate case, the exception escapes as DateTimeException before signature verification reaches the usual invalid-token path.

The relevant code path in 4.5.2 / current master is:

  1. JWTVerifier.verify(String) constructs a JWTDecoder before verifying the decoded token.
    Reference:
    public DecodedJWT verify(String token) throws JWTVerificationException {
    DecodedJWT jwt = new JWTDecoder(parser, token);
    return verify(jwt);
  2. JWTParser.parsePayload(...) catches IOException from Jackson parsing.
    Reference:
    public Payload parsePayload(String json) throws JWTDecodeException {
    if (json == null) {
    throw decodeException();
    }
    try {
    return payloadReader.readValue(json);
    } catch (IOException e) {
  3. PayloadDeserializer.getInstantFromSeconds(...) checks canConvertToLong(), then calls Instant.ofEpochSecond(node.asLong()).
    Reference:
    Instant getInstantFromSeconds(Map<String, JsonNode> tree, String claimName) {
    JsonNode node = tree.get(claimName);
    if (node == null || node.isNull()) {
    return null;
    }
    if (!node.canConvertToLong()) {
    throw new JWTDecodeException(
    String.format("The claim '%s' contained a non-numeric date value.", claimName));
    }
    return Instant.ofEpochSecond(node.asLong());

9223372036854775807 passes the long conversion check but is outside the valid Instant epoch-second range. The result is an unchecked DateTimeException instead of a JWTVerificationException / JWTDecodeException.

Expected behavior:

Out-of-range registered NumericDate claims should be rejected through the library's normal invalid-token exception boundary.

Actual behavior:

JWTVerifier.verify(String) throws java.time.DateTimeException.

Reproduction

Create the following files in an empty directory.

First create the source directory:

mkdir -p src/main/java/repro

pom.xml:

<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>repro</groupId>
  <artifactId>java-jwt-numericdate-repro</artifactId>
  <version>1.0.0</version>

  <properties>
    <maven.compiler.source>11</maven.compiler.source>
    <maven.compiler.target>11</maven.compiler.target>
  </properties>

  <dependencies>
    <dependency>
      <groupId>com.auth0</groupId>
      <artifactId>java-jwt</artifactId>
      <version>4.5.2</version>
    </dependency>
  </dependencies>

  <build>
    <plugins>
      <plugin>
        <groupId>org.codehaus.mojo</groupId>
        <artifactId>exec-maven-plugin</artifactId>
        <version>3.5.0</version>
      </plugin>
    </plugins>
  </build>
</project>

src/main/java/repro/NumericDateRepro.java:

package repro;

import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTVerificationException;

import java.nio.charset.StandardCharsets;
import java.time.DateTimeException;
import java.util.Base64;

public final class NumericDateRepro {
    public static void main(String[] args) {
        String payload = "{\"exp\":9223372036854775807}";
        String token = base64Url("{\"alg\":\"HS256\",\"typ\":\"JWT\"}") + "."
                + base64Url(payload) + ".invalidsig";

        try {
            JWT.require(Algorithm.HMAC256("secret")).build().verify(token);
            System.out.println("unexpected: token accepted");
            System.exit(2);
        } catch (JWTVerificationException expectedBoundary) {
            System.out.println("expected boundary: " + expectedBoundary.getClass().getName());
            System.exit(0);
        } catch (DateTimeException escaped) {
            System.out.println("reproduced unchecked exception: " + escaped.getClass().getName());
            System.out.println("payload=" + payload);
            System.exit(1);
        }
    }

    private static String base64Url(String value) {
        return Base64.getUrlEncoder()
                .withoutPadding()
                .encodeToString(value.getBytes(StandardCharsets.UTF_8));
    }
}

Run:

mvn -q compile exec:java -Dexec.mainClass=repro.NumericDateRepro

Observed result:

reproduced unchecked exception: java.time.DateTimeException
payload={"exp":9223372036854775807}

Success / failure oracle:

  • Current behavior: the command prints reproduced unchecked exception: java.time.DateTimeException and exits with status 1.
  • Expected fixed behavior: the command prints expected boundary: ...JWTVerificationException... or another library-controlled invalid-token exception and exits with status 0.

The same behavior can be exercised with nbf or iat instead of exp.

Additional context

No response

java-jwt version

4.5.2

Java version

Java 11

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugThis points to a verified bug in the code

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions