Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Regression tests: Issuer signing key #2927

Merged
merged 6 commits into from
Oct 22, 2024
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 @@ -341,7 +341,7 @@ await ValidateJWSAsync(actorToken, actorParameters, configuration, callContext,

ValidationResult<ValidatedSigningKeyLifetime> issuerSigningKeyValidationResult =
validationParameters.IssuerSigningKeyValidator(
signatureValidationResult.UnwrapResult(), jsonWebToken, validationParameters, configuration, callContext);
jsonWebToken.SigningKey, jsonWebToken, validationParameters, configuration, callContext);
iNinja marked this conversation as resolved.
Show resolved Hide resolved
if (!issuerSigningKeyValidationResult.IsValid)
{
StackFrame issuerSigningKeyValidationFailureStackFrame = StackFrames.IssuerSigningKeyValidationFailed ??= new StackFrame(true);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ Microsoft.IdentityModel.Tokens.IssuerValidationSource.IssuerMatchedConfiguration
Microsoft.IdentityModel.Tokens.IssuerValidationSource.IssuerMatchedValidationParameters = 2 -> Microsoft.IdentityModel.Tokens.IssuerValidationSource
Microsoft.IdentityModel.Tokens.LifetimeValidationError._expires -> System.DateTime
Microsoft.IdentityModel.Tokens.LifetimeValidationError._notBefore -> System.DateTime
Microsoft.IdentityModel.Tokens.TokenValidationParameters.TimeProvider.get -> System.TimeProvider
Microsoft.IdentityModel.Tokens.TokenValidationParameters.TimeProvider.set -> void
Microsoft.IdentityModel.Tokens.ValidationError.GetException(System.Type exceptionType, System.Exception innerException) -> System.Exception
Microsoft.IdentityModel.Tokens.ValidationResult<TResult>.Error.get -> Microsoft.IdentityModel.Tokens.ValidationError
Microsoft.IdentityModel.Tokens.ValidationResult<TResult>.IsValid.get -> bool
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -540,6 +540,11 @@ public string RoleClaimType
/// </remarks>
public SignatureValidatorUsingConfiguration SignatureValidatorUsingConfiguration { get; set; }

/// <summary>
/// Gets or sets the time provider.
/// </summary>
internal TimeProvider TimeProvider { get; set; } = TimeProvider.System;
iNinja marked this conversation as resolved.
Show resolved Hide resolved

/// <summary>
/// Gets or sets the <see cref="SecurityKey"/> that is to be used for decryption.
/// </summary>
Expand Down
2 changes: 1 addition & 1 deletion src/Microsoft.IdentityModel.Tokens/ValidatorUtilities.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ internal static void ValidateLifetime(DateTime? notBefore, DateTime? expires, Se
Expires = expires
});

