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 @@ -5,6 +5,8 @@
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.IdentityModel.Logging;
using Microsoft.IdentityModel.Tokens;
using TokenLogMessages = Microsoft.IdentityModel.Tokens.LogMessages;
Expand All @@ -15,12 +17,55 @@ namespace Microsoft.IdentityModel.JsonWebTokens
public partial class JsonWebTokenHandler : TokenHandler
{
/// <summary>
/// Decrypts a JWE and returns the clear text.
/// Decrypts a JWE and returns the clear text. Decrypts using the keys from configuration
/// if no keys are specified in <paramref name="validationParameters"/>.
/// </summary>
/// <param name="jwtToken">The JWE that contains the cypher text.</param>
/// <param name="validationParameters">The <see cref="TokenValidationParameters"/> to be used for decrypting the token.</param>
/// <param name="callContext">A <see cref="CallContext"/> that contains call information.</param>
/// <param name="cancellationToken">A <see cref="CancellationToken"/> that can be used to request cancellation of the asynchronous operation.</param>
/// <returns>The decoded / cleartext contents of the JWE.</returns>
internal async Task<ValidationResult<string>> DecryptTokenWithConfigurationAsync(
JsonWebToken jwtToken,
ValidationParameters validationParameters,
CallContext? callContext,
CancellationToken cancellationToken)
{
if (jwtToken == null)
{
return ValidationError.NullParameter(
nameof(jwtToken),
ValidationError.GetCurrentStackFrame());
}

if (validationParameters == null)
{
return ValidationError.NullParameter(
nameof(validationParameters),
ValidationError.GetCurrentStackFrame());
}

if (string.IsNullOrEmpty(jwtToken.Enc))
{
return new ValidationError(
new MessageDetail(TokenLogMessages.IDX10612),
ValidationFailureType.TokenDecryptionFailed,
typeof(SecurityTokenException),
ValidationError.GetCurrentStackFrame());
}

BaseConfiguration? currentConfiguration = await GetCurrentConfigurationAsync(validationParameters, cancellationToken).ConfigureAwait(false);

return DecryptToken(jwtToken, validationParameters, currentConfiguration, callContext);
}

/// <summary>
/// Decrypts a JWE using the keys from <paramref name="validationParameters"/> and returns the clear text.
/// </summary>
/// <param name="jwtToken">The JWE that contains the cypher text.</param>
/// <param name="validationParameters">The <see cref="TokenValidationParameters"/> to be used for validating the token.</param>
/// <param name="configuration">The <see cref="BaseConfiguration"/> to be used for validating the token.</param>
/// <param name="callContext"></param>
/// <param name="callContext">A <see cref="CallContext"/> that contains call information.</param>
/// <returns>The decoded / cleartext contents of the JWE.</returns>
internal ValidationResult<string> DecryptToken(
JsonWebToken jwtToken,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ internal override async Task<ValidationResult<ValidatedToken>> ValidateTokenAsyn
}

BaseConfiguration? currentConfiguration =
await GetCurrentConfigurationAsync(validationParameters).ConfigureAwait(false);
await GetCurrentConfigurationAsync(validationParameters, cancellationToken).ConfigureAwait(false);

ValidationResult<ValidatedToken> result = jsonWebToken.IsEncrypted ?
await ValidateJWEAsync(jsonWebToken, validationParameters, currentConfiguration, callContext, cancellationToken).ConfigureAwait(false) :
Expand Down Expand Up @@ -422,14 +422,14 @@ await ValidateJWSAsync(actorToken, actorParameters, configuration, callContext,
};
}

