Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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 @@ -129,7 +129,7 @@ await ValidateJWEAsync(jsonWebToken, validationParameters, currentConfiguration,
return result;
}

if (TokenUtilities.IsRecoverableExceptionType(result.UnwrapError().ExceptionType))
if (TokenUtilities.IsRecoverableExceptionType(result.UnwrapError().ExceptionType, (currentConfiguration != null && currentConfiguration.TokenDecryptionKeys.Count > 0)))
{
// If we were still unable to validate, attempt to refresh the configuration and validate using it
// but ONLY if the currentConfiguration is not null. We want to avoid refreshing the configuration on
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -511,7 +511,7 @@ await ValidateJWEAsync(jsonWebToken, validationParameters, currentConfiguration)

return tokenValidationResult;
}
else if (TokenUtilities.IsRecoverableException(tokenValidationResult.Exception))
else if (TokenUtilities.IsRecoverableException(tokenValidationResult.Exception, (currentConfiguration != null && currentConfiguration.TokenDecryptionKeys.Count > 0)))
{
// If we were still unable to validate, attempt to refresh the configuration and validate using it
// but ONLY if the currentConfiguration is not null. We want to avoid refreshing the configuration on
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,15 @@ internal static ValidationResult<string> DecryptJwtToken(
StringBuilder exceptionStrings = null;
StringBuilder keysAttempted = null;
string zipAlgorithm = null;
var jwtKidExists = false;
var jwtKidMatchedKeyId = false;

foreach (SecurityKey key in decryptionParameters.Keys)
{
jwtKidExists = !string.IsNullOrEmpty(jsonWebToken.Kid);
if (jwtKidExists && key?.KeyId != null)
jwtKidMatchedKeyId = jsonWebToken.Kid.Equals(key.KeyId, key is X509SecurityKey ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal);

var cryptoProviderFactory = validationParameters.CryptoProviderFactory ?? key.CryptoProviderFactory;
if (cryptoProviderFactory == null)
{
Expand Down Expand Up @@ -103,6 +110,7 @@ internal static ValidationResult<string> DecryptJwtToken(
algorithmNotSupportedByCryptoProvider,
exceptionStrings,
keysAttempted,
jwtKidExists && !jwtKidMatchedKeyId,
callContext);

try
Expand Down
28 changes: 28 additions & 0 deletions src/Microsoft.IdentityModel.JsonWebTokens/JwtTokenUtilities.cs
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,9 @@ internal static string DecryptJwtToken(
StringBuilder exceptionStrings = null;
StringBuilder keysAttempted = null;
string zipAlgorithm = null;
var jwtKidExists = false;
var jwtKidMatchedKeyId = false;

foreach (SecurityKey key in decryptionParameters.Keys)
{
var cryptoProviderFactory = validationParameters.CryptoProviderFactory ?? key.CryptoProviderFactory;
Expand All @@ -281,6 +284,10 @@ internal static string DecryptJwtToken(
// JsonWebToken from JsonWebTokenHandler
if (securityToken is JsonWebToken jsonWebToken)
{
jwtKidExists = !string.IsNullOrEmpty(jsonWebToken.Kid);
if (jwtKidExists && key?.KeyId != null)
jwtKidMatchedKeyId = jsonWebToken.Kid.Equals(key.KeyId, key is X509SecurityKey ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal);

if (!cryptoProviderFactory.IsSupportedAlgorithm(jsonWebToken.Enc, key))
{
if (LogHelper.IsEnabled(EventLogLevel.Warning))
Expand Down Expand Up @@ -347,6 +354,7 @@ internal static string DecryptJwtToken(
algorithmNotSupportedByCryptoProvider,
exceptionStrings,
keysAttempted,
jwtKidExists && !jwtKidMatchedKeyId,
null);

throw LogHelper.LogExceptionMessage(validationError.GetException());
Expand All @@ -370,11 +378,26 @@ private static ValidationError GetDecryptionError(
bool algorithmNotSupportedByCryptoProvider,
StringBuilder exceptionStrings,
StringBuilder keysAttempted,
bool unknownTokenDecryptKey,
#pragma warning disable CA1801 // Review unused parameters
CallContext callContext)
#pragma warning restore CA1801 // Review unused parameters
{
if (keysAttempted is not null)
{
if (unknownTokenDecryptKey)
{
return new ValidationError(
new MessageDetail(
TokenLogMessages.IDX10907,
LogHelper.MarkAsNonPII(keysAttempted.ToString()),
exceptionStrings?.ToString() ?? string.Empty,
LogHelper.MarkAsSecurityArtifact(decryptionParameters.EncodedToken, SafeLogJwtToken)),
ValidationFailureType.TokenDecryptionFailed,
typeof(SecurityTokenEncryptionKeyNotFoundException),
ValidationError.GetCurrentStackFrame());
}

return new ValidationError(
new MessageDetail(
TokenLogMessages.IDX10603,
Expand All @@ -384,7 +407,9 @@ private static ValidationError GetDecryptionError(
ValidationFailureType.TokenDecryptionFailed,
typeof(SecurityTokenDecryptionFailedException),
ValidationError.GetCurrentStackFrame());
}
else if (algorithmNotSupportedByCryptoProvider)
{
return new ValidationError(
new MessageDetail(
TokenLogMessages.IDX10619,
Expand All @@ -393,14 +418,17 @@ private static ValidationError GetDecryptionError(
ValidationFailureType.TokenDecryptionFailed,
typeof(SecurityTokenDecryptionFailedException),
ValidationError.GetCurrentStackFrame());
}
else
{
return new ValidationError(
new MessageDetail(
TokenLogMessages.IDX10609,
LogHelper.MarkAsSecurityArtifact(decryptionParameters.EncodedToken, SafeLogJwtToken)),
ValidationFailureType.TokenDecryptionFailed,
typeof(SecurityTokenDecryptionFailedException),
ValidationError.GetCurrentStackFrame());
}
}

private static byte[] DecryptToken(CryptoProviderFactory cryptoProviderFactory, SecurityKey key, string encAlg, byte[] ciphertext, byte[] headerAscii, byte[] initializationVector, byte[] authenticationTag)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ public SecurityTokenEncryptionKeyNotFoundException()
/// <summary>
/// Initializes a new instance of the <see cref="SecurityTokenEncryptionKeyNotFoundException"/> class.
/// </summary>
/// <param name="message">Addtional information to be included in the exception and displayed to user.</param>
/// <param name="message">Additional information to be included in the exception and displayed to user.</param>
public SecurityTokenEncryptionKeyNotFoundException(string message)
: base(message)
{
Expand All @@ -33,7 +33,7 @@ public SecurityTokenEncryptionKeyNotFoundException(string message)
/// <summary>
/// Initializes a new instance of the <see cref="SecurityTokenEncryptionKeyNotFoundException"/> class.
/// </summary>
/// <param name="message">Addtional information to be included in the exception and displayed to user.</param>
/// <param name="message">Additional information to be included in the exception and displayed to user.</param>
/// <param name="innerException">A <see cref="Exception"/> that represents the root cause of the exception.</param>
public SecurityTokenEncryptionKeyNotFoundException(string message, Exception innerException)
: base(message, innerException)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ public SecurityTokenSignatureKeyNotFoundException()
/// <summary>
/// Initializes a new instance of the <see cref="SecurityTokenSignatureKeyNotFoundException"/> class.
/// </summary>
/// <param name="message">Addtional information to be included in the exception and displayed to user.</param>
/// <param name="message">Additional information to be included in the exception and displayed to user.</param>
public SecurityTokenSignatureKeyNotFoundException(string message)
: base(message)
{
Expand All @@ -32,7 +32,7 @@ public SecurityTokenSignatureKeyNotFoundException(string message)
/// <summary>
/// Initializes a new instance of the <see cref="SecurityTokenSignatureKeyNotFoundException"/> class.
/// </summary>
/// <param name="message">Addtional information to be included in the exception and displayed to user.</param>
/// <param name="message">Additional information to be included in the exception and displayed to user.</param>
/// <param name="innerException">A <see cref="Exception"/> that represents the root cause of the exception.</param>
public SecurityTokenSignatureKeyNotFoundException(string message, Exception innerException)
: base(message, innerException)
Expand Down
3 changes: 2 additions & 1 deletion src/Microsoft.IdentityModel.Tokens/LogMessages.cs
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ internal static class LogMessages
// encryption / decryption
// public const string IDX10600 = "IDX10600:";
// public const string IDX10601 = "IDX10601:";
public const string IDX10603 = "IDX10603: Decryption failed. Keys tried: '{0}'.\nExceptions caught:\n '{1}'.\ntoken: '{2}'";
public const string IDX10603 = "IDX10603: Decryption failed. Keys tried: '{0}'.\nExceptions caught:\n '{1}'.\nToken: '{2}'";
// public const string IDX10604 = "IDX10604:";
// public const string IDX10605 = "IDX10605:";
// public const string IDX10606 = "IDX10606:";
Expand All @@ -146,6 +146,7 @@ internal static class LogMessages
//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.";
public const string IDX10907 = "IDX10907: Decryption failed. The token's kid did not match any keys in TokenValidationParameters or Configuration. Keys tried: '{0}'. \nExceptions caught:\n '{1}'.\nToken: '{2}'.";

// Formatting
public const string IDX10400 = "IDX10400: Unable to decode: '{0}' as Base64url encoded string.";
Expand Down
13 changes: 9 additions & 4 deletions src/Microsoft.IdentityModel.Tokens/TokenUtilities.cs
Original file line number Diff line number Diff line change
Expand Up @@ -245,14 +245,17 @@ internal static IEnumerable<Claim> MergeClaims(IEnumerable<Claim> claims, IEnume

/// <summary>
/// Check whether the given exception type is recoverable by LKG.
/// Decryption error is only recoverable, if the configuration has decryption keys in it.
/// </summary>
/// <param name="exception">The exception to check.</param>
/// <param name="configContainsDecryptionKeys">Whether the configuration contains decryption keys.</param>
/// <returns><c>true</c> if the exception is certain types of exceptions otherwise, <c>false</c>.</returns>
internal static bool IsRecoverableException(Exception exception)
internal static bool IsRecoverableException(Exception exception, bool configContainsDecryptionKeys)
{
return exception is SecurityTokenInvalidSignatureException
|| exception is SecurityTokenInvalidIssuerException
|| exception is SecurityTokenSignatureKeyNotFoundException;
|| exception is SecurityTokenSignatureKeyNotFoundException
|| (exception is SecurityTokenEncryptionKeyNotFoundException && configContainsDecryptionKeys);
}

/// <summary>
Expand Down Expand Up @@ -292,12 +295,14 @@ internal static bool IsRecoverableConfiguration(string kid, BaseConfiguration cu
/// Check whether the given exception type is recoverable by LKG.
/// </summary>
/// <param name="exceptionType">The exception type to check</param>
/// <param name="configContainsDecryptionKeys">Whether the configuration contains decryption keys.</param>
/// <returns><c>true</c> if the exception is certain types of exceptions otherwise, <c>false</c>.</returns>
internal static bool IsRecoverableExceptionType(Type exceptionType)
internal static bool IsRecoverableExceptionType(Type exceptionType, bool configContainsDecryptionKeys)
{
return exceptionType == typeof(SecurityTokenInvalidSignatureException) ||
exceptionType == typeof(SecurityTokenInvalidIssuerException) ||
exceptionType == typeof(SecurityTokenSignatureKeyNotFoundException);
exceptionType == typeof(SecurityTokenSignatureKeyNotFoundException) ||
(exceptionType == typeof(SecurityTokenEncryptionKeyNotFoundException) && configContainsDecryptionKeys);
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,8 @@ internal Exception CreateException(Type exceptionType, Exception? innerException
exception = new SecurityTokenSignatureKeyNotFoundException(MessageDetail.Message);
else if (exceptionType == typeof(SecurityTokenDecryptionFailedException))
exception = new SecurityTokenDecryptionFailedException(MessageDetail.Message);
else if (exceptionType == typeof(SecurityTokenEncryptionKeyNotFoundException))
exception = new SecurityTokenEncryptionKeyNotFoundException(MessageDetail.Message);
else if (exceptionType == typeof(SecurityTokenMalformedException))
exception = new SecurityTokenMalformedException(MessageDetail.Message);
else if (exceptionType == typeof(SecurityTokenInvalidSignatureException))
Expand Down Expand Up @@ -161,6 +163,8 @@ internal Exception CreateException(Type exceptionType, Exception? innerException
exception = new SecurityTokenInvalidIssuerException(MessageDetail.Message, innerException);
else if (exceptionType == typeof(SecurityTokenSignatureKeyNotFoundException))
exception = new SecurityTokenSignatureKeyNotFoundException(MessageDetail.Message, innerException);
else if (exceptionType == typeof(SecurityTokenEncryptionKeyNotFoundException))
exception = new SecurityTokenEncryptionKeyNotFoundException(MessageDetail.Message, innerException);
else if (exceptionType == typeof(SecurityTokenDecryptionFailedException))
exception = new SecurityTokenDecryptionFailedException(MessageDetail.Message, innerException);
else if (exceptionType == typeof(SecurityTokenMalformedException))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -883,7 +883,7 @@ private ClaimsPrincipal ValidateToken(string token, JwtSecurityToken outerToken,

return claimsPrincipal;
}
else if (TokenUtilities.IsRecoverableException(exceptionThrown.SourceException))
else if (TokenUtilities.IsRecoverableException(exceptionThrown.SourceException, (currentConfiguration != null && currentConfiguration.TokenDecryptionKeys.Count > 0)))
{
// If we were still unable to validate, attempt to refresh the configuration and validate using it
// but ONLY if the currentConfiguration is not null. We want to avoid refreshing the configuration on
Expand Down
Loading