Skip to content

Commit 4849a81

Browse files
authored
Signature Validation: Remove exceptions (#2757)
* Refactored ValidateSignature to remove exceptions and logs
1 parent 12fe1ec commit 4849a81

8 files changed

Lines changed: 788 additions & 10 deletions

File tree

src/Microsoft.IdentityModel.JsonWebTokens/JsonWebTokenHandler.ValidateSignature.cs

Lines changed: 368 additions & 0 deletions
Large diffs are not rendered by default.

src/Microsoft.IdentityModel.Tokens/Delegates.cs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
using System;
55
using System.Collections.Generic;
66
using System.Threading.Tasks;
7+
using Microsoft.IdentityModel.JsonWebTokens.Results;
78

89
namespace Microsoft.IdentityModel.Tokens
910
{
@@ -171,6 +172,19 @@ namespace Microsoft.IdentityModel.Tokens
171172
public delegate SecurityToken TransformBeforeSignatureValidation(SecurityToken token, TokenValidationParameters validationParameters);
172173

173174
#nullable enable
175+
/// <summary>
176+
/// Resolves the signing key used for validating a token's signature.
177+
/// </summary>
178+
/// <param name="token">The string representation of the token being validated.</param>
179+
/// <param name="securityToken">The <see cref="SecurityToken"/> being validated, which may be null.</param>
180+
/// <param name="kid">The key identifier, which may be null.</param>
181+
/// <param name="validationParameters">The <see cref="ValidationParameters"/> to be used for validating the token.</param>
182+
/// <param name="configuration">The <see cref="BaseConfiguration"/> to be used for validating the token.</param>
183+
/// <param name="callContext">The <see cref="CallContext"/> used for logging.</param>
184+
/// <returns>The <see cref="SecurityKey"/> used to validate the signature.</returns>
185+
/// <remarks>If both <see cref="IssuerSigningKeyResolverUsingConfiguration"/> and <see cref="IssuerSigningKeyResolver"/> are set, <see cref="IssuerSigningKeyResolverUsingConfiguration"/> takes priority.</remarks>
186+
internal delegate SecurityKey? IssuerSigningKeyResolverDelegate(string token, SecurityToken? securityToken, string? kid, ValidationParameters validationParameters, BaseConfiguration? configuration, CallContext? callContext);
187+
174188
/// <summary>
175189
/// Resolves the decryption key for the security token.
176190
/// </summary>
@@ -181,5 +195,16 @@ namespace Microsoft.IdentityModel.Tokens
181195
/// <param name="callContext">The <see cref="CallContext"/> to be used for logging.</param>
182196
/// <returns>The <see cref="SecurityKey"/> used to decrypt the token.</returns>
183197
internal delegate IList<SecurityKey> ResolveTokenDecryptionKeyDelegate(string token, SecurityToken securityToken, string kid, ValidationParameters validationParameters, CallContext? callContext);
198+
199+
/// <summary>
200+
/// Validates the signature of the security token.
201+
/// </summary>
202+
/// <param name="token">The <see cref="SecurityToken"/> with a signature.</param>
203+
/// <param name="validationParameters">The <see cref="ValidationParameters"/> to be used for validating the token.</param>
204+
/// <param name="configuration">The <see cref="BaseConfiguration"/> to be used for validating the token.</param>
205+
/// <param name="callContext">The <see cref="CallContext"/> to be used for logging.</param>
206+
/// <remarks>This method is not expected to throw.</remarks>
207+
/// <returns>The validated <see cref="SecurityToken"/>.</returns>
208+
internal delegate SignatureValidationResult SignatureValidatorDelegate(SecurityToken token, ValidationParameters validationParameters, BaseConfiguration? configuration, CallContext? callContext);
184209
#nullable restore
185210
}
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
using System;
5+
using Microsoft.IdentityModel.Tokens;
6+
7+
namespace Microsoft.IdentityModel.JsonWebTokens.Results
8+
{
9+
#nullable enable
10+
/// <summary>
11+
/// Contains the result of validating a signature.
12+
/// The <see cref="TokenValidationResult"/> contains a collection of <see cref="ValidationResult"/> for each step in the token validation.
13+
/// </summary>
14+
internal class SignatureValidationResult: ValidationResult
15+
{
16+
private Exception? _exception;
17+
18+
/// <summary>
19+
/// Creates an instance of <see cref="SignatureValidationResult"/> representing the successful result of validating a signature.
20+
/// </summary>
21+
public SignatureValidationResult(bool isValid, ValidationFailureType validationFailureType) : base(validationFailureType)
22+
{
23+
IsValid = isValid;
24+
}
25+
26+
/// <summary>
27+
/// Creates an instance of <see cref="SignatureValidationResult"/> representing the failed result of validating a signature.
28+
/// </summary>
29+
/// <param name="validationFailure"> is the <see cref="ValidationFailure"/> that occurred while validating the signature.</param>
30+
/// <param name="exceptionDetail"> contains the <see cref="ExceptionDetail"/> of the error that occurred while validating the signature.</param>
31+
public SignatureValidationResult(ValidationFailureType validationFailure, ExceptionDetail? exceptionDetail)
32+
: base(validationFailure, exceptionDetail)
33+
{
34+
IsValid = false;
35+
}
36+
37+
/// <summary>
38+
/// Creates an instance of <see cref="SignatureValidationResult"/> representing a successful validation.
39+
/// </summary>
40+
internal static SignatureValidationResult Success() =>
41+
new SignatureValidationResult(true, ValidationFailureType.ValidationSucceeded);
42+
43+
/// <summary>
44+
/// Creates an instance of <see cref="SignatureValidationResult"/> representing a failure due to a null parameter.
45+
/// </summary>
46+
/// <param name="parameterName">The name of the null parameter.</param>
47+
internal static SignatureValidationResult NullParameterFailure(string parameterName) =>
48+
new SignatureValidationResult(
49+
ValidationFailureType.SignatureValidationFailed,
50+
ExceptionDetail.NullParameter(parameterName));
51+
52+
/// <summary>
53+
/// Gets the <see cref="Exception"/> that occurred while validating the signature.
54+
/// </summary>
55+
public override Exception? Exception
56+
{
57+
get
58+
{
59+
if (_exception != null || ExceptionDetail == null)
60+
return _exception;
61+
62+
HasValidOrExceptionWasRead = true;
63+
_exception = ExceptionDetail.GetException();
64+
_exception.Source = "Microsoft.IdentityModel.JsonWebTokens";
65+
66+
if (_exception is SecurityTokenException securityTokenException)
67+
{
68+
securityTokenException.ExceptionDetail = ExceptionDetail;
69+
}
70+
71+
return _exception;
72+
}
73+
}
74+
}
75+
#nullable restore
76+
}

src/Microsoft.IdentityModel.Tokens/Validation/ValidationFailureType.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,12 @@ private class AudienceValidationFailure : ValidationFailureType { internal Audie
5151
public static readonly ValidationFailureType TokenTypeValidationFailed = new TokenTypeValidationFailure("TokenTypeValidationFailed");
5252
private class TokenTypeValidationFailure : ValidationFailureType { internal TokenTypeValidationFailure(string name) : base(name) { } }
5353

54+
/// <summary>
55+
/// Defines a type that represents that the token's signature validation failed.
56+
/// </summary>
57+
public static readonly ValidationFailureType SignatureValidationFailed = new SignatureValidationFailure("SignatureValidationFailed");
58+
private class SignatureValidationFailure : ValidationFailureType { internal SignatureValidationFailure(string name) : base(name) { } }
59+
5460
/// <summary>
5561
/// Defines a type that represents that signing key validation failed.
5662
/// </summary>

src/Microsoft.IdentityModel.Tokens/Validation/ValidationParameters.cs

Lines changed: 23 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ internal class ValidationParameters
2121
private string _nameClaimType = ClaimsIdentity.DefaultNameClaimType;
2222
private string _roleClaimType = ClaimsIdentity.DefaultRoleClaimType;
2323
private Dictionary<string, object> _instancePropertyBag;
24-
24+
private IList<SecurityKey> _issuerSigningKeys;
2525
private IList<string> _validIssuers;
2626
private IList<string> _validTokenTypes;
2727
private IList<string> _validAudiences;
@@ -30,6 +30,7 @@ internal class ValidationParameters
3030
private AudienceValidatorDelegate _audienceValidator = Validators.ValidateAudience;
3131
private IssuerValidationDelegateAsync _issuerValidatorAsync = Validators.ValidateIssuerAsync;
3232
private LifetimeValidatorDelegate _lifetimeValidator = Validators.ValidateLifetime;
33+
private SignatureValidatorDelegate _signatureValidator;
3334
private TokenReplayValidatorDelegate _tokenReplayValidator = Validators.ValidateTokenReplay;
3435
private TypeValidatorDelegate _typeValidator = Validators.ValidateTokenType;
3536

@@ -69,7 +70,7 @@ protected ValidationParameters(ValidationParameters other)
6970
IncludeTokenOnFailedValidation = other.IncludeTokenOnFailedValidation;
7071
IgnoreTrailingSlashWhenValidatingAudience = other.IgnoreTrailingSlashWhenValidatingAudience;
7172
IssuerSigningKeyResolver = other.IssuerSigningKeyResolver;
72-
IssuerSigningKeys = other.IssuerSigningKeys;
73+
_issuerSigningKeys = other.IssuerSigningKeys;
7374
IssuerSigningKeyValidator = other.IssuerSigningKeyValidator;
7475
IssuerValidatorAsync = other.IssuerValidatorAsync;
7576
LifetimeValidator = other.LifetimeValidator;
@@ -204,7 +205,7 @@ public virtual ValidationParameters Clone()
204205
/// <returns>A <see cref="ClaimsIdentity"/> with Authentication, NameClaimType and RoleClaimType set.</returns>
205206
public virtual ClaimsIdentity CreateClaimsIdentity(SecurityToken securityToken, string issuer)
206207
{
207-
string nameClaimType = null;
208+
string nameClaimType;
208209
if (NameClaimTypeRetriever != null)
209210
{
210211
nameClaimType = NameClaimTypeRetriever(securityToken, issuer);
@@ -214,7 +215,7 @@ public virtual ClaimsIdentity CreateClaimsIdentity(SecurityToken securityToken,
214215
nameClaimType = NameClaimType;
215216
}
216217

217-
string roleClaimType = null;
218+
string roleClaimType;
218219
if (RoleClaimTypeRetriever != null)
219220
{
220221
roleClaimType = RoleClaimTypeRetriever(securityToken, issuer);
@@ -293,12 +294,15 @@ public virtual ClaimsIdentity CreateClaimsIdentity(SecurityToken securityToken,
293294
/// If both <see cref="IssuerSigningKeyResolverUsingConfiguration"/> and <see cref="IssuerSigningKeyResolver"/> are set, IssuerSigningKeyResolverUsingConfiguration takes
294295
/// priority.
295296
/// </remarks>
296-
public IssuerSigningKeyResolver IssuerSigningKeyResolver { get; set; }
297+
public IssuerSigningKeyResolverDelegate IssuerSigningKeyResolver { get; set; }
297298

298299
/// <summary>
299-
/// Gets or sets an <see cref="IList{SecurityKey}"/> used for signature validation.
300+
/// Gets the <see cref="IList{SecurityKey}"/> used for signature validation.
300301
/// </summary>
301-
public IList<SecurityKey> IssuerSigningKeys { get; }
302+
public IList<SecurityKey> IssuerSigningKeys =>
303+
_issuerSigningKeys ??
304+
Interlocked.CompareExchange(ref _issuerSigningKeys, [], null) ??
305+
_issuerSigningKeys;
302306

303307
/// <summary>
304308
/// Allows overriding the delegate that will be used to validate the issuer of the token.
@@ -375,7 +379,7 @@ public string NameClaimType
375379
public IDictionary<string, object> PropertyBag { get; }
376380

377381
/// <summary>
378-
/// Gets or sets a boolean to control if configuration required to be refreshed before token validation.
382+
/// A boolean to control whether configuration should be refreshed before validating a token.
379383
/// </summary>
380384
/// <remarks>
381385
/// The default is <c>false</c>.
@@ -433,7 +437,10 @@ public string RoleClaimType
433437
/// <remarks>
434438
/// If set, this delegate will be called to validate the signature of the token, instead of default processing.
435439
/// </remarks>
436-
public SignatureValidator SignatureValidator { get; set; }
440+
public SignatureValidatorDelegate SignatureValidator {
441+
get { return _signatureValidator; }
442+
set { _signatureValidator = value; }
443+
}
437444

438445
/// <summary>
439446
/// Gets or sets a delegate that will be called to retreive a <see cref="SecurityKey"/> used for decryption.
@@ -469,6 +476,13 @@ public TokenReplayValidatorDelegate TokenReplayValidator
469476
set { _tokenReplayValidator = value ?? throw new ArgumentNullException(nameof(value), "TokenReplayValidator cannot be set as null."); }
470477
}
471478

479+
/// <summary>
480+
/// If the IssuerSigningKeyResolver is unable to resolve the key when validating the signature of the SecurityToken,
481+
/// all available keys will be tried.
482+
/// </summary>
483+
/// <remarks>Default is false.</remarks>
484+
public bool TryAllIssuerSigningKeys { get; set; }
485+
472486
/// <summary>
473487
/// Allows overriding the delegate that will be used to validate the type of the token.
474488
/// If the token type cannot be validated, a <see cref="TokenTypeValidationResult"/> MUST be returned by the delegate.

src/Microsoft.IdentityModel.Tokens/Validation/ValidationResult.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ protected ValidationResult(ValidationFailureType validationFailureType)
3838
/// </summary>
3939
/// <param name="validationFailureType">The <see cref="ValidationFailureType"/> that occurred during validation.</param>
4040
/// <param name="exceptionDetail"> The <see cref="ExceptionDetail"/> representing the <see cref="Exception"/> that occurred during validation.</param>
41-
protected ValidationResult(ValidationFailureType validationFailureType, ExceptionDetail exceptionDetail)
41+
protected ValidationResult(ValidationFailureType validationFailureType, ExceptionDetail? exceptionDetail)
4242
{
4343
ValidationFailureType = validationFailureType;
4444
ExceptionDetail = exceptionDetail;

0 commit comments

Comments
 (0)