Skip to content

Commit

Permalink
Extensibility tests: Token Replay - JWT, SAML and SAML2 (#3032)
Browse files Browse the repository at this point in the history
* Added TokenReplayValidationError and updated the default delegate to use it

* Added log message, custom validation errors, custom delegates, and validation failure type for the token replay validation extensibility tests

* Handle the potential case where the token replay delegate throws

* Added extensibility tests for token replay

* Resolved post merge unshipped API errors raised by Visual Studio
  • Loading branch information
iNinja authored Nov 25, 2024
1 parent f8b5bb6 commit 8fd166c
Show file tree
Hide file tree
Showing 13 changed files with 1,166 additions and 31 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -301,13 +301,27 @@ private async ValueTask<ValidationResult<ValidatedToken>> ValidateJWSAsync(
ex);
}

ValidationResult<DateTime?> replayValidationResult = validationParameters.TokenReplayValidator(
expires, jsonWebToken.EncodedToken, validationParameters, callContext);
ValidationResult<DateTime?> replayValidationResult;

if (!replayValidationResult.IsValid)
try
{
StackFrame replayValidationFailureStackFrame = StackFrames.ReplayValidationFailed ??= new StackFrame(true);
return replayValidationResult.UnwrapError().AddStackFrame(replayValidationFailureStackFrame);
replayValidationResult = validationParameters.TokenReplayValidator(
expires, jsonWebToken.EncodedToken, validationParameters, callContext);

if (!replayValidationResult.IsValid)
return replayValidationResult.UnwrapError().AddCurrentStackFrame();
}
#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 TokenReplayValidationError(
new MessageDetail(TokenLogMessages.IDX10276),
ValidationFailureType.TokenReplayValidatorThrew,
typeof(SecurityTokenReplayDetectedException),
ValidationError.GetCurrentStackFrame(),
expires,
ex);
}

ValidationResult<ValidatedToken>? actorValidationResult = null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,14 +90,31 @@ internal async Task<ValidationResult<ValidatedToken>> ValidateTokenAsync(

if (samlToken.Assertion.Conditions is not null)
{
ValidationResult<DateTime?> tokenReplayValidationResult = Validators.ValidateTokenReplay(
samlToken.Assertion.Conditions.NotOnOrAfter,
samlToken.Assertion.CanonicalString,
validationParameters,
callContext);
ValidationResult<DateTime?> tokenReplayValidationResult;

try
{
tokenReplayValidationResult = validationParameters.TokenReplayValidator(
samlToken.Assertion.Conditions.NotOnOrAfter,
samlToken.Assertion.CanonicalString,
validationParameters,
callContext);

if (!tokenReplayValidationResult.IsValid)
return tokenReplayValidationResult.UnwrapError().AddCurrentStackFrame();
if (!tokenReplayValidationResult.IsValid)
return tokenReplayValidationResult.UnwrapError().AddCurrentStackFrame();
}
#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 TokenReplayValidationError(
new MessageDetail(Tokens.LogMessages.IDX10276),
ValidationFailureType.TokenReplayValidatorThrew,
typeof(SecurityTokenReplayDetectedException),
ValidationError.GetCurrentStackFrame(),
samlToken.Assertion.Conditions.NotOnOrAfter,
ex);
}
}

ValidationResult<SecurityKey> signatureValidationResult = ValidateSignature(samlToken, validationParameters, callContext);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,14 +94,31 @@ internal async Task<ValidationResult<ValidatedToken>> ValidateTokenAsync(

if (samlToken.Assertion.Conditions is not null)
{
ValidationResult<DateTime?> tokenReplayValidationResult = Validators.ValidateTokenReplay(
samlToken.Assertion.Conditions.NotOnOrAfter,
samlToken.Assertion.CanonicalString,
validationParameters,
callContext);
ValidationResult<DateTime?> tokenReplayValidationResult;

if (!tokenReplayValidationResult.IsValid)
return tokenReplayValidationResult.UnwrapError().AddCurrentStackFrame();
try
{
tokenReplayValidationResult = validationParameters.TokenReplayValidator(
samlToken.Assertion.Conditions.NotOnOrAfter,
samlToken.Assertion.CanonicalString,
validationParameters,
callContext);

if (!tokenReplayValidationResult.IsValid)
return tokenReplayValidationResult.UnwrapError().AddCurrentStackFrame();
}
#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 TokenReplayValidationError(
new MessageDetail(Tokens.LogMessages.IDX10276),
ValidationFailureType.TokenReplayValidatorThrew,
typeof(SecurityTokenReplayDetectedException),
ValidationError.GetCurrentStackFrame(),
samlToken.Assertion.Conditions.NotOnOrAfter,
ex);
}
}

