Streaming MTOM Attachments in WCF and Certificate Authentication - wcf

I'm looking for a definitive answer as to whether what I'm trying to do is supported or not.
Basically, I'm using WCF to stream large MTOM attachments (200 Mb), this works perfectly fine. The security requirements for the service is to use HTTPS and certificate-based authentication. I can run the service over HTTPS without any problems, but once I set IIS to “Accept client certificates” or “Require client certificates” (no change in the code), the following error is thrown (but only once the attachments get over about 80 Mb or so):
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:30:00'.
I found some resources, sorry can't find them now, that indicated that the failure is probably related to the fact that the incoming messages either cannot be digitally signed, or verified due to the streaming nature of the message contents. I believe the service would have to hash the whole message contents to verify the cert, but this can't be achieved because portions of the message are in transit while validation is trying to occur.
I've setup the message contract so that the body is a single Stream element, and the other elements are contained within the header:
<MessageContract()>
Public Class StreamAttachmentRequest
<MessageHeader(MustUnderstand:=True)>
Public Property AttachmentName As String
<MessageBodyMember(Order:=1)>
Public Property Attachment As Stream
End Class
The service configuration looks as follows:
<system.serviceModel>
<!-- BINDING -->
<bindings>
<basicHttpBinding>
<binding name="TestCaseBasicBinding"
messageEncoding="Mtom"
transferMode="StreamedRequest"
maxReceivedMessageSize="2147483647"
closeTimeout="00:30:00"
openTimeout="00:30:00"
receiveTimeout="00:30:00"
sendTimeout="00:30:00">
<security mode="Transport">
<transport clientCredentialType="None"></transport>
</security>
<readerQuotas maxDepth="32"
maxStringContentLength="8192"
maxArrayLength="16384"
maxBytesPerRead="4096"
maxNameTableCharCount="16384" />
</binding>
</basicHttpBinding>
</bindings>
<!-- BEHAVIORS -->
<behaviors>
<serviceBehaviors>
<!-- TEST CASE SECURE BEHAVIOR -->
<behavior name="TestCaseSecureBehavior">
<serviceMetadata httpsGetEnabled="true" httpGetEnabled="false" />
<serviceDebug includeExceptionDetailInFaults="true"/>
<serviceCredentials>
<serviceCertificate
storeLocation="LocalMachine"
storeName="My"
findValue="DistinguishedNameOfCert"
x509FindType="FindBySubjectDistinguishedName" />
<clientCertificate>
<authentication certificateValidationMode="ChainTrust"/>
</clientCertificate>
</serviceCredentials>
</behavior>
</serviceBehaviors>
</behaviors>
<!-- SERVICES -->
<services>
<service name="StreamingMutualAuthTestCase.Web.Service.TestCaseServiceImplementation"
behaviorConfiguration="TestCaseSecureBehavior">
<!-- SERVICE -->
<endpoint address=""
binding="basicHttpBinding"
bindingConfiguration="TestCaseBasicBinding"
contract="StreamingMutualAuthTestCase.Web.Service.ITestCaseService" />
<endpoint contract="IMetadataExchange" binding="mexHttpsBinding" address="mex" />
</service>
</services>
<serviceHostingEnvironment aspNetCompatibilityEnabled="true" multipleSiteBindingsEnabled="true" />
The client configuration looks like this:
<system.serviceModel>
<bindings>
<basicHttpBinding>
<binding name="BasicHttpBinding_ITestCaseService" closeTimeout="00:30:00"
openTimeout="00:30:00" receiveTimeout="00:30:00" sendTimeout="00:30:00"
maxReceivedMessageSize="2147483647" messageEncoding="Mtom"
transferMode="Streamed">
<readerQuotas maxDepth="32" maxStringContentLength="8192" maxArrayLength="16384"
maxBytesPerRead="4096" maxNameTableCharCount="16384" />
<security mode="Transport">
<transport clientCredentialType="Certificate" realm="" />
</security>
</binding>
</basicHttpBinding>
</bindings>
<!-- BEHAVIORS -->
<behaviors>
<endpointBehaviors>
<behavior name="SecureClientBehavior">
<clientCredentials>
<clientCertificate
storeLocation="LocalMachine"
storeName="My"
findValue="DistinguishedNameOfCert"
x509FindType="FindBySubjectDistinguishedName"/>
<serviceCertificate>
<authentication certificateValidationMode="ChainTrust"/>
</serviceCertificate>
</clientCredentials>
</behavior>
</endpointBehaviors>
</behaviors>
<client>
<endpoint address="https://test7/TestCaseService/TestCaseService.svc"
binding="basicHttpBinding"
bindingConfiguration="BasicHttpBinding_ITestCaseService"
contract="TestCaseService.ITestCaseService"
name="BasicHttpBinding_ITestCaseService"
behaviorConfiguration="SecureClientBehavior"/>
</client>
</system.serviceModel>
Once again, this will work just fine until I set IIS Client Certs to either Accept or Require.
Also, there is a 413 error in the IIS log...
2011-08-18 15:00:06 W3SVC1 10.39.8.111 POST /TestCaseService/TestCaseService.svc - 443 - 10.75.13.81 - - - test7 413 0 0
I've already designed an authentication service on top of my file upload service to work around the issues; but I'd really like to know if what I'm trying to do is 'do-able' or not.
Thanks a ton - Patrick

