diff --git a/src/Microsoft.IdentityModel.JsonWebTokens/JsonWebTokenHandler.ValidateSignature.cs b/src/Microsoft.IdentityModel.JsonWebTokens/JsonWebTokenHandler.ValidateSignature.cs index ff0a9637a3..1c9e490f7a 100644 --- a/src/Microsoft.IdentityModel.JsonWebTokens/JsonWebTokenHandler.ValidateSignature.cs +++ b/src/Microsoft.IdentityModel.JsonWebTokens/JsonWebTokenHandler.ValidateSignature.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; -using System.Diagnostics; using System.Linq; using System.Text; using Microsoft.IdentityModel.Logging; @@ -23,7 +22,7 @@ public partial class JsonWebTokenHandler : TokenHandler /// The parameters used for validation. /// The optional configuration used for validation. /// The context in which the method is called. - /// Returned if or is null." + /// Returned if or is null." /// Returned by the default implementation if the token is not signed, or if the validation fails. /// Returned if the algorithm is not supported by the key. /// Returned if the key cannot be resolved. @@ -34,22 +33,47 @@ internal static ValidationResult ValidateSignature( CallContext callContext) { if (jwtToken is null) - return ValidationError.NullParameter( + return SignatureValidationError.NullParameter( nameof(jwtToken), - new StackFrame(true)); + ValidationError.GetCurrentStackFrame()); if (validationParameters is null) - return ValidationError.NullParameter( + return SignatureValidationError.NullParameter( nameof(validationParameters), - new StackFrame(true)); + ValidationError.GetCurrentStackFrame()); // Delegate is set by the user, we call it and return the result. if (validationParameters.SignatureValidator is not null) - return validationParameters.SignatureValidator(jwtToken, validationParameters, configuration, callContext); + { + try + { + ValidationResult signatureValidationResult = validationParameters.SignatureValidator( + jwtToken, + validationParameters, + configuration, + callContext); + + if (!signatureValidationResult.IsValid) + return signatureValidationResult.UnwrapError().AddCurrentStackFrame(); + + return signatureValidationResult; + } +#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 SignatureValidationError( + new MessageDetail(TokenLogMessages.IDX10272), + ValidationFailureType.SignatureValidatorThrew, + typeof(SecurityTokenInvalidSignatureException), + ValidationError.GetCurrentStackFrame(), + innerException: ex); + } + } // If the user wants to accept unsigned tokens, they must implement the delegate. if (!jwtToken.IsSigned) - return new ValidationError( + return new SignatureValidationError( new MessageDetail( TokenLogMessages.IDX10504, LogHelper.MarkAsSecurityArtifact( @@ -57,7 +81,7 @@ internal static ValidationResult ValidateSignature( JwtTokenUtilities.SafeLogJwtToken)), ValidationFailureType.SignatureValidationFailed, typeof(SecurityTokenInvalidSignatureException), - new StackFrame(true)); + ValidationError.GetCurrentStackFrame()); SecurityKey? key = null; if (validationParameters.IssuerSigningKeyResolver is not null) @@ -93,8 +117,7 @@ internal static ValidationResult ValidateSignature( { if (!string.IsNullOrEmpty(jwtToken.Kid)) { - StackFrame kidNotMatchedNoTryAllStackFrame = StackFrames.KidNotMatchedNoTryAll ??= new StackFrame(true); - return new ValidationError( + return new SignatureValidationError( new MessageDetail( TokenLogMessages.IDX10502, LogHelper.MarkAsNonPII(jwtToken.Kid), @@ -103,15 +126,14 @@ internal static ValidationResult ValidateSignature( LogHelper.MarkAsSecurityArtifact(jwtToken.EncodedToken, JwtTokenUtilities.SafeLogJwtToken)), ValidationFailureType.SignatureValidationFailed, typeof(SecurityTokenSignatureKeyNotFoundException), - kidNotMatchedNoTryAllStackFrame); + ValidationError.GetCurrentStackFrame()); } - StackFrame noKeysProvidedStackFrame = StackFrames.NoKeysProvided ??= new StackFrame(true); - return new ValidationError( + return new SignatureValidationError( new MessageDetail(TokenLogMessages.IDX10500), ValidationFailureType.SignatureValidationFailed, typeof(SecurityTokenSignatureKeyNotFoundException), - noKeysProvidedStackFrame); + ValidationError.GetCurrentStackFrame()); } } @@ -144,11 +166,11 @@ private static ValidationResult ValidateSignatureUsingAllKeys( return unwrappedVpResult; if (vpFailedResult is null && configFailedResult is null) // No keys were attempted - return new ValidationError( + return new SignatureValidationError( new MessageDetail(TokenLogMessages.IDX10500), ValidationFailureType.SignatureValidationFailed, typeof(SecurityTokenSignatureKeyNotFoundException), - new StackFrame(true)); + ValidationError.GetCurrentStackFrame()); StringBuilder exceptionStrings = new(); StringBuilder keysAttempted = new(); @@ -223,61 +245,63 @@ private static ValidationResult ValidateSignatureWithKey( CryptoProviderFactory cryptoProviderFactory = validationParameters.CryptoProviderFactory ?? key.CryptoProviderFactory; if (!cryptoProviderFactory.IsSupportedAlgorithm(jsonWebToken.Alg, key)) { - return new ValidationError( + return new SignatureValidationError( new MessageDetail( TokenLogMessages.IDX10400, LogHelper.MarkAsNonPII(jsonWebToken.Alg), key), ValidationFailureType.SignatureValidationFailed, typeof(SecurityTokenInvalidAlgorithmException), - new StackFrame(true)); + ValidationError.GetCurrentStackFrame()); } - ValidationResult result = validationParameters.AlgorithmValidator( - jsonWebToken.Alg, - key, - jsonWebToken, - validationParameters, - callContext); - - if (!result.IsValid) + try { - if (result.UnwrapError() is AlgorithmValidationError algorithmValidationError) - { - return new AlgorithmValidationError( - new MessageDetail( - TokenLogMessages.IDX10518, - algorithmValidationError.MessageDetail.Message), - ValidationFailureType.AlgorithmValidationFailed, - typeof(SecurityTokenInvalidAlgorithmException), - new StackFrame(true), - algorithmValidationError.InvalidAlgorithm); - } - else + ValidationResult algorithmValidationResult = validationParameters.AlgorithmValidator( + jsonWebToken.Alg, + key, + jsonWebToken, + validationParameters, + callContext); + + if (!algorithmValidationResult.IsValid) { - // overridden delegate did not return an AlgorithmValidationError - return new ValidationError( + var validationError = algorithmValidationResult.UnwrapError().AddCurrentStackFrame(); + return new SignatureValidationError( new MessageDetail( TokenLogMessages.IDX10518, - result.UnwrapError().MessageDetail.Message), - ValidationFailureType.SignatureAlgorithmValidationFailed, - typeof(SecurityTokenInvalidAlgorithmException), - new StackFrame(true)); + validationError.MessageDetail.Message), + validationError.FailureType, // Surface the algorithm validation error's failure type. + typeof(SecurityTokenInvalidSignatureException), + ValidationError.GetCurrentStackFrame(), + validationError); // Pass the algorithm validation error as the inner validation error. } } +#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 SignatureValidationError( + new MessageDetail(TokenLogMessages.IDX10273), + ValidationFailureType.AlgorithmValidatorThrew, + typeof(SecurityTokenInvalidSignatureException), + ValidationError.GetCurrentStackFrame(), + null, // No need to create an AlgorithmValidationError for this case. + ex); + } SignatureProvider signatureProvider = cryptoProviderFactory.CreateForVerifying(key, jsonWebToken.Alg); try { if (signatureProvider == null) - return new ValidationError( + return new SignatureValidationError( new MessageDetail( TokenLogMessages.IDX10636, key?.ToString() ?? "Null", LogHelper.MarkAsNonPII(jsonWebToken.Alg)), ValidationFailureType.SignatureValidationFailed, - typeof(InvalidOperationException), - new StackFrame(true)); + typeof(SecurityTokenInvalidOperationException), + ValidationError.GetCurrentStackFrame()); bool valid = EncodingUtils.PerformEncodingDependentOperation( jsonWebToken.EncodedToken, @@ -292,7 +316,7 @@ private static ValidationResult ValidateSignatureWithKey( if (valid) return key; else - return new ValidationError( + return new SignatureValidationError( new MessageDetail( TokenLogMessages.IDX10504, LogHelper.MarkAsSecurityArtifact( @@ -300,13 +324,13 @@ private static ValidationResult ValidateSignatureWithKey( JwtTokenUtilities.SafeLogJwtToken)), ValidationFailureType.SignatureValidationFailed, typeof(SecurityTokenInvalidSignatureException), - new StackFrame(true)); + ValidationError.GetCurrentStackFrame()); } #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 ValidationError( + return new SignatureValidationError( new MessageDetail( TokenLogMessages.IDX10504, LogHelper.MarkAsSecurityArtifact( @@ -314,8 +338,8 @@ private static ValidationResult ValidateSignatureWithKey( JwtTokenUtilities.SafeLogJwtToken)), ValidationFailureType.SignatureValidationFailed, typeof(SecurityTokenInvalidSignatureException), - new StackFrame(true), - ex); + ValidationError.GetCurrentStackFrame(), + innerException: ex); } finally { @@ -323,7 +347,7 @@ private static ValidationResult ValidateSignatureWithKey( } } - private static ValidationError GetSignatureValidationError( + private static SignatureValidationError GetSignatureValidationError( JsonWebToken jwtToken, ValidationParameters validationParameters, BaseConfiguration? configuration, @@ -343,7 +367,7 @@ private static ValidationError GetSignatureValidationError( JsonWebToken localJwtToken = jwtToken; // avoid closure on non-exceptional path bool isKidInTVP = keysInTokenValidationParameters.Any(x => x.KeyId.Equals(localJwtToken.Kid)); string keyLocation = isKidInTVP ? "TokenValidationParameters" : "Configuration"; - return new ValidationError( + return new SignatureValidationError( new MessageDetail( TokenLogMessages.IDX10511, LogHelper.MarkAsNonPII(keysAttempted.ToString()), @@ -355,11 +379,11 @@ private static ValidationError GetSignatureValidationError( LogHelper.MarkAsSecurityArtifact(jwtToken.EncodedToken, JwtTokenUtilities.SafeLogJwtToken)), ValidationFailureType.SignatureValidationFailed, typeof(SecurityTokenSignatureKeyNotFoundException), - new StackFrame(true)); + ValidationError.GetCurrentStackFrame()); } if (kidExists) - return new ValidationError( + return new SignatureValidationError( new MessageDetail( TokenLogMessages.IDX10503, LogHelper.MarkAsNonPII(jwtToken.Kid), @@ -370,9 +394,9 @@ private static ValidationError GetSignatureValidationError( LogHelper.MarkAsSecurityArtifact(jwtToken.EncodedToken, JwtTokenUtilities.SafeLogJwtToken)), ValidationFailureType.SignatureValidationFailed, typeof(SecurityTokenSignatureKeyNotFoundException), - new StackFrame(true)); + ValidationError.GetCurrentStackFrame()); - return new ValidationError( + return new SignatureValidationError( new MessageDetail( TokenLogMessages.IDX10517, // Kid is missing and no keys match. LogHelper.MarkAsNonPII(keysAttempted.ToString()), @@ -382,7 +406,7 @@ private static ValidationError GetSignatureValidationError( LogHelper.MarkAsSecurityArtifact(jwtToken.EncodedToken, JwtTokenUtilities.SafeLogJwtToken)), ValidationFailureType.SignatureValidationFailed, typeof(SecurityTokenSignatureKeyNotFoundException), - new StackFrame(true)); + ValidationError.GetCurrentStackFrame()); } private static void PopulateFailedResults( diff --git a/src/Microsoft.IdentityModel.Tokens.Saml/Saml/SamlSecurityTokenHandler.ValidateSignature.cs b/src/Microsoft.IdentityModel.Tokens.Saml/Saml/SamlSecurityTokenHandler.ValidateSignature.cs index 3691a62159..5681070c45 100644 --- a/src/Microsoft.IdentityModel.Tokens.Saml/Saml/SamlSecurityTokenHandler.ValidateSignature.cs +++ b/src/Microsoft.IdentityModel.Tokens.Saml/Saml/SamlSecurityTokenHandler.ValidateSignature.cs @@ -1,9 +1,9 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. +using System; using System.Collections.Generic; using System.Text; -using Microsoft.IdentityModel.Xml; using TokenLogMessages = Microsoft.IdentityModel.Tokens.LogMessages; #nullable enable @@ -20,25 +20,50 @@ internal static ValidationResult ValidateSignature( { if (samlToken is null) { - return ValidationError.NullParameter( + return SignatureValidationError.NullParameter( nameof(samlToken), ValidationError.GetCurrentStackFrame()); } if (validationParameters is null) { - return ValidationError.NullParameter( + return SignatureValidationError.NullParameter( nameof(validationParameters), ValidationError.GetCurrentStackFrame()); } // Delegate is set by the user, we call it and return the result. if (validationParameters.SignatureValidator is not null) - return validationParameters.SignatureValidator(samlToken, validationParameters, null, callContext); + { + try + { + ValidationResult signatureValidationResult = validationParameters.SignatureValidator( + samlToken, + validationParameters, + null, // configuration + callContext); + + if (!signatureValidationResult.IsValid) + return signatureValidationResult.UnwrapError().AddCurrentStackFrame(); + + return signatureValidationResult; + } +#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 SignatureValidationError( + new MessageDetail(TokenLogMessages.IDX10272), + ValidationFailureType.SignatureValidatorThrew, + typeof(SecurityTokenInvalidSignatureException), + ValidationError.GetCurrentStackFrame(), + innerException: ex); + } + } // If the user wants to accept unsigned tokens, they must implement the delegate if (samlToken.Assertion.Signature is null) - return new XmlValidationError( + return new SignatureValidationError( new MessageDetail( TokenLogMessages.IDX10504, samlToken.Assertion.CanonicalString), @@ -64,16 +89,15 @@ internal static ValidationResult ValidateSignature( resolvedKey = SamlTokenUtilities.ResolveTokenSigningKey(samlToken.Assertion.Signature.KeyInfo, validationParameters); } - ValidationError? error = null; - if (resolvedKey is not null) { keyMatched = true; var result = ValidateSignatureUsingKey(resolvedKey, samlToken, validationParameters, callContext); - if (result.IsValid) - return result; - error = result.UnwrapError(); + if (!result.IsValid) + return result.UnwrapError().AddCurrentStackFrame(); + + return result; } bool canMatchKey = samlToken.Assertion.Signature.KeyInfo != null; @@ -103,12 +127,12 @@ internal static ValidationResult ValidateSignature( } if (canMatchKey && keyMatched) - return new XmlValidationError( + return new SignatureValidationError( new MessageDetail( TokenLogMessages.IDX10514, keysAttempted?.ToString(), samlToken.Assertion.Signature.KeyInfo, - GetErrorString(error, errors), + GetErrorString(errors), samlToken), ValidationFailureType.SignatureValidationFailed, typeof(SecurityTokenInvalidSignatureException), @@ -121,17 +145,17 @@ internal static ValidationResult ValidateSignature( keysAttemptedString = keysAttempted!.ToString(); if (keysAttemptedString is not null) - return new XmlValidationError( + return new SignatureValidationError( new MessageDetail( TokenLogMessages.IDX10512, keysAttemptedString, - GetErrorString(error, errors), + GetErrorString(errors), samlToken), ValidationFailureType.SignatureValidationFailed, typeof(SecurityTokenSignatureKeyNotFoundException), ValidationError.GetCurrentStackFrame()); - return new XmlValidationError( + return new SignatureValidationError( new MessageDetail(TokenLogMessages.IDX10500), ValidationFailureType.SignatureValidationFailed, typeof(SecurityTokenSignatureKeyNotFoundException), @@ -140,44 +164,61 @@ internal static ValidationResult ValidateSignature( private static ValidationResult ValidateSignatureUsingKey(SecurityKey key, SamlSecurityToken samlToken, ValidationParameters validationParameters, CallContext callContext) { - ValidationResult algorithmValidationResult = validationParameters.AlgorithmValidator( - samlToken.Assertion.Signature.SignedInfo.SignatureMethod, - key, - samlToken, - validationParameters, - callContext); - - if (!algorithmValidationResult.IsValid) + try { - return algorithmValidationResult.UnwrapError().AddCurrentStackFrame(); + ValidationResult algorithmValidationResult = validationParameters.AlgorithmValidator( + samlToken.Assertion.Signature.SignedInfo.SignatureMethod, + key, + samlToken, + validationParameters, + callContext); + + if (!algorithmValidationResult.IsValid) + { + var algorithmValidationError = algorithmValidationResult.UnwrapError().AddCurrentStackFrame(); + return new SignatureValidationError( + new MessageDetail( + TokenLogMessages.IDX10518, + algorithmValidationError.MessageDetail.Message), + algorithmValidationError.FailureType, // Surface the algorithm validation error's failure type. + typeof(SecurityTokenInvalidSignatureException), + ValidationError.GetCurrentStackFrame(), + algorithmValidationError); // Pass the algorithm validation error as the inner validation error. + } } - else +#pragma warning disable CA1031 // Do not catch general exception types + catch (Exception ex) +#pragma warning restore CA1031 // Do not catch general exception types { - var validationError = samlToken.Assertion.Signature.Verify( + return new SignatureValidationError( + new MessageDetail(TokenLogMessages.IDX10273), + ValidationFailureType.AlgorithmValidatorThrew, + typeof(SecurityTokenInvalidSignatureException), + ValidationError.GetCurrentStackFrame(), + null, // No need to create an AlgorithmValidationError for this case. + ex); + } + + var validationError = samlToken.Assertion.Signature.Verify( key, validationParameters.CryptoProviderFactory ?? key.CryptoProviderFactory, callContext); - if (validationError is null) - { - samlToken.SigningKey = key; + if (validationError is null) + { + samlToken.SigningKey = key; - return key; - } - else - { - return validationError.AddCurrentStackFrame(); - } + return key; + } + else + { + return validationError.AddCurrentStackFrame(); } } - private static string GetErrorString(ValidationError? error, List? errorList) + private static string GetErrorString(List? errorList) { // This method is called if there are errors in the signature validation process. - // This check is there to account for the optional parameter. - if (error is not null) - return error.MessageDetail.Message; - if (errorList is null) return string.Empty; diff --git a/src/Microsoft.IdentityModel.Tokens.Saml/Saml2/Saml2SecurityTokenHandler.ValidateSignature.cs b/src/Microsoft.IdentityModel.Tokens.Saml/Saml2/Saml2SecurityTokenHandler.ValidateSignature.cs index 74d2884b26..7aeb16093e 100644 --- a/src/Microsoft.IdentityModel.Tokens.Saml/Saml2/Saml2SecurityTokenHandler.ValidateSignature.cs +++ b/src/Microsoft.IdentityModel.Tokens.Saml/Saml2/Saml2SecurityTokenHandler.ValidateSignature.cs @@ -1,10 +1,10 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. +using System; using System.Collections.Generic; using System.Text; using Microsoft.IdentityModel.Tokens.Saml; -using Microsoft.IdentityModel.Xml; using TokenLogMessages = Microsoft.IdentityModel.Tokens.LogMessages; #nullable enable @@ -19,25 +19,50 @@ internal static ValidationResult ValidateSignature( { if (samlToken is null) { - return ValidationError.NullParameter( + return SignatureValidationError.NullParameter( nameof(samlToken), ValidationError.GetCurrentStackFrame()); } if (validationParameters is null) { - return ValidationError.NullParameter( + return SignatureValidationError.NullParameter( nameof(validationParameters), ValidationError.GetCurrentStackFrame()); } // Delegate is set by the user, we call it and return the result. if (validationParameters.SignatureValidator is not null) - return validationParameters.SignatureValidator(samlToken, validationParameters, null, callContext); + { + try + { + ValidationResult signatureValidationResult = validationParameters.SignatureValidator( + samlToken, + validationParameters, + null, // configuration + callContext); + + if (!signatureValidationResult.IsValid) + return signatureValidationResult.UnwrapError().AddCurrentStackFrame(); + + return signatureValidationResult; + } +#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 SignatureValidationError( + new MessageDetail(TokenLogMessages.IDX10272), + ValidationFailureType.SignatureValidatorThrew, + typeof(SecurityTokenInvalidSignatureException), + ValidationError.GetCurrentStackFrame(), + innerException: ex); + } + } // If the user wants to accept unsigned tokens, they must set validationParameters.SignatureValidator if (samlToken.Assertion.Signature is null) - return new XmlValidationError( + return new SignatureValidationError( new MessageDetail( TokenLogMessages.IDX10504, samlToken.Assertion.CanonicalString), @@ -63,16 +88,14 @@ internal static ValidationResult ValidateSignature( resolvedKey = SamlTokenUtilities.ResolveTokenSigningKey(samlToken.Assertion.Signature.KeyInfo, validationParameters); } - ValidationError? error = null; - if (resolvedKey is not null) { keyMatched = true; var result = ValidateSignatureUsingKey(resolvedKey, samlToken, validationParameters, callContext); - if (result.IsValid) - return result; + if (!result.IsValid) + return result.UnwrapError().AddCurrentStackFrame(); - error = result.UnwrapError(); + return result; } bool canMatchKey = samlToken.Assertion.Signature.KeyInfo != null; @@ -102,12 +125,12 @@ internal static ValidationResult ValidateSignature( } if (canMatchKey && keyMatched) - return new XmlValidationError( + return new SignatureValidationError( new MessageDetail( TokenLogMessages.IDX10514, keysAttempted?.ToString(), samlToken.Assertion.Signature.KeyInfo, - GetErrorStrings(error, errors), + GetErrorStrings(errors), samlToken), ValidationFailureType.SignatureValidationFailed, typeof(SecurityTokenInvalidSignatureException), @@ -120,17 +143,17 @@ internal static ValidationResult ValidateSignature( keysAttemptedString = keysAttempted!.ToString(); if (keysAttemptedString is not null) - return new XmlValidationError( + return new SignatureValidationError( new MessageDetail( TokenLogMessages.IDX10512, keysAttemptedString, - GetErrorStrings(error, errors), + GetErrorStrings(errors), samlToken), ValidationFailureType.SignatureValidationFailed, typeof(SecurityTokenSignatureKeyNotFoundException), ValidationError.GetCurrentStackFrame()); - return new XmlValidationError( + return new SignatureValidationError( new MessageDetail(TokenLogMessages.IDX10500), ValidationFailureType.SignatureValidationFailed, typeof(SecurityTokenSignatureKeyNotFoundException), @@ -139,43 +162,61 @@ internal static ValidationResult ValidateSignature( private static ValidationResult ValidateSignatureUsingKey(SecurityKey key, Saml2SecurityToken samlToken, ValidationParameters validationParameters, CallContext callContext) { - ValidationResult algorithmValidationResult = validationParameters.AlgorithmValidator( - samlToken.Assertion.Signature.SignedInfo.SignatureMethod, - key, - samlToken, - validationParameters, - callContext); - - if (!algorithmValidationResult.IsValid) + try { - return algorithmValidationResult.UnwrapError().AddCurrentStackFrame(); - } - else - { - var validationError = samlToken.Assertion.Signature.Verify( + ValidationResult algorithmValidationResult = validationParameters.AlgorithmValidator( + samlToken.Assertion.Signature.SignedInfo.SignatureMethod, key, - validationParameters.CryptoProviderFactory ?? key.CryptoProviderFactory, + samlToken, + validationParameters, callContext); - if (validationError is null) + if (!algorithmValidationResult.IsValid) { - samlToken.SigningKey = key; - - return key; - } - else - { - return validationError.AddCurrentStackFrame(); + var algorithmValidationError = algorithmValidationResult.UnwrapError().AddCurrentStackFrame(); + return new SignatureValidationError( + new MessageDetail( + TokenLogMessages.IDX10518, + algorithmValidationError.MessageDetail.Message), + algorithmValidationError.FailureType, // Surface the algorithm validation error's failure type. + typeof(SecurityTokenInvalidSignatureException), + SignatureValidationError.GetCurrentStackFrame(), + algorithmValidationError); // Pass the algorithm validation error as the inner validation error. } } +#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 SignatureValidationError( + new MessageDetail(TokenLogMessages.IDX10273), + ValidationFailureType.AlgorithmValidatorThrew, + typeof(SecurityTokenInvalidSignatureException), + ValidationError.GetCurrentStackFrame(), + null, // No need to create an AlgorithmValidationError for this case. + ex); + } + + var validationError = samlToken.Assertion.Signature.Verify( + key, + validationParameters.CryptoProviderFactory ?? key.CryptoProviderFactory, + callContext); + + if (validationError is null) + { + samlToken.SigningKey = key; + + return key; + } + else + { + return validationError.AddCurrentStackFrame(); + } } - private static string GetErrorStrings(ValidationError? error, List? errors) + private static string GetErrorStrings(List? errors) { // This method is called if there are errors in the signature validation process. - // This check is there to account for the optional parameter. - if (error is not null) - return error.MessageDetail.Message; if (errors is null) return string.Empty; diff --git a/src/Microsoft.IdentityModel.Tokens/Delegates.cs b/src/Microsoft.IdentityModel.Tokens/Delegates.cs index e116fc4662..876d267345 100644 --- a/src/Microsoft.IdentityModel.Tokens/Delegates.cs +++ b/src/Microsoft.IdentityModel.Tokens/Delegates.cs @@ -204,6 +204,10 @@ namespace Microsoft.IdentityModel.Tokens /// The to be used for logging. /// This method is not expected to throw. /// The validated . - internal delegate ValidationResult SignatureValidationDelegate(SecurityToken token, ValidationParameters validationParameters, BaseConfiguration? configuration, CallContext? callContext); + internal delegate ValidationResult SignatureValidationDelegate( + SecurityToken token, + ValidationParameters validationParameters, + BaseConfiguration? configuration, + CallContext callContext); #nullable restore } diff --git a/src/Microsoft.IdentityModel.Tokens/Exceptions/SecurityTokenInvalidOperationException.cs b/src/Microsoft.IdentityModel.Tokens/Exceptions/SecurityTokenInvalidOperationException.cs new file mode 100644 index 0000000000..61cf94c224 --- /dev/null +++ b/src/Microsoft.IdentityModel.Tokens/Exceptions/SecurityTokenInvalidOperationException.cs @@ -0,0 +1,41 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Runtime.Serialization; + +namespace Microsoft.IdentityModel.Tokens +{ + /// + /// Throw this exception when a method call is invalid for the object's current state. + /// + [Serializable] + internal class SecurityTokenInvalidOperationException : InvalidOperationException + { + /// + /// Initializes a new instance of the class. + /// + public SecurityTokenInvalidOperationException() : base() { } + + /// + /// Initializes a new instance of the class with a specified error message. + /// + /// The error message that explains the reason for the exception. + public SecurityTokenInvalidOperationException(string message) : base(message) { } + + /// + /// Initializes a new instance of the class with a specified error message + /// and a reference to the inner exception that is the cause of this exception. + /// + /// The error message that explains the reason for the exception. + /// The that is the cause of the current exception, or a null reference if no inner exception is specified. + public SecurityTokenInvalidOperationException(string message, Exception innerException) : base(message, innerException) { } + + /// + /// Initializes a new instance of the class. + /// + /// the that holds the serialized object data. + /// The contextual information about the source or destination. + protected SecurityTokenInvalidOperationException(SerializationInfo info, StreamingContext context) : base(info, context) { } + } +} diff --git a/src/Microsoft.IdentityModel.Tokens/InternalAPI.Unshipped.txt b/src/Microsoft.IdentityModel.Tokens/InternalAPI.Unshipped.txt index 15dcd06514..cbfa1e30c6 100644 --- a/src/Microsoft.IdentityModel.Tokens/InternalAPI.Unshipped.txt +++ b/src/Microsoft.IdentityModel.Tokens/InternalAPI.Unshipped.txt @@ -1,20 +1,18 @@ 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.IDX10272 = "IDX10272: SignatureValidationDelegate threw an exception, see inner exception." -> string +const Microsoft.IdentityModel.Tokens.LogMessages.IDX10273 = "IDX10273: AlgorithmValidationDelegate threw an exception, see inner exception." -> string +const Microsoft.IdentityModel.Tokens.LogMessages.IDX10274 = "IDX10274: IssuerSigningKeyValidationDelegate threw an exception, see inner exception." -> string const Microsoft.IdentityModel.Tokens.LogMessages.IDX10275 = "IDX10275: TokenTypeValidationDelegate threw an exception, see inner exception." -> string Microsoft.IdentityModel.Tokens.AlgorithmValidationError Microsoft.IdentityModel.Tokens.AlgorithmValidationError.AlgorithmValidationError(Microsoft.IdentityModel.Tokens.MessageDetail messageDetail, Microsoft.IdentityModel.Tokens.ValidationFailureType validationFailureType, System.Type exceptionType, System.Diagnostics.StackFrame stackFrame, string invalidAlgorithm, System.Exception innerException = null) -> void Microsoft.IdentityModel.Tokens.AlgorithmValidationError.InvalidAlgorithm.get -> string -Microsoft.IdentityModel.Tokens.AlgorithmValidationError._invalidAlgorithm -> string Microsoft.IdentityModel.Tokens.AudienceValidationError.AudienceValidationError(Microsoft.IdentityModel.Tokens.MessageDetail messageDetail, Microsoft.IdentityModel.Tokens.ValidationFailureType validationFailureType, System.Type exceptionType, System.Diagnostics.StackFrame stackFrame, System.Collections.Generic.IList tokenAudiences, System.Collections.Generic.IList validAudiences, System.Exception innerException = null) -> void Microsoft.IdentityModel.Tokens.AudienceValidationError.TokenAudiences.get -> System.Collections.Generic.IList Microsoft.IdentityModel.Tokens.AudienceValidationError.TokenAudiences.set -> void Microsoft.IdentityModel.Tokens.AudienceValidationError.ValidAudiences.get -> System.Collections.Generic.IList Microsoft.IdentityModel.Tokens.AudienceValidationError.ValidAudiences.set -> void -Microsoft.IdentityModel.Tokens.IssuerSigningKeyValidationError -Microsoft.IdentityModel.Tokens.IssuerSigningKeyValidationError.InvalidSigningKey.get -> Microsoft.IdentityModel.Tokens.SecurityKey -Microsoft.IdentityModel.Tokens.IssuerSigningKeyValidationError.InvalidSigningKey.set -> void -Microsoft.IdentityModel.Tokens.IssuerSigningKeyValidationError.IssuerSigningKeyValidationError(Microsoft.IdentityModel.Tokens.MessageDetail messageDetail, System.Type exceptionType, System.Diagnostics.StackFrame stackFrame, Microsoft.IdentityModel.Tokens.SecurityKey invalidSigningKey, Microsoft.IdentityModel.Tokens.ValidationFailureType failureType = null, System.Exception innerException = null) -> void Microsoft.IdentityModel.Tokens.IssuerValidationError.InvalidIssuer.get -> string Microsoft.IdentityModel.Tokens.IssuerValidationError.IssuerValidationError(Microsoft.IdentityModel.Tokens.MessageDetail messageDetail, Microsoft.IdentityModel.Tokens.ValidationFailureType validationFailureType, System.Type exceptionType, System.Diagnostics.StackFrame stackFrame, string invalidIssuer, System.Exception innerException = null) -> void Microsoft.IdentityModel.Tokens.IssuerValidationSource.IssuerMatchedConfiguration = 1 -> Microsoft.IdentityModel.Tokens.IssuerValidationSource @@ -22,9 +20,17 @@ Microsoft.IdentityModel.Tokens.IssuerValidationSource.IssuerMatchedValidationPar Microsoft.IdentityModel.Tokens.LifetimeValidationError.Expires.get -> System.DateTime? Microsoft.IdentityModel.Tokens.LifetimeValidationError.LifetimeValidationError(Microsoft.IdentityModel.Tokens.MessageDetail messageDetail, Microsoft.IdentityModel.Tokens.ValidationFailureType validationFailureType, System.Type exceptionType, System.Diagnostics.StackFrame stackFrame, System.DateTime? notBefore, System.DateTime? expires, System.Exception innerException = null) -> void Microsoft.IdentityModel.Tokens.LifetimeValidationError.NotBefore.get -> System.DateTime? +Microsoft.IdentityModel.Tokens.SecurityTokenInvalidOperationException +Microsoft.IdentityModel.Tokens.SecurityTokenInvalidOperationException.SecurityTokenInvalidOperationException() -> void +Microsoft.IdentityModel.Tokens.SecurityTokenInvalidOperationException.SecurityTokenInvalidOperationException(string message) -> void +Microsoft.IdentityModel.Tokens.SecurityTokenInvalidOperationException.SecurityTokenInvalidOperationException(string message, System.Exception innerException) -> void +Microsoft.IdentityModel.Tokens.SecurityTokenInvalidOperationException.SecurityTokenInvalidOperationException(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) -> void +Microsoft.IdentityModel.Tokens.SignatureValidationError +Microsoft.IdentityModel.Tokens.SignatureValidationError.InnerValidationError.get -> Microsoft.IdentityModel.Tokens.ValidationError +Microsoft.IdentityModel.Tokens.SignatureValidationError.SignatureValidationError(Microsoft.IdentityModel.Tokens.MessageDetail messageDetail, Microsoft.IdentityModel.Tokens.ValidationFailureType validationFailureType, System.Type exceptionType, System.Diagnostics.StackFrame stackFrame, Microsoft.IdentityModel.Tokens.ValidationError innerValidationError = null, System.Exception innerException = null) -> void Microsoft.IdentityModel.Tokens.TokenTypeValidationError -Microsoft.IdentityModel.Tokens.TokenTypeValidationError.TokenTypeValidationError(Microsoft.IdentityModel.Tokens.MessageDetail messageDetail, Microsoft.IdentityModel.Tokens.ValidationFailureType validationFailureType, System.Type exceptionType, System.Diagnostics.StackFrame stackFrame, string invalidTokenType, System.Exception innerException = null) -> void Microsoft.IdentityModel.Tokens.TokenTypeValidationError.InvalidTokenType.get -> string +Microsoft.IdentityModel.Tokens.TokenTypeValidationError.TokenTypeValidationError(Microsoft.IdentityModel.Tokens.MessageDetail messageDetail, Microsoft.IdentityModel.Tokens.ValidationFailureType validationFailureType, System.Type exceptionType, System.Diagnostics.StackFrame stackFrame, string invalidTokenType, System.Exception innerException = null) -> void Microsoft.IdentityModel.Tokens.TokenValidationParameters.TimeProvider.get -> System.TimeProvider Microsoft.IdentityModel.Tokens.TokenValidationParameters.TimeProvider.set -> void Microsoft.IdentityModel.Tokens.ValidationError.AddCurrentStackFrame(string filePath = "", int lineNumber = 0, int skipFrames = 1) -> Microsoft.IdentityModel.Tokens.ValidationError @@ -36,16 +42,19 @@ Microsoft.IdentityModel.Tokens.ValidationResult.Error.get -> Microsoft. Microsoft.IdentityModel.Tokens.ValidationResult.IsValid.get -> bool Microsoft.IdentityModel.Tokens.ValidationResult.Result.get -> TResult override Microsoft.IdentityModel.Tokens.AlgorithmValidationError.GetException() -> System.Exception -override Microsoft.IdentityModel.Tokens.IssuerSigningKeyValidationError.GetException() -> System.Exception +override Microsoft.IdentityModel.Tokens.SignatureValidationError.GetException() -> System.Exception override Microsoft.IdentityModel.Tokens.TokenTypeValidationError.GetException() -> System.Exception +static Microsoft.IdentityModel.Tokens.SignatureValidationError.NullParameter(string parameterName, System.Diagnostics.StackFrame stackFrame) -> Microsoft.IdentityModel.Tokens.SignatureValidationError static Microsoft.IdentityModel.Tokens.TokenTypeValidationError.NullParameter(string parameterName, System.Diagnostics.StackFrame stackFrame) -> Microsoft.IdentityModel.Tokens.TokenTypeValidationError 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.AlgorithmValidatorThrew -> Microsoft.IdentityModel.Tokens.ValidationFailureType +static readonly Microsoft.IdentityModel.Tokens.ValidationFailureType.IssuerSigningKeyValidatorThrew -> Microsoft.IdentityModel.Tokens.ValidationFailureType 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 +static readonly Microsoft.IdentityModel.Tokens.ValidationFailureType.SignatureValidatorThrew -> Microsoft.IdentityModel.Tokens.ValidationFailureType static readonly Microsoft.IdentityModel.Tokens.ValidationFailureType.TokenExceedsMaximumSize -> Microsoft.IdentityModel.Tokens.ValidationFailureType static readonly Microsoft.IdentityModel.Tokens.ValidationFailureType.TokenIsNotSigned -> Microsoft.IdentityModel.Tokens.ValidationFailureType static readonly Microsoft.IdentityModel.Tokens.ValidationFailureType.TokenTypeValidatorThrew -> Microsoft.IdentityModel.Tokens.ValidationFailureType diff --git a/src/Microsoft.IdentityModel.Tokens/LogMessages.cs b/src/Microsoft.IdentityModel.Tokens/LogMessages.cs index 8e2cecae39..6a203b74e7 100644 --- a/src/Microsoft.IdentityModel.Tokens/LogMessages.cs +++ b/src/Microsoft.IdentityModel.Tokens/LogMessages.cs @@ -88,6 +88,8 @@ 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 IDX10272 = "IDX10272: SignatureValidationDelegate threw an exception, see inner exception."; + public const string IDX10273 = "IDX10273: AlgorithmValidationDelegate threw an exception, see inner exception."; public const string IDX10275 = "IDX10275: TokenTypeValidationDelegate threw an exception, see inner exception."; // 10500 - SignatureValidation diff --git a/src/Microsoft.IdentityModel.Tokens/Validation/Results/Details/AlgorithmValidationError.cs b/src/Microsoft.IdentityModel.Tokens/Validation/Results/Details/AlgorithmValidationError.cs index 940d099f32..de0dd174d5 100644 --- a/src/Microsoft.IdentityModel.Tokens/Validation/Results/Details/AlgorithmValidationError.cs +++ b/src/Microsoft.IdentityModel.Tokens/Validation/Results/Details/AlgorithmValidationError.cs @@ -9,8 +9,6 @@ namespace Microsoft.IdentityModel.Tokens { internal class AlgorithmValidationError : ValidationError { - protected string? _invalidAlgorithm; - public AlgorithmValidationError( MessageDetail messageDetail, ValidationFailureType validationFailureType, @@ -20,7 +18,7 @@ public AlgorithmValidationError( Exception? innerException = null) : base(messageDetail, validationFailureType, exceptionType, stackFrame, innerException) { - _invalidAlgorithm = invalidAlgorithm; + InvalidAlgorithm = invalidAlgorithm; } internal override Exception GetException() @@ -29,8 +27,9 @@ internal override Exception GetException() { SecurityTokenInvalidAlgorithmException exception = new(MessageDetail.Message, InnerException) { - InvalidAlgorithm = _invalidAlgorithm + InvalidAlgorithm = InvalidAlgorithm }; + exception.SetValidationError(this); return exception; } @@ -38,7 +37,7 @@ internal override Exception GetException() return base.GetException(); } - internal string? InvalidAlgorithm => _invalidAlgorithm; + protected string? InvalidAlgorithm { get; } } } #nullable restore diff --git a/src/Microsoft.IdentityModel.Tokens/Validation/Results/Details/SignatureValidationError.cs b/src/Microsoft.IdentityModel.Tokens/Validation/Results/Details/SignatureValidationError.cs new file mode 100644 index 0000000000..78c51069d8 --- /dev/null +++ b/src/Microsoft.IdentityModel.Tokens/Validation/Results/Details/SignatureValidationError.cs @@ -0,0 +1,57 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Diagnostics; + +#nullable enable +namespace Microsoft.IdentityModel.Tokens +{ + internal class SignatureValidationError : ValidationError + { + public SignatureValidationError( + MessageDetail messageDetail, + ValidationFailureType validationFailureType, + Type exceptionType, + StackFrame stackFrame, + ValidationError? innerValidationError = null, + Exception? innerException = null) : + base(messageDetail, validationFailureType, exceptionType, stackFrame, innerException) + { + InnerValidationError = innerValidationError; + } + + internal override Exception GetException() + { + var inner = InnerException ?? InnerValidationError?.GetException(); + + if (ExceptionType == typeof(SecurityTokenInvalidSignatureException)) + { + SecurityTokenInvalidSignatureException exception = new(MessageDetail.Message, inner); + exception.SetValidationError(this); + + return exception; + } + else if (ExceptionType == typeof(SecurityTokenSignatureKeyNotFoundException)) + { + SecurityTokenSignatureKeyNotFoundException exception = new(MessageDetail.Message, inner); + exception.SetValidationError(this); + + return exception; + } + + return base.GetException(); + } + + internal static new SignatureValidationError NullParameter( + string parameterName, StackFrame stackFrame) => new( + MessageDetail.NullParameter(parameterName), + ValidationFailureType.NullArgument, + typeof(SecurityTokenArgumentNullException), + stackFrame, + null); // innerValidationError + + protected internal ValidationError? InnerValidationError { get; } + } +} +#nullable restore diff --git a/src/Microsoft.IdentityModel.Tokens/Validation/Results/Details/ValidationError.cs b/src/Microsoft.IdentityModel.Tokens/Validation/Results/Details/ValidationError.cs index 8f40ffdadb..5347e72d65 100644 --- a/src/Microsoft.IdentityModel.Tokens/Validation/Results/Details/ValidationError.cs +++ b/src/Microsoft.IdentityModel.Tokens/Validation/Results/Details/ValidationError.cs @@ -66,6 +66,8 @@ internal Exception GetException(Type exceptionType, Exception? innerException) exception = new SecurityTokenInvalidIssuerException(MessageDetail.Message); else if (exceptionType == typeof(SecurityTokenInvalidLifetimeException)) exception = new SecurityTokenInvalidLifetimeException(MessageDetail.Message); + else if (exceptionType == typeof(SecurityTokenInvalidOperationException)) + exception = new SecurityTokenInvalidOperationException(MessageDetail.Message); else if (exceptionType == typeof(SecurityTokenReplayDetectedException)) exception = new SecurityTokenReplayDetectedException(MessageDetail.Message); else if (exceptionType == typeof(SecurityTokenReplayAddFailedException)) @@ -123,6 +125,8 @@ internal Exception GetException(Type exceptionType, Exception? innerException) exception = new SecurityTokenInvalidIssuerException(MessageDetail.Message, innerException); else if (exceptionType == typeof(SecurityTokenInvalidLifetimeException)) exception = new SecurityTokenInvalidLifetimeException(MessageDetail.Message, innerException); + else if (exceptionType == typeof(SecurityTokenInvalidOperationException)) + exception = new SecurityTokenInvalidOperationException(MessageDetail.Message, innerException); else if (exceptionType == typeof(SecurityTokenReplayDetectedException)) exception = new SecurityTokenReplayDetectedException(MessageDetail.Message, innerException); else if (exceptionType == typeof(SecurityTokenReplayAddFailedException)) diff --git a/src/Microsoft.IdentityModel.Tokens/Validation/ValidationFailureType.cs b/src/Microsoft.IdentityModel.Tokens/Validation/ValidationFailureType.cs index 1f1dcef7af..bcdab9cfbd 100644 --- a/src/Microsoft.IdentityModel.Tokens/Validation/ValidationFailureType.cs +++ b/src/Microsoft.IdentityModel.Tokens/Validation/ValidationFailureType.cs @@ -129,12 +129,22 @@ private class InvalidSecurityTokenFailure : ValidationFailureType { internal Inv public static readonly ValidationFailureType XmlValidationFailed = new XmlValidationFailure("XmlValidationFailed"); private class XmlValidationFailure : ValidationFailureType { internal XmlValidationFailure(string name) : base(name) { } } + /// + /// Defines a type that represents the fact that the algorithm validation delegate threw an exception. + /// + public static readonly ValidationFailureType AlgorithmValidatorThrew = new AlgorithmValidationFailure("AlgorithmValidatorThrew"); + /// /// Defines a type that represents that a token is invalid. /// public static readonly ValidationFailureType IssuerValidatorThrew = new IssuerValidatorFailure("IssuerValidatorThrew"); private class IssuerValidatorFailure : ValidationFailureType { internal IssuerValidatorFailure(string name) : base(name) { } } + /// + /// Defines a type that represents the fact that the signature validation delegate threw an exception. + /// + public static readonly ValidationFailureType SignatureValidatorThrew = new SignatureValidationFailure("SignatureValidatorThrew"); + /// /// Defines a type that represents the fact that the token type validation delegate threw an exception. /// diff --git a/src/Microsoft.IdentityModel.Xml/Exceptions/XmlValidationError.cs b/src/Microsoft.IdentityModel.Xml/Exceptions/XmlValidationError.cs deleted file mode 100644 index a339d58da7..0000000000 --- a/src/Microsoft.IdentityModel.Xml/Exceptions/XmlValidationError.cs +++ /dev/null @@ -1,37 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -using System; -using System.Diagnostics; -using Microsoft.IdentityModel.Tokens; - -#nullable enable -namespace Microsoft.IdentityModel.Xml -{ - internal class XmlValidationError : ValidationError - { - public XmlValidationError( - MessageDetail messageDetail, - ValidationFailureType validationFailureType, - Type exceptionType, - StackFrame stackFrame, - Exception? innerException = null) : - base(messageDetail, validationFailureType, exceptionType, stackFrame, innerException) - { - - } - - internal override Exception GetException() - { - if (ExceptionType == typeof(XmlValidationException)) - { - XmlValidationException exception = new(MessageDetail.Message, InnerException); - exception.SetValidationError(this); - return exception; - } - - return base.GetException(); - } - } -} -#nullable restore diff --git a/src/Microsoft.IdentityModel.Xml/InternalAPI.Unshipped.txt b/src/Microsoft.IdentityModel.Xml/InternalAPI.Unshipped.txt index 59024a50b0..7541dd5624 100644 --- a/src/Microsoft.IdentityModel.Xml/InternalAPI.Unshipped.txt +++ b/src/Microsoft.IdentityModel.Xml/InternalAPI.Unshipped.txt @@ -1,6 +1,6 @@ -Microsoft.IdentityModel.Xml.Reference.Verify(Microsoft.IdentityModel.Tokens.CryptoProviderFactory cryptoProviderFactory, Microsoft.IdentityModel.Tokens.CallContext callContext) -> Microsoft.IdentityModel.Tokens.ValidationError -Microsoft.IdentityModel.Xml.Signature.Verify(Microsoft.IdentityModel.Tokens.SecurityKey key, Microsoft.IdentityModel.Tokens.CryptoProviderFactory cryptoProviderFactory, Microsoft.IdentityModel.Tokens.CallContext callContext) -> Microsoft.IdentityModel.Tokens.ValidationError -Microsoft.IdentityModel.Xml.SignedInfo.Verify(Microsoft.IdentityModel.Tokens.CryptoProviderFactory cryptoProviderFactory, Microsoft.IdentityModel.Tokens.CallContext callContext) -> Microsoft.IdentityModel.Tokens.ValidationError +Microsoft.IdentityModel.Xml.Reference.Verify(Microsoft.IdentityModel.Tokens.CryptoProviderFactory cryptoProviderFactory, Microsoft.IdentityModel.Tokens.CallContext callContext) -> Microsoft.IdentityModel.Tokens.SignatureValidationError +Microsoft.IdentityModel.Xml.Signature.Verify(Microsoft.IdentityModel.Tokens.SecurityKey key, Microsoft.IdentityModel.Tokens.CryptoProviderFactory cryptoProviderFactory, Microsoft.IdentityModel.Tokens.CallContext callContext) -> Microsoft.IdentityModel.Tokens.SignatureValidationError +Microsoft.IdentityModel.Xml.SignedInfo.Verify(Microsoft.IdentityModel.Tokens.CryptoProviderFactory cryptoProviderFactory, Microsoft.IdentityModel.Tokens.CallContext callContext) -> Microsoft.IdentityModel.Tokens.SignatureValidationError Microsoft.IdentityModel.Xml.XmlValidationError Microsoft.IdentityModel.Xml.XmlValidationError.XmlValidationError(Microsoft.IdentityModel.Tokens.MessageDetail messageDetail, Microsoft.IdentityModel.Tokens.ValidationFailureType validationFailureType, System.Type exceptionType, System.Diagnostics.StackFrame stackFrame, System.Exception innerException = null) -> void Microsoft.IdentityModel.Xml.XmlValidationException.SetValidationError(Microsoft.IdentityModel.Tokens.ValidationError validationError) -> void diff --git a/src/Microsoft.IdentityModel.Xml/Reference.cs b/src/Microsoft.IdentityModel.Xml/Reference.cs index 2a1f6870af..a2534ef1cf 100644 --- a/src/Microsoft.IdentityModel.Xml/Reference.cs +++ b/src/Microsoft.IdentityModel.Xml/Reference.cs @@ -134,23 +134,25 @@ public void Verify(CryptoProviderFactory cryptoProviderFactory) /// supplies the . /// contextual information for diagnostics. /// if is null. - internal ValidationError? Verify( + internal SignatureValidationError? Verify( CryptoProviderFactory cryptoProviderFactory, #pragma warning disable CA1801 // Review unused parameters CallContext callContext) #pragma warning restore CA1801 { if (cryptoProviderFactory == null) - return ValidationError.NullParameter(nameof(cryptoProviderFactory), new System.Diagnostics.StackFrame()); + return SignatureValidationError.NullParameter( + nameof(cryptoProviderFactory), + ValidationError.GetCurrentStackFrame()); if (!Utility.AreEqual(ComputeDigest(cryptoProviderFactory), Convert.FromBase64String(DigestValue))) - return new XmlValidationError( + return new SignatureValidationError( new MessageDetail( LogMessages.IDX30201, Uri ?? Id), ValidationFailureType.XmlValidationFailed, - typeof(XmlValidationException), - new System.Diagnostics.StackFrame()); + typeof(SecurityTokenInvalidSignatureException), + ValidationError.GetCurrentStackFrame()); return null; } diff --git a/src/Microsoft.IdentityModel.Xml/Signature.cs b/src/Microsoft.IdentityModel.Xml/Signature.cs index b7bf6219fa..5a0d122beb 100644 --- a/src/Microsoft.IdentityModel.Xml/Signature.cs +++ b/src/Microsoft.IdentityModel.Xml/Signature.cs @@ -126,7 +126,7 @@ public void Verify(SecurityKey key, CryptoProviderFactory cryptoProviderFactory) } #nullable enable - internal ValidationError? Verify( + internal SignatureValidationError? Verify( SecurityKey key, CryptoProviderFactory cryptoProviderFactory, #pragma warning disable CA1801 // Review unused parameters @@ -134,34 +134,38 @@ public void Verify(SecurityKey key, CryptoProviderFactory cryptoProviderFactory) #pragma warning restore CA1801 { if (key is null) - return ValidationError.NullParameter(nameof(key), ValidationError.GetCurrentStackFrame()); + return SignatureValidationError.NullParameter( + nameof(key), + ValidationError.GetCurrentStackFrame()); if (cryptoProviderFactory is null) - return ValidationError.NullParameter(nameof(cryptoProviderFactory), ValidationError.GetCurrentStackFrame()); + return SignatureValidationError.NullParameter( + nameof(cryptoProviderFactory), + ValidationError.GetCurrentStackFrame()); if (SignedInfo is null) - return new XmlValidationError( + return new SignatureValidationError( new MessageDetail(LogMessages.IDX30212), - ValidationFailureType.XmlValidationFailed, - typeof(XmlValidationException), + ValidationFailureType.SignatureValidationFailed, + typeof(SecurityTokenInvalidSignatureException), ValidationError.GetCurrentStackFrame()); if (!cryptoProviderFactory.IsSupportedAlgorithm(SignedInfo.SignatureMethod, key)) - return new XmlValidationError( + return new SignatureValidationError( new MessageDetail(LogMessages.IDX30207, SignedInfo.SignatureMethod, cryptoProviderFactory.GetType()), ValidationFailureType.XmlValidationFailed, - typeof(XmlValidationException), + typeof(SecurityTokenInvalidSignatureException), ValidationError.GetCurrentStackFrame()); var signatureProvider = cryptoProviderFactory.CreateForVerifying(key, SignedInfo.SignatureMethod); if (signatureProvider is null) - return new XmlValidationError( + return new SignatureValidationError( new MessageDetail(LogMessages.IDX30203, cryptoProviderFactory, key, SignedInfo.SignatureMethod), ValidationFailureType.XmlValidationFailed, - typeof(XmlValidationException), + typeof(SecurityTokenInvalidSignatureException), ValidationError.GetCurrentStackFrame()); - ValidationError? validationError = null; + SignatureValidationError? validationError = null; try { @@ -170,10 +174,10 @@ public void Verify(SecurityKey key, CryptoProviderFactory cryptoProviderFactory) SignedInfo.GetCanonicalBytes(memoryStream); if (!signatureProvider.Verify(memoryStream.ToArray(), Convert.FromBase64String(SignatureValue))) { - validationError = new XmlValidationError( + validationError = new SignatureValidationError( new MessageDetail(LogMessages.IDX30200, cryptoProviderFactory, key), ValidationFailureType.XmlValidationFailed, - typeof(XmlValidationException), + typeof(SecurityTokenInvalidSignatureException), ValidationError.GetCurrentStackFrame()); } } diff --git a/src/Microsoft.IdentityModel.Xml/SignedInfo.cs b/src/Microsoft.IdentityModel.Xml/SignedInfo.cs index 9f16187e56..3ccf96df8e 100644 --- a/src/Microsoft.IdentityModel.Xml/SignedInfo.cs +++ b/src/Microsoft.IdentityModel.Xml/SignedInfo.cs @@ -118,16 +118,18 @@ public void Verify(CryptoProviderFactory cryptoProviderFactory) /// /// supplies any required cryptographic operators. /// contextual information for diagnostics. - internal ValidationError? Verify( + internal SignatureValidationError? Verify( CryptoProviderFactory cryptoProviderFactory, #pragma warning disable CA1801 CallContext callContext) #pragma warning restore CA1801 { if (cryptoProviderFactory == null) - return ValidationError.NullParameter(nameof(cryptoProviderFactory), ValidationError.GetCurrentStackFrame()); + return SignatureValidationError.NullParameter( + nameof(cryptoProviderFactory), + ValidationError.GetCurrentStackFrame()); - ValidationError? validationError = null; + SignatureValidationError? validationError = null; for (int i = 0; i < References.Count; i++) { diff --git a/test/Microsoft.IdentityModel.JsonWebTokens.Tests/JsonWebTokenHandler.Extensibility.Algorithm.cs b/test/Microsoft.IdentityModel.JsonWebTokens.Tests/JsonWebTokenHandler.Extensibility.Algorithm.cs new file mode 100644 index 0000000000..fdfc48ed4a --- /dev/null +++ b/test/Microsoft.IdentityModel.JsonWebTokens.Tests/JsonWebTokenHandler.Extensibility.Algorithm.cs @@ -0,0 +1,334 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Diagnostics; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.IdentityModel.JsonWebTokens.Tests; +using Microsoft.IdentityModel.Logging; +using Microsoft.IdentityModel.TestUtils; +using Microsoft.IdentityModel.Tokens; +using Xunit; + +#nullable enable +namespace Microsoft.IdentityModel.JsonWebTokens.Extensibility.Tests +{ + public partial class JsonWebTokenHandlerValidateTokenAsyncTests + { + [Theory, MemberData(nameof(Algorithm_ExtensibilityTestCases), DisableDiscoveryEnumeration = true)] + public async Task ValidateTokenAsync_AlgorithmValidator_Extensibility(AlgorithmExtensibilityTheoryData theoryData) + { + var context = TestUtilities.WriteHeader($"{this}.{nameof(ValidateTokenAsync_AlgorithmValidator_Extensibility)}", theoryData); + context.IgnoreType = false; + for (int i = 0; i < theoryData.ExtraStackFrames; i++) + theoryData.ValidationError!.AddStackFrame(new StackFrame(false)); + + theoryData.ValidationParameters!.IssuerSigningKeys.Add(theoryData.SigningKey); + + try + { + ValidationResult validationResult = await theoryData.JsonWebTokenHandler.ValidateTokenAsync( + theoryData.JsonWebToken!, + theoryData.ValidationParameters!, + theoryData.CallContext, + CancellationToken.None); + + if (validationResult.IsValid) + { + // We expect the validation to fail, but it passed + context.AddDiff("ValidationResult is Valid."); + } + else + { + ValidationError validationError = validationResult.UnwrapError(); + + if (validationError is SignatureValidationError signatureValidationError && + signatureValidationError.InnerValidationError is not null) + { + IdentityComparer.AreValidationErrorsEqual( + signatureValidationError.InnerValidationError, + theoryData.ValidationError, + context); + } + else + { + IdentityComparer.AreValidationErrorsEqual( + validationError, + theoryData.ValidationError, + context); + } + + var exception = validationError.GetException(); + theoryData.ExpectedException.ProcessException(exception, context); + // Process the inner exception since invalid algorithm exceptions are wrapped inside + // invalid signature exceptions + if (theoryData.ExpectedInnerException is not null) + theoryData.ExpectedInnerException.ProcessException(exception.InnerException, context); + } + } + catch (Exception ex) + { + // We expect the validation to fail, but it threw an exception + context.AddDiff($"ValidateTokenAsync threw exception: {ex}"); + } + + TestUtilities.AssertFailIfErrors(context); + } + + public static TheoryData Algorithm_ExtensibilityTestCases + { + get + { + var theoryData = new TheoryData(); + CallContext callContext = new CallContext(); + var utcNow = DateTime.UtcNow; + var utcPlusOneHour = utcNow + TimeSpan.FromHours(1); + + #region return CustomAlgorithmValidationError + // Test cases where delegate is overridden and return a CustomAlgorithmValidationError + // CustomAlgorithmValidationError : AlgorithmValidationError, ExceptionType: SecurityTokenInvalidAlgorithmException + theoryData.Add(new AlgorithmExtensibilityTheoryData( + "CustomAlgorithmValidatorDelegate", + utcNow, + CustomAlgorithmValidationDelegates.CustomAlgorithmValidatorDelegate, + extraStackFrames: 1) + { + ExpectedException = new ExpectedException( + typeof(SecurityTokenInvalidSignatureException), + "IDX10518:", + typeof(SecurityTokenInvalidAlgorithmException)), + ExpectedInnerException = new ExpectedException( + typeof(SecurityTokenInvalidAlgorithmException), + nameof(CustomAlgorithmValidationDelegates.CustomAlgorithmValidatorDelegate)), + ValidationError = new CustomAlgorithmValidationError( + new MessageDetail( + nameof(CustomAlgorithmValidationDelegates.CustomAlgorithmValidatorDelegate), null), + ValidationFailureType.AlgorithmValidationFailed, + typeof(SecurityTokenInvalidAlgorithmException), + new StackFrame("CustomAlgorithmValidationDelegates.cs", 160), + "algorithm") + }); + + // CustomAlgorithmValidationError : AlgorithmValidationError, ExceptionType: CustomSecurityTokenInvalidAlgorithmException : SecurityTokenInvalidAlgorithmException + theoryData.Add(new AlgorithmExtensibilityTheoryData( + "CustomAlgorithmValidatorCustomExceptionDelegate", + utcNow, + CustomAlgorithmValidationDelegates.CustomAlgorithmValidatorCustomExceptionDelegate, + extraStackFrames: 1) + { + ExpectedException = new ExpectedException( + typeof(SecurityTokenInvalidSignatureException), + "IDX10518:", + typeof(CustomSecurityTokenInvalidAlgorithmException)), + ExpectedInnerException = new ExpectedException( + typeof(CustomSecurityTokenInvalidAlgorithmException), + nameof(CustomAlgorithmValidationDelegates.CustomAlgorithmValidatorCustomExceptionDelegate)), + ValidationError = new CustomAlgorithmValidationError( + new MessageDetail( + nameof(CustomAlgorithmValidationDelegates.CustomAlgorithmValidatorCustomExceptionDelegate), null), + ValidationFailureType.AlgorithmValidationFailed, + typeof(CustomSecurityTokenInvalidAlgorithmException), + new StackFrame("CustomAlgorithmValidationDelegates.cs", 175), + "algorithm"), + }); + + // CustomAlgorithmValidationError : AlgorithmValidationError, ExceptionType: NotSupportedException : SystemException + theoryData.Add(new AlgorithmExtensibilityTheoryData( + "CustomAlgorithmValidatorUnknownExceptionDelegate", + utcNow, + CustomAlgorithmValidationDelegates.CustomAlgorithmValidatorUnknownExceptionDelegate, + extraStackFrames: 1) + { + // CustomAlgorithmValidationError does not handle the exception type 'NotSupportedException' + ExpectedException = new ExpectedException( + typeof(SecurityTokenInvalidSignatureException), + "IDX10518:", + typeof(SecurityTokenException)), + ExpectedInnerException = ExpectedException.SecurityTokenException( + LogHelper.FormatInvariant( + Tokens.LogMessages.IDX10002, // "IDX10002: Unknown exception type returned. Type: '{0}'. Message: '{1}'."; + typeof(NotSupportedException), + nameof(CustomAlgorithmValidationDelegates.CustomAlgorithmValidatorUnknownExceptionDelegate))), + ValidationError = new CustomAlgorithmValidationError( + new MessageDetail( + nameof(CustomAlgorithmValidationDelegates.CustomAlgorithmValidatorUnknownExceptionDelegate), null), + ValidationFailureType.AlgorithmValidationFailed, + typeof(NotSupportedException), + new StackFrame("CustomAlgorithmValidationDelegates.cs", 205), + "algorithm"), + }); + + // CustomAlgorithmValidationError : AlgorithmValidationError, ExceptionType: NotSupportedException : SystemException, ValidationFailureType: CustomAudienceValidationFailureType + theoryData.Add(new AlgorithmExtensibilityTheoryData( + "CustomAlgorithmValidatorCustomExceptionCustomFailureTypeDelegate", + utcNow, + CustomAlgorithmValidationDelegates.CustomAlgorithmValidatorCustomExceptionCustomFailureTypeDelegate, + extraStackFrames: 1) + { + ExpectedException = new ExpectedException( + typeof(SecurityTokenInvalidSignatureException), + "IDX10518:", + typeof(CustomSecurityTokenInvalidAlgorithmException)), + ExpectedInnerException = new ExpectedException( + typeof(CustomSecurityTokenInvalidAlgorithmException), + nameof(CustomAlgorithmValidationDelegates.CustomAlgorithmValidatorCustomExceptionCustomFailureTypeDelegate)), + ValidationError = new CustomAlgorithmValidationError( + new MessageDetail( + nameof(CustomAlgorithmValidationDelegates.CustomAlgorithmValidatorCustomExceptionCustomFailureTypeDelegate), null), + CustomAlgorithmValidationError.CustomAlgorithmValidationFailureType, + typeof(CustomSecurityTokenInvalidAlgorithmException), + new StackFrame("CustomAlgorithmValidationDelegates.cs", 190), + "algorithm"), + }); + #endregion + + #region return AlgorithmValidationError + // Test cases where delegate is overridden and return an AlgorithmValidationError + // AlgorithmValidationError : ValidationError, ExceptionType: SecurityTokenInvalidAlgorithmException + theoryData.Add(new AlgorithmExtensibilityTheoryData( + "AlgorithmValidatorDelegate", + utcNow, + CustomAlgorithmValidationDelegates.AlgorithmValidatorDelegate, + extraStackFrames: 1) + { + ExpectedException = new ExpectedException( + typeof(SecurityTokenInvalidSignatureException), + "IDX10518:", + typeof(SecurityTokenInvalidAlgorithmException)), + ExpectedInnerException = new ExpectedException( + typeof(SecurityTokenInvalidAlgorithmException), + nameof(CustomAlgorithmValidationDelegates.AlgorithmValidatorDelegate)), + ValidationError = new AlgorithmValidationError( + new MessageDetail( + nameof(CustomAlgorithmValidationDelegates.AlgorithmValidatorDelegate), null), + ValidationFailureType.AlgorithmValidationFailed, + typeof(SecurityTokenInvalidAlgorithmException), + new StackFrame("CustomAlgorithmValidationDelegates.cs", 235), + "algorithm") + }); + + // AlgorithmValidationError : ValidationError, ExceptionType: CustomSecurityTokenInvalidAlgorithmException : SecurityTokenInvalidAlgorithmException + theoryData.Add(new AlgorithmExtensibilityTheoryData( + "AlgorithmValidatorCustomAlgorithmExceptionTypeDelegate", + utcNow, + CustomAlgorithmValidationDelegates.AlgorithmValidatorCustomAlgorithmExceptionTypeDelegate, + extraStackFrames: 1) + { + // AlgorithmValidationError does not handle the exception type 'CustomSecurityTokenInvalidAlgorithmException' + ExpectedException = new ExpectedException( + typeof(SecurityTokenInvalidSignatureException), + "IDX10518:", + typeof(SecurityTokenException)), + ExpectedInnerException = ExpectedException.SecurityTokenException( + LogHelper.FormatInvariant( + Tokens.LogMessages.IDX10002, // "IDX10002: Unknown exception type returned. Type: '{0}'. Message: '{1}'."; + typeof(CustomSecurityTokenInvalidAlgorithmException), + nameof(CustomAlgorithmValidationDelegates.AlgorithmValidatorCustomAlgorithmExceptionTypeDelegate))), + ValidationError = new AlgorithmValidationError( + new MessageDetail( + nameof(CustomAlgorithmValidationDelegates.AlgorithmValidatorCustomAlgorithmExceptionTypeDelegate), null), + ValidationFailureType.AlgorithmValidationFailed, + typeof(CustomSecurityTokenInvalidAlgorithmException), + new StackFrame("CustomAlgorithmValidationDelegates.cs", 259), + "algorithm") + }); + + // AlgorithmValidationError : ValidationError, ExceptionType: CustomSecurityTokenException : SystemException + theoryData.Add(new AlgorithmExtensibilityTheoryData( + "AlgorithmValidatorCustomExceptionTypeDelegate", + utcNow, + CustomAlgorithmValidationDelegates.AlgorithmValidatorCustomExceptionTypeDelegate, + extraStackFrames: 1) + { + // AlgorithmValidationError does not handle the exception type 'CustomSecurityTokenException' + ExpectedException = new ExpectedException( + typeof(SecurityTokenInvalidSignatureException), + "IDX10518:", + typeof(SecurityTokenException)), + ExpectedInnerException = ExpectedException.SecurityTokenException( + LogHelper.FormatInvariant( + Tokens.LogMessages.IDX10002, // "IDX10002: Unknown exception type returned. Type: '{0}'. Message: '{1}'."; + typeof(CustomSecurityTokenException), + nameof(CustomAlgorithmValidationDelegates.AlgorithmValidatorCustomExceptionTypeDelegate))), + ValidationError = new AlgorithmValidationError( + new MessageDetail( + nameof(CustomAlgorithmValidationDelegates.AlgorithmValidatorCustomExceptionTypeDelegate), null), + ValidationFailureType.AlgorithmValidationFailed, + typeof(CustomSecurityTokenException), + new StackFrame("CustomAlgorithmValidationDelegates.cs", 274), + "algorithm") + }); + + // SignatureValidationError : ValidationError, ExceptionType: SecurityTokenInvalidSignatureException, inner: CustomSecurityTokenInvalidAlgorithmException + theoryData.Add(new AlgorithmExtensibilityTheoryData( + "AlgorithmValidatorThrows", + utcNow, + CustomAlgorithmValidationDelegates.AlgorithmValidatorThrows, + extraStackFrames: 2) + { + ExpectedException = new ExpectedException( + typeof(SecurityTokenInvalidSignatureException), + "IDX10273:", + typeof(CustomSecurityTokenInvalidAlgorithmException)), + ExpectedInnerException = new ExpectedException( + typeof(CustomSecurityTokenInvalidAlgorithmException), + nameof(CustomAlgorithmValidationDelegates.AlgorithmValidatorThrows)), + ValidationError = new SignatureValidationError( + new MessageDetail( + string.Format(Tokens.LogMessages.IDX10273), null), + ValidationFailureType.AlgorithmValidatorThrew, + typeof(SecurityTokenInvalidSignatureException), + new StackFrame("JsonWebTokenHandler.ValidateSignature.cs", 250), + null, // no inner validation error + new CustomSecurityTokenInvalidAlgorithmException(nameof(CustomAlgorithmValidationDelegates.AlgorithmValidatorThrows), null) + ) + }); + #endregion + + return theoryData; + } + } + + public class AlgorithmExtensibilityTheoryData : ValidateTokenAsyncBaseTheoryData + { + internal AlgorithmExtensibilityTheoryData(string testId, DateTime utcNow, AlgorithmValidationDelegate algorithmValidator, int extraStackFrames) : base(testId) + { + // The token is never read by the custom delegtes, so we create a dummy token + JsonWebToken = JsonWebTokenHandler.ReadJsonWebToken(JsonWebTokenHandler.CreateToken(new SecurityTokenDescriptor() + { + SigningCredentials = Default.SymmetricSigningCredentials, + })); + + ValidationParameters = new ValidationParameters + { + // We leave the default signature validator to call the custom algorithm validator + AlgorithmValidator = algorithmValidator, + AudienceValidator = SkipValidationDelegates.SkipAudienceValidation, + IssuerValidatorAsync = SkipValidationDelegates.SkipIssuerValidation, + IssuerSigningKeyValidator = SkipValidationDelegates.SkipIssuerSigningKeyValidation, + LifetimeValidator = SkipValidationDelegates.SkipLifetimeValidation, + TokenReplayValidator = SkipValidationDelegates.SkipTokenReplayValidation, + TokenTypeValidator = SkipValidationDelegates.SkipTokenTypeValidation, + }; + + ExtraStackFrames = extraStackFrames; + } + + public JsonWebToken JsonWebToken { get; } + + public JsonWebTokenHandler JsonWebTokenHandler { get; } = new JsonWebTokenHandler(); + + public bool IsValid { get; set; } + + internal ValidationError? ValidationError { get; set; } + + public ExpectedException? ExpectedInnerException { get; set; } + + internal int ExtraStackFrames { get; } + + public SecurityKey SigningKey { get; set; } = Default.SymmetricSigningKey; + } + } +} +#nullable restore diff --git a/test/Microsoft.IdentityModel.JsonWebTokens.Tests/JsonWebTokenHandler.Extensibility.Signature.cs b/test/Microsoft.IdentityModel.JsonWebTokens.Tests/JsonWebTokenHandler.Extensibility.Signature.cs new file mode 100644 index 0000000000..ac497daa72 --- /dev/null +++ b/test/Microsoft.IdentityModel.JsonWebTokens.Tests/JsonWebTokenHandler.Extensibility.Signature.cs @@ -0,0 +1,266 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Diagnostics; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.IdentityModel.JsonWebTokens.Tests; +using Microsoft.IdentityModel.Logging; +using Microsoft.IdentityModel.TestUtils; +using Microsoft.IdentityModel.Tokens; +using Microsoft.IdentityModel.Tokens.Json.Tests; +using Xunit; + +#nullable enable +namespace Microsoft.IdentityModel.JsonWebTokens.Extensibility.Tests +{ + public partial class JsonWebTokenHandlerValidateTokenAsyncTests + { + [Theory, MemberData(nameof(Signature_ExtensibilityTestCases), DisableDiscoveryEnumeration = true)] + public async Task ValidateTokenAsync_SignatureValidator_Extensibility(SignatureExtensibilityTheoryData theoryData) + { + var context = TestUtilities.WriteHeader($"{this}.{nameof(ValidateTokenAsync_SignatureValidator_Extensibility)}", theoryData); + context.IgnoreType = false; + for (int i = 0; i < theoryData.ExtraStackFrames; i++) + theoryData.SignatureValidationError!.AddStackFrame(new StackFrame(false)); + + try + { + ValidationResult validationResult = await theoryData.JsonWebTokenHandler.ValidateTokenAsync( + theoryData.JsonWebToken!, + theoryData.ValidationParameters!, + theoryData.CallContext, + CancellationToken.None); + + if (validationResult.IsValid) + { + // We expect the validation to fail, but it passed + context.AddDiff("ValidationResult is Valid."); + } + else + { + ValidationError validationError = validationResult.UnwrapError(); + IdentityComparer.AreValidationErrorsEqual(validationError, theoryData.SignatureValidationError, context); + theoryData.ExpectedException.ProcessException(validationError.GetException(), context); + } + } + catch (Exception ex) + { + theoryData.ExpectedException.ProcessException(ex, context); + } + + TestUtilities.AssertFailIfErrors(context); + } + + public static TheoryData Signature_ExtensibilityTestCases + { + get + { + var theoryData = new TheoryData(); + CallContext callContext = new CallContext(); + var utcNow = DateTime.UtcNow; + var utcPlusOneHour = utcNow + TimeSpan.FromHours(1); + + #region return CustomSignatureValidationError + // Test cases where delegate is overridden and return a CustomSignatureValidationError + // CustomSignatureValidationError : SignatureValidationError, ExceptionType: SecurityTokenInvalidSignatureException + theoryData.Add(new SignatureExtensibilityTheoryData( + "CustomSignatureValidatorDelegate", + utcNow, + CustomSignatureValidationDelegates.CustomSignatureValidatorDelegate, + extraStackFrames: 3) + { + ExpectedException = new ExpectedException( + typeof(SecurityTokenInvalidSignatureException), + nameof(CustomSignatureValidationDelegates.CustomSignatureValidatorDelegate)), + SignatureValidationError = new CustomSignatureValidationError( + new MessageDetail( + nameof(CustomSignatureValidationDelegates.CustomSignatureValidatorDelegate), null), + ValidationFailureType.SignatureValidationFailed, + typeof(SecurityTokenInvalidSignatureException), + new StackFrame("CustomSignatureValidationDelegates.cs", 160)) + }); + + // CustomSignatureValidationError : SignatureValidationError, ExceptionType: CustomSecurityTokenInvalidSignatureException : SecurityTokenInvalidSignatureException + theoryData.Add(new SignatureExtensibilityTheoryData( + "CustomSignatureValidatorCustomExceptionDelegate", + utcNow, + CustomSignatureValidationDelegates.CustomSignatureValidatorCustomExceptionDelegate, + extraStackFrames: 3) + { + ExpectedException = new ExpectedException( + typeof(CustomSecurityTokenInvalidSignatureException), + nameof(CustomSignatureValidationDelegates.CustomSignatureValidatorCustomExceptionDelegate)), + SignatureValidationError = new CustomSignatureValidationError( + new MessageDetail( + nameof(CustomSignatureValidationDelegates.CustomSignatureValidatorCustomExceptionDelegate), null), + ValidationFailureType.SignatureValidationFailed, + typeof(CustomSecurityTokenInvalidSignatureException), + new StackFrame("CustomSignatureValidationDelegates.cs", 175)), + }); + + // CustomSignatureValidationError : SignatureValidationError, ExceptionType: NotSupportedException : SystemException + theoryData.Add(new SignatureExtensibilityTheoryData( + "CustomSignatureValidatorUnknownExceptionDelegate", + utcNow, + CustomSignatureValidationDelegates.CustomSignatureValidatorUnknownExceptionDelegate, + extraStackFrames: 3) + { + // CustomSignatureValidationError does not handle the exception type 'NotSupportedException' + ExpectedException = ExpectedException.SecurityTokenException( + LogHelper.FormatInvariant( + Tokens.LogMessages.IDX10002, // "IDX10002: Unknown exception type returned. Type: '{0}'. Message: '{1}'."; + typeof(NotSupportedException), + nameof(CustomSignatureValidationDelegates.CustomSignatureValidatorUnknownExceptionDelegate))), + SignatureValidationError = new CustomSignatureValidationError( + new MessageDetail( + nameof(CustomSignatureValidationDelegates.CustomSignatureValidatorUnknownExceptionDelegate), null), + ValidationFailureType.SignatureValidationFailed, + typeof(NotSupportedException), + new StackFrame("CustomSignatureValidationDelegates.cs", 205)), + }); + + // CustomSignatureValidationError : SignatureValidationError, ExceptionType: NotSupportedException : SystemException, ValidationFailureType: CustomAudienceValidationFailureType + theoryData.Add(new SignatureExtensibilityTheoryData( + "CustomSignatureValidatorCustomExceptionCustomFailureTypeDelegate", + utcNow, + CustomSignatureValidationDelegates.CustomSignatureValidatorCustomExceptionCustomFailureTypeDelegate, + extraStackFrames: 3) + { + ExpectedException = new ExpectedException( + typeof(CustomSecurityTokenInvalidSignatureException), + nameof(CustomSignatureValidationDelegates.CustomSignatureValidatorCustomExceptionCustomFailureTypeDelegate)), + SignatureValidationError = new CustomSignatureValidationError( + new MessageDetail( + nameof(CustomSignatureValidationDelegates.CustomSignatureValidatorCustomExceptionCustomFailureTypeDelegate), null), + CustomSignatureValidationError.CustomSignatureValidationFailureType, + typeof(CustomSecurityTokenInvalidSignatureException), + new StackFrame("CustomSignatureValidationDelegates.cs", 190)), + }); + #endregion + + #region return SignatureValidationError + // Test cases where delegate is overridden and return an SignatureValidationError + // SignatureValidationError : ValidationError, ExceptionType: SecurityTokenInvalidSignatureException + theoryData.Add(new SignatureExtensibilityTheoryData( + "SignatureValidatorDelegate", + utcNow, + CustomSignatureValidationDelegates.SignatureValidatorDelegate, + extraStackFrames: 3) + { + ExpectedException = new ExpectedException( + typeof(SecurityTokenInvalidSignatureException), + nameof(CustomSignatureValidationDelegates.SignatureValidatorDelegate)), + SignatureValidationError = new SignatureValidationError( + new MessageDetail( + nameof(CustomSignatureValidationDelegates.SignatureValidatorDelegate), null), + ValidationFailureType.SignatureValidationFailed, + typeof(SecurityTokenInvalidSignatureException), + new StackFrame("CustomSignatureValidationDelegates.cs", 235)) + }); + + // SignatureValidationError : ValidationError, ExceptionType: CustomSecurityTokenInvalidSignatureException : SecurityTokenInvalidSignatureException + theoryData.Add(new SignatureExtensibilityTheoryData( + "SignatureValidatorCustomSignatureExceptionTypeDelegate", + utcNow, + CustomSignatureValidationDelegates.SignatureValidatorCustomSignatureExceptionTypeDelegate, + extraStackFrames: 3) + { + // SignatureValidationError does not handle the exception type 'CustomSecurityTokenInvalidSignatureException' + ExpectedException = ExpectedException.SecurityTokenException( + LogHelper.FormatInvariant( + Tokens.LogMessages.IDX10002, // "IDX10002: Unknown exception type returned. Type: '{0}'. Message: '{1}'."; + typeof(CustomSecurityTokenInvalidSignatureException), + nameof(CustomSignatureValidationDelegates.SignatureValidatorCustomSignatureExceptionTypeDelegate))), + SignatureValidationError = new SignatureValidationError( + new MessageDetail( + nameof(CustomSignatureValidationDelegates.SignatureValidatorCustomSignatureExceptionTypeDelegate), null), + ValidationFailureType.SignatureValidationFailed, + typeof(CustomSecurityTokenInvalidSignatureException), + new StackFrame("CustomSignatureValidationDelegates.cs", 259)) + }); + + // SignatureValidationError : ValidationError, ExceptionType: CustomSecurityTokenException : SystemException + theoryData.Add(new SignatureExtensibilityTheoryData( + "SignatureValidatorCustomExceptionTypeDelegate", + utcNow, + CustomSignatureValidationDelegates.SignatureValidatorCustomExceptionTypeDelegate, + extraStackFrames: 3) + { + // SignatureValidationError does not handle the exception type 'CustomSecurityTokenException' + ExpectedException = ExpectedException.SecurityTokenException( + LogHelper.FormatInvariant( + Tokens.LogMessages.IDX10002, // "IDX10002: Unknown exception type returned. Type: '{0}'. Message: '{1}'."; + typeof(CustomSecurityTokenException), + nameof(CustomSignatureValidationDelegates.SignatureValidatorCustomExceptionTypeDelegate))), + SignatureValidationError = new SignatureValidationError( + new MessageDetail( + nameof(CustomSignatureValidationDelegates.SignatureValidatorCustomExceptionTypeDelegate), null), + ValidationFailureType.SignatureValidationFailed, + typeof(CustomSecurityTokenException), + new StackFrame("CustomSignatureValidationDelegates.cs", 274)) + }); + + // SignatureValidationError : ValidationError, ExceptionType: SecurityTokenInvalidSignatureException, inner: CustomSecurityTokenInvalidSignatureException + theoryData.Add(new SignatureExtensibilityTheoryData( + "SignatureValidatorThrows", + utcNow, + CustomSignatureValidationDelegates.SignatureValidatorThrows, + extraStackFrames: 2) + { + ExpectedException = new ExpectedException( + typeof(SecurityTokenInvalidSignatureException), + string.Format(Tokens.LogMessages.IDX10272), + typeof(CustomSecurityTokenInvalidSignatureException)), + SignatureValidationError = new SignatureValidationError( + new MessageDetail( + string.Format(Tokens.LogMessages.IDX10272), null), + ValidationFailureType.SignatureValidatorThrew, + typeof(SecurityTokenInvalidSignatureException), + new StackFrame("JsonWebTokenHandler.ValidateSignature.cs", 250), + null, // no inner validation error + new SecurityTokenInvalidSignatureException(nameof(CustomSignatureValidationDelegates.SignatureValidatorThrows)) + ) + }); + #endregion + + return theoryData; + } + } + + public class SignatureExtensibilityTheoryData : ValidateTokenAsyncBaseTheoryData + { + internal SignatureExtensibilityTheoryData(string testId, DateTime utcNow, SignatureValidationDelegate signatureValidator, int extraStackFrames) : base(testId) + { + // The token is never read by the custom delegtes, so we create a dummy token + JsonWebToken = JsonUtilities.CreateUnsignedJsonWebToken("iss", "issuer"); + + ValidationParameters = new ValidationParameters + { + AlgorithmValidator = SkipValidationDelegates.SkipAlgorithmValidation, + AudienceValidator = SkipValidationDelegates.SkipAudienceValidation, + IssuerValidatorAsync = SkipValidationDelegates.SkipIssuerValidation, + IssuerSigningKeyValidator = SkipValidationDelegates.SkipIssuerSigningKeyValidation, + LifetimeValidator = SkipValidationDelegates.SkipLifetimeValidation, + SignatureValidator = signatureValidator, + TokenReplayValidator = SkipValidationDelegates.SkipTokenReplayValidation, + TokenTypeValidator = SkipValidationDelegates.SkipTokenTypeValidation + }; + + ExtraStackFrames = extraStackFrames; + } + + public JsonWebToken JsonWebToken { get; } + + public JsonWebTokenHandler JsonWebTokenHandler { get; } = new JsonWebTokenHandler(); + + public bool IsValid { get; set; } + + internal SignatureValidationError? SignatureValidationError { get; set; } + + internal int ExtraStackFrames { get; } + } + } +} +#nullable restore diff --git a/test/Microsoft.IdentityModel.JsonWebTokens.Tests/JsonWebTokenHandler.ValidateTokenAsyncTests.Algorithm.cs b/test/Microsoft.IdentityModel.JsonWebTokens.Tests/JsonWebTokenHandler.ValidateTokenAsyncTests.Algorithm.cs index 36fd83ed4a..b26524130c 100644 --- a/test/Microsoft.IdentityModel.JsonWebTokens.Tests/JsonWebTokenHandler.ValidateTokenAsyncTests.Algorithm.cs +++ b/test/Microsoft.IdentityModel.JsonWebTokens.Tests/JsonWebTokenHandler.ValidateTokenAsyncTests.Algorithm.cs @@ -73,9 +73,9 @@ public static TheoryData ValidateTokenAsy validAlgorithms: [SecurityAlgorithms.Sha256]), ExpectedIsValid = false, ExpectedException = ExpectedException.SecurityTokenInvalidSignatureException("IDX10511:"), - ExpectedExceptionValidationParameters = ExpectedException.SecurityTokenInvalidAlgorithmException( + ExpectedExceptionValidationParameters = ExpectedException.SecurityTokenInvalidSignatureException( "IDX10518:", - propertiesExpected: new() { { "InvalidAlgorithm", SecurityAlgorithms.HmacSha256Signature } }), + typeof(SecurityTokenInvalidAlgorithmException)), }); return theoryData; diff --git a/test/Microsoft.IdentityModel.TestUtils/SkipValidationDelegates.cs b/test/Microsoft.IdentityModel.TestUtils/SkipValidationDelegates.cs index 5203f6ea64..2f2bb635e7 100644 --- a/test/Microsoft.IdentityModel.TestUtils/SkipValidationDelegates.cs +++ b/test/Microsoft.IdentityModel.TestUtils/SkipValidationDelegates.cs @@ -70,7 +70,7 @@ public static class SkipValidationDelegates SecurityToken securityToken, ValidationParameters validationParameters, BaseConfiguration? configuration, - CallContext? callContext) + CallContext callContext) { // This key is not used during the validation process. It is only used to satisfy the delegate signature. // Follow up PR will change this to remove the SecurityKey return value. diff --git a/test/Microsoft.IdentityModel.TestUtils/TokenValidationExtensibility/CustomAlgorithmValidationDelegates.cs b/test/Microsoft.IdentityModel.TestUtils/TokenValidationExtensibility/CustomAlgorithmValidationDelegates.cs new file mode 100644 index 0000000000..42fa04d6de --- /dev/null +++ b/test/Microsoft.IdentityModel.TestUtils/TokenValidationExtensibility/CustomAlgorithmValidationDelegates.cs @@ -0,0 +1,144 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using Microsoft.IdentityModel.Tokens; + +#nullable enable +namespace Microsoft.IdentityModel.TestUtils +{ + internal class CustomAlgorithmValidationDelegates + { + internal static ValidationResult CustomAlgorithmValidatorDelegate( + string algorithm, + SecurityKey securityKey, + SecurityToken securityToken, + ValidationParameters validationParameters, + CallContext callContext) + { + // Returns a CustomAlgorithmValidationError : AlgorithmValidationError + return new CustomAlgorithmValidationError( + new MessageDetail(nameof(CustomAlgorithmValidatorDelegate), null), + ValidationFailureType.AlgorithmValidationFailed, + typeof(SecurityTokenInvalidAlgorithmException), + ValidationError.GetCurrentStackFrame(), + algorithm); + } + + internal static ValidationResult CustomAlgorithmValidatorCustomExceptionDelegate( + string algorithm, + SecurityKey securityKey, + SecurityToken securityToken, + ValidationParameters validationParameters, + CallContext callContext) + { + return new CustomAlgorithmValidationError( + new MessageDetail(nameof(CustomAlgorithmValidatorCustomExceptionDelegate), null), + ValidationFailureType.AlgorithmValidationFailed, + typeof(CustomSecurityTokenInvalidAlgorithmException), + ValidationError.GetCurrentStackFrame(), + algorithm); + } + + internal static ValidationResult CustomAlgorithmValidatorCustomExceptionCustomFailureTypeDelegate( + string algorithm, + SecurityKey securityKey, + SecurityToken securityToken, + ValidationParameters validationParameters, + CallContext callContext) + { + return new CustomAlgorithmValidationError( + new MessageDetail(nameof(CustomAlgorithmValidatorCustomExceptionCustomFailureTypeDelegate), null), + CustomAlgorithmValidationError.CustomAlgorithmValidationFailureType, + typeof(CustomSecurityTokenInvalidAlgorithmException), + ValidationError.GetCurrentStackFrame(), + algorithm); + } + + internal static ValidationResult CustomAlgorithmValidatorUnknownExceptionDelegate( + string algorithm, + SecurityKey securityKey, + SecurityToken securityToken, + ValidationParameters validationParameters, + CallContext callContext) + { + return new CustomAlgorithmValidationError( + new MessageDetail(nameof(CustomAlgorithmValidatorUnknownExceptionDelegate), null), + ValidationFailureType.AlgorithmValidationFailed, + typeof(NotSupportedException), + ValidationError.GetCurrentStackFrame(), + algorithm); + } + + internal static ValidationResult CustomAlgorithmValidatorWithoutGetExceptionOverrideDelegate( + string algorithm, + SecurityKey securityKey, + SecurityToken securityToken, + ValidationParameters validationParameters, + CallContext callContext) + { + return new CustomAlgorithmWithoutGetExceptionValidationOverrideError( + new MessageDetail(nameof(CustomAlgorithmValidatorWithoutGetExceptionOverrideDelegate), null), + ValidationFailureType.AlgorithmValidationFailed, + typeof(CustomSecurityTokenInvalidAlgorithmException), + ValidationError.GetCurrentStackFrame(), + algorithm); + } + + internal static ValidationResult AlgorithmValidatorDelegate( + string algorithm, + SecurityKey securityKey, + SecurityToken securityToken, + ValidationParameters validationParameters, + CallContext callContext) + { + return new AlgorithmValidationError( + new MessageDetail(nameof(AlgorithmValidatorDelegate), null), + ValidationFailureType.AlgorithmValidationFailed, + typeof(SecurityTokenInvalidAlgorithmException), + ValidationError.GetCurrentStackFrame(), + algorithm); + } + + internal static ValidationResult AlgorithmValidatorThrows( + string algorithm, + SecurityKey securityKey, + SecurityToken securityToken, + ValidationParameters validationParameters, + CallContext callContext) + { + throw new CustomSecurityTokenInvalidAlgorithmException(nameof(AlgorithmValidatorThrows), null); + } + + internal static ValidationResult AlgorithmValidatorCustomAlgorithmExceptionTypeDelegate( + string algorithm, + SecurityKey securityKey, + SecurityToken securityToken, + ValidationParameters validationParameters, + CallContext callContext) + { + return new AlgorithmValidationError( + new MessageDetail(nameof(AlgorithmValidatorCustomAlgorithmExceptionTypeDelegate), null), + ValidationFailureType.AlgorithmValidationFailed, + typeof(CustomSecurityTokenInvalidAlgorithmException), + ValidationError.GetCurrentStackFrame(), + algorithm); + } + + internal static ValidationResult AlgorithmValidatorCustomExceptionTypeDelegate( + string algorithm, + SecurityKey securityKey, + SecurityToken securityToken, + ValidationParameters validationParameters, + CallContext callContext) + { + return new AlgorithmValidationError( + new MessageDetail(nameof(AlgorithmValidatorCustomExceptionTypeDelegate), null), + ValidationFailureType.AlgorithmValidationFailed, + typeof(CustomSecurityTokenException), + ValidationError.GetCurrentStackFrame(), + algorithm); + } + } +} +#nullable restore diff --git a/test/Microsoft.IdentityModel.TestUtils/TokenValidationExtensibility/CustomSignatureValidationDelegates.cs b/test/Microsoft.IdentityModel.TestUtils/TokenValidationExtensibility/CustomSignatureValidationDelegates.cs new file mode 100644 index 0000000000..f80e2e46c8 --- /dev/null +++ b/test/Microsoft.IdentityModel.TestUtils/TokenValidationExtensibility/CustomSignatureValidationDelegates.cs @@ -0,0 +1,127 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using Microsoft.IdentityModel.Tokens; + +#nullable enable +namespace Microsoft.IdentityModel.TestUtils +{ + internal class CustomSignatureValidationDelegates + { + internal static ValidationResult CustomSignatureValidatorDelegate( + SecurityToken? securityToken, + ValidationParameters validationParameters, + BaseConfiguration? configuration, + CallContext callContext) + { + // Returns a CustomSignatureValidationError : SignatureValidationError + return new CustomSignatureValidationError( + new MessageDetail(nameof(CustomSignatureValidatorDelegate), null), + ValidationFailureType.SignatureValidationFailed, + typeof(SecurityTokenInvalidSignatureException), + ValidationError.GetCurrentStackFrame()); + } + + internal static ValidationResult CustomSignatureValidatorCustomExceptionDelegate( + SecurityToken? securityToken, + ValidationParameters validationParameters, + BaseConfiguration? configuration, + CallContext callContext) + { + return new CustomSignatureValidationError( + new MessageDetail(nameof(CustomSignatureValidatorCustomExceptionDelegate), null), + ValidationFailureType.SignatureValidationFailed, + typeof(CustomSecurityTokenInvalidSignatureException), + ValidationError.GetCurrentStackFrame()); + } + + internal static ValidationResult CustomSignatureValidatorCustomExceptionCustomFailureTypeDelegate( + SecurityToken? securityToken, + ValidationParameters validationParameters, + BaseConfiguration? configuration, + CallContext callContext) + { + return new CustomSignatureValidationError( + new MessageDetail(nameof(CustomSignatureValidatorCustomExceptionCustomFailureTypeDelegate), null), + CustomSignatureValidationError.CustomSignatureValidationFailureType, + typeof(CustomSecurityTokenInvalidSignatureException), + ValidationError.GetCurrentStackFrame()); + } + + internal static ValidationResult CustomSignatureValidatorUnknownExceptionDelegate( + SecurityToken? securityToken, + ValidationParameters validationParameters, + BaseConfiguration? configuration, + CallContext callContext) + { + return new CustomSignatureValidationError( + new MessageDetail(nameof(CustomSignatureValidatorUnknownExceptionDelegate), null), + ValidationFailureType.SignatureValidationFailed, + typeof(NotSupportedException), + ValidationError.GetCurrentStackFrame()); + } + + internal static ValidationResult CustomSignatureValidatorWithoutGetExceptionOverrideDelegate( + SecurityToken? securityToken, + ValidationParameters validationParameters, + BaseConfiguration? configuration, + CallContext callContext) + { + return new CustomSignatureWithoutGetExceptionValidationOverrideError( + new MessageDetail(nameof(CustomSignatureValidatorWithoutGetExceptionOverrideDelegate), null), + ValidationFailureType.SignatureValidationFailed, + typeof(CustomSecurityTokenInvalidSignatureException), + ValidationError.GetCurrentStackFrame()); + } + + internal static ValidationResult SignatureValidatorDelegate( + SecurityToken? securityToken, + ValidationParameters validationParameters, + BaseConfiguration? configuration, + CallContext callContext) + { + return new SignatureValidationError( + new MessageDetail(nameof(SignatureValidatorDelegate), null), + ValidationFailureType.SignatureValidationFailed, + typeof(SecurityTokenInvalidSignatureException), + ValidationError.GetCurrentStackFrame()); + } + + internal static ValidationResult SignatureValidatorThrows( + SecurityToken? securityToken, + ValidationParameters validationParameters, + BaseConfiguration? configuration, + CallContext callContext) + { + throw new CustomSecurityTokenInvalidSignatureException(nameof(SignatureValidatorThrows), null); + } + + internal static ValidationResult SignatureValidatorCustomSignatureExceptionTypeDelegate( + SecurityToken? securityToken, + ValidationParameters validationParameters, + BaseConfiguration? configuration, + CallContext callContext) + { + return new SignatureValidationError( + new MessageDetail(nameof(SignatureValidatorCustomSignatureExceptionTypeDelegate), null), + ValidationFailureType.SignatureValidationFailed, + typeof(CustomSecurityTokenInvalidSignatureException), + ValidationError.GetCurrentStackFrame()); + } + + internal static ValidationResult SignatureValidatorCustomExceptionTypeDelegate( + SecurityToken? securityToken, + ValidationParameters validationParameters, + BaseConfiguration? configuration, + CallContext callContext) + { + return new SignatureValidationError( + new MessageDetail(nameof(SignatureValidatorCustomExceptionTypeDelegate), null), + ValidationFailureType.SignatureValidationFailed, + typeof(CustomSecurityTokenException), + ValidationError.GetCurrentStackFrame()); + } + } +} +#nullable restore diff --git a/test/Microsoft.IdentityModel.TestUtils/TokenValidationExtensibility/CustomValidationErrors.cs b/test/Microsoft.IdentityModel.TestUtils/TokenValidationExtensibility/CustomValidationErrors.cs index 8f20be2168..9623161345 100644 --- a/test/Microsoft.IdentityModel.TestUtils/TokenValidationExtensibility/CustomValidationErrors.cs +++ b/test/Microsoft.IdentityModel.TestUtils/TokenValidationExtensibility/CustomValidationErrors.cs @@ -201,6 +201,98 @@ public CustomTokenTypeWithoutGetExceptionValidationOverrideError( } #endregion // TokenTypeValidationErrors + #region SignatureValidationErrors + internal class CustomSignatureValidationError : SignatureValidationError + { + /// + /// A custom validation failure type. + /// + public static readonly ValidationFailureType CustomSignatureValidationFailureType = new SignatureValidatorFailure("CustomSignatureValidationFailureType"); + private class SignatureValidatorFailure : ValidationFailureType { internal SignatureValidatorFailure(string name) : base(name) { } } + + public CustomSignatureValidationError( + MessageDetail messageDetail, + ValidationFailureType validationFailureType, + Type exceptionType, + StackFrame stackFrame, + ValidationError? innerValidationError = null, + Exception? innerException = null) : + base(messageDetail, validationFailureType, exceptionType, stackFrame, innerValidationError, innerException) + { + } + internal override Exception GetException() + { + if (ExceptionType == typeof(CustomSecurityTokenInvalidSignatureException)) + { + var exception = new CustomSecurityTokenInvalidSignatureException(MessageDetail.Message, InnerException); + exception.SetValidationError(this); + return exception; + } + return base.GetException(); + } + } + + internal class CustomSignatureWithoutGetExceptionValidationOverrideError : SignatureValidationError + { + public CustomSignatureWithoutGetExceptionValidationOverrideError( + MessageDetail messageDetail, + ValidationFailureType validationFailureType, + Type exceptionType, + StackFrame stackFrame, + ValidationError? innerValidationError = null, + Exception? innerException = null) : + base(messageDetail, validationFailureType, exceptionType, stackFrame, innerValidationError, innerException) + { + } + } + #endregion // SignatureValidationErrors + + #region AlgorithmValidationErrors + internal class CustomAlgorithmValidationError : AlgorithmValidationError + { + /// + /// A custom validation failure type. + /// + public static readonly ValidationFailureType CustomAlgorithmValidationFailureType = new AlgorithmValidatorFailure("CustomAlgorithmValidationFailureType"); + private class AlgorithmValidatorFailure : ValidationFailureType { internal AlgorithmValidatorFailure(string name) : base(name) { } } + + public CustomAlgorithmValidationError( + MessageDetail messageDetail, + ValidationFailureType validationFailureType, + Type exceptionType, + StackFrame stackFrame, + string? algorithm, + Exception? innerException = null) + : base(messageDetail, validationFailureType, exceptionType, stackFrame, algorithm, innerException) + { + } + internal override Exception GetException() + { + if (ExceptionType == typeof(CustomSecurityTokenInvalidAlgorithmException)) + { + var exception = new CustomSecurityTokenInvalidAlgorithmException(MessageDetail.Message, InnerException) { InvalidAlgorithm = InvalidAlgorithm }; + exception.SetValidationError(this); + return exception; + } + return base.GetException(); + } + } + + internal class CustomAlgorithmWithoutGetExceptionValidationOverrideError : AlgorithmValidationError + { + public CustomAlgorithmWithoutGetExceptionValidationOverrideError( + MessageDetail messageDetail, + ValidationFailureType validationFailureType, + Type exceptionType, + StackFrame stackFrame, + string? invalidAlgorithm, + Exception? innerException = null) : + base(messageDetail, validationFailureType, exceptionType, stackFrame, invalidAlgorithm, innerException) + { + } + } + #endregion // AlgorithmValidationErrors + // Other custom validation errors to be added here for signature validation, issuer signing key, etc. } #nullable restore diff --git a/test/Microsoft.IdentityModel.Tokens.Saml.Tests/Saml2SecurityTokenHandler.Extensibility.Algorithm.cs b/test/Microsoft.IdentityModel.Tokens.Saml.Tests/Saml2SecurityTokenHandler.Extensibility.Algorithm.cs new file mode 100644 index 0000000000..b2e3171da4 --- /dev/null +++ b/test/Microsoft.IdentityModel.Tokens.Saml.Tests/Saml2SecurityTokenHandler.Extensibility.Algorithm.cs @@ -0,0 +1,337 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Diagnostics; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.IdentityModel.Logging; +using Microsoft.IdentityModel.TestUtils; + +using Xunit; + +#nullable enable +namespace Microsoft.IdentityModel.Tokens.Saml2.Extensibility.Tests +{ + public partial class Saml2SecurityTokenHandlerValidateTokenAsyncTests + { + [Theory, MemberData(nameof(Algorithm_ExtensibilityTestCases), DisableDiscoveryEnumeration = true)] + public async Task ValidateTokenAsync_AlgorithmValidator_Extensibility(AlgorithmExtensibilityTheoryData theoryData) + { + var context = TestUtilities.WriteHeader($"{this}.{nameof(ValidateTokenAsync_AlgorithmValidator_Extensibility)}", theoryData); + context.IgnoreType = false; + for (int i = 0; i < theoryData.ExtraStackFrames; i++) + theoryData.ValidationError!.AddStackFrame(new StackFrame(false)); + + Saml2SecurityToken saml2Token = (Saml2SecurityToken)theoryData.Saml2SecurityTokenHandler.CreateToken(new SecurityTokenDescriptor() + { + Issuer = Default.Issuer, + Subject = Default.SamlClaimsIdentity, + SigningCredentials = KeyingMaterial.DefaultX509SigningCreds_2048_RsaSha2_Sha2, + }); + theoryData.Saml2Token = theoryData.Saml2SecurityTokenHandler.ReadSaml2Token(saml2Token.Assertion.CanonicalString); + + theoryData.ValidationParameters!.IssuerSigningKeys.Add(theoryData.SigningKey); + + try + { + ValidationResult validationResult = await theoryData.Saml2SecurityTokenHandler.ValidateTokenAsync( + theoryData.Saml2Token!, + theoryData.ValidationParameters!, + theoryData.CallContext, + CancellationToken.None); + + if (validationResult.IsValid) + { + // We expect the validation to fail, but it passed + context.AddDiff("ValidationResult is Valid."); + } + else + { + ValidationError validationError = validationResult.UnwrapError(); + + if (validationError is SignatureValidationError signatureValidationError && + signatureValidationError.InnerValidationError is not null) + { + IdentityComparer.AreValidationErrorsEqual( + signatureValidationError.InnerValidationError, + theoryData.ValidationError, + context); + } + else + { + IdentityComparer.AreValidationErrorsEqual( + validationError, + theoryData.ValidationError, + context); + } + + var exception = validationError.GetException(); + theoryData.ExpectedException.ProcessException(exception, context); + // Process the inner exception since invalid algorithm exceptions are wrapped inside + // invalid signature exceptions + if (theoryData.ExpectedInnerException is not null) + theoryData.ExpectedInnerException.ProcessException(exception.InnerException, context); + } + } + catch (Exception ex) + { + // We expect the validation to fail, but it threw an exception + context.AddDiff($"ValidateTokenAsync threw exception: {ex}"); + } + + TestUtilities.AssertFailIfErrors(context); + } + + public static TheoryData Algorithm_ExtensibilityTestCases + { + get + { + var theoryData = new TheoryData(); + CallContext callContext = new CallContext(); + var utcNow = DateTime.UtcNow; + var utcPlusOneHour = utcNow + TimeSpan.FromHours(1); + + #region return CustomAlgorithmValidationError + // Test cases where delegate is overridden and return a CustomAlgorithmValidationError + // CustomAlgorithmValidationError : AlgorithmValidationError, ExceptionType: SecurityTokenInvalidAlgorithmException + theoryData.Add(new AlgorithmExtensibilityTheoryData( + "CustomAlgorithmValidatorDelegate", + utcNow, + CustomAlgorithmValidationDelegates.CustomAlgorithmValidatorDelegate, + extraStackFrames: 1) + { + ExpectedException = new ExpectedException( + typeof(SecurityTokenInvalidSignatureException), + "IDX10518:", + typeof(SecurityTokenInvalidAlgorithmException)), + ExpectedInnerException = new ExpectedException( + typeof(SecurityTokenInvalidAlgorithmException), + nameof(CustomAlgorithmValidationDelegates.CustomAlgorithmValidatorDelegate)), + ValidationError = new CustomAlgorithmValidationError( + new MessageDetail( + nameof(CustomAlgorithmValidationDelegates.CustomAlgorithmValidatorDelegate), null), + ValidationFailureType.AlgorithmValidationFailed, + typeof(SecurityTokenInvalidAlgorithmException), + new StackFrame("CustomAlgorithmValidationDelegates.cs", 160), + "algorithm") + }); + + // CustomAlgorithmValidationError : AlgorithmValidationError, ExceptionType: CustomSecurityTokenInvalidAlgorithmException : SecurityTokenInvalidAlgorithmException + theoryData.Add(new AlgorithmExtensibilityTheoryData( + "CustomAlgorithmValidatorCustomExceptionDelegate", + utcNow, + CustomAlgorithmValidationDelegates.CustomAlgorithmValidatorCustomExceptionDelegate, + extraStackFrames: 1) + { + ExpectedException = new ExpectedException( + typeof(SecurityTokenInvalidSignatureException), + "IDX10518:", + typeof(CustomSecurityTokenInvalidAlgorithmException)), + ExpectedInnerException = new ExpectedException( + typeof(CustomSecurityTokenInvalidAlgorithmException), + nameof(CustomAlgorithmValidationDelegates.CustomAlgorithmValidatorCustomExceptionDelegate)), + ValidationError = new CustomAlgorithmValidationError( + new MessageDetail( + nameof(CustomAlgorithmValidationDelegates.CustomAlgorithmValidatorCustomExceptionDelegate), null), + ValidationFailureType.AlgorithmValidationFailed, + typeof(CustomSecurityTokenInvalidAlgorithmException), + new StackFrame("CustomAlgorithmValidationDelegates.cs", 175), + "algorithm"), + }); + + // CustomAlgorithmValidationError : AlgorithmValidationError, ExceptionType: NotSupportedException : SystemException + theoryData.Add(new AlgorithmExtensibilityTheoryData( + "CustomAlgorithmValidatorUnknownExceptionDelegate", + utcNow, + CustomAlgorithmValidationDelegates.CustomAlgorithmValidatorUnknownExceptionDelegate, + extraStackFrames: 1) + { + // CustomAlgorithmValidationError does not handle the exception type 'NotSupportedException' + ExpectedException = new ExpectedException( + typeof(SecurityTokenInvalidSignatureException), + "IDX10518:", + typeof(SecurityTokenException)), + ExpectedInnerException = ExpectedException.SecurityTokenException( + LogHelper.FormatInvariant( + Tokens.LogMessages.IDX10002, // "IDX10002: Unknown exception type returned. Type: '{0}'. Message: '{1}'."; + typeof(NotSupportedException), + nameof(CustomAlgorithmValidationDelegates.CustomAlgorithmValidatorUnknownExceptionDelegate))), + ValidationError = new CustomAlgorithmValidationError( + new MessageDetail( + nameof(CustomAlgorithmValidationDelegates.CustomAlgorithmValidatorUnknownExceptionDelegate), null), + ValidationFailureType.AlgorithmValidationFailed, + typeof(NotSupportedException), + new StackFrame("CustomAlgorithmValidationDelegates.cs", 205), + "algorithm"), + }); + + // CustomAlgorithmValidationError : AlgorithmValidationError, ExceptionType: NotSupportedException : SystemException, ValidationFailureType: CustomAudienceValidationFailureType + theoryData.Add(new AlgorithmExtensibilityTheoryData( + "CustomAlgorithmValidatorCustomExceptionCustomFailureTypeDelegate", + utcNow, + CustomAlgorithmValidationDelegates.CustomAlgorithmValidatorCustomExceptionCustomFailureTypeDelegate, + extraStackFrames: 1) + { + ExpectedException = new ExpectedException( + typeof(SecurityTokenInvalidSignatureException), + "IDX10518:", + typeof(CustomSecurityTokenInvalidAlgorithmException)), + ExpectedInnerException = new ExpectedException( + typeof(CustomSecurityTokenInvalidAlgorithmException), + nameof(CustomAlgorithmValidationDelegates.CustomAlgorithmValidatorCustomExceptionCustomFailureTypeDelegate)), + ValidationError = new CustomAlgorithmValidationError( + new MessageDetail( + nameof(CustomAlgorithmValidationDelegates.CustomAlgorithmValidatorCustomExceptionCustomFailureTypeDelegate), null), + CustomAlgorithmValidationError.CustomAlgorithmValidationFailureType, + typeof(CustomSecurityTokenInvalidAlgorithmException), + new StackFrame("CustomAlgorithmValidationDelegates.cs", 190), + "algorithm"), + }); + #endregion + + #region return AlgorithmValidationError + // Test cases where delegate is overridden and return an AlgorithmValidationError + // AlgorithmValidationError : ValidationError, ExceptionType: SecurityTokenInvalidAlgorithmException + theoryData.Add(new AlgorithmExtensibilityTheoryData( + "AlgorithmValidatorDelegate", + utcNow, + CustomAlgorithmValidationDelegates.AlgorithmValidatorDelegate, + extraStackFrames: 1) + { + ExpectedException = new ExpectedException( + typeof(SecurityTokenInvalidSignatureException), + "IDX10518:", + typeof(SecurityTokenInvalidAlgorithmException)), + ExpectedInnerException = new ExpectedException( + typeof(SecurityTokenInvalidAlgorithmException), + nameof(CustomAlgorithmValidationDelegates.AlgorithmValidatorDelegate)), + ValidationError = new AlgorithmValidationError( + new MessageDetail( + nameof(CustomAlgorithmValidationDelegates.AlgorithmValidatorDelegate), null), + ValidationFailureType.AlgorithmValidationFailed, + typeof(SecurityTokenInvalidAlgorithmException), + new StackFrame("CustomAlgorithmValidationDelegates.cs", 235), + "algorithm") + }); + + // AlgorithmValidationError : ValidationError, ExceptionType: CustomSecurityTokenInvalidAlgorithmException : SecurityTokenInvalidAlgorithmException + theoryData.Add(new AlgorithmExtensibilityTheoryData( + "AlgorithmValidatorCustomAlgorithmExceptionTypeDelegate", + utcNow, + CustomAlgorithmValidationDelegates.AlgorithmValidatorCustomAlgorithmExceptionTypeDelegate, + extraStackFrames: 1) + { + // AlgorithmValidationError does not handle the exception type 'CustomSecurityTokenInvalidAlgorithmException' + ExpectedException = new ExpectedException( + typeof(SecurityTokenInvalidSignatureException), + "IDX10518:", + typeof(SecurityTokenException)), + ExpectedInnerException = ExpectedException.SecurityTokenException( + LogHelper.FormatInvariant( + Tokens.LogMessages.IDX10002, // "IDX10002: Unknown exception type returned. Type: '{0}'. Message: '{1}'."; + typeof(CustomSecurityTokenInvalidAlgorithmException), + nameof(CustomAlgorithmValidationDelegates.AlgorithmValidatorCustomAlgorithmExceptionTypeDelegate))), + ValidationError = new AlgorithmValidationError( + new MessageDetail( + nameof(CustomAlgorithmValidationDelegates.AlgorithmValidatorCustomAlgorithmExceptionTypeDelegate), null), + ValidationFailureType.AlgorithmValidationFailed, + typeof(CustomSecurityTokenInvalidAlgorithmException), + new StackFrame("CustomAlgorithmValidationDelegates.cs", 259), + "algorithm") + }); + + // AlgorithmValidationError : ValidationError, ExceptionType: CustomSecurityTokenException : SystemException + theoryData.Add(new AlgorithmExtensibilityTheoryData( + "AlgorithmValidatorCustomExceptionTypeDelegate", + utcNow, + CustomAlgorithmValidationDelegates.AlgorithmValidatorCustomExceptionTypeDelegate, + extraStackFrames: 1) + { + // AlgorithmValidationError does not handle the exception type 'CustomSecurityTokenException' + ExpectedException = new ExpectedException( + typeof(SecurityTokenInvalidSignatureException), + "IDX10518:", + typeof(SecurityTokenException)), + ExpectedInnerException = ExpectedException.SecurityTokenException( + LogHelper.FormatInvariant( + Tokens.LogMessages.IDX10002, // "IDX10002: Unknown exception type returned. Type: '{0}'. Message: '{1}'."; + typeof(CustomSecurityTokenException), + nameof(CustomAlgorithmValidationDelegates.AlgorithmValidatorCustomExceptionTypeDelegate))), + ValidationError = new AlgorithmValidationError( + new MessageDetail( + nameof(CustomAlgorithmValidationDelegates.AlgorithmValidatorCustomExceptionTypeDelegate), null), + ValidationFailureType.AlgorithmValidationFailed, + typeof(CustomSecurityTokenException), + new StackFrame("CustomAlgorithmValidationDelegates.cs", 274), + "algorithm") + }); + + // SignatureValidationError : ValidationError, ExceptionType: SecurityTokenInvalidSignatureException, inner: CustomSecurityTokenInvalidAlgorithmException + theoryData.Add(new AlgorithmExtensibilityTheoryData( + "AlgorithmValidatorThrows", + utcNow, + CustomAlgorithmValidationDelegates.AlgorithmValidatorThrows, + extraStackFrames: 2) + { + ExpectedException = new ExpectedException( + typeof(SecurityTokenInvalidSignatureException), + "IDX10273:", + typeof(CustomSecurityTokenInvalidAlgorithmException)), + ExpectedInnerException = new ExpectedException( + typeof(CustomSecurityTokenInvalidAlgorithmException), + nameof(CustomAlgorithmValidationDelegates.AlgorithmValidatorThrows)), + ValidationError = new SignatureValidationError( + new MessageDetail( + string.Format(Tokens.LogMessages.IDX10273), null), + ValidationFailureType.AlgorithmValidatorThrew, + typeof(SecurityTokenInvalidSignatureException), + new StackFrame("Saml2SecurityTokenHandler.ValidateSignature.cs", 250), + null, // no inner validation error + new CustomSecurityTokenInvalidAlgorithmException(nameof(CustomAlgorithmValidationDelegates.AlgorithmValidatorThrows), null) + ) + }); + #endregion + + return theoryData; + } + } + + public class AlgorithmExtensibilityTheoryData : TheoryDataBase + { + internal AlgorithmExtensibilityTheoryData(string testId, DateTime utcNow, AlgorithmValidationDelegate algorithmValidator, int extraStackFrames) : base(testId) + { + ValidationParameters = new ValidationParameters + { + // We leave the default signature validator to call the custom algorithm validator + AlgorithmValidator = algorithmValidator, + AudienceValidator = SkipValidationDelegates.SkipAudienceValidation, + IssuerValidatorAsync = SkipValidationDelegates.SkipIssuerValidation, + IssuerSigningKeyValidator = SkipValidationDelegates.SkipIssuerSigningKeyValidation, + LifetimeValidator = SkipValidationDelegates.SkipLifetimeValidation, + TokenReplayValidator = SkipValidationDelegates.SkipTokenReplayValidation, + TokenTypeValidator = SkipValidationDelegates.SkipTokenTypeValidation, + }; + + ExtraStackFrames = extraStackFrames; + } + + public Saml2SecurityToken? Saml2Token { get; set; } + + public Saml2SecurityTokenHandler Saml2SecurityTokenHandler { get; } = new Saml2SecurityTokenHandler(); + + public bool IsValid { get; set; } + + internal ValidationError? ValidationError { get; set; } + + public ExpectedException? ExpectedInnerException { get; set; } + + internal int ExtraStackFrames { get; } + + internal ValidationParameters ValidationParameters { get; } + + public SecurityKey SigningKey { get; set; } = KeyingMaterial.DefaultX509SigningCreds_2048_RsaSha2_Sha2.Key; + } + } +} +#nullable restore diff --git a/test/Microsoft.IdentityModel.Tokens.Saml.Tests/Saml2SecurityTokenHandler.Extensibility.Signature.cs b/test/Microsoft.IdentityModel.Tokens.Saml.Tests/Saml2SecurityTokenHandler.Extensibility.Signature.cs new file mode 100644 index 0000000000..d1635c6635 --- /dev/null +++ b/test/Microsoft.IdentityModel.Tokens.Saml.Tests/Saml2SecurityTokenHandler.Extensibility.Signature.cs @@ -0,0 +1,270 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Diagnostics; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.IdentityModel.Logging; +using Microsoft.IdentityModel.TestUtils; + +using Xunit; + +#nullable enable +namespace Microsoft.IdentityModel.Tokens.Saml2.Extensibility.Tests +{ + public partial class Saml2SecurityTokenHandlerValidateTokenAsyncTests + { + [Theory, MemberData(nameof(Signature_ExtensibilityTestCases), DisableDiscoveryEnumeration = true)] + public async Task ValidateTokenAsync_SignatureValidator_Extensibility(SignatureExtensibilityTheoryData theoryData) + { + var context = TestUtilities.WriteHeader($"{this}.{nameof(ValidateTokenAsync_SignatureValidator_Extensibility)}", theoryData); + context.IgnoreType = false; + for (int i = 0; i < theoryData.ExtraStackFrames; i++) + theoryData.SignatureValidationError!.AddStackFrame(new StackFrame(false)); + + try + { + ValidationResult validationResult = await theoryData.Saml2SecurityTokenHandler.ValidateTokenAsync( + theoryData.Saml2Token!, + theoryData.ValidationParameters!, + theoryData.CallContext, + CancellationToken.None); + + if (validationResult.IsValid) + { + // We expect the validation to fail, but it passed + context.AddDiff("ValidationResult is Valid."); + } + else + { + ValidationError validationError = validationResult.UnwrapError(); + IdentityComparer.AreValidationErrorsEqual(validationError, theoryData.SignatureValidationError, context); + theoryData.ExpectedException.ProcessException(validationError.GetException(), context); + } + } + catch (Exception ex) + { + theoryData.ExpectedException.ProcessException(ex, context); + } + + TestUtilities.AssertFailIfErrors(context); + } + + public static TheoryData Signature_ExtensibilityTestCases + { + get + { + var theoryData = new TheoryData(); + CallContext callContext = new CallContext(); + var utcNow = DateTime.UtcNow; + var utcPlusOneHour = utcNow + TimeSpan.FromHours(1); + + #region return CustomSignatureValidationError + // Test cases where delegate is overridden and return a CustomSignatureValidationError + // CustomSignatureValidationError : SignatureValidationError, ExceptionType: SecurityTokenInvalidSignatureException + theoryData.Add(new SignatureExtensibilityTheoryData( + "CustomSignatureValidatorDelegate", + utcNow, + CustomSignatureValidationDelegates.CustomSignatureValidatorDelegate, + extraStackFrames: 2) + { + ExpectedException = new ExpectedException( + typeof(SecurityTokenInvalidSignatureException), + nameof(CustomSignatureValidationDelegates.CustomSignatureValidatorDelegate)), + SignatureValidationError = new CustomSignatureValidationError( + new MessageDetail( + nameof(CustomSignatureValidationDelegates.CustomSignatureValidatorDelegate), null), + ValidationFailureType.SignatureValidationFailed, + typeof(SecurityTokenInvalidSignatureException), + new StackFrame("CustomSignatureValidationDelegates.cs", 160)) + }); + + // CustomSignatureValidationError : SignatureValidationError, ExceptionType: CustomSecurityTokenInvalidSignatureException : SecurityTokenInvalidSignatureException + theoryData.Add(new SignatureExtensibilityTheoryData( + "CustomSignatureValidatorCustomExceptionDelegate", + utcNow, + CustomSignatureValidationDelegates.CustomSignatureValidatorCustomExceptionDelegate, + extraStackFrames: 2) + { + ExpectedException = new ExpectedException( + typeof(CustomSecurityTokenInvalidSignatureException), + nameof(CustomSignatureValidationDelegates.CustomSignatureValidatorCustomExceptionDelegate)), + SignatureValidationError = new CustomSignatureValidationError( + new MessageDetail( + nameof(CustomSignatureValidationDelegates.CustomSignatureValidatorCustomExceptionDelegate), null), + ValidationFailureType.SignatureValidationFailed, + typeof(CustomSecurityTokenInvalidSignatureException), + new StackFrame("CustomSignatureValidationDelegates.cs", 175)), + }); + + // CustomSignatureValidationError : SignatureValidationError, ExceptionType: NotSupportedException : SystemException + theoryData.Add(new SignatureExtensibilityTheoryData( + "CustomSignatureValidatorUnknownExceptionDelegate", + utcNow, + CustomSignatureValidationDelegates.CustomSignatureValidatorUnknownExceptionDelegate, + extraStackFrames: 2) + { + // CustomSignatureValidationError does not handle the exception type 'NotSupportedException' + ExpectedException = ExpectedException.SecurityTokenException( + LogHelper.FormatInvariant( + Tokens.LogMessages.IDX10002, // "IDX10002: Unknown exception type returned. Type: '{0}'. Message: '{1}'."; + typeof(NotSupportedException), + nameof(CustomSignatureValidationDelegates.CustomSignatureValidatorUnknownExceptionDelegate))), + SignatureValidationError = new CustomSignatureValidationError( + new MessageDetail( + nameof(CustomSignatureValidationDelegates.CustomSignatureValidatorUnknownExceptionDelegate), null), + ValidationFailureType.SignatureValidationFailed, + typeof(NotSupportedException), + new StackFrame("CustomSignatureValidationDelegates.cs", 205)), + }); + + // CustomSignatureValidationError : SignatureValidationError, ExceptionType: NotSupportedException : SystemException, ValidationFailureType: CustomAudienceValidationFailureType + theoryData.Add(new SignatureExtensibilityTheoryData( + "CustomSignatureValidatorCustomExceptionCustomFailureTypeDelegate", + utcNow, + CustomSignatureValidationDelegates.CustomSignatureValidatorCustomExceptionCustomFailureTypeDelegate, + extraStackFrames: 2) + { + ExpectedException = new ExpectedException( + typeof(CustomSecurityTokenInvalidSignatureException), + nameof(CustomSignatureValidationDelegates.CustomSignatureValidatorCustomExceptionCustomFailureTypeDelegate)), + SignatureValidationError = new CustomSignatureValidationError( + new MessageDetail( + nameof(CustomSignatureValidationDelegates.CustomSignatureValidatorCustomExceptionCustomFailureTypeDelegate), null), + CustomSignatureValidationError.CustomSignatureValidationFailureType, + typeof(CustomSecurityTokenInvalidSignatureException), + new StackFrame("CustomSignatureValidationDelegates.cs", 190)), + }); + #endregion + + #region return SignatureValidationError + // Test cases where delegate is overridden and return an SignatureValidationError + // SignatureValidationError : ValidationError, ExceptionType: SecurityTokenInvalidSignatureException + theoryData.Add(new SignatureExtensibilityTheoryData( + "SignatureValidatorDelegate", + utcNow, + CustomSignatureValidationDelegates.SignatureValidatorDelegate, + extraStackFrames: 2) + { + ExpectedException = new ExpectedException( + typeof(SecurityTokenInvalidSignatureException), + nameof(CustomSignatureValidationDelegates.SignatureValidatorDelegate)), + SignatureValidationError = new SignatureValidationError( + new MessageDetail( + nameof(CustomSignatureValidationDelegates.SignatureValidatorDelegate), null), + ValidationFailureType.SignatureValidationFailed, + typeof(SecurityTokenInvalidSignatureException), + new StackFrame("CustomSignatureValidationDelegates.cs", 235)) + }); + + // SignatureValidationError : ValidationError, ExceptionType: CustomSecurityTokenInvalidSignatureException : SecurityTokenInvalidSignatureException + theoryData.Add(new SignatureExtensibilityTheoryData( + "SignatureValidatorCustomSignatureExceptionTypeDelegate", + utcNow, + CustomSignatureValidationDelegates.SignatureValidatorCustomSignatureExceptionTypeDelegate, + extraStackFrames: 2) + { + // SignatureValidationError does not handle the exception type 'CustomSecurityTokenInvalidSignatureException' + ExpectedException = ExpectedException.SecurityTokenException( + LogHelper.FormatInvariant( + Tokens.LogMessages.IDX10002, // "IDX10002: Unknown exception type returned. Type: '{0}'. Message: '{1}'."; + typeof(CustomSecurityTokenInvalidSignatureException), + nameof(CustomSignatureValidationDelegates.SignatureValidatorCustomSignatureExceptionTypeDelegate))), + SignatureValidationError = new SignatureValidationError( + new MessageDetail( + nameof(CustomSignatureValidationDelegates.SignatureValidatorCustomSignatureExceptionTypeDelegate), null), + ValidationFailureType.SignatureValidationFailed, + typeof(CustomSecurityTokenInvalidSignatureException), + new StackFrame("CustomSignatureValidationDelegates.cs", 259)) + }); + + // SignatureValidationError : ValidationError, ExceptionType: CustomSecurityTokenException : SystemException + theoryData.Add(new SignatureExtensibilityTheoryData( + "SignatureValidatorCustomExceptionTypeDelegate", + utcNow, + CustomSignatureValidationDelegates.SignatureValidatorCustomExceptionTypeDelegate, + extraStackFrames: 2) + { + // SignatureValidationError does not handle the exception type 'CustomSecurityTokenException' + ExpectedException = ExpectedException.SecurityTokenException( + LogHelper.FormatInvariant( + Tokens.LogMessages.IDX10002, // "IDX10002: Unknown exception type returned. Type: '{0}'. Message: '{1}'."; + typeof(CustomSecurityTokenException), + nameof(CustomSignatureValidationDelegates.SignatureValidatorCustomExceptionTypeDelegate))), + SignatureValidationError = new SignatureValidationError( + new MessageDetail( + nameof(CustomSignatureValidationDelegates.SignatureValidatorCustomExceptionTypeDelegate), null), + ValidationFailureType.SignatureValidationFailed, + typeof(CustomSecurityTokenException), + new StackFrame("CustomSignatureValidationDelegates.cs", 274)) + }); + + // SignatureValidationError : ValidationError, ExceptionType: SecurityTokenInvalidSignatureException, inner: CustomSecurityTokenInvalidSignatureException + theoryData.Add(new SignatureExtensibilityTheoryData( + "SignatureValidatorThrows", + utcNow, + CustomSignatureValidationDelegates.SignatureValidatorThrows, + extraStackFrames: 1) + { + ExpectedException = new ExpectedException( + typeof(SecurityTokenInvalidSignatureException), + string.Format(Tokens.LogMessages.IDX10272), + typeof(CustomSecurityTokenInvalidSignatureException)), + SignatureValidationError = new SignatureValidationError( + new MessageDetail( + string.Format(Tokens.LogMessages.IDX10272), null), + ValidationFailureType.SignatureValidatorThrew, + typeof(SecurityTokenInvalidSignatureException), + new StackFrame("Saml2SecurityTokenHandler.ValidateSignature.cs", 250), + null, // no inner validation error + new SecurityTokenInvalidSignatureException(nameof(CustomSignatureValidationDelegates.SignatureValidatorThrows)) + ) + }); + #endregion + + return theoryData; + } + } + + public class SignatureExtensibilityTheoryData : TheoryDataBase + { + internal SignatureExtensibilityTheoryData(string testId, DateTime utcNow, SignatureValidationDelegate signatureValidator, int extraStackFrames) : base(testId) + { + Saml2Token = (Saml2SecurityToken)Saml2SecurityTokenHandler.CreateToken( + new SecurityTokenDescriptor() + { + Subject = Default.SamlClaimsIdentity, + Issuer = Default.Issuer, + }); + + ValidationParameters = new ValidationParameters + { + AlgorithmValidator = SkipValidationDelegates.SkipAlgorithmValidation, + AudienceValidator = SkipValidationDelegates.SkipAudienceValidation, + IssuerValidatorAsync = SkipValidationDelegates.SkipIssuerValidation, + IssuerSigningKeyValidator = SkipValidationDelegates.SkipIssuerSigningKeyValidation, + LifetimeValidator = SkipValidationDelegates.SkipLifetimeValidation, + SignatureValidator = signatureValidator, + TokenReplayValidator = SkipValidationDelegates.SkipTokenReplayValidation, + TokenTypeValidator = SkipValidationDelegates.SkipTokenTypeValidation + }; + + ExtraStackFrames = extraStackFrames; + } + + public Saml2SecurityToken Saml2Token { get; } + + public Saml2SecurityTokenHandler Saml2SecurityTokenHandler { get; } = new Saml2SecurityTokenHandler(); + + public bool IsValid { get; set; } + + internal ValidationParameters ValidationParameters { get; } + + internal SignatureValidationError? SignatureValidationError { get; set; } + + internal int ExtraStackFrames { get; } + } + } +} +#nullable restore diff --git a/test/Microsoft.IdentityModel.Tokens.Saml.Tests/Saml2SecurityTokenHandlerTests.ValidateTokenAsyncTests.IssuerSigningKey.cs b/test/Microsoft.IdentityModel.Tokens.Saml.Tests/Saml2SecurityTokenHandlerTests.ValidateTokenAsyncTests.IssuerSigningKey.cs index 94d38d6151..38f25ddc2a 100644 --- a/test/Microsoft.IdentityModel.Tokens.Saml.Tests/Saml2SecurityTokenHandlerTests.ValidateTokenAsyncTests.IssuerSigningKey.cs +++ b/test/Microsoft.IdentityModel.Tokens.Saml.Tests/Saml2SecurityTokenHandlerTests.ValidateTokenAsyncTests.IssuerSigningKey.cs @@ -132,7 +132,7 @@ static ValidationParameters CreateValidationParameters( SecurityToken token, ValidationParameters validationParameters, BaseConfiguration? configuration, - CallContext? callContext) => + CallContext callContext) => { // Set the signing key for validation token.SigningKey = issuerSigingKey; diff --git a/test/Microsoft.IdentityModel.Tokens.Saml.Tests/SamlSecurityTokenHandler.Extensibility.Algorithm.cs b/test/Microsoft.IdentityModel.Tokens.Saml.Tests/SamlSecurityTokenHandler.Extensibility.Algorithm.cs new file mode 100644 index 0000000000..ade781d8ac --- /dev/null +++ b/test/Microsoft.IdentityModel.Tokens.Saml.Tests/SamlSecurityTokenHandler.Extensibility.Algorithm.cs @@ -0,0 +1,337 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Diagnostics; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.IdentityModel.Logging; +using Microsoft.IdentityModel.TestUtils; + +using Xunit; + +#nullable enable +namespace Microsoft.IdentityModel.Tokens.Saml.Extensibility.Tests +{ + public partial class SamlSecurityTokenHandlerValidateTokenAsyncTests + { + [Theory, MemberData(nameof(Algorithm_ExtensibilityTestCases), DisableDiscoveryEnumeration = true)] + public async Task ValidateTokenAsync_AlgorithmValidator_Extensibility(AlgorithmExtensibilityTheoryData theoryData) + { + var context = TestUtilities.WriteHeader($"{this}.{nameof(ValidateTokenAsync_AlgorithmValidator_Extensibility)}", theoryData); + context.IgnoreType = false; + for (int i = 0; i < theoryData.ExtraStackFrames; i++) + theoryData.ValidationError!.AddStackFrame(new StackFrame(false)); + + SamlSecurityToken SamlToken = (SamlSecurityToken)theoryData.SamlSecurityTokenHandler.CreateToken(new SecurityTokenDescriptor() + { + Issuer = Default.Issuer, + Subject = Default.SamlClaimsIdentity, + SigningCredentials = KeyingMaterial.DefaultX509SigningCreds_2048_RsaSha2_Sha2, + }); + theoryData.SamlToken = theoryData.SamlSecurityTokenHandler.ReadSamlToken(SamlToken.Assertion.CanonicalString); + + theoryData.ValidationParameters!.IssuerSigningKeys.Add(theoryData.SigningKey); + + try + { + ValidationResult validationResult = await theoryData.SamlSecurityTokenHandler.ValidateTokenAsync( + theoryData.SamlToken!, + theoryData.ValidationParameters!, + theoryData.CallContext, + CancellationToken.None); + + if (validationResult.IsValid) + { + // We expect the validation to fail, but it passed + context.AddDiff("ValidationResult is Valid."); + } + else + { + ValidationError validationError = validationResult.UnwrapError(); + + if (validationError is SignatureValidationError signatureValidationError && + signatureValidationError.InnerValidationError is not null) + { + IdentityComparer.AreValidationErrorsEqual( + signatureValidationError.InnerValidationError, + theoryData.ValidationError, + context); + } + else + { + IdentityComparer.AreValidationErrorsEqual( + validationError, + theoryData.ValidationError, + context); + } + + var exception = validationError.GetException(); + theoryData.ExpectedException.ProcessException(exception, context); + // Process the inner exception since invalid algorithm exceptions are wrapped inside + // invalid signature exceptions + if (theoryData.ExpectedInnerException is not null) + theoryData.ExpectedInnerException.ProcessException(exception.InnerException, context); + } + } + catch (Exception ex) + { + // We expect the validation to fail, but it threw an exception + context.AddDiff($"ValidateTokenAsync threw exception: {ex}"); + } + + TestUtilities.AssertFailIfErrors(context); + } + + public static TheoryData Algorithm_ExtensibilityTestCases + { + get + { + var theoryData = new TheoryData(); + CallContext callContext = new CallContext(); + var utcNow = DateTime.UtcNow; + var utcPlusOneHour = utcNow + TimeSpan.FromHours(1); + + #region return CustomAlgorithmValidationError + // Test cases where delegate is overridden and return a CustomAlgorithmValidationError + // CustomAlgorithmValidationError : AlgorithmValidationError, ExceptionType: SecurityTokenInvalidAlgorithmException + theoryData.Add(new AlgorithmExtensibilityTheoryData( + "CustomAlgorithmValidatorDelegate", + utcNow, + CustomAlgorithmValidationDelegates.CustomAlgorithmValidatorDelegate, + extraStackFrames: 1) + { + ExpectedException = new ExpectedException( + typeof(SecurityTokenInvalidSignatureException), + "IDX10518:", + typeof(SecurityTokenInvalidAlgorithmException)), + ExpectedInnerException = new ExpectedException( + typeof(SecurityTokenInvalidAlgorithmException), + nameof(CustomAlgorithmValidationDelegates.CustomAlgorithmValidatorDelegate)), + ValidationError = new CustomAlgorithmValidationError( + new MessageDetail( + nameof(CustomAlgorithmValidationDelegates.CustomAlgorithmValidatorDelegate), null), + ValidationFailureType.AlgorithmValidationFailed, + typeof(SecurityTokenInvalidAlgorithmException), + new StackFrame("CustomAlgorithmValidationDelegates.cs", 160), + "algorithm") + }); + + // CustomAlgorithmValidationError : AlgorithmValidationError, ExceptionType: CustomSecurityTokenInvalidAlgorithmException : SecurityTokenInvalidAlgorithmException + theoryData.Add(new AlgorithmExtensibilityTheoryData( + "CustomAlgorithmValidatorCustomExceptionDelegate", + utcNow, + CustomAlgorithmValidationDelegates.CustomAlgorithmValidatorCustomExceptionDelegate, + extraStackFrames: 1) + { + ExpectedException = new ExpectedException( + typeof(SecurityTokenInvalidSignatureException), + "IDX10518:", + typeof(CustomSecurityTokenInvalidAlgorithmException)), + ExpectedInnerException = new ExpectedException( + typeof(CustomSecurityTokenInvalidAlgorithmException), + nameof(CustomAlgorithmValidationDelegates.CustomAlgorithmValidatorCustomExceptionDelegate)), + ValidationError = new CustomAlgorithmValidationError( + new MessageDetail( + nameof(CustomAlgorithmValidationDelegates.CustomAlgorithmValidatorCustomExceptionDelegate), null), + ValidationFailureType.AlgorithmValidationFailed, + typeof(CustomSecurityTokenInvalidAlgorithmException), + new StackFrame("CustomAlgorithmValidationDelegates.cs", 175), + "algorithm"), + }); + + // CustomAlgorithmValidationError : AlgorithmValidationError, ExceptionType: NotSupportedException : SystemException + theoryData.Add(new AlgorithmExtensibilityTheoryData( + "CustomAlgorithmValidatorUnknownExceptionDelegate", + utcNow, + CustomAlgorithmValidationDelegates.CustomAlgorithmValidatorUnknownExceptionDelegate, + extraStackFrames: 1) + { + // CustomAlgorithmValidationError does not handle the exception type 'NotSupportedException' + ExpectedException = new ExpectedException( + typeof(SecurityTokenInvalidSignatureException), + "IDX10518:", + typeof(SecurityTokenException)), + ExpectedInnerException = ExpectedException.SecurityTokenException( + LogHelper.FormatInvariant( + Tokens.LogMessages.IDX10002, // "IDX10002: Unknown exception type returned. Type: '{0}'. Message: '{1}'."; + typeof(NotSupportedException), + nameof(CustomAlgorithmValidationDelegates.CustomAlgorithmValidatorUnknownExceptionDelegate))), + ValidationError = new CustomAlgorithmValidationError( + new MessageDetail( + nameof(CustomAlgorithmValidationDelegates.CustomAlgorithmValidatorUnknownExceptionDelegate), null), + ValidationFailureType.AlgorithmValidationFailed, + typeof(NotSupportedException), + new StackFrame("CustomAlgorithmValidationDelegates.cs", 205), + "algorithm"), + }); + + // CustomAlgorithmValidationError : AlgorithmValidationError, ExceptionType: NotSupportedException : SystemException, ValidationFailureType: CustomAudienceValidationFailureType + theoryData.Add(new AlgorithmExtensibilityTheoryData( + "CustomAlgorithmValidatorCustomExceptionCustomFailureTypeDelegate", + utcNow, + CustomAlgorithmValidationDelegates.CustomAlgorithmValidatorCustomExceptionCustomFailureTypeDelegate, + extraStackFrames: 1) + { + ExpectedException = new ExpectedException( + typeof(SecurityTokenInvalidSignatureException), + "IDX10518:", + typeof(CustomSecurityTokenInvalidAlgorithmException)), + ExpectedInnerException = new ExpectedException( + typeof(CustomSecurityTokenInvalidAlgorithmException), + nameof(CustomAlgorithmValidationDelegates.CustomAlgorithmValidatorCustomExceptionCustomFailureTypeDelegate)), + ValidationError = new CustomAlgorithmValidationError( + new MessageDetail( + nameof(CustomAlgorithmValidationDelegates.CustomAlgorithmValidatorCustomExceptionCustomFailureTypeDelegate), null), + CustomAlgorithmValidationError.CustomAlgorithmValidationFailureType, + typeof(CustomSecurityTokenInvalidAlgorithmException), + new StackFrame("CustomAlgorithmValidationDelegates.cs", 190), + "algorithm"), + }); + #endregion + + #region return AlgorithmValidationError + // Test cases where delegate is overridden and return an AlgorithmValidationError + // AlgorithmValidationError : ValidationError, ExceptionType: SecurityTokenInvalidAlgorithmException + theoryData.Add(new AlgorithmExtensibilityTheoryData( + "AlgorithmValidatorDelegate", + utcNow, + CustomAlgorithmValidationDelegates.AlgorithmValidatorDelegate, + extraStackFrames: 1) + { + ExpectedException = new ExpectedException( + typeof(SecurityTokenInvalidSignatureException), + "IDX10518:", + typeof(SecurityTokenInvalidAlgorithmException)), + ExpectedInnerException = new ExpectedException( + typeof(SecurityTokenInvalidAlgorithmException), + nameof(CustomAlgorithmValidationDelegates.AlgorithmValidatorDelegate)), + ValidationError = new AlgorithmValidationError( + new MessageDetail( + nameof(CustomAlgorithmValidationDelegates.AlgorithmValidatorDelegate), null), + ValidationFailureType.AlgorithmValidationFailed, + typeof(SecurityTokenInvalidAlgorithmException), + new StackFrame("CustomAlgorithmValidationDelegates.cs", 235), + "algorithm") + }); + + // AlgorithmValidationError : ValidationError, ExceptionType: CustomSecurityTokenInvalidAlgorithmException : SecurityTokenInvalidAlgorithmException + theoryData.Add(new AlgorithmExtensibilityTheoryData( + "AlgorithmValidatorCustomAlgorithmExceptionTypeDelegate", + utcNow, + CustomAlgorithmValidationDelegates.AlgorithmValidatorCustomAlgorithmExceptionTypeDelegate, + extraStackFrames: 1) + { + // AlgorithmValidationError does not handle the exception type 'CustomSecurityTokenInvalidAlgorithmException' + ExpectedException = new ExpectedException( + typeof(SecurityTokenInvalidSignatureException), + "IDX10518:", + typeof(SecurityTokenException)), + ExpectedInnerException = ExpectedException.SecurityTokenException( + LogHelper.FormatInvariant( + Tokens.LogMessages.IDX10002, // "IDX10002: Unknown exception type returned. Type: '{0}'. Message: '{1}'."; + typeof(CustomSecurityTokenInvalidAlgorithmException), + nameof(CustomAlgorithmValidationDelegates.AlgorithmValidatorCustomAlgorithmExceptionTypeDelegate))), + ValidationError = new AlgorithmValidationError( + new MessageDetail( + nameof(CustomAlgorithmValidationDelegates.AlgorithmValidatorCustomAlgorithmExceptionTypeDelegate), null), + ValidationFailureType.AlgorithmValidationFailed, + typeof(CustomSecurityTokenInvalidAlgorithmException), + new StackFrame("CustomAlgorithmValidationDelegates.cs", 259), + "algorithm") + }); + + // AlgorithmValidationError : ValidationError, ExceptionType: CustomSecurityTokenException : SystemException + theoryData.Add(new AlgorithmExtensibilityTheoryData( + "AlgorithmValidatorCustomExceptionTypeDelegate", + utcNow, + CustomAlgorithmValidationDelegates.AlgorithmValidatorCustomExceptionTypeDelegate, + extraStackFrames: 1) + { + // AlgorithmValidationError does not handle the exception type 'CustomSecurityTokenException' + ExpectedException = new ExpectedException( + typeof(SecurityTokenInvalidSignatureException), + "IDX10518:", + typeof(SecurityTokenException)), + ExpectedInnerException = ExpectedException.SecurityTokenException( + LogHelper.FormatInvariant( + Tokens.LogMessages.IDX10002, // "IDX10002: Unknown exception type returned. Type: '{0}'. Message: '{1}'."; + typeof(CustomSecurityTokenException), + nameof(CustomAlgorithmValidationDelegates.AlgorithmValidatorCustomExceptionTypeDelegate))), + ValidationError = new AlgorithmValidationError( + new MessageDetail( + nameof(CustomAlgorithmValidationDelegates.AlgorithmValidatorCustomExceptionTypeDelegate), null), + ValidationFailureType.AlgorithmValidationFailed, + typeof(CustomSecurityTokenException), + new StackFrame("CustomAlgorithmValidationDelegates.cs", 274), + "algorithm") + }); + + // SignatureValidationError : ValidationError, ExceptionType: SecurityTokenInvalidSignatureException, inner: CustomSecurityTokenInvalidAlgorithmException + theoryData.Add(new AlgorithmExtensibilityTheoryData( + "AlgorithmValidatorThrows", + utcNow, + CustomAlgorithmValidationDelegates.AlgorithmValidatorThrows, + extraStackFrames: 2) + { + ExpectedException = new ExpectedException( + typeof(SecurityTokenInvalidSignatureException), + "IDX10273:", + typeof(CustomSecurityTokenInvalidAlgorithmException)), + ExpectedInnerException = new ExpectedException( + typeof(CustomSecurityTokenInvalidAlgorithmException), + nameof(CustomAlgorithmValidationDelegates.AlgorithmValidatorThrows)), + ValidationError = new SignatureValidationError( + new MessageDetail( + string.Format(Tokens.LogMessages.IDX10273), null), + ValidationFailureType.AlgorithmValidatorThrew, + typeof(SecurityTokenInvalidSignatureException), + new StackFrame("SamlSecurityTokenHandler.ValidateSignature.cs", 250), + null, // no inner validation error + new CustomSecurityTokenInvalidAlgorithmException(nameof(CustomAlgorithmValidationDelegates.AlgorithmValidatorThrows), null) + ) + }); + #endregion + + return theoryData; + } + } + + public class AlgorithmExtensibilityTheoryData : TheoryDataBase + { + internal AlgorithmExtensibilityTheoryData(string testId, DateTime utcNow, AlgorithmValidationDelegate algorithmValidator, int extraStackFrames) : base(testId) + { + ValidationParameters = new ValidationParameters + { + // We leave the default signature validator to call the custom algorithm validator + AlgorithmValidator = algorithmValidator, + AudienceValidator = SkipValidationDelegates.SkipAudienceValidation, + IssuerValidatorAsync = SkipValidationDelegates.SkipIssuerValidation, + IssuerSigningKeyValidator = SkipValidationDelegates.SkipIssuerSigningKeyValidation, + LifetimeValidator = SkipValidationDelegates.SkipLifetimeValidation, + TokenReplayValidator = SkipValidationDelegates.SkipTokenReplayValidation, + TokenTypeValidator = SkipValidationDelegates.SkipTokenTypeValidation, + }; + + ExtraStackFrames = extraStackFrames; + } + + public SamlSecurityToken? SamlToken { get; set; } + + public SamlSecurityTokenHandler SamlSecurityTokenHandler { get; } = new SamlSecurityTokenHandler(); + + public bool IsValid { get; set; } + + internal ValidationError? ValidationError { get; set; } + + public ExpectedException? ExpectedInnerException { get; set; } + + internal int ExtraStackFrames { get; } + + internal ValidationParameters ValidationParameters { get; } + + public SecurityKey SigningKey { get; set; } = KeyingMaterial.DefaultX509SigningCreds_2048_RsaSha2_Sha2.Key; + } + } +} +#nullable restore diff --git a/test/Microsoft.IdentityModel.Tokens.Saml.Tests/SamlSecurityTokenHandler.Extensibility.Signature.cs b/test/Microsoft.IdentityModel.Tokens.Saml.Tests/SamlSecurityTokenHandler.Extensibility.Signature.cs new file mode 100644 index 0000000000..467bce3c42 --- /dev/null +++ b/test/Microsoft.IdentityModel.Tokens.Saml.Tests/SamlSecurityTokenHandler.Extensibility.Signature.cs @@ -0,0 +1,270 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Diagnostics; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.IdentityModel.Logging; +using Microsoft.IdentityModel.TestUtils; + +using Xunit; + +#nullable enable +namespace Microsoft.IdentityModel.Tokens.Saml.Extensibility.Tests +{ + public partial class SamlSecurityTokenHandlerValidateTokenAsyncTests + { + [Theory, MemberData(nameof(Signature_ExtensibilityTestCases), DisableDiscoveryEnumeration = true)] + public async Task ValidateTokenAsync_SignatureValidator_Extensibility(SignatureExtensibilityTheoryData theoryData) + { + var context = TestUtilities.WriteHeader($"{this}.{nameof(ValidateTokenAsync_SignatureValidator_Extensibility)}", theoryData); + context.IgnoreType = false; + for (int i = 0; i < theoryData.ExtraStackFrames; i++) + theoryData.SignatureValidationError!.AddStackFrame(new StackFrame(false)); + + try + { + ValidationResult validationResult = await theoryData.SamlSecurityTokenHandler.ValidateTokenAsync( + theoryData.SamlToken!, + theoryData.ValidationParameters!, + theoryData.CallContext, + CancellationToken.None); + + if (validationResult.IsValid) + { + // We expect the validation to fail, but it passed + context.AddDiff("ValidationResult is Valid."); + } + else + { + ValidationError validationError = validationResult.UnwrapError(); + IdentityComparer.AreValidationErrorsEqual(validationError, theoryData.SignatureValidationError, context); + theoryData.ExpectedException.ProcessException(validationError.GetException(), context); + } + } + catch (Exception ex) + { + theoryData.ExpectedException.ProcessException(ex, context); + } + + TestUtilities.AssertFailIfErrors(context); + } + + public static TheoryData Signature_ExtensibilityTestCases + { + get + { + var theoryData = new TheoryData(); + CallContext callContext = new CallContext(); + var utcNow = DateTime.UtcNow; + var utcPlusOneHour = utcNow + TimeSpan.FromHours(1); + + #region return CustomSignatureValidationError + // Test cases where delegate is overridden and return a CustomSignatureValidationError + // CustomSignatureValidationError : SignatureValidationError, ExceptionType: SecurityTokenInvalidSignatureException + theoryData.Add(new SignatureExtensibilityTheoryData( + "CustomSignatureValidatorDelegate", + utcNow, + CustomSignatureValidationDelegates.CustomSignatureValidatorDelegate, + extraStackFrames: 2) + { + ExpectedException = new ExpectedException( + typeof(SecurityTokenInvalidSignatureException), + nameof(CustomSignatureValidationDelegates.CustomSignatureValidatorDelegate)), + SignatureValidationError = new CustomSignatureValidationError( + new MessageDetail( + nameof(CustomSignatureValidationDelegates.CustomSignatureValidatorDelegate), null), + ValidationFailureType.SignatureValidationFailed, + typeof(SecurityTokenInvalidSignatureException), + new StackFrame("CustomSignatureValidationDelegates.cs", 160)) + }); + + // CustomSignatureValidationError : SignatureValidationError, ExceptionType: CustomSecurityTokenInvalidSignatureException : SecurityTokenInvalidSignatureException + theoryData.Add(new SignatureExtensibilityTheoryData( + "CustomSignatureValidatorCustomExceptionDelegate", + utcNow, + CustomSignatureValidationDelegates.CustomSignatureValidatorCustomExceptionDelegate, + extraStackFrames: 2) + { + ExpectedException = new ExpectedException( + typeof(CustomSecurityTokenInvalidSignatureException), + nameof(CustomSignatureValidationDelegates.CustomSignatureValidatorCustomExceptionDelegate)), + SignatureValidationError = new CustomSignatureValidationError( + new MessageDetail( + nameof(CustomSignatureValidationDelegates.CustomSignatureValidatorCustomExceptionDelegate), null), + ValidationFailureType.SignatureValidationFailed, + typeof(CustomSecurityTokenInvalidSignatureException), + new StackFrame("CustomSignatureValidationDelegates.cs", 175)), + }); + + // CustomSignatureValidationError : SignatureValidationError, ExceptionType: NotSupportedException : SystemException + theoryData.Add(new SignatureExtensibilityTheoryData( + "CustomSignatureValidatorUnknownExceptionDelegate", + utcNow, + CustomSignatureValidationDelegates.CustomSignatureValidatorUnknownExceptionDelegate, + extraStackFrames: 2) + { + // CustomSignatureValidationError does not handle the exception type 'NotSupportedException' + ExpectedException = ExpectedException.SecurityTokenException( + LogHelper.FormatInvariant( + Tokens.LogMessages.IDX10002, // "IDX10002: Unknown exception type returned. Type: '{0}'. Message: '{1}'."; + typeof(NotSupportedException), + nameof(CustomSignatureValidationDelegates.CustomSignatureValidatorUnknownExceptionDelegate))), + SignatureValidationError = new CustomSignatureValidationError( + new MessageDetail( + nameof(CustomSignatureValidationDelegates.CustomSignatureValidatorUnknownExceptionDelegate), null), + ValidationFailureType.SignatureValidationFailed, + typeof(NotSupportedException), + new StackFrame("CustomSignatureValidationDelegates.cs", 205)), + }); + + // CustomSignatureValidationError : SignatureValidationError, ExceptionType: NotSupportedException : SystemException, ValidationFailureType: CustomAudienceValidationFailureType + theoryData.Add(new SignatureExtensibilityTheoryData( + "CustomSignatureValidatorCustomExceptionCustomFailureTypeDelegate", + utcNow, + CustomSignatureValidationDelegates.CustomSignatureValidatorCustomExceptionCustomFailureTypeDelegate, + extraStackFrames: 2) + { + ExpectedException = new ExpectedException( + typeof(CustomSecurityTokenInvalidSignatureException), + nameof(CustomSignatureValidationDelegates.CustomSignatureValidatorCustomExceptionCustomFailureTypeDelegate)), + SignatureValidationError = new CustomSignatureValidationError( + new MessageDetail( + nameof(CustomSignatureValidationDelegates.CustomSignatureValidatorCustomExceptionCustomFailureTypeDelegate), null), + CustomSignatureValidationError.CustomSignatureValidationFailureType, + typeof(CustomSecurityTokenInvalidSignatureException), + new StackFrame("CustomSignatureValidationDelegates.cs", 190)), + }); + #endregion + + #region return SignatureValidationError + // Test cases where delegate is overridden and return an SignatureValidationError + // SignatureValidationError : ValidationError, ExceptionType: SecurityTokenInvalidSignatureException + theoryData.Add(new SignatureExtensibilityTheoryData( + "SignatureValidatorDelegate", + utcNow, + CustomSignatureValidationDelegates.SignatureValidatorDelegate, + extraStackFrames: 2) + { + ExpectedException = new ExpectedException( + typeof(SecurityTokenInvalidSignatureException), + nameof(CustomSignatureValidationDelegates.SignatureValidatorDelegate)), + SignatureValidationError = new SignatureValidationError( + new MessageDetail( + nameof(CustomSignatureValidationDelegates.SignatureValidatorDelegate), null), + ValidationFailureType.SignatureValidationFailed, + typeof(SecurityTokenInvalidSignatureException), + new StackFrame("CustomSignatureValidationDelegates.cs", 235)) + }); + + // SignatureValidationError : ValidationError, ExceptionType: CustomSecurityTokenInvalidSignatureException : SecurityTokenInvalidSignatureException + theoryData.Add(new SignatureExtensibilityTheoryData( + "SignatureValidatorCustomSignatureExceptionTypeDelegate", + utcNow, + CustomSignatureValidationDelegates.SignatureValidatorCustomSignatureExceptionTypeDelegate, + extraStackFrames: 2) + { + // SignatureValidationError does not handle the exception type 'CustomSecurityTokenInvalidSignatureException' + ExpectedException = ExpectedException.SecurityTokenException( + LogHelper.FormatInvariant( + Tokens.LogMessages.IDX10002, // "IDX10002: Unknown exception type returned. Type: '{0}'. Message: '{1}'."; + typeof(CustomSecurityTokenInvalidSignatureException), + nameof(CustomSignatureValidationDelegates.SignatureValidatorCustomSignatureExceptionTypeDelegate))), + SignatureValidationError = new SignatureValidationError( + new MessageDetail( + nameof(CustomSignatureValidationDelegates.SignatureValidatorCustomSignatureExceptionTypeDelegate), null), + ValidationFailureType.SignatureValidationFailed, + typeof(CustomSecurityTokenInvalidSignatureException), + new StackFrame("CustomSignatureValidationDelegates.cs", 259)) + }); + + // SignatureValidationError : ValidationError, ExceptionType: CustomSecurityTokenException : SystemException + theoryData.Add(new SignatureExtensibilityTheoryData( + "SignatureValidatorCustomExceptionTypeDelegate", + utcNow, + CustomSignatureValidationDelegates.SignatureValidatorCustomExceptionTypeDelegate, + extraStackFrames: 2) + { + // SignatureValidationError does not handle the exception type 'CustomSecurityTokenException' + ExpectedException = ExpectedException.SecurityTokenException( + LogHelper.FormatInvariant( + Tokens.LogMessages.IDX10002, // "IDX10002: Unknown exception type returned. Type: '{0}'. Message: '{1}'."; + typeof(CustomSecurityTokenException), + nameof(CustomSignatureValidationDelegates.SignatureValidatorCustomExceptionTypeDelegate))), + SignatureValidationError = new SignatureValidationError( + new MessageDetail( + nameof(CustomSignatureValidationDelegates.SignatureValidatorCustomExceptionTypeDelegate), null), + ValidationFailureType.SignatureValidationFailed, + typeof(CustomSecurityTokenException), + new StackFrame("CustomSignatureValidationDelegates.cs", 274)) + }); + + // SignatureValidationError : ValidationError, ExceptionType: SecurityTokenInvalidSignatureException, inner: CustomSecurityTokenInvalidSignatureException + theoryData.Add(new SignatureExtensibilityTheoryData( + "SignatureValidatorThrows", + utcNow, + CustomSignatureValidationDelegates.SignatureValidatorThrows, + extraStackFrames: 1) + { + ExpectedException = new ExpectedException( + typeof(SecurityTokenInvalidSignatureException), + string.Format(Tokens.LogMessages.IDX10272), + typeof(CustomSecurityTokenInvalidSignatureException)), + SignatureValidationError = new SignatureValidationError( + new MessageDetail( + string.Format(Tokens.LogMessages.IDX10272), null), + ValidationFailureType.SignatureValidatorThrew, + typeof(SecurityTokenInvalidSignatureException), + new StackFrame("SamlSecurityTokenHandler.ValidateSignature.cs", 250), + null, // no inner validation error + new SecurityTokenInvalidSignatureException(nameof(CustomSignatureValidationDelegates.SignatureValidatorThrows)) + ) + }); + #endregion + + return theoryData; + } + } + + public class SignatureExtensibilityTheoryData : TheoryDataBase + { + internal SignatureExtensibilityTheoryData(string testId, DateTime utcNow, SignatureValidationDelegate signatureValidator, int extraStackFrames) : base(testId) + { + SamlToken = (SamlSecurityToken)SamlSecurityTokenHandler.CreateToken( + new SecurityTokenDescriptor() + { + Subject = Default.SamlClaimsIdentity, + Issuer = Default.Issuer, + }); + + ValidationParameters = new ValidationParameters + { + AlgorithmValidator = SkipValidationDelegates.SkipAlgorithmValidation, + AudienceValidator = SkipValidationDelegates.SkipAudienceValidation, + IssuerValidatorAsync = SkipValidationDelegates.SkipIssuerValidation, + IssuerSigningKeyValidator = SkipValidationDelegates.SkipIssuerSigningKeyValidation, + LifetimeValidator = SkipValidationDelegates.SkipLifetimeValidation, + SignatureValidator = signatureValidator, + TokenReplayValidator = SkipValidationDelegates.SkipTokenReplayValidation, + TokenTypeValidator = SkipValidationDelegates.SkipTokenTypeValidation + }; + + ExtraStackFrames = extraStackFrames; + } + + public SamlSecurityToken SamlToken { get; } + + public SamlSecurityTokenHandler SamlSecurityTokenHandler { get; } = new SamlSecurityTokenHandler(); + + public bool IsValid { get; set; } + + internal ValidationParameters ValidationParameters { get; } + + internal SignatureValidationError? SignatureValidationError { get; set; } + + internal int ExtraStackFrames { get; } + } + } +} +#nullable restore diff --git a/test/Microsoft.IdentityModel.Tokens.Saml.Tests/SamlSecurityTokenHandlerTests.ValidateTokenAsyncTests.IssuerSigningKey.cs b/test/Microsoft.IdentityModel.Tokens.Saml.Tests/SamlSecurityTokenHandlerTests.ValidateTokenAsyncTests.IssuerSigningKey.cs index f34c777f4f..3598ad2c06 100644 --- a/test/Microsoft.IdentityModel.Tokens.Saml.Tests/SamlSecurityTokenHandlerTests.ValidateTokenAsyncTests.IssuerSigningKey.cs +++ b/test/Microsoft.IdentityModel.Tokens.Saml.Tests/SamlSecurityTokenHandlerTests.ValidateTokenAsyncTests.IssuerSigningKey.cs @@ -131,7 +131,7 @@ static ValidationParameters CreateValidationParameters( SecurityToken token, ValidationParameters validationParameters, BaseConfiguration? configuration, - CallContext? callContext) => + CallContext callContext) => { // Set the signing key for validation token.SigningKey = issuerSigingKey;