diff --git a/src/Microsoft.IdentityModel.JsonWebTokens/JsonWebTokenHandler.cs b/src/Microsoft.IdentityModel.JsonWebTokens/JsonWebTokenHandler.cs index 45d2480467..b1656eac25 100644 --- a/src/Microsoft.IdentityModel.JsonWebTokens/JsonWebTokenHandler.cs +++ b/src/Microsoft.IdentityModel.JsonWebTokens/JsonWebTokenHandler.cs @@ -1264,11 +1264,11 @@ private async Task ValidateTokenAsync(JsonWebToken jsonWe validationParameters.ValidateWithLKG = true; var recoverableException = tokenValidationResult.Exception; - foreach (BaseConfiguration lkgConfiguration in validationParameters.ConfigurationManager.GetValidLkgConfiguraitonFromCache().Keys) + foreach (BaseConfiguration lkgConfiguration in validationParameters.ConfigurationManager.GetValidLkgConfiguraitonFromCache()) { if (!lkgConfiguration.Equals(currentConfiguration) && TokenUtilities.IsRecoverableConfiguration(jsonWebToken.Kid, currentConfiguration, lkgConfiguration, recoverableException)) { - tokenValidationResult = await ValidateTokenAsync(jsonWebToken, validationParameters, currentConfiguration).ConfigureAwait(false); + tokenValidationResult = await ValidateTokenAsync(jsonWebToken, validationParameters, lkgConfiguration).ConfigureAwait(false); if (tokenValidationResult.IsValid) return tokenValidationResult; diff --git a/src/Microsoft.IdentityModel.Tokens/BaseConfigurationComparer.cs b/src/Microsoft.IdentityModel.Tokens/BaseConfigurationComparer.cs index ee61f8aa1f..c551abb352 100644 --- a/src/Microsoft.IdentityModel.Tokens/BaseConfigurationComparer.cs +++ b/src/Microsoft.IdentityModel.Tokens/BaseConfigurationComparer.cs @@ -1,14 +1,14 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using System; using System.Collections.Generic; using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace Microsoft.IdentityModel.Tokens { + /// + /// Comparison class for a . + /// internal class BaseConfigurationComparer : IEqualityComparer { public bool Equals(BaseConfiguration config1, BaseConfiguration config2) @@ -26,7 +26,15 @@ public bool Equals(BaseConfiguration config1, BaseConfiguration config2) public int GetHashCode(BaseConfiguration config) { - return (config.Issuer + string.Join("", config.SigningKeys.Select(x => x.InternalId).OrderBy(x => x.FirstOrDefault()))).GetHashCode(); + int defaultHash = string.Empty.GetHashCode(); + int hashCode = defaultHash; + hashCode ^= string.IsNullOrEmpty(config.Issuer) ? defaultHash : config.Issuer.GetHashCode(); + foreach(string internalId in config.SigningKeys.Select(x => x.InternalId)) + { + hashCode ^= internalId.GetHashCode(); + } + + return hashCode; } } } diff --git a/src/Microsoft.IdentityModel.Tokens/BaseConfigurationManager.cs b/src/Microsoft.IdentityModel.Tokens/BaseConfigurationManager.cs index 30c144d595..455e0aba9f 100644 --- a/src/Microsoft.IdentityModel.Tokens/BaseConfigurationManager.cs +++ b/src/Microsoft.IdentityModel.Tokens/BaseConfigurationManager.cs @@ -80,6 +80,25 @@ public virtual Task GetBaseConfigurationAsync(CancellationTok throw new NotImplementedException(); } + /// + /// Gets all valid last known good configurations from the cache. + /// + /// A collection of all valid last known good configurations. + internal ICollection GetValidLkgConfiguraitonFromCache() + { + if (_lkgConfigurationCache == null) + return null; + + var expiredLkgConfiguration = _lkgConfigurationCache.Where(x => x.Value < DateTime.UtcNow).ToArray(); + + foreach (KeyValuePair lkgConfiguration in expiredLkgConfiguration) + { + _lkgConfigurationCache.Remove(lkgConfiguration.Key); + } + + return _lkgConfigurationCache.Any() ? _lkgConfigurationCache.Keys : null; + } + /// /// The last known good configuration or LKG (a configuration retrieved in the past that we were able to successfully validate a token against). /// @@ -100,30 +119,13 @@ public BaseConfiguration LastKnownGoodConfiguration _lkgConfigurationCache[_lastKnownGoodConfiguration] = DateTime.UtcNow + LastKnownGoodLifetime; //remove expired configuration to avoid memory leak - foreach (KeyValuePair lkgConfiguration in _lkgConfigurationCache) - { - if (lkgConfiguration.Value < DateTime.UtcNow) - _lkgConfigurationCache.Remove(lkgConfiguration.Key); - } - } - } + var expiredLkgConfiguration = _lkgConfigurationCache.Where(x => x.Value < DateTime.UtcNow).ToArray(); - /// - /// - /// - /// - public IDictionary GetValidLkgConfiguraitonFromCache() - { - if (_lkgConfigurationCache == null) - return null; - - foreach (KeyValuePair lkgConfiguration in _lkgConfigurationCache) - { - if (lkgConfiguration.Value < DateTime.UtcNow) + foreach (KeyValuePair lkgConfiguration in expiredLkgConfiguration) + { _lkgConfigurationCache.Remove(lkgConfiguration.Key); + } } - - return _lkgConfigurationCache; } /// diff --git a/src/Microsoft.IdentityModel.Tokens/TokenUtilities.cs b/src/Microsoft.IdentityModel.Tokens/TokenUtilities.cs index 011d3a82a6..057a5380c3 100644 --- a/src/Microsoft.IdentityModel.Tokens/TokenUtilities.cs +++ b/src/Microsoft.IdentityModel.Tokens/TokenUtilities.cs @@ -199,30 +199,36 @@ internal static bool IsRecoverableException(Exception exception) /// /// Check whether the given configuration is recoverable by LKG. /// - /// + /// The kid from token."/> /// The to check. - /// - /// + /// The LKG exception to check. + /// The exception to check. /// true if the configuration is recoverable otherwise, false. internal static bool IsRecoverableConfiguration(string kid, BaseConfiguration currentConfiguration, BaseConfiguration lkgConfiguration, Exception currentException) { - if (currentException is SecurityTokenInvalidIssuerException || (currentException as SecurityTokenUnableToValidateException)?.ValidationFailure == ValidationFailure.InvalidIssuer) + Lazy isRecoverableIssuer = new Lazy(() => currentConfiguration.Issuer != lkgConfiguration.Issuer); + Lazy isRecoverableSigningKey = new Lazy(() => lkgConfiguration.SigningKeys.Any(signingKey => signingKey.KeyId == kid)); + + if (currentException is SecurityTokenInvalidIssuerException) { - return currentConfiguration.Issuer != lkgConfiguration.Issuer; + return isRecoverableIssuer.Value; + } + else if (currentException is SecurityTokenSignatureKeyNotFoundException) + { + return isRecoverableSigningKey.Value; + } + else if ((currentException as SecurityTokenUnableToValidateException)?.ValidationFailure == ValidationFailure.InvalidIssuer) + { + return isRecoverableIssuer.Value && isRecoverableSigningKey.Value; } else if (currentException.GetType().Equals(typeof(SecurityTokenInvalidSignatureException))) { - SecurityKey currentSigningKey = currentConfiguration.SigningKeys.Where(x => x.KeyId == kid).FirstOrDefault(); + SecurityKey currentSigningKey = currentConfiguration.SigningKeys.FirstOrDefault(x => x.KeyId == kid); if (currentSigningKey == null) - return true; + return isRecoverableSigningKey.Value; - SecurityKey lkgSigningKey = lkgConfiguration.SigningKeys.Where(x => x.KeyId == kid).FirstOrDefault(); - return currentSigningKey.InternalId != lkgSigningKey.InternalId; - } - else if (currentException is SecurityTokenSignatureKeyNotFoundException) - { - SecurityKey lkgSigningKey = lkgConfiguration.SigningKeys.Where(x => x.KeyId == kid).FirstOrDefault(); - return lkgSigningKey != null; + SecurityKey lkgSigningKey = lkgConfiguration.SigningKeys.FirstOrDefault(signingKey => signingKey.KeyId == kid); + return lkgSigningKey != null && currentSigningKey.InternalId != lkgSigningKey.InternalId; } return false; diff --git a/src/System.IdentityModel.Tokens.Jwt/JwtSecurityTokenHandler.cs b/src/System.IdentityModel.Tokens.Jwt/JwtSecurityTokenHandler.cs index 34b034f154..d18f73eebb 100644 --- a/src/System.IdentityModel.Tokens.Jwt/JwtSecurityTokenHandler.cs +++ b/src/System.IdentityModel.Tokens.Jwt/JwtSecurityTokenHandler.cs @@ -934,13 +934,15 @@ private ClaimsPrincipal ValidateToken(string token, JwtSecurityToken outerToken, validationParameters.RefreshBeforeValidation = false; validationParameters.ValidateWithLKG = true; var recoverableException = exceptionThrown.SourceException; + string kid = outerToken != null ? outerToken.Header.Kid : + (ValidateSignatureUsingDelegates(token, validationParameters, null) ?? GetJwtSecurityTokenFromToken(token, validationParameters)).Header.Kid; - foreach (BaseConfiguration lkgConfiguration in validationParameters.ConfigurationManager.GetValidLkgConfiguraitonFromCache().Keys) + foreach (BaseConfiguration lkgConfiguration in validationParameters.ConfigurationManager.GetValidLkgConfiguraitonFromCache()) { - if (!lkgConfiguration.Equals(currentConfiguration) && TokenUtilities.IsRecoverableConfiguration(outerToken.Header.Kid, currentConfiguration, lkgConfiguration, recoverableException)) + if (!lkgConfiguration.Equals(currentConfiguration) && TokenUtilities.IsRecoverableConfiguration(kid, currentConfiguration, lkgConfiguration, recoverableException)) { - claimsPrincipal = outerToken != null ? ValidateJWE(token, outerToken, validationParameters, currentConfiguration, out signatureValidatedToken, out exceptionThrown) : - ValidateJWS(token, validationParameters, currentConfiguration, out signatureValidatedToken, out exceptionThrown); + claimsPrincipal = outerToken != null ? ValidateJWE(token, outerToken, validationParameters, lkgConfiguration, out signatureValidatedToken, out exceptionThrown) : + ValidateJWS(token, validationParameters, lkgConfiguration, out signatureValidatedToken, out exceptionThrown); if (claimsPrincipal != null) return claimsPrincipal; diff --git a/test/Microsoft.IdentityModel.Protocols.OpenIdConnect.Tests/ConfigurationManagerTests.cs b/test/Microsoft.IdentityModel.Protocols.OpenIdConnect.Tests/ConfigurationManagerTests.cs index 17ab65575a..616197c81d 100644 --- a/test/Microsoft.IdentityModel.Protocols.OpenIdConnect.Tests/ConfigurationManagerTests.cs +++ b/test/Microsoft.IdentityModel.Protocols.OpenIdConnect.Tests/ConfigurationManagerTests.cs @@ -105,11 +105,12 @@ public void GetSets() { TestUtilities.WriteHeader($"{this}.GetSets", "GetSets", true); + int ExpectedPropertyCount = 8; var configManager = new ConfigurationManager("OpenIdConnectMetadata.json", new OpenIdConnectConfigurationRetriever(), new FileDocumentRetriever()); Type type = typeof(ConfigurationManager); PropertyInfo[] properties = type.GetProperties(); - if (properties.Length != 7) - Assert.True(false, "Number of properties has changed from 7 to: " + properties.Length + ", adjust tests"); + if (properties.Length != ExpectedPropertyCount) + Assert.True(false, "Number of properties has changed from {ExpectedPropertyCount} to: " + properties.Length + ", adjust tests"); var defaultAutomaticRefreshInterval = ConfigurationManager.DefaultAutomaticRefreshInterval; var defaultRefreshInterval = ConfigurationManager.DefaultRefreshInterval; @@ -330,6 +331,43 @@ public void ResetLastKnownGoodLifetime() TestUtilities.AssertFailIfErrors(context); } + [Fact] + public void TestConfigurationComparer() + { + TestUtilities.WriteHeader($"{this}.TestConfigurationComparer", "TestConfigurationComparer", true); + var context = new CompareContext(); + + var config1 = new OpenIdConnectConfiguration() { TokenEndpoint = Default.Issuer + "oauth/token", Issuer = Default.Issuer }; + config1.SigningKeys.Add(KeyingMaterial.DefaultX509Key_2048); + config1.SigningKeys.Add(KeyingMaterial.DefaultRsaSecurityKey1); + config1.SigningKeys.Add(KeyingMaterial.DefaultRsaSecurityKey2); + + var config2 = new OpenIdConnectConfiguration() { TokenEndpoint = Default.Issuer + "oauth/token", Issuer = Default.Issuer }; + config2.SigningKeys.Add(KeyingMaterial.DefaultRsaSecurityKey1); + config2.SigningKeys.Add(KeyingMaterial.DefaultX509Key_2048); + config2.SigningKeys.Add(KeyingMaterial.DefaultRsaSecurityKey2); + + var config3 = new OpenIdConnectConfiguration() { TokenEndpoint = Default.Issuer + "oauth/token", Issuer = Default.Issuer }; + config3.SigningKeys.Add(KeyingMaterial.DefaultRsaSecurityKey1); + + var config4 = new OpenIdConnectConfiguration() { TokenEndpoint = Default.Issuer + "oauth/token", Issuer = Default.Issuer + "1" }; + config3.SigningKeys.Add(KeyingMaterial.DefaultRsaSecurityKey1); + + var configurationManager = new MockConfigurationManager(config1, config1); + IdentityComparer.AreEqual(configurationManager.GetValidLkgConfiguraitonFromCache().Count, 1, context); + + configurationManager.LastKnownGoodConfiguration = config2; + IdentityComparer.AreEqual(configurationManager.GetValidLkgConfiguraitonFromCache().Count, 1, context); + + configurationManager.LastKnownGoodConfiguration = config3; + IdentityComparer.AreEqual(configurationManager.GetValidLkgConfiguraitonFromCache().Count, 2, context); + + configurationManager.LastKnownGoodConfiguration = config4; + IdentityComparer.AreEqual(configurationManager.GetValidLkgConfiguraitonFromCache().Count, 3, context); + + TestUtilities.AssertFailIfErrors(context); + } + [Theory, MemberData(nameof(ValidateOpenIdConnectConfigurationTestCases), DisableDiscoveryEnumeration = true)] public void ValidateOpenIdConnectConfigurationTests(ConfigurationManagerTheoryData theoryData) { diff --git a/test/Microsoft.IdentityModel.TestUtils/MockConfigurationManager.cs b/test/Microsoft.IdentityModel.TestUtils/MockConfigurationManager.cs index ee371de0b6..ccc9019af4 100644 --- a/test/Microsoft.IdentityModel.TestUtils/MockConfigurationManager.cs +++ b/test/Microsoft.IdentityModel.TestUtils/MockConfigurationManager.cs @@ -74,6 +74,30 @@ public MockConfigurationManager(T configuration, T lkgConfiguration, T refreshed _refreshedConfiguration = refreshedConfiguration; } + /// + /// Initializes an new instance of with a Configuration instance and a LKG Configuration instance. + /// + /// Configuration of type OpenIdConnectConfiguration or WsFederationConfiguration. + /// The LKG configuration lifetime. + public MockConfigurationManager(T configuration, TimeSpan lkgLifetime) : this(configuration) + { + LastKnownGoodLifetime = lkgLifetime; + } + + /// + /// Initializes an new instance of with a Configuration instance and a LKG Configuration instance. + /// + /// Configuration of type OpenIdConnectConfiguration or WsFederationConfiguration. + /// Configuration of type OpenIdConnectConfiguration or WsFederationConfiguration. + /// The LKG configuration lifetime. + public MockConfigurationManager(T configuration, T lkgConfiguration, TimeSpan lkgLifetime) : this(configuration, lkgLifetime) + { + if (lkgConfiguration == null) + throw LogHelper.LogExceptionMessage(new ArgumentNullException(nameof(lkgConfiguration))); + + LastKnownGoodConfiguration = lkgConfiguration as BaseConfiguration; + } + /// /// Obtains an updated version of Configuration. /// diff --git a/test/System.IdentityModel.Tokens.Jwt.Tests/JwtTestDatasets.cs b/test/System.IdentityModel.Tokens.Jwt.Tests/JwtTestDatasets.cs index c420e48dee..9a5d81861b 100644 --- a/test/System.IdentityModel.Tokens.Jwt.Tests/JwtTestDatasets.cs +++ b/test/System.IdentityModel.Tokens.Jwt.Tests/JwtTestDatasets.cs @@ -287,18 +287,24 @@ public static TheoryData ValidateJwsWithLastKnownGoodTheoryData var validConfigKeyValidationFails = new OpenIdConnectConfiguration() { TokenEndpoint = Default.Issuer + "oauth/token", Issuer = Default.Issuer }; validConfigKeyValidationFails.SigningKeys.Add(KeyingMaterial.DefaultX509Key_2048); - var invalidIssuerConfig = new OpenIdConnectConfiguration() { TokenEndpoint = Default.Issuer + "oauth/token", Issuer = Default.Issuer + "2" }; + var invalidIssuerConfig = new OpenIdConnectConfiguration() { TokenEndpoint = Default.Issuer + "oauth/token", Issuer = Default.Issuer + "1" }; invalidIssuerConfig.SigningKeys.Add(KeyingMaterial.DefaultX509Key_2048); var incorrectSigningKeysConfig = new OpenIdConnectConfiguration() { TokenEndpoint = Default.Issuer + "oauth/token", Issuer = Default.Issuer }; incorrectSigningKeysConfig.SigningKeys.Add(KeyingMaterial.X509SecurityKey2); - var incorrectIssuerAndSigningKeysConfig = new OpenIdConnectConfiguration() { TokenEndpoint = Default.Issuer + "oauth/token", Issuer = Default.Issuer + "2" }; + var incorrectIssuerAndSigningKeysConfig = new OpenIdConnectConfiguration() { TokenEndpoint = Default.Issuer + "oauth/token", Issuer = Default.Issuer + "1" }; + incorrectIssuerAndSigningKeysConfig.SigningKeys.Add(KeyingMaterial.X509SecurityKey2); + + var incorrectIssuerAndSigningKeysConfig2 = new OpenIdConnectConfiguration() { TokenEndpoint = Default.Issuer + "oauth/token", Issuer = Default.Issuer + "2" }; incorrectIssuerAndSigningKeysConfig.SigningKeys.Add(KeyingMaterial.X509SecurityKey2); var incorrectSigningKeysConfigWithMatchingKid = new OpenIdConnectConfiguration() { TokenEndpoint = Default.Issuer + "oauth/token", Issuer = Default.Issuer }; incorrectSigningKeysConfigWithMatchingKid.SigningKeys.Add(KeyingMaterial.CreateJsonWebKeyEC(JsonWebKeyECTypes.P256, Default.X509AsymmetricSigningCredentials.Key.KeyId, KeyingMaterial.P256_D, KeyingMaterial.P256_X, KeyingMaterial.P256_Y)); + var incorrectIssuerAndIncorrectSigningKeysConfigWithMatchingKid = new OpenIdConnectConfiguration() { TokenEndpoint = Default.Issuer + "oauth/token", Issuer = Default.Issuer + "1" }; + incorrectSigningKeysConfigWithMatchingKid.SigningKeys.Add(KeyingMaterial.CreateJsonWebKeyEC(JsonWebKeyECTypes.P521, Default.X509AsymmetricSigningCredentials.Key.KeyId, KeyingMaterial.P521_D, KeyingMaterial.P521_X, KeyingMaterial.P521_Y)); + var expiredSecurityTokenDescriptor = Default.X509SecurityTokenDescriptor(Default.X509AsymmetricSigningCredentials); expiredSecurityTokenDescriptor.NotBefore = DateTime.UtcNow + TimeSpan.FromDays(1); expiredSecurityTokenDescriptor.Expires = DateTime.UtcNow + System.TimeSpan.FromDays(2); @@ -368,6 +374,7 @@ public static TheoryData ValidateJwsWithLastKnownGoodTheoryData }, new JwtTheoryData { + // SecurityTokenInvalidSigningKeyException is no longer a recoverable exception TestId = nameof(Default.AsymmetricJws) + "_ConfigInvalid_IssuerSigningKeyValidationFails_LKGValid", Token = Default.AsymmetricJws, ValidationParameters = new TokenValidationParameters @@ -386,7 +393,8 @@ public static TheoryData ValidateJwsWithLastKnownGoodTheoryData else return true; }, - } + }, + ExpectedException = new ExpectedException(typeof(SecurityTokenInvalidSigningKeyException)) }, new JwtTheoryData { @@ -394,7 +402,7 @@ public static TheoryData ValidateJwsWithLastKnownGoodTheoryData Token = Default.AsymmetricJws, ValidationParameters = new TokenValidationParameters { - ConfigurationManager = new MockConfigurationManager(incorrectSigningKeysConfig, validConfig) {LastKnownGoodLifetime = TimeSpan.FromMilliseconds(.000001) }, + ConfigurationManager = new MockConfigurationManager(incorrectSigningKeysConfig, validConfig, TimeSpan.FromMilliseconds(.000001)), ValidateIssuerSigningKey = true, RequireSignedTokens = true, ValidateIssuer = true, @@ -409,7 +417,7 @@ public static TheoryData ValidateJwsWithLastKnownGoodTheoryData Token = Default.AsymmetricJws, ValidationParameters = new TokenValidationParameters { - ConfigurationManager = new MockConfigurationManager(invalidIssuerConfig, validConfig) {LastKnownGoodLifetime = TimeSpan.FromMilliseconds(.000001) }, + ConfigurationManager = new MockConfigurationManager(invalidIssuerConfig, validConfig, TimeSpan.FromMilliseconds(.000001)), ValidateIssuerSigningKey = true, RequireSignedTokens = true, ValidateIssuer = true, @@ -424,7 +432,7 @@ public static TheoryData ValidateJwsWithLastKnownGoodTheoryData Token = Default.AsymmetricJws, ValidationParameters = new TokenValidationParameters { - ConfigurationManager = new MockConfigurationManager(incorrectSigningKeysConfigWithMatchingKid, validConfig) {LastKnownGoodLifetime = TimeSpan.FromMilliseconds(.000001) }, + ConfigurationManager = new MockConfigurationManager(incorrectSigningKeysConfigWithMatchingKid, validConfig, TimeSpan.FromMilliseconds(.000001)), ValidateIssuerSigningKey = true, RequireSignedTokens = true, ValidateIssuer = true, @@ -439,7 +447,7 @@ public static TheoryData ValidateJwsWithLastKnownGoodTheoryData Token = Default.AsymmetricJws, ValidationParameters = new TokenValidationParameters { - ConfigurationManager = new MockConfigurationManager(incorrectIssuerAndSigningKeysConfig, validConfig) {LastKnownGoodLifetime = TimeSpan.FromMilliseconds(.000001) }, + ConfigurationManager = new MockConfigurationManager(incorrectIssuerAndSigningKeysConfig, validConfig, TimeSpan.FromMilliseconds(.000001)), ValidateIssuerSigningKey = true, RequireSignedTokens = true, ValidateIssuer = true, @@ -454,7 +462,7 @@ public static TheoryData ValidateJwsWithLastKnownGoodTheoryData Token = Default.AsymmetricJws, ValidationParameters = new TokenValidationParameters { - ConfigurationManager = new MockConfigurationManager(validConfigKeyValidationFails, validConfig) {LastKnownGoodLifetime = TimeSpan.FromMilliseconds(.000001) }, + ConfigurationManager = new MockConfigurationManager(validConfigKeyValidationFails, validConfig, TimeSpan.FromMilliseconds(.000001)), ValidateIssuerSigningKey = true, RequireSignedTokens = true, ValidateIssuer = true, @@ -561,7 +569,7 @@ public static TheoryData ValidateJwsWithLastKnownGoodTheoryData }, new JwtTheoryData { - TestId = nameof(Default.AsymmetricJws) + "_ConfigIssuerInvalid_AadIssuerValidatorThrow_LKGInvalid", + TestId = nameof(Default.AsymmetricJws) + "_ConfigIssuerInvalid_AadIssuerValidatorThrow_LKGSameInvalidIssuer", Token = Default.AadAsymmetricJws, ValidationParameters = new TokenValidationParameters { @@ -573,9 +581,41 @@ public static TheoryData ValidateJwsWithLastKnownGoodTheoryData ValidateAudience = false, ValidateLifetime = false, }, + ExpectedException = new ExpectedException(typeof(SecurityTokenInvalidIssuerException)) + }, + new JwtTheoryData + { + TestId = nameof(Default.AsymmetricJws) + "_ConfigIssuerInvalid_AadIssuerValidatorThrow_LKGDiffInvalidIssuer", + Token = Default.AadAsymmetricJws, + ValidationParameters = new TokenValidationParameters + { + ConfigurationManager = new MockConfigurationManager(invalidIssuerConfig, incorrectIssuerAndSigningKeysConfig2), + ValidateIssuerSigningKey = true, + RequireSignedTokens = true, + ValidateIssuer = true, + IssuerValidator = AadIssuerValidator.GetAadIssuerValidator(Default.AadV1Authority).Validate, + ValidateAudience = false, + ValidateLifetime = false, + }, ExpectedException = new ExpectedException(typeof(SecurityTokenUnableToValidateException)) }, new JwtTheoryData + { + TestId = nameof(Default.AsymmetricJws) + "_ConfigInvalidSigningKeyMatchingKid_AadIssuerValidatorThrow_LKGDiffInvalidSigningKeyMatchingKidAndInvalidIssuer", + Token = Default.AadAsymmetricJws, + ValidationParameters = new TokenValidationParameters + { + ConfigurationManager = new MockConfigurationManager(incorrectSigningKeysConfigWithMatchingKid, incorrectIssuerAndSigningKeysConfig), + ValidateIssuerSigningKey = true, + RequireSignedTokens = true, + ValidateIssuer = true, + IssuerValidator = AadIssuerValidator.GetAadIssuerValidator(Default.AadV1Authority).Validate, + ValidateAudience = false, + ValidateLifetime = false, + }, + ExpectedException = new ExpectedException(typeof(SecurityTokenInvalidSignatureException)) + }, + new JwtTheoryData { TestId = nameof(Default.AsymmetricJws) + "_ConfigInvalid_AadIssuerValidatorThrow_LKGIssuerInvalid_RefreshedConfigKeyInvalid", Token = Default.AadAsymmetricJws, @@ -639,18 +679,24 @@ public static TheoryData ValidateJWEWithLastKnownGoodTheoryData var validConfigKeyValidationFails = new OpenIdConnectConfiguration() { TokenEndpoint = Default.Issuer + "oauth/token", Issuer = Default.Issuer }; validConfigKeyValidationFails.SigningKeys.Add(Default.SymmetricSigningKey256); - var invalidIssuerConfig = new OpenIdConnectConfiguration() { TokenEndpoint = Default.Issuer + "oauth/token", Issuer = Default.Issuer + "2" }; + var invalidIssuerConfig = new OpenIdConnectConfiguration() { TokenEndpoint = Default.Issuer + "oauth/token", Issuer = Default.Issuer + "1" }; invalidIssuerConfig.SigningKeys.Add(Default.SymmetricSigningKey256); var incorrectSigningKeysConfig = new OpenIdConnectConfiguration() { TokenEndpoint = Default.Issuer + "oauth/token", Issuer = Default.Issuer }; incorrectSigningKeysConfig.SigningKeys.Add(KeyingMaterial.X509SecurityKey2); - var incorrectIssuerAndSigningKeysConfig = new OpenIdConnectConfiguration() { TokenEndpoint = Default.Issuer + "oauth/token", Issuer = Default.Issuer + "2" }; + var incorrectIssuerAndSigningKeysConfig = new OpenIdConnectConfiguration() { TokenEndpoint = Default.Issuer + "oauth/token", Issuer = Default.Issuer + "1" }; + incorrectIssuerAndSigningKeysConfig.SigningKeys.Add(KeyingMaterial.X509SecurityKey2); + + var incorrectIssuerAndSigningKeysConfig2 = new OpenIdConnectConfiguration() { TokenEndpoint = Default.Issuer + "oauth/token", Issuer = Default.Issuer + "2" }; incorrectIssuerAndSigningKeysConfig.SigningKeys.Add(KeyingMaterial.X509SecurityKey2); var incorrectSigningKeysConfigWithMatchingKid = new OpenIdConnectConfiguration() { TokenEndpoint = Default.Issuer + "oauth/token", Issuer = Default.Issuer }; incorrectSigningKeysConfigWithMatchingKid.SigningKeys.Add(new SymmetricSecurityKey(KeyingMaterial.DefaultSymmetricSecurityKey_128.Key) { KeyId = KeyingMaterial.DefaultSymmetricSecurityKey_256.KeyId }); + var incorrectIssuerAndIncorrectSigningKeysConfigWithMatchingKid = new OpenIdConnectConfiguration() { TokenEndpoint = Default.Issuer + "oauth/token", Issuer = Default.Issuer + "1" }; + incorrectSigningKeysConfigWithMatchingKid.SigningKeys.Add(new SymmetricSecurityKey(KeyingMaterial.DefaultSymmetricSecurityKey_64.Key) { KeyId = KeyingMaterial.DefaultSymmetricSecurityKey_256.KeyId }); + var notYetValidSecurityTokenDescriptor = Default.X509SecurityTokenDescriptor(Default.SymmetricEncryptingCredentials, Default.X509AsymmetricSigningCredentials, null); notYetValidSecurityTokenDescriptor.NotBefore = DateTime.UtcNow + TimeSpan.FromDays(1); notYetValidSecurityTokenDescriptor.Expires = DateTime.UtcNow + TimeSpan.FromDays(2); @@ -722,6 +768,7 @@ public static TheoryData ValidateJWEWithLastKnownGoodTheoryData }, new JwtTheoryData { + // SecurityTokenInvalidSigningKeyException is no longer a recoverable exception TestId = nameof(jwe) + "_ConfigIssuerSigningKeyValidationFails_LKGValid", Token = jwe, ValidationParameters = new TokenValidationParameters @@ -741,7 +788,9 @@ public static TheoryData ValidateJWEWithLastKnownGoodTheoryData return true; }, TokenDecryptionKey = KeyingMaterial.DefaultSymmetricEncryptingCreds_Aes128_Sha2.Key - } + }, + ExpectedException = new ExpectedException(typeof(SecurityTokenInvalidSigningKeyException)) + }, new JwtTheoryData { @@ -749,7 +798,7 @@ public static TheoryData ValidateJWEWithLastKnownGoodTheoryData Token = jwe, ValidationParameters = new TokenValidationParameters { - ConfigurationManager = new MockConfigurationManager(incorrectSigningKeysConfig, validConfig) {LastKnownGoodLifetime = TimeSpan.FromMilliseconds(.000001) }, + ConfigurationManager = new MockConfigurationManager(incorrectSigningKeysConfig, validConfig, TimeSpan.FromMilliseconds(.000001)), ValidateIssuerSigningKey = true, RequireSignedTokens = true, ValidateIssuer = true, @@ -765,7 +814,7 @@ public static TheoryData ValidateJWEWithLastKnownGoodTheoryData Token = jwe, ValidationParameters = new TokenValidationParameters { - ConfigurationManager = new MockConfigurationManager(invalidIssuerConfig, validConfig) {LastKnownGoodLifetime = TimeSpan.FromMilliseconds(.000001) }, + ConfigurationManager = new MockConfigurationManager(invalidIssuerConfig, validConfig, TimeSpan.FromMilliseconds(.000001)), ValidateIssuerSigningKey = true, RequireSignedTokens = true, ValidateIssuer = true, @@ -781,7 +830,7 @@ public static TheoryData ValidateJWEWithLastKnownGoodTheoryData Token = jwe, ValidationParameters = new TokenValidationParameters { - ConfigurationManager = new MockConfigurationManager(incorrectSigningKeysConfigWithMatchingKid, validConfig) {LastKnownGoodLifetime = TimeSpan.FromMilliseconds(.000001) }, + ConfigurationManager = new MockConfigurationManager(incorrectSigningKeysConfigWithMatchingKid, validConfig, TimeSpan.FromMilliseconds(.000001)), ValidateIssuerSigningKey = true, RequireSignedTokens = true, ValidateIssuer = true, @@ -797,7 +846,7 @@ public static TheoryData ValidateJWEWithLastKnownGoodTheoryData Token = jwe, ValidationParameters = new TokenValidationParameters { - ConfigurationManager = new MockConfigurationManager(incorrectIssuerAndSigningKeysConfig, validConfig) {LastKnownGoodLifetime = TimeSpan.FromMilliseconds(.000001) }, + ConfigurationManager = new MockConfigurationManager(incorrectIssuerAndSigningKeysConfig, validConfig, TimeSpan.FromMilliseconds(.000001)), ValidateIssuerSigningKey = true, RequireSignedTokens = true, ValidateIssuer = true, @@ -813,7 +862,7 @@ public static TheoryData ValidateJWEWithLastKnownGoodTheoryData Token = jwe, ValidationParameters = new TokenValidationParameters { - ConfigurationManager = new MockConfigurationManager(validConfigKeyValidationFails, validConfig) {LastKnownGoodLifetime = TimeSpan.FromMilliseconds(.000001) }, + ConfigurationManager = new MockConfigurationManager(validConfigKeyValidationFails, validConfig, TimeSpan.FromMilliseconds(.000001)), ValidateIssuerSigningKey = true, RequireSignedTokens = true, ValidateIssuer = true, @@ -927,7 +976,7 @@ public static TheoryData ValidateJWEWithLastKnownGoodTheoryData }, new JwtTheoryData { - TestId = nameof(aadJwe) + "_ConfigIssuerInvalid_AadIssuerValidatorThrow_LKGInvalid", + TestId = nameof(aadJwe) + "_ConfigIssuerInvalid_AadIssuerValidatorThrow_LKGSameInvalidIssuer", Token = aadJwe, ValidationParameters = new TokenValidationParameters { @@ -939,11 +988,44 @@ public static TheoryData ValidateJWEWithLastKnownGoodTheoryData ValidateAudience = false, ValidateLifetime = false, TokenDecryptionKey = KeyingMaterial.DefaultSymmetricEncryptingCreds_Aes128_Sha2.Key - + }, + ExpectedException = new ExpectedException(typeof(SecurityTokenInvalidIssuerException)) + }, + new JwtTheoryData + { + TestId = nameof(aadJwe) + "_ConfigIssuerInvalid_AadIssuerValidatorThrow_LKGDiffInvalidIssuer", + Token = aadJwe, + ValidationParameters = new TokenValidationParameters + { + ConfigurationManager = new MockConfigurationManager(invalidIssuerConfig, incorrectIssuerAndSigningKeysConfig2), + ValidateIssuerSigningKey = true, + RequireSignedTokens = true, + ValidateIssuer = true, + IssuerValidator = AadIssuerValidator.GetAadIssuerValidator(Default.AadV1Authority).Validate, + ValidateAudience = false, + ValidateLifetime = false, + TokenDecryptionKey = KeyingMaterial.DefaultSymmetricEncryptingCreds_Aes128_Sha2.Key }, ExpectedException = new ExpectedException(typeof(SecurityTokenUnableToValidateException)) }, new JwtTheoryData + { + TestId = nameof(aadJwe) + "_ConfigInvalidSigningKeyMatchingKid_AadIssuerValidatorThrow_LKGDiffInvalidSigningKeyMatchingKidAndInvalidIssuer", + Token = aadJwe, + ValidationParameters = new TokenValidationParameters + { + ConfigurationManager = new MockConfigurationManager(incorrectSigningKeysConfigWithMatchingKid, incorrectIssuerAndIncorrectSigningKeysConfigWithMatchingKid), + ValidateIssuerSigningKey = true, + RequireSignedTokens = true, + ValidateIssuer = true, + IssuerValidator = AadIssuerValidator.GetAadIssuerValidator(Default.AadV1Authority).Validate, + ValidateAudience = false, + ValidateLifetime = false, + TokenDecryptionKey = KeyingMaterial.DefaultSymmetricEncryptingCreds_Aes128_Sha2.Key + }, + ExpectedException = new ExpectedException(typeof(SecurityTokenInvalidSignatureException)) + }, + new JwtTheoryData { TestId = nameof(aadJwe) + "_ConfigInvalid_AadIssuerValidatorThrow_LKGIssuerInvalid_RefreshedConfigKeyInvalid", Token = aadJwe,