//------------------------------------------------------------ // Copyright (c) Microsoft Corporation. All rights reserved. //------------------------------------------------------------ namespace System.ServiceModel.Channels { using System.Collections.ObjectModel; using System.IdentityModel.Claims; using System.IdentityModel.Policy; using System.IdentityModel.Selectors; using System.IdentityModel.Tokens; using System.Net; using System.Net.Security; using System.Runtime; using System.Runtime.CompilerServices; using System.Security.Cryptography.X509Certificates; using System.ServiceModel; using System.ServiceModel.Description; using System.ServiceModel.Security; using System.ServiceModel.Security.Tokens; class HttpsChannelFactory : HttpChannelFactory { bool requireClientCertificate; IChannelBindingProvider channelBindingProvider; RemoteCertificateValidationCallback remoteCertificateValidationCallback; X509CertificateValidator sslCertificateValidator; internal HttpsChannelFactory(HttpsTransportBindingElement httpsBindingElement, BindingContext context) : base(httpsBindingElement, context) { this.requireClientCertificate = httpsBindingElement.RequireClientCertificate; this.channelBindingProvider = new ChannelBindingProviderHelper(); ClientCredentials credentials = context.BindingParameters.Find(); if (credentials != null && credentials.ServiceCertificate.SslCertificateAuthentication != null) { this.sslCertificateValidator = credentials.ServiceCertificate.SslCertificateAuthentication.GetCertificateValidator(); this.remoteCertificateValidationCallback = new RemoteCertificateValidationCallback(RemoteCertificateValidationCallback); } } public override string Scheme { get { return Uri.UriSchemeHttps; } } public bool RequireClientCertificate { get { return this.requireClientCertificate; } } public override bool IsChannelBindingSupportEnabled { get { return this.channelBindingProvider.IsChannelBindingSupportEnabled; } } public override T GetProperty() { if (typeof(T) == typeof(IChannelBindingProvider)) { return (T)(object)this.channelBindingProvider; } return base.GetProperty(); } internal override SecurityMessageProperty CreateReplySecurityProperty(HttpWebRequest request, HttpWebResponse response) { SecurityMessageProperty result = null; X509Certificate certificate = request.ServicePoint.Certificate; if (certificate != null) { X509Certificate2 certificateEx = new X509Certificate2(certificate); SecurityToken token = new X509SecurityToken(certificateEx, false); ReadOnlyCollection authorizationPolicies = SecurityUtils.NonValidatingX509Authenticator.ValidateToken(token); result = new SecurityMessageProperty(); result.TransportToken = new SecurityTokenSpecification(token, authorizationPolicies); result.ServiceSecurityContext = new ServiceSecurityContext(authorizationPolicies); } else { result = base.CreateReplySecurityProperty(request, response); } return result; } protected override void ValidateCreateChannelParameters(EndpointAddress remoteAddress, Uri via) { if (remoteAddress.Identity != null) { X509CertificateEndpointIdentity certificateIdentity = remoteAddress.Identity as X509CertificateEndpointIdentity; if (certificateIdentity != null) { if (certificateIdentity.Certificates.Count > 1) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgument("remoteAddress", SR.GetString( SR.HttpsIdentityMultipleCerts, remoteAddress.Uri)); } } EndpointIdentity identity = remoteAddress.Identity; bool validIdentity = (certificateIdentity != null) || ClaimTypes.Spn.Equals(identity.IdentityClaim.ClaimType) || ClaimTypes.Upn.Equals(identity.IdentityClaim.ClaimType) || ClaimTypes.Dns.Equals(identity.IdentityClaim.ClaimType); if (!HttpChannelFactory.IsWindowsAuth(this.AuthenticationScheme) && !validIdentity) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgument("remoteAddress", SR.GetString( SR.HttpsExplicitIdentity)); } } base.ValidateCreateChannelParameters(remoteAddress, via); } protected override TChannel OnCreateChannelCore(EndpointAddress address, Uri via) { ValidateCreateChannelParameters(address, via); this.ValidateWebSocketTransportUsage(); if (typeof(TChannel) == typeof(IRequestChannel)) { return (TChannel)(object)new HttpsRequestChannel((HttpsChannelFactory)(object)this, address, via, ManualAddressing); } else { return (TChannel)(object)new ClientWebSocketTransportDuplexSessionChannel((HttpChannelFactory)(object)this, this.ClientWebSocketFactory, address, via, this.WebSocketBufferPool); } } protected override bool IsSecurityTokenManagerRequired() { return this.requireClientCertificate || base.IsSecurityTokenManagerRequired(); } protected override string OnGetConnectionGroupPrefix(HttpWebRequest httpWebRequest, SecurityTokenContainer clientCertificateToken) { System.Text.StringBuilder inputStringBuilder = new System.Text.StringBuilder(); string delimiter = "\0"; // nonprintable characters are invalid for SSPI Domain/UserName/Password if (this.RequireClientCertificate) { HttpsChannelFactory.SetCertificate(httpWebRequest, clientCertificateToken); X509CertificateCollection certificateCollection = httpWebRequest.ClientCertificates; for (int i = 0; i < certificateCollection.Count; i++) { inputStringBuilder.AppendFormat("{0}{1}", certificateCollection[i].GetCertHashString(), delimiter); } } return inputStringBuilder.ToString(); } void OnOpenCore() { if (this.requireClientCertificate && this.SecurityTokenManager == null) { throw Fx.AssertAndThrow("HttpsChannelFactory: SecurityTokenManager is null on open."); } } protected override void OnEndOpen(IAsyncResult result) { base.OnEndOpen(result); OnOpenCore(); } protected override void OnOpen(TimeSpan timeout) { base.OnOpen(timeout); OnOpenCore(); } internal SecurityTokenProvider CreateAndOpenCertificateTokenProvider(EndpointAddress target, Uri via, ChannelParameterCollection channelParameters, TimeSpan timeout) { if (!this.RequireClientCertificate) { return null; } SecurityTokenProvider certificateProvider = TransportSecurityHelpers.GetCertificateTokenProvider( this.SecurityTokenManager, target, via, this.Scheme, channelParameters); SecurityUtils.OpenTokenProviderIfRequired(certificateProvider, timeout); return certificateProvider; } static void SetCertificate(HttpWebRequest request, SecurityTokenContainer clientCertificateToken) { if (clientCertificateToken != null) { X509SecurityToken x509Token = (X509SecurityToken)clientCertificateToken.Token; request.ClientCertificates.Add(x509Token.Certificate); } } internal SecurityTokenContainer GetCertificateSecurityToken(SecurityTokenProvider certificateProvider, EndpointAddress to, Uri via, ChannelParameterCollection channelParameters, ref TimeoutHelper timeoutHelper) { SecurityToken token = null; SecurityTokenContainer tokenContainer = null; SecurityTokenProvider webRequestCertificateProvider; if (ManualAddressing && this.RequireClientCertificate) { webRequestCertificateProvider = CreateAndOpenCertificateTokenProvider(to, via, channelParameters, timeoutHelper.RemainingTime()); } else { webRequestCertificateProvider = certificateProvider; } if (webRequestCertificateProvider != null) { token = webRequestCertificateProvider.GetToken(timeoutHelper.RemainingTime()); } if (ManualAddressing && this.RequireClientCertificate) { SecurityUtils.AbortTokenProviderIfRequired(webRequestCertificateProvider); } if (token != null) { tokenContainer = new SecurityTokenContainer(token); } return tokenContainer; } void AddServerCertMappingOrSetRemoteCertificateValidationCallback(HttpWebRequest request, EndpointAddress to) { Fx.Assert(request != null, "request should not be null."); if (this.sslCertificateValidator != null) { request.ServerCertificateValidationCallback = this.remoteCertificateValidationCallback; } else { HttpTransportSecurityHelpers.AddServerCertMapping(request, to); } } [System.Diagnostics.CodeAnalysis.SuppressMessage(FxCop.Category.ReliabilityBasic, "Reliability104:CaughtAndHandledExceptionsRule", Justification = "The exception being thrown out comes from user code. Any non-fatal exception should be handled and translated into validation failure(return false).")] bool RemoteCertificateValidationCallback(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors) { Fx.Assert(sender is HttpWebRequest, "sender should be HttpWebRequest"); Fx.Assert(this.sslCertificateValidator != null, "sslCertificateAuthentidation should not be null."); try { this.sslCertificateValidator.Validate(new X509Certificate2(certificate)); return true; } catch (SecurityTokenValidationException ex) { FxTrace.Exception.AsInformation(ex); return false; } catch (Exception ex) { if (Fx.IsFatal(ex)) { throw; } FxTrace.Exception.AsWarning(ex); return false; } } class HttpsRequestChannel : HttpRequestChannel { SecurityTokenProvider certificateProvider; HttpsChannelFactory factory; public HttpsRequestChannel(HttpsChannelFactory factory, EndpointAddress to, Uri via, bool manualAddressing) : base(factory, to, via, manualAddressing) { this.factory = factory; } public new HttpsChannelFactory Factory { get { return this.factory; } } void CreateAndOpenTokenProvider(TimeSpan timeout) { if (!ManualAddressing && this.Factory.RequireClientCertificate) { this.certificateProvider = Factory.CreateAndOpenCertificateTokenProvider(this.RemoteAddress, this.Via, this.ChannelParameters, timeout); } } void CloseTokenProvider(TimeSpan timeout) { if (this.certificateProvider != null) { SecurityUtils.CloseTokenProviderIfRequired(this.certificateProvider, timeout); } } void AbortTokenProvider() { if (this.certificateProvider != null) { SecurityUtils.AbortTokenProviderIfRequired(this.certificateProvider); } } protected override IAsyncResult OnBeginOpen(TimeSpan timeout, AsyncCallback callback, object state) { TimeoutHelper timeoutHelper = new TimeoutHelper(timeout); CreateAndOpenTokenProvider(timeoutHelper.RemainingTime()); return base.OnBeginOpen(timeoutHelper.RemainingTime(), callback, state); } protected override void OnOpen(TimeSpan timeout) { TimeoutHelper timeoutHelper = new TimeoutHelper(timeout); CreateAndOpenTokenProvider(timeoutHelper.RemainingTime()); base.OnOpen(timeoutHelper.RemainingTime()); } protected override void OnAbort() { AbortTokenProvider(); base.OnAbort(); } protected override IAsyncResult OnBeginClose(TimeSpan timeout, AsyncCallback callback, object state) { TimeoutHelper timeoutHelper = new TimeoutHelper(timeout); CloseTokenProvider(timeoutHelper.RemainingTime()); return base.OnBeginClose(timeoutHelper.RemainingTime(), callback, state); } protected override void OnClose(TimeSpan timeout) { TimeoutHelper timeoutHelper = new TimeoutHelper(timeout); CloseTokenProvider(timeoutHelper.RemainingTime()); base.OnClose(timeoutHelper.RemainingTime()); } public IAsyncResult BeginBaseGetWebRequest(EndpointAddress to, Uri via, SecurityTokenContainer clientCertificateToken, ref TimeoutHelper timeoutHelper, AsyncCallback callback, object state) { return base.BeginGetWebRequest(to, via, clientCertificateToken, ref timeoutHelper, callback, state); } public HttpWebRequest EndBaseGetWebRequest(IAsyncResult result) { return base.EndGetWebRequest(result); } public override HttpWebRequest GetWebRequest(EndpointAddress to, Uri via, ref TimeoutHelper timeoutHelper) { SecurityTokenContainer clientCertificateToken = Factory.GetCertificateSecurityToken(this.certificateProvider, to, via, this.ChannelParameters, ref timeoutHelper); HttpWebRequest request = base.GetWebRequest(to, via, clientCertificateToken, ref timeoutHelper); this.factory.AddServerCertMappingOrSetRemoteCertificateValidationCallback(request, to); return request; } public override IAsyncResult BeginGetWebRequest(EndpointAddress to, Uri via, ref TimeoutHelper timeoutHelper, AsyncCallback callback, object state) { return new GetWebRequestAsyncResult(this, to, via, ref timeoutHelper, callback, state); } public override HttpWebRequest EndGetWebRequest(IAsyncResult result) { return GetWebRequestAsyncResult.End(result); } public override bool WillGetWebRequestCompleteSynchronously() { if (!base.WillGetWebRequestCompleteSynchronously()) { return false; } return (this.certificateProvider == null && !Factory.ManualAddressing); } internal override void OnWebRequestCompleted(HttpWebRequest request) { HttpTransportSecurityHelpers.RemoveServerCertMapping(request); } class GetWebRequestAsyncResult : AsyncResult { SecurityTokenProvider certificateProvider; HttpsChannelFactory factory; HttpsRequestChannel httpsChannel; HttpWebRequest request; EndpointAddress to; Uri via; TimeoutHelper timeoutHelper; SecurityTokenContainer tokenContainer; static AsyncCallback onGetBaseWebRequestCallback = Fx.ThunkCallback(new AsyncCallback(OnGetBaseWebRequestCallback)); static AsyncCallback onGetTokenCallback; public GetWebRequestAsyncResult(HttpsRequestChannel httpsChannel, EndpointAddress to, Uri via, ref TimeoutHelper timeoutHelper, AsyncCallback callback, object state) : base(callback, state) { this.httpsChannel = httpsChannel; this.to = to; this.via = via; this.timeoutHelper = timeoutHelper; this.factory = httpsChannel.Factory; this.certificateProvider = httpsChannel.certificateProvider; if (this.factory.ManualAddressing && this.factory.RequireClientCertificate) { this.certificateProvider = this.factory.CreateAndOpenCertificateTokenProvider(to, via, httpsChannel.ChannelParameters, timeoutHelper.RemainingTime()); } if (!GetToken()) { return; } if (!GetWebRequest()) { return; } base.Complete(true); } bool GetWebRequest() { IAsyncResult result = this.httpsChannel.BeginBaseGetWebRequest(to, via, tokenContainer, ref timeoutHelper, onGetBaseWebRequestCallback, this); if (!result.CompletedSynchronously) { return false; } this.request = this.httpsChannel.EndBaseGetWebRequest(result); this.factory.AddServerCertMappingOrSetRemoteCertificateValidationCallback(this.request, this.to); return true; } bool GetToken() { if (this.certificateProvider != null) { if (onGetTokenCallback == null) { onGetTokenCallback = Fx.ThunkCallback(new AsyncCallback(OnGetTokenCallback)); } IAsyncResult result = this.certificateProvider.BeginGetToken( timeoutHelper.RemainingTime(), onGetTokenCallback, this); if (!result.CompletedSynchronously) { return false; } OnGetToken(result); } return true; } static void OnGetBaseWebRequestCallback(IAsyncResult result) { if (result.CompletedSynchronously) return; GetWebRequestAsyncResult thisPtr = (GetWebRequestAsyncResult)result.AsyncState; Exception completionException = null; try { thisPtr.request = thisPtr.httpsChannel.EndBaseGetWebRequest(result); thisPtr.factory.AddServerCertMappingOrSetRemoteCertificateValidationCallback(thisPtr.request, thisPtr.to); } #pragma warning suppress 56500 // Microsoft, transferring exception to another thread catch (Exception e) { if (Fx.IsFatal(e)) { throw; } completionException = e; } thisPtr.Complete(false, completionException); } static void OnGetTokenCallback(IAsyncResult result) { if (result.CompletedSynchronously) return; GetWebRequestAsyncResult thisPtr = (GetWebRequestAsyncResult)result.AsyncState; Exception completionException = null; bool completeSelf; try { thisPtr.OnGetToken(result); completeSelf = thisPtr.GetWebRequest(); } #pragma warning suppress 56500 // Microsoft, transferring exception to another thread catch (Exception e) { if (Fx.IsFatal(e)) { throw; } completeSelf = true; completionException = e; } if (completeSelf) { thisPtr.Complete(false, completionException); } } void OnGetToken(IAsyncResult result) { SecurityToken token = this.certificateProvider.EndGetToken(result); if (token != null) { this.tokenContainer = new SecurityTokenContainer(token); } CloseCertificateProviderIfRequired(); } void CloseCertificateProviderIfRequired() { if (this.factory.ManualAddressing && this.certificateProvider != null) { SecurityUtils.AbortTokenProviderIfRequired(this.certificateProvider); } } public static HttpWebRequest End(IAsyncResult result) { GetWebRequestAsyncResult thisPtr = AsyncResult.End(result); return thisPtr.request; } } } } }