Skip to content

Commit e4928af

Browse files
Merge pull request #1413 from AzureAD/keegancaruso/api-restrict-crypto-algorithms
Allow users to specify the acceptable algorithms for crypto operations
2 parents 8ca7558 + b4b289a commit e4928af

File tree

17 files changed

+919
-13
lines changed

17 files changed

+919
-13
lines changed

src/Microsoft.IdentityModel.JsonWebTokens/JsonWebTokenHandler.cs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -636,6 +636,7 @@ public string DecryptToken(JsonWebToken jwtToken, TokenValidationParameters vali
636636

637637
try
638638
{
639+
Validators.ValidateAlgorithm(jwtToken.Enc, key, jwtToken, validationParameters);
639640
decryptedTokenBytes = DecryptToken(jwtToken, cryptoProviderFactory, key);
640641
decryptionSucceeded = true;
641642
break;
@@ -1213,7 +1214,7 @@ private JsonWebToken ValidateSignature(string token, TokenValidationParameters v
12131214
{
12141215
try
12151216
{
1216-
if (ValidateSignature(encodedBytes, signatureBytes, key, jwtToken.Alg, validationParameters))
1217+
if (ValidateSignature(encodedBytes, signatureBytes, key, jwtToken.Alg, jwtToken, validationParameters))
12171218
{
12181219
LogHelper.LogInformation(TokenLogMessages.IDX10242, token);
12191220
jwtToken.SigningKey = key;
@@ -1258,9 +1259,10 @@ private JsonWebToken ValidateSignature(string token, TokenValidationParameters v
12581259
/// <param name="signature">Signature to compare against.</param>
12591260
/// <param name="key"><See cref="SecurityKey"/> to use.</param>
12601261
/// <param name="algorithm">Crypto algorithm to use.</param>
1262+
/// <param name="securityToken">The <see cref="SecurityToken"/> being validated.</param>
12611263
/// <param name="validationParameters">Priority will be given to <see cref="TokenValidationParameters.CryptoProviderFactory"/> over <see cref="SecurityKey.CryptoProviderFactory"/>.</param>
12621264
/// <returns>'true' if signature is valid.</returns>
1263-
internal bool ValidateSignature(byte[] encodedBytes, byte[] signature, SecurityKey key, string algorithm, TokenValidationParameters validationParameters)
1265+
internal bool ValidateSignature(byte[] encodedBytes, byte[] signature, SecurityKey key, string algorithm, SecurityToken securityToken, TokenValidationParameters validationParameters)
12641266
{
12651267
var cryptoProviderFactory = validationParameters.CryptoProviderFactory ?? key.CryptoProviderFactory;
12661268
if (!cryptoProviderFactory.IsSupportedAlgorithm(algorithm, key))
@@ -1269,6 +1271,8 @@ internal bool ValidateSignature(byte[] encodedBytes, byte[] signature, SecurityK
12691271
return false;
12701272
}
12711273

1274+
Validators.ValidateAlgorithm(algorithm, key, securityToken, validationParameters);
1275+
12721276
var signatureProvider = cryptoProviderFactory.CreateForVerifying(key, algorithm);
12731277
if (signatureProvider == null)
12741278
throw LogHelper.LogExceptionMessage(new InvalidOperationException(LogHelper.FormatInvariant(TokenLogMessages.IDX10636, key == null ? "Null" : key.ToString(), algorithm ?? "Null")));

src/Microsoft.IdentityModel.Tokens.Saml/Saml/SamlSecurityTokenHandler.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1056,6 +1056,8 @@ private SamlSecurityToken ValidateSignature(SamlSecurityToken samlToken, string
10561056
{
10571057
try
10581058
{
1059+
Validators.ValidateAlgorithm(samlToken.Assertion.Signature.SignedInfo.SignatureMethod, key, samlToken, validationParameters);
1060+
10591061
samlToken.Assertion.Signature.Verify(key, validationParameters.CryptoProviderFactory ?? key.CryptoProviderFactory);
10601062
LogHelper.LogInformation(TokenLogMessages.IDX10242, token);
10611063
samlToken.SigningKey = key;

src/Microsoft.IdentityModel.Tokens.Saml/Saml2/Saml2SecurityTokenHandler.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -420,6 +420,8 @@ private Saml2SecurityToken ValidateSignature(Saml2SecurityToken samlToken, strin
420420
{
421421
try
422422
{
423+
Validators.ValidateAlgorithm(samlToken.Assertion.Signature.SignedInfo.SignatureMethod, key, samlToken, validationParameters);
424+
423425
samlToken.Assertion.Signature.Verify(key, validationParameters.CryptoProviderFactory ?? key.CryptoProviderFactory);
424426
LogHelper.LogInformation(TokenLogMessages.IDX10242, token);
425427
samlToken.SigningKey = key;
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
//------------------------------------------------------------------------------
2+
//
3+
// Copyright (c) Microsoft Corporation.
4+
// All rights reserved.
5+
//
6+
// This code is licensed under the MIT License.
7+
//
8+
// Permission is hereby granted, free of charge, to any person obtaining a copy
9+
// of this software and associated documentation files(the "Software"), to deal
10+
// in the Software without restriction, including without limitation the rights
11+
// to use, copy, modify, merge, publish, distribute, sublicense, and / or sell
12+
// copies of the Software, and to permit persons to whom the Software is
13+
// furnished to do so, subject to the following conditions :
14+
//
15+
// The above copyright notice and this permission notice shall be included in
16+
// all copies or substantial portions of the Software.
17+
//
18+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19+
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20+
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE
21+
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22+
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23+
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
24+
// THE SOFTWARE.
25+
//
26+
//------------------------------------------------------------------------------
27+
28+
using System;
29+
using System.Runtime.Serialization;
30+
31+
namespace Microsoft.IdentityModel.Tokens
32+
{
33+
/// <summary>
34+
/// This exception is thrown when a cryptographic algorithm is invalid.
35+
/// </summary>
36+
[Serializable]
37+
public class SecurityTokenInvalidAlgorithmException : SecurityTokenValidationException
38+
{
39+
[NonSerialized]
40+
const string _Prefix = "Microsoft.IdentityModel." + nameof(SecurityTokenInvalidAlgorithmException) + ".";
41+
42+
[NonSerialized]
43+
const string _InvalidAlgorithmKey = _Prefix + nameof(InvalidAlgorithm);
44+
45+
/// <summary>
46+
/// Gets or sets the invalid algorithm that created the validation exception.
47+
/// </summary>
48+
public string InvalidAlgorithm { get; set; }
49+
50+
/// <summary>
51+
/// Initializes a new instance of the <see cref="SecurityTokenInvalidAlgorithmException"/> class.
52+
/// </summary>
53+
public SecurityTokenInvalidAlgorithmException()
54+
: base()
55+
{
56+
}
57+
58+
/// <summary>
59+
/// Initializes a new instance of the <see cref="SecurityTokenInvalidAlgorithmException"/> class.
60+
/// </summary>
61+
/// <param name="message">Additional information to be included in the exception and displayed to user.</param>
62+
public SecurityTokenInvalidAlgorithmException(string message)
63+
: base(message)
64+
{
65+
}
66+
67+
/// <summary>
68+
/// Initializes a new instance of the <see cref="SecurityTokenInvalidAlgorithmException"/> class.
69+
/// </summary>
70+
/// <param name="message">Additional information to be included in the exception and displayed to user.</param>
71+
/// <param name="innerException">A <see cref="Exception"/> that represents the root cause of the exception.</param>
72+
public SecurityTokenInvalidAlgorithmException(string message, Exception innerException)
73+
: base(message, innerException)
74+
{
75+
}
76+
77+
/// <summary>
78+
/// Initializes a new instance of the <see cref="SecurityTokenInvalidTypeException"/> class.
79+
/// </summary>
80+
/// <param name="info">the <see cref="SerializationInfo"/> that holds the serialized object data.</param>
81+
/// <param name="context">The contextual information about the source or destination.</param>
82+
protected SecurityTokenInvalidAlgorithmException(SerializationInfo info, StreamingContext context)
83+
: base(info, context)
84+
{
85+
SerializationInfoEnumerator enumerator = info.GetEnumerator();
86+
while (enumerator.MoveNext())
87+
{
88+
switch (enumerator.Name)
89+
{
90+
case _InvalidAlgorithmKey:
91+
InvalidAlgorithm = info.GetString(_InvalidAlgorithmKey);
92+
break;
93+
94+
default:
95+
// Ignore other fields.
96+
break;
97+
}
98+
}
99+
}
100+
101+
/// <inheritdoc/>
102+
public override void GetObjectData(SerializationInfo info, StreamingContext context)
103+
{
104+
base.GetObjectData(info, context);
105+
106+
if (!string.IsNullOrEmpty(InvalidAlgorithm))
107+
info.AddValue(_InvalidAlgorithmKey, InvalidAlgorithm);
108+
}
109+
}
110+
}

src/Microsoft.IdentityModel.Tokens/LogMessages.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,8 @@ internal static class LogMessages
197197
public const string IDX10693 = "IDX10693: RSACryptoServiceProvider doesn't support the RSASSA-PSS signature algorithm. The list of supported algorithms is available here: https://aka.ms/IdentityModel/supported-algorithms";
198198
public const string IDX10694 = "IDX10694: JsonWebKeyConverter threw attempting to convert JsonWebKey: '{0}'. Exception: '{1}'.";
199199
public const string IDX10695 = "IDX10695: Unable to create a JsonWebKey from an ECDsa object. Required ECParameters structure is not supported by .NET Framework < 4.7.";
200+
public const string IDX10696 = "IDX10696: The algorithm '{0}' is not in the user-defined accepted list of algorithms.";
201+
public const string IDX10697 = "IDX10697: The user defined 'Delegate' AlgorithmValidator specified on TokenValidationParameters returned false when validating Algorithm: '{0}', SecurityKey: '{1}'.";
200202

201203
// security keys
202204
public const string IDX10700 = "IDX10700: {0} is unable to use 'rsaParameters'. {1} is null.";

src/Microsoft.IdentityModel.Tokens/TokenValidationParameters.cs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,16 @@
3333

3434
namespace Microsoft.IdentityModel.Tokens
3535
{
36+
/// <summary>
37+
/// Definition for AlgorithmValidator
38+
/// </summary>
39+
/// <param name="algorithm">The algorithm to validate.</param>
40+
/// <param name="securityKey">The <see cref="SecurityKey"/> that signed the <see cref="SecurityToken"/>.</param>
41+
/// <param name="securityToken">The <see cref="SecurityToken"/> being validated.</param>
42+
/// <param name="validationParameters"><see cref="TokenValidationParameters"/> required for validation.</param>
43+
/// <returns><c>true</c> if the algorithm is considered valid</returns>
44+
public delegate bool AlgorithmValidator(string algorithm, SecurityKey securityKey, SecurityToken securityToken, TokenValidationParameters validationParameters);
45+
3646
/// <summary>
3747
/// Definition for AudienceValidator.
3848
/// </summary>
@@ -156,6 +166,7 @@ protected TokenValidationParameters(TokenValidationParameters other)
156166
if (other == null)
157167
throw LogHelper.LogExceptionMessage(new ArgumentNullException(nameof(other)));
158168

169+
AlgorithmValidator = other.AlgorithmValidator;
159170
ActorValidationParameters = other.ActorValidationParameters?.Clone();
160171
AudienceValidator = other.AudienceValidator;
161172
_authenticationType = other._authenticationType;
@@ -192,6 +203,7 @@ protected TokenValidationParameters(TokenValidationParameters other)
192203
ValidateIssuerSigningKey = other.ValidateIssuerSigningKey;
193204
ValidateLifetime = other.ValidateLifetime;
194205
ValidateTokenReplay = other.ValidateTokenReplay;
206+
ValidAlgorithms = other.ValidAlgorithms;
195207
ValidAudience = other.ValidAudience;
196208
ValidAudiences = other.ValidAudiences;
197209
ValidIssuer = other.ValidIssuer;
@@ -222,6 +234,15 @@ public TokenValidationParameters()
222234
/// </summary>
223235
public TokenValidationParameters ActorValidationParameters { get; set; }
224236

237+
/// <summary>
238+
/// Gets or sets a delegate used to validate the cryptographic algorithm used.
239+
/// </summary>
240+
/// <remarks>
241+
/// If set, this delegate will validate the cryptographic algorithm used and
242+
/// the algorithm will not be checked against <see cref="ValidAlgorithms"/>.
243+
/// </remarks>
244+
public AlgorithmValidator AlgorithmValidator { get; set; }
245+
225246
/// <summary>
226247
/// Gets or sets a delegate that will be used to validate the audience.
227248
/// </summary>
@@ -605,6 +626,14 @@ public string RoleClaimType
605626
[DefaultValue(false)]
606627
public bool ValidateTokenReplay { get; set; }
607628

629+
/// <summary>
630+
/// Gets or sets the valid algorithms for cryptographic operations.
631+
/// </summary>
632+
/// <remarks>
633+
/// If set to a non-empty collection, only the algorithms listed will be considered valid.
634+
/// </remarks>
635+
public IEnumerable<string> ValidAlgorithms { get; set; }
636+
608637
/// <summary>
609638
/// Gets or sets a string that represents a valid audience that will be used to check against the token's audience.
610639
/// </summary>

src/Microsoft.IdentityModel.Tokens/Validators.cs

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,40 @@ namespace Microsoft.IdentityModel.Tokens
3838
/// </summary>
3939
public static class Validators
4040
{
41+
/// <summary>
42+
/// Validates if a given algorithm for a <see cref="SecurityKey"/> is valid.
43+
/// </summary>
44+
/// <param name="algorithm">The algorithm to be validated.</param>
45+
/// <param name="securityKey">The <see cref="SecurityKey"/> that signed the <see cref="SecurityToken"/>.</param>
46+
/// <param name="securityToken">The <see cref="SecurityToken"/> being validated.</param>
47+
/// <param name="validationParameters"><see cref="TokenValidationParameters"/> required for validation.</param>
48+
public static void ValidateAlgorithm(string algorithm, SecurityKey securityKey, SecurityToken securityToken, TokenValidationParameters validationParameters)
49+
{
50+
if (validationParameters == null)
51+
throw LogHelper.LogArgumentNullException(nameof(validationParameters));
52+
53+
if (validationParameters.AlgorithmValidator != null)
54+
{
55+
if (!validationParameters.AlgorithmValidator(algorithm, securityKey, securityToken, validationParameters))
56+
{
57+
throw LogHelper.LogExceptionMessage(new SecurityTokenInvalidAlgorithmException(LogHelper.FormatInvariant(LogMessages.IDX10697, algorithm, securityKey))
58+
{
59+
InvalidAlgorithm = algorithm,
60+
});
61+
}
62+
63+
return;
64+
}
65+
66+
if (validationParameters.ValidAlgorithms != null && validationParameters.ValidAlgorithms.Any() && !validationParameters.ValidAlgorithms.Contains(algorithm, StringComparer.Ordinal))
67+
{
68+
throw LogHelper.LogExceptionMessage(new SecurityTokenInvalidAlgorithmException(LogHelper.FormatInvariant(LogMessages.IDX10696, algorithm))
69+
{
70+
InvalidAlgorithm = algorithm,
71+
});
72+
}
73+
}
74+
4175
/// <summary>
4276
/// Determines if the audiences found in a <see cref="SecurityToken"/> are valid.
4377
/// </summary>

src/System.IdentityModel.Tokens.Jwt/JwtSecurityTokenHandler.cs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -827,10 +827,13 @@ public override string WriteToken(SecurityToken token)
827827
/// <param name="signature">Signature to compare against.</param>
828828
/// <param name="key"><See cref="SecurityKey"/> to use.</param>
829829
/// <param name="algorithm">Crypto algorithm to use.</param>
830+
/// <param name="securityToken">The <see cref="SecurityToken"/> being validated.</param>
830831
/// <param name="validationParameters">Priority will be given to <see cref="TokenValidationParameters.CryptoProviderFactory"/> over <see cref="SecurityKey.CryptoProviderFactory"/>.</param>
831832
/// <returns>'true' if signature is valid.</returns>
832-
private bool ValidateSignature(byte[] encodedBytes, byte[] signature, SecurityKey key, string algorithm, TokenValidationParameters validationParameters)
833+
private bool ValidateSignature(byte[] encodedBytes, byte[] signature, SecurityKey key, string algorithm, SecurityToken securityToken, TokenValidationParameters validationParameters)
833834
{
835+
Validators.ValidateAlgorithm(algorithm, key, securityToken, validationParameters);
836+
834837
var cryptoProviderFactory = validationParameters.CryptoProviderFactory ?? key.CryptoProviderFactory;
835838
var signatureProvider = cryptoProviderFactory.CreateForVerifying(key, algorithm);
836839
if (signatureProvider == null)
@@ -953,7 +956,7 @@ protected virtual JwtSecurityToken ValidateSignature(string token, TokenValidati
953956
{
954957
try
955958
{
956-
if (ValidateSignature(encodedBytes, signatureBytes, key, jwtToken.Header.Alg, validationParameters))
959+
if (ValidateSignature(encodedBytes, signatureBytes, key, jwtToken.Header.Alg, jwtToken, validationParameters))
957960
{
958961
LogHelper.LogInformation(TokenLogMessages.IDX10242, token);
959962
jwtToken.SigningKey = key;
@@ -1347,6 +1350,7 @@ protected string DecryptToken(JwtSecurityToken jwtToken, TokenValidationParamete
13471350

13481351
try
13491352
{
1353+
Validators.ValidateAlgorithm(jwtToken.Header.Enc, key, jwtToken, validationParameters);
13501354
decryptedTokenBytes = DecryptToken(jwtToken, cryptoProviderFactory, key);
13511355
decryptionSucceeded = true;
13521356
break;

0 commit comments

Comments
 (0)