If you want to turn on client certificates in IIS you must do the same for your service (and client):
<security mode="Transport">
<transport clientCredentialType="Certificate" />
</security>
Your client must provide certificate to the proxy:
yourProxy.ClientCredentials.ClientCertificate.SetCertificate(...);
Also your server must trust these certificates so client certificates must be either issued by certification authority trusted by the server or they must be installed to LocalMachine\Trusted people store directly on the server.
WCF endpoints don't support "Accept client certificates" - you must either use client certificates or not.

Related

WCF Rest Service with optional client certificate

Is it possible to have an WCF Rest Webservice which excepts SSL Client Certificates and have the IIS SSL Setting to not set to "require SSL" and to anly "accept" client certificates and not "require" them?
I have the following config:
<system.serviceModel>
<services>
<service behaviorConfiguration="RestServiceBehaviour" name="PM.WCF.Service.PmRestService">
<endpoint address="" behaviorConfiguration="web" binding="webHttpBinding"
bindingConfiguration="StreamedRequestWebBinding" contract="PM.WCF.Service.IPmRestService" />
</service>
</services>
<bindings>
<webHttpBinding>
<binding name="StreamedRequestWebBinding"
bypassProxyOnLocal="true"
useDefaultWebProxy="false"
hostNameComparisonMode="WeakWildcard"
sendTimeout="10:15:00"
openTimeout="10:15:00"
receiveTimeout="10:15:00"
maxReceivedMessageSize="2147483647"
maxBufferSize="2147483647"
maxBufferPoolSize="2147483647"
transferMode="StreamedRequest">
<readerQuotas maxArrayLength="2147483647" maxStringContentLength="2147483647" />
<security mode="Transport">
<transport clientCredentialType="Certificate"/>
</security>
</binding>
</webHttpBinding>
</bindings>
<behaviors>
<endpointBehaviors>
<behavior name="web">
<webHttp />
</behavior>
</endpointBehaviors>
<serviceBehaviors>
<behavior name="RestServiceBehaviour">
<serviceMetadata httpsGetEnabled="true" />
</behavior>
</serviceBehaviors>
</behaviors>
<serviceHostingEnvironment multipleSiteBindingsEnabled="true" />
My problem is the following line:
<transport clientCredentialType="Certificate"/>
If i use this setting and IIS configured to accept, but not require client certificates, I get the following error:
The SSL settings for the service 'SslRequireCert' does not match those of the IIS 'SslNegotiateCert'.
Sadly setting
<transport clientCredentialType="None"/>
Does not work either. I'm pretty sure the browser/client does send it's certificate, but OperationContext.Current.ServiceSecurityContext.PrimaryIdentity.IsAuthenticated is False.
Is there any other way then to have two IIS Websites, one configured to require SSL and client certificates and one which does not?
Even if there is no way around it. How do I debug this in Visual Studio? Because the moment I require SSL client certificates, I can't just start the webservice. Visual Studio tries to access http://localhost/Foo.WCF.Service/debugattach.aspx and fails because of the missing client certificate.

configuring https endpoint with WCF self hosting

