[java-oidc-common] branch main updated: JCOMOIDC-121 - Maximum lifetime for jti-claim validator's replay cache records

Henri Mikkonen henri.mikkonen at iki.fi
Thu Sep 12 05:53:32 UTC 2024


This is an automated email from the git hooks/post-receive script.

hjmikkon pushed a commit to branch main
in repository java-oidc-common.

View the commit online:
http://git.shibboleth.net/view/?p=java-oidc-common.git;a=commit;h=6df28bb04703d7e2d1a533588fcf8ce28d89c0d8

The following commit(s) were added to refs/heads/main by this push:
     new 6df28bb  JCOMOIDC-121 - Maximum lifetime for jti-claim validator's replay cache records
6df28bb is described below

commit 6df28bb04703d7e2d1a533588fcf8ce28d89c0d8
Author: Henri Mikkonen <henri.mikkonen at iki.fi>
AuthorDate: Thu Sep 12 08:53:05 2024 +0300

    JCOMOIDC-121 - Maximum lifetime for jti-claim validator's replay cache records
    
    https://shibboleth.atlassian.net/browse/JCOMOIDC-121
    
    If set (i.e. non-null), 'maxReplayCacheRecordLifetime' is used for overriding the expiration time
    set by the 'exp' -claim, if the claim's expiry time is longer than it.
---
 .../claims/impl/JWTIdentifierClaimsValidator.java  | 28 ++++++++++++++++++++--
 .../impl/JWTIdentifierClaimsValidatorTest.java     | 24 +++++++++++++++++++
 2 files changed, 50 insertions(+), 2 deletions(-)

