Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Issuer signing key validation: Remove exceptions #2672

Merged
merged 7 commits into from
Jun 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using System;
#nullable enable
namespace Microsoft.IdentityModel.Tokens
{
/// <summary>
/// Contains the result of validating the <see cref="SecurityKey"/> used to sign a <see cref="SecurityToken"/>.
/// The <see cref="TokenValidationResult"/> contains a collection of <see cref="ValidationResult"/> for each step in the token validation.
/// </summary>
internal class SigningKeyValidationResult : ValidationResult
{
private Exception? _exception;

/// <summary>
/// Creates an instance of <see cref="SigningKeyValidationResult"/>
/// </summary>
/// <paramref name="signingKey"/> is the security key that was validated successfully.
public SigningKeyValidationResult(SecurityKey? signingKey)
: base(ValidationFailureType.ValidationSucceeded)
{
SigningKey = signingKey;
keegan-caruso marked this conversation as resolved.
Show resolved Hide resolved
IsValid = true;
}

/// <summary>
/// Creates an instance of <see cref="SigningKeyValidationResult"/>
/// </summary>
/// <paramref name="signingKey"/> is the security key that was intended to be validated.
/// <paramref name="validationFailure"/> is the <see cref="ValidationFailureType"/> that occurred during validation.
/// <paramref name="exceptionDetail"/> is the <see cref="ExceptionDetail"/> that occurred during validation.
public SigningKeyValidationResult(SecurityKey? signingKey, ValidationFailureType validationFailure, ExceptionDetail exceptionDetail)
: base(validationFailure, exceptionDetail)
{
SigningKey = signingKey;
IsValid = false;
}

/// <summary>
/// Gets the <see cref="Exception"/> that occurred during validation.
/// </summary>
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;
}
}

