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

Regression testing: Add JWE use cases #2815

Merged
merged 7 commits into from
Sep 11, 2024
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 @@ -170,18 +170,38 @@ internal Result<string> DecryptToken(
#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
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));
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
iNinja marked this conversation as resolved.
Show resolved Hide resolved
// 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.EphemeralDecryptionKey as ECDsaSecurityKey;
}

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
@@ -1,12 +1,14 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using System;
using System.Collections.Generic;
using System.Security.Claims;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.IdentityModel.TestUtils;
using Microsoft.IdentityModel.Tokens;
using Newtonsoft.Json.Linq;
using Xunit;

namespace Microsoft.IdentityModel.JsonWebTokens.Tests
Expand Down Expand Up @@ -53,7 +55,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 +206,95 @@ 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),
},
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),
ephemeralDecryptionKey: new ECDsaSecurityKey(KeyingMaterial.JsonWebKeyP521, true)),
},
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,
// TVP path returns a key wrap exception listing the 0 keys tried in the same way as if there had been more
// while VP path returns a decryption failed exception stating that no keys were tried.
ExpectedException = ExpectedException.SecurityTokenKeyWrapException("IDX10618:"),
ExpectedExceptionValidationParameters = 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,8 @@ static ValidationParameters CreateValidationParameters(
string issuer,
List<string> audiences,
SecurityKey issuerSigningKey,
SecurityKey tokenDecryptionKey = null,
SecurityKey ephemeralDecryptionKey = null,
List<string> validAlgorithms = null,
bool tryAllKeys = false)
{
Expand All @@ -237,9 +327,31 @@ static ValidationParameters CreateValidationParameters(
validationParameters.TryAllIssuerSigningKeys = tryAllKeys;
if (validAlgorithms is not null)
validationParameters.ValidAlgorithms = validAlgorithms;
if (tokenDecryptionKey is not null)
validationParameters.TokenDecryptionKeys = [tokenDecryptionKey];
if (ephemeralDecryptionKey is not null)
validationParameters.EphemeralDecryptionKey = ephemeralDecryptionKey;

return validationParameters;
}

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;
}
}
}

Expand Down
Loading