From ad3901ae5f04548687836f12e014d0dc13262e0e Mon Sep 17 00:00:00 2001 From: id4s Date: Mon, 15 Jul 2024 09:59:26 -0700 Subject: [PATCH 1/2] Base for new ValidationParameters --- .../Validation/ValidationParameters.cs | 504 +++++++++++++++ .../Validators.cs | 577 ++++++++++++++++++ 2 files changed, 1081 insertions(+) create mode 100644 src/Microsoft.IdentityModel.Tokens/Validation/ValidationParameters.cs create mode 100644 src/Microsoft.IdentityModel.Tokens/Validators.cs diff --git a/src/Microsoft.IdentityModel.Tokens/Validation/ValidationParameters.cs b/src/Microsoft.IdentityModel.Tokens/Validation/ValidationParameters.cs new file mode 100644 index 0000000000..d1765eef92 --- /dev/null +++ b/src/Microsoft.IdentityModel.Tokens/Validation/ValidationParameters.cs @@ -0,0 +1,504 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Security.Claims; +using Microsoft.IdentityModel.Abstractions; +using Microsoft.IdentityModel.Logging; + +namespace Microsoft.IdentityModel.Tokens +{ + /// + /// Contains a set of parameters that are used by a when validating a . + /// + internal class ValidationParameters + { + private string _authenticationType; + private TimeSpan _clockSkew = DefaultClockSkew; + private string _nameClaimType = ClaimsIdentity.DefaultNameClaimType; + private string _roleClaimType = ClaimsIdentity.DefaultRoleClaimType; + private Dictionary _instancePropertyBag; + + private IssuerValidationDelegateAsync _issuerValidationDelegate = Validators.ValidateIssuerAsync; + + /// + /// This is the default value of when creating a . + /// The value is "AuthenticationTypes.Federation". + /// To change the value, set to a different value. + /// + public static readonly string DefaultAuthenticationType = "AuthenticationTypes.Federation"; // Note: The change was because 5.x removed the dependency on System.IdentityModel and we used a different string which was a mistake. + + /// + /// Default for the clock skew. + /// + /// 300 seconds (5 minutes). + public static readonly TimeSpan DefaultClockSkew = TimeSpan.FromSeconds(300); // 5 min. + + /// + /// Default for the maximum token size. + /// + /// 250 KB (kilobytes). + public const int DefaultMaximumTokenSizeInBytes = 1024 * 250; + + /// + /// Copy constructor for . + /// + protected ValidationParameters(ValidationParameters other) + { + if (other == null) + throw LogHelper.LogExceptionMessage(new ArgumentNullException(nameof(other))); + + AlgorithmValidator = other.AlgorithmValidator; + AudienceValidator = other.AudienceValidator; + _authenticationType = other._authenticationType; + ClockSkew = other.ClockSkew; + ConfigurationManager = other.ConfigurationManager; + DebugId = other.DebugId; + IncludeTokenOnFailedValidation = other.IncludeTokenOnFailedValidation; + IgnoreTrailingSlashWhenValidatingAudience = other.IgnoreTrailingSlashWhenValidatingAudience; + IssuerSigningKeyResolver = other.IssuerSigningKeyResolver; + IssuerSigningKeys = other.IssuerSigningKeys; + IssuerSigningKeyValidator = other.IssuerSigningKeyValidator; + IssuerValidatorAsync = other.IssuerValidatorAsync; + LifetimeValidator = other.LifetimeValidator; + LogTokenId = other.LogTokenId; + NameClaimType = other.NameClaimType; + NameClaimTypeRetriever = other.NameClaimTypeRetriever; + PropertyBag = other.PropertyBag; + RefreshBeforeValidation = other.RefreshBeforeValidation; + RoleClaimType = other.RoleClaimType; + RoleClaimTypeRetriever = other.RoleClaimTypeRetriever; + SaveSigninToken = other.SaveSigninToken; + SignatureValidator = other.SignatureValidator; + TokenDecryptionKeyResolver = other.TokenDecryptionKeyResolver; + TokenDecryptionKeys = other.TokenDecryptionKeys; + TokenReplayCache = other.TokenReplayCache; + TokenReplayValidator = other.TokenReplayValidator; + TransformBeforeSignatureValidation = other.TransformBeforeSignatureValidation; + TypeValidator = other.TypeValidator; + ValidateActor = other.ValidateActor; + ValidateSignatureLast = other.ValidateSignatureLast; + ValidateWithLKG = other.ValidateWithLKG; + ValidAlgorithms = other.ValidAlgorithms; + ValidAudiences = other.ValidAudiences; + ValidIssuers = other.ValidIssuers; + ValidTypes = other.ValidTypes; + } + + /// + /// Initializes a new instance of the class. + /// + public ValidationParameters() + { + LogTokenId = true; + SaveSigninToken = false; + ValidateActor = false; + } + + /// + /// Gets or sets . + /// + public ValidationParameters ActorValidationParameters { get; set; } + + /// + /// Gets or sets a delegate used to validate the cryptographic algorithm used. + /// + /// + /// If set, this delegate will validate the cryptographic algorithm used and + /// the algorithm will not be checked against . + /// + public AlgorithmValidator AlgorithmValidator { get; set; } + + /// + /// Gets or sets a delegate that will be used to validate the audience. + /// + /// + /// If set, this delegate will be called to validate the 'audience', instead of default processing. + /// This means that no default 'audience' validation will occur. + /// Even if is false, this delegate will still be called. + /// + public AudienceValidator AudienceValidator { get; set; } + + /// + /// Gets or sets the AuthenticationType when creating a . + /// + /// If 'value' is null or whitespace. + public string AuthenticationType + { + get + { + return _authenticationType; + } + set + { + if (string.IsNullOrWhiteSpace(value)) + { + throw LogHelper.LogExceptionMessage(new ArgumentNullException("AuthenticationType")); + } + + _authenticationType = value; + } + } + + /// + /// Gets or sets the clock skew to apply when validating a time. + /// + /// If 'value' is less than 0. + /// The default is 300 seconds (5 minutes). + [DefaultValue(300)] + public TimeSpan ClockSkew + { + get + { + return _clockSkew; + } + + set + { + if (value < TimeSpan.Zero) + throw LogHelper.LogExceptionMessage(new ArgumentOutOfRangeException(nameof(value), LogHelper.FormatInvariant(LogMessages.IDX10100, LogHelper.MarkAsNonPII(value)))); + + _clockSkew = value; + } + } + + /// + /// Returns a new instance of with values copied from this object. + /// + /// A new object copied from this object + /// This is a shallow Clone. + public virtual ValidationParameters Clone() + { + return new(this) + { + IsClone = true + }; + } + + /// + /// Creates a using: + /// + /// 'NameClaimType': If NameClaimTypeRetriever is set, call delegate, else call NameClaimType. If the result is a null or empty string, use . + /// 'RoleClaimType': If RoleClaimTypeRetriever is set, call delegate, else call RoleClaimType. If the result is a null or empty string, use . + /// + /// A with Authentication, NameClaimType and RoleClaimType set. + public virtual ClaimsIdentity CreateClaimsIdentity(SecurityToken securityToken, string issuer) + { + string nameClaimType = null; + if (NameClaimTypeRetriever != null) + { + nameClaimType = NameClaimTypeRetriever(securityToken, issuer); + } + else + { + nameClaimType = NameClaimType; + } + + string roleClaimType = null; + if (RoleClaimTypeRetriever != null) + { + roleClaimType = RoleClaimTypeRetriever(securityToken, issuer); + } + else + { + roleClaimType = RoleClaimType; + } + + if (LogHelper.IsEnabled(EventLogLevel.Informational)) + LogHelper.LogInformation(LogMessages.IDX10245, securityToken); + + return new ClaimsIdentity(authenticationType: AuthenticationType ?? DefaultAuthenticationType, nameType: nameClaimType ?? ClaimsIdentity.DefaultNameClaimType, roleType: roleClaimType ?? ClaimsIdentity.DefaultRoleClaimType); + } + + /// + /// If set, this property will be used to obtain the issuer and signing keys associated with the metadata endpoint of . + /// The obtained issuer and signing keys will then be used along with those present on the ValidationParameters for validation of the incoming token. + /// + public BaseConfigurationManager ConfigurationManager { get; set; } + + /// + /// Users can override the default with this property. This factory will be used for creating signature providers. + /// + public CryptoProviderFactory CryptoProviderFactory { get; set; } + + /// + /// Gets or sets a string that helps with setting breakpoints when debugging. + /// + public string DebugId { get; set; } + + /// + /// Gets or sets a boolean that controls if a '/' is significant at the end of the audience. + /// The default is true. + /// + [DefaultValue(true)] + public bool IgnoreTrailingSlashWhenValidatingAudience { get; set; } = true; + + /// + /// Gets or sets the flag that indicates whether to include the when the validation fails. + /// + public bool IncludeTokenOnFailedValidation { get; set; } = false; + + /// + /// Gets or sets a delegate for validating the that signed the token. + /// + /// + /// If set, this delegate will be called to validate the that signed the token, instead of default processing. + /// This means that no default validation will occur. + /// Even if is false, this delegate will still be called. + /// If both and are set, IssuerSigningKeyResolverUsingConfiguration takes + /// priority. + /// + public IssuerSigningKeyValidator IssuerSigningKeyValidator { get; set; } + + /// + /// Gets a that is unique to this instance. + /// Calling will result in a new instance of this IDictionary. + /// + public IDictionary InstancePropertyBag => _instancePropertyBag ??= new Dictionary(); + + /// + /// Gets a value indicating if was called to obtain this instance. + /// + public bool IsClone { get; protected set; } = false; + + /// + /// Gets or sets a delegate that will be called to retrieve a used for signature validation. + /// + /// + /// This will be used to check the signature. This can be helpful when the does not contain a key identifier. + /// If both and are set, IssuerSigningKeyResolverUsingConfiguration takes + /// priority. + /// + public IssuerSigningKeyResolverDelegate IssuerSigningKeyResolver { get; set; } + + /// + /// Gets or sets an used for signature validation. + /// + public IList IssuerSigningKeys { get; } + + /// + /// Gets or sets a delegate that will be used to validate the issuer of the token. + /// + public IssuerValidationDelegateAsync IssuerValidatorAsync + { + get + { + return _issuerValidationDelegate; + } + set + { + _issuerValidationDelegate = value ?? throw LogHelper.LogArgumentNullException(nameof(value)); + } + } + + /// + /// Gets or sets a delegate that will be called to transform a token to a supported format before validation. + /// + public TransformBeforeSignatureValidation TransformBeforeSignatureValidation { get; set; } + + /// + /// Gets or sets a delegate that will be used to validate the lifetime of the token + /// + public LifetimeValidator LifetimeValidator { get; set; } + + /// + /// Gets or sets a that will decide if the token identifier claim needs to be logged. + /// Default value is true. + /// + [DefaultValue(true)] + public bool LogTokenId { get; set; } + + /// + /// Gets or sets a that defines the . + /// + /// + /// Controls the value returns. It will return the first where the equals . + /// The default is . + /// + public string NameClaimType + { + get + { + return _nameClaimType; + } + + set + { + if (string.IsNullOrWhiteSpace(value)) + throw LogHelper.LogExceptionMessage(new ArgumentOutOfRangeException(nameof(value), LogMessages.IDX10102)); + + _nameClaimType = value; + } + } + + /// + /// Gets or sets a delegate that will be called to set the property after validating a token. + /// + /// + /// The function will be passed: + /// The that is being validated. + /// The issuer associated with the token. + /// Returns the value that will set the property . + /// + public Func NameClaimTypeRetriever { get; set; } + + /// + /// Gets or sets the that contains a collection of custom key/value pairs. + /// This allows addition of parameters that could be used in custom token validation scenarios. + /// + public IDictionary PropertyBag { get; } + + /// + /// Gets or sets a boolean to control if configuration required to be refreshed before token validation. + /// + /// + /// The default is false. + /// + [DefaultValue(false)] + public bool RefreshBeforeValidation { get; set; } + + /// + /// Gets or sets the that defines the . + /// + /// + /// Controls the results of . + /// Each where == will be checked for a match against the 'string' passed to . + /// The default is . + /// + public string RoleClaimType + { + get + { + return _roleClaimType; + } + + set + { + if (string.IsNullOrWhiteSpace(value)) + throw LogHelper.LogExceptionMessage(new ArgumentOutOfRangeException(nameof(value), LogMessages.IDX10103)); + + _roleClaimType = value; + } + } + + /// + /// Gets or sets a delegate that will be called to set the property after validating a token. + /// + /// + /// The function will be passed: + /// The that is being validated. + /// The issuer associated with the token. + /// Returns the value that will set the property . + /// + public Func RoleClaimTypeRetriever { get; set; } + + /// + /// Gets or sets a boolean to control if the original token should be saved after the security token is validated. + /// + /// The runtime will consult this value and save the original token that was validated. + /// The default is false. + /// + [DefaultValue(false)] + public bool SaveSigninToken { get; set; } + + /// + /// Gets or sets a delegate that will be used to validate the signature of the token. + /// + /// + /// If set, this delegate will be called to validate the signature of the token, instead of default processing. + /// + public SignatureValidationDelegate SignatureValidator { get; set; } + + /// + /// Gets or sets a delegate that will be called to retreive a used for decryption. + /// + /// + /// This will be used to decrypt the token. This can be helpful when the does not contain a key identifier. + /// + public TokenDecryptionKeyResolver TokenDecryptionKeyResolver { get; set; } + + /// + /// Gets the that is to be used for decrypting inbound tokens. + /// + public IList TokenDecryptionKeys { get; } + + /// + /// Gets or set the that store tokens that can be checked to help detect token replay. + /// + /// If set, then tokens must have an expiration time or the runtime will fault. + public ITokenReplayCache TokenReplayCache { get; set; } + + /// + /// Gets or sets a delegate that will be used to validate the token replay of the token + /// + /// + /// If set, this delegate will be called to validate the token replay of the token, instead of default processing. + /// This means no default token replay validation will occur. + /// Even if is false, this delegate will still be called. + /// + public TokenReplayValidator TokenReplayValidator { get; set; } + + /// + /// Gets or sets a delegate that will be used to validate the type of the token. + /// If the token type cannot be validated, an exception MUST be thrown by the delegate. + /// Note: the 'type' parameter may be null if it couldn't be extracted from its usual location. + /// Implementations that need to resolve it from a different location can use the 'token' parameter. + /// + /// + /// If set, this delegate will be called to validate the 'type' of the token, instead of default processing. + /// This means that no default 'type' validation will occur. + /// + public TypeValidator TypeValidator { get; set; } + + /// + /// Gets or sets a boolean to control if the LKG configuration will be used for token validation. + /// + /// + /// The default is false. + /// + [DefaultValue(false)] + public bool ValidateWithLKG { get; set; } + + /// + /// Gets or sets a boolean that controls the validation order of the payload and signature during token validation. + /// + /// If is set to true, it will validate payload ahead of signature. + /// The default is false. + /// + [DefaultValue(false)] + public bool ValidateSignatureLast { get; set; } + + /// + /// Gets or sets the valid algorithms for cryptographic operations. + /// + /// + /// If set to a non-empty collection, only the algorithms listed will be considered valid. + /// The default is null. + /// + public IList ValidAlgorithms { get; } + + /// + /// Gets the that contains valid audiences that will be used to check against the token's audience. + /// The default is null. + /// + public IList ValidAudiences { get; } + + /// + /// Gets the that contains valid issuers that will be used to check against the token's issuer. + /// The default is null. + /// + public IList ValidIssuers { get; } + + /// + /// Gets the that contains valid types that will be used to check against the JWT header's 'typ' claim. + /// If this property is not set, the 'typ' header claim will not be validated and all types will be accepted. + /// In the case of a JWE, this property will ONLY apply to the inner token header. + /// The default is null. + /// + public IList ValidTypes { get; } + + public bool ValidateActor { get; set; } + } +} diff --git a/src/Microsoft.IdentityModel.Tokens/Validators.cs b/src/Microsoft.IdentityModel.Tokens/Validators.cs new file mode 100644 index 0000000000..2c9f9e6ca1 --- /dev/null +++ b/src/Microsoft.IdentityModel.Tokens/Validators.cs @@ -0,0 +1,577 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Security.Cryptography.X509Certificates; +using System.Threading.Tasks; +using Microsoft.IdentityModel.Abstractions; +using Microsoft.IdentityModel.Logging; + +namespace Microsoft.IdentityModel.Tokens +{ + /// + /// AudienceValidator + /// + public static class Validators + { + /// + /// Validates if a given algorithm for a is valid. + /// + /// The algorithm to be validated. + /// The that signed the . + /// The being validated. + /// required for validation. + public static void ValidateAlgorithm(string algorithm, SecurityKey securityKey, SecurityToken securityToken, TokenValidationParameters validationParameters) + { + if (validationParameters == null) + throw LogHelper.LogArgumentNullException(nameof(validationParameters)); + + if (validationParameters.AlgorithmValidator != null) + { + if (!validationParameters.AlgorithmValidator(algorithm, securityKey, securityToken, validationParameters)) + { + throw LogHelper.LogExceptionMessage(new SecurityTokenInvalidAlgorithmException(LogHelper.FormatInvariant(LogMessages.IDX10697, LogHelper.MarkAsNonPII(algorithm), securityKey)) + { + InvalidAlgorithm = algorithm, + }); + } + + return; + } + + if (validationParameters.ValidAlgorithms != null && validationParameters.ValidAlgorithms.Any() && !validationParameters.ValidAlgorithms.Contains(algorithm, StringComparer.Ordinal)) + { + throw LogHelper.LogExceptionMessage(new SecurityTokenInvalidAlgorithmException(LogHelper.FormatInvariant(LogMessages.IDX10696, LogHelper.MarkAsNonPII(algorithm))) + { + InvalidAlgorithm = algorithm, + }); + } + } + + /// + /// Determines if the audiences found in a are valid. + /// + /// The audiences found in the . + /// The being validated. + /// required for validation. + /// If 'validationParameters' is null. + /// If 'audiences' is null and is true. + /// If is null or whitespace and is null. + /// If none of the 'audiences' matched either or one of . + /// An EXACT match is required. + public static void ValidateAudience(IEnumerable audiences, SecurityToken securityToken, TokenValidationParameters validationParameters) + { + if (validationParameters == null) + throw LogHelper.LogArgumentNullException(nameof(validationParameters)); + + if (validationParameters.AudienceValidator != null) + { + if (!validationParameters.AudienceValidator(audiences, securityToken, validationParameters)) + throw LogHelper.LogExceptionMessage( + new SecurityTokenInvalidAudienceException( + LogHelper.FormatInvariant( + LogMessages.IDX10231, + LogHelper.MarkAsUnsafeSecurityArtifact(securityToken, t => t.ToString()))) + { + InvalidAudience = Utility.SerializeAsSingleCommaDelimitedString(audiences) + }); + + return; + } + + if (!validationParameters.ValidateAudience) + { + LogHelper.LogWarning(LogMessages.IDX10233); + return; + } + + if (audiences == null) + throw LogHelper.LogExceptionMessage(new SecurityTokenInvalidAudienceException(LogMessages.IDX10207) { InvalidAudience = null }); + + if (string.IsNullOrWhiteSpace(validationParameters.ValidAudience) && (validationParameters.ValidAudiences == null)) + throw LogHelper.LogExceptionMessage(new SecurityTokenInvalidAudienceException(LogMessages.IDX10208) { InvalidAudience = Utility.SerializeAsSingleCommaDelimitedString(audiences) }); + + if (!audiences.Any()) + throw LogHelper.LogExceptionMessage( + new SecurityTokenInvalidAudienceException(LogHelper.FormatInvariant(LogMessages.IDX10206)) + { InvalidAudience = Utility.SerializeAsSingleCommaDelimitedString(audiences) }); + + // create enumeration of all valid audiences from validationParameters + IEnumerable validationParametersAudiences; + + if (validationParameters.ValidAudiences == null) + validationParametersAudiences = new[] { validationParameters.ValidAudience }; + else if (string.IsNullOrWhiteSpace(validationParameters.ValidAudience)) + validationParametersAudiences = validationParameters.ValidAudiences; + else + validationParametersAudiences = validationParameters.ValidAudiences.Concat(new[] { validationParameters.ValidAudience }); + + if (AudienceIsValid(audiences, validationParameters, validationParametersAudiences)) + return; + + SecurityTokenInvalidAudienceException ex = new SecurityTokenInvalidAudienceException( + LogHelper.FormatInvariant(LogMessages.IDX10214, + LogHelper.MarkAsNonPII(Utility.SerializeAsSingleCommaDelimitedString(audiences)), + LogHelper.MarkAsNonPII(validationParameters.ValidAudience ?? "null"), + LogHelper.MarkAsNonPII(Utility.SerializeAsSingleCommaDelimitedString(validationParameters.ValidAudiences)))) + { InvalidAudience = Utility.SerializeAsSingleCommaDelimitedString(audiences) }; + + if (!validationParameters.LogValidationExceptions) + throw ex; + + throw LogHelper.LogExceptionMessage(ex); + } + + private static bool AudienceIsValid(IEnumerable audiences, TokenValidationParameters validationParameters, IEnumerable validationParametersAudiences) + { + foreach (string tokenAudience in audiences) + { + if (string.IsNullOrWhiteSpace(tokenAudience)) + continue; + + foreach (string validAudience in validationParametersAudiences) + { + if (string.IsNullOrWhiteSpace(validAudience)) + continue; + + if (AudiencesMatch(validationParameters, tokenAudience, validAudience)) + { + if (LogHelper.IsEnabled(EventLogLevel.Informational)) + LogHelper.LogInformation(LogMessages.IDX10234, LogHelper.MarkAsNonPII(tokenAudience)); + + return true; + } + } + } + + return false; + } + + private static bool AudiencesMatch(TokenValidationParameters validationParameters, string tokenAudience, string validAudience) + { + if (validAudience.Length == tokenAudience.Length) + { + if (string.Equals(validAudience, tokenAudience)) + return true; + } + else if (validationParameters.IgnoreTrailingSlashWhenValidatingAudience && AudiencesMatchIgnoringTrailingSlash(tokenAudience, validAudience)) + return true; + + return false; + } + + private static bool AudiencesMatchIgnoringTrailingSlash(string tokenAudience, string validAudience) + { + int length = -1; + + if (validAudience.Length == tokenAudience.Length + 1 && validAudience.EndsWith("/", StringComparison.InvariantCulture)) + length = validAudience.Length - 1; + else if (tokenAudience.Length == validAudience.Length + 1 && tokenAudience.EndsWith("/", StringComparison.InvariantCulture)) + length = tokenAudience.Length - 1; + + // the length of the audiences is different by more than 1 and neither ends in a "/" + if (length == -1) + return false; + + if (string.CompareOrdinal(validAudience, 0, tokenAudience, 0, length) == 0) + { + if (LogHelper.IsEnabled(EventLogLevel.Informational)) + LogHelper.LogInformation(LogMessages.IDX10234, LogHelper.MarkAsNonPII(tokenAudience)); + + return true; + } + + return false; + } + + /// + /// Determines if an issuer found in a is valid. + /// + /// The issuer to validate + /// The that is being validated. + /// required for validation. + /// The issuer to use when creating the "Claim"(s) in a "ClaimsIdentity". + /// If 'validationParameters' is null. + /// If 'issuer' is null or whitespace and is true. + /// If is null or whitespace and is null. + /// If 'issuer' failed to matched either or one of . + /// An EXACT match is required. + public static string ValidateIssuer(string issuer, SecurityToken securityToken, TokenValidationParameters validationParameters) + { + return ValidateIssuer(issuer, securityToken, validationParameters, null); + } + + /// + /// Determines if an issuer found in a is valid. + /// + /// The issuer to validate + /// The that is being validated. + /// required for validation. + /// The required for issuer and signing key validation. + /// The issuer to use when creating the "Claim"(s) in a "ClaimsIdentity". + /// If 'validationParameters' is null. + /// If 'issuer' is null or whitespace and is true. + /// If ' configuration' is null. + /// If is null or whitespace and is null and is null. + /// If 'issuer' failed to matched either or one of or . + /// An EXACT match is required. + internal static string ValidateIssuer(string issuer, SecurityToken securityToken, TokenValidationParameters validationParameters, BaseConfiguration configuration) + { + ValueTask vt = ValidateIssuerAsync(issuer, securityToken, validationParameters, configuration); + return vt.IsCompletedSuccessfully ? + vt.Result : + vt.AsTask().ConfigureAwait(false).GetAwaiter().GetResult(); + } + + /// + /// Determines if an issuer found in a is valid. + /// + /// The issuer to validate + /// The that is being validated. + /// required for validation. + /// The required for issuer and signing key validation. + /// The issuer to use when creating the "Claim"(s) in a "ClaimsIdentity". + /// If 'validationParameters' is null. + /// If 'issuer' is null or whitespace and is true. + /// If ' configuration' is null. + /// If is null or whitespace and is null and is null. + /// If 'issuer' failed to matched either or one of or . + /// An EXACT match is required. + internal static async ValueTask ValidateIssuerAsync( + string issuer, + SecurityToken securityToken, + TokenValidationParameters validationParameters, + BaseConfiguration configuration) + { + if (validationParameters == null) + throw LogHelper.LogArgumentNullException(nameof(validationParameters)); + + if (validationParameters.IssuerValidatorAsync != null) + return await validationParameters.IssuerValidatorAsync(issuer, securityToken, validationParameters).ConfigureAwait(false); + + if (validationParameters.IssuerValidatorUsingConfiguration != null) + return validationParameters.IssuerValidatorUsingConfiguration(issuer, securityToken, validationParameters, configuration); + + if (validationParameters.IssuerValidator != null) + return validationParameters.IssuerValidator(issuer, securityToken, validationParameters); + + if (!validationParameters.ValidateIssuer) + { + LogHelper.LogWarning(LogMessages.IDX10235); + return issuer; + } + + if (string.IsNullOrWhiteSpace(issuer)) + throw LogHelper.LogExceptionMessage(new SecurityTokenInvalidIssuerException(LogMessages.IDX10211) + { InvalidIssuer = issuer }); + + // Throw if all possible places to validate against are null or empty + if ( string.IsNullOrWhiteSpace(validationParameters.ValidIssuer) + && validationParameters.ValidIssuers.IsNullOrEmpty() + && string.IsNullOrWhiteSpace(configuration?.Issuer)) + throw LogHelper.LogExceptionMessage(new SecurityTokenInvalidIssuerException(LogMessages.IDX10204) + { InvalidIssuer = issuer }); + + if (configuration != null) + { + if (string.Equals(configuration.Issuer, issuer)) + { + if (LogHelper.IsEnabled(EventLogLevel.Informational)) + LogHelper.LogInformation(LogMessages.IDX10236, LogHelper.MarkAsNonPII(issuer)); + + return issuer; + } + } + + if (string.Equals(validationParameters.ValidIssuer, issuer)) + { + if (LogHelper.IsEnabled(EventLogLevel.Informational)) + LogHelper.LogInformation(LogMessages.IDX10236, LogHelper.MarkAsNonPII(issuer)); + + return issuer; + } + + if (validationParameters.ValidIssuers != null) + { + foreach (string str in validationParameters.ValidIssuers) + { + if (string.IsNullOrEmpty(str)) + { + LogHelper.LogInformation(LogMessages.IDX10262); + continue; + } + + if (string.Equals(str, issuer)) + { + if (LogHelper.IsEnabled(EventLogLevel.Informational)) + LogHelper.LogInformation(LogMessages.IDX10236, LogHelper.MarkAsNonPII(issuer)); + + return issuer; + } + } + } + + SecurityTokenInvalidIssuerException ex = new SecurityTokenInvalidIssuerException( + LogHelper.FormatInvariant(LogMessages.IDX10205, + LogHelper.MarkAsNonPII(issuer), + LogHelper.MarkAsNonPII(validationParameters.ValidIssuer ?? "null"), + LogHelper.MarkAsNonPII(Utility.SerializeAsSingleCommaDelimitedString(validationParameters.ValidIssuers)), + LogHelper.MarkAsNonPII(configuration?.Issuer))) + { InvalidIssuer = issuer }; + + if (!validationParameters.LogValidationExceptions) + throw ex; + + throw LogHelper.LogExceptionMessage(ex); + } + + /// + /// Validates the that signed a . + /// + /// The that signed the . + /// The being validated. + /// required for validation. + /// if 'securityKey' is null and ValidateIssuerSigningKey is true. + /// if 'securityToken' is null and ValidateIssuerSigningKey is true. + /// if 'validationParameters' is null. + public static void ValidateIssuerSecurityKey(SecurityKey securityKey, SecurityToken securityToken, TokenValidationParameters validationParameters) + { + ValidateIssuerSecurityKey(securityKey, securityToken, validationParameters, null); + } + + /// + /// Validates the that signed a . + /// + /// The that signed the . + /// The being validated. + /// required for validation. + /// The required for issuer and signing key validation. + /// if 'securityKey' is null and ValidateIssuerSigningKey is true. + /// if 'securityToken' is null and ValidateIssuerSigningKey is true. + /// if 'validationParameters' is null. + internal static void ValidateIssuerSecurityKey(SecurityKey securityKey, SecurityToken securityToken, TokenValidationParameters validationParameters, BaseConfiguration configuration) + { + if (validationParameters == null) + throw LogHelper.LogArgumentNullException(nameof(validationParameters)); + + if (validationParameters.IssuerSigningKeyValidatorUsingConfiguration != null) + { + if (!validationParameters.IssuerSigningKeyValidatorUsingConfiguration(securityKey, securityToken, validationParameters, configuration)) + throw LogHelper.LogExceptionMessage(new SecurityTokenInvalidSigningKeyException(LogHelper.FormatInvariant(LogMessages.IDX10232, securityKey)) { SigningKey = securityKey }); + + return; + } + + if (validationParameters.IssuerSigningKeyValidator != null) + { + if (!validationParameters.IssuerSigningKeyValidator(securityKey, securityToken, validationParameters)) + throw LogHelper.LogExceptionMessage(new SecurityTokenInvalidSigningKeyException(LogHelper.FormatInvariant(LogMessages.IDX10232, securityKey)) { SigningKey = securityKey }); + + return; + } + + if (!validationParameters.ValidateIssuerSigningKey) + { + LogHelper.LogVerbose(LogMessages.IDX10237); + return; + } + + if (!validationParameters.RequireSignedTokens && securityKey == null) + { + LogHelper.LogInformation(LogMessages.IDX10252); + return; + } + else if (securityKey == null) + { + throw LogHelper.LogExceptionMessage(new ArgumentNullException(nameof(securityKey), LogMessages.IDX10253)); + } + + if (securityToken == null) + throw LogHelper.LogArgumentNullException(nameof(securityToken)); + + ValidateIssuerSigningKeyLifeTime(securityKey, validationParameters); + } + + /// + /// Given a signing key, when it's derived from a certificate, validates that the certificate is already active and non-expired + /// + /// The that signed the . + /// The that are used to validate the token. + internal static void ValidateIssuerSigningKeyLifeTime(SecurityKey securityKey, TokenValidationParameters validationParameters) + { + X509SecurityKey x509SecurityKey = securityKey as X509SecurityKey; + if (x509SecurityKey?.Certificate is X509Certificate2 cert) + { + DateTime utcNow = DateTime.UtcNow; + var notBeforeUtc = cert.NotBefore.ToUniversalTime(); + var notAfterUtc = cert.NotAfter.ToUniversalTime(); + + if (notBeforeUtc > DateTimeUtil.Add(utcNow, validationParameters.ClockSkew)) + throw LogHelper.LogExceptionMessage(new SecurityTokenInvalidSigningKeyException(LogHelper.FormatInvariant(LogMessages.IDX10248, LogHelper.MarkAsNonPII(notBeforeUtc), LogHelper.MarkAsNonPII(utcNow)))); + + if (LogHelper.IsEnabled(EventLogLevel.Informational)) + LogHelper.LogInformation(LogMessages.IDX10250, LogHelper.MarkAsNonPII(notBeforeUtc), LogHelper.MarkAsNonPII(utcNow)); + + if (notAfterUtc < DateTimeUtil.Add(utcNow, validationParameters.ClockSkew.Negate())) + throw LogHelper.LogExceptionMessage(new SecurityTokenInvalidSigningKeyException(LogHelper.FormatInvariant(LogMessages.IDX10249, LogHelper.MarkAsNonPII(notAfterUtc), LogHelper.MarkAsNonPII(utcNow)))); + + if (LogHelper.IsEnabled(EventLogLevel.Informational)) + LogHelper.LogInformation(LogMessages.IDX10251, LogHelper.MarkAsNonPII(notAfterUtc), LogHelper.MarkAsNonPII(utcNow)); + } + } + + /// + /// Validates the lifetime of a . + /// + /// The 'notBefore' time found in the . + /// The 'expiration' time found in the . + /// The being validated. + /// required for validation. + /// If 'validationParameters' is null. + /// If 'expires.HasValue' is false and is true. + /// If 'notBefore' is > 'expires'. + /// If 'notBefore' is > DateTime.UtcNow. + /// If 'expires' is < DateTime.UtcNow. + /// All time comparisons apply . + public static void ValidateLifetime(DateTime? notBefore, DateTime? expires, SecurityToken securityToken, TokenValidationParameters validationParameters) + { + if (validationParameters == null) + throw LogHelper.LogArgumentNullException(nameof(validationParameters)); + + if (validationParameters.LifetimeValidator != null) + { + if (!validationParameters.LifetimeValidator(notBefore, expires, securityToken, validationParameters)) + throw LogHelper.LogExceptionMessage(new SecurityTokenInvalidLifetimeException(LogHelper.FormatInvariant(LogMessages.IDX10230, securityToken)) + { NotBefore = notBefore, Expires = expires }); + + return; + } + + if (!validationParameters.ValidateLifetime) + { + LogHelper.LogInformation(LogMessages.IDX10238); + return; + } + + ValidatorUtilities.ValidateLifetime(notBefore, expires, securityToken, validationParameters); + } + + /// + /// Validates if a token has been replayed. + /// + /// When does the security token expire. + /// The being validated. + /// required for validation. + /// If 'securityToken' is null or whitespace. + /// If 'validationParameters' is null or whitespace. + /// If is not null and expirationTime.HasValue is false. When a TokenReplayCache is set, tokens require an expiration time. + /// If the 'securityToken' is found in the cache. + /// If the 'securityToken' could not be added to the . + public static void ValidateTokenReplay(DateTime? expirationTime, string securityToken, TokenValidationParameters validationParameters) + { + if (string.IsNullOrWhiteSpace(securityToken)) + throw LogHelper.LogArgumentNullException(nameof(securityToken)); + + if (validationParameters == null) + throw LogHelper.LogArgumentNullException(nameof(validationParameters)); + + if (validationParameters.TokenReplayValidator != null) + { + if (!validationParameters.TokenReplayValidator(expirationTime, securityToken, validationParameters)) + throw LogHelper.LogExceptionMessage(new SecurityTokenReplayDetectedException( + LogHelper.FormatInvariant( + LogMessages.IDX10228, + LogHelper.MarkAsUnsafeSecurityArtifact(securityToken, t => t.ToString())))); + return; + } + + if (!validationParameters.ValidateTokenReplay) + { + LogHelper.LogVerbose(LogMessages.IDX10246); + return; + } + + // check if token if replay cache is set, then there must be an expiration time. + if (validationParameters.TokenReplayCache != null) + { + if (!expirationTime.HasValue) + throw LogHelper.LogExceptionMessage(new SecurityTokenNoExpirationException(LogHelper.FormatInvariant(LogMessages.IDX10227, securityToken))); + + if (validationParameters.TokenReplayCache.TryFind(securityToken)) + throw LogHelper.LogExceptionMessage(new SecurityTokenReplayDetectedException(LogHelper.FormatInvariant(LogMessages.IDX10228, securityToken))); + + if (!validationParameters.TokenReplayCache.TryAdd(securityToken, expirationTime.Value)) + throw LogHelper.LogExceptionMessage(new SecurityTokenReplayAddFailedException(LogHelper.FormatInvariant(LogMessages.IDX10229, securityToken))); + } + + // if it reaches here, that means no token replay is detected. + LogHelper.LogInformation(LogMessages.IDX10240); + } + + /// + /// Validates if a token has been replayed. + /// + /// The being validated. + /// When does the security token expire. + /// required for validation. + /// If 'securityToken' is null or whitespace. + /// If 'validationParameters' is null or whitespace. + /// If is not null and expirationTime.HasValue is false. When a TokenReplayCache is set, tokens require an expiration time. + /// If the 'securityToken' is found in the cache. + /// If the 'securityToken' could not be added to the . + public static void ValidateTokenReplay(string securityToken, DateTime? expirationTime, TokenValidationParameters validationParameters) + { + ValidateTokenReplay(expirationTime, securityToken, validationParameters); + } + + /// + /// Validates the type of the token. + /// + /// The token type or null if it couldn't be resolved (e.g from the 'typ' header for a JWT). + /// The that is being validated. + /// required for validation. + /// If is null. + /// If is null. + /// If is null or whitespace and is not null. + /// If failed to match . + /// An EXACT match is required. (case sensitive) is used for comparing against . + /// The actual token type, that may be the same as or a different value if the token type was resolved from a different location. + public static string ValidateTokenType(string type, SecurityToken securityToken, TokenValidationParameters validationParameters) + { + if (securityToken == null) + throw new ArgumentNullException(nameof(securityToken)); + + if (validationParameters == null) + throw LogHelper.LogArgumentNullException(nameof(validationParameters)); + + if (validationParameters.TypeValidator == null && (validationParameters.ValidTypes == null || !validationParameters.ValidTypes.Any())) + { + LogHelper.LogVerbose(LogMessages.IDX10255); + return type; + } + + if (validationParameters.TypeValidator != null) + return validationParameters.TypeValidator(type, securityToken, validationParameters); + + // Note: don't throw an exception for a null or empty token type when a user-defined delegate is set + // to allow it to extract the actual token type from a different location (e.g from the claims). + if (string.IsNullOrEmpty(type)) + throw LogHelper.LogExceptionMessage(new SecurityTokenInvalidTypeException(LogMessages.IDX10256) { InvalidType = null }); + + if (!validationParameters.ValidTypes.Contains(type, StringComparer.Ordinal)) + { + throw LogHelper.LogExceptionMessage( + new SecurityTokenInvalidTypeException(LogHelper.FormatInvariant(LogMessages.IDX10257, LogHelper.MarkAsNonPII(type), Utility.SerializeAsSingleCommaDelimitedString(validationParameters.ValidTypes))) + { InvalidType = type }); + } + + // if it reaches here, token type was succcessfully validated. + if (LogHelper.IsEnabled(EventLogLevel.Informational)) + LogHelper.LogInformation(LogMessages.IDX10258, LogHelper.MarkAsNonPII(type)); + + return type; + } + } +} From fee3a4eec0b0f2fc608d5bd4ddce50e49d331127 Mon Sep 17 00:00:00 2001 From: Ignacio Inglese Date: Mon, 15 Jul 2024 18:23:44 +0100 Subject: [PATCH 2/2] Clean up new files. Keep the original validator code in Validators.cs --- .../Validation/ValidationParameters.cs | 7 +- .../Validation/Validators.Algorithm.cs | 33 ---- .../Validation/Validators.Audience.cs | 72 +-------- .../Validation/Validators.Issuer.cs | 141 ------------------ .../Validators.IssuerSecurityKey.cs | 95 ------------ .../Validation/Validators.Lifetime.cs | 36 ----- .../Validation/Validators.TokenReplay.cs | 68 --------- .../Validation/Validators.TokenType.cs | 48 ------ .../Validators.cs | 4 +- 9 files changed, 7 insertions(+), 497 deletions(-) diff --git a/src/Microsoft.IdentityModel.Tokens/Validation/ValidationParameters.cs b/src/Microsoft.IdentityModel.Tokens/Validation/ValidationParameters.cs index d1765eef92..15b26f04a1 100644 --- a/src/Microsoft.IdentityModel.Tokens/Validation/ValidationParameters.cs +++ b/src/Microsoft.IdentityModel.Tokens/Validation/ValidationParameters.cs @@ -28,7 +28,7 @@ internal class ValidationParameters /// The value is "AuthenticationTypes.Federation". /// To change the value, set to a different value. /// - public static readonly string DefaultAuthenticationType = "AuthenticationTypes.Federation"; // Note: The change was because 5.x removed the dependency on System.IdentityModel and we used a different string which was a mistake. + public const string DefaultAuthenticationType = "AuthenticationTypes.Federation"; // Note: The change was because 5.x removed the dependency on System.IdentityModel and we used a different string which was a mistake. /// /// Default for the clock skew. @@ -246,7 +246,6 @@ public virtual ClaimsIdentity CreateClaimsIdentity(SecurityToken securityToken, /// /// If set, this delegate will be called to validate the that signed the token, instead of default processing. /// This means that no default validation will occur. - /// Even if is false, this delegate will still be called. /// If both and are set, IssuerSigningKeyResolverUsingConfiguration takes /// priority. /// @@ -271,7 +270,7 @@ public virtual ClaimsIdentity CreateClaimsIdentity(SecurityToken securityToken, /// If both and are set, IssuerSigningKeyResolverUsingConfiguration takes /// priority. /// - public IssuerSigningKeyResolverDelegate IssuerSigningKeyResolver { get; set; } + public IssuerSigningKeyResolver IssuerSigningKeyResolver { get; set; } /// /// Gets or sets an used for signature validation. @@ -409,7 +408,7 @@ public string RoleClaimType /// /// If set, this delegate will be called to validate the signature of the token, instead of default processing. /// - public SignatureValidationDelegate SignatureValidator { get; set; } + public SignatureValidator SignatureValidator { get; set; } /// /// Gets or sets a delegate that will be called to retreive a used for decryption. diff --git a/src/Microsoft.IdentityModel.Tokens/Validation/Validators.Algorithm.cs b/src/Microsoft.IdentityModel.Tokens/Validation/Validators.Algorithm.cs index 8a118f81a0..445d60c998 100644 --- a/src/Microsoft.IdentityModel.Tokens/Validation/Validators.Algorithm.cs +++ b/src/Microsoft.IdentityModel.Tokens/Validation/Validators.Algorithm.cs @@ -9,38 +9,5 @@ namespace Microsoft.IdentityModel.Tokens { public static partial class Validators { - /// - /// Validates if a given algorithm for a is valid. - /// - /// The algorithm to be validated. - /// The that signed the . - /// The being validated. - /// The to be used for validating the token. - public static void ValidateAlgorithm(string algorithm, SecurityKey securityKey, SecurityToken securityToken, TokenValidationParameters validationParameters) - { - if (validationParameters == null) - throw LogHelper.LogArgumentNullException(nameof(validationParameters)); - - if (validationParameters.AlgorithmValidator != null) - { - if (!validationParameters.AlgorithmValidator(algorithm, securityKey, securityToken, validationParameters)) - { - throw LogHelper.LogExceptionMessage(new SecurityTokenInvalidAlgorithmException(LogHelper.FormatInvariant(LogMessages.IDX10697, LogHelper.MarkAsNonPII(algorithm), securityKey)) - { - InvalidAlgorithm = algorithm, - }); - } - - return; - } - - if (validationParameters.ValidAlgorithms != null && validationParameters.ValidAlgorithms.Any() && !validationParameters.ValidAlgorithms.Contains(algorithm, StringComparer.Ordinal)) - { - throw LogHelper.LogExceptionMessage(new SecurityTokenInvalidAlgorithmException(LogHelper.FormatInvariant(LogMessages.IDX10696, LogHelper.MarkAsNonPII(algorithm))) - { - InvalidAlgorithm = algorithm, - }); - } - } } } diff --git a/src/Microsoft.IdentityModel.Tokens/Validation/Validators.Audience.cs b/src/Microsoft.IdentityModel.Tokens/Validation/Validators.Audience.cs index e61e77651f..ffb299af33 100644 --- a/src/Microsoft.IdentityModel.Tokens/Validation/Validators.Audience.cs +++ b/src/Microsoft.IdentityModel.Tokens/Validation/Validators.Audience.cs @@ -31,74 +31,6 @@ internal delegate AudienceValidationResult ValidateAudience( /// public static partial class Validators { - /// - /// Determines if the audiences found in a are valid. - /// - /// The audiences found in the . - /// The being validated. - /// The to be used for validating the token. - /// If 'validationParameters' is null. - /// If 'audiences' is null and is true. - /// If is null or whitespace and is null. - /// If none of the 'audiences' matched either or one of . - /// An EXACT match is required. - public static void ValidateAudience(IEnumerable audiences, SecurityToken securityToken, TokenValidationParameters validationParameters) - { - if (validationParameters == null) - throw LogHelper.LogArgumentNullException(nameof(validationParameters)); - - if (validationParameters.AudienceValidator != null) - { - if (!validationParameters.AudienceValidator(audiences, securityToken, validationParameters)) - throw LogHelper.LogExceptionMessage( - new SecurityTokenInvalidAudienceException( - LogHelper.FormatInvariant( - LogMessages.IDX10231, - LogHelper.MarkAsUnsafeSecurityArtifact(securityToken, t => t.ToString()))) - { - InvalidAudience = Utility.SerializeAsSingleCommaDelimitedString(audiences) - }); - - return; - } - - if (!validationParameters.ValidateAudience) - { - LogHelper.LogWarning(LogMessages.IDX10233); - return; - } - - if (audiences == null) - throw LogHelper.LogExceptionMessage(new SecurityTokenInvalidAudienceException(LogMessages.IDX10207) { InvalidAudience = null }); - - if (string.IsNullOrWhiteSpace(validationParameters.ValidAudience) && (validationParameters.ValidAudiences == null)) - throw LogHelper.LogExceptionMessage(new SecurityTokenInvalidAudienceException(LogMessages.IDX10208) { InvalidAudience = Utility.SerializeAsSingleCommaDelimitedString(audiences) }); - - if (!audiences.Any()) - throw LogHelper.LogExceptionMessage( - new SecurityTokenInvalidAudienceException(LogHelper.FormatInvariant(LogMessages.IDX10206)) - { InvalidAudience = Utility.SerializeAsSingleCommaDelimitedString(audiences) }); - - if (audiences is not List audiencesAsList) - audiencesAsList = audiences.ToList(); - - if (AudienceIsValid(audiencesAsList, validationParameters)) - return; - - SecurityTokenInvalidAudienceException ex = new SecurityTokenInvalidAudienceException( - LogHelper.FormatInvariant(LogMessages.IDX10214, - LogHelper.MarkAsNonPII(Utility.SerializeAsSingleCommaDelimitedString(audiences)), - LogHelper.MarkAsNonPII(validationParameters.ValidAudience ?? "null"), - LogHelper.MarkAsNonPII(Utility.SerializeAsSingleCommaDelimitedString(validationParameters.ValidAudiences)))) - { InvalidAudience = Utility.SerializeAsSingleCommaDelimitedString(audiences) }; - - if (!validationParameters.LogValidationExceptions) - throw ex; - - throw LogHelper.LogExceptionMessage(ex); - } - - /// /// Determines if the audiences found in a are valid. /// @@ -259,13 +191,13 @@ private static bool AudiencesMatch(bool ignoreTrailingSlashWhenValidatingAudienc { if (validAudience.Length == tokenAudience.Length) return string.Equals(validAudience, tokenAudience); - else if (ignoreTrailingSlashWhenValidatingAudience && AudiencesMatchIgnoringTrailingSlash(tokenAudience, validAudience)) + else if (ignoreTrailingSlashWhenValidatingAudience && NewAudiencesMatchIgnoringTrailingSlash(tokenAudience, validAudience)) return true; return false; } - private static bool AudiencesMatchIgnoringTrailingSlash(string tokenAudience, string validAudience) + private static bool NewAudiencesMatchIgnoringTrailingSlash(string tokenAudience, string validAudience) { int length = -1; diff --git a/src/Microsoft.IdentityModel.Tokens/Validation/Validators.Issuer.cs b/src/Microsoft.IdentityModel.Tokens/Validation/Validators.Issuer.cs index 6535351bc9..d1db65f255 100644 --- a/src/Microsoft.IdentityModel.Tokens/Validation/Validators.Issuer.cs +++ b/src/Microsoft.IdentityModel.Tokens/Validation/Validators.Issuer.cs @@ -32,147 +32,6 @@ internal delegate Task IssuerValidationDelegateAsync( /// public static partial class Validators { - /// - /// Determines if an issuer found in a is valid. - /// - /// The issuer to validate - /// The that is being validated. - /// The to be used for validating the token. - /// The issuer to use when creating the "Claim"(s) in a "ClaimsIdentity". - /// If 'validationParameters' is null. - /// If 'issuer' is null or whitespace and is true. - /// If is null or whitespace and is null. - /// If 'issuer' failed to matched either or one of . - /// An EXACT match is required. - public static string ValidateIssuer(string issuer, SecurityToken securityToken, TokenValidationParameters validationParameters) - { - return ValidateIssuer(issuer, securityToken, validationParameters, null); - } - - /// - /// Determines if an issuer found in a is valid. - /// - /// The issuer to validate - /// The that is being validated. - /// The to be used for validating the token. - /// The required for issuer and signing key validation. - /// The issuer to use when creating the "Claim"(s) in a "ClaimsIdentity". - /// If 'validationParameters' is null. - /// If 'issuer' is null or whitespace and is true. - /// If ' configuration' is null. - /// If is null or whitespace and is null and is null. - /// If 'issuer' failed to matched either or one of or . - /// An EXACT match is required. - internal static string ValidateIssuer(string issuer, SecurityToken securityToken, TokenValidationParameters validationParameters, BaseConfiguration configuration) - { - ValueTask vt = ValidateIssuerAsync(issuer, securityToken, validationParameters, configuration); - return vt.IsCompletedSuccessfully ? - vt.Result : - vt.AsTask().ConfigureAwait(false).GetAwaiter().GetResult(); - } - - /// - /// Determines if an issuer found in a is valid. - /// - /// The issuer to validate - /// The that is being validated. - /// The to be used for validating the token. - /// The required for issuer and signing key validation. - /// The issuer to use when creating the "Claim"(s) in a "ClaimsIdentity". - /// If 'validationParameters' is null. - /// If 'issuer' is null or whitespace and is true. - /// If ' configuration' is null. - /// If is null or whitespace and is null and is null. - /// If 'issuer' failed to matched either or one of or . - /// An EXACT match is required. - internal static async ValueTask ValidateIssuerAsync( - string issuer, - SecurityToken securityToken, - TokenValidationParameters validationParameters, - BaseConfiguration configuration) - { - if (validationParameters == null) - throw LogHelper.LogArgumentNullException(nameof(validationParameters)); - - if (validationParameters.IssuerValidatorAsync != null) - return await validationParameters.IssuerValidatorAsync(issuer, securityToken, validationParameters).ConfigureAwait(false); - - if (validationParameters.IssuerValidatorUsingConfiguration != null) - return validationParameters.IssuerValidatorUsingConfiguration(issuer, securityToken, validationParameters, configuration); - - if (validationParameters.IssuerValidator != null) - return validationParameters.IssuerValidator(issuer, securityToken, validationParameters); - - if (!validationParameters.ValidateIssuer) - { - LogHelper.LogWarning(LogMessages.IDX10235); - return issuer; - } - - if (string.IsNullOrWhiteSpace(issuer)) - throw LogHelper.LogExceptionMessage(new SecurityTokenInvalidIssuerException(LogMessages.IDX10211) - { InvalidIssuer = issuer }); - - // Throw if all possible places to validate against are null or empty - if (string.IsNullOrWhiteSpace(validationParameters.ValidIssuer) - && validationParameters.ValidIssuers.IsNullOrEmpty() - && string.IsNullOrWhiteSpace(configuration?.Issuer)) - throw LogHelper.LogExceptionMessage(new SecurityTokenInvalidIssuerException(LogMessages.IDX10204) - { InvalidIssuer = issuer }); - - if (configuration != null) - { - if (string.Equals(configuration.Issuer, issuer)) - { - if (LogHelper.IsEnabled(EventLogLevel.Informational)) - LogHelper.LogInformation(LogMessages.IDX10236, LogHelper.MarkAsNonPII(issuer)); - - return issuer; - } - } - - if (string.Equals(validationParameters.ValidIssuer, issuer)) - { - if (LogHelper.IsEnabled(EventLogLevel.Informational)) - LogHelper.LogInformation(LogMessages.IDX10236, LogHelper.MarkAsNonPII(issuer)); - - return issuer; - } - - if (validationParameters.ValidIssuers != null) - { - foreach (string str in validationParameters.ValidIssuers) - { - if (string.IsNullOrEmpty(str)) - { - LogHelper.LogInformation(LogMessages.IDX10262); - continue; - } - - if (string.Equals(str, issuer)) - { - if (LogHelper.IsEnabled(EventLogLevel.Informational)) - LogHelper.LogInformation(LogMessages.IDX10236, LogHelper.MarkAsNonPII(issuer)); - - return issuer; - } - } - } - - SecurityTokenInvalidIssuerException ex = new SecurityTokenInvalidIssuerException( - LogHelper.FormatInvariant(LogMessages.IDX10205, - LogHelper.MarkAsNonPII(issuer), - LogHelper.MarkAsNonPII(validationParameters.ValidIssuer ?? "null"), - LogHelper.MarkAsNonPII(Utility.SerializeAsSingleCommaDelimitedString(validationParameters.ValidIssuers)), - LogHelper.MarkAsNonPII(configuration?.Issuer))) - { InvalidIssuer = issuer }; - - if (!validationParameters.LogValidationExceptions) - throw ex; - - throw LogHelper.LogExceptionMessage(ex); - } - /// /// Determines if an issuer found in a is valid. /// diff --git a/src/Microsoft.IdentityModel.Tokens/Validation/Validators.IssuerSecurityKey.cs b/src/Microsoft.IdentityModel.Tokens/Validation/Validators.IssuerSecurityKey.cs index a4026bd20c..9adcde0db3 100644 --- a/src/Microsoft.IdentityModel.Tokens/Validation/Validators.IssuerSecurityKey.cs +++ b/src/Microsoft.IdentityModel.Tokens/Validation/Validators.IssuerSecurityKey.cs @@ -35,101 +35,6 @@ internal delegate Task IssuerSecurityKeyValidationDe public static partial class Validators { - /// - /// Validates the that signed a . - /// - /// The that signed the . - /// The being validated. - /// The to be used for validating the token. - /// if 'securityKey' is null and ValidateIssuerSigningKey is true. - /// if 'securityToken' is null and ValidateIssuerSigningKey is true. - /// if 'validationParameters' is null. - public static void ValidateIssuerSecurityKey(SecurityKey securityKey, SecurityToken securityToken, TokenValidationParameters validationParameters) - { - ValidateIssuerSecurityKey(securityKey, securityToken, validationParameters, configuration: null); - } - - /// - /// Validates the that signed a . - /// - /// The that signed the . - /// The being validated. - /// The to be used for validating the token. - /// The required for issuer and signing key validation. - /// if 'securityKey' is null and ValidateIssuerSigningKey is true. - /// if 'securityToken' is null and ValidateIssuerSigningKey is true. - /// if 'validationParameters' is null. - internal static void ValidateIssuerSecurityKey(SecurityKey securityKey, SecurityToken securityToken, TokenValidationParameters validationParameters, BaseConfiguration? configuration) - { - if (validationParameters == null) - throw LogHelper.LogArgumentNullException(nameof(validationParameters)); - - if (validationParameters.IssuerSigningKeyValidatorUsingConfiguration != null) - { - if (!validationParameters.IssuerSigningKeyValidatorUsingConfiguration(securityKey, securityToken, validationParameters, configuration)) - throw LogHelper.LogExceptionMessage(new SecurityTokenInvalidSigningKeyException(LogHelper.FormatInvariant(LogMessages.IDX10232, securityKey)) { SigningKey = securityKey }); - - return; - } - - if (validationParameters.IssuerSigningKeyValidator != null) - { - if (!validationParameters.IssuerSigningKeyValidator(securityKey, securityToken, validationParameters)) - throw LogHelper.LogExceptionMessage(new SecurityTokenInvalidSigningKeyException(LogHelper.FormatInvariant(LogMessages.IDX10232, securityKey)) { SigningKey = securityKey }); - - return; - } - - if (!validationParameters.ValidateIssuerSigningKey) - { - LogHelper.LogVerbose(LogMessages.IDX10237); - return; - } - - if (!validationParameters.RequireSignedTokens && securityKey == null) - { - LogHelper.LogInformation(LogMessages.IDX10252); - return; - } - else if (securityKey == null) - { - throw LogHelper.LogExceptionMessage(new ArgumentNullException(nameof(securityKey), LogMessages.IDX10253)); - } - - if (securityToken == null) - throw LogHelper.LogArgumentNullException(nameof(securityToken)); - - ValidateIssuerSigningKeyLifeTime(securityKey, validationParameters); - } - - /// - /// Given a signing key, when it's derived from a certificate, validates that the certificate is already active and non-expired - /// - /// The that signed the . - /// The to be used for validating the token. - internal static void ValidateIssuerSigningKeyLifeTime(SecurityKey securityKey, TokenValidationParameters validationParameters) - { - X509SecurityKey? x509SecurityKey = securityKey as X509SecurityKey; - if (x509SecurityKey?.Certificate is X509Certificate2 cert) - { - DateTime utcNow = DateTime.UtcNow; - var notBeforeUtc = cert.NotBefore.ToUniversalTime(); - var notAfterUtc = cert.NotAfter.ToUniversalTime(); - - if (notBeforeUtc > DateTimeUtil.Add(utcNow, validationParameters.ClockSkew)) - throw LogHelper.LogExceptionMessage(new SecurityTokenInvalidSigningKeyException(LogHelper.FormatInvariant(LogMessages.IDX10248, LogHelper.MarkAsNonPII(notBeforeUtc), LogHelper.MarkAsNonPII(utcNow)))); - - if (LogHelper.IsEnabled(EventLogLevel.Informational)) - LogHelper.LogInformation(LogMessages.IDX10250, LogHelper.MarkAsNonPII(notBeforeUtc), LogHelper.MarkAsNonPII(utcNow)); - - if (notAfterUtc < DateTimeUtil.Add(utcNow, validationParameters.ClockSkew.Negate())) - throw LogHelper.LogExceptionMessage(new SecurityTokenInvalidSigningKeyException(LogHelper.FormatInvariant(LogMessages.IDX10249, LogHelper.MarkAsNonPII(notAfterUtc), LogHelper.MarkAsNonPII(utcNow)))); - - if (LogHelper.IsEnabled(EventLogLevel.Informational)) - LogHelper.LogInformation(LogMessages.IDX10251, LogHelper.MarkAsNonPII(notAfterUtc), LogHelper.MarkAsNonPII(utcNow)); - } - } - /// /// Validates the that signed a . /// diff --git a/src/Microsoft.IdentityModel.Tokens/Validation/Validators.Lifetime.cs b/src/Microsoft.IdentityModel.Tokens/Validation/Validators.Lifetime.cs index 0f3af21a02..41b53dd688 100644 --- a/src/Microsoft.IdentityModel.Tokens/Validation/Validators.Lifetime.cs +++ b/src/Microsoft.IdentityModel.Tokens/Validation/Validators.Lifetime.cs @@ -31,42 +31,6 @@ internal delegate LifetimeValidationResult LifetimeValidationDelegate( /// public static partial class Validators { - /// - /// Validates the lifetime of a . - /// - /// The 'notBefore' time found in the . - /// The 'expiration' time found in the . - /// The being validated. - /// The to be used for validating the token. - /// If 'validationParameters' is null. - /// If 'expires.HasValue' is false and is true. - /// If 'notBefore' is > 'expires'. - /// If 'notBefore' is > DateTime.UtcNow. - /// If 'expires' is < DateTime.UtcNow. - /// All time comparisons apply . - public static void ValidateLifetime(DateTime? notBefore, DateTime? expires, SecurityToken securityToken, TokenValidationParameters validationParameters) - { - if (validationParameters == null) - throw LogHelper.LogArgumentNullException(nameof(validationParameters)); - - if (validationParameters.LifetimeValidator != null) - { - if (!validationParameters.LifetimeValidator(notBefore, expires, securityToken, validationParameters)) - throw LogHelper.LogExceptionMessage(new SecurityTokenInvalidLifetimeException(LogHelper.FormatInvariant(LogMessages.IDX10230, securityToken)) - { NotBefore = notBefore, Expires = expires }); - - return; - } - - if (!validationParameters.ValidateLifetime) - { - LogHelper.LogInformation(LogMessages.IDX10238); - return; - } - - ValidatorUtilities.ValidateLifetime(notBefore, expires, securityToken, validationParameters); - } - /// /// Validates the lifetime of a . /// diff --git a/src/Microsoft.IdentityModel.Tokens/Validation/Validators.TokenReplay.cs b/src/Microsoft.IdentityModel.Tokens/Validation/Validators.TokenReplay.cs index 086f3473d2..c9807ac53e 100644 --- a/src/Microsoft.IdentityModel.Tokens/Validation/Validators.TokenReplay.cs +++ b/src/Microsoft.IdentityModel.Tokens/Validation/Validators.TokenReplay.cs @@ -31,74 +31,6 @@ internal delegate ReplayValidationResult ValidateTokenReplay( /// public static partial class Validators { - /// - /// Validates if a token has been replayed. - /// - /// When does the security token expire. - /// The being validated. - /// The to be used for validating the token. - /// If 'securityToken' is null or whitespace. - /// If 'validationParameters' is null or whitespace. - /// If is not null and expirationTime.HasValue is false. When a TokenReplayCache is set, tokens require an expiration time. - /// If the 'securityToken' is found in the cache. - /// If the 'securityToken' could not be added to the . - public static void ValidateTokenReplay(DateTime? expirationTime, string securityToken, TokenValidationParameters validationParameters) - { - if (string.IsNullOrWhiteSpace(securityToken)) - throw LogHelper.LogArgumentNullException(nameof(securityToken)); - - if (validationParameters == null) - throw LogHelper.LogArgumentNullException(nameof(validationParameters)); - - if (validationParameters.TokenReplayValidator != null) - { - if (!validationParameters.TokenReplayValidator(expirationTime, securityToken, validationParameters)) - throw LogHelper.LogExceptionMessage(new SecurityTokenReplayDetectedException( - LogHelper.FormatInvariant( - LogMessages.IDX10228, - LogHelper.MarkAsUnsafeSecurityArtifact(securityToken, t => t.ToString())))); - return; - } - - if (!validationParameters.ValidateTokenReplay) - { - LogHelper.LogVerbose(LogMessages.IDX10246); - return; - } - - // check if token if replay cache is set, then there must be an expiration time. - if (validationParameters.TokenReplayCache != null) - { - if (!expirationTime.HasValue) - throw LogHelper.LogExceptionMessage(new SecurityTokenNoExpirationException(LogHelper.FormatInvariant(LogMessages.IDX10227, securityToken))); - - if (validationParameters.TokenReplayCache.TryFind(securityToken)) - throw LogHelper.LogExceptionMessage(new SecurityTokenReplayDetectedException(LogHelper.FormatInvariant(LogMessages.IDX10228, securityToken))); - - if (!validationParameters.TokenReplayCache.TryAdd(securityToken, expirationTime.Value)) - throw LogHelper.LogExceptionMessage(new SecurityTokenReplayAddFailedException(LogHelper.FormatInvariant(LogMessages.IDX10229, securityToken))); - } - - // if it reaches here, that means no token replay is detected. - LogHelper.LogInformation(LogMessages.IDX10240); - } - - /// - /// Validates if a token has been replayed. - /// - /// The being validated. - /// When does the security token expire. - /// The to be used for validating the token. - /// If 'securityToken' is null or whitespace. - /// If 'validationParameters' is null or whitespace. - /// If is not null and expirationTime.HasValue is false. When a TokenReplayCache is set, tokens require an expiration time. - /// If the 'securityToken' is found in the cache. - /// If the 'securityToken' could not be added to the . - public static void ValidateTokenReplay(string securityToken, DateTime? expirationTime, TokenValidationParameters validationParameters) - { - ValidateTokenReplay(expirationTime, securityToken, validationParameters); - } - /// /// Validates if a token has been replayed. /// diff --git a/src/Microsoft.IdentityModel.Tokens/Validation/Validators.TokenType.cs b/src/Microsoft.IdentityModel.Tokens/Validation/Validators.TokenType.cs index 16b786f88d..4cbb6aa701 100644 --- a/src/Microsoft.IdentityModel.Tokens/Validation/Validators.TokenType.cs +++ b/src/Microsoft.IdentityModel.Tokens/Validation/Validators.TokenType.cs @@ -12,54 +12,6 @@ namespace Microsoft.IdentityModel.Tokens { public static partial class Validators { - /// - /// Validates the type of the token. - /// - /// The token type or null if it couldn't be resolved (e.g from the 'typ' header for a JWT). - /// The that is being validated. - /// The to be used for validating the token. - /// If is null. - /// If is null. - /// If is null or whitespace and is not null. - /// If failed to match . - /// An EXACT match is required. (case sensitive) is used for comparing against . - /// The actual token type, that may be the same as or a different value if the token type was resolved from a different location. - public static string ValidateTokenType(string type, SecurityToken securityToken, TokenValidationParameters validationParameters) - { - if (securityToken == null) - throw new ArgumentNullException(nameof(securityToken)); - - if (validationParameters == null) - throw LogHelper.LogArgumentNullException(nameof(validationParameters)); - - if (validationParameters.TypeValidator == null && (validationParameters.ValidTypes == null || !validationParameters.ValidTypes.Any())) - { - LogHelper.LogVerbose(LogMessages.IDX10255); - return type; - } - - if (validationParameters.TypeValidator != null) - return validationParameters.TypeValidator(type, securityToken, validationParameters); - - // Note: don't throw an exception for a null or empty token type when a user-defined delegate is set - // to allow it to extract the actual token type from a different location (e.g from the claims). - if (string.IsNullOrEmpty(type)) - throw LogHelper.LogExceptionMessage(new SecurityTokenInvalidTypeException(LogMessages.IDX10256) { InvalidType = null }); - - if (!validationParameters.ValidTypes.Contains(type, StringComparer.Ordinal)) - { - throw LogHelper.LogExceptionMessage( - new SecurityTokenInvalidTypeException(LogHelper.FormatInvariant(LogMessages.IDX10257, LogHelper.MarkAsNonPII(type), Utility.SerializeAsSingleCommaDelimitedString(validationParameters.ValidTypes))) - { InvalidType = type }); - } - - // if it reaches here, token type was succcessfully validated. - if (LogHelper.IsEnabled(EventLogLevel.Informational)) - LogHelper.LogInformation(LogMessages.IDX10258, LogHelper.MarkAsNonPII(type)); - - return type; - } - /// /// Validates the type of the token. /// diff --git a/src/Microsoft.IdentityModel.Tokens/Validators.cs b/src/Microsoft.IdentityModel.Tokens/Validators.cs index 2c9f9e6ca1..ab9de078f2 100644 --- a/src/Microsoft.IdentityModel.Tokens/Validators.cs +++ b/src/Microsoft.IdentityModel.Tokens/Validators.cs @@ -14,7 +14,7 @@ namespace Microsoft.IdentityModel.Tokens /// /// AudienceValidator /// - public static class Validators + public static partial class Validators { /// /// Validates if a given algorithm for a is valid. @@ -338,7 +338,7 @@ internal static async ValueTask ValidateIssuerAsync( /// if 'validationParameters' is null. public static void ValidateIssuerSecurityKey(SecurityKey securityKey, SecurityToken securityToken, TokenValidationParameters validationParameters) { - ValidateIssuerSecurityKey(securityKey, securityToken, validationParameters, null); + ValidateIssuerSecurityKey(securityKey, securityToken, validationParameters, configuration: null); } ///