Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ public class DPoPOptions
public TimeSpan ProofTokenValidityDuration { get; set; } = TimeSpan.FromMinutes(1);

/// <summary>
/// Clock skew used in validating DPoP proof token expiration using a server-senerated nonce value. Defaults to zero.
/// Clock skew used in validating DPoP proof token expiration using a server-generated nonce value. Defaults to ten seconds.
/// </summary>
public TimeSpan ServerClockSkew { get; set; } = TimeSpan.FromMinutes(0);
public TimeSpan ServerClockSkew { get; set; } = TimeSpan.FromSeconds(10);
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#nullable enable

using Duende.IdentityServer.Stores.Serialization;
using Duende.IdentityServer.Validation;

namespace Duende.IdentityServer.Configuration;

Expand Down Expand Up @@ -207,4 +208,16 @@ public class IdentityServerOptions
/// Options for Pushed Authorization Requests (PAR).
/// </summary>
public PushedAuthorizationOptions PushedAuthorization { get; set; } = new PushedAuthorizationOptions();

/// <summary>
/// The allowed clock skew for JWT lifetime validation. All JWTs that have
/// their lifetime validated use this setting to control the clock skew of
/// lifetime validation. This includes JWT access tokens passed to the user
/// info, introspection, and local api endpoints, client authentication JWTs
/// used in private_key_jwt authentication, JWT secured authorization
/// requests (JAR), and custom usage of the <see cref="TokenValidator"/>,
/// such as in a token exchange implementation. Defaults to ten seconds.
/// </summary>
public TimeSpan JwtValidationClockSkew { get; set; } = TimeSpan.FromSeconds(10);

}
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,9 @@ protected virtual async Task<JsonWebToken> ValidateJwtAsync(JwtRequestValidation
ValidateAudience = true,

RequireSignedTokens = true,
RequireExpirationTime = true
RequireExpirationTime = true,

ClockSkew = Options.JwtValidationClockSkew
};

var strictJarValidation = context.StrictJarValidation.HasValue ? context.StrictJarValidation.Value : Options.StrictJarValidation;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ public async Task<SecretValidationResult> ValidateAsync(IEnumerable<Secret> secr
RequireSignedTokens = true,
RequireExpirationTime = true,

ClockSkew = TimeSpan.FromMinutes(5)
ClockSkew = _options.JwtValidationClockSkew
};

if (_options.StrictClientAssertionAudienceValidation)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -282,7 +282,8 @@ private async Task<TokenValidationResult> ValidateJwtAsync(string jwtString,
{
ValidIssuer = await _issuerNameService.GetCurrentAsync(),
IssuerSigningKeys = validationKeys.Select(k => k.Key),
ValidateLifetime = validateLifetime
ValidateLifetime = validateLifetime,
ClockSkew = _options.JwtValidationClockSkew
};

if (audience.IsPresent())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,13 @@
// See LICENSE in the project root for license information.


using System;
using System.IdentityModel.Tokens.Jwt;
using System.Linq;
using System.Threading.Tasks;
using Duende.IdentityServer.Configuration;
using Duende.IdentityServer.Models;
using Duende.IdentityServer.Stores;
using Shouldly;
using Duende.IdentityModel;
using UnitTests.Common;
using UnitTests.Validation.Setup;
using Xunit;

namespace UnitTests.Validation;

Expand Down Expand Up @@ -235,6 +230,45 @@ public async Task JWT_Token_Too_Long()
result.Error.ShouldBe(OidcConstants.ProtectedResourceErrors.InvalidToken);
}

[Fact]
[Trait("Category", Category)]
public async Task JWT_respects_clock_skew_setting_to_allow_token_with_off_time()
{
var futureClock = new StubClock();
var definitelyNotNow = DateTime.UtcNow.AddSeconds(9);
futureClock.UtcNowFunc = () => definitelyNotNow;
var signer = Factory.CreateDefaultTokenCreator(clock: futureClock);
var token = TokenFactory.CreateAccessToken(new Client { ClientId = "roclient" }, "valid", 600, "read", "write");
var jwt = await signer.CreateTokenAsync(token);

var options = TestIdentityServerOptions.Create();
options.JwtValidationClockSkew = TimeSpan.FromSeconds(10);
var validator = Factory.CreateTokenValidator(options: options);
var result = await validator.ValidateAccessTokenAsync(jwt);

result.IsError.ShouldBeFalse();
}

