//-----------------------------------------------------------------------
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
//-----------------------------------------------------------------------
namespace System.IdentityModel.Tokens
{
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IdentityModel.Configuration;
using System.IdentityModel.Selectors;
using System.Runtime;
using System.Runtime.Remoting.Metadata.W3cXsd2001;
using System.Security.Claims;
using System.Security.Cryptography.X509Certificates;
using System.Security.Principal;
using System.ServiceModel.Security;
using System.Xml;
using Claim = System.Security.Claims.Claim;
///
/// SecurityTokenHandler for X509SecurityToken. By default, the
/// handler will do chain-trust validation of the Certificate.
///
public class X509SecurityTokenHandler : SecurityTokenHandler
{
//
// The below defaults will only be used if some verification properties are set in config and others are not
//
private static X509RevocationMode defaultRevocationMode = X509RevocationMode.Online;
private static X509CertificateValidationMode defaultValidationMode = X509CertificateValidationMode.PeerOrChainTrust;
private static StoreLocation defaultStoreLocation = StoreLocation.LocalMachine;
private X509NTAuthChainTrustValidator x509NTAuthChainTrustValidator;
object lockObject = new object();
private bool mapToWindows;
private X509CertificateValidator certificateValidator;
private bool writeXmlDSigDefinedClauseTypes;
private X509DataSecurityKeyIdentifierClauseSerializer x509DataKeyIdentifierClauseSerializer = new X509DataSecurityKeyIdentifierClauseSerializer();
///
/// Creates an instance of . MapToWindows is defaulted to false.
/// Uses as the default certificate validator.
///
public X509SecurityTokenHandler()
: this(false, null)
{
}
///
/// Creates an instance of with an X509 certificate validator.
/// MapToWindows is to false by default.
///
/// The certificate validator.
public X509SecurityTokenHandler(X509CertificateValidator certificateValidator)
: this(false, certificateValidator)
{
}
///
/// Creates an instance of . Uses
/// as the default certificate validator.
///
/// Boolean to indicate if the certificate should be mapped to a
/// windows account. Default is false.
public X509SecurityTokenHandler(bool mapToWindows)
: this(mapToWindows, null)
{
}
///
/// Creates an instance of .
///
/// Boolean to indicate if the certificate should be mapped to a windows account.
/// The certificate validator.
public X509SecurityTokenHandler(bool mapToWindows, X509CertificateValidator certificateValidator)
{
this.mapToWindows = mapToWindows;
this.certificateValidator = certificateValidator;
}
///
/// Load custom configuration from Xml
///
/// XmlElement to custom configuration.
/// The param 'customConfigElements' is null.
/// Custom configuration specified was invalid.
public override void LoadCustomConfiguration(XmlNodeList customConfigElements)
{
if (customConfigElements == null)
{
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("customConfigElements");
}
List configNodes = XmlUtil.GetXmlElements(customConfigElements);
bool foundValidConfig = false;
bool foundCustomX509Validator = false;
X509RevocationMode revocationMode = defaultRevocationMode;
X509CertificateValidationMode certificateValidationMode = defaultValidationMode;
StoreLocation trustedStoreLocation = defaultStoreLocation;
string customValidator = null;
foreach (XmlElement customConfigElement in configNodes)
{
if (!StringComparer.Ordinal.Equals(customConfigElement.LocalName, ConfigurationStrings.X509SecurityTokenHandlerRequirement))
{
continue;
}
if (foundValidConfig)
{
throw DiagnosticUtility.ThrowHelperInvalidOperation(SR.GetString(SR.ID7026, ConfigurationStrings.X509SecurityTokenHandlerRequirement));
}
foreach (XmlAttribute attribute in customConfigElement.Attributes)
{
if (StringComparer.OrdinalIgnoreCase.Equals(attribute.LocalName, ConfigurationStrings.MapToWindows))
{
mapToWindows = XmlConvert.ToBoolean(attribute.Value.ToLowerInvariant());
}
else if (StringComparer.OrdinalIgnoreCase.Equals(attribute.LocalName, ConfigurationStrings.X509CertificateValidator))
{
customValidator = attribute.Value.ToString();
}
else if (StringComparer.OrdinalIgnoreCase.Equals(attribute.LocalName, ConfigurationStrings.X509CertificateRevocationMode))
{
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, customConfigElement.LocalName)));
}
}
else if (StringComparer.OrdinalIgnoreCase.Equals(attribute.LocalName, ConfigurationStrings.X509CertificateValidationMode))
{
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, customConfigElement.LocalName)));
}
}
else if (StringComparer.OrdinalIgnoreCase.Equals(attribute.LocalName, ConfigurationStrings.X509TrustedStoreLocation))
{
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, customConfigElement.LocalName)));
}
}
else
{
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(SR.GetString(SR.ID7004, attribute.LocalName, customConfigElement.LocalName)));
}
}
foundValidConfig = true;
}
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(new CustomTypeElement(customValidatorType));
}
else if (foundCustomX509Validator)
{
certificateValidator = X509Util.CreateCertificateValidator(certificateValidationMode, revocationMode, trustedStoreLocation);
}
}
///
/// Gets or sets a value indicating whether if the validating token should be mapped to a
/// Windows account.
///
public bool MapToWindows
{
get { return mapToWindows; }
set { mapToWindows = value; }
}
///
/// Gets or sets the X509CeritificateValidator that is used by the current instance.
///
public X509CertificateValidator CertificateValidator
{
get
{
if (certificateValidator == null)
{
if (Configuration != null)
{
return Configuration.CertificateValidator;
}
else
{
return null;
}
}
else
{
return certificateValidator;
}
}
set
{
certificateValidator = value;
}
}
///
/// Gets or sets the X509NTAuthChainTrustValidator that is used by the current instance during certificate validation when the incoming certificate is mapped to windows.
///
public X509NTAuthChainTrustValidator X509NTAuthChainTrustValidator
{
get
{
return this.x509NTAuthChainTrustValidator;
}
set
{
this.x509NTAuthChainTrustValidator = value;
}
}
///
/// Gets or sets a value indicating whether XmlDsig defined clause types are
/// preferred. Supported XmlDSig defined SecurityKeyIdentifierClause types
/// are,
/// 1. X509IssuerSerial
/// 2. X509SKI
/// 3. X509Certificate
///
public bool WriteXmlDSigDefinedClauseTypes
{
get { return writeXmlDSigDefinedClauseTypes; }
set { writeXmlDSigDefinedClauseTypes = value; }
}
///
/// Gets a boolean indicating if the handler can validate tokens.
/// Returns true by default.
///
public override bool CanValidateToken
{
get
{
return true;
}
}
///
/// Gets a boolean indicating if the handler can write tokens.
/// Returns true by default.
///
public override bool CanWriteToken
{
get
{
return true;
}
}
///
/// Checks if the given reader is referring to a <ds:X509Data> element.
///
/// XmlReader positioned at the SecurityKeyIdentifierClause.
/// True if the XmlReader is referring to a <ds:X509Data> element.
/// The input parameter 'reader' is null.
public override bool CanReadKeyIdentifierClause(XmlReader reader)
{
if (reader == null)
{
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("reader");
}
return x509DataKeyIdentifierClauseSerializer.CanReadKeyIdentifierClause(reader);
}
///
/// Checks if the reader points to a X.509 Security Token as defined in WS-Security.
///
/// Reader pointing to the token XML.
/// Returns true if the element is pointing to a X.509 Security Token.
/// The parameter 'reader' is null.
public override bool CanReadToken(XmlReader reader)
{
if (reader == null)
{
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("reader");
}
if (reader.IsStartElement(WSSecurity10Constants.Elements.BinarySecurityToken, WSSecurity10Constants.Namespace))
{
string valueTypeUri = reader.GetAttribute(WSSecurity10Constants.Attributes.ValueType, null);
return StringComparer.Ordinal.Equals(valueTypeUri, WSSecurity10Constants.X509TokenType);
}
return false;
}
///
/// Checks if the given SecurityKeyIdentifierClause can be serialized by this handler. The
/// supported SecurityKeyIdentifierClause are,
/// 1.
/// 2.
/// 3.
///
/// SecurityKeyIdentifierClause to be serialized.
/// True if the 'securityKeyIdentifierClause' is supported and if WriteXmlDSigDefinedClausTypes
/// is set to true.
/// The parameter 'securityKeyIdentifierClause' is null.
public override bool CanWriteKeyIdentifierClause(SecurityKeyIdentifierClause securityKeyIdentifierClause)
{
if (securityKeyIdentifierClause == null)
{
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("securityKeyIdentifierClause");
}
return writeXmlDSigDefinedClauseTypes && x509DataKeyIdentifierClauseSerializer.CanWriteKeyIdentifierClause(securityKeyIdentifierClause);
}
///
/// Gets X509SecurityToken type.
///
public override Type TokenType
{
get { return typeof(X509SecurityToken); }
}
///
/// Deserializes a SecurityKeyIdentifierClause referenced by the XmlReader.
///
/// XmlReader referencing the SecurityKeyIdentifierClause.
/// Instance of SecurityKeyIdentifierClause.
/// The input parameter 'reader' is null.
public override SecurityKeyIdentifierClause ReadKeyIdentifierClause(XmlReader reader)
{
if (reader == null)
{
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("reader");
}
return x509DataKeyIdentifierClauseSerializer.ReadKeyIdentifierClause(reader);
}
///
/// Reads the X.509 Security token referenced by the XmlReader.
///
/// XmlReader pointing to a X.509 Security token.
/// An instance of .
/// The parameter 'reader' is null.
/// XmlReader is not pointing to an valid X509SecurityToken as
/// defined in WS-Security X.509 Token Profile. Or the encodingType specified is other than Base64
/// or HexBinary.
public override SecurityToken ReadToken(XmlReader reader)
{
if (reader == null)
{
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("reader");
}
XmlDictionaryReader dicReader = XmlDictionaryReader.CreateDictionaryReader(reader);
if (!dicReader.IsStartElement(WSSecurity10Constants.Elements.BinarySecurityToken, WSSecurity10Constants.Namespace))
{
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(
new XmlException(
SR.GetString(
SR.ID4065,
WSSecurity10Constants.Elements.BinarySecurityToken,
WSSecurity10Constants.Namespace,
dicReader.LocalName,
dicReader.NamespaceURI)));
}
string valueTypeUri = dicReader.GetAttribute(WSSecurity10Constants.Attributes.ValueType, null);
if (!StringComparer.Ordinal.Equals(valueTypeUri, WSSecurity10Constants.X509TokenType))
{
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(
new XmlException(
SR.GetString(
SR.ID4066,
WSSecurity10Constants.Elements.BinarySecurityToken,
WSSecurity10Constants.Namespace,
WSSecurity10Constants.Attributes.ValueType,
WSSecurity10Constants.X509TokenType,
valueTypeUri)));
}
string wsuId = dicReader.GetAttribute(WSSecurityUtilityConstants.Attributes.Id, WSSecurityUtilityConstants.Namespace);
string encoding = dicReader.GetAttribute(WSSecurity10Constants.Attributes.EncodingType, null);
byte[] binaryData;
if (encoding == null || StringComparer.Ordinal.Equals(encoding, WSSecurity10Constants.Base64EncodingType))
{
binaryData = dicReader.ReadElementContentAsBase64();
}
else if (StringComparer.Ordinal.Equals(encoding, WSSecurity10Constants.HexBinaryEncodingType))
{
binaryData = SoapHexBinary.Parse(dicReader.ReadElementContentAsString()).Value;
}
else
{
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new XmlException(SR.GetString(SR.ID4068)));
}
return String.IsNullOrEmpty(wsuId) ?
new X509SecurityToken(new X509Certificate2(binaryData)) :
new X509SecurityToken(new X509Certificate2(binaryData), wsuId);
}
///
/// Gets the X.509 Security Token Type defined in WS-Security X.509 Token profile.
///
/// The token type identifier.
public override string[] GetTokenTypeIdentifiers()
{
return new string[] { SecurityTokenTypes.X509Certificate };
}
///
/// Validates an .
///
/// The to validate.
/// A of representing the identities contained in the token.
/// The parameter 'token' is null.
/// The token is not assignable from .
/// Configuration is null.
/// The current was unable to validate the certificate in the Token.
/// Configuration.IssuerNameRegistry is null.
/// Configuration.IssuerNameRegistry return null when resolving the issuer of the certificate in the Token.
public override ReadOnlyCollection ValidateToken(SecurityToken token)
{
if (token == null)
{
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("token");
}
X509SecurityToken x509Token = token as X509SecurityToken;
if (x509Token == null)
{
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgument("token", SR.GetString(SR.ID0018, typeof(X509SecurityToken)));
}
if (this.Configuration == null)
{
throw DiagnosticUtility.ThrowHelperInvalidOperation(SR.GetString(SR.ID4274));
}
try
{
// Validate the token.
try
{
this.CertificateValidator.Validate(x509Token.Certificate);
}
catch (SecurityTokenValidationException e)
{
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new SecurityTokenValidationException(SR.GetString(SR.ID4257,
X509Util.GetCertificateId(x509Token.Certificate)), e));
}
if (this.Configuration.IssuerNameRegistry == null)
{
throw DiagnosticUtility.ThrowHelperInvalidOperation(SR.GetString(SR.ID4277));
}
string issuer = X509Util.GetCertificateIssuerName(x509Token.Certificate, this.Configuration.IssuerNameRegistry);
if (String.IsNullOrEmpty(issuer))
{
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new SecurityTokenException(SR.GetString(SR.ID4175)));
}
ClaimsIdentity identity = null;
if (!mapToWindows)
{
identity = new ClaimsIdentity(AuthenticationTypes.X509);
// PARTIAL TRUST: will fail when adding claims, AddClaim is SecurityCritical.
identity.AddClaim(new Claim(ClaimTypes.AuthenticationMethod, AuthenticationMethods.X509));
}
else
{
WindowsIdentity windowsIdentity;
X509WindowsSecurityToken x509WindowsSecurityToken = token as X509WindowsSecurityToken;
// if this is the case, then the user has already been mapped to a windows account, just return the identity after adding a couple of claims.
if (x509WindowsSecurityToken != null && x509WindowsSecurityToken.WindowsIdentity != null)
{
// X509WindowsSecurityToken is disposable, make a copy.
windowsIdentity = new WindowsIdentity(x509WindowsSecurityToken.WindowsIdentity.Token, x509WindowsSecurityToken.AuthenticationType);
}
else
{
// Ensure NT_AUTH chain policy for certificate account mapping
if (this.x509NTAuthChainTrustValidator == null)
{
lock (this.lockObject)
{
if (this.x509NTAuthChainTrustValidator == null)
{
this.x509NTAuthChainTrustValidator = new X509NTAuthChainTrustValidator();
}
}
}
this.x509NTAuthChainTrustValidator.Validate(x509Token.Certificate);
windowsIdentity = ClaimsHelper.CertificateLogon(x509Token.Certificate);
}
// PARTIAL TRUST: will fail when adding claims, AddClaim is SecurityCritical.
windowsIdentity.AddClaim(new Claim(ClaimTypes.AuthenticationMethod, AuthenticationMethods.X509));
identity = windowsIdentity;
}
if (this.Configuration.SaveBootstrapContext)
{
identity.BootstrapContext = new BootstrapContext(token, this);
}
identity.AddClaim(new Claim(ClaimTypes.AuthenticationInstant, XmlConvert.ToString(DateTime.UtcNow, DateTimeFormats.Generated), ClaimValueTypes.DateTime));
identity.AddClaims(X509Util.GetClaimsFromCertificate(x509Token.Certificate, issuer));
this.TraceTokenValidationSuccess(token);
List identities = new List(1);
identities.Add(identity);
return identities.AsReadOnly();
}
catch (Exception e)
{
if (Fx.IsFatal(e))
{
throw;
}
this.TraceTokenValidationFailure(token, e.Message);
throw e;
}
}
///
/// Serializes a given SecurityKeyIdentifierClause to the XmlWriter.
///
/// XmlWriter to which the 'securityKeyIdentifierClause' should be serialized.
/// SecurityKeyIdentifierClause to serialize.
/// Input parameter 'wrtier' or 'securityKeyIdentifierClause' is null.
/// The property WriteXmlDSigDefinedClauseTypes is false.
public override void WriteKeyIdentifierClause(XmlWriter writer, SecurityKeyIdentifierClause securityKeyIdentifierClause)
{
if (writer == null)
{
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("writer");
}
if (securityKeyIdentifierClause == null)
{
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("securityKeyIdentifierClause");
}
if (!writeXmlDSigDefinedClauseTypes)
{
throw DiagnosticUtility.ThrowHelperInvalidOperation(SR.GetString(SR.ID4261));
}
x509DataKeyIdentifierClauseSerializer.WriteKeyIdentifierClause(writer, securityKeyIdentifierClause);
}
///
/// Writes the X509SecurityToken to the given XmlWriter.
///
/// XmlWriter to write the token into.
/// The SecurityToken of type X509SecurityToken to be written.
/// The parameter 'writer' or 'token' is null.
/// The token is not of type X509SecurityToken.
public override void WriteToken(XmlWriter writer, SecurityToken token)
{
if (writer == null)
{
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("writer");
}
if (token == null)
{
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("token");
}
X509SecurityToken x509Token = token as X509SecurityToken;
if (x509Token == null)
{
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgument("token", SR.GetString(SR.ID0018, typeof(X509SecurityToken)));
}
writer.WriteStartElement(WSSecurity10Constants.Elements.BinarySecurityToken, WSSecurity10Constants.Namespace);
if (!String.IsNullOrEmpty(x509Token.Id))
{
writer.WriteAttributeString(WSSecurityUtilityConstants.Attributes.Id, WSSecurityUtilityConstants.Namespace, x509Token.Id);
}
writer.WriteAttributeString(WSSecurity10Constants.Attributes.ValueType, null, WSSecurity10Constants.X509TokenType);
writer.WriteAttributeString(WSSecurity10Constants.Attributes.EncodingType, WSSecurity10Constants.Base64EncodingType);
byte[] rawData = x509Token.Certificate.GetRawCertData();
writer.WriteBase64(rawData, 0, rawData.Length);
writer.WriteEndElement();
}
internal static WindowsIdentity KerberosCertificateLogon(X509Certificate2 certificate)
{
return X509SecurityTokenAuthenticator.KerberosCertificateLogon(certificate);
}
}
}