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

SAML and SAML2 new model validation: Token Replay #2994

Merged
merged 7 commits into from
Nov 22, 2024
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
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();
iNinja marked this conversation as resolved.
Show resolved Hide resolved
}

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
Loading