I have created basichttpendpoint with security mode="TransportWithMessageCredential" for my self hosted WCF service.
My server config has:
enter code here
<system.serviceModel>
<bindings>
<basicHttpBinding>
<binding name="BasicHttpBinding_IViewerManager" maxBufferSize="655360000" maxReceivedMessageSize="655360000" messageEncoding="Mtom" textEncoding="utf-8" transferMode="Buffered">
<readerQuotas maxDepth="32" maxStringContentLength="8192" maxArrayLength="16384" maxBytesPerRead="4096" maxNameTableCharCount="16384"/>
<security mode="TransportWithMessageCredential">
<message clientCredentialType="Certificate" />
</security>
</binding>
</basicHttpBinding>
</bindings>
<services>
<service behaviorConfiguration="SecureBehavior" name="Lumedx.ApolloLXPACS.ViewerServiceLibrary.ViewerManager">
<endpoint name="basicHTTP" address="https://localhost:5100/ViewerService" binding="basicHttpBinding" bindingConfiguration="BasicHttpBinding_IViewerManager" contract="Lumedx.ApolloLXPACS.ServiceContracts.IViewerManager"/>
<host>
<baseAddresses>
<add baseAddress="https://localhost:5100/ViewerService"/>
</baseAddresses>
</host>
</service>
</services>
<behaviors>
<endpointBehaviors>
<behavior name="HttpsBehavior">
</behavior>
</endpointBehaviors>
<serviceBehaviors>
<behavior name="SecureBehavior">
<serviceMetadata httpGetEnabled="false" httpsGetEnabled="true"/>
<serviceDebug includeExceptionDetailInFaults="true"/>
<dataContractSerializer maxItemsInObjectGraph="2147483647"/>
<serviceCredentials>
<serviceCertificate findValue="RootCATest"
storeLocation="LocalMachine"
storeName="My"
x509FindType="FindByIssuerName" />
</serviceCredentials>
</behavior>
</serviceBehaviors>
</behaviors>
</system.serviceModel>
my windows client config has:
<?xml version="1.0"?>
<configuration>
<appSettings>
<add key="priorityEndpoint1" value="basicHttpEndpoint"/>
<add key="maxCommunicationRetries" value="0"/>
</appSettings>
<system.serviceModel>
<bindings>
<basicHttpBinding>
<binding name="BasicHttpBinding_IViewerManager" maxBufferSize="655360000" maxReceivedMessageSize="655360000" messageEncoding="Mtom" textEncoding="utf-8" transferMode="Buffered">
<readerQuotas maxDepth="32" maxStringContentLength="8192" maxArrayLength="16384" maxBytesPerRead="4096" maxNameTableCharCount="16384"/>
<security mode="TransportWithMessageCredential">
<message clientCredentialType="Certificate" />
</security>
</binding>
</basicHttpBinding>
</bindings>
<client>
<endpoint name="basicHttpEndpoint" address="https://10.10.10.100:5100/ViewerService/" binding="basicHttpBinding" bindingConfiguration="BasicHttpBinding_IViewerManager" contract="Lumedx.ApolloLXPACS.ServiceContracts.IViewerManager" behaviorConfiguration="HttpsBehavior"/>
</client>
<behaviors>
<endpointBehaviors>
<behavior name="HttpsBehavior">
<clientCredentials>
<clientCertificate findValue="RootCATest"
storeLocation="LocalMachine"
storeName="My"
x509FindType="FindByIssuerName" />
</clientCredentials>
</behavior>
</endpointBehaviors>
</behaviors>
</system.serviceModel>
<
I have installed and configured certificate.
I am connecting my windows client with the self hosted WCF service with https end points. I captured network traffic using wireshark. All I see in the network traffic is TCP packets between the server and client. When I follow the TCP stream the message does not seem to be encrypted.
What am I doing wrong here?
Security mode TransportWithMessageCredential encrypts transport (SSL) and leaves message body not encrypted.
Encryption is made using your certificate.
If you see not encrypted data one of the following scenarios happend:
Traffic for some reason is not encrypted with ssl
Wireshark has access to your certificate and encrypted traffic using it.
Is this looks similar to what you did? http://trycatch.be/blogs/decaluwet/archive/2009/04/08/decrypting-ssl-traffic-using-wireshark.aspx
In any case you should be safe if transport level encryption is enable and your certificate is not compromised.

WCF service - data being rejected virtually instantly according to size despite large max values

