Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add encryption keys to base configuration #2023

Merged
merged 7 commits into from
Mar 31, 2023
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 @@ -20,7 +20,6 @@
[assembly: SuppressMessage("Design", "CA1031:Do not catch general exception types", Justification = "Exception is written to a string", Scope = "member", Target = "~M:Microsoft.IdentityModel.JsonWebTokens.JsonWebTokenHandler.ValidateSignature(Microsoft.IdentityModel.JsonWebTokens.JsonWebToken,Microsoft.IdentityModel.Tokens.TokenValidationParameters)~Microsoft.IdentityModel.JsonWebTokens.JsonWebToken")]
[assembly: SuppressMessage("Performance", "CA1815:Override equals and operator equals on value types", Justification = "Has no fields or properties", Scope = "type", Target = "~T:Microsoft.IdentityModel.JsonWebTokens.JwtHeaderParameterNames")]
[assembly: SuppressMessage("Performance", "CA1815:Override equals and operator equals on value types", Justification = "Has no fields or properties", Scope = "type", Target = "~T:Microsoft.IdentityModel.JsonWebTokens.JwtRegisteredClaimNames")]
[assembly: SuppressMessage("Design", "CA1031:Do not catch general exception types", Justification = "Exception is written to a string", Scope = "member", Target = "~M:Microsoft.IdentityModel.JsonWebTokens.JsonWebTokenHandler.GetContentEncryptionKeys(Microsoft.IdentityModel.JsonWebTokens.JsonWebToken,Microsoft.IdentityModel.Tokens.TokenValidationParameters)~System.Collections.Generic.IEnumerable{Microsoft.IdentityModel.Tokens.SecurityKey}")]
[assembly: SuppressMessage("Design", "CA1031:Do not catch general exception types", Justification = "Exception is written to a string", Scope = "member", Target = "~M:Microsoft.IdentityModel.JsonWebTokens.JwtTokenUtilities.DecryptJwtToken(Microsoft.IdentityModel.Tokens.SecurityToken,Microsoft.IdentityModel.Tokens.TokenValidationParameters,Microsoft.IdentityModel.JsonWebTokens.JwtTokenDecryptionParameters)~System.String")]
[assembly: SuppressMessage("Design", "CA1031:Do not catch general exception types", Justification = "Exception is written to a string", Scope = "member", Target = "~M:Microsoft.IdentityModel.JsonWebTokens.JsonWebTokenHandler.ValidateSignature(System.String,Microsoft.IdentityModel.Tokens.TokenValidationParameters,Microsoft.IdentityModel.Tokens.BaseConfiguration)~Microsoft.IdentityModel.JsonWebTokens.JsonWebToken")]
[assembly: SuppressMessage("Design", "CA1031:Do not catch general exception types", Justification = "Exception is returned in the TokenValidationResult", Scope = "member", Target = "~M:Microsoft.IdentityModel.JsonWebTokens.JsonWebTokenHandler.ValidateJWE(Microsoft.IdentityModel.JsonWebTokens.JsonWebToken,System.String,Microsoft.IdentityModel.Tokens.TokenValidationParameters,Microsoft.IdentityModel.Tokens.BaseConfiguration)~Microsoft.IdentityModel.Tokens.TokenValidationResult")]
Expand All @@ -32,3 +31,4 @@
[assembly: SuppressMessage("Design", "CA1031:Do not catch general exception types", Justification = "Exception is written to a string", Scope = "member", Target = "~M:Microsoft.IdentityModel.JsonWebTokens.JsonWebTokenHandler.CreateTokenPrivate(System.String,Microsoft.IdentityModel.Tokens.SigningCredentials,Microsoft.IdentityModel.Tokens.EncryptingCredentials,System.String,System.Collections.Generic.IDictionary{System.String,System.Object},System.Collections.Generic.IDictionary{System.String,System.Object},System.String)~System.String")]
[assembly: SuppressMessage("Design", "CA1031:Do not catch general exception types", Justification = "Exception is written to a string", Scope = "member", Target = "~M:Microsoft.IdentityModel.JsonWebTokens.JsonWebTokenHandler.ValidateSignature(System.String,Microsoft.IdentityModel.JsonWebTokens.JsonWebToken,Microsoft.IdentityModel.Tokens.TokenValidationParameters,Microsoft.IdentityModel.Tokens.BaseConfiguration)~Microsoft.IdentityModel.JsonWebTokens.JsonWebToken")]
[assembly: SuppressMessage("Design", "CA1031:Do not catch general exception types", Justification = "There are additional keys to check, the next one may be successful", Scope = "member", Target = "~M:Microsoft.IdentityModel.JsonWebTokens.JsonWebTokenHandler.ValidateSignature(Microsoft.IdentityModel.JsonWebTokens.JsonWebToken,Microsoft.IdentityModel.Tokens.TokenValidationParameters,Microsoft.IdentityModel.Tokens.BaseConfiguration)~Microsoft.IdentityModel.JsonWebTokens.JsonWebToken")]
[assembly: SuppressMessage("Design", "CA1031:Do not catch general exception types", Justification = "Exception is written to a string", Scope = "member", Target = "~M:Microsoft.IdentityModel.JsonWebTokens.JsonWebTokenHandler.GetContentEncryptionKeys(Microsoft.IdentityModel.JsonWebTokens.JsonWebToken,Microsoft.IdentityModel.Tokens.TokenValidationParameters,Microsoft.IdentityModel.Tokens.BaseConfiguration)~System.Collections.Generic.IEnumerable{Microsoft.IdentityModel.Tokens.SecurityKey}")]
Original file line number Diff line number Diff line change
Expand Up @@ -753,6 +753,11 @@ private ClaimsIdentity CreateClaimsIdentityPrivate(JsonWebToken jwtToken, TokenV
/// <exception cref="SecurityTokenEncryptionKeyNotFoundException">if '<paramref name="jwtToken"/> .Kid' is not null AND decryption fails.</exception>
/// <exception cref="SecurityTokenDecryptionFailedException">if the JWE was not able to be decrypted.</exception>
public string DecryptToken(JsonWebToken jwtToken, TokenValidationParameters validationParameters)
{
return DecryptToken(jwtToken, validationParameters, null);
}

private string DecryptToken(JsonWebToken jwtToken, TokenValidationParameters validationParameters, BaseConfiguration configuration)
{
if (jwtToken == null)
throw LogHelper.LogArgumentNullException(nameof(jwtToken));
Expand All @@ -763,7 +768,7 @@ public string DecryptToken(JsonWebToken jwtToken, TokenValidationParameters vali
if (string.IsNullOrEmpty(jwtToken.Enc))
throw LogHelper.LogExceptionMessage(new SecurityTokenException(LogHelper.FormatInvariant(TokenLogMessages.IDX10612)));

var keys = GetContentEncryptionKeys(jwtToken, validationParameters);
var keys = GetContentEncryptionKeys(jwtToken, validationParameters, configuration);
return JwtTokenUtilities.DecryptJwtToken(
jwtToken,
validationParameters,
Expand Down Expand Up @@ -939,10 +944,43 @@ private static string EncryptTokenPrivate(string innerJwt, EncryptingCredentials
}
}

internal IEnumerable<SecurityKey> GetContentEncryptionKeys(JsonWebToken jwtToken, TokenValidationParameters validationParameters)
private static SecurityKey ResolveTokenDecryptionKeyFromConfig(JsonWebToken jwtToken, BaseConfiguration configuration)
{
if (jwtToken == null)
throw LogHelper.LogArgumentNullException(nameof(jwtToken));

if (!string.IsNullOrEmpty(jwtToken.Kid) && configuration.TokenDecryptionKeys != null)
{
foreach (var key in configuration.TokenDecryptionKeys)
{
if (key != null && string.Equals(key.KeyId, jwtToken.Kid, GetStringComparisonRuleIf509OrECDsa(key)))
return key;
}
}

if (!string.IsNullOrEmpty(jwtToken.X5t) && configuration.TokenDecryptionKeys != null)
{
foreach (var key in configuration.TokenDecryptionKeys)
{
if (key != null && string.Equals(key.KeyId, jwtToken.X5t, GetStringComparisonRuleIf509(key)))
return key;

var x509Key = key as X509SecurityKey;
if (x509Key != null && string.Equals(x509Key.X5t, jwtToken.X5t, StringComparison.OrdinalIgnoreCase))
return key;
}
}

return null;
}

internal IEnumerable<SecurityKey> GetContentEncryptionKeys(JsonWebToken jwtToken, TokenValidationParameters validationParameters, BaseConfiguration configuration)
{
IEnumerable<SecurityKey> keys = null;

RojaEnnam marked this conversation as resolved.
Show resolved Hide resolved
// First we check to see if the caller has set a custom decryption resolver on TVP for the call, if so any keys set on TVP and keys in Configuration are ignored.
// If no custom decryption resolver set, we'll check to see if they've set some static decryption keys on TVP. If a key found, we ignore configuration.
// If no key found in TVP, we'll check the configuration.
if (validationParameters.TokenDecryptionKeyResolver != null)
{
keys = validationParameters.TokenDecryptionKeyResolver(jwtToken.EncodedToken, jwtToken, jwtToken.Kid, validationParameters);
Expand All @@ -951,7 +989,18 @@ internal IEnumerable<SecurityKey> GetContentEncryptionKeys(JsonWebToken jwtToken
{
var key = ResolveTokenDecryptionKey(jwtToken.EncodedToken, jwtToken, validationParameters);
if (key != null)
keys = new List<SecurityKey> { key };
{
LogHelper.LogInformation(TokenLogMessages.IDX10904, key);
}
else if (configuration != null)
{
key = ResolveTokenDecryptionKeyFromConfig(jwtToken, configuration);
if ( key != null )
LogHelper.LogInformation(TokenLogMessages.IDX10905, key);
}

if (key != null)
keys = new List<SecurityKey> { key };
}

// on decryption for ECDH-ES, we get the public key from the EPK value see: https://datatracker.ietf.org/doc/html/rfc7518#appendix-C
Expand All @@ -960,9 +1009,14 @@ internal IEnumerable<SecurityKey> GetContentEncryptionKeys(JsonWebToken jwtToken
// control gets here if:
// 1. User specified delegate: TokenDecryptionKeyResolver returned null
// 2. ResolveTokenDecryptionKey returned null
RojaEnnam marked this conversation as resolved.
Show resolved Hide resolved
// 3. ResolveTokenDecryptionKeyFromConfig returned null
// Try all the keys. This is the degenerate case, not concerned about perf.
if (keys == null)
{
keys = JwtTokenUtilities.GetAllDecryptionKeys(validationParameters);
if (configuration != null)
keys = keys == null ? configuration.TokenDecryptionKeys : keys.Concat(configuration.TokenDecryptionKeys);
}

if (jwtToken.Alg.Equals(JwtConstants.DirectKeyUseAlg, StringComparison.Ordinal)
|| jwtToken.Alg.Equals(SecurityAlgorithms.EcdhEs, StringComparison.Ordinal))
Expand Down Expand Up @@ -1329,7 +1383,7 @@ private TokenValidationResult ValidateJWE(JsonWebToken jwtToken, TokenValidation
{
try
{
string jws = DecryptToken(jwtToken, validationParameters);
string jws = DecryptToken(jwtToken, validationParameters, configuration);
TokenValidationResult readTokenResult = ReadToken(jws, validationParameters);
if (!readTokenResult.IsValid)
return readTokenResult;
Expand Down
10 changes: 10 additions & 0 deletions src/Microsoft.IdentityModel.Tokens/BaseConfiguration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

using System.Collections.Generic;
using System.Collections.ObjectModel;
using Microsoft.IdentityModel.Json;

namespace Microsoft.IdentityModel.Tokens
{
Expand Down Expand Up @@ -30,5 +31,14 @@ public virtual ICollection<SecurityKey> SigningKeys
/// Or the token_endpoint in the OIDC metadata.
/// </summary>
public virtual string TokenEndpoint { get; set; }

/// <summary>
/// Gets the <see cref="ICollection{SecurityKey}"/> that the IdentityProvider indicates are to be used in order to decrypt tokens.
/// </summary>
[JsonIgnore]
public virtual ICollection<SecurityKey> TokenDecryptionKeys
{
get;
} = new Collection<SecurityKey>();
}
}
2 changes: 2 additions & 0 deletions src/Microsoft.IdentityModel.Tokens/LogMessages.cs
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,8 @@ internal static class LogMessages
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 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.";

// Formating
public const string IDX10400 = "IDX10400: Unable to decode: '{0}' as Base64url encoded string.";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -631,7 +631,7 @@ public void GetEncryptionKeys(CreateTokenTheoryData theoryData)
{
string jweFromJsonHandlerWithKid = theoryData.JsonWebTokenHandler.CreateToken(theoryData.Payload, theoryData.TokenDescriptor.SigningCredentials, theoryData.TokenDescriptor.EncryptingCredentials);
var jwtTokenFromJsonHandlerWithKid = new JsonWebToken(jweFromJsonHandlerWithKid);
var encryptionKeysFromJsonHandlerWithKid = theoryData.JsonWebTokenHandler.GetContentEncryptionKeys(jwtTokenFromJsonHandlerWithKid, theoryData.ValidationParameters);
var encryptionKeysFromJsonHandlerWithKid = theoryData.JsonWebTokenHandler.GetContentEncryptionKeys(jwtTokenFromJsonHandlerWithKid, theoryData.ValidationParameters, theoryData.Configuration);

IdentityComparer.AreEqual(encryptionKeysFromJsonHandlerWithKid, theoryData.ExpectedDecryptionKeys);
theoryData.ExpectedException.ProcessNoException(context);
Expand All @@ -643,6 +643,7 @@ public void GetEncryptionKeys(CreateTokenTheoryData theoryData)

TestUtilities.AssertFailIfErrors(context);
}

public static TheoryData<CreateTokenTheoryData> SecurityTokenDecryptionTheoryData
{
get
Expand All @@ -652,12 +653,61 @@ public static TheoryData<CreateTokenTheoryData> SecurityTokenDecryptionTheoryDat
SetDefaultTimesOnTokenCreation = false
};

var configurationWithDecryptionKeys = new OpenIdConnectConfiguration();
configurationWithDecryptionKeys.TokenDecryptionKeys.Add(KeyingMaterial.DefaultSymmetricSecurityKey_256);
configurationWithDecryptionKeys.TokenDecryptionKeys.Add(KeyingMaterial.DefaultSymmetricSecurityKey_512);

tokenHandler.InboundClaimTypeMap.Clear();
return new TheoryData<CreateTokenTheoryData>
{
new CreateTokenTheoryData
{
First = true,
TestId = "EncryptionKeyInConfig",
Payload = Default.PayloadString,
TokenDescriptor = new SecurityTokenDescriptor
{
SigningCredentials = KeyingMaterial.JsonWebKeyRsa256SigningCredentials,
EncryptingCredentials = KeyingMaterial.DefaultSymmetricEncryptingCreds_Aes128_Sha2,
Claims = Default.PayloadDictionary
},
JsonWebTokenHandler = new JsonWebTokenHandler(),
ValidationParameters = new TokenValidationParameters
{
IssuerSigningKey = KeyingMaterial.JsonWebKeyRsa256SigningCredentials.Key,
ValidAudience = Default.Audience,
ValidIssuer = Default.Issuer
},
Configuration = configurationWithDecryptionKeys,
ExpectedDecryptionKeys = new List<SecurityKey>(){ KeyingMaterial.DefaultSymmetricSecurityKey_256 },
Algorithm = JwtConstants.DirectKeyUseAlg,
EncryptingCredentials = KeyingMaterial.DefaultSymmetricEncryptingCreds_Aes128_Sha2_NoKeyId
},
new CreateTokenTheoryData
{
TestId = "ValidEncryptionKeyInConfig",
Payload = Default.PayloadString,
TokenDescriptor = new SecurityTokenDescriptor
{
SigningCredentials = KeyingMaterial.JsonWebKeyRsa256SigningCredentials,
EncryptingCredentials = KeyingMaterial.DefaultSymmetricEncryptingCreds_Aes128_Sha2,
Claims = Default.PayloadDictionary
},
JsonWebTokenHandler = new JsonWebTokenHandler(),
ValidationParameters = new TokenValidationParameters
{
IssuerSigningKey = KeyingMaterial.JsonWebKeyRsa256SigningCredentials.Key,
TokenDecryptionKeys = new List<SecurityKey>(){ KeyingMaterial.DefaultSymmetricSecurityKey_512 },
ValidAudience = Default.Audience,
ValidIssuer = Default.Issuer
},
Configuration = configurationWithDecryptionKeys,
ExpectedDecryptionKeys = new List<SecurityKey>(){ KeyingMaterial.DefaultSymmetricSecurityKey_256 },
Algorithm = JwtConstants.DirectKeyUseAlg,
EncryptingCredentials = KeyingMaterial.DefaultSymmetricEncryptingCreds_Aes128_Sha2_NoKeyId
},
new CreateTokenTheoryData
{
TestId = "Valid",
Payload = Default.PayloadString,
TokenDescriptor = new SecurityTokenDescriptor
Expand Down Expand Up @@ -3448,6 +3498,8 @@ public CreateTokenTheoryData(string testId)

public string CompressionAlgorithm { get; set; }

public BaseConfiguration Configuration { get; set; }

public CompressionProviderFactory CompressionProviderFactory { get; set; }

public EncryptingCredentials EncryptingCredentials { get; set; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ public void GetSets()
OpenIdConnectConfiguration configuration = new OpenIdConnectConfiguration();
Type type = typeof(OpenIdConnectConfiguration);
PropertyInfo[] properties = type.GetProperties();
if (properties.Length != 47)
if (properties.Length != 48)
Assert.True(false, "Number of properties has changed from 47 to: " + properties.Length + ", adjust tests");

TestUtilities.CallAllPublicInstanceAndStaticPropertyGets(configuration, "OpenIdConnectConfiguration_GetSets");
Expand Down