Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -1320,7 +1320,7 @@ internal IEnumerable<SecurityKey> GetContentEncryptionKeys(JsonWebToken jwtToken
// 2. ResolveTokenDecryptionKey returned null
// 3. ResolveTokenDecryptionKeyFromConfig returned null
// Try all the keys. This is the degenerate case, not concerned about perf.
if (keys == null)
if (validationParameters.TryAllDecryptionKeys && keys.IsNullOrEmpty())
{
keys = JwtTokenUtilities.GetAllDecryptionKeys(validationParameters);
if (configuration != null)
Expand All @@ -1335,60 +1335,62 @@ internal IEnumerable<SecurityKey> GetContentEncryptionKeys(JsonWebToken jwtToken
// keep track of exceptions thrown, keys that were tried
StringBuilder exceptionStrings = null;
StringBuilder keysAttempted = null;
foreach (var key in keys)
if (keys != null)
{
try
foreach (var key in keys)
{
#if NET472 || NET6_0_OR_GREATER
if (SupportedAlgorithms.EcdsaWrapAlgorithms.Contains(jwtToken.Alg))
try
{
ECDsaSecurityKey publicKey;

// Since developers may have already worked around this issue, implicitly taking a dependency on the
// old behavior, we guard the new behavior behind an AppContext switch. The new/RFC-conforming behavior
// is treated as opt-in. When the library is at the point where it is able to make breaking changes
// (such as the next major version update) we should consider whether or not this app-compat switch
// needs to be maintained.
if (AppContextSwitches.UseRfcDefinitionOfEpkAndKid)
#if NET472 || NET6_0_OR_GREATER
if (SupportedAlgorithms.EcdsaWrapAlgorithms.Contains(jwtToken.Alg))
{
// on decryption we get the public key from the EPK value see: https://datatracker.ietf.org/doc/html/rfc7518#appendix-C
jwtToken.TryGetHeaderValue(JwtHeaderParameterNames.Epk, out string epk);
publicKey = new ECDsaSecurityKey(new JsonWebKey(epk), false);
ECDsaSecurityKey publicKey;

// Since developers may have already worked around this issue, implicitly taking a dependency on the
// old behavior, we guard the new behavior behind an AppContext switch. The new/RFC-conforming behavior
// is treated as opt-in. When the library is at the point where it is able to make breaking changes
// (such as the next major version update) we should consider whether or not this app-compat switch
// needs to be maintained.
if (AppContextSwitches.UseRfcDefinitionOfEpkAndKid)
{
// on decryption we get the public key from the EPK value see: https://datatracker.ietf.org/doc/html/rfc7518#appendix-C
jwtToken.TryGetHeaderValue(JwtHeaderParameterNames.Epk, out string epk);
publicKey = new ECDsaSecurityKey(new JsonWebKey(epk), false);
}
else
{
publicKey = validationParameters.TokenDecryptionKey as ECDsaSecurityKey;
}

var ecdhKeyExchangeProvider = new EcdhKeyExchangeProvider(
key as ECDsaSecurityKey,
publicKey,
jwtToken.Alg,
jwtToken.Enc);
jwtToken.TryGetHeaderValue(JwtHeaderParameterNames.Apu, out string apu);
jwtToken.TryGetHeaderValue(JwtHeaderParameterNames.Apv, out string apv);
SecurityKey kdf = ecdhKeyExchangeProvider.GenerateKdf(apu, apv);
var kwp = key.CryptoProviderFactory.CreateKeyWrapProviderForUnwrap(kdf, ecdhKeyExchangeProvider.GetEncryptionAlgorithm());
var unwrappedKey = kwp.UnwrapKey(Base64UrlEncoder.DecodeBytes(jwtToken.EncryptedKey));
unwrappedKeys.Add(new SymmetricSecurityKey(unwrappedKey));
}
else
#endif
if (key.CryptoProviderFactory.IsSupportedAlgorithm(jwtToken.Alg, key))
{
publicKey = validationParameters.TokenDecryptionKey as ECDsaSecurityKey;
var kwp = key.CryptoProviderFactory.CreateKeyWrapProviderForUnwrap(key, jwtToken.Alg);
var unwrappedKey = kwp.UnwrapKey(jwtToken.EncryptedKeyBytes);
unwrappedKeys.Add(new SymmetricSecurityKey(unwrappedKey));
}

var ecdhKeyExchangeProvider = new EcdhKeyExchangeProvider(
key as ECDsaSecurityKey,
publicKey,
jwtToken.Alg,
jwtToken.Enc);
jwtToken.TryGetHeaderValue(JwtHeaderParameterNames.Apu, out string apu);
jwtToken.TryGetHeaderValue(JwtHeaderParameterNames.Apv, out string apv);
SecurityKey kdf = ecdhKeyExchangeProvider.GenerateKdf(apu, apv);
var kwp = key.CryptoProviderFactory.CreateKeyWrapProviderForUnwrap(kdf, ecdhKeyExchangeProvider.GetEncryptionAlgorithm());
var unwrappedKey = kwp.UnwrapKey(Base64UrlEncoder.DecodeBytes(jwtToken.EncryptedKey));
unwrappedKeys.Add(new SymmetricSecurityKey(unwrappedKey));
}
else
#endif
if (key.CryptoProviderFactory.IsSupportedAlgorithm(jwtToken.Alg, key))
catch (Exception ex)
{
var kwp = key.CryptoProviderFactory.CreateKeyWrapProviderForUnwrap(key, jwtToken.Alg);
var unwrappedKey = kwp.UnwrapKey(jwtToken.EncryptedKeyBytes);
unwrappedKeys.Add(new SymmetricSecurityKey(unwrappedKey));
(exceptionStrings ??= new StringBuilder()).AppendLine(ex.ToString());
}
}
catch (Exception ex)
{
(exceptionStrings ??= new StringBuilder()).AppendLine(ex.ToString());
}

(keysAttempted ??= new StringBuilder()).AppendLine(key.KeyId);
(keysAttempted ??= new StringBuilder()).AppendLine(key.KeyId);
}
}

if (unwrappedKeys.Count > 0 || exceptionStrings is null)
return unwrappedKeys;
else
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ internal ValidationResult<string> DecryptToken(
// 2. ResolveTokenDecryptionKey returned null
// 3. ResolveTokenDecryptionKeyFromConfig returned null
// Try all the keys. This is the degenerate case, not concerned about perf.
if (keys == null)
if (validationParameters.TryAllDecryptionKeys && keys.IsNullOrEmpty())
{
keys = validationParameters.TokenDecryptionKeys;
if (configuration != null)
Expand Down
2 changes: 1 addition & 1 deletion src/Microsoft.IdentityModel.Tokens/LogMessages.cs
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ internal static class LogMessages
public const string IDX10617 = "IDX10617: Encryption failed. Keywrap is only supported for: '{0}', '{1}' and '{2}'. The content encryption specified is: '{3}'.";
public const string IDX10618 = "IDX10618: Key unwrap failed using decryption Keys: '{0}'.\nExceptions caught:\n '{1}'.\ntoken: '{2}'.";
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.";
public const string IDX10620 = "IDX10620: Unable to obtain a CryptoProviderFactory, both EncryptingCredentials.CryptoProviderFactory and EncryptingCredentials.Key.CrypoProviderFactory are null.";
public const string IDX10620 = "IDX10620: Unable to obtain a CryptoProviderFactory, both EncryptingCredentials.CryptoProviderFactory and EncryptingCredentials.Key.CryptoProviderFactory are null.";
//public const string IDX10903 = "IDX10903: Token decryption succeeded. With thumbprint: '{0}'.";
public const string IDX10904 = "IDX10904: Token decryption key : '{0}' found in TokenValidationParameters.";
public const string IDX10905 = "IDX10905: Token decryption key : '{0}' found in Configuration/Metadata.";
Expand Down
2 changes: 2 additions & 0 deletions src/Microsoft.IdentityModel.Tokens/PublicAPI.Unshipped.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
Microsoft.IdentityModel.Tokens.SecurityTokenDescriptor.IncludeKeyIdInHeader.get -> bool
Microsoft.IdentityModel.Tokens.SecurityTokenDescriptor.IncludeKeyIdInHeader.set -> void
static Microsoft.IdentityModel.Tokens.JsonWebKeyConverter.TryConvertToSecurityKey(Microsoft.IdentityModel.Tokens.JsonWebKey webKey, out Microsoft.IdentityModel.Tokens.SecurityKey key) -> bool
Microsoft.IdentityModel.Tokens.TokenValidationParameters.TryAllDecryptionKeys.get -> bool
Microsoft.IdentityModel.Tokens.TokenValidationParameters.TryAllDecryptionKeys.set -> void
19 changes: 17 additions & 2 deletions src/Microsoft.IdentityModel.Tokens/TokenValidationParameters.cs
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ protected TokenValidationParameters(TokenValidationParameters other)
TokenReplayCache = other.TokenReplayCache;
TokenReplayValidator = other.TokenReplayValidator;
TransformBeforeSignatureValidation = other.TransformBeforeSignatureValidation;
TryAllDecryptionKeys = other.TryAllDecryptionKeys;
TryAllIssuerSigningKeys = other.TryAllIssuerSigningKeys;
TypeValidator = other.TypeValidator;
ValidateActor = other.ValidateActor;
Expand Down Expand Up @@ -118,6 +119,7 @@ public TokenValidationParameters()
RequireSignedTokens = true;
RequireAudience = true;
SaveSigninToken = false;
TryAllDecryptionKeys = true;
TryAllIssuerSigningKeys = true;
ValidateActor = false;
ValidateAudience = true;
Expand Down Expand Up @@ -552,10 +554,13 @@ public string RoleClaimType
/// <summary>
/// Gets or sets the <see cref="SecurityKey"/> that is to be used for decryption.
/// </summary>
/// <remarks>
/// This <see cref="TokenDecryptionKey"/> will only be used if its <see cref="SecurityKey.KeyId"/> matches the 'kid' parameter in the token.
/// </remarks>
public SecurityKey TokenDecryptionKey { get; set; }

/// <summary>
/// Gets or sets a delegate that will be called to retreive a <see cref="SecurityKey"/> used for decryption.
/// Gets or sets a delegate that will be called to retrieve a <see cref="SecurityKey"/> used for decryption.
/// </summary>
/// <remarks>
/// 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.
Expand All @@ -565,6 +570,9 @@ public string RoleClaimType
/// <summary>
/// Gets or sets the <see cref="IEnumerable{SecurityKey}"/> that is to be used for decrypting inbound tokens.
/// </summary>
/// <remarks>
/// 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.
/// </remarks>
public IEnumerable<SecurityKey> TokenDecryptionKeys { get; set; }

/// <summary>
Expand Down Expand Up @@ -592,7 +600,14 @@ public string RoleClaimType
public TokenReplayValidator TokenReplayValidator { get; set; }

/// <summary>
/// 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.
/// 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.
/// The default is <c>true</c>.
/// </summary>
[DefaultValue(true)]
public bool TryAllDecryptionKeys { get; set; }

/// <summary>
/// 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.
/// The default is <c>true</c>.
/// </summary>
[DefaultValue(true)]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ protected ValidationParameters(ValidationParameters other)
SaveSigninToken = other.SaveSigninToken;
_signatureValidator = other.SignatureValidator;
TimeProvider = other.TimeProvider;
TryAllDecryptionKeys = other.TryAllDecryptionKeys;
TokenDecryptionKeyResolver = other.TokenDecryptionKeyResolver;
_tokenDecryptionKeys = other.TokenDecryptionKeys;
TokenReplayCache = other.TokenReplayCache;
Expand All @@ -112,6 +113,7 @@ public ValidationParameters()
{
LogTokenId = true;
SaveSigninToken = false;
TryAllDecryptionKeys = true;
ValidateActor = false;
}

Expand Down Expand Up @@ -478,6 +480,9 @@ public SignatureValidationDelegate? SignatureValidator
/// <summary>
/// Gets the <see cref="IList{T}"/> that is to be used for decrypting inbound tokens.
/// </summary>
/// <remarks>
/// 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.
/// </remarks>
public IList<SecurityKey> TokenDecryptionKeys
{
get
Expand Down Expand Up @@ -513,6 +518,13 @@ public TokenReplayValidationDelegate TokenReplayValidator
set { _tokenReplayValidator = value ?? throw new ArgumentNullException(nameof(value), "TokenReplayValidator cannot be set as null."); }
}

/// <summary>
/// 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.
/// The default is <c>true</c>.
/// </summary>
[DefaultValue(true)]
public bool TryAllDecryptionKeys { get; set; }

/// <summary>
/// If the IssuerSigningKeyResolver is unable to resolve the key when validating the signature of the SecurityToken,
/// all available keys will be tried.
Expand Down
Loading
Loading