/// <summary>
/// Gets the security key that was validated or intended to be validated.
/// </summary>
public SecurityKey? SigningKey { get; }
}
}
#nullable restore
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,12 @@ private class IssuerValidationFailure : ValidationFailureType { internal IssuerV
public static readonly ValidationFailureType AudienceValidationFailed = new AudienceValidationFailure("AudienceValidationFailed");
private class AudienceValidationFailure : ValidationFailureType { internal AudienceValidationFailure(string name) : base(name) { } }

/// <summary>
/// Defines a type that represents that signing key validation failed.
/// </summary>
public static readonly ValidationFailureType SigningKeyValidationFailed = new SigningKeyValidationFailure("SigningKeyValidationFailed");
private class SigningKeyValidationFailure : ValidationFailureType { internal SigningKeyValidationFailure(string name) : base(name) { } }

/// <summary>
/// Defines a type that represents that lifetime validation failed.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,37 @@
// Licensed under the MIT License.

using System;
using System.Diagnostics;
using System.Security.Cryptography.X509Certificates;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.IdentityModel.Abstractions;
using Microsoft.IdentityModel.Logging;

#nullable enable
namespace Microsoft.IdentityModel.Tokens
{
/// <summary>
/// Definition for delegate that will validate the <see cref="SecurityKey"/> that signed a <see cref="SecurityToken"/>.
/// </summary>
/// <param name="signingKey">The security key to validate.</param>
/// <param name="securityToken">The <see cref="SecurityToken"/> that is being validated.</param>
/// <param name="validationParameters"><see cref="TokenValidationParameters"/> required for validation.</param>
/// <param name="callContext"></param>
keegan-caruso marked this conversation as resolved.
Show resolved Hide resolved
/// <param name="cancellationToken"></param>
/// <returns>A <see cref="SigningKeyValidationResult"/>that contains the results of validating the issuer.</returns>
/// <remarks>This delegate is not expected to throw.</remarks>
internal delegate Task<SigningKeyValidationResult> IssuerSecurityKeyValidationDelegate(
SecurityKey signingKey,
SecurityToken securityToken,
TokenValidationParameters validationParameters,
CallContext callContext,
CancellationToken cancellationToken);

/// <summary>
/// SigningKeyValidation
/// </summary>

public static partial class Validators
{
/// <summary>
Expand All @@ -21,7 +46,7 @@ public static partial class Validators
/// <exception cref="ArgumentNullException"> if 'validationParameters' is null.</exception>
public static void ValidateIssuerSecurityKey(SecurityKey securityKey, SecurityToken securityToken, TokenValidationParameters validationParameters)
{
ValidateIssuerSecurityKey(securityKey, securityToken, validationParameters, null);
ValidateIssuerSecurityKey(securityKey, securityToken, validationParameters, configuration: null);
}

/// <summary>
Expand All @@ -34,7 +59,7 @@ public static void ValidateIssuerSecurityKey(SecurityKey securityKey, SecurityTo
/// <exception cref="ArgumentNullException"> if 'securityKey' is null and ValidateIssuerSigningKey is true.</exception>
/// <exception cref="ArgumentNullException"> if 'securityToken' is null and ValidateIssuerSigningKey is true.</exception>
/// <exception cref="ArgumentNullException"> if 'validationParameters' is null.</exception>
internal static void ValidateIssuerSecurityKey(SecurityKey securityKey, SecurityToken securityToken, TokenValidationParameters validationParameters, BaseConfiguration configuration)
internal static void ValidateIssuerSecurityKey(SecurityKey securityKey, SecurityToken securityToken, TokenValidationParameters validationParameters, BaseConfiguration? configuration)
{
if (validationParameters == null)
throw LogHelper.LogArgumentNullException(nameof(validationParameters));
Expand Down Expand Up @@ -84,7 +109,7 @@ internal static void ValidateIssuerSecurityKey(SecurityKey securityKey, Security
/// <param name="validationParameters">The <see cref="TokenValidationParameters"/> that are used to validate the token.</param>
internal static void ValidateIssuerSigningKeyLifeTime(SecurityKey securityKey, TokenValidationParameters validationParameters)
{
X509SecurityKey x509SecurityKey = securityKey as X509SecurityKey;
X509SecurityKey? x509SecurityKey = securityKey as X509SecurityKey;
if (x509SecurityKey?.Certificate is X509Certificate2 cert)
{
DateTime utcNow = DateTime.UtcNow;
Expand All @@ -104,5 +129,186 @@ internal static void ValidateIssuerSigningKeyLifeTime(SecurityKey securityKey, T
LogHelper.LogInformation(LogMessages.IDX10251, LogHelper.MarkAsNonPII(notAfterUtc), LogHelper.MarkAsNonPII(utcNow));
}
}

/// <summary>
/// Validates the <see cref="SecurityKey"/> that signed a <see cref="SecurityToken"/>.
/// </summary>
/// <param name="securityKey">The <see cref="SecurityKey"/> that signed the <see cref="SecurityToken"/>.</param>
/// <param name="securityToken">The <see cref="SecurityToken"/> being validated.</param>
/// <param name="validationParameters"><see cref="TokenValidationParameters"/> required for validation.</param>
/// <param name="callContext"></param>
/// <exception cref="ArgumentNullException"> if 'securityKey' is null and ValidateIssuerSigningKey is true.</exception>
/// <exception cref="ArgumentNullException"> if 'securityToken' is null and ValidateIssuerSigningKey is true.</exception>
/// <exception cref="ArgumentNullException"> if 'validationParameters' is null.</exception>
internal static SigningKeyValidationResult ValidateIssuerSecurityKey(SecurityKey securityKey, SecurityToken securityToken, TokenValidationParameters validationParameters, CallContext callContext)
{
return ValidateIssuerSecurityKey(securityKey, securityToken, validationParameters, null, callContext);
}

/// <summary>
/// Validates the <see cref="SecurityKey"/> that signed a <see cref="SecurityToken"/>.
/// </summary>
/// <param name="securityKey">The <see cref="SecurityKey"/> that signed the <see cref="SecurityToken"/>.</param>
/// <param name="securityToken">The <see cref="SecurityToken"/> being validated.</param>
/// <param name="validationParameters"><see cref="TokenValidationParameters"/> required for validation.</param>
/// <param name="configuration">The <see cref="BaseConfiguration"/> required for issuer and signing key validation.</param>
/// <param name="callContext"></param>
/// <exception cref="ArgumentNullException"> if 'securityKey' is null and ValidateIssuerSigningKey is true.</exception>
/// <exception cref="ArgumentNullException"> if 'securityToken' is null and ValidateIssuerSigningKey is true.</exception>
/// <exception cref="ArgumentNullException"> if 'validationParameters' is null.</exception>
internal static SigningKeyValidationResult ValidateIssuerSecurityKey(SecurityKey securityKey, SecurityToken securityToken, TokenValidationParameters validationParameters, BaseConfiguration? configuration, CallContext callContext)
{
if (validationParameters == null)
return new SigningKeyValidationResult(
securityKey,
ValidationFailureType.NullArgument,
new ExceptionDetail(
new MessageDetail(
LogMessages.IDX10000,
LogHelper.MarkAsNonPII(nameof(validationParameters))),
typeof(ArgumentNullException),
new StackFrame(true)));

if (validationParameters.IssuerSigningKeyValidatorUsingConfiguration != null)
{
return ValidateSigningKeyUsingDelegateAndConfiguration(securityKey, securityToken, validationParameters, configuration);
}

if (validationParameters.IssuerSigningKeyValidator != null)
{
return ValidateSigningKeyUsingDelegateAndConfiguration(securityKey, securityToken, validationParameters, null);
}

if (!validationParameters.ValidateIssuerSigningKey)
{
LogHelper.LogVerbose(LogMessages.IDX10237);
return new SigningKeyValidationResult(securityKey);
}

if (!validationParameters.RequireSignedTokens && securityKey == null)
{
LogHelper.LogInformation(LogMessages.IDX10252);
return new SigningKeyValidationResult(securityKey);
}
else if (securityKey == null)
{
return new SigningKeyValidationResult(
securityKey,
ValidationFailureType.NullArgument,
new ExceptionDetail(
new MessageDetail(
LogMessages.IDX10253,
LogHelper.MarkAsNonPII(nameof(securityKey))),
typeof(ArgumentNullException),
new StackFrame(true)));
}

if (securityToken == null)
return new SigningKeyValidationResult(
securityKey,
ValidationFailureType.NullArgument,
new ExceptionDetail(
new MessageDetail(
LogMessages.IDX10000,
LogHelper.MarkAsNonPII(nameof(securityToken))),
typeof(ArgumentNullException),
new StackFrame(true)));

return ValidateIssuerSigningKeyLifeTime(securityKey, validationParameters, callContext);
}

/// <summary>
/// Given a signing key, when it's derived from a certificate, validates that the certificate is already active and non-expired
/// </summary>
/// <param name="securityKey">The <see cref="SecurityKey"/> that signed the <see cref="SecurityToken"/>.</param>
/// <param name="validationParameters">The <see cref="TokenValidationParameters"/> that are used to validate the token.</param>
/// <param name="callContext"></param>
#pragma warning disable CA1801 // Review unused parameters
internal static SigningKeyValidationResult ValidateIssuerSigningKeyLifeTime(SecurityKey securityKey, TokenValidationParameters validationParameters, CallContext callContext)
#pragma warning restore CA1801 // Review unused parameters
{
X509SecurityKey? x509SecurityKey = securityKey as X509SecurityKey;
if (x509SecurityKey?.Certificate is X509Certificate2 cert)
{
DateTime utcNow = DateTime.UtcNow;
var notBeforeUtc = cert.NotBefore.ToUniversalTime();
var notAfterUtc = cert.NotAfter.ToUniversalTime();

if (notBeforeUtc > DateTimeUtil.Add(utcNow, validationParameters.ClockSkew))
return new SigningKeyValidationResult(
securityKey,
ValidationFailureType.SigningKeyValidationFailed,
new ExceptionDetail(
new MessageDetail(
LogHelper.FormatInvariant(
LogMessages.IDX10248,
LogHelper.MarkAsNonPII(notBeforeUtc),
LogHelper.MarkAsNonPII(utcNow))),
typeof(SecurityTokenInvalidSigningKeyException),
new StackFrame(true)));

if (LogHelper.IsEnabled(EventLogLevel.Informational))
LogHelper.LogInformation(LogMessages.IDX10250, LogHelper.MarkAsNonPII(notBeforeUtc), LogHelper.MarkAsNonPII(utcNow));

if (notAfterUtc < DateTimeUtil.Add(utcNow, validationParameters.ClockSkew.Negate()))
return new SigningKeyValidationResult(
securityKey,
ValidationFailureType.SigningKeyValidationFailed,
new ExceptionDetail(
new MessageDetail(
LogHelper.FormatInvariant(
LogMessages.IDX10249,
LogHelper.MarkAsNonPII(notAfterUtc),
LogHelper.MarkAsNonPII(utcNow))),
typeof(SecurityTokenInvalidSigningKeyException),
new StackFrame(true)));

if (LogHelper.IsEnabled(EventLogLevel.Informational))
LogHelper.LogInformation(LogMessages.IDX10251, LogHelper.MarkAsNonPII(notAfterUtc), LogHelper.MarkAsNonPII(utcNow));
}

return new SigningKeyValidationResult(securityKey);
}

private static SigningKeyValidationResult ValidateSigningKeyUsingDelegateAndConfiguration(SecurityKey securityKey, SecurityToken securityToken, TokenValidationParameters validationParameters, BaseConfiguration? configuration)
{
try
{
bool success;
if (configuration != null)
success = validationParameters.IssuerSigningKeyValidatorUsingConfiguration(securityKey, securityToken, validationParameters, configuration);
else
success = validationParameters.IssuerSigningKeyValidator(securityKey, securityToken, validationParameters);

if (!success)
return new SigningKeyValidationResult(
securityKey,
ValidationFailureType.SigningKeyValidationFailed,
new ExceptionDetail(
new MessageDetail(
LogMessages.IDX10232,
securityKey),
typeof(SecurityTokenInvalidSigningKeyException),
new StackFrame(true)));

return new SigningKeyValidationResult(securityKey);
}
#pragma warning disable CA1031 // Do not catch general exception types
catch (Exception exception)
#pragma warning restore CA1031 // Do not catch general exception types
{
return new SigningKeyValidationResult(
securityKey,
ValidationFailureType.SigningKeyValidationFailed,
new ExceptionDetail(
new MessageDetail(
LogMessages.IDX10232,
securityKey),
exception.GetType(),
new StackFrame(true),
exception));
}
}
}
}
#nullable restore
Loading