Skip to content

Commit

Permalink
Issuer signing key validation: Remove exceptions (#2672)
Browse files Browse the repository at this point in the history
* Added SigningKeyValidationResult
* Added validation failure type for signing key, removed exception throwing from ValidateIssuerSigningKey, and added tests
  • Loading branch information
iNinja authored Jun 29, 2024
1 parent 24411fd commit 0c5172c
Show file tree
Hide file tree
Showing 5 changed files with 682 additions and 3 deletions.
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;
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>
/// <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

0 comments on commit 0c5172c

Please sign in to comment.