e79aa3c0ed
Former-commit-id: a2155e9bd80020e49e72e86c44da02a8ac0e57a4
560 lines
23 KiB
C#
560 lines
23 KiB
C#
//------------------------------------------------------------
|
|
// 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<TChannel> : HttpChannelFactory<TChannel>
|
|
{
|
|
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<ClientCredentials>();
|
|
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<T>()
|
|
{
|
|
if (typeof(T) == typeof(IChannelBindingProvider))
|
|
{
|
|
return (T)(object)this.channelBindingProvider;
|
|
}
|
|
|
|
return base.GetProperty<T>();
|
|
}
|
|
|
|
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<IAuthorizationPolicy> 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<TChannel>.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<IRequestChannel>)(object)this, address, via, ManualAddressing);
|
|
}
|
|
else
|
|
{
|
|
return (TChannel)(object)new ClientWebSocketTransportDuplexSessionChannel((HttpChannelFactory<IDuplexSessionChannel>)(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<TChannel>.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<IRequestChannel> factory;
|
|
|
|
public HttpsRequestChannel(HttpsChannelFactory<IRequestChannel> factory, EndpointAddress to, Uri via, bool manualAddressing)
|
|
: base(factory, to, via, manualAddressing)
|
|
{
|
|
this.factory = factory;
|
|
}
|
|
|
|
public new HttpsChannelFactory<IRequestChannel> 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<IRequestChannel> 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 // [....], 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 // [....], 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<GetWebRequestAsyncResult>(result);
|
|
return thisPtr.request;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|