e79aa3c0ed
Former-commit-id: a2155e9bd80020e49e72e86c44da02a8ac0e57a4
419 lines
18 KiB
C#
419 lines
18 KiB
C#
//------------------------------------------------------------
|
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
|
//------------------------------------------------------------
|
|
namespace System.IdentityModel.Tokens
|
|
{
|
|
using System.Collections.Generic;
|
|
using System.Diagnostics;
|
|
using System.IdentityModel.Configuration;
|
|
using System.IdentityModel.Diagnostics;
|
|
using System.IdentityModel.Selectors;
|
|
using System.Security.Claims;
|
|
using System.Security.Cryptography.X509Certificates;
|
|
using System.ServiceModel.Security;
|
|
using System.Text;
|
|
using System.Xml;
|
|
|
|
/// <summary>
|
|
/// Extends SecurityTokenRequirement by adding new properties which are
|
|
/// useful for issued tokens.
|
|
/// </summary>
|
|
public class SamlSecurityTokenRequirement
|
|
{
|
|
|
|
//
|
|
// The below defaults will only be used if some verification properties are set in config and others are not
|
|
//
|
|
static X509RevocationMode DefaultRevocationMode = X509RevocationMode.Online;
|
|
static X509CertificateValidationMode DefaultValidationMode = X509CertificateValidationMode.PeerOrChainTrust;
|
|
static StoreLocation DefaultStoreLocation = StoreLocation.LocalMachine;
|
|
|
|
string _nameClaimType = ClaimsIdentity.DefaultNameClaimType;
|
|
string _roleClaimType = ClaimTypes.Role;
|
|
|
|
bool _mapToWindows;
|
|
|
|
X509CertificateValidator _certificateValidator;
|
|
|
|
/// <summary>
|
|
/// Creates an instance of <see cref="SamlSecurityTokenRequirement"/>
|
|
/// </summary>
|
|
public SamlSecurityTokenRequirement()
|
|
{
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates an instance of <see cref="SamlSecurityTokenRequirement"/>
|
|
/// <param name="element">The XmlElement from which the instance is to be loaded.</param>
|
|
/// </summary>
|
|
public SamlSecurityTokenRequirement(XmlElement element)
|
|
{
|
|
if (element == null)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("element");
|
|
}
|
|
|
|
if (element.LocalName != ConfigurationStrings.SamlSecurityTokenRequirement)
|
|
{
|
|
throw DiagnosticUtility.ThrowHelperInvalidOperation(SR.GetString(SR.ID7000, ConfigurationStrings.SamlSecurityTokenRequirement, element.LocalName));
|
|
}
|
|
|
|
bool foundCustomX509Validator = false;
|
|
X509RevocationMode revocationMode = DefaultRevocationMode;
|
|
X509CertificateValidationMode certificateValidationMode = DefaultValidationMode;
|
|
StoreLocation trustedStoreLocation = DefaultStoreLocation;
|
|
string customValidator = null;
|
|
|
|
foreach (XmlAttribute attribute in element.Attributes)
|
|
{
|
|
if (StringComparer.OrdinalIgnoreCase.Equals(attribute.LocalName, ConfigurationStrings.MapToWindows))
|
|
{
|
|
bool outMapToWindows = false;
|
|
if (!bool.TryParse(attribute.Value, out outMapToWindows))
|
|
{
|
|
throw DiagnosticUtility.ThrowHelperInvalidOperation(SR.GetString(SR.ID7022, attribute.Value));
|
|
}
|
|
this.MapToWindows = outMapToWindows;
|
|
}
|
|
else if (StringComparer.OrdinalIgnoreCase.Equals(attribute.LocalName, ConfigurationStrings.IssuerCertificateValidator))
|
|
{
|
|
customValidator = attribute.Value.ToString();
|
|
}
|
|
else if (StringComparer.OrdinalIgnoreCase.Equals(attribute.LocalName, ConfigurationStrings.IssuerCertificateRevocationMode))
|
|
{
|
|
foundCustomX509Validator = true;
|
|
|
|
string revocationModeString = attribute.Value.ToString();
|
|
|
|
if (StringComparer.OrdinalIgnoreCase.Equals(revocationModeString, ConfigurationStrings.X509RevocationModeNoCheck))
|
|
{
|
|
revocationMode = X509RevocationMode.NoCheck;
|
|
}
|
|
|
|
else if (StringComparer.OrdinalIgnoreCase.Equals(revocationModeString, ConfigurationStrings.X509RevocationModeOffline))
|
|
{
|
|
revocationMode = X509RevocationMode.Offline;
|
|
}
|
|
|
|
else if (StringComparer.OrdinalIgnoreCase.Equals(revocationModeString, ConfigurationStrings.X509RevocationModeOnline))
|
|
{
|
|
revocationMode = X509RevocationMode.Online;
|
|
}
|
|
|
|
else
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(SR.GetString(SR.ID7011, attribute.LocalName, element.LocalName)));
|
|
}
|
|
}
|
|
else if (StringComparer.OrdinalIgnoreCase.Equals(attribute.LocalName, ConfigurationStrings.IssuerCertificateValidationMode))
|
|
{
|
|
foundCustomX509Validator = true;
|
|
|
|
string validationModeString = attribute.Value.ToString();
|
|
|
|
if (StringComparer.OrdinalIgnoreCase.Equals(validationModeString, ConfigurationStrings.X509CertificateValidationModeChainTrust))
|
|
{
|
|
certificateValidationMode = X509CertificateValidationMode.ChainTrust;
|
|
}
|
|
|
|
else if (StringComparer.OrdinalIgnoreCase.Equals(validationModeString, ConfigurationStrings.X509CertificateValidationModePeerOrChainTrust))
|
|
{
|
|
certificateValidationMode = X509CertificateValidationMode.PeerOrChainTrust;
|
|
}
|
|
|
|
else if (StringComparer.OrdinalIgnoreCase.Equals(validationModeString, ConfigurationStrings.X509CertificateValidationModePeerTrust))
|
|
{
|
|
certificateValidationMode = X509CertificateValidationMode.PeerTrust;
|
|
}
|
|
|
|
else if (StringComparer.OrdinalIgnoreCase.Equals(validationModeString, ConfigurationStrings.X509CertificateValidationModeNone))
|
|
{
|
|
certificateValidationMode = X509CertificateValidationMode.None;
|
|
}
|
|
|
|
else if (StringComparer.OrdinalIgnoreCase.Equals(validationModeString, ConfigurationStrings.X509CertificateValidationModeCustom))
|
|
{
|
|
certificateValidationMode = X509CertificateValidationMode.Custom;
|
|
}
|
|
|
|
else
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(SR.GetString(SR.ID7011, attribute.LocalName, element.LocalName)));
|
|
}
|
|
}
|
|
else if (StringComparer.OrdinalIgnoreCase.Equals(attribute.LocalName, ConfigurationStrings.IssuerCertificateTrustedStoreLocation))
|
|
{
|
|
foundCustomX509Validator = true;
|
|
|
|
string trustedStoreLocationString = attribute.Value.ToString();
|
|
|
|
if (StringComparer.OrdinalIgnoreCase.Equals(trustedStoreLocationString, ConfigurationStrings.X509TrustedStoreLocationCurrentUser))
|
|
{
|
|
trustedStoreLocation = StoreLocation.CurrentUser;
|
|
}
|
|
|
|
else if (StringComparer.OrdinalIgnoreCase.Equals(trustedStoreLocationString, ConfigurationStrings.X509TrustedStoreLocationLocalMachine))
|
|
{
|
|
trustedStoreLocation = StoreLocation.LocalMachine;
|
|
}
|
|
|
|
else
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(SR.GetString(SR.ID7011, attribute.LocalName, element.LocalName)));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(SR.GetString(SR.ID7004, attribute.LocalName, element.LocalName)));
|
|
}
|
|
}
|
|
|
|
List<XmlElement> configElements = XmlUtil.GetXmlElements(element.ChildNodes);
|
|
|
|
foreach (XmlElement childElement in configElements)
|
|
{
|
|
if (StringComparer.Ordinal.Equals(childElement.LocalName, ConfigurationStrings.NameClaimType))
|
|
{
|
|
if (childElement.Attributes.Count != 1 || !StringComparer.Ordinal.Equals(childElement.Attributes[0].LocalName, ConfigurationStrings.Value))
|
|
{
|
|
throw DiagnosticUtility.ThrowHelperInvalidOperation(SR.GetString(SR.ID7001, String.Format(System.Globalization.CultureInfo.InvariantCulture, "{0}/{1}", element.LocalName, childElement.LocalName), ConfigurationStrings.Value));
|
|
}
|
|
this.NameClaimType = childElement.Attributes[0].Value;
|
|
}
|
|
else if (StringComparer.Ordinal.Equals(childElement.LocalName, ConfigurationStrings.RoleClaimType))
|
|
{
|
|
if (childElement.Attributes.Count != 1 || !StringComparer.Ordinal.Equals(childElement.Attributes[0].LocalName, ConfigurationStrings.Value))
|
|
{
|
|
throw DiagnosticUtility.ThrowHelperInvalidOperation(SR.GetString(SR.ID7001, String.Format(System.Globalization.CultureInfo.InvariantCulture, "{0}/{1}", element.LocalName, childElement.LocalName), ConfigurationStrings.Value));
|
|
}
|
|
this.RoleClaimType = childElement.Attributes[0].Value;
|
|
}
|
|
else
|
|
{
|
|
throw DiagnosticUtility.ThrowHelperInvalidOperation(SR.GetString(SR.ID7002, childElement.LocalName, ConfigurationStrings.SamlSecurityTokenRequirement));
|
|
}
|
|
}
|
|
|
|
if (certificateValidationMode == X509CertificateValidationMode.Custom)
|
|
{
|
|
if (string.IsNullOrEmpty(customValidator))
|
|
{
|
|
throw DiagnosticUtility.ThrowHelperInvalidOperation(SR.GetString(SR.ID7028));
|
|
}
|
|
|
|
Type customValidatorType = Type.GetType(customValidator, true);
|
|
|
|
if (customValidatorType == null)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgument("value", SR.GetString(SR.ID7007, customValidatorType));
|
|
}
|
|
|
|
_certificateValidator = CustomTypeElement.Resolve<X509CertificateValidator>(new CustomTypeElement(customValidatorType));
|
|
}
|
|
else if (foundCustomX509Validator)
|
|
{
|
|
_certificateValidator = X509Util.CreateCertificateValidator(certificateValidationMode, revocationMode, trustedStoreLocation);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets/sets the X509CertificateValidator associated with this token requirement
|
|
/// </summary>
|
|
public X509CertificateValidator CertificateValidator
|
|
{
|
|
get
|
|
{
|
|
return _certificateValidator;
|
|
}
|
|
set
|
|
{
|
|
if (value == null)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("value");
|
|
}
|
|
_certificateValidator = value;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or sets the Claim Type that will be used to generate the
|
|
/// FederatedIdentity.Name property.
|
|
/// </summary>
|
|
public string NameClaimType
|
|
{
|
|
get
|
|
{
|
|
return _nameClaimType;
|
|
}
|
|
set
|
|
{
|
|
_nameClaimType = value;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the Claim Types that are used to generate the
|
|
/// FederatedIdentity.Roles property.
|
|
/// </summary>
|
|
public string RoleClaimType
|
|
{
|
|
get
|
|
{
|
|
return _roleClaimType;
|
|
}
|
|
set
|
|
{
|
|
_roleClaimType = value;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Determines if the token handler will attempt to map the SAML identity to a
|
|
/// Windows identity via the unique principal name (UPN) claim.
|
|
/// </summary>
|
|
public bool MapToWindows
|
|
{
|
|
get { return _mapToWindows; }
|
|
set { _mapToWindows = value; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Checks if Audience Enforcement checks are required for the given token
|
|
/// based on this SamlSecurityTokenRequirement settings.
|
|
/// </summary>
|
|
/// <param name="audienceUriMode">
|
|
/// The <see cref="AudienceUriMode"/> defining the audience requirement.
|
|
/// </param>
|
|
/// <param name="token">The Security token to be tested for Audience
|
|
/// Enforcement.</param>
|
|
/// <returns>True if Audience Enforcement should be applied.</returns>
|
|
/// <exception cref="ArgumentNullException">The input argument 'token' is null.</exception>
|
|
public virtual bool ShouldEnforceAudienceRestriction(AudienceUriMode audienceUriMode, SecurityToken token)
|
|
{
|
|
if (null == token)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("token");
|
|
}
|
|
|
|
//
|
|
// Use AudienceUriMode to determine whether the audience
|
|
// should be enforced
|
|
//
|
|
switch (audienceUriMode)
|
|
{
|
|
case AudienceUriMode.Always:
|
|
return true;
|
|
|
|
case AudienceUriMode.Never:
|
|
return false;
|
|
|
|
case AudienceUriMode.BearerKeyOnly:
|
|
#pragma warning suppress 56506
|
|
return (null == token.SecurityKeys || 0 == token.SecurityKeys.Count);
|
|
|
|
default:
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(SR.GetString(SR.ID4025, audienceUriMode)));
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Checks the given list of Audience URIs with the AllowedAudienceUri list.
|
|
/// </summary>
|
|
/// <param name="allowedAudienceUris">Collection of AudienceUris.</param>
|
|
/// <param name="tokenAudiences">Collection of audience URIs the token applies to.</param>
|
|
/// <exception cref="ArgumentNullException">The input argument 'allowedAudienceUris' is null.</exception>
|
|
/// <exception cref="ArgumentNullException">The input argument 'tokenAudiences' is null.</exception>
|
|
/// <exception cref="AudienceUriValidationFailedException">Either the input argument 'tokenAudiences' or the configured
|
|
/// 'AudienceUris' collection is empty.</exception>
|
|
public virtual void ValidateAudienceRestriction(IList<Uri> allowedAudienceUris, IList<Uri> tokenAudiences)
|
|
{
|
|
if (null == allowedAudienceUris)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("allowedAudienceUris");
|
|
}
|
|
|
|
if (null == tokenAudiences)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("tokenAudiences");
|
|
}
|
|
|
|
if (0 == tokenAudiences.Count)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new AudienceUriValidationFailedException(
|
|
SR.GetString(SR.ID1036)));
|
|
}
|
|
|
|
if (0 == allowedAudienceUris.Count)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new AudienceUriValidationFailedException(
|
|
SR.GetString(SR.ID1043)));
|
|
}
|
|
|
|
bool found = false;
|
|
foreach (Uri audience in tokenAudiences)
|
|
{
|
|
if (audience != null)
|
|
{
|
|
// Strip off any query string or fragment. This is necessary because the
|
|
// CardSpace uses the raw Request-URI to form the audience when issuing
|
|
// tokens for personal cards, but we clearly don't want things like the
|
|
// ReturnUrl parameter affecting the audience matching.
|
|
Uri audienceLeftPart;
|
|
if (audience.IsAbsoluteUri)
|
|
{
|
|
audienceLeftPart = new Uri(audience.GetLeftPart(UriPartial.Path));
|
|
}
|
|
else
|
|
{
|
|
Uri baseUri = new Uri("http://www.example.com");
|
|
Uri resolved = new Uri(baseUri, audience);
|
|
audienceLeftPart = baseUri.MakeRelativeUri(new Uri(resolved.GetLeftPart(UriPartial.Path)));
|
|
}
|
|
|
|
if (allowedAudienceUris.Contains(audienceLeftPart))
|
|
{
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!found)
|
|
{
|
|
#pragma warning suppress 56506
|
|
if (1 == tokenAudiences.Count || null != tokenAudiences[0])
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new AudienceUriValidationFailedException(
|
|
SR.GetString(SR.ID1038, tokenAudiences[0].OriginalString)));
|
|
}
|
|
else
|
|
{
|
|
StringBuilder sb = new StringBuilder(SR.GetString(SR.ID8007));
|
|
bool first = true;
|
|
|
|
foreach (Uri a in tokenAudiences)
|
|
{
|
|
if (a != null)
|
|
{
|
|
if (first)
|
|
{
|
|
first = false;
|
|
}
|
|
else
|
|
{
|
|
sb.Append(", ");
|
|
}
|
|
|
|
sb.Append(a.OriginalString);
|
|
}
|
|
}
|
|
|
|
TraceUtility.TraceString(TraceEventType.Error, sb.ToString());
|
|
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new AudienceUriValidationFailedException(SR.GetString(SR.ID1037)));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|