Skip to content

Commit

Permalink
Regression tests: Algorithm (#2934)
Browse files Browse the repository at this point in the history
* Added AlgorithmValidationError, refactored error creation in ValidateSignature to surface the invalid algorithm as part of the error

* Added regression/comparison tests for algorithm validation scenarios.
  • Loading branch information
iNinja authored Oct 24, 2024
1 parent 8536e07 commit 4a28a69
Show file tree
Hide file tree
Showing 5 changed files with 207 additions and 10 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -241,13 +241,29 @@ private static ValidationResult<SecurityKey> ValidateSignatureWithKey(
callContext);

if (!result.IsValid)
return new ValidationError(
new MessageDetail(
TokenLogMessages.IDX10518,
result.UnwrapError().MessageDetail.Message),
ValidationFailureType.SignatureAlgorithmValidationFailed,
typeof(SecurityTokenInvalidAlgorithmException),
new StackFrame(true));
{
if (result.UnwrapError() is AlgorithmValidationError algorithmValidationError)
{
return new AlgorithmValidationError(
new MessageDetail(
TokenLogMessages.IDX10518,
algorithmValidationError.MessageDetail.Message),
typeof(SecurityTokenInvalidAlgorithmException),
new StackFrame(true),
algorithmValidationError.InvalidAlgorithm);
}
else
{
// overridden delegate did not return an AlgorithmValidationError
return new ValidationError(
new MessageDetail(
TokenLogMessages.IDX10518,
result.UnwrapError().MessageDetail.Message),
ValidationFailureType.SignatureAlgorithmValidationFailed,
typeof(SecurityTokenInvalidAlgorithmException),
new StackFrame(true));
}
}

SignatureProvider signatureProvider = cryptoProviderFactory.CreateForVerifying(key, jsonWebToken.Alg);
try
Expand Down
5 changes: 5 additions & 0 deletions src/Microsoft.IdentityModel.Tokens/InternalAPI.Unshipped.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
const Microsoft.IdentityModel.Tokens.LogMessages.IDX10002 = "IDX10002: Unknown exception type returned. Type: '{0}'. Message: '{1}'." -> string
const Microsoft.IdentityModel.Tokens.LogMessages.IDX10268 = "IDX10268: Unable to validate audience, validationParameters.ValidAudiences.Count == 0." -> string
Microsoft.IdentityModel.Tokens.AlgorithmValidationError
Microsoft.IdentityModel.Tokens.AlgorithmValidationError.AlgorithmValidationError(Microsoft.IdentityModel.Tokens.MessageDetail messageDetail, System.Type exceptionType, System.Diagnostics.StackFrame stackFrame, string invalidAlgorithm) -> void
Microsoft.IdentityModel.Tokens.AlgorithmValidationError.InvalidAlgorithm.get -> string
Microsoft.IdentityModel.Tokens.AlgorithmValidationError._invalidAlgorithm -> string
Microsoft.IdentityModel.Tokens.AudienceValidationError.AudienceValidationError(Microsoft.IdentityModel.Tokens.MessageDetail messageDetail, Microsoft.IdentityModel.Tokens.ValidationFailureType failureType, System.Type exceptionType, System.Diagnostics.StackFrame stackFrame, System.Collections.Generic.IList<string> tokenAudiences, System.Collections.Generic.IList<string> validAudiences) -> void
Microsoft.IdentityModel.Tokens.AudienceValidationError.TokenAudiences.get -> System.Collections.Generic.IList<string>
Microsoft.IdentityModel.Tokens.IssuerValidationSource.IssuerMatchedConfiguration = 1 -> Microsoft.IdentityModel.Tokens.IssuerValidationSource
Expand All @@ -12,6 +16,7 @@ Microsoft.IdentityModel.Tokens.ValidationError.GetException(System.Type exceptio
Microsoft.IdentityModel.Tokens.ValidationResult<TResult>.Error.get -> Microsoft.IdentityModel.Tokens.ValidationError
Microsoft.IdentityModel.Tokens.ValidationResult<TResult>.IsValid.get -> bool
Microsoft.IdentityModel.Tokens.ValidationResult<TResult>.Result.get -> TResult
override Microsoft.IdentityModel.Tokens.AlgorithmValidationError.GetException() -> System.Exception
static Microsoft.IdentityModel.Tokens.AudienceValidationError.AudiencesCountZero -> System.Diagnostics.StackFrame
static Microsoft.IdentityModel.Tokens.AudienceValidationError.AudiencesNull -> System.Diagnostics.StackFrame
static Microsoft.IdentityModel.Tokens.AudienceValidationError.ValidateAudienceFailed -> System.Diagnostics.StackFrame
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using System;
using System.Diagnostics;

#nullable enable
namespace Microsoft.IdentityModel.Tokens
{
internal class AlgorithmValidationError : ValidationError
{
protected string? _invalidAlgorithm;

public AlgorithmValidationError(
MessageDetail messageDetail,
Type exceptionType,
StackFrame stackFrame,
string? invalidAlgorithm) :
base(messageDetail, ValidationFailureType.AlgorithmValidationFailed, exceptionType, stackFrame)
{
_invalidAlgorithm = invalidAlgorithm;
}

internal override Exception GetException()
{
if (ExceptionType == typeof(SecurityTokenInvalidAlgorithmException))
{
SecurityTokenInvalidAlgorithmException exception = new(MessageDetail.Message, InnerException)
{
InvalidAlgorithm = _invalidAlgorithm
};

return exception;
}

return base.GetException();
}

internal string? InvalidAlgorithm => _invalidAlgorithm;
}
}
#nullable restore
Original file line number Diff line number Diff line change
Expand Up @@ -53,13 +53,13 @@ internal static ValidationResult<string> ValidateAlgorithm(
if (validationParameters.ValidAlgorithms != null &&
validationParameters.ValidAlgorithms.Count > 0 &&
!validationParameters.ValidAlgorithms.Contains(algorithm, StringComparer.Ordinal))
return new ValidationError(
return new AlgorithmValidationError(
new MessageDetail(
LogMessages.IDX10696,
LogHelper.MarkAsNonPII(algorithm)),
ValidationFailureType.AlgorithmValidationFailed,
typeof(SecurityTokenInvalidAlgorithmException),
new StackFrame(true));
new StackFrame(true),
algorithm);

return algorithm;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

#nullable enable
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.IdentityModel.TestUtils;
using Microsoft.IdentityModel.Tokens;
using Xunit;

namespace Microsoft.IdentityModel.JsonWebTokens.Tests
{
public partial class JsonWebTokenHandlerValidateTokenAsyncTests
{
[Theory, MemberData(nameof(ValidateTokenAsync_AlgorithmTestCases), DisableDiscoveryEnumeration = true)]
public async Task ValidateTokenAsync_Algorithm(ValidateTokenAsyncAlgorithmTheoryData theoryData)
{
var context = TestUtilities.WriteHeader($"{this}.ValidateTokenAsync_Algorithm", theoryData);

string jwtString = CreateTokenWithSigningCredentials(theoryData.SigningCredentials);

await ValidateAndCompareResults(jwtString, theoryData, context);

TestUtilities.AssertFailIfErrors(context);
}

public static TheoryData<ValidateTokenAsyncAlgorithmTheoryData> ValidateTokenAsync_AlgorithmTestCases
{
get
{
var theoryData = new TheoryData<ValidateTokenAsyncAlgorithmTheoryData>();

theoryData.Add(new ValidateTokenAsyncAlgorithmTheoryData("Valid_AlgorithmIsValid")
{
SigningCredentials = KeyingMaterial.DefaultX509SigningCreds_2048_RsaSha2_Sha2,
TokenValidationParameters = CreateTokenValidationParameters(
KeyingMaterial.DefaultX509SigningCreds_2048_RsaSha2_Sha2.Key,
validAlgorithms: [SecurityAlgorithms.RsaSha256Signature]),
ValidationParameters = CreateValidationParameters(
KeyingMaterial.DefaultX509SigningCreds_2048_RsaSha2_Sha2.Key,
validAlgorithms: [SecurityAlgorithms.RsaSha256Signature]),
});

theoryData.Add(new ValidateTokenAsyncAlgorithmTheoryData("Valid_ValidAlgorithmsIsNull")
{
SigningCredentials = KeyingMaterial.DefaultX509SigningCreds_2048_RsaSha2_Sha2,
TokenValidationParameters = CreateTokenValidationParameters(
KeyingMaterial.DefaultX509SigningCreds_2048_RsaSha2_Sha2.Key,
validAlgorithms: null),
ValidationParameters = CreateValidationParameters(
KeyingMaterial.DefaultX509SigningCreds_2048_RsaSha2_Sha2.Key,
validAlgorithms: null),
});

theoryData.Add(new ValidateTokenAsyncAlgorithmTheoryData("Valid_ValidAlgorithmsIsEmptyList")
{
SigningCredentials = KeyingMaterial.DefaultX509SigningCreds_2048_RsaSha2_Sha2,
TokenValidationParameters = CreateTokenValidationParameters(
KeyingMaterial.DefaultX509SigningCreds_2048_RsaSha2_Sha2.Key, validAlgorithms: []),
ValidationParameters = CreateValidationParameters(
KeyingMaterial.DefaultX509SigningCreds_2048_RsaSha2_Sha2.Key, validAlgorithms: []),
});

theoryData.Add(new ValidateTokenAsyncAlgorithmTheoryData("Invalid_TokenIsSignedWithAnInvalidAlgorithm")
{
// Token is signed with HmacSha256 but only sha256 is considered valid for this test's purposes
SigningCredentials = KeyingMaterial.DefaultSymmetricSigningCreds_256_Sha2,
TokenValidationParameters = CreateTokenValidationParameters(
KeyingMaterial.DefaultSymmetricSigningCreds_256_Sha2.Key,
validAlgorithms: [SecurityAlgorithms.Sha256]),
ValidationParameters = CreateValidationParameters(
KeyingMaterial.DefaultSymmetricSigningCreds_256_Sha2.Key,
validAlgorithms: [SecurityAlgorithms.Sha256]),
ExpectedIsValid = false,
ExpectedException = ExpectedException.SecurityTokenInvalidSignatureException("IDX10511:"),
ExpectedExceptionValidationParameters = ExpectedException.SecurityTokenInvalidAlgorithmException(
"IDX10518:",
propertiesExpected: new() { { "InvalidAlgorithm", SecurityAlgorithms.HmacSha256Signature } }),
});

return theoryData;

static TokenValidationParameters CreateTokenValidationParameters(
SecurityKey? signingKey = null, List<string>? validAlgorithms = null)
{
// only validate the signature and algorithm
var tokenValidationParameters = new TokenValidationParameters
{
ValidateAudience = false,
ValidateIssuer = false,
ValidateLifetime = false,
ValidateTokenReplay = false,
ValidateIssuerSigningKey = false,
RequireSignedTokens = true,
IssuerSigningKey = signingKey,
};

tokenValidationParameters.ValidAlgorithms = validAlgorithms;

return tokenValidationParameters;
}

static ValidationParameters CreateValidationParameters(
SecurityKey? signingKey = null, List<string>? validAlgorithms = null)
{
ValidationParameters validationParameters = new ValidationParameters();

if (signingKey is not null)
validationParameters.IssuerSigningKeys.Add(signingKey);

validationParameters.ValidAlgorithms = validAlgorithms;

// Skip all validations except signature and algorithm
validationParameters.AudienceValidator = SkipValidationDelegates.SkipAudienceValidation;
validationParameters.IssuerSigningKeyValidator = SkipValidationDelegates.SkipIssuerSigningKeyValidation;
validationParameters.IssuerValidatorAsync = SkipValidationDelegates.SkipIssuerValidation;
validationParameters.LifetimeValidator = SkipValidationDelegates.SkipLifetimeValidation;
validationParameters.TokenReplayValidator = SkipValidationDelegates.SkipTokenReplayValidation;
validationParameters.TypeValidator = SkipValidationDelegates.SkipTokenTypeValidation;

return validationParameters;
}
}
}

public class ValidateTokenAsyncAlgorithmTheoryData : ValidateTokenAsyncBaseTheoryData
{
public ValidateTokenAsyncAlgorithmTheoryData(string testId) : base(testId) { }

public SigningCredentials? SigningCredentials { get; set; }
}
}
}
#nullable restore

0 comments on commit 4a28a69

Please sign in to comment.