var signatureValidationResult = ValidateSignature(samlToken, validationParameters, callContext);
Expand Down
7 changes: 7 additions & 0 deletions src/Microsoft.IdentityModel.Tokens/InternalAPI.Unshipped.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ const Microsoft.IdentityModel.Tokens.LogMessages.IDX10272 = "IDX10272: Signature
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
const Microsoft.IdentityModel.Tokens.LogMessages.IDX10276 = "IDX10276: TokenReplayValidationDelegate 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
Expand All @@ -28,6 +29,9 @@ Microsoft.IdentityModel.Tokens.SecurityTokenInvalidOperationException.SecurityTo
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.TokenReplayValidationError
Microsoft.IdentityModel.Tokens.TokenReplayValidationError.ExpirationTime.get -> System.DateTime?
Microsoft.IdentityModel.Tokens.TokenReplayValidationError.TokenReplayValidationError(Microsoft.IdentityModel.Tokens.MessageDetail messageDetail, Microsoft.IdentityModel.Tokens.ValidationFailureType validationFailureType, System.Type exceptionType, System.Diagnostics.StackFrame stackFrame, System.DateTime? expirationTime, System.Exception innerException = null) -> void
Microsoft.IdentityModel.Tokens.TokenTypeValidationError
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
Expand All @@ -43,8 +47,10 @@ Microsoft.IdentityModel.Tokens.ValidationResult<TResult>.IsValid.get -> bool
Microsoft.IdentityModel.Tokens.ValidationResult<TResult>.Result.get -> TResult
override Microsoft.IdentityModel.Tokens.AlgorithmValidationError.GetException() -> System.Exception
override Microsoft.IdentityModel.Tokens.SignatureValidationError.GetException() -> System.Exception
override Microsoft.IdentityModel.Tokens.TokenReplayValidationError.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.TokenReplayValidationError.NullParameter(string parameterName, System.Diagnostics.StackFrame stackFrame) -> Microsoft.IdentityModel.Tokens.TokenReplayValidationError
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<string> strings) -> string
static Microsoft.IdentityModel.Tokens.ValidationError.GetCurrentStackFrame(string filePath = "", int lineNumber = 0, int skipFrames = 1) -> System.Diagnostics.StackFrame
Expand All @@ -57,5 +63,6 @@ static readonly Microsoft.IdentityModel.Tokens.ValidationFailureType.SignatureAl
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.TokenReplayValidatorThrew -> Microsoft.IdentityModel.Tokens.ValidationFailureType
static readonly Microsoft.IdentityModel.Tokens.ValidationFailureType.TokenTypeValidatorThrew -> Microsoft.IdentityModel.Tokens.ValidationFailureType
static readonly Microsoft.IdentityModel.Tokens.ValidationFailureType.XmlValidationFailed -> Microsoft.IdentityModel.Tokens.ValidationFailureType
1 change: 1 addition & 0 deletions src/Microsoft.IdentityModel.Tokens/LogMessages.cs
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ internal static class LogMessages
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.";
public const string IDX10276 = "IDX10276: TokenReplayValidationDelegate threw an exception, see inner exception.";

