Skip to content

Commit 35aaac2

Browse files
committed
Add TryAllDecryptionKeys flag.
1 parent 1a266e5 commit 35aaac2

File tree

8 files changed

+149
-75
lines changed

8 files changed

+149
-75
lines changed

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

Lines changed: 44 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1320,7 +1320,7 @@ internal IEnumerable<SecurityKey> GetContentEncryptionKeys(JsonWebToken jwtToken
13201320
// 2. ResolveTokenDecryptionKey returned null
13211321
// 3. ResolveTokenDecryptionKeyFromConfig returned null
13221322
// Try all the keys. This is the degenerate case, not concerned about perf.
1323-
if (keys == null)
1323+
if (validationParameters.TryAllDecryptionKeys && keys.IsNullOrEmpty())
13241324
{
13251325
keys = JwtTokenUtilities.GetAllDecryptionKeys(validationParameters);
13261326
if (configuration != null)
@@ -1335,60 +1335,62 @@ internal IEnumerable<SecurityKey> GetContentEncryptionKeys(JsonWebToken jwtToken
13351335
// keep track of exceptions thrown, keys that were tried
13361336
StringBuilder exceptionStrings = null;
13371337
StringBuilder keysAttempted = null;
1338-
foreach (var key in keys)
1338+
if (keys != null)
13391339
{
1340-
try
1340+
foreach (var key in keys)
13411341
{
1342-
#if NET472 || NET6_0_OR_GREATER
1343-
if (SupportedAlgorithms.EcdsaWrapAlgorithms.Contains(jwtToken.Alg))
1342+
try
13441343
{
1345-
ECDsaSecurityKey publicKey;
1346-
1347-
// Since developers may have already worked around this issue, implicitly taking a dependency on the
1348-
// old behavior, we guard the new behavior behind an AppContext switch. The new/RFC-conforming behavior
1349-
// is treated as opt-in. When the library is at the point where it is able to make breaking changes
1350-
// (such as the next major version update) we should consider whether or not this app-compat switch
1351-
// needs to be maintained.
1352-
if (AppContextSwitches.UseRfcDefinitionOfEpkAndKid)
1344+
#if NET472 || NET6_0_OR_GREATER
1345+
if (SupportedAlgorithms.EcdsaWrapAlgorithms.Contains(jwtToken.Alg))
13531346
{
1354-
// on decryption we get the public key from the EPK value see: https://datatracker.ietf.org/doc/html/rfc7518#appendix-C
1355-
jwtToken.TryGetHeaderValue(JwtHeaderParameterNames.Epk, out string epk);
1356-
publicKey = new ECDsaSecurityKey(new JsonWebKey(epk), false);
1347+
ECDsaSecurityKey publicKey;
1348+
1349+
// Since developers may have already worked around this issue, implicitly taking a dependency on the
1350+
// old behavior, we guard the new behavior behind an AppContext switch. The new/RFC-conforming behavior
1351+
// is treated as opt-in. When the library is at the point where it is able to make breaking changes
1352+
// (such as the next major version update) we should consider whether or not this app-compat switch
1353+
// needs to be maintained.
1354+
if (AppContextSwitches.UseRfcDefinitionOfEpkAndKid)
1355+
{
1356+
// on decryption we get the public key from the EPK value see: https://datatracker.ietf.org/doc/html/rfc7518#appendix-C
1357+
jwtToken.TryGetHeaderValue(JwtHeaderParameterNames.Epk, out string epk);
1358+
publicKey = new ECDsaSecurityKey(new JsonWebKey(epk), false);
1359+
}
1360+
else
1361+
{
1362+
publicKey = validationParameters.TokenDecryptionKey as ECDsaSecurityKey;
1363+
}
1364+
1365+
var ecdhKeyExchangeProvider = new EcdhKeyExchangeProvider(
1366+
key as ECDsaSecurityKey,
1367+
publicKey,
1368+
jwtToken.Alg,
1369+
jwtToken.Enc);
1370+
jwtToken.TryGetHeaderValue(JwtHeaderParameterNames.Apu, out string apu);
1371+
jwtToken.TryGetHeaderValue(JwtHeaderParameterNames.Apv, out string apv);
1372+
SecurityKey kdf = ecdhKeyExchangeProvider.GenerateKdf(apu, apv);
1373+
var kwp = key.CryptoProviderFactory.CreateKeyWrapProviderForUnwrap(kdf, ecdhKeyExchangeProvider.GetEncryptionAlgorithm());
1374+
var unwrappedKey = kwp.UnwrapKey(Base64UrlEncoder.DecodeBytes(jwtToken.EncryptedKey));
1375+
unwrappedKeys.Add(new SymmetricSecurityKey(unwrappedKey));
13571376
}
13581377
else
1378+
#endif
1379+
if (key.CryptoProviderFactory.IsSupportedAlgorithm(jwtToken.Alg, key))
13591380
{
1360-
publicKey = validationParameters.TokenDecryptionKey as ECDsaSecurityKey;
1381+
var kwp = key.CryptoProviderFactory.CreateKeyWrapProviderForUnwrap(key, jwtToken.Alg);
1382+
var unwrappedKey = kwp.UnwrapKey(jwtToken.EncryptedKeyBytes);
1383+
unwrappedKeys.Add(new SymmetricSecurityKey(unwrappedKey));
13611384
}
1362-
1363-
var ecdhKeyExchangeProvider = new EcdhKeyExchangeProvider(
1364-
key as ECDsaSecurityKey,
1365-
publicKey,
1366-
jwtToken.Alg,
1367-
jwtToken.Enc);
1368-
jwtToken.TryGetHeaderValue(JwtHeaderParameterNames.Apu, out string apu);
1369-
jwtToken.TryGetHeaderValue(JwtHeaderParameterNames.Apv, out string apv);
1370-
SecurityKey kdf = ecdhKeyExchangeProvider.GenerateKdf(apu, apv);
1371-
var kwp = key.CryptoProviderFactory.CreateKeyWrapProviderForUnwrap(kdf, ecdhKeyExchangeProvider.GetEncryptionAlgorithm());
1372-
var unwrappedKey = kwp.UnwrapKey(Base64UrlEncoder.DecodeBytes(jwtToken.EncryptedKey));
1373-
unwrappedKeys.Add(new SymmetricSecurityKey(unwrappedKey));
13741385
}
1375-
else
1376-
#endif
1377-
if (key.CryptoProviderFactory.IsSupportedAlgorithm(jwtToken.Alg, key))
1386+
catch (Exception ex)
13781387
{
1379-
var kwp = key.CryptoProviderFactory.CreateKeyWrapProviderForUnwrap(key, jwtToken.Alg);
1380-
var unwrappedKey = kwp.UnwrapKey(jwtToken.EncryptedKeyBytes);
1381-
unwrappedKeys.Add(new SymmetricSecurityKey(unwrappedKey));
1388+
(exceptionStrings ??= new StringBuilder()).AppendLine(ex.ToString());
13821389
}
1383-
}
1384-
catch (Exception ex)
1385-
{
1386-
(exceptionStrings ??= new StringBuilder()).AppendLine(ex.ToString());
1387-
}
13881390

1389-
(keysAttempted ??= new StringBuilder()).AppendLine(key.KeyId);
1391+
(keysAttempted ??= new StringBuilder()).AppendLine(key.KeyId);
1392+
}
13901393
}
1391-
13921394
if (unwrappedKeys.Count > 0 || exceptionStrings is null)
13931395
return unwrappedKeys;
13941396
else

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,7 @@ internal ValidationResult<string> DecryptToken(
121121
// 2. ResolveTokenDecryptionKey returned null
122122
// 3. ResolveTokenDecryptionKeyFromConfig returned null
123123
// Try all the keys. This is the degenerate case, not concerned about perf.
124-
if (keys == null)
124+
if (validationParameters.TryAllDecryptionKeys && keys.IsNullOrEmpty())
125125
{
126126
keys = validationParameters.TokenDecryptionKeys;
127127
if (configuration != null)

src/Microsoft.IdentityModel.Tokens/LogMessages.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,7 @@ internal static class LogMessages
142142
public const string IDX10617 = "IDX10617: Encryption failed. Keywrap is only supported for: '{0}', '{1}' and '{2}'. The content encryption specified is: '{3}'.";
143143
public const string IDX10618 = "IDX10618: Key unwrap failed using decryption Keys: '{0}'.\nExceptions caught:\n '{1}'.\ntoken: '{2}'.";
144144
public const string IDX10619 = "IDX10619: Decryption failed. Algorithm: '{0}'. Either the Encryption Algorithm: '{1}' or none of the Security Keys are supported by the CryptoProviderFactory.";
145-
public const string IDX10620 = "IDX10620: Unable to obtain a CryptoProviderFactory, both EncryptingCredentials.CryptoProviderFactory and EncryptingCredentials.Key.CrypoProviderFactory are null.";
145+
public const string IDX10620 = "IDX10620: Unable to obtain a CryptoProviderFactory, both EncryptingCredentials.CryptoProviderFactory and EncryptingCredentials.Key.CryptoProviderFactory are null.";
146146
//public const string IDX10903 = "IDX10903: Token decryption succeeded. With thumbprint: '{0}'.";
147147
public const string IDX10904 = "IDX10904: Token decryption key : '{0}' found in TokenValidationParameters.";
148148
public const string IDX10905 = "IDX10905: Token decryption key : '{0}' found in Configuration/Metadata.";
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
11
Microsoft.IdentityModel.Tokens.SecurityTokenDescriptor.IncludeKeyIdInHeader.get -> bool
22
Microsoft.IdentityModel.Tokens.SecurityTokenDescriptor.IncludeKeyIdInHeader.set -> void
33
static Microsoft.IdentityModel.Tokens.JsonWebKeyConverter.TryConvertToSecurityKey(Microsoft.IdentityModel.Tokens.JsonWebKey webKey, out Microsoft.IdentityModel.Tokens.SecurityKey key) -> bool
4+
Microsoft.IdentityModel.Tokens.TokenValidationParameters.TryAllDecryptionKeys.get -> bool
5+
Microsoft.IdentityModel.Tokens.TokenValidationParameters.TryAllDecryptionKeys.set -> void

src/Microsoft.IdentityModel.Tokens/TokenValidationParameters.cs

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ protected TokenValidationParameters(TokenValidationParameters other)
8989
TokenReplayCache = other.TokenReplayCache;
9090
TokenReplayValidator = other.TokenReplayValidator;
9191
TransformBeforeSignatureValidation = other.TransformBeforeSignatureValidation;
92+
TryAllDecryptionKeys = other.TryAllDecryptionKeys;
9293
TryAllIssuerSigningKeys = other.TryAllIssuerSigningKeys;
9394
TypeValidator = other.TypeValidator;
9495
ValidateActor = other.ValidateActor;
@@ -118,6 +119,7 @@ public TokenValidationParameters()
118119
RequireSignedTokens = true;
119120
RequireAudience = true;
120121
SaveSigninToken = false;
122+
TryAllDecryptionKeys = true;
121123
TryAllIssuerSigningKeys = true;
122124
ValidateActor = false;
123125
ValidateAudience = true;
@@ -552,10 +554,13 @@ public string RoleClaimType
552554
/// <summary>
553555
/// Gets or sets the <see cref="SecurityKey"/> that is to be used for decryption.
554556
/// </summary>
557+
/// <remarks>
558+
/// This <see cref="TokenDecryptionKey"/> will only be used if its <see cref="SecurityKey.KeyId"/> matches the 'kid' parameter in the token.
559+
/// </remarks>
555560
public SecurityKey TokenDecryptionKey { get; set; }
556561

557562
/// <summary>
558-
/// Gets or sets a delegate that will be called to retreive a <see cref="SecurityKey"/> used for decryption.
563+
/// Gets or sets a delegate that will be called to retrieve a <see cref="SecurityKey"/> used for decryption.
559564
/// </summary>
560565
/// <remarks>
561566
/// This <see cref="SecurityKey"/> will be used to decrypt the token. This can be helpful when the <see cref="SecurityToken"/> does not contain a key identifier.
@@ -565,6 +570,9 @@ public string RoleClaimType
565570
/// <summary>
566571
/// Gets or sets the <see cref="IEnumerable{SecurityKey}"/> that is to be used for decrypting inbound tokens.
567572
/// </summary>
573+
/// <remarks>
574+
/// The decryption keys in this <see cref="TokenDecryptionKeys"/> collection will only be used if their <see cref="SecurityKey.KeyId"/> matches the 'kid' parameter in the token.
575+
/// </remarks>
568576
public IEnumerable<SecurityKey> TokenDecryptionKeys { get; set; }
569577

570578
/// <summary>
@@ -592,7 +600,14 @@ public string RoleClaimType
592600
public TokenReplayValidator TokenReplayValidator { get; set; }
593601

594602
/// <summary>
595-
/// Gets or sets a value indicating whether all <see cref="IssuerSigningKeys"/> should be tried during signature validation when a key is not matched to token kid or if token kid is empty.
603+
/// Gets or sets a value indicating whether all <see cref="TokenDecryptionKeys"/> should be tried during token decryption when a key is not matched to token 'kid' or if token 'kid' is empty.
604+
/// The default is <c>true</c>.
605+
/// </summary>
606+
[DefaultValue(true)]
607+
public bool TryAllDecryptionKeys { get; set; }
608+
609+
/// <summary>
610+
/// Gets or sets a value indicating whether all <see cref="IssuerSigningKeys"/> should be tried during signature validation when a key is not matched to token 'kid' or if token 'kid' is empty.
596611
/// The default is <c>true</c>.
597612
/// </summary>
598613
[DefaultValue(true)]

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

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@ protected ValidationParameters(ValidationParameters other)
9292
SaveSigninToken = other.SaveSigninToken;
9393
_signatureValidator = other.SignatureValidator;
9494
TimeProvider = other.TimeProvider;
95+
TryAllDecryptionKeys = other.TryAllDecryptionKeys;
9596
TokenDecryptionKeyResolver = other.TokenDecryptionKeyResolver;
9697
_tokenDecryptionKeys = other.TokenDecryptionKeys;
9798
TokenReplayCache = other.TokenReplayCache;
@@ -112,6 +113,7 @@ public ValidationParameters()
112113
{
113114
LogTokenId = true;
114115
SaveSigninToken = false;
116+
TryAllDecryptionKeys = true;
115117
ValidateActor = false;
116118
}
117119

@@ -478,6 +480,9 @@ public SignatureValidationDelegate? SignatureValidator
478480
/// <summary>
479481
/// Gets the <see cref="IList{T}"/> that is to be used for decrypting inbound tokens.
480482
/// </summary>
483+
/// <remarks>
484+
/// The decryption keys in this <see cref="TokenDecryptionKeys"/> collection will only be used if their <see cref="SecurityKey.KeyId"/> matches the 'kid' parameter in the token.
485+
/// </remarks>
481486
public IList<SecurityKey> TokenDecryptionKeys
482487
{
483488
get
@@ -513,6 +518,13 @@ public TokenReplayValidationDelegate TokenReplayValidator
513518
set { _tokenReplayValidator = value ?? throw new ArgumentNullException(nameof(value), "TokenReplayValidator cannot be set as null."); }
514519
}
515520

521+
/// <summary>
522+
/// Gets or sets a value indicating whether all <see cref="TokenDecryptionKeys"/> should be tried during token decryption when a key is not matched to token 'kid' or if token 'kid' is empty.
523+
/// The default is <c>true</c>.
524+
/// </summary>
525+
[DefaultValue(true)]
526+
public bool TryAllDecryptionKeys { get; set; }
527+
516528
/// <summary>
517529
/// If the IssuerSigningKeyResolver is unable to resolve the key when validating the signature of the SecurityToken,
518530
/// all available keys will be tried.

0 commit comments

Comments
 (0)