I have a WCF webservice with 1 functioning web method that takes a single string parameter.
I have a WinForms app that references the WCF webservice and it is all running on .NET 4.0.
Problem is that when I call the web method with too much data for the string data (around 4MB) this debugging both client and server on my local machine (Win 7 Pro 64bit, IIS 7) I get the following error (inner exception, exception):
The request was aborted: The request
was canceled.
An error (The request was aborted: The
request was canceled.) occurred while
transmitting data over the HTTP
channel.
When running on the production server I get a different message (stupid I'd didn't copy it) saying something along the lines of the server may have aborted the connection or similar.
If I reduce the data to around half (2MB) it works fine. The frustrating thing is I've maxed all the config settings, reset IIS, rebooted my machine but still it remains.
The web service validation code gets hit, validates user as okay and then as soon as it exits the validation dumps the message above back to the client. The stack trace when in the validation method (my code) shows only .NET stuff so there is no more of my code to debug, 8(.
On the server side I have this in the web.config:
<system.serviceModel>
<serviceHostingEnvironment aspNetCompatibilityEnabled="true" multipleSiteBindingsEnabled="true"/>
<services>
<service name="MyNS.MyService" behaviorConfiguration="MyNS.MyServiceBehavior">
<endpoint binding="wsHttpBinding" bindingConfiguration="httpWithMessageSecurity" contract="MyNS..MyService">
<identity>
<dns value="localhost"/>
</identity>
</endpoint>
<endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange"/>
</service>
</services>
<behaviors>
<serviceBehaviors>
<behavior name="MyNS.MyServiceBehavior">
<serviceCredentials>
<serviceCertificate findValue="Server" storeLocation="LocalMachine" storeName="My" x509FindType="FindBySubjectName"/>
<userNameAuthentication userNamePasswordValidationMode="Custom" customUserNamePasswordValidatorType="MyNS.Validators.MyServiceUserValidator, MyNS./>
</serviceCredentials>
<serviceMetadata httpGetEnabled="true"/>
<serviceDebug includeExceptionDetailInFaults="true"/>
</behavior>
<behavior name="">
<serviceMetadata httpGetEnabled="true"/>
<serviceDebug includeExceptionDetailInFaults="false"/>
</behavior>
</serviceBehaviors>
</behaviors>
<bindings>
<wsHttpBinding>
<binding name="httpWithMessageSecurity"
maxReceivedMessageSize="2147483647"
maxBufferPoolSize="2147483647">
<readerQuotas
maxBytesPerRead="2147483647"
maxNameTableCharCount="2147483647"
maxArrayLength="2147483647"
maxStringContentLength="2147483647"/>
<security mode="Message">
<message clientCredentialType="UserName"/>
</security>
</binding>
</wsHttpBinding>
</bindings>
On the client side I have this in the app.config:
<system.serviceModel>
<bindings>
<wsHttpBinding>
<binding name="serviceHttpBinding"
maxBufferPoolSize="2147483647"
maxReceivedMessageSize="2147483647"
messageEncoding="Text"
textEncoding="utf-8"
sendTimeout="01:00:00"
closeTimeout="00:00:20"
openTimeout="00:00:20"
receiveTimeout="00:20:00">
<readerQuotas
maxBytesPerRead="2147483647"
maxNameTableCharCount="2147483647"
maxArrayLength="2147483647"
maxStringContentLength="2147483647"/>
<security mode="Message">
<message clientCredentialType="UserName" negotiateServiceCredential="true"
algorithmSuite="Default" />
</security>
</binding>
</wsHttpBinding>
</bindings>
<behaviors>
<endpointBehaviors>
<behavior name="clientServerBehaviour">
<clientCredentials>
<serviceCertificate>
<authentication certificateValidationMode="PeerOrChainTrust"/>
</serviceCertificate>
</clientCredentials>
</behavior>
</endpointBehaviors>
</behaviors>
<client>
<endpoint address="http://mymachine/TheServices/Services/MyService.svc"
binding="wsHttpBinding" bindingConfiguration="serviceHttpBinding"
contract="DirectorySubmitService.IDirectorySubmitService" name="WSHttpBinding_IDirectorySubmitService">
<identity>
<dns value="Server" />
</identity>
</endpoint>
</client>
EDIT 1:
After enabled debug symbols in VS, after my validation code in the WCF service completes I get the following exceptions:
System.Web.HttpException: Maximum Length Exceeded
Stack: System.Web.HttpRequest.GetEntireRawContent()
Then:
System.ServiceModel.CommunicationException: Maximum Length Exceeded
Stack: System.ServiceModel.Activation.HostedHttpRequestAsyncResult.GetInputStream()
Subsequent CommunicationExceptions are related to Channels etc.. but all are the same error message.
I all so checked the app pool in IIS that was set to 30 MB as a request limit so I have run out maximum limits I can increase so this message is a real slap in the face.
Any help would be much apprecianted.
It turns out that the httpRuntime web.config element that I'd put to set the request limit to 50MB was being ignored because the location path I'd specified was missing a folder level, i.e. location path="MyService.svc" instead of location path="Services/MyService.svc".
The net result being it was using the default 4MB limit which of cause gave the max length exceeded message because that was exactly the case.
Doh!

Authentication a WCF Request via Client Certificate over HTTPS

I've been struggling with the configuration for this blasted WCF service for the past week, and I'm slowing beginning to suspect that what I'm trying to do is just not possible, despite the documentation.
Quite simply, I want to have a WCF service require a client certificate (which the server will have in its cert store), and then access that identity with System.ServiceModel.ServiceSecurityContext. Additionally, this needs to use transport security.
Here's my server config:
<system.serviceModel>
<services>
<service behaviorConfiguration="requireCertificate" name="Server.CXPClient">
<endpoint address="" binding="wsHttpBinding" bindingConfiguration="wsHttpEndpointBinding" name="wsHttpEndpoint" contract="PartnerComm.ContentXpert.Server.ICXPClient" />
<endpoint address="mex" binding="wsHttpBinding" bindingConfiguration="wsHttpEndpointBinding" name="mexEndpoint" contract="IMetadataExchange" />
<host>
<baseAddresses>
<add baseAddress="https://localhost:8371/Design_Time_Addresses/Server/CXPClient/" />
</baseAddresses>
</host>
</service>
</services>
<behaviors>
<serviceBehaviors>
<behavior name="requireCertificate">
<serviceMetadata httpsGetEnabled="true" />
<serviceCredentials>
<serviceCertificate findValue="CyberdyneIndustries" storeLocation="LocalMachine" storeName="TrustedPeople" x509FindType="FindBySubjectName"/>
<clientCertificate>
<authentication certificateValidationMode="ChainTrust" trustedStoreLocation="LocalMachine" />
</clientCertificate>
</serviceCredentials>
</behavior>
</serviceBehaviors>
</behaviors>
<bindings>
<wsHttpBinding>
<binding name="wsHttpEndpointBinding" maxBufferPoolSize="5242880" maxReceivedMessageSize="5242880">
<readerQuotas maxDepth="32" maxStringContentLength="5242880" maxArrayLength="1073741824" maxBytesPerRead="4096" maxNameTableCharCount="16384" />
<security mode="Transport">
<transport clientCredentialType="Certificate" />
</security>
</binding>
</wsHttpBinding>
</bindings>
</system.serviceModel>
Here's my client config:
<system.serviceModel>
<bindings>
<wsHttpBinding>
<binding name="wsHttpEndpoint" closeTimeout="00:01:00" openTimeout="00:01:00"
receiveTimeout="00:10:00" sendTimeout="00:01:00" bypassProxyOnLocal="false"
transactionFlow="false" hostNameComparisonMode="StrongWildcard"
maxBufferPoolSize="524288" maxReceivedMessageSize="65536" messageEncoding="Text"
textEncoding="utf-8" useDefaultWebProxy="true" allowCookies="false">
<readerQuotas maxDepth="32" maxStringContentLength="8192" maxArrayLength="16384"
maxBytesPerRead="4096" maxNameTableCharCount="16384" />
<reliableSession ordered="true" inactivityTimeout="00:10:00"
enabled="false" />
<security mode="Transport">
<transport clientCredentialType="Certificate" />
</security>
</binding>
</wsHttpBinding>
</bindings>
<client>
<endpoint address="https://localhost:8371/Design_Time_Addresses/Server/CXPClient/"
binding="wsHttpBinding" bindingConfiguration="wsHttpEndpoint" behaviorConfiguration="ClientCertificateBehavior"
contract="ContentXPertServer.ICXPClient" name="wsHttpEndpoint" />
</client>
<behaviors>
<endpointBehaviors>
<behavior name="ClientCertificateBehavior">
<clientCredentials>
<clientCertificate x509FindType="FindBySubjectName" findValue="CyberdyneIndustries" storeLocation="LocalMachine" storeName="TrustedPeople" />
</clientCredentials>
</behavior>
</endpointBehaviors>
</behaviors>
</system.serviceModel>
The code all works perfectly when security mode='None' over http, but of course, there's no authentication, and nothing in System.ServiceModel.ServiceSecurityContext. I've tried dozens of variations on all of these elements, and it all ends up inevitably with the request throwing an exception "An existing connection was forcibly closed by the remote host".
I'm using a self-signed cert "CyberdyneIndustries", whose CA cert I've added to the trusted CA store. The cert checks out when I view it. I've gone through the hell of http namespace management, and solved those problems as well. It simply looks like WCF doesn't really support this...please tell me I'm wrong.
TIA.
Ultimately, I decided to try message security, to see if that would shed some light on the situation - it did, and I'm going to cut my losses and go with that. So, there's no definitive answer to this.
Implementing message security did, however, expose a BIG problem, and this may have been the root of the transport security problem. There is a piece of poison documentation from MSDN:
http://msdn.microsoft.com/en-us/library/ff650751.aspx
On this page, the command to create the self-signed cert is as follows:
makecert -sk MyKeyName -iv
RootCaClientTest.pvk -n
"CN=tempClientcert" -ic
RootCaClientTest.cer -sr currentuser
-ss my -sky signature -pe
The argument "signature" should instead be "exchange". Once I regenerated all my certs, message security started working. One big takeaway from all of this is that if you're wanting to implement transport security, get message security working first, because the error messages you get from the system are much more descriptive.
Does the SSL handshake succeed? Enable SChannel logging to troubleshoot the SSL layer. See this old KB article: How to enable Schannel event logging in IIS. Although is an KB for W2K and XP, the steps to enable SChannel logging are the same and still valid on newer systems. With the logging enabled you'll be able to determine why is SSL rejecting the certificate.
I know this is 3 years old, but for those who might still be interested...
I'm in the process of learning WCF (security among other things) and was able to get things working properly with netTcpBinding (presumably, this will work for WsHttpBindings as well) using Transport security mode with a clientCredentialType="Certificate" (and, protectionLevel="EncryptAndSign", though that wasn't germane to the issue).
I did encounter the force connection close error from the server-side too, but discovered I was missing one piece of configuration. It's all working now.
Here's my server-side config:
<configuration>
<system.serviceModel>
<services>
<service name="MyNamespace.MyService" behaviorConfiguration="MyServiceBehavior">
<endpoint address="net.tcp://localhost:9002/MyServer" binding="netTcpBinding" bindingConfiguration="TcpCertSecurity" contract="MyNamespace.IMyService" />
</service>
</services>
<behaviors>
<serviceBehaviors>
<behavior name="MyServiceBehavior">
<serviceCredentials>
<serviceCertificate findValue="MyServiceCert" storeLocation="LocalMachine" storeName="My" x509FindType="FindBySubjectName" />
<clientCertificate>
<authentication certificateValidationMode="PeerTrust"/>
</clientCertificate>
</serviceCredentials>
</behavior>
</serviceBehaviors>
</behaviors>
<bindings>
<netTcpBinding>
<binding name="TcpCertSecurity">
<security mode="Transport">
<transport clientCredentialType="Certificate" protectionLevel="EncryptAndSign" />
</security>
</binding>
</netTcpBinding>
</bindings>
</system.serviceModel>
</configuration>
And my client-side configuration:
<configuration>
<system.serviceModel>
<client>
<endpoint address="net.tcp://localhost:9002/MyServer" binding="netTcpBinding"
bindingConfiguration="TcpCertSecurity" contract="MyNamespace.IMyService"
behaviorConfiguration="MyServiceBehavior">
<identity>
<dns value="MyServiceCert" />
</identity>
</endpoint>
</client>
<bindings>
<netTcpBinding>
<binding name="TcpCertSecurity">
<security mode="Transport">
<transport clientCredentialType="Certificate" protectionLevel="EncryptAndSign" />
</security>
</binding>
</netTcpBinding>
</bindings>
<behaviors>
<endpointBehaviors>
<behavior name="MyServiceBehavior">
<clientCredentials>
<serviceCertificate>
<authentication certificateValidationMode="PeerTrust" />
</serviceCertificate>
<clientCertificate findValue="MyServiceCert" storeLocation="LocalMachine" storeName="TrustedPeople" x509FindType="FindBySubjectName" />
</clientCredentials>
</behavior>
</endpointBehaviors>
</behaviors>
</system.serviceModel>
</configuration>
I created a certificate chain for the server (self-signed Trusted Root certificate + a certificate built using that root) using the technique described here and stored both the Root cert and child cert in the certificate store of my server host machine. And, finally, I imported that server certificate + public key into the cert store on my client host machine (in LocalMachine/TrustedPeople).
WsHttpBinding DOES support certificate authentication for transport security.
There can be a few things wrong:
Did you add both certificates to your store? CyberdyneIndustries as well a CA that you used to sign it? CA should be in "Trusted Root Certification Authorities"
Also, i've done this self-hosted, never in Visual Studio Dev server. Try to host your service in IIS at least. I am not sure if VS Dev server supports certificates.
Try to turn off service authentication. So the client doesn't have to authenticate the service. I don't know if you want this in your app or not but just for testing so we can rule that out
<behavior name="ClientCertificateBehavior">
<clientCredentials>
<clientCertificate x509FindType="FindBySubjectName" findValue="CyberdyneIndustries" storeLocation="LocalMachine" storeName="TrustedPeople" />
<serviceCertificate>
<authentication certificateValidationMode="None"/>
</serviceCertificate>
</clientCredentials>

