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);