From 8b6f130e962bb13051abe727337aa30b33f1cc25 Mon Sep 17 00:00:00 2001 From: Ignacio Inglese Date: Sun, 17 Nov 2024 16:30:24 +0000 Subject: [PATCH] Handle potential exception thrown by the lifetime validation delegate in JWT, SAML, and SAML2 --- ...nWebTokenHandler.ValidateToken.Internal.cs | 25 +++++++++++--- ...rityTokenHandler.ValidateToken.Internal.cs | 33 ++++++++++++++----- ...rityTokenHandler.ValidateToken.Internal.cs | 33 ++++++++++++++----- .../InternalAPI.Unshipped.txt | 2 ++ .../LogMessages.cs | 2 +- .../Validation/ValidationFailureType.cs | 5 +++ 6 files changed, 76 insertions(+), 24 deletions(-) diff --git a/src/Microsoft.IdentityModel.JsonWebTokens/JsonWebTokenHandler.ValidateToken.Internal.cs b/src/Microsoft.IdentityModel.JsonWebTokens/JsonWebTokenHandler.ValidateToken.Internal.cs index c2ec88ac19..5988a55ae8 100644 --- a/src/Microsoft.IdentityModel.JsonWebTokens/JsonWebTokenHandler.ValidateToken.Internal.cs +++ b/src/Microsoft.IdentityModel.JsonWebTokens/JsonWebTokenHandler.ValidateToken.Internal.cs @@ -255,13 +255,28 @@ private async ValueTask> ValidateJWSAsync( DateTime? expires = jsonWebToken.HasPayloadClaim(JwtRegisteredClaimNames.Exp) ? jsonWebToken.ValidTo : null; DateTime? notBefore = jsonWebToken.HasPayloadClaim(JwtRegisteredClaimNames.Nbf) ? jsonWebToken.ValidFrom : null; - ValidationResult lifetimeValidationResult = validationParameters.LifetimeValidator( - notBefore, expires, jsonWebToken, validationParameters, callContext); + ValidationResult lifetimeValidationResult; - if (!lifetimeValidationResult.IsValid) + try { - StackFrame lifetimeValidationFailureStackFrame = StackFrames.LifetimeValidationFailed ??= new StackFrame(true); - return lifetimeValidationResult.UnwrapError().AddStackFrame(lifetimeValidationFailureStackFrame); + lifetimeValidationResult = validationParameters.LifetimeValidator( + notBefore, expires, jsonWebToken, validationParameters, callContext); + + if (!lifetimeValidationResult.IsValid) + return lifetimeValidationResult.UnwrapError().AddCurrentStackFrame(); + } +#pragma warning disable CA1031 // Do not catch general exception types + catch (Exception ex) +#pragma warning restore CA1031 // Do not catch general exception types + { + return new LifetimeValidationError( + new MessageDetail(TokenLogMessages.IDX10271), + typeof(SecurityTokenInvalidLifetimeException), + ValidationError.GetCurrentStackFrame(), + notBefore, + expires, + ValidationFailureType.LifetimeValidatorThrew, + ex); } if (jsonWebToken.Audiences is not IList tokenAudiences) diff --git a/src/Microsoft.IdentityModel.Tokens.Saml/Saml/SamlSecurityTokenHandler.ValidateToken.Internal.cs b/src/Microsoft.IdentityModel.Tokens.Saml/Saml/SamlSecurityTokenHandler.ValidateToken.Internal.cs index 7d45db493a..893d358355 100644 --- a/src/Microsoft.IdentityModel.Tokens.Saml/Saml/SamlSecurityTokenHandler.ValidateToken.Internal.cs +++ b/src/Microsoft.IdentityModel.Tokens.Saml/Saml/SamlSecurityTokenHandler.ValidateToken.Internal.cs @@ -126,17 +126,32 @@ internal virtual ValidationResult ValidateConditions( StackFrames.AssertionConditionsNull); } - var lifetimeValidationResult = validationParameters.LifetimeValidator( - samlToken.Assertion.Conditions.NotBefore, - samlToken.Assertion.Conditions.NotOnOrAfter, - samlToken, - validationParameters, - callContext); + ValidationResult lifetimeValidationResult; - if (!lifetimeValidationResult.IsValid) + try + { + lifetimeValidationResult = validationParameters.LifetimeValidator( + samlToken.Assertion.Conditions.NotBefore, + samlToken.Assertion.Conditions.NotOnOrAfter, + samlToken, + validationParameters, + callContext); + + if (!lifetimeValidationResult.IsValid) + return lifetimeValidationResult.UnwrapError().AddCurrentStackFrame(); + } +#pragma warning disable CA1031 // Do not catch general exception types + catch (Exception ex) +#pragma warning restore CA1031 // Do not catch general exception types { - StackFrames.LifetimeValidationFailed ??= new StackFrame(true); - return lifetimeValidationResult.UnwrapError().AddStackFrame(StackFrames.LifetimeValidationFailed); + return new LifetimeValidationError( + new MessageDetail(Tokens.LogMessages.IDX10271), + typeof(SecurityTokenInvalidLifetimeException), + ValidationError.GetCurrentStackFrame(), + samlToken.Assertion.Conditions.NotBefore, + samlToken.Assertion.Conditions.NotOnOrAfter, + ValidationFailureType.LifetimeValidatorThrew, + ex); } string? validatedAudience = null; diff --git a/src/Microsoft.IdentityModel.Tokens.Saml/Saml2/Saml2SecurityTokenHandler.ValidateToken.Internal.cs b/src/Microsoft.IdentityModel.Tokens.Saml/Saml2/Saml2SecurityTokenHandler.ValidateToken.Internal.cs index 2ff1a01def..c09a4bbe88 100644 --- a/src/Microsoft.IdentityModel.Tokens.Saml/Saml2/Saml2SecurityTokenHandler.ValidateToken.Internal.cs +++ b/src/Microsoft.IdentityModel.Tokens.Saml/Saml2/Saml2SecurityTokenHandler.ValidateToken.Internal.cs @@ -129,17 +129,32 @@ internal virtual ValidationResult ValidateConditions( StackFrames.AssertionConditionsNull); } - var lifetimeValidationResult = validationParameters.LifetimeValidator( - samlToken.Assertion.Conditions.NotBefore, - samlToken.Assertion.Conditions.NotOnOrAfter, - samlToken, - validationParameters, - callContext); + ValidationResult lifetimeValidationResult; - if (!lifetimeValidationResult.IsValid) + try { - StackFrames.LifetimeValidationFailed ??= new StackFrame(true); - return lifetimeValidationResult.UnwrapError().AddStackFrame(StackFrames.LifetimeValidationFailed); + lifetimeValidationResult = validationParameters.LifetimeValidator( + samlToken.Assertion.Conditions.NotBefore, + samlToken.Assertion.Conditions.NotOnOrAfter, + samlToken, + validationParameters, + callContext); + + if (!lifetimeValidationResult.IsValid) + return lifetimeValidationResult.UnwrapError().AddCurrentStackFrame(); + } +#pragma warning disable CA1031 // Do not catch general exception types + catch (Exception ex) +#pragma warning restore CA1031 // Do not catch general exception types + { + return new LifetimeValidationError( + new MessageDetail(Tokens.LogMessages.IDX10271), + typeof(SecurityTokenInvalidLifetimeException), + ValidationError.GetCurrentStackFrame(), + samlToken.Assertion.Conditions.NotBefore, + samlToken.Assertion.Conditions.NotOnOrAfter, + ValidationFailureType.LifetimeValidatorThrew, + ex); } if (samlToken.Assertion.Conditions.OneTimeUse) diff --git a/src/Microsoft.IdentityModel.Tokens/InternalAPI.Unshipped.txt b/src/Microsoft.IdentityModel.Tokens/InternalAPI.Unshipped.txt index 03e642a883..359bdced05 100644 --- a/src/Microsoft.IdentityModel.Tokens/InternalAPI.Unshipped.txt +++ b/src/Microsoft.IdentityModel.Tokens/InternalAPI.Unshipped.txt @@ -1,6 +1,7 @@ const Microsoft.IdentityModel.Tokens.LogMessages.IDX10002 = "IDX10002: Unknown exception type returned. Type: '{0}'. Message: '{1}'." -> string const Microsoft.IdentityModel.Tokens.LogMessages.IDX10268 = "IDX10268: Unable to validate audience, validationParameters.ValidAudiences.Count == 0." -> string const Microsoft.IdentityModel.Tokens.LogMessages.IDX10269 = "IDX10269: IssuerValidationDelegate threw an exception, see inner exception." -> string +const Microsoft.IdentityModel.Tokens.LogMessages.IDX10271 = "IDX10271: LifetimeValidationDelegate threw an exception, see inner exception." -> string Microsoft.IdentityModel.Tokens.AlgorithmValidationError Microsoft.IdentityModel.Tokens.AlgorithmValidationError.AlgorithmValidationError(Microsoft.IdentityModel.Tokens.MessageDetail messageDetail, System.Type exceptionType, System.Diagnostics.StackFrame stackFrame, string invalidAlgorithm, Microsoft.IdentityModel.Tokens.ValidationFailureType validationFailureType = null, System.Exception innerException = null) -> void Microsoft.IdentityModel.Tokens.AlgorithmValidationError.InvalidAlgorithm.get -> string @@ -42,6 +43,7 @@ static Microsoft.IdentityModel.Tokens.AudienceValidationError.ValidationParamete static Microsoft.IdentityModel.Tokens.Utility.SerializeAsSingleCommaDelimitedString(System.Collections.Generic.IList strings) -> string static Microsoft.IdentityModel.Tokens.ValidationError.GetCurrentStackFrame(string filePath = "", int lineNumber = 0, int skipFrames = 1) -> System.Diagnostics.StackFrame static readonly Microsoft.IdentityModel.Tokens.ValidationFailureType.IssuerValidatorThrew -> Microsoft.IdentityModel.Tokens.ValidationFailureType +static readonly Microsoft.IdentityModel.Tokens.ValidationFailureType.LifetimeValidatorThrew -> Microsoft.IdentityModel.Tokens.ValidationFailureType static readonly Microsoft.IdentityModel.Tokens.ValidationFailureType.NoTokenAudiencesProvided -> Microsoft.IdentityModel.Tokens.ValidationFailureType static readonly Microsoft.IdentityModel.Tokens.ValidationFailureType.NoValidationParameterAudiencesProvided -> Microsoft.IdentityModel.Tokens.ValidationFailureType static readonly Microsoft.IdentityModel.Tokens.ValidationFailureType.SignatureAlgorithmValidationFailed -> Microsoft.IdentityModel.Tokens.ValidationFailureType diff --git a/src/Microsoft.IdentityModel.Tokens/LogMessages.cs b/src/Microsoft.IdentityModel.Tokens/LogMessages.cs index 59c48703f3..c2f8d6b2f4 100644 --- a/src/Microsoft.IdentityModel.Tokens/LogMessages.cs +++ b/src/Microsoft.IdentityModel.Tokens/LogMessages.cs @@ -88,7 +88,7 @@ internal static class LogMessages public const string IDX10267 = "IDX10267: '{0}' has been called by a derived class '{1}' which has not implemented this method. For this call graph to succeed, '{1}' will need to implement '{0}'."; public const string IDX10268 = "IDX10268: Unable to validate audience, validationParameters.ValidAudiences.Count == 0."; public const string IDX10269 = "IDX10269: IssuerValidationDelegate threw an exception, see inner exception."; - + public const string IDX10271 = "IDX10271: LifetimeValidationDelegate threw an exception, see inner exception."; // 10500 - SignatureValidation public const string IDX10500 = "IDX10500: Signature validation failed. No security keys were provided to validate the signature."; diff --git a/src/Microsoft.IdentityModel.Tokens/Validation/ValidationFailureType.cs b/src/Microsoft.IdentityModel.Tokens/Validation/ValidationFailureType.cs index e512d2af5d..293fcab45e 100644 --- a/src/Microsoft.IdentityModel.Tokens/Validation/ValidationFailureType.cs +++ b/src/Microsoft.IdentityModel.Tokens/Validation/ValidationFailureType.cs @@ -134,5 +134,10 @@ private class XmlValidationFailure : ValidationFailureType { internal XmlValidat /// public static readonly ValidationFailureType IssuerValidatorThrew = new IssuerValidatorFailure("IssuerValidatorThrew"); private class IssuerValidatorFailure : ValidationFailureType { internal IssuerValidatorFailure(string name) : base(name) { } } + + /// + /// Defines a type that represents that the audience validation delegate threw and exception. + /// + public static readonly ValidationFailureType LifetimeValidatorThrew = new LifetimeValidationFailure("LifetimeValidatorThrew"); } }