WCF and Kerberos Authentication

I have followed numerous msdn articles and the codeplex guidance but cannot get WCF to work with Kerberos authentication and delegation and would appreciate a little help.
Setup
I have the WCF service in an IIS website on a remote machine
IIS 6.0 on Windows 2003 R2 - SP 2
The SPN for the machine has been added (http/myserver && http/myserver:8080)
An AD account has been created for the IIS app pool
The AD account has the setting, allow delegation (for Kerberos), set to true
I am using Brian Booth's debug site on 8080 and the site passes all requirements for Kerberos delegation. The debug IIS site has anonymous authentication off, and Integrated Windows authentication on.
I have mirrored these settings to the site hosting the WCF service.
Web Service - Web Config (Original)
<system.serviceModel>
<bindings>
<wsHttpBinding>
<binding name="WsHttpBindingConfig">
<security>
<message negotiateServiceCredential="true" />
</security>
</binding>
</wsHttpBinding>
</bindings>
<services>
<service behaviorConfiguration="ServiceBehavior" name="Service">
<endpoint address=""
binding="wsHttpBinding"
bindingConfiguration="WsHttpBindingConfig"
contract="IService">
<identity>
<servicePrincipalName value="http/myserver" />
<dns value="" />
</identity>
</endpoint>
<endpoint address="mex"
binding="mexHttpBinding"
contract="IMetadataExchange" />
</service>
</services>
<behaviors>
<serviceBehaviors>
<behavior name="ServiceBehavior">
<serviceMetadata httpGetEnabled="true"/>
<serviceDebug includeExceptionDetailInFaults="true"/>
<serviceAuthorization
impersonateCallerForAllOperations="true" />
</behavior>
</serviceBehaviors>
</behaviors>
</system.serviceModel>
Web Service - Web Method
[OperationBehavior(Impersonation = ImpersonationOption.Required)]
public string GetCurrentUserName()
{
string name = WindowsIdentity.GetCurrent().Name;
return name;
}
Client App - App Config
<system.serviceModel>
<bindings>
<wsHttpBinding>
<binding name="WSHttpBinding_IService"
... />
...
<security mode="Message">
<transport clientCredentialType="Windows"
proxyCredentialType="None"
realm="" />
<message clientCredentialType="Windows"
negotiateServiceCredential="true"
algorithmSuite="Default"
establishSecurityContext="true" />
</security>
</binding>
</wsHttpBinding>
</bindings>
<client>
<endpoint address="http://myserver/Service.svc"
binding="wsHttpBinding"
bindingConfiguration="WSHttpBinding_IService"
contract="KerberosService.IService"
name="WSHttpBinding_IService">
<identity>
<servicePrincipalName value="http/myserver" />
</identity>
</endpoint>
</client>
</system.serviceModel>
Application Error
The following error occurs when my test application, a WinForms app, tries to call the web method:
"The HTTP request is unauthorized with
client authentication scheme
'Anonymous'. The authentication header
received from the server was
'Negotiate,NTLM'."
Event Log
The following error is in the event log:
Exception:
System.ServiceModel.ServiceActivationException:
The service '/Service.svc' cannot be
activated due to an exception during
compilation. The exception message
is: Security settings for this service
require 'Anonymous' Authentication but
it is not enabled for the IIS
application that hosts this service.
Which I don't understand. The whole point of this service is to not allow anonymous authentication, every user/request must be authenticated using Kerberos tickets, then passing them through to other machines.
How should I configure this WCF service for Kerberos authentication and delegation?
Revision 1
After reading this SO question I removed the metadata endpoint. This has not resolved the issue.
Revision 2
After more researching I found a few posts suggesting to change wsHttpBinding to basicHttpBinding. The modification to that portion of the web.config has been included below, and the service endpoint has been updated to refer to that binding.
Web Service - Web Config (Revised)
<basicHttpBinding>
<binding name="basicBindingConfig">
<security mode="TransportCredentialOnly">
<transport clientCredentialType="Windows"
proxyCredentialType="Windows"
realm="" />
</security>
</binding>
</basicHttpBinding>
Client App - App Config (Revised)
<!-- ... -->
<security mode="TransportCredentialOnly">
<transport clientCredentialType="Windows"
proxyCredentialType="Windows"
realm="" />
<message clientCredentialType="UserName"
algorithmSuite="Default" />
</security>
<!-- ... -->
Error (Revised)
The current error looks like it contains a Kerberos authentication header.
The HTTP request is unauthorized with
client authentication scheme
'Negotiate'. The authentication header
received from the server was
'Negotiate SOMEHUGESCARYKEYHERE
For me the current setup does work:
On the Server:
<system.serviceModel>
<bindings>
<wsHttpBinding>
<binding name="wsHttpBindingConf" useDefaultWebProxy="true"/>
</wsHttpBinding>
</bindings>
<services>
<service behaviorConfiguration="returnFaults" name="Epze.BusinessLayer.ZeitManager">
<endpoint binding="wsHttpBinding" bindingConfiguration="wsHttpBindingConf" contract="Epze.Contract.IZeitManager"/>
<endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange"/>
</service>
</services>
<behaviors>
<serviceBehaviors>
<behavior name="returnFaults">
<serviceMetadata httpGetEnabled="true"/>
<serviceDebug includeExceptionDetailInFaults="true"/>
<serviceAuthorization impersonateCallerForAllOperations="true"/>
</behavior>
</serviceBehaviors>
</behaviors>
</system.serviceModel>
Set the following attribute on all methods for the WCF:
[OperationBehavior(Impersonation = ImpersonationOption.Required)]
On the Client:
<system.serviceModel>
<bindings>
<wsHttpBinding>
<binding name="WSHttpBinding_IZeitManager" closeTimeout="00:01:00" openTimeout="00:01:00" receiveTimeout="00:10:00" sendTimeout="00:01:00" bypassProxyOnLocal="false" transactionFlow="false" hostNameComparisonMode="StrongWildcard" maxBufferPoolSize="524288" maxReceivedMessageSize="65536" messageEncoding="Text" textEncoding="utf-8" useDefaultWebProxy="true" allowCookies="false">
<readerQuotas maxDepth="32" maxStringContentLength="8192" maxArrayLength="16384" maxBytesPerRead="4096" maxNameTableCharCount="16384"/>
<reliableSession ordered="true" inactivityTimeout="00:10:00" enabled="false"/>
<security mode="Message">
<transport clientCredentialType="Windows" proxyCredentialType="None" realm=""/>
<message clientCredentialType="Windows" negotiateServiceCredential="true" algorithmSuite="Default" establishSecurityContext="true"/>
</security>
</binding>
</wsHttpBinding>
</bindings>
<behaviors>
<endpointBehaviors>
<behavior name="Delegation">
<clientCredentials>
<windows allowedImpersonationLevel="Delegation" />
</clientCredentials>
</behavior>
</endpointBehaviors>
</behaviors>
<client>
<endpoint address="http://server.mydomain.net/ePZEsvc/ZeitManager.svc" binding="wsHttpBinding" bindingConfiguration="WSHttpBinding_IZeitManager"
contract="External.Epze.IZeitManager" name="WSHttpBinding_IZeitManager" behaviorConfiguration="Delegation">
<identity>
<servicePrincipalName value="HOST/localhost"/>
</identity>
</endpoint>
</client>
</system.serviceModel>
HTH, Sven
Something that I notice: the client and server config don't seem to agree on security mode.
In the original section, you have <security>..... in the web.config (omitted the mode="message"), and <security mode="Message"> on the client side.
After your edit, it seems that the client side is unchanged, but the server (web.config) now contains <security mode="TransportCredentialOnly">.
The question really is: can you guarantee that there's only ever going to be one network leg between the client and the server being called? I.e. is this behind a corporate firewall? In that case, I would recommend netTcp binding with <security mode="Transport"> on both ends.
If that's not the case, then you're ok with either wsHttpBinding (which supports more security and reliability features, but is slower and "heavier") or basicHttpBinding. In that case, you would have to use <security mode="Message"> on both ends, and authenticate the service with a certificate (so that the service and client have a common "secret" which to use for encryption).
I would try to leave out the impersonation parts out for the beginning and just get the basic communication and mutual authentication between service and client up and running first - once that's in place, you can start adding the impersonation bits to it, and you can always fall back on a known configuration which works.
David Sackstein has a great series of blog posts explaining the five security scenarios that industry guru Juval Lowy has identified (in his Programming WCF book - the WCF Bible) as the most common and most useful - in order to limit the number of possible combinations of parameters you might want to tweak. One of them is a "Internet" scenario which would probably apply here, if your service is outward facing.
Marc
You need to specify a behaviorConfiguration in your client config. SVCUtil does not auto generate. This resolved my issue and I am now successfully using Kerberos. It was a mission though!
<client>
<endpoint address="..."
binding="customBinding" bindingConfiguration="..."
contract="..." name="..." behaviorConfiguration="ImpersonationBehavior" />
</client>
<behaviors>
<endpointBehaviors>
<behavior name="ImpersonationBehavior">
<clientCredentials>
<windows allowedImpersonationLevel="Impersonation"/> </clientCredentials>
</behavior>
</endpointBehaviors>
</behaviors>
You should try your initial configuration and make sure to set the IIS to be anonymous and windows authentication at the same time.The reason is when you are using wsHttpBinding default security is message security and there is no transport security defined unless you want to do https. SO Clr states that it needs anonymous authentication turned-on on the IIS.