diff --git a/src/Microsoft.IdentityModel.Tokens/Validation/SigningKeyValidationResult.cs b/src/Microsoft.IdentityModel.Tokens/Validation/SigningKeyValidationResult.cs new file mode 100644 index 0000000000..6c3905c1cf --- /dev/null +++ b/src/Microsoft.IdentityModel.Tokens/Validation/SigningKeyValidationResult.cs @@ -0,0 +1,69 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +#nullable enable +namespace Microsoft.IdentityModel.Tokens +{ + /// + /// Contains the result of validating the used to sign a . + /// The contains a collection of for each step in the token validation. + /// + internal class SigningKeyValidationResult : ValidationResult + { + private Exception? _exception; + + /// + /// Creates an instance of + /// + /// is the security key that was validated successfully. + public SigningKeyValidationResult(SecurityKey? signingKey) + : base(ValidationFailureType.ValidationSucceeded) + { + SigningKey = signingKey; + IsValid = true; + } + + /// + /// Creates an instance of + /// + /// is the security key that was intended to be validated. + /// is the that occurred during validation. + /// is the that occurred during validation. + public SigningKeyValidationResult(SecurityKey? signingKey, ValidationFailureType validationFailure, ExceptionDetail exceptionDetail) + : base(validationFailure, exceptionDetail) + { + SigningKey = signingKey; + IsValid = false; + } + + /// + /// Gets the that occurred during validation. + /// + public override Exception? Exception + { + get + { + if (_exception != null || ExceptionDetail == null) + return _exception; + + HasValidOrExceptionWasRead = true; + _exception = ExceptionDetail.GetException(); + if (_exception is SecurityTokenInvalidSigningKeyException securityTokenInvalidSigningKeyException) + { + securityTokenInvalidSigningKeyException.SigningKey = SigningKey; + securityTokenInvalidSigningKeyException.ExceptionDetail = ExceptionDetail; + securityTokenInvalidSigningKeyException.Source = "Microsoft.IdentityModel.Tokens"; + } + + return _exception; + } + } + + /// + /// Gets the security key that was validated or intended to be validated. + /// + public SecurityKey? SigningKey { get; } + } +} +#nullable restore diff --git a/test/Microsoft.IdentityModel.TestUtils/IdentityComparer.cs b/test/Microsoft.IdentityModel.TestUtils/IdentityComparer.cs index c8caa5156d..5d35510816 100644 --- a/test/Microsoft.IdentityModel.TestUtils/IdentityComparer.cs +++ b/test/Microsoft.IdentityModel.TestUtils/IdentityComparer.cs @@ -673,6 +673,72 @@ internal static bool AreAudienceValidationResultsEqual( return context.Merge(localContext); } + public static bool AreSigningKeyValidationResultsEqual(object object1, object object2, CompareContext context) + { + var localContext = new CompareContext(context); + if (!ContinueCheckingEquality(object1, object2, context)) + return context.Merge(localContext); + + return AreSigningKeyValidationResultsEqual( + object1 as SigningKeyValidationResult, + object2 as SigningKeyValidationResult, + "SigningKeyValidationResult1", + "SigningKeyValidationResult2", + null, + context); + } + + internal static bool AreSigningKeyValidationResultsEqual( + SigningKeyValidationResult signingKeyValidationResult1, + SigningKeyValidationResult signingKeyValidationResult2, + string name1, + string name2, + string stackPrefix, + CompareContext context) + { + var localContext = new CompareContext(context); + + AreSecurityKeysEqual(signingKeyValidationResult1.SigningKey, signingKeyValidationResult2.SigningKey, localContext); + + if (!ContinueCheckingEquality(signingKeyValidationResult1, signingKeyValidationResult2, localContext)) + return context.Merge(localContext); + + if (signingKeyValidationResult1.IsValid != signingKeyValidationResult2.IsValid) + localContext.Diffs.Add($"SigningKeyValidationResult1.IsValid: {signingKeyValidationResult2.IsValid} != SigningKeyValidationResult2.IsValid: {signingKeyValidationResult2.IsValid}"); + + if (signingKeyValidationResult1.ValidationFailureType != signingKeyValidationResult2.ValidationFailureType) + localContext.Diffs.Add($"SigningKeyValidationResult1.ValidationFailureType: {signingKeyValidationResult1.ValidationFailureType} != SigningKeyValidationResult2.ValidationFailureType: {signingKeyValidationResult2.ValidationFailureType}"); + + // true => both are not null. + if (ContinueCheckingEquality(signingKeyValidationResult1.Exception, signingKeyValidationResult2.Exception, localContext)) + { + AreStringsEqual( + signingKeyValidationResult1.Exception.Message, + signingKeyValidationResult2.Exception.Message, + $"({name1})signingKeyValidationResult1.Exception.Message", + $"({name2})signingKeyValidationResult2.Exception.Message", + localContext); + + AreStringsEqual( + signingKeyValidationResult1.Exception.Source, + signingKeyValidationResult2.Exception.Source, + $"({name1})signingKeyValidationResult1.Exception.Source", + $"({name2})signingKeyValidationResult2.Exception.Source", + localContext); + + if (!string.IsNullOrEmpty(stackPrefix)) + AreStringPrefixesEqual( + signingKeyValidationResult1.Exception.StackTrace.Trim(), + signingKeyValidationResult2.Exception.StackTrace.Trim(), + $"({name1})signingKeyValidationResult1.Exception.StackTrace", + $"({name2})signingKeyValidationResult2.Exception.StackTrace", + stackPrefix.Trim(), + localContext); + } + + return context.Merge(localContext); + } + public static bool AreJArraysEqual(object object1, object object2, CompareContext context) { var localContext = new CompareContext(context);