-
Notifications
You must be signed in to change notification settings - Fork 401
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
SAML new model validation: Signature (#2958)
* Added XmlValidationError. Added ValidationError property to XmlValidationException to provide custom stack traces * Added alternative versions using ValidationParameters to XML signature validations * Added XmlValidationFailure to ValidationFailureType * Added refactored ValidateSignature method to SamlSecurityTokenHandler. Updated ValidateTokenAsync to call ValidateSignature. * Added tests to compare signature validation between the legacy and new path * Re-added API lost in merge to InternalAPI.Unshipped.txt
- Loading branch information
Showing
16 changed files
with
649 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
167 changes: 167 additions & 0 deletions
167
src/Microsoft.IdentityModel.Tokens.Saml/Saml/SamlSecurityTokenHandler.ValidateSignature.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,167 @@ | ||
// Copyright (c) Microsoft Corporation. All rights reserved. | ||
// Licensed under the MIT License. | ||
|
||
using System.Collections.Generic; | ||
using System.Diagnostics; | ||
using System.Text; | ||
using Microsoft.IdentityModel.Xml; | ||
using TokenLogMessages = Microsoft.IdentityModel.Tokens.LogMessages; | ||
|
||
#nullable enable | ||
namespace Microsoft.IdentityModel.Tokens.Saml | ||
{ | ||
public partial class SamlSecurityTokenHandler : SecurityTokenHandler | ||
{ | ||
internal static ValidationResult<SecurityKey> ValidateSignature( | ||
SamlSecurityToken samlToken, | ||
ValidationParameters validationParameters, | ||
#pragma warning disable CA1801 // Review unused parameters | ||
CallContext callContext) | ||
#pragma warning restore CA1801 // Review unused parameters | ||
{ | ||
if (samlToken is null) | ||
{ | ||
return ValidationError.NullParameter( | ||
nameof(samlToken), | ||
new StackFrame(true)); | ||
} | ||
|
||
if (validationParameters is null) | ||
{ | ||
return ValidationError.NullParameter( | ||
nameof(validationParameters), | ||
new StackFrame(true)); | ||
} | ||
|
||
// Delegate is set by the user, we call it and return the result. | ||
if (validationParameters.SignatureValidator is not null) | ||
return validationParameters.SignatureValidator(samlToken, validationParameters, null, callContext); | ||
|
||
// If the user wants to accept unsigned tokens, they must implement the delegate | ||
if (samlToken.Assertion.Signature is null) | ||
return new XmlValidationError( | ||
new MessageDetail( | ||
TokenLogMessages.IDX10504, | ||
samlToken.Assertion.CanonicalString), | ||
ValidationFailureType.SignatureValidationFailed, | ||
typeof(SecurityTokenValidationException), | ||
new StackFrame(true)); | ||
|
||
IList<SecurityKey>? keys = null; | ||
SecurityKey? resolvedKey = null; | ||
bool keyMatched = false; | ||
|
||
if (validationParameters.IssuerSigningKeyResolver is not null) | ||
{ | ||
resolvedKey = validationParameters.IssuerSigningKeyResolver( | ||
samlToken.Assertion.CanonicalString, | ||
samlToken, | ||
samlToken.Assertion.Signature.KeyInfo?.Id, | ||
validationParameters, | ||
null, | ||
callContext); | ||
} | ||
else | ||
{ | ||
resolvedKey = SamlTokenUtilities.ResolveTokenSigningKey(samlToken.Assertion.Signature.KeyInfo, validationParameters); | ||
} | ||
|
||
if (resolvedKey is null) | ||
{ | ||
if (validationParameters.TryAllIssuerSigningKeys) | ||
keys = validationParameters.IssuerSigningKeys; | ||
} | ||
else | ||
{ | ||
keys = [resolvedKey]; | ||
keyMatched = true; | ||
} | ||
|
||
bool canMatchKey = samlToken.Assertion.Signature.KeyInfo != null; | ||
List<ValidationError> errors = new(); | ||
StringBuilder keysAttempted = new(); | ||
|
||
if (keys is not null) | ||
{ | ||
for (int i = 0; i < keys.Count; i++) | ||
{ | ||
SecurityKey key = keys[i]; | ||
ValidationResult<string> algorithmValidationResult = validationParameters.AlgorithmValidator( | ||
samlToken.Assertion.Signature.SignedInfo.SignatureMethod, | ||
key, | ||
samlToken, | ||
validationParameters, | ||
callContext); | ||
|
||
if (!algorithmValidationResult.IsValid) | ||
{ | ||
errors.Add(algorithmValidationResult.UnwrapError()); | ||
} | ||
else | ||
{ | ||
var validationError = samlToken.Assertion.Signature.Verify( | ||
key, | ||
validationParameters.CryptoProviderFactory ?? key.CryptoProviderFactory, | ||
callContext); | ||
|
||
if (validationError is null) | ||
{ | ||
samlToken.SigningKey = key; | ||
|
||
return key; | ||
} | ||
else | ||
{ | ||
errors.Add(validationError.AddStackFrame(new StackFrame())); | ||
} | ||
} | ||
|
||
keysAttempted.Append(key.ToString()); | ||
if (canMatchKey && !keyMatched && key.KeyId is not null && samlToken.Assertion.Signature.KeyInfo is not null) | ||
keyMatched = samlToken.Assertion.Signature.KeyInfo.MatchesKey(key); | ||
} | ||
} | ||
|
||
if (canMatchKey && keyMatched) | ||
return new XmlValidationError( | ||
new MessageDetail( | ||
TokenLogMessages.IDX10514, | ||
keysAttempted.ToString(), | ||
samlToken.Assertion.Signature.KeyInfo, | ||
GetErrorStrings(errors), | ||
samlToken), | ||
ValidationFailureType.SignatureValidationFailed, | ||
typeof(SecurityTokenInvalidSignatureException), | ||
new StackFrame(true)); | ||
|
||
if (keysAttempted.Length > 0) | ||
return new XmlValidationError( | ||
new MessageDetail( | ||
TokenLogMessages.IDX10512, | ||
keysAttempted.ToString(), | ||
GetErrorStrings(errors), | ||
samlToken), | ||
ValidationFailureType.SignatureValidationFailed, | ||
typeof(SecurityTokenSignatureKeyNotFoundException), | ||
new StackFrame(true)); | ||
|
||
return new XmlValidationError( | ||
new MessageDetail(TokenLogMessages.IDX10500), | ||
ValidationFailureType.SignatureValidationFailed, | ||
typeof(SecurityTokenSignatureKeyNotFoundException), | ||
new StackFrame(true)); | ||
} | ||
|
||
private static string GetErrorStrings(List<ValidationError> errors) | ||
{ | ||
StringBuilder sb = new(); | ||
for (int i = 0; i < errors.Count; i++) | ||
{ | ||
sb.AppendLine(errors[i].ToString()); | ||
} | ||
|
||
return sb.ToString(); | ||
} | ||
} | ||
} | ||
#nullable restore |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
34 changes: 34 additions & 0 deletions
34
src/Microsoft.IdentityModel.Xml/Exceptions/XmlValidationError.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
// Copyright (c) Microsoft Corporation. All rights reserved. | ||
// Licensed under the MIT License. | ||
|
||
using System; | ||
using System.Diagnostics; | ||
using Microsoft.IdentityModel.Tokens; | ||
|
||
namespace Microsoft.IdentityModel.Xml | ||
{ | ||
internal class XmlValidationError : ValidationError | ||
{ | ||
public XmlValidationError( | ||
MessageDetail messageDetail, | ||
ValidationFailureType validationFailureType, | ||
Type exceptionType, | ||
StackFrame stackFrame) : | ||
base(messageDetail, validationFailureType, exceptionType, stackFrame) | ||
{ | ||
|
||
} | ||
|
||
internal override Exception GetException() | ||
{ | ||
if (ExceptionType == typeof(XmlValidationException)) | ||
{ | ||
XmlValidationException exception = new(MessageDetail.Message, InnerException); | ||
exception.SetValidationError(this); | ||
return exception; | ||
} | ||
|
||
return base.GetException(); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
Microsoft.IdentityModel.Xml.Reference.Verify(Microsoft.IdentityModel.Tokens.CryptoProviderFactory cryptoProviderFactory, Microsoft.IdentityModel.Tokens.CallContext callContext) -> Microsoft.IdentityModel.Tokens.ValidationError | ||
Microsoft.IdentityModel.Xml.Signature.Verify(Microsoft.IdentityModel.Tokens.SecurityKey key, Microsoft.IdentityModel.Tokens.CryptoProviderFactory cryptoProviderFactory, Microsoft.IdentityModel.Tokens.CallContext callContext) -> Microsoft.IdentityModel.Tokens.ValidationError | ||
Microsoft.IdentityModel.Xml.SignedInfo.Verify(Microsoft.IdentityModel.Tokens.CryptoProviderFactory cryptoProviderFactory, Microsoft.IdentityModel.Tokens.CallContext callContext) -> Microsoft.IdentityModel.Tokens.ValidationError | ||
Microsoft.IdentityModel.Xml.XmlValidationError | ||
Microsoft.IdentityModel.Xml.XmlValidationError.XmlValidationError(Microsoft.IdentityModel.Tokens.MessageDetail messageDetail, Microsoft.IdentityModel.Tokens.ValidationFailureType validationFailureType, System.Type exceptionType, System.Diagnostics.StackFrame stackFrame) -> void | ||
Microsoft.IdentityModel.Xml.XmlValidationException.SetValidationError(Microsoft.IdentityModel.Tokens.ValidationError validationError) -> void | ||
override Microsoft.IdentityModel.Xml.XmlValidationError.GetException() -> System.Exception |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
override Microsoft.IdentityModel.Xml.XmlValidationException.StackTrace.get -> string |
Oops, something went wrong.