Skip to content

Commit

Permalink
(feature): Add ActiveTokenEndpoint to WsFederationConfiguration (#2100)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
victorm-hernandez authored Jun 21, 2023
1 parent 5068c6e commit 23808d5
Show file tree
Hide file tree
Showing 12 changed files with 900 additions and 60 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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; }

/// <summary>
/// This base class property is not used in OpenIdConnect.
/// </summary>
[JsonIgnore]
public override string ActiveTokenEndpoint { get; set; }

/// <summary>
/// Gets the collection of 'token_endpoint_auth_methods_supported'.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
@@ -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
{
/// <summary>
/// Defines a class for validating the WsFederationConfiguration.
/// </summary>
public class WsFederationConfigurationValidator : IConfigurationValidator<WsFederationConfiguration>
{
/// <summary>
/// Validates a WsFederationConfiguration.
/// </summary>
/// <param name="configuration">WsFederationConfiguration to validate</param>
/// <returns>A <see cref="ConfigurationValidationResult"/> containing the validation result.</returns>
/// <exception cref="ArgumentNullException">If the provided configuration is null</exception>
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 <fed:TokenSigningKey> 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
};
}
}
}
30 changes: 24 additions & 6 deletions src/Microsoft.IdentityModel.Protocols.WsFederation/LogMessages.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,23 @@ public List<KeyInfo> KeyInfos
} = new List<KeyInfo>();

/// <summary>
/// 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
/// </summary>
public string TokenEndpoint
{
get;
set;
}

/// <summary>
/// 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
/// </summary>
public string ActiveTokenEndpoint
{
get;
set;
}
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ namespace Microsoft.IdentityModel.Protocols.WsFederation
{
/// <summary>
/// Constants for WsFederation.
/// As defined in the http://docs.oasis-open.org/wsfed/federation/v1.2/os/ws-federation-1.2-spec-os.html
/// </summary>
public static class WsFederationConstants
{
Expand Down Expand Up @@ -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";
}

Expand Down
Loading

0 comments on commit 23808d5

Please sign in to comment.