Skip to content

Commit

Permalink
Merge branch 'dev' into iinglese/new-model-validation-for-saml-algorithm
Browse files Browse the repository at this point in the history
  • Loading branch information
iNinja authored Nov 8, 2024
2 parents 7129fe6 + dfae4af commit 4e95142
Show file tree
Hide file tree
Showing 19 changed files with 511 additions and 50 deletions.
6 changes: 3 additions & 3 deletions build/cgmanifest.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"Registrations": [
"registrations": [
{
"component": {
"type": "git",
Expand All @@ -10,6 +10,6 @@
}
}
],
"Version": 1,
"version": 1,
"$schema": "https://json.schemastore.org/component-detection-manifest.json"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
<ProjectReference Include="..\Microsoft.IdentityModel.Tokens\Microsoft.IdentityModel.Tokens.csproj" />
</ItemGroup>

<ItemGroup>
<ItemGroup Condition="!$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', 'net8.0'))">
<PackageReference Include="Microsoft.Bcl.TimeProvider" Version="$(MicrosoftBclTimeProviderVersion)" />
</ItemGroup>

Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
const Microsoft.IdentityModel.Tokens.Saml.LogMessages.IDX11402 = "IDX11402: Unable to read SamlSecurityToken. Exception thrown: '{0}'." -> string
const Microsoft.IdentityModel.Tokens.Saml2.LogMessages.IDX13003 = "IDX13003: Unable to read Saml2SecurityToken. Exception thrown: '{0}'." -> string
Microsoft.IdentityModel.Tokens.Saml.SamlSecurityTokenHandler.StackFrames
Microsoft.IdentityModel.Tokens.Saml.SamlSecurityTokenHandler.ValidatedConditions
Microsoft.IdentityModel.Tokens.Saml.SamlSecurityTokenHandler.ValidatedConditions.ValidatedAudience.get -> string
Expand All @@ -7,13 +9,22 @@ Microsoft.IdentityModel.Tokens.Saml.SamlSecurityTokenHandler.ValidatedConditions
Microsoft.IdentityModel.Tokens.Saml.SamlSecurityTokenHandler.ValidatedConditions.ValidatedLifetime.get -> Microsoft.IdentityModel.Tokens.ValidatedLifetime?
Microsoft.IdentityModel.Tokens.Saml.SamlSecurityTokenHandler.ValidatedConditions.ValidatedLifetime.set -> void
Microsoft.IdentityModel.Tokens.Saml.SamlSecurityTokenHandler.ValidateTokenAsync(Microsoft.IdentityModel.Tokens.Saml.SamlSecurityToken samlToken, Microsoft.IdentityModel.Tokens.ValidationParameters validationParameters, Microsoft.IdentityModel.Tokens.CallContext callContext, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task<Microsoft.IdentityModel.Tokens.ValidationResult<Microsoft.IdentityModel.Tokens.ValidatedToken>>
Microsoft.IdentityModel.Tokens.Saml.SamlSecurityTokenHandler.ValidateTokenAsync(string token, Microsoft.IdentityModel.Tokens.ValidationParameters validationParameters, Microsoft.IdentityModel.Tokens.CallContext callContext, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task<Microsoft.IdentityModel.Tokens.ValidationResult<Microsoft.IdentityModel.Tokens.ValidatedToken>>
Microsoft.IdentityModel.Tokens.Saml.SamlValidationError
Microsoft.IdentityModel.Tokens.Saml.SamlValidationError.SamlValidationError(Microsoft.IdentityModel.Tokens.MessageDetail MessageDetail, Microsoft.IdentityModel.Tokens.ValidationFailureType failureType, System.Type exceptionType, System.Diagnostics.StackFrame stackFrame) -> void
Microsoft.IdentityModel.Tokens.Saml.SamlValidationError.SamlValidationError(Microsoft.IdentityModel.Tokens.MessageDetail messageDetail, Microsoft.IdentityModel.Tokens.ValidationFailureType failureType, System.Type exceptionType, System.Diagnostics.StackFrame stackFrame, System.Exception innerException) -> void
Microsoft.IdentityModel.Tokens.Saml2.Saml2SecurityTokenHandler.StackFrames
Microsoft.IdentityModel.Tokens.Saml2.Saml2SecurityTokenHandler.ValidateTokenAsync(Microsoft.IdentityModel.Tokens.Saml2.Saml2SecurityToken samlToken, Microsoft.IdentityModel.Tokens.ValidationParameters validationParameters, Microsoft.IdentityModel.Tokens.CallContext callContext, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task<Microsoft.IdentityModel.Tokens.ValidationResult<Microsoft.IdentityModel.Tokens.ValidatedToken>>
Microsoft.IdentityModel.Tokens.Saml2.Saml2SecurityTokenHandler.ValidateTokenAsync(string token, Microsoft.IdentityModel.Tokens.ValidationParameters validationParameters, Microsoft.IdentityModel.Tokens.CallContext callContext, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task<Microsoft.IdentityModel.Tokens.ValidationResult<Microsoft.IdentityModel.Tokens.ValidatedToken>>
Microsoft.IdentityModel.Tokens.Saml2.Saml2ValidationError
Microsoft.IdentityModel.Tokens.Saml2.Saml2ValidationError.Saml2ValidationError(Microsoft.IdentityModel.Tokens.MessageDetail MessageDetail, Microsoft.IdentityModel.Tokens.ValidationFailureType failureType, System.Type exceptionType, System.Diagnostics.StackFrame stackFrame) -> void
Microsoft.IdentityModel.Tokens.Saml2.Saml2ValidationError.Saml2ValidationError(Microsoft.IdentityModel.Tokens.MessageDetail messageDetail, Microsoft.IdentityModel.Tokens.ValidationFailureType failureType, System.Type exceptionType, System.Diagnostics.StackFrame stackFrame, System.Exception innerException) -> 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.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>
static Microsoft.IdentityModel.Tokens.Saml.SamlTokenUtilities.PopulateValidationParametersWithCurrentConfigurationAsync(Microsoft.IdentityModel.Tokens.ValidationParameters validationParameters, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task<Microsoft.IdentityModel.Tokens.ValidationParameters>
Microsoft.IdentityModel.Tokens.Saml2.SamlSecurityTokenHandler.ValidateTokenAsync(SamlSecurityToken samlToken, Microsoft.IdentityModel.Tokens.ValidationParameters validationParameters, Microsoft.IdentityModel.Tokens.CallContext callContext, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task<Microsoft.IdentityModel.Tokens.ValidationResult<Microsoft.IdentityModel.Tokens.ValidatedToken>>
static Microsoft.IdentityModel.Tokens.Saml.SamlSecurityTokenHandler.StackFrames.AssertionConditionsNull -> System.Diagnostics.StackFrame
static Microsoft.IdentityModel.Tokens.Saml.SamlSecurityTokenHandler.StackFrames.AssertionConditionsValidationFailed -> System.Diagnostics.StackFrame
static Microsoft.IdentityModel.Tokens.Saml.SamlSecurityTokenHandler.StackFrames.AssertionNull -> System.Diagnostics.StackFrame
Expand All @@ -34,4 +45,6 @@ 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>
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using System;
using System.Diagnostics;

namespace Microsoft.IdentityModel.Tokens.Saml
{
internal class SamlValidationError : ValidationError
{
internal SamlValidationError(MessageDetail messageDetail, ValidationFailureType failureType, Type exceptionType, StackFrame stackFrame, Exception innerException) : base(messageDetail, failureType, exceptionType, stackFrame, innerException)
{
}

internal override Exception GetException()
{
if (ExceptionType == typeof(SamlSecurityTokenReadException))
{
var exception = new SamlSecurityTokenReadException(MessageDetail.Message, InnerException);
return exception;
}

return base.GetException();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ internal static class LogMessages
// SecurityTokenHandler messages
internal const string IDX11400 = "IDX11400: The '{0}', can only process SecurityTokens of type: '{1}'. The SecurityToken received is of type: '{2}'.";
internal const string IDX11401 = "IDX11401: Unable to validate token. TokenValidationParameters.RequireAudience is true but no AudienceRestrictions were found in the inbound token.";
internal const string IDX11402 = "IDX11402: Unable to read SamlSecurityToken. Exception thrown: '{0}'.";

// signature creation / validation
internal const string IDX11312 = "IDX11312: Unable to validate token. A SamlSamlAttributeStatement can only have one SamlAttribute of type 'Actor'. This special SamlAttribute is used in delegation scenarios.";
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using System;
using System.Text;
using System.Xml;
using Microsoft.IdentityModel.Logging;
using TokenLogMessages = Microsoft.IdentityModel.Tokens.LogMessages;

namespace Microsoft.IdentityModel.Tokens.Saml
{
public partial class SamlSecurityTokenHandler : SecurityTokenHandler
{
/// <summary>
/// Converts a string into an instance of <see cref="SamlSecurityToken"/>.
/// </summary>
/// <param name="token">a Saml token as a string.</param>
/// <param name="callContext">An opaque context used to store work when working with authentication artifacts.</param>
/// <returns>A <see cref="SamlSecurityToken"/></returns>
/// <exception cref="ArgumentNullException">If <paramref name="token"/> is null or empty.</exception>
/// <exception cref="ArgumentException">If 'token.Length' is greater than <see cref="TokenHandler.MaximumTokenSizeInBytes"/>.</exception>
internal virtual ValidationResult<SamlSecurityToken> ReadSamlToken(string token, CallContext callContext)
{
if (string.IsNullOrEmpty(token))
return ValidationError.NullParameter(nameof(token), ValidationError.GetCurrentStackFrame());

if (token.Length > MaximumTokenSizeInBytes)
return new ValidationError(
new MessageDetail(
TokenLogMessages.IDX10209,
LogHelper.MarkAsNonPII(token.Length),
LogHelper.MarkAsNonPII(MaximumTokenSizeInBytes)),
ValidationFailureType.TokenExceedsMaximumSize,
typeof(ArgumentOutOfRangeException),
ValidationError.GetCurrentStackFrame());

try
{
using (var reader = XmlDictionaryReader.CreateTextReader(Encoding.UTF8.GetBytes(token), XmlDictionaryReaderQuotas.Max))
{
return ReadSamlToken(reader);
}
}
#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 SamlValidationError(
new MessageDetail(LogMessages.IDX11402, ex.Message),
ValidationFailureType.TokenReadingFailed,
typeof(SamlSecurityTokenReadException),
ValidationError.GetCurrentStackFrame(),
ex);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -42,11 +42,10 @@ internal static ValidationResult<SecurityKey> ValidateSignature(
new MessageDetail(
TokenLogMessages.IDX10504,
samlToken.Assertion.CanonicalString),
ValidationFailureType.SignatureValidationFailed,
ValidationFailureType.TokenIsNotSigned,
typeof(SecurityTokenValidationException),
ValidationError.GetCurrentStackFrame());

IList<SecurityKey>? keys = null;
SecurityKey? resolvedKey = null;
bool keyMatched = false;

Expand All @@ -65,10 +64,7 @@ internal static ValidationResult<SecurityKey> ValidateSignature(
resolvedKey = SamlTokenUtilities.ResolveTokenSigningKey(samlToken.Assertion.Signature.KeyInfo, validationParameters);
}

bool canMatchKey = samlToken.Assertion.Signature.KeyInfo != null;
List<ValidationError>? errors = null;
ValidationError? error = null;
StringBuilder? keysAttempted = null;

if (resolvedKey is not null)
{
Expand All @@ -79,19 +75,21 @@ internal static ValidationResult<SecurityKey> ValidateSignature(

error = result.UnwrapError();
}
else
{
if (validationParameters.TryAllIssuerSigningKeys)
keys = validationParameters.IssuerSigningKeys;
}

if (keys is not null)
bool canMatchKey = samlToken.Assertion.Signature.KeyInfo != null;
List<ValidationError>? errors = null;
StringBuilder? keysAttempted = null;

if (!keyMatched && validationParameters.TryAllIssuerSigningKeys && validationParameters.IssuerSigningKeys is not null)
{
// Control reaches here only if the key could not be resolved and TryAllIssuerSigningKeys is set to true.
// We try all the keys in the list and return the first valid key. This is the degenerate case.
for (int i = 0; i < keys.Count; i++)
for (int i = 0; i < validationParameters.IssuerSigningKeys.Count; i++)
{
SecurityKey key = keys[i];
SecurityKey key = validationParameters.IssuerSigningKeys[i];
if (key is null)
continue;

var result = ValidateSignatureUsingKey(key, samlToken, validationParameters, callContext);
if (result.IsValid)
return result;
Expand All @@ -110,7 +108,7 @@ internal static ValidationResult<SecurityKey> ValidateSignature(
TokenLogMessages.IDX10514,
keysAttempted?.ToString(),
samlToken.Assertion.Signature.KeyInfo,
GetErrorStrings(error, errors),
GetErrorString(error, errors),
samlToken),
ValidationFailureType.SignatureValidationFailed,
typeof(SecurityTokenInvalidSignatureException),
Expand All @@ -127,7 +125,7 @@ internal static ValidationResult<SecurityKey> ValidateSignature(
new MessageDetail(
TokenLogMessages.IDX10512,
keysAttemptedString,
GetErrorStrings(error, errors),
GetErrorString(error, errors),
samlToken),
ValidationFailureType.SignatureValidationFailed,
typeof(SecurityTokenSignatureKeyNotFoundException),
Expand Down Expand Up @@ -156,9 +154,9 @@ private static ValidationResult<SecurityKey> ValidateSignatureUsingKey(SecurityK
else
{
var validationError = samlToken.Assertion.Signature.Verify(
key,
validationParameters.CryptoProviderFactory ?? key.CryptoProviderFactory,
callContext);
key,
validationParameters.CryptoProviderFactory ?? key.CryptoProviderFactory,
callContext);

if (validationError is null)
{
Expand All @@ -173,23 +171,23 @@ private static ValidationResult<SecurityKey> ValidateSignatureUsingKey(SecurityK
}
}

private static string GetErrorStrings(ValidationError? error, List<ValidationError>? errors)
private static string GetErrorString(ValidationError? error, List<ValidationError>? errorList)
{
// This method is called if there are errors in the signature validation process.
// This check is there to account for the optional parameter.
if (error is not null)
return error.MessageDetail.Message;

if (errors is null)
if (errorList is null)
return string.Empty;

if (errors.Count == 1)
return errors[0].MessageDetail.Message;
if (errorList.Count == 1)
return errorList[0].MessageDetail.Message;

StringBuilder sb = new();
for (int i = 0; i < errors.Count; i++)
for (int i = 0; i < errorList.Count; i++)
{
sb.AppendLine(errors[i].MessageDetail.Message);
sb.AppendLine(errorList[i].MessageDetail.Message);
}

return sb.ToString();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,26 @@ namespace Microsoft.IdentityModel.Tokens.Saml
/// </summary>
public partial class SamlSecurityTokenHandler : SecurityTokenHandler
{
#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously
internal async Task<ValidationResult<ValidatedToken>> ValidateTokenAsync(
#pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously
string token,
ValidationParameters validationParameters,
CallContext callContext,
CancellationToken cancellationToken)
{
if (token is null)
return ValidationError.NullParameter(nameof(token), ValidationError.GetCurrentStackFrame());

if (validationParameters is null)
return ValidationError.NullParameter(nameof(validationParameters), ValidationError.GetCurrentStackFrame());

var tokenReadingResult = ReadSamlToken(token, callContext);
if (!tokenReadingResult.IsValid)
return tokenReadingResult.UnwrapError().AddCurrentStackFrame();

return await ValidateTokenAsync(tokenReadingResult.UnwrapResult(), validationParameters, callContext, cancellationToken).ConfigureAwait(false);
}

internal async Task<ValidationResult<ValidatedToken>> ValidateTokenAsync(
SamlSecurityToken samlToken,
ValidationParameters validationParameters,
CallContext callContext,
Expand Down
Loading

0 comments on commit 4e95142

Please sign in to comment.