diff --git a/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/jwt/claims/impl/JWTIdentifierClaimsValidator.java b/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/jwt/claims/impl/JWTIdentifierClaimsValidator.java
index e303bf2..0df4ec5 100644
--- a/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/jwt/claims/impl/JWTIdentifierClaimsValidator.java
+++ b/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/jwt/claims/impl/JWTIdentifierClaimsValidator.java
@@ -38,7 +38,7 @@ import net.shibboleth.shared.primitive.StringSupport;
  * Verifies the identifier (jit) from the JWT against replay via configurable {@link ReplayCache}.
  * 
  * <p>Single identifier is stored in the cache for the lifetime of the JWT (expiration instant is
- * taken from the 'exp' -claim).</p>
+ * taken from the 'exp' -claim if found, with configurable default and maximum lifetime).</p>
  */
 public class JWTIdentifierClaimsValidator extends AbstractClaimsValidator {
 
@@ -55,6 +55,11 @@ public class JWTIdentifierClaimsValidator extends AbstractClaimsValidator {
      */
     @Nullable private Duration replayCacheRecordLifetime;
 
+    /**
+     * Maximum lifetime to be used in replay cache when 'exp' claim is exploited.
+     */
+    @Nullable private Duration maxReplayCacheRecordLifetime;
+
     /**
      * Optional function to calculate they key used with {@link ReplayCache}.
      */
@@ -68,6 +73,7 @@ public class JWTIdentifierClaimsValidator extends AbstractClaimsValidator {
         assert oneMinute != null;
         clockSkew = oneMinute;
         replayCacheRecordLifetime = null;
+        maxReplayCacheRecordLifetime = null;
         replayCacheKeyCalculationStrategy = null;
     }
     
@@ -103,6 +109,18 @@ public class JWTIdentifierClaimsValidator extends AbstractClaimsValidator {
         replayCacheRecordLifetime = lifetime;
     }
 
+    /**
+     * Set the maximum lifetime to be used in replay cache when 'exp' claim is exploited.
+     * 
+     * @param lifetime record lifetime to set.
+     * 
+     * @since 3.2.0
+     */
+    public void setMaxReplayCacheRecordLifetime(@Nullable final Duration lifetime) {
+        ifInitializedThrowUnmodifiabledComponentException();
+        maxReplayCacheRecordLifetime = lifetime;
+    }
+
     /**
      * Set the function to calculate they key used with {@link ReplayCache}.
      * 
@@ -138,7 +156,13 @@ public class JWTIdentifierClaimsValidator extends AbstractClaimsValidator {
             }
             expiry = Instant.now().plus(replayCacheRecordLifetime);
         } else {
-            expiry = exp.toInstant().plus(clockSkew);
+            final Instant claimExpiry = exp.toInstant().plus(clockSkew);
+            if (maxReplayCacheRecordLifetime == null) {
+                expiry = claimExpiry;
+            } else {
+                final Instant maxExpiry = Instant.now().plus(maxReplayCacheRecordLifetime).plus(clockSkew);
+                expiry = maxExpiry.isBefore(claimExpiry) ? maxExpiry : claimExpiry;
+            }
         }
         final String jti = claims.getJWTID();
         if (StringSupport.trimOrNull(jti) == null) {
diff --git a/oidc-common-crypto-impl/src/test/java/net/shibboleth/oidc/security/jwt/claims/impl/JWTIdentifierClaimsValidatorTest.java b/oidc-common-crypto-impl/src/test/java/net/shibboleth/oidc/security/jwt/claims/impl/JWTIdentifierClaimsValidatorTest.java
index 8e3f60f..078763a 100644
--- a/oidc-common-crypto-impl/src/test/java/net/shibboleth/oidc/security/jwt/claims/impl/JWTIdentifierClaimsValidatorTest.java
+++ b/oidc-common-crypto-impl/src/test/java/net/shibboleth/oidc/security/jwt/claims/impl/JWTIdentifierClaimsValidatorTest.java
@@ -14,6 +14,7 @@
 
 package net.shibboleth.oidc.security.jwt.claims.impl;
 
+import java.time.Duration;
 import java.time.Instant;
 import java.util.Date;
 import java.util.function.Function;
@@ -50,6 +51,11 @@ public class JWTIdentifierClaimsValidatorTest {
 
     public void setup(@Nullable final Function<String, String> keyCalculationStrategy)
             throws ComponentInitializationException {
+        setup(keyCalculationStrategy, null);
+    }
+
+    public void setup(@Nullable final Function<String, String> keyCalculationStrategy,
+            @Nullable final Duration maxLifetime) throws ComponentInitializationException {
         validator = new JWTIdentifierClaimsValidator();
         replayCache = new StorageServiceReplayCache();
         final MemoryStorageService storageService = new MemoryStorageService();
@@ -59,6 +65,8 @@ public class JWTIdentifierClaimsValidatorTest {
         validator.setReplayCache(replayCache);
         validator.setId("test-validator");
         validator.setReplayCacheKeyCalculationStrategy(keyCalculationStrategy);
+        validator.setClockSkew(Duration.ZERO);
+        validator.setMaxReplayCacheRecordLifetime(maxLifetime);
         validator.initialize();
         prc = new ProfileRequestContext();
     }
@@ -193,4 +201,20 @@ public class JWTIdentifierClaimsValidatorTest {
         validator.doValidate(claimsSet, prc);
     }
 
+    @Test
+    public void doValidateTest_longExp()
+            throws JWTValidationException, ComponentInitializationException, InterruptedException {
+        setup(null, Duration.ofSeconds(2));
+        final JWTClaimsSet claimsSet = new JWTClaimsSet.Builder()
+                .jwtID("mockId")
+                .expirationTime(Date.from(Instant.now().plus(Duration.ofHours(24))))
+                .build();
+        validator.doValidate(claimsSet, prc);
+        Assert.assertFalse(replayCache.check(JWTIdentifierClaimsValidator.class.getName(),
+                "mockId", Instant.now().plusSeconds(300)));
+        Thread.sleep(2000);
+        Assert.assertTrue(replayCache.check(JWTIdentifierClaimsValidator.class.getName(),
+                "mockId", Instant.now().plusSeconds(300)));
+    }
+
 }

-- 
To stop receiving notification emails like this one, please contact
the administrator of this repository.


More information about the commits mailing list