From cdeb11db97b46c912ce30db758ae6a852546e52d Mon Sep 17 00:00:00 2001 From: Colm O hEigeartaigh Date: Fri, 22 May 2026 15:31:07 +0100 Subject: [PATCH] Handle NaN or infinite times in JSON/JWT --- .../json/basic/JsonMapObjectReaderWriter.java | 6 ++- .../basic/JsonMapObjectReaderWriterTest.java | 22 ++++++++++ .../cxf/rs/security/jose/jwt/JwtUtils.java | 37 ++++++++++++----- .../rs/security/jose/jwt/JwtUtilsTest.java | 40 +++++++++++++++++++ 4 files changed, 95 insertions(+), 10 deletions(-) diff --git a/rt/rs/extensions/json-basic/src/main/java/org/apache/cxf/jaxrs/json/basic/JsonMapObjectReaderWriter.java b/rt/rs/extensions/json-basic/src/main/java/org/apache/cxf/jaxrs/json/basic/JsonMapObjectReaderWriter.java index 88445b17e68..a99e7c43e73 100644 --- a/rt/rs/extensions/json-basic/src/main/java/org/apache/cxf/jaxrs/json/basic/JsonMapObjectReaderWriter.java +++ b/rt/rs/extensions/json-basic/src/main/java/org/apache/cxf/jaxrs/json/basic/JsonMapObjectReaderWriter.java @@ -258,7 +258,11 @@ protected Object readPrimitiveValue(String name, String json, int from, int to) try { value = Long.valueOf(valueStr); } catch (NumberFormatException ex) { - value = Double.valueOf(valueStr); + Double doubleValue = Double.valueOf(valueStr); + if (doubleValue.isInfinite() || doubleValue.isNaN()) { + throw new NumberFormatException("Non-finite numeric value is not allowed"); + } + value = doubleValue; } } diff --git a/rt/rs/extensions/json-basic/src/test/java/org/apache/cxf/jaxrs/json/basic/JsonMapObjectReaderWriterTest.java b/rt/rs/extensions/json-basic/src/test/java/org/apache/cxf/jaxrs/json/basic/JsonMapObjectReaderWriterTest.java index 424eb72c5dd..9c66add08c1 100644 --- a/rt/rs/extensions/json-basic/src/test/java/org/apache/cxf/jaxrs/json/basic/JsonMapObjectReaderWriterTest.java +++ b/rt/rs/extensions/json-basic/src/test/java/org/apache/cxf/jaxrs/json/basic/JsonMapObjectReaderWriterTest.java @@ -33,6 +33,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; public class JsonMapObjectReaderWriterTest { @@ -210,4 +211,25 @@ public void testAlreadyEscapedBackslash() throws Exception { assertEquals("a\\", entry.getValue()); } + @Test + public void testRejectInfinityNumericValue() { + assertInvalidNumericLiteral("Infinity"); + assertInvalidNumericLiteral("-Infinity"); + } + + @Test + public void testRejectNaNNumericValue() { + assertInvalidNumericLiteral("NaN"); + } + + private void assertInvalidNumericLiteral(String value) { + JsonMapObjectReaderWriter jsonMapObjectReaderWriter = new JsonMapObjectReaderWriter(); + try { + jsonMapObjectReaderWriter.fromJson("{\"exp\":" + value + "}"); + fail("Expected NumberFormatException for invalid numeric value: " + value); + } catch (NumberFormatException ex) { + // expected + } + } + } diff --git a/rt/rs/security/jose-parent/jose/src/main/java/org/apache/cxf/rs/security/jose/jwt/JwtUtils.java b/rt/rs/security/jose-parent/jose/src/main/java/org/apache/cxf/rs/security/jose/jwt/JwtUtils.java index 31653190db8..10b08220a93 100644 --- a/rt/rs/security/jose-parent/jose/src/main/java/org/apache/cxf/rs/security/jose/jwt/JwtUtils.java +++ b/rt/rs/security/jose-parent/jose/src/main/java/org/apache/cxf/rs/security/jose/jwt/JwtUtils.java @@ -50,9 +50,14 @@ public static void validateJwtExpiry(JwtClaims claims, int clockOffset, boolean return; } Instant now = Instant.now(); - Instant expires = Instant.ofEpochMilli(expiryTime * 1000L); - if (clockOffset != 0) { - expires = expires.plusSeconds(clockOffset); + Instant expires; + try { + expires = Instant.ofEpochSecond(expiryTime); + if (clockOffset != 0) { + expires = expires.plusSeconds(clockOffset); + } + } catch (RuntimeException ex) { + throw new JwtException("The token has expired", ex); } if (expires.isBefore(now)) { throw new JwtException("The token has expired"); @@ -69,10 +74,15 @@ public static void validateJwtNotBefore(JwtClaims claims, int clockOffset, boole } Instant validCreation = Instant.now(); - if (clockOffset != 0) { - validCreation = validCreation.plusSeconds(clockOffset); + Instant notBeforeDate; + try { + if (clockOffset != 0) { + validCreation = validCreation.plusSeconds(clockOffset); + } + notBeforeDate = Instant.ofEpochSecond(notBeforeTime); + } catch (RuntimeException ex) { + throw new JwtException("The token cannot be accepted yet", ex); } - Instant notBeforeDate = Instant.ofEpochMilli(notBeforeTime * 1000L); // Check to see if the not before time is in the future if (notBeforeDate.isAfter(validCreation)) { @@ -89,11 +99,20 @@ public static void validateJwtIssuedAt(JwtClaims claims, int timeToLive, int clo return; } - Instant createdDate = Instant.ofEpochMilli(issuedAtInSecs * 1000L); + Instant createdDate; + try { + createdDate = Instant.ofEpochSecond(issuedAtInSecs); + } catch (RuntimeException ex) { + throw new JwtException("Invalid issuedAt", ex); + } Instant validCreation = Instant.now(); - if (clockOffset != 0) { - validCreation = validCreation.plusSeconds(clockOffset); + try { + if (clockOffset != 0) { + validCreation = validCreation.plusSeconds(clockOffset); + } + } catch (RuntimeException ex) { + throw new JwtException("Invalid issuedAt", ex); } // Check to see if the IssuedAt time is in the future diff --git a/rt/rs/security/jose-parent/jose/src/test/java/org/apache/cxf/rs/security/jose/jwt/JwtUtilsTest.java b/rt/rs/security/jose-parent/jose/src/test/java/org/apache/cxf/rs/security/jose/jwt/JwtUtilsTest.java index e53a47060c8..af58d2d0e7c 100644 --- a/rt/rs/security/jose-parent/jose/src/test/java/org/apache/cxf/rs/security/jose/jwt/JwtUtilsTest.java +++ b/rt/rs/security/jose-parent/jose/src/test/java/org/apache/cxf/rs/security/jose/jwt/JwtUtilsTest.java @@ -174,4 +174,44 @@ public void testExpectedAudience() throws Exception { } } + @org.junit.Test + public void testInfiniteExpiryTokenRejected() { + try { + String claimsJson = "{\"sub\":\"alice\",\"iss\":\"DoubleItSTSIssuer\"," + + "\"exp\":Infinity}"; + JwtClaims claims = JwtUtils.jsonToClaims(claimsJson); + JwtUtils.validateJwtExpiry(claims, 0, true); + fail("Failure expected on a token with infinite expiry"); + } catch (JwtException | NumberFormatException ex) { + // expected + } + } + + @org.junit.Test + public void testInfiniteNotBeforeTokenRejected() { + try { + String claimsJson = "{\"sub\":\"alice\",\"iss\":\"DoubleItSTSIssuer\"," + + "\"nbf\":Infinity}"; + JwtClaims claims = JwtUtils.jsonToClaims(claimsJson); + JwtUtils.validateJwtNotBefore(claims, 0, true); + fail("Failure expected on a token with infinite not before"); + } catch (JwtException | NumberFormatException ex) { + // expected + } + } + + @org.junit.Test + public void testInfiniteIssuedAtTokenRejected() { + try { + String claimsJson = "{\"sub\":\"alice\",\"iss\":\"DoubleItSTSIssuer\"," + + "\"iat\":Infinity}"; + JwtClaims claims = JwtUtils.jsonToClaims(claimsJson); + JwtUtils.validateJwtIssuedAt(claims, 0, 0, true); + fail("Failure expected on a token with infinite issued at"); + } catch (JwtException | NumberFormatException ex) { + // expected + } + } + + }