Skip to content

Commit

Permalink
SAML and SAML2 new model validation: Token Replay (#2994)
Browse files Browse the repository at this point in the history
* Updated SAML and SAML2 token replay validation and OneTimeCondition to match existing behaviour. Added comparison tests between new validation model and the legacy one

* Commented log message no longer in use given we leave the lifetime validation for after the token is created

* Fixed test failure due to wrong merge
  • Loading branch information
iNinja authored Nov 22, 2024
1 parent 90173bf commit 6150749
Show file tree
Hide file tree
Showing 8 changed files with 439 additions and 47 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ Microsoft.IdentityModel.Tokens.Saml2.Saml2ValidationError
Microsoft.IdentityModel.Tokens.Saml2.Saml2ValidationError.Saml2ValidationError(Microsoft.IdentityModel.Tokens.MessageDetail messageDetail, Microsoft.IdentityModel.Tokens.ValidationFailureType validationFailureType, System.Type exceptionType, System.Diagnostics.StackFrame stackFrame, System.Exception innerException = null) -> void
override Microsoft.IdentityModel.Tokens.Saml.SamlValidationError.GetException() -> System.Exception
override Microsoft.IdentityModel.Tokens.Saml2.Saml2ValidationError.GetException() -> System.Exception
static Microsoft.IdentityModel.Tokens.Saml.SamlSecurityTokenHandler.StackFrames.IssuerSigningKeyValidationFailed -> System.Diagnostics.StackFrame
static Microsoft.IdentityModel.Tokens.Saml.SamlSecurityTokenHandler.StackFrames.IssuerValidationFailed -> System.Diagnostics.StackFrame
static Microsoft.IdentityModel.Tokens.Saml.SamlSecurityTokenHandler.StackFrames.SignatureValidationFailed -> System.Diagnostics.StackFrame
static Microsoft.IdentityModel.Tokens.Saml.SamlSecurityTokenHandler.ValidateSignature(Microsoft.IdentityModel.Tokens.Saml.SamlSecurityToken samlToken, Microsoft.IdentityModel.Tokens.ValidationParameters validationParameters, Microsoft.IdentityModel.Tokens.CallContext callContext) -> Microsoft.IdentityModel.Tokens.ValidationResult<Microsoft.IdentityModel.Tokens.SecurityKey>
Expand All @@ -45,6 +44,7 @@ static Microsoft.IdentityModel.Tokens.Saml2.Saml2SecurityTokenHandler.StackFrame
static Microsoft.IdentityModel.Tokens.Saml2.Saml2SecurityTokenHandler.StackFrames.TokenNull -> System.Diagnostics.StackFrame
static Microsoft.IdentityModel.Tokens.Saml2.Saml2SecurityTokenHandler.StackFrames.TokenValidationParametersNull -> System.Diagnostics.StackFrame
static Microsoft.IdentityModel.Tokens.Saml2.Saml2SecurityTokenHandler.ValidateSignature(Microsoft.IdentityModel.Tokens.Saml2.Saml2SecurityToken samlToken, Microsoft.IdentityModel.Tokens.ValidationParameters validationParameters, Microsoft.IdentityModel.Tokens.CallContext callContext) -> Microsoft.IdentityModel.Tokens.ValidationResult<Microsoft.IdentityModel.Tokens.SecurityKey>
virtual Microsoft.IdentityModel.Tokens.Saml2.Saml2SecurityTokenHandler.ReadSaml2Token(string token, Microsoft.IdentityModel.Tokens.CallContext callContext) -> Microsoft.IdentityModel.Tokens.ValidationResult<Microsoft.IdentityModel.Tokens.Saml2.Saml2SecurityToken>
virtual Microsoft.IdentityModel.Tokens.Saml.SamlSecurityTokenHandler.ReadSamlToken(string token, Microsoft.IdentityModel.Tokens.CallContext callContext) -> Microsoft.IdentityModel.Tokens.ValidationResult<Microsoft.IdentityModel.Tokens.Saml.SamlSecurityToken>
virtual Microsoft.IdentityModel.Tokens.Saml.SamlSecurityTokenHandler.ValidateConditions(Microsoft.IdentityModel.Tokens.Saml.SamlSecurityToken samlToken, Microsoft.IdentityModel.Tokens.ValidationParameters validationParameters, Microsoft.IdentityModel.Tokens.CallContext callContext) -> Microsoft.IdentityModel.Tokens.ValidationResult<Microsoft.IdentityModel.Tokens.Saml.SamlSecurityTokenHandler.ValidatedConditions>
virtual Microsoft.IdentityModel.Tokens.Saml2.Saml2SecurityTokenHandler.ReadSaml2Token(string token, Microsoft.IdentityModel.Tokens.CallContext callContext) -> Microsoft.IdentityModel.Tokens.ValidationResult<Microsoft.IdentityModel.Tokens.Saml2.Saml2SecurityToken>
virtual Microsoft.IdentityModel.Tokens.Saml2.Saml2SecurityTokenHandler.ValidateOneTimeUseCondition(Microsoft.IdentityModel.Tokens.Saml2.Saml2SecurityToken samlToken, Microsoft.IdentityModel.Tokens.ValidationParameters validationParameters, Microsoft.IdentityModel.Tokens.CallContext callContext) -> Microsoft.IdentityModel.Tokens.ValidationError
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using System;
using System.Diagnostics;
using System.Linq;
using System.Threading;
Expand Down Expand Up @@ -60,10 +61,7 @@ internal async Task<ValidationResult<ValidatedToken>> ValidateTokenAsync(
ValidationResult<ValidatedConditions> conditionsResult = ValidateConditions(samlToken, validationParameters, callContext);

if (!conditionsResult.IsValid)
{
StackFrames.AssertionConditionsValidationFailed ??= new StackFrame(true);
return conditionsResult.UnwrapError().AddStackFrame(StackFrames.AssertionConditionsValidationFailed);
}
return conditionsResult.UnwrapError().AddCurrentStackFrame();

ValidationResult<ValidatedIssuer> issuerValidationResult = await validationParameters.IssuerValidatorAsync(
samlToken.Issuer,
Expand All @@ -78,6 +76,18 @@ internal async Task<ValidationResult<ValidatedToken>> ValidateTokenAsync(
return issuerValidationResult.UnwrapError().AddStackFrame(StackFrames.IssuerValidationFailed);
}

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<SecurityKey> signatureValidationResult = ValidateSignature(samlToken, validationParameters, callContext);

if (!signatureValidationResult.IsValid)
Expand All @@ -94,10 +104,7 @@ internal async Task<ValidationResult<ValidatedToken>> ValidateTokenAsync(
callContext);

if (!issuerSigningKeyValidationResult.IsValid)
{
StackFrames.IssuerSigningKeyValidationFailed ??= new StackFrame(true);
return issuerSigningKeyValidationResult.UnwrapError().AddStackFrame(StackFrames.IssuerSigningKeyValidationFailed);
}
return issuerSigningKeyValidationResult.UnwrapError().AddCurrentStackFrame();

return new ValidatedToken(samlToken, this, validationParameters);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ internal static class StackFrames
internal static StackFrame? TokenNull;
internal static StackFrame? TokenValidationParametersNull;
internal static StackFrame? IssuerValidationFailed;
internal static StackFrame? IssuerSigningKeyValidationFailed;
internal static StackFrame? SignatureValidationFailed;

// Stack frames from ValidateConditions
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ internal static class LogMessages
internal const string IDX13511 = "IDX13511: The Saml2SecurityToken cannot be validated because the Assertion specifies a ProxyRestriction condition.Enforcement of the ProxyRestriction condition is not supported by default. To customize the enforcement of Saml2Conditions, extend Saml2SecurityTokenHandler and override ValidateConditions.";
internal const string IDX13512 = "IDX13512: Unable to validate token. A Saml2SamlAttributeStatement can only have one Saml2Attribute of type 'Actor'. This special Saml2Attribute is used in delegation scenarios.";
internal const string IDX13513 = "IDX13513: NotBefore '{0}', is after NotOnOrAfter '{1}'.";
internal const string IDX13514 = "IDX13514: NotOnOrAfter '{0}', is before NotBefore '{1}'.";
//internal const string IDX13514 = "IDX13514: NotOnOrAfter '{0}', is before NotBefore '{1}'.";
internal const string IDX13515 = "IDX13515: SamlId value threw on XmlConvert.VerifyNCName. value: '{0}'";
internal const string IDX13516 = "IDX13516: A Saml2Statement of type: '{0}' was found when ProcessingStatements and creating the ClaimsIdentity. These claims have been skipped. If you need to process this Statement, you will need to derive a custom Saml2SecurityTokenHandler and override ProcessStatements.";

Expand Down
14 changes: 1 addition & 13 deletions src/Microsoft.IdentityModel.Tokens.Saml/Saml2/Saml2Conditions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -78,19 +78,7 @@ public DateTime? NotBefore
public DateTime? NotOnOrAfter
{
get { return _notOnOrAfter; }
set
{
value = DateTimeUtil.ToUniversalTime(value);

//This should not be checked here, instead fail during validation of the token. Will remove this code once we release new validation model bug #2905
/* if (value != null && NotBefore.HasValue)
{
if (value.Value <= NotBefore.Value)
throw LogExceptionMessage(new ArgumentException(FormatInvariant(LogMessages.IDX13514, MarkAsNonPII(value), MarkAsNonPII(NotBefore))));
}*/

_notOnOrAfter = value;
}
set { _notOnOrAfter = DateTimeUtil.ToUniversalTime(value); }
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading;
Expand Down Expand Up @@ -64,10 +65,7 @@ internal async Task<ValidationResult<ValidatedToken>> ValidateTokenAsync(
callContext);

if (!conditionsResult.IsValid)
{
StackFrames.AssertionConditionsValidationFailed ??= new StackFrame(true);
return conditionsResult.UnwrapError().AddStackFrame(StackFrames.AssertionConditionsValidationFailed);
}
return conditionsResult.UnwrapError().AddCurrentStackFrame();

ValidationResult<ValidatedIssuer> validatedIssuerResult = await validationParameters.IssuerValidatorAsync(
samlToken.Issuer,
Expand All @@ -82,6 +80,18 @@ internal async Task<ValidationResult<ValidatedToken>> ValidateTokenAsync(
return validatedIssuerResult.UnwrapError().AddStackFrame(StackFrames.IssuerValidationFailed);
}

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

var signatureValidationResult = ValidateSignature(samlToken, validationParameters, callContext);
if (!signatureValidationResult.IsValid)
{
Expand Down Expand Up @@ -144,33 +154,21 @@ internal virtual ValidationResult<ValidatedConditions> ValidateConditions(

if (samlToken.Assertion.Conditions.OneTimeUse)
{
//ValidateOneTimeUseCondition(samlToken, validationParameters);
// We can keep an overridable method for this, or rely on the TokenReplayValidator delegate.
var oneTimeUseValidationResult = validationParameters.TokenReplayValidator(
samlToken.Assertion.Conditions.NotOnOrAfter,
samlToken.Assertion.CanonicalString,
validationParameters,
callContext);
var oneTimeUseValidationError = ValidateOneTimeUseCondition(samlToken, validationParameters, callContext);

if (!oneTimeUseValidationResult.IsValid)
{
StackFrames.OneTimeUseValidationFailed ??= new StackFrame(true);
return oneTimeUseValidationResult.UnwrapError().AddStackFrame(StackFrames.OneTimeUseValidationFailed);
}
if (oneTimeUseValidationError is not null)
return oneTimeUseValidationError.AddCurrentStackFrame();
}

if (samlToken.Assertion.Conditions.ProxyRestriction != null)
if (samlToken.Assertion.Conditions.ProxyRestriction is not null)
{
//throw LogExceptionMessage(new SecurityTokenValidationException(LogMessages.IDX13511));
var proxyValidationError = ValidateProxyRestriction(
samlToken,
validationParameters,
callContext);

if (proxyValidationError is not null)
{
return proxyValidationError;
}
return proxyValidationError.AddCurrentStackFrame();
}

string? validatedAudience = null;
Expand Down Expand Up @@ -203,7 +201,13 @@ internal virtual ValidationResult<ValidatedConditions> ValidateConditions(
internal virtual ValidationError? ValidateProxyRestriction(Saml2SecurityToken samlToken, ValidationParameters validationParameters, CallContext callContext)
#pragma warning restore CA1801 // Review unused parameters
{
// return an error, or ignore and allow overriding?
return null;
}

#pragma warning disable CA1801 // Review unused parameters
internal virtual ValidationError? ValidateOneTimeUseCondition(Saml2SecurityToken samlToken, ValidationParameters validationParameters, CallContext callContext)
#pragma warning restore CA1801 // Review unused parameters
{
return null;
}
}
Expand Down
Loading

0 comments on commit 6150749

Please sign in to comment.