diff --git a/src/Microsoft.IdentityModel.Tokens.Saml/Saml/SamlSecurityTokenHandler.cs b/src/Microsoft.IdentityModel.Tokens.Saml/Saml/SamlSecurityTokenHandler.cs index e173fea1e7..608e7ff958 100644 --- a/src/Microsoft.IdentityModel.Tokens.Saml/Saml/SamlSecurityTokenHandler.cs +++ b/src/Microsoft.IdentityModel.Tokens.Saml/Saml/SamlSecurityTokenHandler.cs @@ -8,6 +8,7 @@ using System.Linq; using System.Security.Claims; using System.Text; +using System.Threading; using System.Threading.Tasks; using System.Xml; using Microsoft.IdentityModel.Abstractions; @@ -1139,6 +1140,9 @@ public override ClaimsPrincipal ValidateToken(XmlReader reader, TokenValidationP if (validationParameters == null) throw LogArgumentNullException(nameof(validationParameters)); + validationParameters = SamlTokenUtilities.PopulateValidationParametersWithCurrentConfigurationAsync(validationParameters).ConfigureAwait(false).GetAwaiter() + .GetResult(); + var samlToken = ReadSamlToken(reader); if (samlToken == null) throw LogExceptionMessage( @@ -1150,25 +1154,41 @@ public override ClaimsPrincipal ValidateToken(XmlReader reader, TokenValidationP } /// - public override Task ValidateTokenAsync(string token, TokenValidationParameters validationParameters) + public override async Task ValidateTokenAsync(string token, TokenValidationParameters validationParameters) { try { - var claimsPrincipal = ValidateToken(token, validationParameters, out var validatedToken); - return Task.FromResult(new TokenValidationResult + if (string.IsNullOrWhiteSpace(token)) + throw LogArgumentNullException(nameof(token)); + + if (validationParameters == null) + throw LogArgumentNullException(nameof(validationParameters)); + + if (token.Length > MaximumTokenSizeInBytes) + throw LogExceptionMessage(new ArgumentException(FormatInvariant(TokenLogMessages.IDX10209, LogHelper.MarkAsNonPII(token.Length), LogHelper.MarkAsNonPII(MaximumTokenSizeInBytes)))); + + validationParameters = await SamlTokenUtilities.PopulateValidationParametersWithCurrentConfigurationAsync(validationParameters).ConfigureAwait(false); + + var samlToken = ValidateSignature(token, validationParameters); + if (samlToken == null) + throw LogExceptionMessage(new SecurityTokenValidationException( + FormatInvariant(TokenLogMessages.IDX10254, LogHelper.MarkAsNonPII(_className), LogHelper.MarkAsNonPII("ValidateToken"), LogHelper.MarkAsNonPII(GetType()), LogHelper.MarkAsNonPII("ValidateSignature"), LogHelper.MarkAsNonPII(typeof(SamlSecurityToken))))); + + var claimsPrincipal = ValidateToken(samlToken, token, validationParameters, out var validatedToken); + return new TokenValidationResult { SecurityToken = validatedToken, ClaimsIdentity = claimsPrincipal?.Identities.First(), IsValid = true, - }); + }; } catch (Exception ex) { - return Task.FromResult(new TokenValidationResult + return new TokenValidationResult { IsValid = false, Exception = ex - }); + }; } } @@ -1193,6 +1213,9 @@ public override ClaimsPrincipal ValidateToken(string token, TokenValidationParam if (token.Length > MaximumTokenSizeInBytes) throw LogExceptionMessage(new ArgumentException(FormatInvariant(TokenLogMessages.IDX10209, LogHelper.MarkAsNonPII(token.Length), LogHelper.MarkAsNonPII(MaximumTokenSizeInBytes)))); + validationParameters = SamlTokenUtilities.PopulateValidationParametersWithCurrentConfigurationAsync(validationParameters).ConfigureAwait(false).GetAwaiter() + .GetResult(); + var samlToken = ValidateSignature(token, validationParameters); if (samlToken == null) throw LogExceptionMessage(new SecurityTokenValidationException( diff --git a/src/Microsoft.IdentityModel.Tokens.Saml/Saml/SamlTokenUtilities.cs b/src/Microsoft.IdentityModel.Tokens.Saml/Saml/SamlTokenUtilities.cs index 2677b3663e..122be3cd98 100644 --- a/src/Microsoft.IdentityModel.Tokens.Saml/Saml/SamlTokenUtilities.cs +++ b/src/Microsoft.IdentityModel.Tokens.Saml/Saml/SamlTokenUtilities.cs @@ -7,6 +7,9 @@ using System.Collections.Generic; using Microsoft.IdentityModel.Xml; using System; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; using Microsoft.IdentityModel.Logging; using TokenLogMessages = Microsoft.IdentityModel.Tokens.LogMessages; @@ -165,5 +168,29 @@ internal static string GetXsiTypeForValue(object value) return null; } + + /// + /// Fetches current configuration from the ConfigurationManager of + /// and populates ValidIssuers and IssuerSigningKeys. + /// + /// the token validation parameters to update. + /// New TokenValidationParameters with ValidIssuers and IssuerSigningKeys updated. + internal static async Task PopulateValidationParametersWithCurrentConfigurationAsync( + TokenValidationParameters validationParameters) + { + if (validationParameters.ConfigurationManager == null) + { + return validationParameters; + } + + var currentConfiguration = await validationParameters.ConfigurationManager.GetBaseConfigurationAsync(CancellationToken.None).ConfigureAwait(false); + var validationParametersCloned = validationParameters.Clone(); + var issuers = new[] { currentConfiguration.Issuer }; + + validationParametersCloned.ValidIssuers = (validationParametersCloned.ValidIssuers == null ? issuers : validationParametersCloned.ValidIssuers.Concat(issuers)); + validationParametersCloned.IssuerSigningKeys = (validationParametersCloned.IssuerSigningKeys == null ? currentConfiguration.SigningKeys : validationParametersCloned.IssuerSigningKeys.Concat(currentConfiguration.SigningKeys)); + return validationParametersCloned; + + } } } diff --git a/src/Microsoft.IdentityModel.Tokens.Saml/Saml2/Saml2SecurityTokenHandler.cs b/src/Microsoft.IdentityModel.Tokens.Saml/Saml2/Saml2SecurityTokenHandler.cs index 8ff5d48f47..a588d4d467 100644 --- a/src/Microsoft.IdentityModel.Tokens.Saml/Saml2/Saml2SecurityTokenHandler.cs +++ b/src/Microsoft.IdentityModel.Tokens.Saml/Saml2/Saml2SecurityTokenHandler.cs @@ -8,6 +8,7 @@ using System.Linq; using System.Security.Claims; using System.Text; +using System.Threading; using System.Threading.Tasks; using System.Xml; using Microsoft.IdentityModel.Abstractions; @@ -171,28 +172,45 @@ public virtual SecurityToken CreateToken(SecurityTokenDescriptor tokenDescriptor } /// - public override Task ValidateTokenAsync(string token, TokenValidationParameters validationParameters) + public override async Task ValidateTokenAsync(string token, TokenValidationParameters validationParameters) { try { - var claimsPrincipal = ValidateToken(token, validationParameters, out var validatedToken); - return Task.FromResult(new TokenValidationResult + if (string.IsNullOrEmpty(token)) + throw LogArgumentNullException(nameof(token)); + + if (validationParameters == null) + throw LogArgumentNullException(nameof(validationParameters)); + + if (token.Length > MaximumTokenSizeInBytes) + throw LogExceptionMessage(new ArgumentException(FormatInvariant(TokenLogMessages.IDX10209, LogHelper.MarkAsNonPII(token.Length), LogHelper.MarkAsNonPII(MaximumTokenSizeInBytes)))); + + validationParameters = await SamlTokenUtilities.PopulateValidationParametersWithCurrentConfigurationAsync(validationParameters).ConfigureAwait(false); + + var samlToken = ValidateSignature(token, validationParameters); + if (samlToken == null) + throw LogExceptionMessage( + new SecurityTokenValidationException(FormatInvariant(TokenLogMessages.IDX10254, LogHelper.MarkAsNonPII(_className), LogHelper.MarkAsNonPII("ValidateToken"), LogHelper.MarkAsNonPII(_className), LogHelper.MarkAsNonPII("ValidateSignature"), LogHelper.MarkAsNonPII(typeof(Saml2SecurityToken))))); + var claimsPrincipal = ValidateToken(samlToken, token, validationParameters, out var validatedToken); + + return new TokenValidationResult { SecurityToken = validatedToken, ClaimsIdentity = claimsPrincipal?.Identities.First(), IsValid = true, - }); + }; } catch (Exception ex) { - return Task.FromResult(new TokenValidationResult + return new TokenValidationResult { IsValid = false, Exception = ex - }); + }; } } + /// /// Reads and validates a . /// @@ -212,6 +230,8 @@ public override ClaimsPrincipal ValidateToken(XmlReader reader, TokenValidationP if (validationParameters == null) throw LogArgumentNullException(nameof(validationParameters)); + validationParameters = SamlTokenUtilities.PopulateValidationParametersWithCurrentConfigurationAsync(validationParameters).ConfigureAwait(false).GetAwaiter().GetResult(); + var samlToken = ReadSaml2Token(reader); if (samlToken == null) throw LogExceptionMessage( @@ -236,21 +256,14 @@ public override ClaimsPrincipal ValidateToken(XmlReader reader, TokenValidationP /// A representing the identity contained in the token. public override ClaimsPrincipal ValidateToken(string token, TokenValidationParameters validationParameters, out SecurityToken validatedToken) { - if (string.IsNullOrEmpty(token)) - throw LogArgumentNullException(nameof(token)); - - if (validationParameters == null) - throw LogArgumentNullException(nameof(validationParameters)); - - if (token.Length > MaximumTokenSizeInBytes) - throw LogExceptionMessage(new ArgumentException(FormatInvariant(TokenLogMessages.IDX10209, LogHelper.MarkAsNonPII(token.Length), LogHelper.MarkAsNonPII(MaximumTokenSizeInBytes)))); - - var samlToken = ValidateSignature(token, validationParameters); - if (samlToken == null) - throw LogExceptionMessage( - new SecurityTokenValidationException(FormatInvariant(TokenLogMessages.IDX10254, LogHelper.MarkAsNonPII(_className), LogHelper.MarkAsNonPII("ValidateToken"), LogHelper.MarkAsNonPII(_className), LogHelper.MarkAsNonPII("ValidateSignature"), LogHelper.MarkAsNonPII(typeof(Saml2SecurityToken))))); + var tokenValidationResult = ValidateTokenAsync(token, validationParameters).ConfigureAwait(false).GetAwaiter().GetResult(); + if (!tokenValidationResult.IsValid) + { + throw tokenValidationResult.Exception; + } - return ValidateToken(samlToken, token, validationParameters, out validatedToken); + validatedToken = tokenValidationResult.SecurityToken; + return new ClaimsPrincipal(tokenValidationResult.ClaimsIdentity); } private ClaimsPrincipal ValidateToken(Saml2SecurityToken samlToken, string token, TokenValidationParameters validationParameters, out SecurityToken validatedToken) diff --git a/test/Microsoft.IdentityModel.Tokens.Saml.Tests/Saml2SecurityTokenHandlerTests.cs b/test/Microsoft.IdentityModel.Tokens.Saml.Tests/Saml2SecurityTokenHandlerTests.cs index 7c7915d2f0..bf4c87f786 100644 --- a/test/Microsoft.IdentityModel.Tokens.Saml.Tests/Saml2SecurityTokenHandlerTests.cs +++ b/test/Microsoft.IdentityModel.Tokens.Saml.Tests/Saml2SecurityTokenHandlerTests.cs @@ -5,6 +5,8 @@ using System.Collections.Generic; using System.Linq; using System.Security.Claims; +using Microsoft.IdentityModel.Protocols; +using Microsoft.IdentityModel.Protocols.WsFederation; using Microsoft.IdentityModel.TestUtils; using Microsoft.IdentityModel.Tokens.Saml.Tests; using Microsoft.IdentityModel.Xml; @@ -672,6 +674,63 @@ public static TheoryData ValidateTokenTheoryData Token = ReferenceTokens.Saml2Token_NoAttributes, ValidationParameters = new TokenValidationParameters(), }, + new Saml2TheoryData("ReferenceTokens_Saml2Token_Valid_Issuer_ConfigurationManager") + { + ExpectedException = ExpectedException.SecurityTokenSignatureKeyNotFoundException("IDX10500:"), + Token = ReferenceTokens.Saml2Token_Valid, + ValidationParameters = new TokenValidationParameters + { + ConfigurationManager = new StaticConfigurationManager(new WsFederationConfiguration() + { + Issuer = "https://sts.windows.net/add29489-7269-41f4-8841-b63c95564420/", + }), + ValidateIssuerSigningKey = false, + ValidateIssuer = true, + ValidateAudience = false, + ValidateLifetime = false, + } + }, + new Saml2TheoryData("ReferenceTokens_Saml2Token_Valid_IssuerSigningKey_ConfigurationManager") + { + Token = ReferenceTokens.Saml2Token_Valid, + ValidationParameters = new TokenValidationParameters + { + ConfigurationManager = new StaticConfigurationManager(new WsFederationConfiguration() + { + SigningKeys = { KeyingMaterial.DefaultAADSigningKey }, + }), + ValidateIssuer = false, + ValidateAudience = false, + ValidateLifetime = false, + } + }, + new Saml2TheoryData("ReferenceTokens_Saml2Token_Valid_IssuerSigningKey_and_Issuer_ConfigurationManager") + { + Token = ReferenceTokens.Saml2Token_Valid, + ValidationParameters = new TokenValidationParameters + { + ConfigurationManager = new StaticConfigurationManager(new WsFederationConfiguration() + { + Issuer = "https://sts.windows.net/add29489-7269-41f4-8841-b63c95564420/", + SigningKeys = { KeyingMaterial.DefaultAADSigningKey }, + }), + ValidateIssuer = true, + ValidateAudience = false, + ValidateLifetime = false, + } + }, + new Saml2TheoryData("ReferenceTokens_Saml2Token_Valid_NoSigningKey_ConfigurationManager") + { + ExpectedException = ExpectedException.SecurityTokenSignatureKeyNotFoundException("IDX10500:"), + Token = ReferenceTokens.Saml2Token_Valid, + ValidationParameters = new TokenValidationParameters + { + ConfigurationManager = new StaticConfigurationManager(new WsFederationConfiguration()), + ValidateIssuer = false, + ValidateAudience = false, + ValidateLifetime = false, + } + }, new Saml2TheoryData("ReferenceTokens_Saml2Token_Valid_IssuerSigningKey_set") { Token = ReferenceTokens.Saml2Token_Valid, diff --git a/test/Microsoft.IdentityModel.Tokens.Saml.Tests/SamlSecurityTokenHandlerTests.cs b/test/Microsoft.IdentityModel.Tokens.Saml.Tests/SamlSecurityTokenHandlerTests.cs index 96bb7ebd79..c22802d852 100644 --- a/test/Microsoft.IdentityModel.Tokens.Saml.Tests/SamlSecurityTokenHandlerTests.cs +++ b/test/Microsoft.IdentityModel.Tokens.Saml.Tests/SamlSecurityTokenHandlerTests.cs @@ -7,7 +7,10 @@ using System.IO; using System.Security.Claims; using System.Security.Cryptography; +using System.Threading.Tasks; using System.Xml; +using Microsoft.IdentityModel.Protocols; +using Microsoft.IdentityModel.Protocols.WsFederation; using Microsoft.IdentityModel.TestUtils; using Microsoft.IdentityModel.Xml; using Xunit; @@ -319,6 +322,23 @@ public void ValidateToken(SamlTheoryData theoryData) TestUtilities.AssertFailIfErrors(context); } + [Theory, MemberData(nameof(ValidateTokenTheoryData))] + public async Task ValidateTokenAsync(SamlTheoryData theoryData) + { + var context = TestUtilities.WriteHeader($"{this}.ValidateToken", theoryData); + var validationResult = await (theoryData.Handler as SamlSecurityTokenHandler).ValidateTokenAsync(theoryData.Token, theoryData.ValidationParameters); + if (validationResult.Exception != null) + { + theoryData.ExpectedException.ProcessException(validationResult.Exception, context); + } + else + { + theoryData.ExpectedException.ProcessNoException(context); + } + + TestUtilities.AssertFailIfErrors(context); + } + public static TheoryData ValidateTokenTheoryData { get @@ -427,6 +447,63 @@ public static TheoryData ValidateTokenTheoryData Token = ReferenceTokens.SamlToken_NoAttributes, ValidationParameters = new TokenValidationParameters(), }, + new SamlTheoryData("ReferenceTokens_SamlToken_Valid_Issuer_ConfigurationManager") + { + ExpectedException = ExpectedException.SecurityTokenSignatureKeyNotFoundException("IDX10500:"), + Token = ReferenceTokens.SamlToken_Valid, + ValidationParameters = new TokenValidationParameters + { + ConfigurationManager = new StaticConfigurationManager(new WsFederationConfiguration() + { + Issuer = "http://Default.Issuer.com", + }), + ValidateIssuerSigningKey = false, + ValidateIssuer = true, + ValidateAudience = false, + ValidateLifetime = false, + } + }, + new SamlTheoryData("ReferenceTokens_SamlToken_Valid_IssuerSigningKey_ConfigurationManager") + { + Token = ReferenceTokens.SamlToken_Valid, + ValidationParameters = new TokenValidationParameters + { + ConfigurationManager = new StaticConfigurationManager(new WsFederationConfiguration() + { + SigningKeys = { KeyingMaterial.DefaultX509SigningCreds_2048_RsaSha2_Sha2.Key }, + }), + ValidateIssuer = false, + ValidateAudience = false, + ValidateLifetime = false, + } + }, + new SamlTheoryData("ReferenceTokens_SamlToken_Valid_IssuerSigningKey_and_Issuer_ConfigurationManager") + { + Token = ReferenceTokens.SamlToken_Valid, + ValidationParameters = new TokenValidationParameters + { + ConfigurationManager = new StaticConfigurationManager(new WsFederationConfiguration() + { + Issuer = "http://Default.Issuer.com", + SigningKeys = { KeyingMaterial.DefaultX509SigningCreds_2048_RsaSha2_Sha2.Key }, + }), + ValidateIssuer = true, + ValidateAudience = false, + ValidateLifetime = false, + } + }, + new SamlTheoryData("ReferenceTokens_SamlToken_Valid_NoSigningKey_ConfigurationManager") + { + ExpectedException = ExpectedException.SecurityTokenSignatureKeyNotFoundException("IDX10500:"), + Token = ReferenceTokens.SamlToken_Valid, + ValidationParameters = new TokenValidationParameters + { + ConfigurationManager = new StaticConfigurationManager(new WsFederationConfiguration()), + ValidateIssuer = false, + ValidateAudience = false, + ValidateLifetime = false, + } + }, new SamlTheoryData("ReferenceTokens_SamlToken_Valid_IssuerSigningKey_Set") { Token = ReferenceTokens.SamlToken_Valid,