private static async Task<BaseConfiguration?> GetCurrentConfigurationAsync(ValidationParameters validationParameters)
private static async Task<BaseConfiguration?> GetCurrentConfigurationAsync(ValidationParameters validationParameters, CancellationToken cancellationToken)
{
BaseConfiguration? currentConfiguration = null;
if (validationParameters.ConfigurationManager is not null)
{
try
{
currentConfiguration = await validationParameters.ConfigurationManager.GetBaseConfigurationAsync(CancellationToken.None).ConfigureAwait(false);
currentConfiguration = await validationParameters.ConfigurationManager.GetBaseConfigurationAsync(cancellationToken).ConfigureAwait(false);
}
#pragma warning disable CA1031 // Do not catch general exception types
catch
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
using System.Collections.Generic;
using System.Security.Claims;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.IdentityModel.Abstractions;
using Microsoft.IdentityModel.Logging;
using Microsoft.IdentityModel.Tokens;
Expand Down Expand Up @@ -315,6 +317,54 @@ private ClaimsIdentity CreateClaimsIdentityPrivate(JsonWebToken jwtToken, TokenV
return identity;
}

/// <summary>
/// Decrypts a JWE and returns the clear text.
/// </summary>
/// <param name="jwtToken">The JWE that contains the cypher text.</param>
/// <param name="validationParameters">The <see cref="TokenValidationParameters"/> to be used for validating the token.</param>
/// <param name="cancellationToken">A <see cref="CancellationToken"/> that can be used to request cancellation of the asynchronous operation.</param>
/// <returns>The decoded (clear text) contents of the JWE.</returns>
/// <exception cref="ArgumentNullException">Thrown if <paramref name="jwtToken"/> is null.</exception>
/// <exception cref="ArgumentNullException">Thrown if <paramref name="validationParameters"/> is null.</exception>
/// <exception cref="SecurityTokenException">Thrown if <see cref="JsonWebToken.Enc"/> is null or empty.</exception>
/// <exception cref="SecurityTokenDecompressionFailedException">Thrown if the decompression failed.</exception>
/// <exception cref="SecurityTokenEncryptionKeyNotFoundException">Thrown if <see cref="JsonWebToken.Kid"/> is not null AND the decryption fails.</exception>
/// <exception cref="SecurityTokenDecryptionFailedException">Thrown if the JWE was not able to be decrypted.</exception>
public async Task<string> DecryptTokenWithConfigurationAsync(
JsonWebToken jwtToken,
TokenValidationParameters validationParameters,
CancellationToken cancellationToken)
{
if (jwtToken == null)
throw LogHelper.LogArgumentNullException(nameof(jwtToken));

if (validationParameters == null)
throw LogHelper.LogArgumentNullException(nameof(validationParameters));

if (string.IsNullOrEmpty(jwtToken.Enc))
throw LogHelper.LogExceptionMessage(new SecurityTokenException(LogHelper.FormatInvariant(TokenLogMessages.IDX10612)));

BaseConfiguration currentConfiguration = null;
if (validationParameters.ConfigurationManager != null)
{
try
{
currentConfiguration = await validationParameters.ConfigurationManager.GetBaseConfigurationAsync(cancellationToken).ConfigureAwait(false);
}
#pragma warning disable CA1031 // Do not catch general exception types
catch (Exception ex)
#pragma warning restore CA1031 // Do not catch general exception types
{
// The exception is not re-thrown as the decryption key may be set
// on TokenValidationParameters, allowing the library to continue with token decryption.
if (LogHelper.IsEnabled(EventLogLevel.Warning))
LogHelper.LogWarning(LogHelper.FormatInvariant(TokenLogMessages.IDX10278, validationParameters.ConfigurationManager.MetadataAddress, ex.ToString()));
}
}

return DecryptToken(jwtToken, validationParameters, currentConfiguration);
}

/// <summary>
/// Decrypts a JWE and returns the clear text.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Microsoft.IdentityModel.JsonWebTokens.JsonWebTokenHandler.DecryptTokenWithConfigurationAsync(Microsoft.IdentityModel.JsonWebTokens.JsonWebToken jwtToken, Microsoft.IdentityModel.Tokens.TokenValidationParameters validationParameters, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task<string>
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
const Microsoft.IdentityModel.Tokens.AppContextSwitches.UseCapitalizedXMLTypeAttrSwitch = "Switch.Microsoft.IdentityModel.UseCapitalizedXMLTypeAttr" -> string
const Microsoft.IdentityModel.Tokens.LogMessages.IDX10278 = "IDX10278: Unable to retrieve configuration from authority: '{0}'. \nProceeding with token decryption in case the relevant properties have been set manually on the TokenValidationParameters. Exception caught: \n {1}. See https://aka.ms/validate-using-configuration-manager for additional information." -> string
static Microsoft.IdentityModel.Tokens.AppContextSwitches.UseCapitalizedXMLTypeAttr.get -> bool
1 change: 1 addition & 0 deletions src/Microsoft.IdentityModel.Tokens/LogMessages.cs
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ internal static class LogMessages
public const string IDX10275 = "IDX10275: TokenTypeValidationDelegate threw an exception, see inner exception.";
public const string IDX10276 = "IDX10276: TokenReplayValidationDelegate threw an exception, see inner exception.";
public const string IDX10277 = "IDX10277: RequireAudience property on ValidationParameters is set to false. Exiting without validating the audience.";
public const string IDX10278 = "IDX10278: Unable to retrieve configuration from authority: '{0}'. \nProceeding with token decryption in case the relevant properties have been set manually on the TokenValidationParameters. Exception caught: \n {1}. See https://aka.ms/validate-using-configuration-manager for additional information.";

// 10500 - SignatureValidation
public const string IDX10500 = "IDX10500: Signature validation failed. No security keys were provided to validate the signature.";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,60 @@
using Microsoft.IdentityModel.Tokens;
using Xunit;
using TokenLogMessages = Microsoft.IdentityModel.Tokens.LogMessages;
using System.Threading.Tasks;
using Microsoft.IdentityModel.Protocols;

namespace Microsoft.IdentityModel.JsonWebTokens.Tests
{
public class JsonWebTokenHandlerDecryptTokenTests
{
[Theory, MemberData(nameof(JsonWebTokenHandlerDecryptTokenTestCases), DisableDiscoveryEnumeration = false)]
public async Task DecryptTokenWithConfiguration(TokenDecryptingTheoryData theoryData)
{
JsonWebTokenHandler jsonWebTokenHandler = new JsonWebTokenHandler();
if (theoryData.Token == null)
{
string tokenString = null;
if (theoryData.SecurityTokenDescriptor != null)
tokenString = jsonWebTokenHandler.CreateToken(theoryData.SecurityTokenDescriptor);
else
tokenString = theoryData.TokenString;

if (tokenString != null)
theoryData.Token = new JsonWebToken(tokenString);
}

CompareContext context = TestUtilities.WriteHeader($"{this}.JsonWebTokenHandlerDecryptTokenTests", theoryData);
ValidationResult<string> result = await jsonWebTokenHandler.DecryptTokenWithConfigurationAsync(
theoryData.Token,
theoryData.ValidationParameters,
new CallContext(),
default);

if (result.IsValid)
{
IdentityComparer.AreStringsEqual(
result.UnwrapResult(),
theoryData.Result.UnwrapResult(),
context);

theoryData.ExpectedException.ProcessNoException(context);
}
else
{
ValidationError validationError = result.UnwrapError();
IdentityComparer.AreStringsEqual(
validationError.FailureType.Name,
theoryData.Result.UnwrapError().FailureType.Name,
context);

Exception exception = validationError.GetException();
theoryData.ExpectedException.ProcessException(exception, context);
}

TestUtilities.AssertFailIfErrors(context);
}

[Theory, MemberData(nameof(JsonWebTokenHandlerDecryptTokenTestCases), DisableDiscoveryEnumeration = false)]
public void DecryptToken(TokenDecryptingTheoryData theoryData)
{
Expand Down Expand Up @@ -188,7 +237,10 @@ static Dictionary<string, object> AdditionalEcdhEsHeaderParameters(JsonWebKey pu
{
TestId = "Valid_Aes128_FromConfiguration",
TokenString = ReferenceTokens.JWEDirectEncryptionUnsignedInnerJWTWithAdditionalHeaderClaims,
ValidationParameters = new ValidationParameters(),
ValidationParameters = new ValidationParameters
{
ConfigurationManager = new StaticConfigurationManager<BaseConfiguration>(new CustomConfiguration(Default.SymmetricEncryptingCredentials.Key))
},
Configuration = new CustomConfiguration(Default.SymmetricEncryptingCredentials.Key),
Result = "eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJlbWFpbCI6IkJvYkBjb250b3NvLmNvbSIsImdpdmVuX25hbWUiOiJCb2IiLCJpc3MiOiJodHRwOi8vRGVmYXVsdC5Jc3N1ZXIuY29tIiwiYXVkIjoiaHR0cDovL0RlZmF1bHQuQXVkaWVuY2UuY29tIiwiaWF0IjoiMTQ4OTc3NTYxNyIsIm5iZiI6IjE0ODk3NzU2MTciLCJleHAiOiIyNTM0MDIzMDA3OTkifQ.",
},
Expand Down Expand Up @@ -229,7 +281,10 @@ static Dictionary<string, object> AdditionalEcdhEsHeaderParameters(JsonWebKey pu
EncryptingCredentials = new EncryptingCredentials(KeyingMaterial.RsaSecurityKey_2048, SecurityAlgorithms.RsaPKCS1, SecurityAlgorithms.Aes128CbcHmacSha256),
Claims = Default.PayloadDictionary
},
ValidationParameters = new ValidationParameters(),
ValidationParameters = new ValidationParameters
{
ConfigurationManager = new StaticConfigurationManager<BaseConfiguration>(configurationThatThrows)
},
Configuration = configurationThatThrows,
Result = "eyJhbGciOiJSUzI1NiIsImtpZCI6Ikpzb25XZWJLZXlSc2FfMjA0OCIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJodHRwOi8vRGVmYXVsdC5BdWRpZW5jZS5jb20iLCJhenAiOiJodHRwOi8vRGVmYXVsdC5BenAuY29tIiwiZW1haWwiOiJCb2JAY29udG9zby5jb20iLCJleHAiOiIyNTM0MDIzMDA3OTkiLCJnaXZlbl9uYW1lIjoiQm9iIiwiaXNzIjoiaHR0cDovL0RlZmF1bHQuSXNzdWVyLmNvbSIsImlhdCI6IjE0ODk3NzU2MTciLCJqdGkiOiJKdGkiLCJuYmYiOiIxNDg5Nzc1NjE3In0.Et69LAC4sn6nNm_HNz_AnJ8siLT6LRTjDSb1aY8APcwJmPn-TxU-8GG5_bmNkoVukR7hkYG2JuWPxJKbjDd73BlmelaiyZBoPUyU0S-GX3XgyC2v_CkOq4yYbtD-kq5s7kNNj5QJjZDq0oJeqcUMrq4xRWATPtUMkIZ0GpEhO_C5MFxT8jAWe_a2gyUA4KoibalKtkYgFvgLcvyZJhUx7AERbli6b7OkUksFp9zIwmc_jZZCXJ_F_wASyj9KgHQKN9VHER3bB2zQeWHR0q32ODYC4ggsan-Nkm-jIsATi2tgkKzROzK55dy8ZdFArXUYJRpI_raYkTUHRK_wP3GqtQ",
},
Expand Down Expand Up @@ -266,7 +321,10 @@ static Dictionary<string, object> AdditionalEcdhEsHeaderParameters(JsonWebKey pu
EncryptingCredentials = new EncryptingCredentials(KeyingMaterial.RsaSecurityKey_2048, SecurityAlgorithms.RsaPKCS1, SecurityAlgorithms.Aes128CbcHmacSha256),
Claims = Default.PayloadDictionary
},
ValidationParameters = new ValidationParameters(), // TryAllDecryptionKeys is true by default
ValidationParameters = new ValidationParameters // TryAllDecryptionKeys is true by default
{
ConfigurationManager = new StaticConfigurationManager<BaseConfiguration>(configurationWithMismatchedKeys)
},
Configuration = configurationWithMismatchedKeys,
Result = "eyJhbGciOiJSUzI1NiIsImtpZCI6Ikpzb25XZWJLZXlSc2FfMjA0OCIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJodHRwOi8vRGVmYXVsdC5BdWRpZW5jZS5jb20iLCJhenAiOiJodHRwOi8vRGVmYXVsdC5BenAuY29tIiwiZW1haWwiOiJCb2JAY29udG9zby5jb20iLCJleHAiOiIyNTM0MDIzMDA3OTkiLCJnaXZlbl9uYW1lIjoiQm9iIiwiaXNzIjoiaHR0cDovL0RlZmF1bHQuSXNzdWVyLmNvbSIsImlhdCI6IjE0ODk3NzU2MTciLCJqdGkiOiJKdGkiLCJuYmYiOiIxNDg5Nzc1NjE3In0.Et69LAC4sn6nNm_HNz_AnJ8siLT6LRTjDSb1aY8APcwJmPn-TxU-8GG5_bmNkoVukR7hkYG2JuWPxJKbjDd73BlmelaiyZBoPUyU0S-GX3XgyC2v_CkOq4yYbtD-kq5s7kNNj5QJjZDq0oJeqcUMrq4xRWATPtUMkIZ0GpEhO_C5MFxT8jAWe_a2gyUA4KoibalKtkYgFvgLcvyZJhUx7AERbli6b7OkUksFp9zIwmc_jZZCXJ_F_wASyj9KgHQKN9VHER3bB2zQeWHR0q32ODYC4ggsan-Nkm-jIsATi2tgkKzROzK55dy8ZdFArXUYJRpI_raYkTUHRK_wP3GqtQ",
},
Expand Down
Loading
Loading