[Fact]
[Trait("Category", Category)]
public async Task Jwt_when_token_time_outside_of_configured_clock_skew_token_is_considered_invalid()
{
var futureClock = new StubClock();
var definitelyNotNow = DateTime.UtcNow.AddSeconds(10);
futureClock.UtcNowFunc = () => definitelyNotNow;
var signer = Factory.CreateDefaultTokenCreator(clock: futureClock);
var token = TokenFactory.CreateAccessToken(new Client { ClientId = "roclient" }, "valid", 600, "read", "write");
var jwt = await signer.CreateTokenAsync(token);

var options = TestIdentityServerOptions.Create();
options.JwtValidationClockSkew = TimeSpan.FromSeconds(5);
var validator = Factory.CreateTokenValidator(options: options);
var result = await validator.ValidateAccessTokenAsync(jwt);

result.IsError.ShouldBeTrue();
result.Error.ShouldBe(OidcConstants.ProtectedResourceErrors.InvalidToken);
}

[Fact]
[Trait("Category", Category)]
public async Task Valid_AccessToken_but_Client_not_active()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,26 +2,21 @@
// See LICENSE in the project root for license information.


using System;
using System.Collections.Generic;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text.Json;
using System.Threading.Tasks;
using Duende.IdentityServer;
using Duende.IdentityServer.Configuration;
using Duende.IdentityServer.Models;
using Duende.IdentityServer.Services;
using Duende.IdentityServer.Stores;
using Duende.IdentityServer.Validation;
using Shouldly;
using Duende.IdentityModel;
using UnitTests.Common;
using UnitTests.Services.Default;
using UnitTests.Validation.Setup;
using Microsoft.Extensions.Logging;
using Microsoft.IdentityModel.Tokens;
using Xunit;

namespace UnitTests.Validation.Secrets;

Expand Down Expand Up @@ -351,4 +346,46 @@ public async Task Invalid_Unsigned_Token()

result.Success.ShouldBeFalse();
}

[Fact]
public async Task Invalid_Not_Yet_Valid_Token()
{
var clientId = "certificate_base64_valid";
var client = await _clients.FindEnabledClientByIdAsync(clientId);

var token = CreateToken(clientId, nowOverride: DateTime.UtcNow.AddSeconds(30));
var secret = new ParsedSecret
{
Id = clientId,
Credential = new JwtSecurityTokenHandler().WriteToken(token),
Type = IdentityServerConstants.ParsedSecretTypes.JwtBearer
};

_options.JwtValidationClockSkew = TimeSpan.FromSeconds(5);

var result = await _validator.ValidateAsync(client.ClientSecrets, secret);

result.Success.ShouldBeFalse();
}

[Fact]
public async Task Configuration_Allows_For_Clock_Skew_In_Token()
{
var clientId = "certificate_base64_valid";
var client = await _clients.FindEnabledClientByIdAsync(clientId);

var token = CreateToken(clientId, nowOverride: DateTime.UtcNow.AddSeconds(5));
var secret = new ParsedSecret
{
Id = clientId,
Credential = new JwtSecurityTokenHandler().WriteToken(token),
Type = IdentityServerConstants.ParsedSecretTypes.JwtBearer
};

_options.JwtValidationClockSkew = TimeSpan.FromSeconds(10);

var result = await _validator.ValidateAsync(client.ClientSecrets, secret);

result.Success.ShouldBeTrue();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -175,10 +175,11 @@ internal static IResourceValidator CreateResourceValidator(IResourceStore store
return new DefaultResourceValidator(store, new DefaultScopeParser(TestLogger.Create<DefaultScopeParser>()), TestLogger.Create<DefaultResourceValidator>());
}

internal static ITokenCreationService CreateDefaultTokenCreator(IdentityServerOptions options = null)
internal static ITokenCreationService CreateDefaultTokenCreator(IdentityServerOptions options = null,
IClock clock = null)
{
return new DefaultTokenCreationService(
new StubClock(),
clock ?? new StubClock(),
new DefaultKeyMaterialService(
new IValidationKeysStore[] { },
new ISigningCredentialStore[] { new InMemorySigningCredentialsStore(TestCert.LoadSigningCredentials()) },
Expand Down