//----------------------------------------------------------------------- // // 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); } } }