// 10500 - SignatureValidation
public const string IDX10500 = "IDX10500: Signature validation failed. No security keys were provided to validate the signature.";
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
// 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 TokenReplayValidationError : ValidationError
{
internal TokenReplayValidationError(
MessageDetail messageDetail,
ValidationFailureType validationFailureType,
Type exceptionType,
StackFrame stackFrame,
DateTime? expirationTime,
Exception? innerException = null)
: base(messageDetail, validationFailureType, exceptionType, stackFrame, innerException)
{
ExpirationTime = expirationTime;
}

internal override Exception GetException()
{
if (ExceptionType == typeof(SecurityTokenReplayDetectedException))
{
SecurityTokenReplayDetectedException exception = new(MessageDetail.Message, InnerException);
exception.SetValidationError(this);

return exception;
}
else if (ExceptionType == typeof(SecurityTokenReplayAddFailedException))
{
SecurityTokenReplayAddFailedException exception = new(MessageDetail.Message, InnerException);
exception.SetValidationError(this);

return exception;
}

return base.GetException();
}

internal static new TokenReplayValidationError NullParameter(string parameterName, StackFrame stackFrame) => new(
MessageDetail.NullParameter(parameterName),
ValidationFailureType.NullArgument,
typeof(SecurityTokenArgumentNullException),
stackFrame,
null);

protected DateTime? ExpirationTime { get; }
}
}
#nullable restore
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ private class XmlValidationFailure : ValidationFailureType { internal XmlValidat
public static readonly ValidationFailureType AlgorithmValidatorThrew = new AlgorithmValidationFailure("AlgorithmValidatorThrew");

/// <summary>
/// Defines a type that represents that a token is invalid.
/// Defines a type that represents the fact that the issuer validation delegate threw an exception.
/// </summary>
public static readonly ValidationFailureType IssuerValidatorThrew = new IssuerValidatorFailure("IssuerValidatorThrew");
private class IssuerValidatorFailure : ValidationFailureType { internal IssuerValidatorFailure(string name) : base(name) { } }
Expand All @@ -145,6 +145,11 @@ private class IssuerValidatorFailure : ValidationFailureType { internal IssuerVa
/// </summary>
public static readonly ValidationFailureType SignatureValidatorThrew = new SignatureValidationFailure("SignatureValidatorThrew");

/// <summary>
/// Defines a type that represents the fact that the token replay validation delegate threw an exception.
/// </summary>
public static readonly ValidationFailureType TokenReplayValidatorThrew = new TokenReplayValidationFailure("TokenReplayValidatorThrew");

/// <summary>
/// Defines a type that represents the fact that the token type validation delegate threw an exception.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
// Licensed under the MIT License.

using System;
using System.Diagnostics;

namespace Microsoft.IdentityModel.Tokens
{
Expand Down Expand Up @@ -43,44 +42,47 @@ public static partial class Validators
#pragma warning restore CA1801 // Review unused parameters
{
if (string.IsNullOrWhiteSpace(securityToken))
return ValidationError.NullParameter(
return TokenReplayValidationError.NullParameter(
nameof(securityToken),
new StackFrame(true));
ValidationError.GetCurrentStackFrame());

if (validationParameters == null)
return ValidationError.NullParameter(
return TokenReplayValidationError.NullParameter(
nameof(validationParameters),
new StackFrame(true));
ValidationError.GetCurrentStackFrame());

// check if token if replay cache is set, then there must be an expiration time.
if (validationParameters.TokenReplayCache != null)
{
if (expirationTime == null)
return new ValidationError(
return new TokenReplayValidationError(
new MessageDetail(
LogMessages.IDX10227,
securityToken),
ValidationFailureType.TokenReplayValidationFailed,
typeof(SecurityTokenNoExpirationException),
new StackFrame(true));
ValidationError.GetCurrentStackFrame(),
expirationTime);

if (validationParameters.TokenReplayCache.TryFind(securityToken))
return new ValidationError(
return new TokenReplayValidationError(
new MessageDetail(
LogMessages.IDX10228,
securityToken),
ValidationFailureType.TokenReplayValidationFailed,
typeof(SecurityTokenReplayDetectedException),
new StackFrame(true));
ValidationError.GetCurrentStackFrame(),
expirationTime);

if (!validationParameters.TokenReplayCache.TryAdd(securityToken, expirationTime.Value))
return new ValidationError(
return new TokenReplayValidationError(
new MessageDetail(
LogMessages.IDX10229,
securityToken),
ValidationFailureType.TokenReplayValidationFailed,
typeof(SecurityTokenReplayAddFailedException),
new StackFrame(true));
ValidationError.GetCurrentStackFrame(),
expirationTime);
}

// if it reaches here, that means no token replay is detected.
Expand Down
Loading

0 comments on commit 8fd166c

Please sign in to comment.