Skip to content

Commit

Permalink
Extensibility tests: Algorithm and Signature - JWT, SAML and SAML2 (#…
Browse files Browse the repository at this point in the history
…3034)

* Added SecurityTokenInvalidOperationException to replace InvalidOperationException when used with ValidationError subclasses

(cherry picked from commit 0c59aef)

* Added SignatureValidationError to handle exceptions generated during signature validation

* Removed XmlValidationError. Updated XML signature validation to use SignatureValidationError instead

* Added log messages, validation failure types. Removed nullability of CallContext in the signature validation delegate.

* Handle potential exceptions thrown during signature validation, both signature and algorithm related

* Updated validation errors and test

* Added custom validation errors and delegates for algorithm and signature validation

* Added algorithm extensibility tests for JWT, SAML, and SAML2

(cherry picked from commit 462e576)

* Added signature extensibility tests for JWT, SAML, and SAML2

(cherry picked from commit 1ec5741)
  • Loading branch information
iNinja authored Nov 25, 2024
1 parent 27bc920 commit f8b5bb6
Show file tree
Hide file tree
Showing 29 changed files with 2,601 additions and 221 deletions.

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

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

#nullable enable
Expand All @@ -20,25 +20,50 @@ internal static ValidationResult<SecurityKey> ValidateSignature(
{
if (samlToken is null)
{
return ValidationError.NullParameter(
return SignatureValidationError.NullParameter(
nameof(samlToken),
ValidationError.GetCurrentStackFrame());
}

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

// 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);
{
try
{
ValidationResult<SecurityKey> signatureValidationResult = validationParameters.SignatureValidator(
samlToken,
validationParameters,
null, // configuration
callContext);

if (!signatureValidationResult.IsValid)
return signatureValidationResult.UnwrapError().AddCurrentStackFrame();

return signatureValidationResult;
}
#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 SignatureValidationError(
new MessageDetail(TokenLogMessages.IDX10272),
ValidationFailureType.SignatureValidatorThrew,
typeof(SecurityTokenInvalidSignatureException),
ValidationError.GetCurrentStackFrame(),
innerException: ex);
}
}

// If the user wants to accept unsigned tokens, they must implement the delegate
if (samlToken.Assertion.Signature is null)
return new XmlValidationError(
return new SignatureValidationError(
new MessageDetail(
TokenLogMessages.IDX10504,
samlToken.Assertion.CanonicalString),
Expand All @@ -64,16 +89,15 @@ internal static ValidationResult<SecurityKey> ValidateSignature(
resolvedKey = SamlTokenUtilities.ResolveTokenSigningKey(samlToken.Assertion.Signature.KeyInfo, validationParameters);
}

ValidationError? error = null;

if (resolvedKey is not null)
{
keyMatched = true;
var result = ValidateSignatureUsingKey(resolvedKey, samlToken, validationParameters, callContext);
if (result.IsValid)
return result;

error = result.UnwrapError();
if (!result.IsValid)
return result.UnwrapError().AddCurrentStackFrame();

return result;
}

bool canMatchKey = samlToken.Assertion.Signature.KeyInfo != null;
Expand Down Expand Up @@ -103,12 +127,12 @@ internal static ValidationResult<SecurityKey> ValidateSignature(
}

if (canMatchKey && keyMatched)
return new XmlValidationError(
return new SignatureValidationError(
new MessageDetail(
TokenLogMessages.IDX10514,
keysAttempted?.ToString(),
samlToken.Assertion.Signature.KeyInfo,
GetErrorString(error, errors),
GetErrorString(errors),
samlToken),
ValidationFailureType.SignatureValidationFailed,
typeof(SecurityTokenInvalidSignatureException),
Expand All @@ -121,17 +145,17 @@ internal static ValidationResult<SecurityKey> ValidateSignature(
keysAttemptedString = keysAttempted!.ToString();

if (keysAttemptedString is not null)
return new XmlValidationError(
return new SignatureValidationError(
new MessageDetail(
TokenLogMessages.IDX10512,
keysAttemptedString,
GetErrorString(error, errors),
GetErrorString(errors),
samlToken),
ValidationFailureType.SignatureValidationFailed,
typeof(SecurityTokenSignatureKeyNotFoundException),
ValidationError.GetCurrentStackFrame());

return new XmlValidationError(
return new SignatureValidationError(
new MessageDetail(TokenLogMessages.IDX10500),
ValidationFailureType.SignatureValidationFailed,
typeof(SecurityTokenSignatureKeyNotFoundException),
Expand All @@ -140,44 +164,61 @@ internal static ValidationResult<SecurityKey> ValidateSignature(

private static ValidationResult<SecurityKey> ValidateSignatureUsingKey(SecurityKey key, SamlSecurityToken samlToken, ValidationParameters validationParameters, CallContext callContext)
{
ValidationResult<string> algorithmValidationResult = validationParameters.AlgorithmValidator(
samlToken.Assertion.Signature.SignedInfo.SignatureMethod,
key,
samlToken,
validationParameters,
callContext);

if (!algorithmValidationResult.IsValid)
try
{
return algorithmValidationResult.UnwrapError().AddCurrentStackFrame();
ValidationResult<string> algorithmValidationResult = validationParameters.AlgorithmValidator(
samlToken.Assertion.Signature.SignedInfo.SignatureMethod,
key,
samlToken,
validationParameters,
callContext);

if (!algorithmValidationResult.IsValid)
{
var algorithmValidationError = algorithmValidationResult.UnwrapError().AddCurrentStackFrame();
return new SignatureValidationError(
new MessageDetail(
TokenLogMessages.IDX10518,
algorithmValidationError.MessageDetail.Message),
algorithmValidationError.FailureType, // Surface the algorithm validation error's failure type.
typeof(SecurityTokenInvalidSignatureException),
ValidationError.GetCurrentStackFrame(),
algorithmValidationError); // Pass the algorithm validation error as the inner validation error.
}
}
else
#pragma warning disable CA1031 // Do not catch general exception types
catch (Exception ex)
#pragma warning restore CA1031 // Do not catch general exception types
{
var validationError = samlToken.Assertion.Signature.Verify(
return new SignatureValidationError(
new MessageDetail(TokenLogMessages.IDX10273),
ValidationFailureType.AlgorithmValidatorThrew,
typeof(SecurityTokenInvalidSignatureException),
ValidationError.GetCurrentStackFrame(),
null, // No need to create an AlgorithmValidationError for this case.
ex);
}

var validationError = samlToken.Assertion.Signature.Verify(
key,
validationParameters.CryptoProviderFactory ?? key.CryptoProviderFactory,
callContext);

if (validationError is null)
{
samlToken.SigningKey = key;
if (validationError is null)
{
samlToken.SigningKey = key;

return key;
}
else
{
return validationError.AddCurrentStackFrame();
}
return key;
}
else
{
return validationError.AddCurrentStackFrame();
}
}

private static string GetErrorString(ValidationError? error, List<ValidationError>? errorList)
private static string GetErrorString(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 (errorList is null)
return string.Empty;

Expand Down
Loading

0 comments on commit f8b5bb6

Please sign in to comment.