11using System . IdentityModel . Tokens . Jwt ;
2- using System . Security . Claims ;
32using System . Security . Cryptography ;
43using FluentAssertions ;
54using Microsoft . Extensions . Options ;
65using Microsoft . Extensions . Time . Testing ;
6+ using Microsoft . IdentityModel . JsonWebTokens ;
77using Microsoft . IdentityModel . Tokens ;
88using Moq ;
99using Sentinel . DPoP ;
@@ -152,8 +152,8 @@ public async Task Iat_Boundary_AtPlus5Seconds_MustPass()
152152 [ Fact ]
153153 public async Task Iat_Boundary_AtPlus5_001Seconds_MustFail ( )
154154 {
155- // Arrange: Create proof issued 5.001 seconds in the future
156- var proofIssuedAt = _referenceTime . AddMilliseconds ( 5001 ) ;
155+ // Arrange: iat is second-granularity, so use +6s to be strictly beyond +5s window
156+ var proofIssuedAt = _referenceTime . AddSeconds ( 6 ) ;
157157 var proof = CreateDpopProof (
158158 "ES256" ,
159159 proofIssuedAt ,
@@ -246,11 +246,20 @@ public async Task ClockRegression_PreviouslyRejectedProofStaysRejected()
246246 new DpopValidationRequest ( oldProof , "GET" , new Uri ( "https://api.example.com/old" ) ) ) ;
247247 resultBefore . IsSuccess . Should ( ) . BeFalse ( "Proof from 120s ago should be rejected" ) ;
248248
249- // Act 2: System clock regresses by 2 seconds
250- _timeProvider . SetUtcNow ( _timeProvider . GetUtcNow ( ) . AddSeconds ( - 2 ) ) ;
249+ // Act 2: Simulate a 2-second regressed clock with a fresh validator/time provider
250+ var regressedTimeProvider = new FakeTimeProvider ( _referenceTime . AddSeconds ( - 2 ) ) ;
251+ var validatorAfterRegression = new DpopProofValidator (
252+ _replayCache . Object ,
253+ Options . Create ( new DPoPOptions
254+ {
255+ ProofLifetimeSeconds = 55 ,
256+ AllowedClockSkewSeconds = 5
257+ } ) ,
258+ null ,
259+ regressedTimeProvider ) ;
251260
252261 // Assert: Even with backward clock, old proof must still be rejected
253- var resultAfter = await validator . ValidateAsync (
262+ var resultAfter = await validatorAfterRegression . ValidateAsync (
254263 new DpopValidationRequest ( oldProof , "GET" , new Uri ( "https://api.example.com/old" ) ) ) ;
255264 resultAfter . IsSuccess . Should ( ) . BeFalse (
256265 "Clock regression must not cause replay of previously-rejected proofs" ) ;
@@ -261,9 +270,17 @@ public async Task ClockRegression_PreviouslyRejectedProofStaysRejected()
261270 /// </summary>
262271 private DpopProofValidator CreateValidator ( )
263272 {
273+ var options = new DPoPOptions
274+ {
275+ // Validator window is [now - lifetime - skew, now + skew].
276+ // Configure to enforce the intended [-60s, +5s] test window.
277+ ProofLifetimeSeconds = 55 ,
278+ AllowedClockSkewSeconds = 5
279+ } ;
280+
264281 return new DpopProofValidator (
265282 _replayCache . Object ,
266- Options . Create ( new DPoPOptions ( ) ) ,
283+ Options . Create ( options ) ,
267284 null , // thumbprintComputer (use default)
268285 _timeProvider ) ;
269286 }
@@ -280,31 +297,36 @@ private static string CreateDpopProof(
280297 string httpUri ,
281298 string thumbprintJkt )
282299 {
300+ _ = thumbprintJkt ;
301+
283302 using var ecKey = ECDsa . Create ( ECCurve . NamedCurves . nistP256 ) ;
284303 var securityKey = new ECDsaSecurityKey ( ecKey ) ;
304+ var jwk = JsonWebKeyConverter . ConvertFromECDsaSecurityKey ( securityKey ) ;
285305
286- var handler = new JwtSecurityTokenHandler ( ) ;
306+ var handler = new JsonWebTokenHandler ( ) ;
287307 var tokenDescriptor = new SecurityTokenDescriptor
288308 {
289- Subject = new ClaimsIdentity ( ) ,
290- IssuedAt = issuedAtOffset . DateTime ,
291309 Claims = new Dictionary < string , object >
292310 {
293311 [ "htm" ] = httpMethod . ToUpperInvariant ( ) ,
294312 [ "htu" ] = httpUri . ToLowerInvariant ( ) ,
295313 [ "iat" ] = issuedAtOffset . ToUnixTimeSeconds ( ) ,
296- [ "jti" ] = jti ,
297- [ "typ" ] = "dpop+jwt"
314+ [ "jti" ] = jti
298315 } ,
299316 SigningCredentials = new SigningCredentials ( securityKey , signingAlgorithm ) ,
317+ TokenType = "dpop+jwt" ,
300318 AdditionalHeaderClaims = new Dictionary < string , object >
301319 {
302- [ "typ" ] = "dpop+jwt" ,
303- [ "jwk" ] = new { kty = "EC" , use = "sig" , crv = "P-256" , jkt = thumbprintJkt }
320+ [ "jwk" ] = new Dictionary < string , string >
321+ {
322+ [ "kty" ] = jwk . Kty ! ,
323+ [ "crv" ] = jwk . Crv ! ,
324+ [ "x" ] = jwk . X ! ,
325+ [ "y" ] = jwk . Y !
326+ }
304327 }
305328 } ;
306329
307- var token = handler . CreateToken ( tokenDescriptor ) ;
308- return handler . WriteToken ( token ) ;
330+ return handler . CreateToken ( tokenDescriptor ) ;
309331 }
310332}
0 commit comments