Skip to content

Commit

Permalink
Regression testing: Add JWE use cases (#2815)
Browse files Browse the repository at this point in the history
* Ported EcdhSa key changes from main path to VP one
* Added regression tests using JWE
* Wrapped EcdSa test within if block for unsupported versions
* Removed workaround for ecdsa encryption using ephemeral key in favour of the RFC approach. Updated tests.
  • Loading branch information
iNinja authored Sep 11, 2024
1 parent 1bdfaf5 commit 411d084
Show file tree
Hide file tree
Showing 4 changed files with 150 additions and 18 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -171,17 +171,22 @@ internal ValidationResult<string> DecryptToken(
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
var ecdhKeyExchangeProvider = new EcdhKeyExchangeProvider(
key as ECDsaSecurityKey,
validationParameters.EphemeralDecryptionKey as ECDsaSecurityKey,
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));
jwtToken.TryGetHeaderValue(JwtHeaderParameterNames.Epk, out string epk);
ECDsaSecurityKey? publicKey = new ECDsaSecurityKey(new JsonWebKey(epk), false);
if (publicKey is not null)
{
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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -251,11 +251,6 @@ public virtual ClaimsIdentity CreateClaimsIdentity(SecurityToken securityToken,
/// </summary>
public string DebugId { get; set; }

/// <summary>
/// Gets the <see cref="SecurityKey"/> representing the ephemeral decryption key used for decryption by certain algorithms.
/// </summary>
public SecurityKey EphemeralDecryptionKey { get; set; }

/// <summary>
/// Gets or sets a boolean that controls if a '/' is significant at the end of the audience.
/// The default is <c>true</c>.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@

using System;
using System.Collections.Generic;
#if NET472_OR_GREATER || NET6_0_OR_GREATER
using Newtonsoft.Json.Linq;
#endif
using System.IdentityModel.Tokens.Jwt.Tests;
using Microsoft.IdentityModel.Logging;
using Microsoft.IdentityModel.TestUtils;
Expand Down Expand Up @@ -94,10 +97,29 @@ public static TheoryData<TokenDecryptingTheoryData> JsonWebTokenHandlerDecryptTo
Expires = DateTime.MaxValue,
NotBefore = DateTime.MinValue,
IssuedAt = DateTime.MinValue,
AdditionalHeaderClaims = AdditionalEcdhEsHeaderParameters(KeyingMaterial.JsonWebKeyP256_Public),
};

var jsonWebTokenHandler = new JsonWebTokenHandler();
var ecdsaToken = new JsonWebToken(jsonWebTokenHandler.CreateToken(ecdsaTokenDescriptor));

static Dictionary<string, object> AdditionalEcdhEsHeaderParameters(JsonWebKey publicKeySender)
{
var epkJObject = new JObject();
epkJObject.Add(JsonWebKeyParameterNames.Kty, publicKeySender.Kty);
epkJObject.Add(JsonWebKeyParameterNames.Crv, publicKeySender.Crv);
epkJObject.Add(JsonWebKeyParameterNames.X, publicKeySender.X);
epkJObject.Add(JsonWebKeyParameterNames.Y, publicKeySender.Y);

Dictionary<string, object> additionalHeaderParams = new Dictionary<string, object>()
{
{ JsonWebTokens.JwtHeaderParameterNames.Apu, Guid.NewGuid().ToString() },
{ JsonWebTokens.JwtHeaderParameterNames.Apv, Guid.NewGuid().ToString() },
{ JsonWebTokens.JwtHeaderParameterNames.Epk, epkJObject.ToString(Newtonsoft.Json.Formatting.None) }
};

return additionalHeaderParams;
}
#endif
var configurationThatThrows = CreateCustomConfigurationThatThrows();

Expand Down Expand Up @@ -175,7 +197,6 @@ public static TheoryData<TokenDecryptingTheoryData> JsonWebTokenHandlerDecryptTo
ValidationParameters = new ValidationParameters
{
TokenDecryptionKeys = [new ECDsaSecurityKey(KeyingMaterial.JsonWebKeyP256, true)],
EphemeralDecryptionKey = new ECDsaSecurityKey(KeyingMaterial.JsonWebKeyP256, true)
},
Result = "eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJleHAiOjI1MzQwMjMwMDgwMCwiaWF0IjowLCJuYmYiOjB9."
},
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

#if NET472_OR_GREATER || NET6_0_OR_GREATER
using System;
using Newtonsoft.Json.Linq;
#endif
using System.Collections.Generic;
using System.Security.Claims;
using System.Threading;
Expand Down Expand Up @@ -53,7 +57,9 @@ await jsonWebTokenHandler.ValidateTokenAsync(
if (validationParametersResult.IsSuccess != theoryData.ExpectedIsValid)
context.AddDiff($"validationParametersResult.IsSuccess != theoryData.ExpectedIsValid");

if (theoryData.ExpectedIsValid)
if (theoryData.ExpectedIsValid &&
tokenValidationParametersResult.IsValid &&
validationParametersResult.IsSuccess)
{
IdentityComparer.AreEqual(
tokenValidationParametersResult.ClaimsIdentity,
Expand Down Expand Up @@ -202,12 +208,93 @@ public static TheoryData<JsonWebTokenHandlerValidationParametersTheoryData> Json
"IDX10518:",
innerTypeExpected: typeof(SecurityTokenInvalidAlgorithmException))
},
new JsonWebTokenHandlerValidationParametersTheoryData("Valid_JWE")
{
EncryptingCredentials = new EncryptingCredentials(
KeyingMaterial.DefaultX509Key_2048,
SecurityAlgorithms.RsaPKCS1,
SecurityAlgorithms.Aes128CbcHmacSha256),
SigningCredentials = KeyingMaterial.DefaultSymmetricSigningCreds_256_Sha2,
TokenValidationParameters = CreateTokenValidationParameters(
Default.Issuer, [Default.Audience], KeyingMaterial.DefaultSymmetricSigningCreds_256_Sha2.Key,
tokenDecryptionKey: KeyingMaterial.DefaultX509Key_2048),
ValidationParameters = CreateValidationParameters(
Default.Issuer, [Default.Audience], KeyingMaterial.DefaultSymmetricSigningCreds_256_Sha2.Key,
tokenDecryptionKey: KeyingMaterial.DefaultX509Key_2048),
},
#if NET472 || NET6_0_OR_GREATER
new JsonWebTokenHandlerValidationParametersTheoryData("Valid_JWE_EcdhEs")
{
EncryptingCredentials = new EncryptingCredentials(
new ECDsaSecurityKey(KeyingMaterial.JsonWebKeyP521, true),
SecurityAlgorithms.EcdhEsA256kw,
SecurityAlgorithms.Aes128CbcHmacSha256)
{
KeyExchangePublicKey = KeyingMaterial.JsonWebKeyP521_Public
},
SigningCredentials = KeyingMaterial.DefaultSymmetricSigningCreds_256_Sha2,
AdditionalHeaderParams = AdditionalEcdhEsHeaderParameters(KeyingMaterial.JsonWebKeyP521_Public),
TokenValidationParameters = CreateTokenValidationParameters(
Default.Issuer, [Default.Audience], KeyingMaterial.DefaultSymmetricSigningCreds_256_Sha2.Key,
tokenDecryptionKey: new ECDsaSecurityKey(KeyingMaterial.JsonWebKeyP521, true)),
ValidationParameters = CreateValidationParameters(
Default.Issuer, [Default.Audience], KeyingMaterial.DefaultSymmetricSigningCreds_256_Sha2.Key,
tokenDecryptionKey: new ECDsaSecurityKey(KeyingMaterial.JsonWebKeyP521, true)),
},
#endif
new JsonWebTokenHandlerValidationParametersTheoryData("Invalid_JWE_NoDecryptionKeys")
{
EncryptingCredentials = new EncryptingCredentials(
KeyingMaterial.DefaultX509Key_2048,
SecurityAlgorithms.RsaPKCS1,
SecurityAlgorithms.Aes128CbcHmacSha256),
SigningCredentials = KeyingMaterial.DefaultSymmetricSigningCreds_256_Sha2,
TokenValidationParameters = CreateTokenValidationParameters(
Default.Issuer, [Default.Audience], KeyingMaterial.DefaultSymmetricSigningCreds_256_Sha2.Key),
ValidationParameters = CreateValidationParameters(
Default.Issuer, [Default.Audience], KeyingMaterial.DefaultSymmetricSigningCreds_256_Sha2.Key),
ExpectedIsValid = false,
ExpectedException = ExpectedException.SecurityTokenDecryptionFailedException("IDX10609:"),
},
new JsonWebTokenHandlerValidationParametersTheoryData("Invalid_JWE_WrongDecryptionKey")
{
EncryptingCredentials = new EncryptingCredentials(
KeyingMaterial.DefaultX509Key_2048,
SecurityAlgorithms.RsaPKCS1,
SecurityAlgorithms.Aes128CbcHmacSha256),
SigningCredentials = KeyingMaterial.DefaultSymmetricSigningCreds_256_Sha2,
TokenValidationParameters = CreateTokenValidationParameters(
Default.Issuer, [Default.Audience], KeyingMaterial.DefaultSymmetricSigningCreds_256_Sha2.Key,
tokenDecryptionKey: KeyingMaterial.DefaultRsaSecurityKey1),
ValidationParameters = CreateValidationParameters(
Default.Issuer, [Default.Audience], KeyingMaterial.DefaultSymmetricSigningCreds_256_Sha2.Key,
tokenDecryptionKey: KeyingMaterial.DefaultRsaSecurityKey1),
ExpectedIsValid = false,
ExpectedException = ExpectedException.SecurityTokenKeyWrapException("IDX10618:"),
},
new JsonWebTokenHandlerValidationParametersTheoryData("Invalid_JWE_WrongDecryptionKey")
{
EncryptingCredentials = new EncryptingCredentials(
KeyingMaterial.DefaultX509Key_2048,
SecurityAlgorithms.RsaPKCS1,
SecurityAlgorithms.Aes128CbcHmacSha256),
SigningCredentials = KeyingMaterial.DefaultSymmetricSigningCreds_256_Sha2,
TokenValidationParameters = CreateTokenValidationParameters(
Default.Issuer, [Default.Audience], KeyingMaterial.DefaultSymmetricSigningCreds_256_Sha2.Key,
tokenDecryptionKey: KeyingMaterial.DefaultRsaSecurityKey1),
ValidationParameters = CreateValidationParameters(
Default.Issuer, [Default.Audience], KeyingMaterial.DefaultSymmetricSigningCreds_256_Sha2.Key,
tokenDecryptionKey: KeyingMaterial.DefaultRsaSecurityKey1),
ExpectedIsValid = false,
ExpectedException = ExpectedException.SecurityTokenKeyWrapException("IDX10618:"),
},
};

static TokenValidationParameters CreateTokenValidationParameters(
string issuer,
List<string> audiences,
SecurityKey issuerSigningKey,
SecurityKey tokenDecryptionKey = null,
List<string> validAlgorithms = null,
bool tryAllKeys = false) => new TokenValidationParameters
{
Expand All @@ -218,6 +305,7 @@ static TokenValidationParameters CreateTokenValidationParameters(
ValidateTokenReplay = true,
ValidateIssuerSigningKey = true,
IssuerSigningKey = issuerSigningKey,
TokenDecryptionKey = tokenDecryptionKey,
ValidAudiences = audiences,
ValidIssuer = issuer,
TryAllIssuerSigningKeys = tryAllKeys,
Expand All @@ -227,6 +315,7 @@ static ValidationParameters CreateValidationParameters(
string issuer,
List<string> audiences,
SecurityKey issuerSigningKey,
SecurityKey tokenDecryptionKey = null,
List<string> validAlgorithms = null,
bool tryAllKeys = false)
{
Expand All @@ -237,9 +326,31 @@ static ValidationParameters CreateValidationParameters(
validationParameters.TryAllIssuerSigningKeys = tryAllKeys;
if (validAlgorithms is not null)
validationParameters.ValidAlgorithms = validAlgorithms;
if (tokenDecryptionKey is not null)
validationParameters.TokenDecryptionKeys = [tokenDecryptionKey];

return validationParameters;
}

#if NET472 || NET6_0_OR_GREATER
static Dictionary<string, object> AdditionalEcdhEsHeaderParameters(JsonWebKey publicKeySender)
{
var epkJObject = new JObject();
epkJObject.Add(JsonWebKeyParameterNames.Kty, publicKeySender.Kty);
epkJObject.Add(JsonWebKeyParameterNames.Crv, publicKeySender.Crv);
epkJObject.Add(JsonWebKeyParameterNames.X, publicKeySender.X);
epkJObject.Add(JsonWebKeyParameterNames.Y, publicKeySender.Y);

Dictionary<string, object> additionalHeaderParams = new Dictionary<string, object>()
{
{ JsonWebTokens.JwtHeaderParameterNames.Apu, Guid.NewGuid().ToString() },
{ JsonWebTokens.JwtHeaderParameterNames.Apv, Guid.NewGuid().ToString() },
{ JsonWebTokens.JwtHeaderParameterNames.Epk, epkJObject.ToString(Newtonsoft.Json.Formatting.None) }
};

return additionalHeaderParams;
}
#endif
}
}

Expand Down

0 comments on commit 411d084

Please sign in to comment.