DateTime utcNow = DateTime.UtcNow;
DateTime utcNow = validationParameters.TimeProvider.GetUtcNow().UtcDateTime;
if (notBefore.HasValue && (notBefore.Value > DateTimeUtil.Add(utcNow, validationParameters.ClockSkew)))
throw LogHelper.LogExceptionMessage(new SecurityTokenNotYetValidException(LogHelper.FormatInvariant(LogMessages.IDX10222, LogHelper.MarkAsNonPII(notBefore.Value), LogHelper.MarkAsNonPII(utcNow)))
{
Expand Down
2 changes: 1 addition & 1 deletion src/Microsoft.IdentityModel.Tokens/Validators.cs
Original file line number Diff line number Diff line change
Expand Up @@ -404,7 +404,7 @@ internal static void ValidateIssuerSigningKeyLifeTime(SecurityKey securityKey, T
X509SecurityKey x509SecurityKey = securityKey as X509SecurityKey;
if (x509SecurityKey?.Certificate is X509Certificate2 cert)
{
DateTime utcNow = DateTime.UtcNow;
DateTime utcNow = validationParameters.TimeProvider.GetUtcNow().UtcDateTime;
var notBeforeUtc = cert.NotBefore.ToUniversalTime();
var notAfterUtc = cert.NotAfter.ToUniversalTime();

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

#nullable enable
using System;
using System.Threading.Tasks;
using Microsoft.IdentityModel.TestUtils;
using Microsoft.IdentityModel.Tokens;
using Xunit;

namespace Microsoft.IdentityModel.JsonWebTokens.Tests
{
public partial class JsonWebTokenHandlerValidateTokenAsyncTests
{
[Theory, MemberData(nameof(ValidateTokenAsync_IssuerSigningKeyTestCases), DisableDiscoveryEnumeration = true)]
public async Task ValidateTokenAsync_IssuerSigningKey(ValidateTokenAsyncIssuerSigningKeyTheoryData theoryData)
{
var context = TestUtilities.WriteHeader($"{this}.ValidateTokenAsync_IssuerSigningKey", theoryData);

string jwtString = CreateTokenWithSigningCredentials(theoryData.SigningCredentials);

await ValidateAndCompareResults(jwtString, theoryData, context);

TestUtilities.AssertFailIfErrors(context);
}

public static TheoryData<ValidateTokenAsyncIssuerSigningKeyTheoryData> ValidateTokenAsync_IssuerSigningKeyTestCases
{
get
{
int currentYear = DateTime.UtcNow.Year;
// Mock time provider, 100 years in the future
TimeProvider futureTimeProvider = new MockTimeProvider(new DateTimeOffset(currentYear + 100, 1, 1, 0, 0, 0, new(0)));
// Mock time provider, 100 years in the past
TimeProvider pastTimeProvider = new MockTimeProvider(new DateTimeOffset(currentYear - 100, 9, 16, 0, 0, 0, new(0)));

return new TheoryData<ValidateTokenAsyncIssuerSigningKeyTheoryData>
{
new ValidateTokenAsyncIssuerSigningKeyTheoryData("Valid_IssuerSigningKeyIsValid")
{
SigningCredentials = KeyingMaterial.DefaultX509SigningCreds_2048_RsaSha2_Sha2,
TokenValidationParameters = CreateTokenValidationParameters(KeyingMaterial.DefaultX509SigningCreds_2048_RsaSha2_Sha2.Key),
ValidationParameters = CreateValidationParameters(KeyingMaterial.DefaultX509SigningCreds_2048_RsaSha2_Sha2.Key),
},
new ValidateTokenAsyncIssuerSigningKeyTheoryData("Invalid_IssuerSigningKeyIsExpired")
{
// Signing key is valid between September 2011 and December 2039
// Mock time provider is set to 100 years in the future, after the key expired
SigningCredentials = KeyingMaterial.DefaultX509SigningCreds_2048_RsaSha2_Sha2,
TokenValidationParameters = CreateTokenValidationParameters(
KeyingMaterial.DefaultX509SigningCreds_2048_RsaSha2_Sha2.Key, futureTimeProvider),
ValidationParameters = CreateValidationParameters(
KeyingMaterial.DefaultX509SigningCreds_2048_RsaSha2_Sha2.Key, futureTimeProvider),
ExpectedIsValid = false,
ExpectedException = ExpectedException.SecurityTokenInvalidSigningKeyException("IDX10249:"),
},
new ValidateTokenAsyncIssuerSigningKeyTheoryData("Invalid_IssuerSigningKeyNotYetValid")
{
// Signing key is valid between September 2011 and December 2039
// Mock time provider is set to 100 years in the past, before the key was valid.
SigningCredentials = KeyingMaterial.DefaultX509SigningCreds_2048_RsaSha2_Sha2,
TokenValidationParameters = CreateTokenValidationParameters(
KeyingMaterial.DefaultX509SigningCreds_2048_RsaSha2_Sha2.Key, pastTimeProvider),
ValidationParameters = CreateValidationParameters(
KeyingMaterial.DefaultX509SigningCreds_2048_RsaSha2_Sha2.Key, pastTimeProvider),
ExpectedIsValid = false,
ExpectedException = ExpectedException.SecurityTokenInvalidSigningKeyException("IDX10248:"),
},
};

static TokenValidationParameters CreateTokenValidationParameters(
SecurityKey? signingKey = null, TimeProvider? timeProvider = null)
{
// only validate the signature and issuer signing key
var tokenValidationParameters = new TokenValidationParameters
{
ValidateAudience = false,
ValidateIssuer = false,
ValidateLifetime = false,
ValidateTokenReplay = false,
ValidateIssuerSigningKey = true,
RequireSignedTokens = true,
IssuerSigningKey = signingKey,
};

if (timeProvider is not null)
tokenValidationParameters.TimeProvider = timeProvider;

return tokenValidationParameters;
}

static ValidationParameters CreateValidationParameters(
SecurityKey? signingKey = null, TimeProvider? timeProvider = null)
{
ValidationParameters validationParameters = new ValidationParameters();

if (signingKey is not null)
validationParameters.IssuerSigningKeys.Add(signingKey);

if (timeProvider is not null)
validationParameters.TimeProvider = timeProvider;

// Skip all validations except signature and issuer signing key
validationParameters.AlgorithmValidator = SkipValidationDelegates.SkipAlgorithmValidation;
validationParameters.AudienceValidator = SkipValidationDelegates.SkipAudienceValidation;
validationParameters.IssuerValidatorAsync = SkipValidationDelegates.SkipIssuerValidation;
validationParameters.LifetimeValidator = SkipValidationDelegates.SkipLifetimeValidation;
validationParameters.TokenReplayValidator = SkipValidationDelegates.SkipTokenReplayValidation;
validationParameters.TypeValidator = SkipValidationDelegates.SkipTokenTypeValidation;

return validationParameters;
}
}
}

public class ValidateTokenAsyncIssuerSigningKeyTheoryData : ValidateTokenAsyncBaseTheoryData
{
public ValidateTokenAsyncIssuerSigningKeyTheoryData(string testId) : base(testId) { }

public SigningCredentials? SigningCredentials { get; set; }
}

// Tokens must be signed in order to validate the issuer signing key.
// While the ValidationParameters path allows us to test the issuer signing key without a signature,
// the TokenValidationParameters path requires a signature or it will skip the issuer signing key validation.
private static string CreateTokenWithSigningCredentials(SigningCredentials? signingCredentials)
{
JsonWebTokenHandler jsonWebTokenHandler = new JsonWebTokenHandler();

SecurityTokenDescriptor securityTokenDescriptor = new SecurityTokenDescriptor
{
Subject = Default.ClaimsIdentity,
SigningCredentials = signingCredentials,
};

return jsonWebTokenHandler.CreateToken(securityTokenDescriptor);
}
}
}
#nullable restore
10 changes: 8 additions & 2 deletions test/Microsoft.IdentityModel.TestUtils/MockTimeProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,13 @@ namespace Microsoft.IdentityModel.TestUtils
{
internal class MockTimeProvider : TimeProvider
{
// always return 09/16/2024 00:00:00:00
public override DateTimeOffset GetUtcNow() => new DateTimeOffset(2024, 9, 16, 0, 0, 0, new(0));
DateTimeOffset _mockUtcNow;
public MockTimeProvider(DateTimeOffset? mockUtcNow = null)
{
// if left unspecified, it will return 09/16/2024 00:00:00:00
_mockUtcNow = mockUtcNow ?? new DateTimeOffset(2024, 9, 16, 0, 0, 0, new(0));
}

public override DateTimeOffset GetUtcNow() => _mockUtcNow;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,12 @@ namespace Microsoft.IdentityModel.Tokens.Tests
{
public class TokenValidationParametersTests
{
int ExpectedPropertyCount = 59;
int ExpectedPropertyCount = 60;

// GetSets() compares the total property count which includes internal properties, against a list of public properties, minus delegates.
// This allows us to keep track of any properties we are including in the total that are not public nor delegates.
// Remove if/once we make TimeProvider public. As the GetSets() test will fail.
List<string> internalNonDelegateProperties = new() { "TimeProvider" };

[Fact]
public void Publics()
Expand Down Expand Up @@ -236,7 +241,7 @@ public void GetSets()
};

// check that we have checked all properties, subract the number of delegates.
if (context.PropertyNamesAndSetGetValue.Count != ExpectedPropertyCount - delegates.Count)
if (context.PropertyNamesAndSetGetValue.Count != ExpectedPropertyCount - delegates.Count - internalNonDelegateProperties.Count)
compareContext.AddDiff($"Number of properties being set is: {context.PropertyNamesAndSetGetValue.Count}, number of properties is: {properties.Length - delegates.Count} (#Properties - #Delegates), adjust tests");

TestUtilities.GetSet(context);
Expand Down
Loading