From 23808d5c7b11c3e0e9f202e48129c054e2b4f7ab Mon Sep 17 00:00:00 2001 From: Victor Hernandez Guzman <11362043+victorm-hernandez@users.noreply.github.com> Date: Tue, 20 Jun 2023 20:22:09 -0700 Subject: [PATCH] (feature): Add ActiveTokenEndpoint to WsFederationConfiguration (#2100) Added WsFederationConfigurationValidator, updates to WsFederationMetadataSerializer/WsFederationConfiguration Added support to the WsFederationMetadataSerializer to read the SecurityTokenSerivceEndpoint element from WS-Federation metadata and exposed it via the WsFederationConfiguration object as ActiveTokenEndpoint. Added WsFederationConfigurationValidator and related test cases. --- .../OpenIdConnectConfiguration.cs | 6 + .../WsFederationConfigurationValidator.cs | 205 +++++++++++++ .../LogMessages.cs | 30 +- .../SecurityTokenServiceTypeRoleDescriptor.cs | 14 +- .../WsFederationConstants.cs | 2 + .../WsFederationMetadataSerializer.cs | 105 ++++++- .../BaseConfiguration.cs | 8 +- .../OpenIdConnectConfigurationTests.cs | 4 +- ...WsFederationConfigurationRetrieverTests.cs | 145 ++++++++-- ...WsFederationConfigurationValidatorTests.cs | 269 ++++++++++++++++++ .../IdentityComparer.cs | 11 + .../ReferenceMetadata.cs | 161 +++++++++-- 12 files changed, 900 insertions(+), 60 deletions(-) create mode 100644 src/Microsoft.IdentityModel.Protocols.WsFederation/Configuration/WsFederationConfigurationValidator.cs create mode 100644 test/Microsoft.IdentityModel.Protocols.WsFederation.Tests/WsFederationConfigurationValidatorTests.cs diff --git a/src/Microsoft.IdentityModel.Protocols.OpenIdConnect/Configuration/OpenIdConnectConfiguration.cs b/src/Microsoft.IdentityModel.Protocols.OpenIdConnect/Configuration/OpenIdConnectConfiguration.cs index 521d8c17c8..77252a50fc 100644 --- a/src/Microsoft.IdentityModel.Protocols.OpenIdConnect/Configuration/OpenIdConnectConfiguration.cs +++ b/src/Microsoft.IdentityModel.Protocols.OpenIdConnect/Configuration/OpenIdConnectConfiguration.cs @@ -317,6 +317,12 @@ public OpenIdConnectConfiguration(string json) [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore, NullValueHandling = NullValueHandling.Ignore, PropertyName = OpenIdProviderMetadataNames.TokenEndpoint, Required = Required.Default)] public override string TokenEndpoint { get; set; } + /// + /// This base class property is not used in OpenIdConnect. + /// + [JsonIgnore] + public override string ActiveTokenEndpoint { get; set; } + /// /// Gets the collection of 'token_endpoint_auth_methods_supported'. /// diff --git a/src/Microsoft.IdentityModel.Protocols.WsFederation/Configuration/WsFederationConfigurationValidator.cs b/src/Microsoft.IdentityModel.Protocols.WsFederation/Configuration/WsFederationConfigurationValidator.cs new file mode 100644 index 0000000000..d5d5275e02 --- /dev/null +++ b/src/Microsoft.IdentityModel.Protocols.WsFederation/Configuration/WsFederationConfigurationValidator.cs @@ -0,0 +1,205 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Security.Cryptography; +using System.Security.Cryptography.X509Certificates; +using Microsoft.IdentityModel.Tokens; +using Microsoft.IdentityModel.Xml; +using static Microsoft.IdentityModel.Logging.LogHelper; + +namespace Microsoft.IdentityModel.Protocols.WsFederation +{ + /// + /// Defines a class for validating the WsFederationConfiguration. + /// + public class WsFederationConfigurationValidator : IConfigurationValidator + { + /// + /// Validates a WsFederationConfiguration. + /// + /// WsFederationConfiguration to validate + /// A containing the validation result. + /// If the provided configuration is null + public ConfigurationValidationResult Validate(WsFederationConfiguration configuration) + { + if (configuration == null) + throw LogArgumentNullException(nameof(configuration)); + + if (string.IsNullOrWhiteSpace(configuration.Issuer)) + { + return new ConfigurationValidationResult + { + ErrorMessage = LogMessages.IDX22700, + Succeeded = false + }; + } + + if (configuration.Signature == null) + { + return new ConfigurationValidationResult + { + ErrorMessage = LogMessages.IDX22701, + Succeeded = false + }; + } + + if (configuration.Signature.KeyInfo == null) + { + return new ConfigurationValidationResult + { + ErrorMessage = LogMessages.IDX22702, + Succeeded = false + }; + } + + if (string.IsNullOrWhiteSpace(configuration.Signature.SignatureValue)) + { + return new ConfigurationValidationResult + { + ErrorMessage = LogMessages.IDX22703, + Succeeded = false + }; + } + + if (configuration.Signature.SignedInfo == null) + { + return new ConfigurationValidationResult + { + ErrorMessage = LogMessages.IDX22704, + Succeeded = false + }; + } + + if (string.IsNullOrWhiteSpace(configuration.Signature.SignedInfo.SignatureMethod)) + { + return new ConfigurationValidationResult + { + ErrorMessage = LogMessages.IDX22705, + Succeeded = false + }; + } + + if (configuration.Signature.SignedInfo.References == null || configuration.Signature.SignedInfo.References.Count == 0) + { + return new ConfigurationValidationResult + { + ErrorMessage = LogMessages.IDX22706, + Succeeded = false + }; + } + + if (string.IsNullOrWhiteSpace(configuration.ActiveTokenEndpoint)) + { + return new ConfigurationValidationResult + { + ErrorMessage = LogMessages.IDX22707, + Succeeded = false + }; + } + + if (!Uri.IsWellFormedUriString(configuration.ActiveTokenEndpoint, UriKind.Absolute)) + { + return new ConfigurationValidationResult + { + ErrorMessage = LogMessages.IDX22708, + Succeeded = false + }; + } + + if (string.IsNullOrWhiteSpace(configuration.TokenEndpoint)) + { + return new ConfigurationValidationResult + { + ErrorMessage = LogMessages.IDX22709, + Succeeded = false + }; + } + + if (!Uri.IsWellFormedUriString(configuration.TokenEndpoint, UriKind.Absolute)) + { + return new ConfigurationValidationResult + { + ErrorMessage = LogMessages.IDX22710, + Succeeded = false + }; + } + + if (configuration.SigningKeys == null || configuration.SigningKeys.Count == 0) + { + return new ConfigurationValidationResult + { + ErrorMessage = LogMessages.IDX22711, + Succeeded = false + }; + } + + // Get the thumbprint of the cert used to sign the metadata + string signingKeyId = string.Empty; + var signatureX509Data = configuration.Signature.KeyInfo.X509Data.GetEnumerator(); + + if (signatureX509Data.MoveNext()) + { + var signatureCertData = signatureX509Data.Current.Certificates.GetEnumerator(); + if (signatureCertData.MoveNext() && !string.IsNullOrWhiteSpace(signatureCertData.Current)) + { + X509Certificate2 cert = null; + + try + { + cert = new X509Certificate2(Convert.FromBase64String(signatureCertData.Current)); + signingKeyId = cert.Thumbprint; + } + catch (CryptographicException) + { + return new ConfigurationValidationResult + { + ErrorMessage = LogMessages.IDX22712, + Succeeded = false + }; + } + finally + { + if (cert != null) + { + ((IDisposable)cert).Dispose(); + } + } + } + } + + // We know the key used to sign the doc is part of the token signing keys as per the spec. + // http://docs.oasis-open.org/wsfed/federation/v1.2/os/ws-federation-1.2-spec-os.html#_Toc223174958:~:text=%3C/fed%3ATargetScopes%20%3E-,3.1.15%20%5BSignature%5D%20Property,-The%20OPTIONAL%20%5BSignature + // If the metadata is for a token issuer then the key used to sign issued tokens SHOULD + // be used to sign this document. This means that if a is specified, + // it SHOULD be used to sign this document. + + foreach (SecurityKey key in configuration.SigningKeys) + { + if (key == null || key.CryptoProviderFactory == null || signingKeyId != key.KeyId) + continue; + + try + { + configuration.Signature.Verify(key, key.CryptoProviderFactory); + + return new ConfigurationValidationResult + { + Succeeded = true + }; + } + catch (XmlValidationException) + { + // We know the signature is invalid at this point + break; + } + } + + return new ConfigurationValidationResult + { + ErrorMessage = LogMessages.IDX22713, + Succeeded = false + }; + } + } +} diff --git a/src/Microsoft.IdentityModel.Protocols.WsFederation/LogMessages.cs b/src/Microsoft.IdentityModel.Protocols.WsFederation/LogMessages.cs index 2194fd704a..6c34b9ac0c 100644 --- a/src/Microsoft.IdentityModel.Protocols.WsFederation/LogMessages.cs +++ b/src/Microsoft.IdentityModel.Protocols.WsFederation/LogMessages.cs @@ -23,17 +23,35 @@ internal static class LogMessages internal const string IDX22904 = "IDX22904: Wresult does not contain a 'RequestedSecurityToken' element."; // xml metadata messages - internal const string IDX22800 = "IDX22800: Exception thrown while reading WsFedereationMetadata. Element '{0}'. Caught exception: '{1}'."; - internal const string IDX22801 = "IDX22801: entityID attribute is not found in EntityDescriptor element in metadata file."; + internal const string IDX22800 = "IDX22800: Exception thrown while reading WsFederationMetadata. Element '{0}'. Caught exception: '{1}'."; + internal const string IDX22801 = "IDX22801: 'entityID' attribute is not found in EntityDescriptor element in metadata file."; internal const string IDX22802 = "IDX22802: Current name '{0} and namespace '{1}' do not match the expected name '{2}' and namespace '{3}'."; - internal const string IDX22803 = "IDX22803: Token reference address is missing in SecurityTokenServiceEndpoint in metadata file."; - internal const string IDX22804 = "IDX22804: Security token type role descriptor is expected."; - internal const string IDX22806 = "IDX22806: Key descriptor for signing is missing in security token service type RoleDescriptor."; - internal const string IDX22807 = "IDX22807: Token endpoint is missing in security token service type RoleDescriptor."; + internal const string IDX22803 = "IDX22803: Token reference address is missing in 'PassiveRequestorEndpoint' in metadata file."; + internal const string IDX22804 = "IDX22804: 'SecurityTokenServiceTypeRoleDescriptor' is expected."; + internal const string IDX22806 = "IDX22806: Key descriptor for signing is missing in 'SecurityTokenServiceTypeRoleDescriptor'."; + internal const string IDX22807 = "IDX22807: Token endpoint is missing in 'SecurityTokenServiceTypeRoleDescriptor'."; internal const string IDX22808 = "IDX22808: 'Use' attribute is missing in KeyDescriptor."; internal const string IDX22810 = "IDX22810: 'Issuer' value is missing in wsfederationconfiguration."; internal const string IDX22811 = "IDX22811: 'TokenEndpoint' value is missing in wsfederationconfiguration."; internal const string IDX22812 = "IDX22812: Element: '{0}' was an empty element. 'TokenEndpoint' value is missing in wsfederationconfiguration."; + internal const string IDX22813 = "IDX22813: 'ActiveTokenEndpoint' is missing in 'SecurityTokenServiceTypeRoleDescriptor'."; + internal const string IDX22814 = "IDX22814: Token reference address is missing in 'SecurityTokenServiceEndpoint' in metadata."; + + // WsFederationConfigurationValidator messages + internal const string IDX22700 = "IDX22700: The Issuer property is null or empty."; + internal const string IDX22701 = "IDX22701: The Signature property is null."; + internal const string IDX22702 = "IDX22702: The Signature's KeyInfo property is null."; + internal const string IDX22703 = "IDX22703: The Signature's SignatureValue property is null or empty."; + internal const string IDX22704 = "IDX22704: The Signature.SignedInfo property is null or empty."; + internal const string IDX22705 = "IDX22705: The Signature.SignedInfo.SignatureMethod property is null or empty."; + internal const string IDX22706 = "IDX22706: The Signature.SignedInfo.References property is null or an empty collection."; + internal const string IDX22707 = "IDX22707: The ActiveTokenEndpoint property is not defined."; + internal const string IDX22708 = "IDX22708: The ActiveTokenEndpoint property is not a valid URI."; + internal const string IDX22709 = "IDX22709: The TokenEndpoint property is not defined."; + internal const string IDX22710 = "IDX22710: The TokenEndpoint property is not a valid URI."; + internal const string IDX22711 = "IDX22711: The SigningKeys is null or an empty collection."; + internal const string IDX22712 = "IDX22712: Could not identify the thumbprint of the key used to sign the metadata."; + internal const string IDX22713 = "IDX22713: Metadata signature validation failed."; #pragma warning restore 1591 } diff --git a/src/Microsoft.IdentityModel.Protocols.WsFederation/SecurityTokenServiceTypeRoleDescriptor.cs b/src/Microsoft.IdentityModel.Protocols.WsFederation/SecurityTokenServiceTypeRoleDescriptor.cs index a2ab06e3ff..2bddf7dd21 100644 --- a/src/Microsoft.IdentityModel.Protocols.WsFederation/SecurityTokenServiceTypeRoleDescriptor.cs +++ b/src/Microsoft.IdentityModel.Protocols.WsFederation/SecurityTokenServiceTypeRoleDescriptor.cs @@ -21,13 +21,23 @@ public List KeyInfos } = new List(); /// - /// Token endpoint + /// Passive Requestor Token endpoint + /// fed:PassiveRequestorEndpoint, https://docs.oasis-open.org/wsfed/federation/v1.2/os/ws-federation-1.2-spec-os.html#:~:text=fed%3ASecurityTokenServiceType/fed%3APassiveRequestorEndpoint /// public string TokenEndpoint { get; set; } + + /// + /// Active Requestor Token Endpoint + /// fed:SecurityTokenServiceType, http://docs.oasis-open.org/wsfed/federation/v1.2/os/ws-federation-1.2-spec-os.html#:~:text=fed%3ASecurityTokenSerivceEndpoint + /// + public string ActiveTokenEndpoint + { + get; + set; + } } } - diff --git a/src/Microsoft.IdentityModel.Protocols.WsFederation/WsFederationConstants.cs b/src/Microsoft.IdentityModel.Protocols.WsFederation/WsFederationConstants.cs index 94a5022d70..44900ef645 100644 --- a/src/Microsoft.IdentityModel.Protocols.WsFederation/WsFederationConstants.cs +++ b/src/Microsoft.IdentityModel.Protocols.WsFederation/WsFederationConstants.cs @@ -5,6 +5,7 @@ namespace Microsoft.IdentityModel.Protocols.WsFederation { /// /// Constants for WsFederation. + /// As defined in the http://docs.oasis-open.org/wsfed/federation/v1.2/os/ws-federation-1.2-spec-os.html /// public static class WsFederationConstants { @@ -93,6 +94,7 @@ public static class Elements public const string KeyDescriptor = "KeyDescriptor"; public const string RoleDescriptor = "RoleDescriptor"; public const string PassiveRequestorEndpoint = "PassiveRequestorEndpoint"; + public const string SecurityTokenServiceEndpoint = "SecurityTokenServiceEndpoint"; public const string SpssoDescriptor = "SPSSODescriptor"; } diff --git a/src/Microsoft.IdentityModel.Protocols.WsFederation/WsFederationMetadataSerializer.cs b/src/Microsoft.IdentityModel.Protocols.WsFederation/WsFederationMetadataSerializer.cs index 68ec35bbee..5bc0929711 100644 --- a/src/Microsoft.IdentityModel.Protocols.WsFederation/WsFederationMetadataSerializer.cs +++ b/src/Microsoft.IdentityModel.Protocols.WsFederation/WsFederationMetadataSerializer.cs @@ -106,7 +106,9 @@ protected virtual WsFederationConfiguration ReadEntityDescriptor(XmlReader reade } } } + configuration.TokenEndpoint = roleDescriptor.TokenEndpoint; + configuration.ActiveTokenEndpoint = roleDescriptor.ActiveTokenEndpoint; } else { @@ -177,6 +179,8 @@ protected virtual SecurityTokenServiceTypeRoleDescriptor ReadSecurityTokenServic roleDescriptor.KeyInfos.Add(ReadKeyDescriptorForSigning(reader)); else if (reader.IsStartElement(Elements.PassiveRequestorEndpoint, Namespace)) roleDescriptor.TokenEndpoint = ReadPassiveRequestorEndpoint(reader); + else if (reader.IsStartElement(Elements.SecurityTokenServiceEndpoint, Namespace)) + roleDescriptor.ActiveTokenEndpoint = ReadSecurityTokenServiceEndpoint(reader); else reader.ReadOuterXml(); } @@ -191,6 +195,9 @@ protected virtual SecurityTokenServiceTypeRoleDescriptor ReadSecurityTokenServic if (string.IsNullOrEmpty(roleDescriptor.TokenEndpoint)) LogHelper.LogWarning(LogMessages.IDX22807); + if (string.IsNullOrEmpty(roleDescriptor.ActiveTokenEndpoint)) + LogHelper.LogWarning(LogMessages.IDX22813); + return roleDescriptor; } @@ -211,6 +218,7 @@ protected virtual string ReadPassiveRequestorEndpoint(XmlReader reader) reader.ReadStartElement(); reader.MoveToContent(); + // XmlUtil.CheckReaderOnEntry(reader, WsAddressing.Elements.EndpointReference, WsAddressing.Namespace); if (reader.IsEmptyElement) throw XmlUtil.LogReadException(LogMessages.IDX22812, WsAddressing.Elements.EndpointReference); @@ -221,7 +229,9 @@ protected virtual string ReadPassiveRequestorEndpoint(XmlReader reader) if (reader.IsEmptyElement) throw XmlUtil.LogReadException(LogMessages.IDX22803); + //
XmlUtil.CheckReaderOnEntry(reader, WsAddressing.Elements.Address, WsAddressing.Namespace); + if (reader.IsEmptyElement) throw XmlUtil.LogReadException(LogMessages.IDX22812, WsAddressing.Elements.Address); @@ -248,6 +258,75 @@ protected virtual string ReadPassiveRequestorEndpoint(XmlReader reader) return tokenEndpoint; } + /// + /// Read fed:SecurityTokenServiceEndpoint element from metadata XML. + /// + /// used to read SecurityTokenServiceEndpoint. + /// Active token endpoint string + /// If an error occurs while reading the SecurityTokenServiceEndpoint + protected virtual string ReadSecurityTokenServiceEndpoint(XmlReader reader) + { + XmlUtil.CheckReaderOnEntry(reader, Elements.SecurityTokenServiceEndpoint, Namespace); + + // + if (reader.IsEmptyElement) + throw XmlUtil.LogReadException(LogMessages.IDX22812, Elements.SecurityTokenServiceEndpoint); + + reader.ReadStartElement(); + reader.MoveToContent(); + + // + XmlUtil.CheckReaderOnEntry(reader, WsAddressing.Elements.EndpointReference, WsAddressing.Namespace); + if (reader.IsEmptyElement) + throw XmlUtil.LogReadException(LogMessages.IDX22812, WsAddressing.Elements.EndpointReference); + + reader.ReadStartElement(WsAddressing.Elements.EndpointReference, WsAddressing.Namespace); + reader.MoveToContent(); + + if (reader.IsEmptyElement) + throw XmlUtil.LogReadException(LogMessages.IDX22814); + + string tokenEndpoint = null; + + while (reader.IsStartElement()) + { + if (reader.IsStartElement(WsAddressing.Elements.Address, WsAddressing.Namespace)) + { + //
+ XmlUtil.CheckReaderOnEntry(reader, WsAddressing.Elements.Address, WsAddressing.Namespace); + + if (reader.IsEmptyElement) + throw XmlUtil.LogReadException(LogMessages.IDX22812, WsAddressing.Elements.Address); + + reader.ReadStartElement(WsAddressing.Elements.Address, WsAddressing.Namespace); + reader.MoveToContent(); + + tokenEndpoint = Trim(reader.ReadContentAsString()); + + if (string.IsNullOrEmpty(tokenEndpoint)) + throw XmlUtil.LogReadException(LogMessages.IDX22814); + + //
+ reader.MoveToContent(); + reader.ReadEndElement(); + } + else + { + reader.ReadOuterXml(); + } + } + + //
+ reader.MoveToContent(); + reader.ReadEndElement(); + + //
+ reader.MoveToContent(); + reader.ReadEndElement(); + + return tokenEndpoint; + } + private static bool IsSecurityTokenServiceTypeRoleDescriptor(XmlReader reader) { if (reader == null || !reader.IsStartElement(Elements.RoleDescriptor, MetadataNamespace)) @@ -301,7 +380,7 @@ public void WriteMetadata(XmlWriter writer, WsFederationConfiguration configurat if (string.IsNullOrEmpty(configuration.TokenEndpoint)) throw XmlUtil.LogWriteException(LogMessages.IDX22811); - + if (configuration.SigningCredentials != null) writer = new EnvelopedSignatureWriter(writer, configuration.SigningCredentials, "id"); @@ -334,6 +413,30 @@ public void WriteMetadata(XmlWriter writer, WsFederationConfiguration configurat } } + if (!string.IsNullOrEmpty(configuration.ActiveTokenEndpoint)) + { + // + writer.WriteStartElement(PreferredPrefix, Elements.SecurityTokenServiceEndpoint, Namespace); + + // + writer.WriteStartElement(WsAddressing.PreferredPrefix, WsAddressing.Elements.EndpointReference, WsAddressing.Namespace); + + // + writer.WriteStartElement(WsAddressing.PreferredPrefix, WsAddressing.Elements.Address, WsAddressing.Namespace); + + // write TokenEndpoint + writer.WriteString(configuration.ActiveTokenEndpoint); + + // + writer.WriteEndElement(); + + // + writer.WriteEndElement(); + + // + writer.WriteEndElement(); + } + // writer.WriteStartElement(PreferredPrefix, Elements.PassiveRequestorEndpoint, Namespace); diff --git a/src/Microsoft.IdentityModel.Tokens/BaseConfiguration.cs b/src/Microsoft.IdentityModel.Tokens/BaseConfiguration.cs index bebbcbb95d..85f1a7578c 100644 --- a/src/Microsoft.IdentityModel.Tokens/BaseConfiguration.cs +++ b/src/Microsoft.IdentityModel.Tokens/BaseConfiguration.cs @@ -27,11 +27,17 @@ public virtual ICollection SigningKeys /// /// Gets or sets the token endpoint specified via the metadata endpoint. - /// This can be the fed:SecurityTokenServiceType in WS-Federation, http://docs.oasis-open.org/wsfed/federation/v1.2/os/ws-federation-1.2-spec-os.html#:~:text=fed%3ASecurityTokenSerivceEndpoint + /// This is the fed:PassiveRequestorEndpoint in WS-Federation, https://docs.oasis-open.org/wsfed/federation/v1.2/os/ws-federation-1.2-spec-os.html#:~:text=fed%3ASecurityTokenServiceType/fed%3APassiveRequestorEndpoint /// Or the token_endpoint in the OIDC metadata. /// public virtual string TokenEndpoint { get; set; } + /// + /// Gets or sets the token endpoint specified via the metadata endpoint. + /// This is the fed:SecurityTokenServiceType in WS-Federation, http://docs.oasis-open.org/wsfed/federation/v1.2/os/ws-federation-1.2-spec-os.html#:~:text=fed%3ASecurityTokenSerivceEndpoint + /// + public virtual string ActiveTokenEndpoint { get; set; } + /// /// Gets the that the IdentityProvider indicates are to be used in order to decrypt tokens. /// diff --git a/test/Microsoft.IdentityModel.Protocols.OpenIdConnect.Tests/OpenIdConnectConfigurationTests.cs b/test/Microsoft.IdentityModel.Protocols.OpenIdConnect.Tests/OpenIdConnectConfigurationTests.cs index 98cdd75625..04e6b2072e 100644 --- a/test/Microsoft.IdentityModel.Protocols.OpenIdConnect.Tests/OpenIdConnectConfigurationTests.cs +++ b/test/Microsoft.IdentityModel.Protocols.OpenIdConnect.Tests/OpenIdConnectConfigurationTests.cs @@ -108,8 +108,8 @@ public void GetSets() OpenIdConnectConfiguration configuration = new OpenIdConnectConfiguration(); Type type = typeof(OpenIdConnectConfiguration); PropertyInfo[] properties = type.GetProperties(); - if (properties.Length != 48) - Assert.True(false, "Number of properties has changed from 47 to: " + properties.Length + ", adjust tests"); + if (properties.Length != 49) + Assert.True(false, "Number of properties has changed from 49 to: " + properties.Length + ", adjust tests"); TestUtilities.CallAllPublicInstanceAndStaticPropertyGets(configuration, "OpenIdConnectConfiguration_GetSets"); diff --git a/test/Microsoft.IdentityModel.Protocols.WsFederation.Tests/WsFederationConfigurationRetrieverTests.cs b/test/Microsoft.IdentityModel.Protocols.WsFederation.Tests/WsFederationConfigurationRetrieverTests.cs index 6caaeed3e6..f32304f90a 100644 --- a/test/Microsoft.IdentityModel.Protocols.WsFederation.Tests/WsFederationConfigurationRetrieverTests.cs +++ b/test/Microsoft.IdentityModel.Protocols.WsFederation.Tests/WsFederationConfigurationRetrieverTests.cs @@ -23,11 +23,10 @@ public class WsFederationConfigurationRetrieverTests public void ReadMetadata(WsFederationMetadataTheoryData theoryData) { var context = TestUtilities.WriteHeader($"{this}.ReadMetadata", theoryData); + var configuration = new WsFederationConfiguration(); + try { - var config = ReferenceMetadata.AADCommonEndpoint; - var configuration = new WsFederationConfiguration(); - if (!string.IsNullOrEmpty(theoryData.Metadata)) { var reader = XmlReader.Create(new StringReader(theoryData.Metadata)); @@ -38,9 +37,9 @@ public void ReadMetadata(WsFederationMetadataTheoryData theoryData) var reader = XmlReader.Create(theoryData.MetadataPath); configuration = theoryData.Serializer.ReadMetadata(reader); } - - if (theoryData.SigingKey != null) - configuration.Signature.Verify(theoryData.SigingKey, theoryData.SigingKey.CryptoProviderFactory); + + if (theoryData.SigningKey != null) + configuration.Signature.Verify(theoryData.SigningKey, theoryData.SigningKey.CryptoProviderFactory); theoryData.ExpectedException.ProcessNoException(context); IdentityComparer.AreWsFederationConfigurationsEqual(configuration, theoryData.Configuration, context); @@ -64,38 +63,79 @@ public static TheoryData ReadMetadataTheoryData { new WsFederationMetadataTheoryData { + // Base case for common scenario (not tenant specific). + // All data is present as expected. Configuration = ReferenceMetadata.AADCommonEndpoint, First = true, Metadata = ReferenceMetadata.AADCommonMetadata, - SigingKey = ReferenceMetadata.MetadataSigningKey, + SigningKey = ReferenceMetadata.MetadataSigningKey, TestId = nameof(ReferenceMetadata.AADCommonMetadata) }, new WsFederationMetadataTheoryData { + // Only EntityDescriptor tag, empty XML. Configuration = ReferenceMetadata.EmptyEntityDescriptor, Metadata = ReferenceMetadata.MetadataEmptyEntityDescriptor, TestId = nameof(ReferenceMetadata.MetadataEmptyEntityDescriptor) }, + // --------------------------------------------------------------------------------------------------------------------- + // Passive Requestor variations (EntityDescriptor\RoleDescriptor\PassiveRequestorEndpoint) + // --------------------------------------------------------------------------------------------------------------------- new WsFederationMetadataTheoryData { + // Empty EntityDescriptor\RoleDescriptor\PassiveRequestorEndpoint tag + // Error Message: "IDX22812: Element: '{0}' was an empty element. 'TokenEndpoint' value is missing in wsfederationconfiguration."; ExpectedException = new ExpectedException(typeof(XmlReadException), "IDX22812:"), Metadata = ReferenceMetadata.MetadataEmptyPassiveRequestorEndpoint, TestId = nameof(ReferenceMetadata.MetadataEmptyPassiveRequestorEndpoint) }, new WsFederationMetadataTheoryData { + // Empty EntityDescriptor\RoleDescriptor\PassiveRequestorEndpoint\EndpointReference\Address tag + // Error Message: "IDX22803: Token reference address is missing in PassiveRequestorEndpoint in metadata file." ExpectedException = new ExpectedException(typeof(XmlReadException), "IDX22803:"), - Metadata = ReferenceMetadata.MetadataEmptyEndpointAddress, - TestId = nameof(ReferenceMetadata.MetadataEmptyEndpointAddress) + Metadata = ReferenceMetadata.MetadataEmptyPassiveRequestorEndpointAddress, + TestId = nameof(ReferenceMetadata.MetadataEmptyPassiveRequestorEndpointAddress) + }, + new WsFederationMetadataTheoryData + { + // Empty EntityDescriptor\RoleDescriptor\PassiveRequestorEndpoint\EndpointReference + // Error Message: Element: '{0}' was an empty element. 'TokenEndpoint' value is missing in wsfederationconfiguration."; + ExpectedException = new ExpectedException(typeof(XmlReadException), "IDX22812:"), + Metadata = ReferenceMetadata.MetadataEmptyPassiveRequestorEndpointReference, + TestId = nameof(ReferenceMetadata.MetadataEmptyPassiveRequestorEndpointReference) + }, + // --------------------------------------------------------------------------------------------------------------------- + // SecurityTokenServiceEndpoint variations (EntityDescriptor\RoleDescriptor\SecurityTokenServiceEndpoint) + // --------------------------------------------------------------------------------------------------------------------- + new WsFederationMetadataTheoryData + { + // Empty EntityDescriptor\RoleDescriptor\SecurityTokenServiceEndpoint tag + // Error Message: "IDX22812: Element: '{0}' was an empty element. 'TokenEndpoint' value is missing in wsfederationconfiguration."; + ExpectedException = new ExpectedException(typeof(XmlReadException), "IDX22812:"), + Metadata = ReferenceMetadata.MetadataEmptySecurityTokenServiceEndpoint, + TestId = nameof(ReferenceMetadata.MetadataEmptySecurityTokenServiceEndpoint) + }, + new WsFederationMetadataTheoryData + { + // Empty EntityDescriptor\RoleDescriptor\SecurityTokenServiceEndpoint\EndpointReference\Address tag + // Error Message: "IDX22814: Token reference address is missing in SecurityTokenServiceEndpoint in metadata file." + ExpectedException = new ExpectedException(typeof(XmlReadException), "IDX22814:"), + Metadata = ReferenceMetadata.MetadataEmptySecurityTokenServiceEndpointAddress, + TestId = nameof(ReferenceMetadata.MetadataEmptySecurityTokenServiceEndpointAddress) }, new WsFederationMetadataTheoryData { + // Empty EntityDescriptor\RoleDescriptor\SecurityTokenServiceEndpoint\EndpointReference + // Error Message: "IDX22812: Element: '{0}' was an empty element. 'TokenEndpoint' value is missing in wsfederationconfiguration."; ExpectedException = new ExpectedException(typeof(XmlReadException), "IDX22812:"), - Metadata = ReferenceMetadata.MetadataEmptyEndpointReference, - TestId = nameof(ReferenceMetadata.MetadataEmptyEndpointReference) + Metadata = ReferenceMetadata.MetadataEmptySecurityTokenServiceEndpointReference, + TestId = nameof(ReferenceMetadata.MetadataEmptySecurityTokenServiceEndpointReference) }, new WsFederationMetadataTheoryData { + // Base case for tenant specific scenario (tenant 268da1a1-9db4-48b9-b1fe-683250ba90cc). + // All data is present as expected. Configuration = ReferenceMetadata.AADCommonFormated, Metadata = ReferenceMetadata.AADCommonMetadataFormated, TestId = nameof(ReferenceMetadata.AADCommonMetadataFormated) @@ -105,17 +145,19 @@ public static TheoryData ReadMetadataTheoryData ExpectedException = new ExpectedException(typeof(XmlValidationException), "IDX30200:"), Configuration = ReferenceMetadata.AADCommonFormated, Metadata = ReferenceMetadata.AADCommonMetadataFormated, - SigingKey = ReferenceMetadata.MetadataSigningKey, + SigningKey = ReferenceMetadata.MetadataSigningKey, TestId = nameof(ReferenceMetadata.AADCommonMetadataFormated) + " Signature Failure" }, new WsFederationMetadataTheoryData { + // Validate that the presence of spaces or new lines does not affect the parsing of the XML content. Configuration = ReferenceMetadata.AADCommonFormated, Metadata = ReferenceMetadata.MetadataWithBlanks, TestId = nameof(ReferenceMetadata.MetadataWithBlanks) }, new WsFederationMetadataTheoryData { + // EntityDescriptor\RoleDescriptor\KeyDescriptor is missing. Validate the resulting Configuration object only includes the data present. Configuration = new WsFederationConfiguration { Issuer = ReferenceMetadata.Issuer, @@ -126,54 +168,80 @@ public static TheoryData ReadMetadataTheoryData }, new WsFederationMetadataTheoryData { + // EntityDescriptor\@entityID attribute (issuer) is missing from EntityDescriptor tag. + // Error Message: IDX22801: entityID attribute is not found in EntityDescriptor element in metadata file. ExpectedException = new ExpectedException(typeof(XmlReadException), "IDX22801:"), Metadata = ReferenceMetadata.MetadataNoIssuer, TestId = nameof(ReferenceMetadata.MetadataNoIssuer) }, new WsFederationMetadataTheoryData { + // EntityDescriptor\RoleDescriptor\PassiveRequestorEndpoint\EndpointReference\Address Empty Address value (white space and new line only) + // Error Message: "IDX22803: Token reference address is missing in PassiveRequestorEndpoint in metadata file."; ExpectedException = new ExpectedException(typeof(XmlReadException), "IDX22803:"), - Metadata = ReferenceMetadata.MetadataNoTokenUri, - TestId = nameof(ReferenceMetadata.MetadataNoTokenUri) + Metadata = ReferenceMetadata.MetadataNoPassiveRequestorEndpointUri, + TestId = nameof(ReferenceMetadata.MetadataNoPassiveRequestorEndpointUri) }, new WsFederationMetadataTheoryData { + // EntityDescriptor\RoleDescriptor\SecurityTokenServiceEndpoint\EndpointReference\Address Empty Address value (white space and new line only) + // Error Message: "IDX22814: Token reference address is missing in SecurityTokenServiceEndpoint in metadata."; + ExpectedException = new ExpectedException(typeof(XmlReadException), "IDX22814:"), + Metadata = ReferenceMetadata.MetadataNoSecurityTokenServiceEndpointUri, + TestId = nameof(ReferenceMetadata.MetadataNoSecurityTokenServiceEndpointUri) + }, + new WsFederationMetadataTheoryData + { + // KeyDescriptor\KeyInfo\X509Data tag holds invalid certificate data. + // Error Message: "IDX22800: Exception thrown while reading WsFedereationMetadata. Element '{0}'. Caught exception: '{1}'."; ExpectedException = new ExpectedException(typeof(XmlReadException), "IDX22800:", typeof(FormatException)), Metadata = ReferenceMetadata.MetadataMalformedCertificate, TestId = nameof(ReferenceMetadata.MetadataMalformedCertificate) }, + // --------------------------------------------------------------------------------------------------------------------- + // XML Signature validation (EntityDescriptor\Signature) + // --------------------------------------------------------------------------------------------------------------------- new WsFederationMetadataTheoryData { + // Invalid XML signature. Unknown element before + // Error Message: "IDX30025: Unable to read XML. Expecting XmlReader to be at EndElement: '{0}'. Found XmlNode 'type.name': '{1}.{2}'."; ExpectedException = new ExpectedException(typeof(XmlReadException), "IDX30025:"), Metadata = ReferenceMetadata.MetadataUnknownElementBeforeSignatureEndElement, TestId = nameof(ReferenceMetadata.MetadataUnknownElementBeforeSignatureEndElement) }, new WsFederationMetadataTheoryData { + // Invalid XML signature. SignedInfo tag is missing. + // Error Message: "IDX30011: Unable to read XML. Expecting XmlReader to be at ns.element: '{0}.{1}', found: '{2}.{3}'." ExpectedException = new ExpectedException(typeof(XmlReadException), "IDX30011:"), Metadata = ReferenceMetadata.MetadataNoSignedInfoInSignature, TestId = nameof(ReferenceMetadata.MetadataNoSignedInfoInSignature) }, new WsFederationMetadataTheoryData { + // EntityDescriptor tag missing. + // Error Message: "IDX30011: Unable to read XML. Expecting XmlReader to be at ns.element: '{0}.{1}', found: '{2}.{3}'." ExpectedException = new ExpectedException(typeof(XmlReadException), "IDX30011:"), Metadata = ReferenceMetadata.MetadataNoEntityDescriptor, TestId = nameof(ReferenceMetadata.MetadataNoEntityDescriptor) }, new WsFederationMetadataTheoryData { + // EntityDescriptor\RoleDescriptor tag missing. Configuration = ReferenceMetadata.NoRoleDescriptor, Metadata = ReferenceMetadata.MetadataNoRoleDescriptor, TestId = nameof(ReferenceMetadata.MetadataNoRoleDescriptor) }, new WsFederationMetadataTheoryData { + // EntityDescriptor\RoleDescriptor\KeyDescriptor\KeyInfo tag missing. ExpectedException = new ExpectedException(typeof(XmlReadException), "IDX22802:"), Metadata = ReferenceMetadata.MetadataNoKeyInfoInKeyDescriptor, TestId = nameof(ReferenceMetadata.MetadataNoKeyInfoInKeyDescriptor) }, new WsFederationMetadataTheoryData { + // EntityDescriptor\RoleDescriptor\PassiveRequestorEndpoint tag is missing. Configuration = new WsFederationConfiguration { Issuer = ReferenceMetadata.Issuer @@ -183,55 +251,74 @@ public static TheoryData ReadMetadataTheoryData }, new WsFederationMetadataTheoryData { + // EntityDescriptor\RoleDescriptor\PassiveRequestorEndpoint\EndpointReference tag is missing. + // Error Message: "IDX30011: Unable to read XML. Expecting XmlReader to be at ns.element: '{0}.{1}', found: '{2}.{3}'." ExpectedException = new ExpectedException(typeof(XmlReadException), "IDX30011:"), Metadata = ReferenceMetadata.MetadataNoEndpointReference, TestId = nameof(ReferenceMetadata.MetadataNoEndpointReference) }, new WsFederationMetadataTheoryData { + // EntityDescriptor\RoleDescriptor\PassiveRequestorEndpoint\EndpointReference\Address tag is missing. + // Error Message: "IDX30011: Unable to read XML. Expecting XmlReader to be at ns.element: '{0}.{1}', found: '{2}.{3}'." ExpectedException = new ExpectedException(typeof(XmlReadException), "IDX30011:"), Metadata = ReferenceMetadata.MetadataNoAddressInEndpointReference, TestId = nameof(ReferenceMetadata.MetadataNoAddressInEndpointReference) }, + // --------------------------------------------------------------------------------------------------------------------- + // Active Directory Federation Services + // --------------------------------------------------------------------------------------------------------------------- new WsFederationMetadataTheoryData { + // Base case for Active Directory Federation Services V2. + // All data present and valid. Metadata = ReferenceMetadata.AdfsV2Metadata, - SigingKey = ReferenceMetadata.AdfsV2MetadataSigningKey, + SigningKey = ReferenceMetadata.AdfsV2MetadataSigningKey, Configuration = ReferenceMetadata.AdfsV2Endpoint, TestId = nameof(ReferenceMetadata.AdfsV2Metadata) }, new WsFederationMetadataTheoryData { + // Base case for Active Directory Federation Services V3. + // All data present and valid. Metadata = ReferenceMetadata.AdfsV3Metadata, - SigingKey = ReferenceMetadata.AdfsV3MetadataSigningKey, + SigningKey = ReferenceMetadata.AdfsV3MetadataSigningKey, Configuration = ReferenceMetadata.AdfsV3Endpoint, TestId = nameof(ReferenceMetadata.AdfsV3Metadata) }, new WsFederationMetadataTheoryData { + // Base case for Active Directory Federation Services V4. + // All data present and valid. Metadata = ReferenceMetadata.AdfsV4Metadata, - SigingKey = ReferenceMetadata.AdfsV4MetadataSigningKey, + SigningKey = ReferenceMetadata.AdfsV4MetadataSigningKey, Configuration = ReferenceMetadata.AdfsV4Endpoint, TestId = nameof(ReferenceMetadata.AdfsV4Metadata) }, new WsFederationMetadataTheoryData { + // Base case for Active Directory Federation Services V2. + // All data present and valid. MetadataPath = Path.Combine(Directory.GetCurrentDirectory(), "../../../adfs-v2-metadata.xml"), - SigingKey = ReferenceMetadata.AdfsV2MetadataSigningKey, + SigningKey = ReferenceMetadata.AdfsV2MetadataSigningKey, Configuration = ReferenceMetadata.AdfsV2Endpoint, TestId = "AdfsV2Metadata from xml file" }, new WsFederationMetadataTheoryData { + // Base case for Active Directory Federation Services V3. + // All data present and valid. MetadataPath = Path.Combine(Directory.GetCurrentDirectory(), "../../../adfs-v3-metadata.xml"), - SigingKey = ReferenceMetadata.AdfsV3MetadataSigningKey, + SigningKey = ReferenceMetadata.AdfsV3MetadataSigningKey, Configuration = ReferenceMetadata.AdfsV3Endpoint, TestId = "AdfsV3Metadata from xml file" }, new WsFederationMetadataTheoryData { + // Base case for Active Directory Federation Services V4. + // All data present and valid. MetadataPath = Path.Combine(Directory.GetCurrentDirectory(), "../../../adfs-v4-metadata.xml"), - SigingKey = ReferenceMetadata.AdfsV4MetadataSigningKey, + SigningKey = ReferenceMetadata.AdfsV4MetadataSigningKey, Configuration = ReferenceMetadata.AdfsV4Endpoint, TestId = "AdfsV4Metadata from xml file" } @@ -492,6 +579,8 @@ public void WriteMetadata(WsFederationMetadataTheoryData theoryData) // remove the signature and do the comparison configuration.Signature = null; + theoryData.Configuration.Signature = null; + theoryData.ExpectedException.ProcessNoException(context); IdentityComparer.AreWsFederationConfigurationsEqual(configuration, theoryData.Configuration, context); } @@ -540,7 +629,19 @@ public static TheoryData WriteMetadataTheoryData ExpectedException = new ExpectedException(typeof(XmlWriteException), "IDX22811:"), Configuration = ReferenceMetadata.AADCommonFormatedNoTokenEndpoint, TestId = nameof(ReferenceMetadata.AADCommonFormatedNoTokenEndpoint) - } + }, + new WsFederationMetadataTheoryData + { + // The active token endpoint is optional at this point and should not throw an exception if missing. + Configuration = ReferenceMetadata.AADCommonFormatedNoActiveTokenEndpoint, + TestId = nameof(ReferenceMetadata.AADCommonFormatedNoActiveTokenEndpoint) + }, + new WsFederationMetadataTheoryData + { + // All data is present including signature and SigningCredentials (required for signature validation) + Configuration = ReferenceMetadata.AADCommonFormated, + TestId = nameof(ReferenceMetadata.AADCommonFormated) + }, }; } } @@ -555,7 +656,7 @@ public class WsFederationMetadataTheoryData : TheoryDataBase public WsFederationMetadataSerializer Serializer { get; set; } = new WsFederationMetadataSerializer(); - public SecurityKey SigingKey { get; set; } + public SecurityKey SigningKey { get; set; } public override string ToString() { diff --git a/test/Microsoft.IdentityModel.Protocols.WsFederation.Tests/WsFederationConfigurationValidatorTests.cs b/test/Microsoft.IdentityModel.Protocols.WsFederation.Tests/WsFederationConfigurationValidatorTests.cs new file mode 100644 index 0000000000..f3610e8ca9 --- /dev/null +++ b/test/Microsoft.IdentityModel.Protocols.WsFederation.Tests/WsFederationConfigurationValidatorTests.cs @@ -0,0 +1,269 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.IO; +using System.Xml; +using Microsoft.IdentityModel.TestUtils; +using Xunit; + +#pragma warning disable CS3016 // Arrays as attribute arguments is not CLS-compliant + +namespace Microsoft.IdentityModel.Protocols.WsFederation.Tests +{ + /// + /// WS Federation Configuration Validator tests. + /// + public class WsFederationConfigurationValidatorTests + { + [Theory, MemberData(nameof(ValidateConfigurationTheoryData))] + public void ValidateConfiguration(WsFederationConfigurationTheoryData theoryData) + { + var context = TestUtilities.WriteHeader($"{this}.ValidateConfiguration", theoryData); + var validator = new WsFederationConfigurationValidator(); + var configToValidate = theoryData.Configuration; + + if (!string.IsNullOrEmpty(theoryData.Metadata)) + { + var reader = XmlReader.Create(new StringReader(theoryData.Metadata)); + configToValidate = new WsFederationMetadataSerializer().ReadMetadata(reader); + } + + try + { + var result = validator.Validate(configToValidate); + theoryData.ExpectedException.ProcessNoException(context); + IdentityComparer.AreConfigurationValidationResultEqual(result, theoryData.ExpectedResult, context); + } + catch (Exception ex) + { + theoryData.ExpectedException.ProcessException(ex, context); + } + + TestUtilities.AssertFailIfErrors(context); + } + + public static TheoryData ValidateConfigurationTheoryData + { + get + { + return new TheoryData + { + new WsFederationConfigurationTheoryData + { + // Base case for common scenario. All data is present as expected. + Metadata = ReferenceMetadata.AADCommonMetadata, + ExpectedResult = new ConfigurationValidationResult + { + Succeeded = true + }, + TestId = nameof(ReferenceMetadata.AADCommonMetadata) + }, + new WsFederationConfigurationTheoryData + { + // Base case for Active Directory Federation Services V4. All data is present as expected. + Metadata = ReferenceMetadata.AdfsV4Metadata, + ExpectedResult = new ConfigurationValidationResult + { + Succeeded = true + }, + TestId = nameof(ReferenceMetadata.AdfsV4Metadata) + }, + new WsFederationConfigurationTheoryData + { + Configuration =null, + ExpectedException = new ExpectedException(typeof(ArgumentNullException), "IDX10000:"), + TestId = "NullConfiguration" + }, + new WsFederationConfigurationTheoryData + { + Configuration = ((Func)(() =>{ + var config = ReferenceMetadata.AdfsV4Endpoint; + config.Issuer = null; + return config; + }))(), + ExpectedResult = new ConfigurationValidationResult + { + Succeeded = false, + ErrorMessage = LogMessages.IDX22700 + }, + TestId = "NullIssuer" + }, + new WsFederationConfigurationTheoryData + { + Configuration = ((Func)(() =>{ + var config = ReferenceMetadata.AdfsV4Endpoint; + config.Signature.KeyInfo = null; + return config; + }))(), + ExpectedResult = new ConfigurationValidationResult + { + Succeeded = false, + ErrorMessage = LogMessages.IDX22702 + }, + TestId = "NullSignatureKeyInfo" + }, + new WsFederationConfigurationTheoryData + { + Configuration = ((Func)(() =>{ + var config = ReferenceMetadata.AdfsV4Endpoint; + config.Signature.SignatureValue = " "; + return config; + }))(), + ExpectedResult = new ConfigurationValidationResult + { + Succeeded = false, + ErrorMessage = LogMessages.IDX22703 + }, + TestId = "EmptySignatureValue" + }, + new WsFederationConfigurationTheoryData + { + Configuration = ((Func)(() =>{ + var config = ReferenceMetadata.AdfsV4Endpoint; + config.Signature.SignedInfo.SignatureMethod = " "; + return config; + }))(), + ExpectedResult = new ConfigurationValidationResult + { + Succeeded = false, + ErrorMessage = LogMessages.IDX22705 + }, + TestId = "EmptySignatureMethod" + }, + new WsFederationConfigurationTheoryData + { + Configuration = ((Func)(() =>{ + var config = ReferenceMetadata.AdfsV4Endpoint; + config.Signature.SignedInfo.References.Clear(); + return config; + }))(), + ExpectedResult = new ConfigurationValidationResult + { + Succeeded = false, + ErrorMessage = LogMessages.IDX22706 + }, + TestId = "NoSignatureReferences" + }, + new WsFederationConfigurationTheoryData + { + Configuration = ((Func)(() =>{ + var config = ReferenceMetadata.AdfsV4Endpoint; + config.ActiveTokenEndpoint = string.Empty; + return config; + }))(), + ExpectedResult = new ConfigurationValidationResult + { + Succeeded = false, + ErrorMessage = LogMessages.IDX22707 + }, + TestId = "EmptyActiveTokenEndpoint" + }, + new WsFederationConfigurationTheoryData + { + Configuration = ((Func)(() =>{ + var config = ReferenceMetadata.AdfsV4Endpoint; + config.ActiveTokenEndpoint = "SomeRandomValue@here"; + return config; + }))(), + ExpectedResult = new ConfigurationValidationResult + { + Succeeded = false, + ErrorMessage = LogMessages.IDX22708 + }, + TestId = "InvalidActiveTokenEndpointUri" + }, + new WsFederationConfigurationTheoryData + { + Configuration = ((Func)(() =>{ + var config = ReferenceMetadata.AdfsV4Endpoint; + config.TokenEndpoint = string.Empty; + return config; + }))(), + ExpectedResult = new ConfigurationValidationResult + { + Succeeded = false, + ErrorMessage = LogMessages.IDX22709 + }, + TestId = "EmptyTokenEndpoint" + }, + new WsFederationConfigurationTheoryData + { + Configuration = ((Func)(() =>{ + var config = ReferenceMetadata.AdfsV4Endpoint; + config.TokenEndpoint = "SomeStringThatIsNotAUrl!"; + return config; + }))(), + ExpectedResult = new ConfigurationValidationResult + { + Succeeded = false, + ErrorMessage = LogMessages.IDX22710 + }, + TestId = "InvalidTokenEndpointUri" + }, + new WsFederationConfigurationTheoryData + { + Configuration = ((Func)(() =>{ + var config = ReferenceMetadata.AdfsV4Endpoint; + config.SigningKeys.Clear(); + return config; + }))(), + ExpectedResult = new ConfigurationValidationResult + { + Succeeded = false, + ErrorMessage = LogMessages.IDX22711 + }, + TestId = "NoSigningKeys" + }, + new WsFederationConfigurationTheoryData + { + Metadata = ReferenceMetadata.AdfsV4Metadata.Replace(@"
https://fs.msidlab11.com/adfs/ls/
", + @"
https://fs.malicious.com/adfs/ls/
"), + ExpectedResult = new ConfigurationValidationResult + { + Succeeded = false, + ErrorMessage = LogMessages.IDX22713 + }, + TestId = "TamperedMetadata-TokenEndpoints-PassiveRequestor" + }, + new WsFederationConfigurationTheoryData + { + Metadata = ReferenceMetadata.AADCommonMetadata.Replace(@"https://login.microsoftonline.com/common/wsfed", + @"https://login.malicious.com/common/wsfed"), + ExpectedResult = new ConfigurationValidationResult + { + Succeeded = false, + ErrorMessage = LogMessages.IDX22713 + }, + TestId = "TamperedMetadata-TokenEndpoints-ActiveRequestor" + }, + new WsFederationConfigurationTheoryData + { + Metadata = ReferenceMetadata.AADCommonMetadata.Replace(@"", + @"MIIHXTCCBUWgAwIBAgITMwBfyXeHIx8iTPP04wAAAF/JdzANBgkqhkiG9w0BAQwFADBZMQswCQYDVQQGEwJVUzEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSowKAYDVQQDEyFNaWNyb3NvZnQgQXp1cmUgVExTIElzc3VpbmcgQ0EgMDEwHhcNMjIwOTE0MjM1NjAzWhcNMjMwOTA5MjM1NjAzWjBxMQswCQYDVQQGEwJVUzELMAkGA1UECBMCV0ExEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEjMCEGA1UEAwwaKi5kc3RzLmNvcmUuYXp1cmUtdGVzdC5uZXQwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDiwD1xUOpyC71qUdtvVktWMtaaZi6rz88sMdR1+P6d0Jxaze+9IOVHLz5/I9Ge6oxBzndpz9VaM1P/M75B9Wp4v1KMnr+EmCVnkZOQseC50ZUvcYATAATnZ01AIdc3mQ0j9nL1WKl+mMFhmjsCjh2RhzJHvS3cMjl5lwrIyNwjIutLtEFYxbxVhcgjc++QmsZvMwE9qDInzD6Yl5cVHCl0Xm9/vkbjoSbjMXcp6OaWdRZfjqtC9oHF82ZqbQkVH7Hw+EER4rP+aEUam3OhtDGZ5Fs/UymnvoE9i+5wxTKjuKHJJKiggOl+ai8bQ7FkNO+LJgXO4V293SPCx8wv+/4JAgMBAAGjggMEMIIDADAOBgNVHQ8BAf8EBAMCBLAwHQYDVR0lBBYwFAYIKwYBBQUHAwIGCCsGAQUFBwMBMB0GA1UdDgQWBBR/FXyrNwBgMGDo3Pu93V9VyjmqdzCBmgYDVR0RBIGSMIGPghoqLmRzdHMuY29yZS5henVyZS10ZXN0Lm5ldIIbKi5kc3RzLmNvcmUud2luZG93cy1pbnQubmV0ghsqLmRzdHMuY29yZS53aW5kb3dzLXRzdC5uZXSCHSouZHN0cy5lMmV0ZXN0Mi5henVyZS1pbnQubmV0ghgqLmRzdHMuaW50LmF6dXJlLWludC5uZXQwHwYDVR0jBBgwFoAUDyBd16FXlduSzyvQx8J3BM5ygHYwZAYDVR0fBF0wWzBZoFegVYZTaHR0cDovL3d3dy5taWNyb3NvZnQuY29tL3BraW9wcy9jcmwvTWljcm9zb2Z0JTIwQXp1cmUlMjBUTFMlMjBJc3N1aW5nJTIwQ0ElMjAwMS5jcmwwga4GCCsGAQUFBwEBBIGhMIGeMG0GCCsGAQUFBzAChmFodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2NlcnRzL01pY3Jvc29mdCUyMEF6dXJlJTIwVExTJTIwSXNzdWluZyUyMENBJTIwMDElMjAtJTIweHNpZ24uY3J0MC0GCCsGAQUFBzABhiFodHRwOi8vb25lb2NzcC5taWNyb3NvZnQuY29tL29jc3AwDAYDVR0TAQH/BAIwADA8BgkrBgEEAYI3FQcELzAtBiUrBgEEAYI3FQiHvdcbgefrRoKBnS6O0AyH8NodXYKE5WmC86c+AgFkAgElMCcGCSsGAQQBgjcVCgQaMBgwCgYIKwYBBQUHAwIwCgYIKwYBBQUHAwEwZgYDVR0gBF8wXTBRBgwrBgEEAYI3TIN9AQEwQTA/BggrBgEFBQcCARYzaHR0cDovL3d3dy5taWNyb3NvZnQuY29tL3BraW9wcy9Eb2NzL1JlcG9zaXRvcnkuaHRtMAgGBmeBDAECAjANBgkqhkiG9w0BAQwFAAOCAgEADdXQBQATdRGyTPLNbslNAWHETaCZhmXkEwHEtG/Srt4TXqP92wojLaPwPlKuyqHtibKqGOE22Hww2JBfwIe+aJtplT5QLH/r05yDYXj6kioZ1BUgXmhZWSTzyaqT1u8nUcZkAGDii8HeSSlvKVUIqbQpUT+mUg6ijmdsp07ZsEDiH7tAc0U+M1oIydjIIwIiTOSuVsoM4Fi+yQ6E7xPSMXdtFlUwUINgnrcFGgQ6L7uY2DsgVCKgw3pzTWa3ulg4sypCelJ1i9ngxn0aIDPBkxWXcauIV/QYHeIp65Zv8JqN1mNACZj2/2a5JkK6AO6zD8fPvTwN+pMUEw3/ha+pQzLWFsx00Y+hC5wMWKpU4AjYVmmTJ6zyovb1eaZG30KdQP3ucdVIJQnzJZ1E8opYgIkvvndb7VbRFDyonsNcOZ9s4VYK/HZvDM4BtULoU1q5/BVPXodJ9dn/A8GHBXS2S6uolxolFtrQz0WTtADWWGr28wlNj5vWBhoNYvWVXc8SWcB2W4caFRSavoZ+2fHwySGRJGJrLhXb3kyMhdS/VVrIsOnuUXUhQA6q6Q/laie6kmMEKDfW8S9XcgUDWe0ay79qww12VZzBZmGoFPGOwpXkeov2NL5FZ48daoK9j826iJn/9kFfvgDBSGBrS8GWof6f90n9Ngt327l1M+RbLOc="), + ExpectedResult = new ConfigurationValidationResult + { + Succeeded = false, + ErrorMessage = LogMessages.IDX22713 + }, + TestId = "TamperedMetadata-ExtraMaliciousKey" + } + }; + } + } + + public class WsFederationConfigurationTheoryData : TheoryDataBase + { + public WsFederationConfiguration Configuration { get; set; } + + public string Metadata { get; set; } + + public ConfigurationValidationResult ExpectedResult { get; set; } + + public override string ToString() + { + return $"TestId: {TestId}, {ExpectedException}"; + } + } + } +} diff --git a/test/Microsoft.IdentityModel.TestUtils/IdentityComparer.cs b/test/Microsoft.IdentityModel.TestUtils/IdentityComparer.cs index 6533b83b55..d7c2777a79 100644 --- a/test/Microsoft.IdentityModel.TestUtils/IdentityComparer.cs +++ b/test/Microsoft.IdentityModel.TestUtils/IdentityComparer.cs @@ -997,6 +997,17 @@ public static bool AreX509Certificate2Equal(object object1, object object2, Comp return context.Merge(localContext); } +#if !CrossVersionTokenValidation + public static bool AreConfigurationValidationResultEqual(ConfigurationValidationResult result1, ConfigurationValidationResult result2, CompareContext context) + { + var localContext = new CompareContext(context); + if (ContinueCheckingEquality(result1, result2, localContext)) + CompareAllPublicProperties(result1, result2, localContext); + + return context.Merge(localContext); + } +#endif + public static string BuildStringDiff(string label, object str1, object str2) { return (label ?? "label") + ": '" + GetString(str1) + "', '" + GetString(str2) + "'"; diff --git a/test/Microsoft.IdentityModel.TestUtils/ReferenceMetadata.cs b/test/Microsoft.IdentityModel.TestUtils/ReferenceMetadata.cs index 0144d89643..ecb68b40dd 100644 --- a/test/Microsoft.IdentityModel.TestUtils/ReferenceMetadata.cs +++ b/test/Microsoft.IdentityModel.TestUtils/ReferenceMetadata.cs @@ -56,7 +56,6 @@ public static X509Certificate2 X509Certificate3 { get => new X509Certificate2(Convert.FromBase64String(X509CertificateData3)); } - public static X509Certificate2 X509CertificateAdfsV2 { get => new X509Certificate2(Convert.FromBase64String(X509CertificateDataAdfsV2)); @@ -141,7 +140,8 @@ public static WsFederationConfiguration AADCommonEndpoint SignatureValue = AADCommonMetadataSignatureValue, SignedInfo = AADCommonSignedInfo }, - TokenEndpoint = TokenEndpointForCommon + TokenEndpoint = TokenEndpointForCommon, + ActiveTokenEndpoint = ActiveTokenEndpointForCommon }; configuration.KeyInfos.Add(keyInfo1); @@ -181,7 +181,8 @@ public static WsFederationConfiguration AADCommonFormated KeyInfo = keyInfo1, SignatureValue = AADCommonMetadataSignatureValue, }, - TokenEndpoint = "https://login.microsoftonline.com/268da1a1-9db4-48b9-b1fe-683250ba90cc/wsfed" + TokenEndpoint = "https://login.microsoftonline.com/268da1a1-9db4-48b9-b1fe-683250ba90cc/wsfed", + ActiveTokenEndpoint = "https://login.microsoftonline.com/268da1a1-9db4-48b9-b1fe-683250ba90cc/wsfed" }; configuration.KeyInfos.Add(keyInfo1); @@ -227,6 +228,16 @@ public static WsFederationConfiguration AADCommonFormatedNoTokenEndpoint } } + public static WsFederationConfiguration AADCommonFormatedNoActiveTokenEndpoint + { + get + { + var configuration = AADCommonFormated; + configuration.ActiveTokenEndpoint = null; + return configuration; + } + } + public static WsFederationConfiguration AdfsV2Endpoint { get @@ -243,7 +254,8 @@ public static WsFederationConfiguration AdfsV2Endpoint KeyInfo = keyInfo, SignatureValue = AdfsV2SignatureValue, }, - TokenEndpoint = "https://fs.msidlab7.com/adfs/ls/" + TokenEndpoint = "https://fs.msidlab7.com/adfs/ls/", + ActiveTokenEndpoint = "https://fs.msidlab7.com/adfs/services/trust/2005/certificatemixed" }; configuration.KeyInfos.Add(keyInfo); @@ -271,7 +283,8 @@ public static WsFederationConfiguration AdfsV3Endpoint Prefix = "ds", SignatureValue = AdfsV3SignatureValue, }, - TokenEndpoint = "https://fs.msidlab2.com/adfs/ls/" + TokenEndpoint = "https://fs.msidlab2.com/adfs/ls/", + ActiveTokenEndpoint = "https://fs.msidlab2.com/adfs/services/trust/2005/certificatemixed" }; configuration.KeyInfos.Add(keyInfo); @@ -299,7 +312,8 @@ public static WsFederationConfiguration AdfsV4Endpoint Prefix = "ds", SignatureValue = AdfsV4SignatureValue, }, - TokenEndpoint = "https://fs.msidlab11.com/adfs/ls/" + TokenEndpoint = "https://fs.msidlab11.com/adfs/ls/", + ActiveTokenEndpoint = "https://fs.msidlab11.com/adfs/services/trust/2005/certificatemixed" }; configuration.KeyInfos.Add(keyInfo); @@ -438,6 +452,8 @@ public static SignedInfo AdfsV4SignedInfo public static string TokenEndpointForCommon { get => @"https://login.microsoftonline.com/common/wsfed"; } + public static string ActiveTokenEndpointForCommon { get => @"https://login.microsoftonline.com/common/wsfed"; } + public static string KeyDescriptorNoKeyUse { get @@ -731,7 +747,20 @@ public static string MetadataEmptyPassiveRequestorEndpoint } } - public static string MetadataEmptyEndpointReference + public static string MetadataEmptySecurityTokenServiceEndpoint + { + get + { + return + @" + + + + "; + } + } + + public static string MetadataEmptyPassiveRequestorEndpointReference { get { @@ -746,7 +775,22 @@ public static string MetadataEmptyEndpointReference } } - public static string MetadataEmptyEndpointAddress + public static string MetadataEmptySecurityTokenServiceEndpointReference + { + get + { + return + @" + + + + + + "; + } + } + + public static string MetadataEmptyPassiveRequestorEndpointAddress { get { @@ -763,6 +807,23 @@ public static string MetadataEmptyEndpointAddress } } + public static string MetadataEmptySecurityTokenServiceEndpointAddress + { + get + { + return + @" + + + + + + + + "; + } + } + public static string MetadataMalformedCertificate { get @@ -1043,7 +1104,7 @@ public static string MetadataNoSignedInfoInSignature } } - public static string MetadataNoTokenUri + public static string MetadataNoPassiveRequestorEndpointUri { get { @@ -1086,15 +1147,58 @@ public static string MetadataNoTokenUri } } - public static string MetadataWithBlanks + public static string MetadataNoSecurityTokenServiceEndpointUri { get { return @" - + + + + + + + + + i6nrvd1p0+HbCCrFBN5z3jrCe/56R3DlWYQanX6cygM= + + + + gdmviHtNhy8FQ6gSbyovhzMBxioMs6hoHYYzoyjS4DxHqhLgaPrRe948NKfXRYe4o1syVp+cZaGTcRzlPmCFOxH1zjY9qPUT2tCsJ1aCUCoiepu0uYGkWKV9CifHt7+aixQEufxM06iwZcMdfXPF3lqqdOoC7pRTcPlBJo6m6odXmjIcHPpsBGtkJuS7W6JULFhzBC9ytS0asrVaEZhVijP95QM0SZRL/pnJp1gOtKYKsQV246lV8tHFfFIddtklVYTvhlagjVUHsUtUhfwrt/5i/Rnr40qMNx/H10ZClTAQXthQH3GnzObAmhfoMNS1hAMpnX4BEhBOAqHHv2jyPA== + + + + + MIIDBTCCAe2gAwIBAgIQY4RNIR0dX6dBZggnkhCRoDANBgkqhkiG9w0BAQsFADAtMSswKQYDVQQDEyJhY2NvdW50cy5hY2Nlc3Njb250cm9sLndpbmRvd3MubmV0MB4XDTE3MDIxMzAwMDAwMFoXDTE5MDIxNDAwMDAwMFowLTErMCkGA1UEAxMiYWNjb3VudHMuYWNjZXNzY29udHJvbC53aW5kb3dzLm5ldDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMBEizU1OJms31S/ry7iav/IICYVtQ2MRPhHhYknHImtU03sgVk1Xxub4GD7R15i9UWIGbzYSGKaUtGU9lP55wrfLpDjQjEgaXi4fE6mcZBwa9qc22is23B6R67KMcVyxyDWei+IP3sKmCcMX7Ibsg+ubZUpvKGxXZ27YgqFTPqCT2znD7K81YKfy+SVg3uW6epW114yZzClTQlarptYuE2mujxjZtx7ZUlwc9AhVi8CeiLwGO1wzTmpd/uctpner6oc335rvdJikNmc1cFKCK+2irew1bgUJHuN+LJA0y5iVXKvojiKZ2Ii7QKXn19Ssg1FoJ3x2NWA06wc0CnruLsCAwEAAaMhMB8wHQYDVR0OBBYEFDAr/HCMaGqmcDJa5oualVdWAEBEMA0GCSqGSIb3DQEBCwUAA4IBAQAiUke5mA86R/X4visjceUlv5jVzCn/SIq6Gm9/wCqtSxYvifRXxwNpQTOyvHhrY/IJLRUp2g9/fDELYd65t9Dp+N8SznhfB6/Cl7P7FRo99rIlj/q7JXa8UB/vLJPDlr+NREvAkMwUs1sDhL3kSuNBoxrbLC5Jo4es+juQLXd9HcRraE4U3UZVhUS2xqjFOfaGsCbJEqqkjihssruofaxdKT1CPzPMANfREFJznNzkpJt4H0aMDgVzq69NxZ7t1JiIuc43xRjeiixQMRGMi1mAB75fTyfFJ/rWQ5J/9kh0HMZVtHsqICBF1tHMTMIK5rwoweY0cuCIpN7A/zMOQtoD + + + + + + + + + + + + + + "; + } + } + + public static string MetadataWithBlanks + { + get + { + return + @" + + + @@ -1108,17 +1212,17 @@ public static string MetadataWithBlanks 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 - + @@ -1128,34 +1232,39 @@ public static string MetadataWithBlanks - + MIIDBTCCAe2gAwIBAgIQXxLnqm1cOoVGe62j7W7wZzANBgkqhkiG9w0BAQsFADAtMSswKQYDVQQDEyJhY2NvdW50cy5hY2Nlc3Njb250cm9sLndpbmRvd3MubmV0MB4XDTE3MDMyNjAwMDAwMFoXDTE5MDMyNzAwMDAwMFowLTErMCkGA1UEAxMiYWNjb3VudHMuYWNjZXNzY29udHJvbC53aW5kb3dzLm5ldDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKJGarCm4IF0/Gz5Xx/zyZwD2rdJJZtO2Ukk1Oz+Br1sLVY8I5vj5esB+lotmLEblA9N/w188vmTvykaEzUl49NA4s86x44SW6WtdQbGJ0IjpQJUalUMyy91vIBkK/7K3nBXeVBsRk7tm528leoQ05/aZ+1ycJBIU+1oGYThv8MOjyHAlXJmCaGXwXTisZ+hHjcwlMk/+ZEutHflKLIpPUNEi7j4Xw+zp9UKo5pzWIr/iJ4HjvCkFofW90AMF2xp8dMhpbVcfJGS/Ii3J66LuNLCH/HtSZ42FO+tnRL/nNzzFWUhGT92Q5VFVngfWJ3PAg1zz8I1wowLD2fiB2udGXcCAwEAAaMhMB8wHQYDVR0OBBYEFFXPbFXjmMR3BluF+2MeSXd1NQ3rMA0GCSqGSIb3DQEBCwUAA4IBAQAsd3wGVilJxDtbY1K2oAsWLdNJgmCaYdrtdlAsjGlarSQSzBH0Ybf78fcPX//DYaLXlvaEGKVKp0jPq+RnJ17oP/RJpJTwVXPGRIlZopLIgnKpWlS/PS0uKAdNvLmz1zbGSILdcF+Qf41OozD4QNsS1c9YbDO4vpC9v8x3PVjfJvJwPonzNoOsLXA+8IONSXwCApsnmrwepKu8sifsFYSwgrwxRPGTEAjkdzRJ0yMqiY/VoJ7lqJ/FBJqqAjGPGq/yI9rVoG+mbO1amrIDWHHTKgfbKk0bXGtVUbsayyHR5jSgadmkLBh5AaN/HcgDK/jINrnpiQ+/2ewH/8qLaQ3B - - + + - + - + - - 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 + + + + https://login.microsoftonline.com/268da1a1-9db4-48b9-b1fe-683250ba90cc/wsfed + + + https://login.microsoftonline.com/268da1a1-9db4-48b9-b1fe-683250ba90cc/wsfed - - - + +
+