The scenario I am working on involves an ASP.NET web application communicating with a self-hosted WCF service with a netTcpBinding. The two components are hosted on separate machines. The web server is a stand-alone machine in a DMZ with no trust relationship with the application server.
I want the communication to be secured at transport level. As I understand if both the client and the app server have suitable X509 certificates installed, they can authenticate to each other, and the data exchange can also be encrypted.
I have provisioned the a web server with a ClientAuthentication certificate, and the app server with a ServerAuthentication certificate. In each case, the certificates are stored in the LocalMachine\My store. I have ensured that Private Keys are available for all certificates, and that the app pool identity and the BUILTIN\LOCAL identities (on the web and app server respectively) have full access to the private keys. I have also copied the public key part of the certificates (i.e. .cer files) to the LocalMachine\TrustedPeople store on the opposite server.
The configuration looks like this:
App server
<services>
<service behaviorConfiguration="DefaultServiceBehavior" name="Evs.Application.Services.XXXXXProvider">
<endpoint address="net.tcp://localhost:8000/Evs.Application.Services/XXXXXServiceCert"
binding="netTcpBinding" bindingConfiguration="SecureCertificateNetTcpBinding" contract="Evs.Application.Contracts.IXXXXXProvider"
behaviorConfiguration="endpointBehavior"/>
</service>
</services>
<behaviors>
<serviceBehaviors>
<behavior name="DefaultServiceBehavior">
<serviceDebug includeExceptionDetailInFaults="true" />
<serviceCredentials>
<serviceCertificate storeLocation="LocalMachine" storeName="My" x509FindType="FindByThumbprint" findValue="xxxxxxxxxxxxxx"/>
</serviceCredentials>
</behavior>
</serviceBehaviors>
</behaviors>
<bindings>
<binding name="SecureNetTcpBinding">
<security mode="Transport" />
</binding>
<binding name="SecureCertificateNetTcpBinding">
<security mode="Transport">
<transport clientCredentialType="Certificate" />
</security>
</binding>
</netTcpBinding>
</bindings>
Web server
<client>
<endpoint address="net.tcp://app-ext-01.prod.mydomain.com:8000/Evs.Application.Services/XXXXXServiceCert"
behaviorConfiguration="secureEndPointBehaviour" binding="netTcpBinding"
bindingConfiguration="SecureCertificateNetTcpBinding" contract="Evs.Application.Contracts.IXXXXXProvider"
name="XXXXXProvider"/>
<client>
<behaviors>
<endpointBehaviors>
<behavior name="secureEndPointBehaviour">
<clientCredentials>
<clientCertificate findValue= "xxxxxxxxxxxxxxxxxxx"
storeLocation="LocalMachine" storeName="My"
x509FindType="FindByThumbprint"/>
</clientCredentials>
</behavior>
</endpointBehaviors>
</behaviors>
<bindings>
<netTcpBinding>
<binding name="SecureCertificateNetTcpBinding">
<security mode="Transport">
<transport clientCredentialType="Certificate" />
</security>
</binding>
</netTcpBinding>
</bindings>
In the staging environment that I have for testing this, a call to the service fails with the exception below (retrieved from server-side logs)
System.ServiceModel.CommunicationException: The socket connection was aborted. This could be caused by an error processing your message or a receive timeout being exceeded by the remote host, or an underlying network resource issue. Local socket timeout was '00:00:59.9840000'. ---&gt; System.IO.IOException: The read operation failed, see inner exception. ---&gt;
System.ServiceModel.CommunicationException: The socket connection was aborted. This could be caused by an error processing your message or a receive timeout being exceeded by the remote host, or an underlying network resource issue. Local socket timeout was '00:00:59.9840000'. ---&gt; System.Net.Sockets.SocketException: An existing connection was forcibly closed by the remote host
at System.Net.Sockets.Socket.Receive(Byte[] buffer, Int32 offset, Int32 size, SocketFlags socketFlags)
at System.ServiceModel.Channels.SocketConnection.ReadCore(Byte[] buffer, Int32 offset, Int32 size, TimeSpan timeout, Boolean closing)
--- End of inner exception stack trace ---
at System.ServiceModel.Channels.SocketConnection.ReadCore(Byte[] buffer, Int32 offset, Int32 size, TimeSpan timeout, Boolean closing)
at System.ServiceModel.Channels.SocketConnection.Read(Byte[] buffer, Int32 offset, Int32 size, TimeSpan timeout)
at System.ServiceModel.Channels.ConnectionStream.Read(Byte[] buffer, Int32 offset, Int32 count)
at System.Net.FixedSizeReader.ReadPacket(Byte[] buffer, Int32 offset, Int32 count)
at System.Net.Security._SslStream.StartFrameHeader(Byte[] buffer, Int32 offset, Int32 count, AsyncProtocolRequest asyncRequest)
at System.Net.Security._SslStream.StartReading(Byte[] buffer, Int32 offset, Int32 count, AsyncProtocolRequest asyncRequest)
at System.Net.Security._SslStream.ProcessRead(Byte[] buffer, Int32 offset, Int32 count, AsyncProtocolRequest asyncRequest)
--- End of inner exception stack trace ---
at System.Net.Security._SslStream.ProcessRead(Byte[] buffer, Int32 offset, Int32 count, AsyncProtocolRequest asyncRequest)
at System.Net.Security.SslStream.Read(Byte[] buffer, Int32 offset, Int32 count)
at System.ServiceModel.Channels.StreamConnection.Read(Byte[] buffer, Int32 offset, Int32 size, TimeSpan timeout)
--- End of inner exception stack trace ---
Can anyone suggest what I need to do to this configuration to get it working?
I believe you're missing:
Server-Side
<serviceCredentials>
<clientCertificate>
<authentication certificateValidationMode="PeerTrust" />
</clientCertificate>
</serviceCredentials>
Client-Side
<clientCredentials>
<serviceCertificate>
<authentication certificateValidationMode="PeerTrust" />
</serviceCertificate>
</clientCredentials>