diff --git a/src/Microsoft.IdentityModel.Tokens/Validators.cs b/src/Microsoft.IdentityModel.Tokens/Validators.cs index 38364489ad..20e76450e7 100644 --- a/src/Microsoft.IdentityModel.Tokens/Validators.cs +++ b/src/Microsoft.IdentityModel.Tokens/Validators.cs @@ -373,6 +373,16 @@ internal static void ValidateIssuerSecurityKey(SecurityKey securityKey, Security if (securityToken == null) throw LogHelper.LogArgumentNullException(nameof(securityToken)); + ValidateIssuerSigningKeyLifeTime(securityKey, validationParameters); + } + + /// + /// Given a signing key, when it's derived from a certificate, validates that the certificate is already active and non-expired + /// + /// The that signed the . + /// The that are used to validate the token. + internal static void ValidateIssuerSigningKeyLifeTime(SecurityKey securityKey, TokenValidationParameters validationParameters) + { X509SecurityKey x509SecurityKey = securityKey as X509SecurityKey; if (x509SecurityKey?.Certificate is X509Certificate2 cert) { diff --git a/src/Microsoft.IdentityModel.Validators/AadIssuerValidator/AadIssuerValidator.cs b/src/Microsoft.IdentityModel.Validators/AadIssuerValidator/AadIssuerValidator.cs index 1ce12b0c1f..83090efa27 100644 --- a/src/Microsoft.IdentityModel.Validators/AadIssuerValidator/AadIssuerValidator.cs +++ b/src/Microsoft.IdentityModel.Validators/AadIssuerValidator/AadIssuerValidator.cs @@ -24,7 +24,7 @@ public class AadIssuerValidator private static readonly TimeSpan LastKnownGoodConfigurationLifetime = new TimeSpan(0, 24, 0, 0); internal const string V2EndpointSuffix = "/v2.0"; - internal const string TenantidTemplate = "{tenantid}"; + internal const string TenantIdTemplate = "{tenantid}"; internal AadIssuerValidator( HttpClient httpClient, @@ -292,9 +292,9 @@ private static bool IsValidIssuer(string validIssuerTemplate, string tenantId, s if (string.IsNullOrEmpty(validIssuerTemplate)) return false; - if (validIssuerTemplate.Contains(TenantidTemplate)) + if (validIssuerTemplate.Contains(TenantIdTemplate)) { - return validIssuerTemplate.Replace(TenantidTemplate, tenantId) == actualIssuer; + return validIssuerTemplate.Replace(TenantIdTemplate, tenantId) == actualIssuer; } else { @@ -311,7 +311,7 @@ private BaseConfigurationManager GetEffectiveConfigurationManager(SecurityToken /// A JWT token. /// A string containing the tenant ID, if found or . /// Only and are acceptable types. - private static string GetTenantIdFromToken(SecurityToken securityToken) + internal static string GetTenantIdFromToken(SecurityToken securityToken) { if (securityToken is JwtSecurityToken jwtSecurityToken) { diff --git a/src/Microsoft.IdentityModel.Validators/AadTokenValidationParametersExtension.cs b/src/Microsoft.IdentityModel.Validators/AadTokenValidationParametersExtension.cs new file mode 100644 index 0000000000..d745bf124c --- /dev/null +++ b/src/Microsoft.IdentityModel.Validators/AadTokenValidationParametersExtension.cs @@ -0,0 +1,133 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Linq; +using Microsoft.IdentityModel.Logging; +using Microsoft.IdentityModel.Protocols.OpenIdConnect; +using Microsoft.IdentityModel.Tokens; + +namespace Microsoft.IdentityModel.Validators +{ + /// + /// A generic class for additional validation checks on issued by the Microsoft identity platform (AAD). + /// + public static class AadTokenValidationParametersExtension + { + /// + /// Enables the validation of the issuer of the signing keys used by the Microsoft identity platform (AAD) against the issuer of the token. + /// + /// The that are used to validate the token. + public static void EnableAadSigningKeyIssuerValidation(this TokenValidationParameters tokenValidationParameters) + { + if (tokenValidationParameters == null) + throw LogHelper.LogArgumentNullException(nameof(tokenValidationParameters)); + + var userProvidedIssuerSigningKeyValidatorUsingConfiguration = tokenValidationParameters.IssuerSigningKeyValidatorUsingConfiguration; + var userProvidedIssuerSigningKeyValidator = tokenValidationParameters.IssuerSigningKeyValidator; + + tokenValidationParameters.IssuerSigningKeyValidatorUsingConfiguration = (securityKey, securityToken, tvp, config) => + { + ValidateIssuerSigningKey(securityKey, securityToken, config); + + // preserve and run provided logic + if (userProvidedIssuerSigningKeyValidatorUsingConfiguration != null) + return userProvidedIssuerSigningKeyValidatorUsingConfiguration(securityKey, securityToken, tvp, config); + + if (userProvidedIssuerSigningKeyValidator != null) + return userProvidedIssuerSigningKeyValidator(securityKey, securityToken, tvp); + + return ValidateIssuerSigningKeyCertificate(securityKey, tvp); + }; + } + + /// + /// Validates the issuer signing key. + /// + /// The that signed the . + /// The being validated, could be a JwtSecurityToken or JsonWebToken. + /// The provided. + /// true if the issuer signing key is valid; otherwise, false. + internal static bool ValidateIssuerSigningKey(SecurityKey securityKey, SecurityToken securityToken, BaseConfiguration configuration) + { + if (securityKey == null) + return true; + + if (securityToken == null) + throw LogHelper.LogArgumentNullException(nameof(securityToken)); + + var openIdConnectConfiguration = configuration as OpenIdConnectConfiguration; + if (openIdConnectConfiguration == null) + return true; + + var matchedKeyFromConfig = openIdConnectConfiguration.JsonWebKeySet?.Keys.FirstOrDefault(x => x != null && x.Kid == securityKey.KeyId); + if (matchedKeyFromConfig != null && matchedKeyFromConfig.AdditionalData.TryGetValue(OpenIdProviderMetadataNames.Issuer, out object value)) + { + var signingKeyIssuer = value as string; + if (string.IsNullOrWhiteSpace(signingKeyIssuer)) + return true; + + var tenantIdFromToken = AadIssuerValidator.GetTenantIdFromToken(securityToken); + if (string.IsNullOrEmpty(tenantIdFromToken)) + return true; + + var tokenIssuer = securityToken.Issuer; + +#if NET6_0_OR_GREATER + if (!string.IsNullOrEmpty(tokenIssuer) && !tokenIssuer.Contains(tenantIdFromToken, StringComparison.Ordinal)) + throw LogHelper.LogExceptionMessage(new SecurityTokenInvalidIssuerException(LogHelper.FormatInvariant(LogMessages.IDX40004, LogHelper.MarkAsNonPII(tokenIssuer), LogHelper.MarkAsNonPII(tenantIdFromToken)))); + + // creating an effectiveSigningKeyIssuer is required as signingKeyIssuer might contain {tenantid} + var effectiveSigningKeyIssuer = signingKeyIssuer.Replace(AadIssuerValidator.TenantIdTemplate, tenantIdFromToken, StringComparison.Ordinal); + var v2TokenIssuer = openIdConnectConfiguration.Issuer?.Replace(AadIssuerValidator.TenantIdTemplate, tenantIdFromToken, StringComparison.Ordinal); +#else + if (!string.IsNullOrEmpty(tokenIssuer) && !tokenIssuer.Contains(tenantIdFromToken)) + throw LogHelper.LogExceptionMessage(new SecurityTokenInvalidIssuerException(LogHelper.FormatInvariant(LogMessages.IDX40004, LogHelper.MarkAsNonPII(tokenIssuer), LogHelper.MarkAsNonPII(tenantIdFromToken)))); + + // creating an effectiveSigningKeyIssuer is required as signingKeyIssuer might contain {tenantid} + var effectiveSigningKeyIssuer = signingKeyIssuer.Replace(AadIssuerValidator.TenantIdTemplate, tenantIdFromToken); + var v2TokenIssuer = openIdConnectConfiguration.Issuer?.Replace(AadIssuerValidator.TenantIdTemplate, tenantIdFromToken); +#endif + + // comparing effectiveSigningKeyIssuer with v2TokenIssuer is required as well because of the following scenario: + // 1. service trusts /common/v2.0 endpoint + // 2. service receieves a v1 token that has issuer like sts.windows.net + // 3. signing key issuers will never match sts.windows.net as v1 endpoint doesn't have issuers attached to keys + // v2TokenIssuer is the representation of Token.Issuer (if it was a v2 issuer) + if (effectiveSigningKeyIssuer != tokenIssuer && effectiveSigningKeyIssuer != v2TokenIssuer) + throw LogHelper.LogExceptionMessage(new SecurityTokenInvalidIssuerException(LogHelper.FormatInvariant(LogMessages.IDX40005, LogHelper.MarkAsNonPII(tokenIssuer), LogHelper.MarkAsNonPII(effectiveSigningKeyIssuer)))); + } + + return true; + } + + /// + /// Validates the issuer signing key certificate. + /// + /// The that signed the . + /// The that are used to validate the token. + /// true if the issuer signing key certificate is valid; otherwise, false. + internal static bool ValidateIssuerSigningKeyCertificate(SecurityKey securityKey, TokenValidationParameters validationParameters) + { + if (!validationParameters.RequireSignedTokens && securityKey == null) + { + LogHelper.LogInformation(Tokens.LogMessages.IDX10252); + return true; + } + else if (securityKey == null) + { + throw LogHelper.LogExceptionMessage(new ArgumentNullException(nameof(securityKey), LogMessages.IDX40007)); + } + + if (!validationParameters.ValidateIssuerSigningKey) + { + LogHelper.LogVerbose(Tokens.LogMessages.IDX10237); + return true; + } + + Tokens.Validators.ValidateIssuerSigningKeyLifeTime(securityKey, validationParameters); + + return true; + } + } +} diff --git a/src/Microsoft.IdentityModel.Validators/AadIssuerValidator/LogMessages.cs b/src/Microsoft.IdentityModel.Validators/LogMessages.cs similarity index 68% rename from src/Microsoft.IdentityModel.Validators/AadIssuerValidator/LogMessages.cs rename to src/Microsoft.IdentityModel.Validators/LogMessages.cs index 25cbc2e3fb..e8edf46300 100644 --- a/src/Microsoft.IdentityModel.Validators/AadIssuerValidator/LogMessages.cs +++ b/src/Microsoft.IdentityModel.Validators/LogMessages.cs @@ -20,5 +20,8 @@ internal static class LogMessages // Protocol public const string IDX40003 = "IDX40003: Neither `tid` nor `tenantId` claim is present in the token obtained from Microsoft identity platform. "; + public const string IDX40004 = "IDX40004: Token issuer: '{0}', does not contain the `tid` or `tenantId` claim present in the token: '{1}'."; + public const string IDX40005 = "IDX40005: Token issuer: '{0}', does not match the signing key issuer: '{1}'."; + public const string IDX40007 = "IDX40007: RequireSignedTokens property on ValidationParameters is set to true, but the issuer signing key is null."; } } diff --git a/src/Microsoft.IdentityModel.Validators/Microsoft.IdentityModel.Validators.csproj b/src/Microsoft.IdentityModel.Validators/Microsoft.IdentityModel.Validators.csproj index fe8b6863fc..d784708d0a 100644 --- a/src/Microsoft.IdentityModel.Validators/Microsoft.IdentityModel.Validators.csproj +++ b/src/Microsoft.IdentityModel.Validators/Microsoft.IdentityModel.Validators.csproj @@ -25,6 +25,7 @@ + diff --git a/test/Microsoft.IdentityModel.Validators.Tests/AadSigningKeyIssuerValidatorTests.cs b/test/Microsoft.IdentityModel.Validators.Tests/AadSigningKeyIssuerValidatorTests.cs new file mode 100644 index 0000000000..e768a2372f --- /dev/null +++ b/test/Microsoft.IdentityModel.Validators.Tests/AadSigningKeyIssuerValidatorTests.cs @@ -0,0 +1,353 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.IdentityModel.Tokens.Jwt; +using System.Security.Claims; +using System.Threading.Tasks; +using Microsoft.IdentityModel.JsonWebTokens; +using Microsoft.IdentityModel.Protocols; +using Microsoft.IdentityModel.Protocols.OpenIdConnect; +using Microsoft.IdentityModel.TestUtils; +using Microsoft.IdentityModel.Tokens; +using Xunit; + +#pragma warning disable CS3016 // Arrays as attribute arguments is not CLS-compliant + +namespace Microsoft.IdentityModel.Validators.Tests +{ + public class AadSigningKeyIssuerValidatorTests + { + [Theory, MemberData(nameof(EnableAadSigningKeyIssuerValidationTestCases))] + public async Task EnableAadSigningKeyIssuerValidationTests(AadSigningKeyIssuerTheoryData theoryData) + { + var context = TestUtilities.WriteHeader($"{this}.EnableAadSigningKeyIssuerValidationTests", theoryData); + try + { + // set delegates + bool delegateSet = false; + if (theoryData.SetDelegateUsingConfig) + { + theoryData.TokenValidationParameters.IssuerSigningKeyValidatorUsingConfiguration = (securityKey, securityToken, tvp, config) => { delegateSet = true; return true; }; + } + else if (theoryData.SetDelegateWithoutConfig) + { + theoryData.TokenValidationParameters.IssuerSigningKeyValidatorUsingConfiguration = null; + theoryData.TokenValidationParameters.IssuerSigningKeyValidator = (securityKey, securityToken, tvp) => { delegateSet = true; return true; }; + } + + var handler = new JsonWebTokenHandler(); + var jwt = handler.ReadJsonWebToken(Default.AsymmetricJws); + AadIssuerValidator.GetAadIssuerValidator(Default.AadV1Authority).ConfigurationManagerV1 = theoryData.TokenValidationParameters.ConfigurationManager; + theoryData.TokenValidationParameters.EnableAadSigningKeyIssuerValidation(); + + var validationResult = await handler.ValidateTokenAsync(jwt, theoryData.TokenValidationParameters).ConfigureAwait(false); + theoryData.ExpectedException.ProcessNoException(context); + Assert.NotNull(theoryData.TokenValidationParameters.IssuerSigningKeyValidatorUsingConfiguration); + Assert.True(validationResult.IsValid); + + // verify delegates were executed + if (theoryData.SetDelegateUsingConfig || theoryData.SetDelegateWithoutConfig) + Assert.True(delegateSet); + } + catch (Exception ex) + { + theoryData.ExpectedException.ProcessException(ex, context); + } + + TestUtilities.AssertFailIfErrors(context); + } + + public static TheoryData EnableAadSigningKeyIssuerValidationTestCases() + { + var signingKeysConfig = new OpenIdConnectConfiguration() { TokenEndpoint = Default.Issuer + "oauth/token", Issuer = Default.Issuer }; + signingKeysConfig.SigningKeys.Add(KeyingMaterial.DefaultX509Key_2048); + var validationParameters = new TokenValidationParameters() + { + ConfigurationManager = new StaticConfigurationManager(signingKeysConfig), + ValidateIssuerSigningKey = true, + ValidateAudience = false, + ValidateLifetime = false + }; + + var theoryData = new TheoryData + { + new AadSigningKeyIssuerTheoryData + { + TestId = "IssuerSigningKeyValidatorUsingConfiguration_Delegate_IsSetByWilson", + TokenValidationParameters = validationParameters + }, + new AadSigningKeyIssuerTheoryData + { + TestId = "IssuerSigningKeyValidatorUsingConfiguration_Delegate_IsSetByDeveloper", + TokenValidationParameters = validationParameters, + SetDelegateUsingConfig = true, + }, + new AadSigningKeyIssuerTheoryData + { + TestId = "IssuerSigningKeyValidator_Delegate_IsSetByDeveloper", + TokenValidationParameters = validationParameters, + SetDelegateWithoutConfig = true, + } + }; + + return theoryData; + } + + [Theory, MemberData(nameof(ValidateIssuerSigningKeyCertificateTestCases))] + public void ValidateIssuerSigningKeyCertificateTests(AadSigningKeyIssuerTheoryData theoryData) + { + var context = TestUtilities.WriteHeader($"{this}.ValidateIssuerSigningKeyCertificateTests", theoryData); + + try + { + var result = AadTokenValidationParametersExtension.ValidateIssuerSigningKeyCertificate(theoryData.SecurityKey, theoryData.TokenValidationParameters); + theoryData.ExpectedException.ProcessNoException(context); + Assert.True(result); + } + catch (Exception ex) + { + theoryData.ExpectedException.ProcessException(ex, context); + } + + TestUtilities.AssertFailIfErrors(context); + } + + public static TheoryData ValidateIssuerSigningKeyCertificateTestCases() + { + var theoryData = new TheoryData + { + new AadSigningKeyIssuerTheoryData + { + TestId = "SecurityKeyIsNull", + SecurityKey = null, + TokenValidationParameters = new TokenValidationParameters() { RequireSignedTokens = true, ValidateIssuerSigningKey = true }, + ExpectedException = ExpectedException.ArgumentNullException("IDX40007:") + }, + new AadSigningKeyIssuerTheoryData + { + TestId = "SecurityKeyIsNull_RequireSignedTokensFalse", + SecurityKey = null, + TokenValidationParameters = new TokenValidationParameters() { RequireSignedTokens = false, ValidateIssuerSigningKey = true }, + }, + new AadSigningKeyIssuerTheoryData + { + TestId = "ServiceAcceptsUnsignedTokens", + SecurityKey = KeyingMaterial.JsonWebKeyP256, + TokenValidationParameters = new TokenValidationParameters() { RequireSignedTokens = false, ValidateIssuerSigningKey = true }, + }, + new AadSigningKeyIssuerTheoryData + { + TestId = "SkipValidaingIssuerSigningKey", + SecurityKey = KeyingMaterial.JsonWebKeyP256, + TokenValidationParameters = new TokenValidationParameters() { RequireSignedTokens = true, ValidateIssuerSigningKey = false }, + }, + new AadSigningKeyIssuerTheoryData + { + TestId = "SkipValidaingIssuerSigningKey", + SecurityKey = KeyingMaterial.JsonWebKeyP256, + TokenValidationParameters = new TokenValidationParameters() { RequireSignedTokens = false, ValidateIssuerSigningKey = false }, + }, + new AadSigningKeyIssuerTheoryData + { + TestId = "CertificateLifeTimeValidated", + SecurityKey = KeyingMaterial.X509SecurityKeySelfSigned1024_SHA256, + TokenValidationParameters = new TokenValidationParameters() { RequireSignedTokens = true, ValidateIssuerSigningKey = true }, + } + }; + + return theoryData; + } + + [Theory, MemberData(nameof(ValidateIssuerSigningKeyTestCases))] + public void ValidateIssuerSigningKeyTests(AadSigningKeyIssuerTheoryData theoryData) + { + var context = TestUtilities.WriteHeader($"{this}.ValidateIssuerSigningKeyTests", theoryData); + + try + { + var result = AadTokenValidationParametersExtension.ValidateIssuerSigningKey(theoryData.SecurityKey, theoryData.SecurityToken, theoryData.OpenIdConnectConfiguration); + theoryData.ExpectedException.ProcessNoException(context); + Assert.True(result); + } + catch (Exception ex) + { + theoryData.ExpectedException.ProcessException(ex, context); + } + + TestUtilities.AssertFailIfErrors(context); + } + + public static TheoryData ValidateIssuerSigningKeyTestCases + { + get + { + var theoryData = new TheoryData(); + + var tidClaim = new Claim(ValidatorConstants.ClaimNameTid, ValidatorConstants.TenantIdAsGuid); + var issClaim = new Claim(ValidatorConstants.ClaimNameIss, ValidatorConstants.AadIssuer); + var jwtSecurityToken = new JwtSecurityToken(issuer: ValidatorConstants.AadIssuer, claims: new[] { issClaim, tidClaim }); + theoryData.Add(new AadSigningKeyIssuerTheoryData + { + TestId = "NullSecurityKey", + SecurityKey = null, + SecurityToken = jwtSecurityToken, + OpenIdConnectConfiguration = GetConfigurationMock() + }); + + theoryData.Add(new AadSigningKeyIssuerTheoryData + { + TestId = "NullSecurityToken", + SecurityKey = KeyingMaterial.JsonWebKeyP256, + SecurityToken = null, + OpenIdConnectConfiguration = GetConfigurationMock(), + ExpectedException = ExpectedException.ArgumentNullException("IDX10000") + }); + + theoryData.Add(new AadSigningKeyIssuerTheoryData + { + TestId = "NullConfiguration", + SecurityKey = KeyingMaterial.JsonWebKeyP256, + SecurityToken = jwtSecurityToken, + OpenIdConnectConfiguration = null, + }); + + theoryData.Add(new AadSigningKeyIssuerTheoryData + { + TestId = "NoSigningKeysInConfiguration", + SecurityKey = KeyingMaterial.JsonWebKeyP256, + SecurityToken = jwtSecurityToken, + OpenIdConnectConfiguration = new OpenIdConnectConfiguration() + }); + + var mockConfiguration = GetConfigurationMock(); + mockConfiguration.JsonWebKeySet.Keys.Add(KeyingMaterial.JsonWebKeyP384); + theoryData.Add(new AadSigningKeyIssuerTheoryData + { + TestId = "NoMatchingKeysInConfiguration", + SecurityKey = KeyingMaterial.JsonWebKeyP256, + SecurityToken = jwtSecurityToken, + OpenIdConnectConfiguration = new OpenIdConnectConfiguration() + }); + + theoryData.Add(new AadSigningKeyIssuerTheoryData + { + TestId = "MissingIssuerInConfiguration", + SecurityKey = KeyingMaterial.JsonWebKeyP384, + SecurityToken = jwtSecurityToken, + OpenIdConnectConfiguration = mockConfiguration + }); + + var jwk = KeyingMaterial.JsonWebKeySymmetric128; + jwk.AdditionalData.Add(OpenIdProviderMetadataNames.Issuer, " "); + mockConfiguration.JsonWebKeySet.Keys.Add(jwk); + theoryData.Add(new AadSigningKeyIssuerTheoryData + { + TestId = "WhitespaceForIssuerInConfiguration", + SecurityKey = KeyingMaterial.JsonWebKeySymmetric128, + SecurityToken = jwtSecurityToken, + OpenIdConnectConfiguration = mockConfiguration, + }); + + jwk = KeyingMaterial.JsonWebKeyP521; + jwk.AdditionalData.Add(OpenIdProviderMetadataNames.Issuer, ValidatorConstants.UsGovIssuer); + mockConfiguration.JsonWebKeySet.Keys.Add(jwk); + theoryData.Add(new AadSigningKeyIssuerTheoryData + { + TestId = "JST_TokenIssuer_MismatchesWith_SigningKeyIssuer", + SecurityKey = KeyingMaterial.JsonWebKeyP521, + SecurityToken = jwtSecurityToken, + OpenIdConnectConfiguration = mockConfiguration, + ExpectedException = ExpectedException.SecurityTokenInvalidIssuerException("IDX40005") + }); + + List claims = new List + { + tidClaim, + issClaim + }; + var jsonWebToken = new JsonWebToken(Default.Jwt(Default.SecurityTokenDescriptor(Default.SymmetricSigningCredentials, claims))); + theoryData.Add(new AadSigningKeyIssuerTheoryData + { + TestId = "JWT_TokenIssuer_MismatchesWith_SigningKeyIssuer", + SecurityKey = KeyingMaterial.JsonWebKeyP521, + SecurityToken = jsonWebToken, + OpenIdConnectConfiguration = mockConfiguration, + ExpectedException = ExpectedException.SecurityTokenInvalidIssuerException("IDX40005") + }); + + jwk = KeyingMaterial.JsonWebKeyP256; + jwk.AdditionalData.Add(OpenIdProviderMetadataNames.Issuer, ValidatorConstants.AadIssuerV2CommonAuthority); + mockConfiguration.JsonWebKeySet.Keys.Add(jwk); + mockConfiguration.Issuer = ValidatorConstants.AadIssuerV2CommonAuthority; + theoryData.Add(new AadSigningKeyIssuerTheoryData + { + TestId = "HappyPath_TokenIssuer_Matches_SigningKeyIssuer", + SecurityKey = KeyingMaterial.JsonWebKeyP256, + SecurityToken = jwtSecurityToken, + OpenIdConnectConfiguration = mockConfiguration + }); + + theoryData.Add(new AadSigningKeyIssuerTheoryData + { + TestId = "MissingTenantIdClaimInToken", + SecurityKey = KeyingMaterial.JsonWebKeyP256, + SecurityToken = new JwtSecurityToken(), + OpenIdConnectConfiguration = mockConfiguration + }); + + theoryData.Add(new AadSigningKeyIssuerTheoryData + { + TestId = "JST_TokenIssuer_MismatchesWith_TenantIdInToken", + SecurityKey = KeyingMaterial.JsonWebKeyP256, + SecurityToken = new JwtSecurityToken(issuer: ValidatorConstants.AadIssuer, claims: new[] { issClaim, new Claim(ValidatorConstants.ClaimNameTid, ValidatorConstants.B2CTenantAsGuid) }), + OpenIdConnectConfiguration = mockConfiguration, + ExpectedException = ExpectedException.SecurityTokenInvalidIssuerException("IDX40004") + }); + + claims = new List + { + new Claim(ValidatorConstants.ClaimNameTid, ValidatorConstants.B2CTenantAsGuid), + issClaim + }; + jsonWebToken = new JsonWebToken(Default.Jwt(Default.SecurityTokenDescriptor(Default.SymmetricSigningCredentials, claims))); + theoryData.Add(new AadSigningKeyIssuerTheoryData + { + TestId = "JWT_TokenIssuer_MismatchesWith_TenantIdInToken", + SecurityKey = KeyingMaterial.JsonWebKeyP256, + SecurityToken = jsonWebToken, + OpenIdConnectConfiguration = mockConfiguration, + ExpectedException = ExpectedException.SecurityTokenInvalidIssuerException("IDX40004") + }); + + return theoryData; + } + } + + private static OpenIdConnectConfiguration GetConfigurationMock() + { + var config = new OpenIdConnectConfiguration(); + config.JsonWebKeySet = new JsonWebKeySet(); + config.JsonWebKeySet.Keys.Add(KeyingMaterial.JsonWebKeyP384); + return config; + } + + public class AadSigningKeyIssuerTheoryData : TheoryDataBase + { + public SecurityKey SecurityKey { get; set; } + + public TokenValidationParameters TokenValidationParameters { get; set; } + + public SecurityToken SecurityToken { get; set; } + + public OpenIdConnectConfiguration OpenIdConnectConfiguration { get; set; } + + public bool SetDelegateUsingConfig { get; set; } = false; + + public bool SetDelegateWithoutConfig { get; set; } = false; + } + } +} + +#pragma warning restore CS3016 // Arrays as attribute arguments is not CLS-compliant diff --git a/test/Microsoft.IdentityModel.Validators.Tests/ValidatorConstants.cs b/test/Microsoft.IdentityModel.Validators.Tests/ValidatorConstants.cs index 1c41fcd8d1..68adf2b3c0 100644 --- a/test/Microsoft.IdentityModel.Validators.Tests/ValidatorConstants.cs +++ b/test/Microsoft.IdentityModel.Validators.Tests/ValidatorConstants.cs @@ -31,6 +31,7 @@ internal class ValidatorConstants public const string UsGovIssuer = "https://login.microsoftonline.us/" + UsGovTenantId + "/v2.0"; public const string UsGovTenantId = "72f988bf-86f1-41af-91ab-2d7cd011db47"; public const string V1Issuer = "https://sts.windows.net/f645ad92-e38d-4d1a-b510-d1b09a74a8ca/"; + public const string AadIssuerV2CommonAuthority = AadInstance + "/{tenantid}/v2.0"; public const string B2CSignUpSignInUserFlow = "b2c_1_susi"; public const string B2CInstance = "https://fabrikamb2c.b2clogin.com";