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 (JWTVerificationExceptionexception) {
// 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:
    publicDecodedJWTverify(Stringtoken) throwsJWTVerificationException {
    DecodedJWTjwt = newJWTDecoder(parser, token);
    returnverify(jwt);
  2. JWTParser.parsePayload(...) catches IOException from Jackson parsing.
    Reference:
    publicPayloadparsePayload(Stringjson) throwsJWTDecodeException {
    if (json == null) {
    throwdecodeException();
    }
    try {
    returnpayloadReader.readValue(json);
    } catch (IOExceptione) {
  3. PayloadDeserializer.getInstantFromSeconds(...) checks canConvertToLong(), then calls Instant.ofEpochSecond(node.asLong()).
    Reference:
    InstantgetInstantFromSeconds(Map<String, JsonNode> tree, StringclaimName) {
    JsonNodenode = tree.get(claimName);
    if (node == null || node.isNull()) {
    returnnull;
    }
    if (!node.canConvertToLong()) {
    thrownewJWTDecodeException(
    String.format("The claim '%s' contained a non-numeric date value.", claimName));
    }
    returnInstant.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:

<projectxmlns="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:

packagerepro;
importcom.auth0.jwt.JWT;
importcom.auth0.jwt.algorithms.Algorithm;
importcom.auth0.jwt.exceptions.JWTVerificationException;
importjava.nio.charset.StandardCharsets;
importjava.time.DateTimeException;
importjava.util.Base64;
publicfinalclassNumericDateRepro {
publicstaticvoidmain(String[] args) {
Stringpayload = "{\"exp\":9223372036854775807}";
Stringtoken = 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 (JWTVerificationExceptionexpectedBoundary) {
System.out.println("expected boundary: " + expectedBoundary.getClass().getName());
System.exit(0);
} catch (DateTimeExceptionescaped) {
System.out.println("reproduced unchecked exception: " + escaped.getClass().getName());
System.out.println("payload=" + payload);
System.exit(1);
}
}
privatestaticStringbase64Url(Stringvalue) {
returnBase64.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

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