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

Extensibility tests: Token Replay - JWT, SAML and SAML2 #3032

Merged
merged 8 commits into from
Nov 25, 2024
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 @@ -78,14 +78,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);

if (!tokenReplayValidationResult.IsValid)
return tokenReplayValidationResult.UnwrapError().AddCurrentStackFrame();
ValidationResult<DateTime?> tokenReplayValidationResult;

try
{
tokenReplayValidationResult = validationParameters.TokenReplayValidator(
brentschmaltz marked this conversation as resolved.
Show resolved Hide resolved
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);
}
}

ValidationResult<SecurityKey> signatureValidationResult = ValidateSignature(samlToken, validationParameters, callContext);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,14 +82,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
16 changes: 7 additions & 9 deletions src/Microsoft.IdentityModel.Tokens/InternalAPI.Unshipped.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ const Microsoft.IdentityModel.Tokens.LogMessages.IDX10002 = "IDX10002: Unknown e
const Microsoft.IdentityModel.Tokens.LogMessages.IDX10268 = "IDX10268: Unable to validate audience, validationParameters.ValidAudiences.Count == 0." -> string
const Microsoft.IdentityModel.Tokens.LogMessages.IDX10269 = "IDX10269: IssuerValidationDelegate threw an exception, see inner exception." -> string
const Microsoft.IdentityModel.Tokens.LogMessages.IDX10271 = "IDX10271: LifetimeValidationDelegate 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
brentschmaltz marked this conversation as resolved.
Show resolved Hide resolved
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 @@ -12,17 +12,16 @@ Microsoft.IdentityModel.Tokens.AudienceValidationError.TokenAudiences.get -> Sys
Microsoft.IdentityModel.Tokens.AudienceValidationError.TokenAudiences.set -> void
Microsoft.IdentityModel.Tokens.AudienceValidationError.ValidAudiences.get -> System.Collections.Generic.IList<string>
Microsoft.IdentityModel.Tokens.AudienceValidationError.ValidAudiences.set -> void
Microsoft.IdentityModel.Tokens.IssuerSigningKeyValidationError
Microsoft.IdentityModel.Tokens.IssuerSigningKeyValidationError.InvalidSigningKey.get -> Microsoft.IdentityModel.Tokens.SecurityKey
Microsoft.IdentityModel.Tokens.IssuerSigningKeyValidationError.InvalidSigningKey.set -> void
Microsoft.IdentityModel.Tokens.IssuerSigningKeyValidationError.IssuerSigningKeyValidationError(Microsoft.IdentityModel.Tokens.MessageDetail messageDetail, System.Type exceptionType, System.Diagnostics.StackFrame stackFrame, Microsoft.IdentityModel.Tokens.SecurityKey invalidSigningKey, Microsoft.IdentityModel.Tokens.ValidationFailureType failureType = null, System.Exception innerException = null) -> void
Microsoft.IdentityModel.Tokens.IssuerValidationError.InvalidIssuer.get -> string
Microsoft.IdentityModel.Tokens.IssuerValidationError.IssuerValidationError(Microsoft.IdentityModel.Tokens.MessageDetail messageDetail, Microsoft.IdentityModel.Tokens.ValidationFailureType validationFailureType, System.Type exceptionType, System.Diagnostics.StackFrame stackFrame, string invalidIssuer, System.Exception innerException = null) -> void
Microsoft.IdentityModel.Tokens.IssuerValidationSource.IssuerMatchedConfiguration = 1 -> Microsoft.IdentityModel.Tokens.IssuerValidationSource
Microsoft.IdentityModel.Tokens.IssuerValidationSource.IssuerMatchedValidationParameters = 2 -> Microsoft.IdentityModel.Tokens.IssuerValidationSource
Microsoft.IdentityModel.Tokens.LifetimeValidationError.Expires.get -> System.DateTime?
Microsoft.IdentityModel.Tokens.LifetimeValidationError.LifetimeValidationError(Microsoft.IdentityModel.Tokens.MessageDetail messageDetail, Microsoft.IdentityModel.Tokens.ValidationFailureType validationFailureType, System.Type exceptionType, System.Diagnostics.StackFrame stackFrame, System.DateTime? notBefore, System.DateTime? expires, System.Exception innerException = null) -> void
Microsoft.IdentityModel.Tokens.LifetimeValidationError.NotBefore.get -> System.DateTime?
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.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
Microsoft.IdentityModel.Tokens.TokenTypeValidationError._invalidTokenType -> string
Expand All @@ -37,17 +36,16 @@ Microsoft.IdentityModel.Tokens.ValidationResult<TResult>.Error.get -> Microsoft.
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.IssuerSigningKeyValidationError.GetException() -> System.Exception
override Microsoft.IdentityModel.Tokens.TokenReplayValidationError.GetException() -> System.Exception
override Microsoft.IdentityModel.Tokens.TokenTypeValidationError.GetException() -> System.Exception
static Microsoft.IdentityModel.Tokens.IssuerSigningKeyValidationError.NullParameter(string parameterName, System.Diagnostics.StackFrame stackFrame) -> Microsoft.IdentityModel.Tokens.IssuerSigningKeyValidationError
static Microsoft.IdentityModel.Tokens.TokenReplayValidationError.NullParameter(string parameterName, System.Diagnostics.StackFrame stackFrame) -> Microsoft.IdentityModel.Tokens.TokenReplayValidationError
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
static readonly Microsoft.IdentityModel.Tokens.ValidationFailureType.IssuerValidatorThrew -> Microsoft.IdentityModel.Tokens.ValidationFailureType
static readonly Microsoft.IdentityModel.Tokens.ValidationFailureType.LifetimeValidatorThrew -> Microsoft.IdentityModel.Tokens.ValidationFailureType
static readonly Microsoft.IdentityModel.Tokens.ValidationFailureType.NoTokenAudiencesProvided -> Microsoft.IdentityModel.Tokens.ValidationFailureType
static readonly Microsoft.IdentityModel.Tokens.ValidationFailureType.NoValidationParameterAudiencesProvided -> Microsoft.IdentityModel.Tokens.ValidationFailureType
static readonly Microsoft.IdentityModel.Tokens.ValidationFailureType.SignatureAlgorithmValidationFailed -> 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.TokenTypeValidatorThrew -> Microsoft.IdentityModel.Tokens.ValidationFailureType
static readonly Microsoft.IdentityModel.Tokens.ValidationFailureType.TokenReplayValidatorThrew -> Microsoft.IdentityModel.Tokens.ValidationFailureType
static readonly Microsoft.IdentityModel.Tokens.ValidationFailureType.XmlValidationFailed -> Microsoft.IdentityModel.Tokens.ValidationFailureType
2 changes: 1 addition & 1 deletion src/Microsoft.IdentityModel.Tokens/LogMessages.cs
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ internal static class LogMessages
public const string IDX10267 = "IDX10267: '{0}' has been called by a derived class '{1}' which has not implemented this method. For this call graph to succeed, '{1}' will need to implement '{0}'.";
public const string IDX10268 = "IDX10268: Unable to validate audience, validationParameters.ValidAudiences.Count == 0.";
public const string IDX10269 = "IDX10269: IssuerValidationDelegate 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
brentschmaltz marked this conversation as resolved.
Show resolved Hide resolved
{
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 @@ -134,5 +134,10 @@ private class XmlValidationFailure : ValidationFailureType { internal XmlValidat
/// </summary>
public static readonly ValidationFailureType IssuerValidatorThrew = new IssuerValidatorFailure("IssuerValidatorThrew");
private class IssuerValidatorFailure : ValidationFailureType { internal IssuerValidatorFailure(string name) : base(name) { } }

/// <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");
}
}
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
Loading