WCF w/ mutual authentication - c#

I'm trying to help troubleshoot a third-party self-hosted web service written in WCF that requires mutual authentication. The issue is that web service is returning a 401 Unauthorized. I've read several articles about how to write the client and server pieces in WCF to use mutual authentication, but I still have the following questions:
Once the client sends the client certificate, how does the WCF service determine whether or not to accept it as being an authenticated endpoint with access to the given resource. Does the certificate only need to be trusted by the service being able to find the certificate's root CA in the certificate store as a trusted root CA, or is there some mechanism that maps the certificate to a list of entities that have been identified as allowed to access the resource?
Normally when I've seen mutual authentication used, in Wireshark I see the server respond to a Client Hello and Certificate Request with a Server Hello, Certificate, and Certificate Request. However, in the case I am troubleshooting, I do not see the server send a Certificate Request. I believe the client is sending its certificate in encrypted data, but I'm not able to decrypt the data to see it. Is there a way to force the WCF service to send a Certificate Request with the Server Hello?
The configuration file has the following:
<bindings>
<webHttpBinding>
<binding name="webHttpTransportSecurity">
<security mode="Transport">
<transport clientCredentialType="Certificate" />
</security>
</binding>
</webHttpBinding>
</bindings>
I believe that's enough to indicate the client should authenticate with a certificate, but now how the service decides if that certificate is one that's allowed?

Your binding definition looks correct. The certificate is defined in the endpointBehaviors. It is a little hard to follow, because it is split-up in separate XML groups.
Here is an example of what is working for my projects:
<client>
<endpoint address="(address to our)WebService.svc"
behaviorConfiguration="behaviorConfig"
binding="webHttpTransportSecurity"
bindingConfiguration="bindingConfig"
contract="((your contract name))"
name="mainEndPoint">
<identity>
<certificateReference findValue="CN=((cert name like blah.blah.blah-blah.blah)), OU=((lookup)), O=((lookup))"
storeLocation="LocalMachine"
storeName="TrustedPeople"
x509FindType="FindBySubjectDistinguishedName" />
</identity>
</endpoint>
</client>
<bindings>
<!-- you already have a good looking binding (above) -->
</bindings>
<behaviors>
<serviceBehaviors ...etc />
<endpointBehaviors>
<behavior name="behaviorConfig">
<clientCredentials>
<clientCertificate findValue="CN=((short name)), OU=((lookup)), O=((lookup))"
storeLocation="LocalMachine"
storeName="My"
x509FindType="FindBySubjectDistinguishedName" />
<serviceCertificate>
<defaultCertificate findValue="CN=((same content from certificateReference above)), OU=((lookup)), O=((lookup))"
storeLocation="LocalMachine"
storeName="TrustedPeople"
x509FindType="FindBySubjectDistinguishedName" />
<authentication certificateValidationMode="PeerTrust"
revocationMode="NoCheck"
trustedStoreLocation="LocalMachine" />
</serviceCertificate>
</clientCredentials>
<callbackTimeouts />
</behavior>
</endpointBehaviors>
</behaviors>

Related

WCF Certificate Based Authentication Issue. Service is Getting authenticated with an invalid Certificate

I'm a beginner with WCF services. Trying to implement a Certificated based authentication on a WCF service and facing an issue. The service expects a specific Certificate from the calling client. The server throws an authentication error if the client is not passing any certificate. But at the same time, the service call is passing authentication with any certificates provided by the client(The service suppose to authenticate if the client provides a specific certificate).
Following is the code snippet of server config :
Service Config :
<bindings>
<wsHttpBinding>
<binding name="MyWsHttpBinding" maxReceivedMessageSize="2147483647" receiveTimeout="00:30:00">
<readerQuotas maxStringContentLength="2147483647" maxBytesPerRead="2147483647" maxDepth="2147483647" maxArrayLength="2147483647"/>
<security mode="Transport">
<transport clientCredentialType="None" proxyCredentialType="None"/>
<message clientCredentialType="Certificate" algorithmSuite="Default"/>
</security>
</binding>
</wsHttpBinding>
</bindings>
<serviceBehaviors>
<behavior name="MyServiceBehavior">
<serviceCredentials>
<clientCertificate>
<authentication certificateValidationMode="ChainTrust" />
</clientCertificate>
<serviceCertificate findValue="e616ebcd940951794736624acc6484802018c8d4" storeLocation="LocalMachine" storeName="My" x509FindType="FindByThumbprint" />
</serviceCredentials>
<serviceMetadata httpsGetEnabled="true" httpGetEnabled="true"/>
<CustomBehaviorExtensionElement/>
<serviceDebug includeExceptionDetailInFaults="true"/>
</behavior>
</serviceBehaviors>
<endpointBehaviors>
<behavior name="MyEndpointBehavior">
<MySchemaValidator validateRequest="True" validateReply="False">
<schemas>
<add location="App_Data\model-service.xsd"/>
</schemas>
</MySchemaValidator>
</behavior>
</endpointBehaviors>
<services>
<service name="MyService" behaviorConfiguration="MyServiceBehavior">
<endpoint binding="wsHttpBinding" bindingConfiguration="MyWsHttpBinding" contract="MyExchangeService" behaviorConfiguration="MyEndpointBehavior" bindingNamespace="http://www.mycompany.com/exchange/"/>
<endpoint contract="IMetadataExchange" binding="mexHttpsBinding" address="mex" name="mex"/>
</service>
</services>
The cause of the problem is the security mode you use is transport, so only the following code works:
<transport clientCredentialType="None" proxyCredentialType="None"/>
The following message settings have no effect:
<message clientCredentialType="Certificate" algorithmSuite="Default"/>
Change the value in transport to certificate, you can also download the wcf demo on the official website, there are examples of related certificate verification, and there are tutorials corresponding to the demo.
I see that the certificate validation mode used in your code is ChainTrust.
<clientCertificate>
<authentication certificateValidationMode="ChainTrust" />
</clientCertificate>
As mentioned in Microsoft Docs, using ChainTrust means -
The certificate is valid if the chain builds to a certification authority in the trusted root store
Meaning, the client need not send certificate with the exact same thumbprint as mentioned in your service web.config. Infact, any certificate whose Root / Intermediate Certification Authority is present in your VM's Trusted Root Store will pass validation.
To make sure that the client is able to use only a specific certificate to authenticate to your service, change ChainTrust to PeerTrust and add the certificate to the trusted people store on your VM's Certificate Store (certmgr).
<authentication certificateValidationMode="PeerTrust" />
References:
MS Docs - Working with certificates in WCF
Authentication element in web.config
More info on Certificate Chain of Trust

WCF showing 403 Forbidden using SSL and client certificates

We are having a problem with WCF - we are getting the error below when trying to connect. There are tons of suggestions for various configurations, having tried them all we could use some help.
We are using HTTPS for transport security, using a real SSL certificate that we got from GoDaddy. It seems to be installed and working properly when we browse to web pages on the site. With no authentication, we can connect properly to our WCF service.
For authentication, we are using client certificates that we created ourselves. These client certificates were working fine before we switched to HTTPS, when we were using message security with a self-signed server certificate (which was a pain because we had to get the clients to install the server certificate).
Error
The HTTP request was forbidden with client authentication scheme 'Anonymous'.
Inner exception: The remote server returned an error: (403) Forbidden
Server configuration file
<system.serviceModel>
<bindings>
<wsHttpBinding>
<binding name="NewBinding0">
<security mode="Transport">
<transport clientCredentialType="Certificate" />
</security>
</binding>
</wsHttpBinding>
</bindings>
<services>
<service name="WcfService1.Service1">
<endpoint address="" binding="wsHttpBinding" bindingConfiguration="NewBinding0" contract="WcfService1.IService1" />
</service>
</services>
<behaviors>
<serviceBehaviors>
<behavior name="">
<serviceMetadata httpGetEnabled="true" httpsGetEnabled="true" />
<serviceDebug includeExceptionDetailInFaults="true" />
<serviceCredentials>
<clientCertificate>
<authentication certificateValidationMode="PeerTrust" />
</clientCertificate>
<serviceCertificate findValue="....." x509FindType="FindByThumbprint" />
</serviceCredentials>
</behavior>
</serviceBehaviors>
</behaviors>
<protocolMapping>
<add scheme="https" binding="wsHttpBinding" bindingConfiguration="NewBinding0" />
</protocolMapping>
<serviceHostingEnvironment aspNetCompatibilityEnabled="true" multipleSiteBindingsEnabled="true" />
</system.serviceModel>
Client configuration file
<system.serviceModel>
<behaviors>
<endpointBehaviors>
<behavior name="NewBehavior0">
<clientCredentials>
<clientCertificate findValue="customuser1"
storeName="TrustedPeople" x509FindType="FindBySubjectName" />
</clientCredentials>
</behavior>
</endpointBehaviors>
</behaviors>
<bindings>
<wsHttpBinding>
<binding name="NewBinding0">
<security mode="Transport">
<transport clientCredentialType="Certificate" />
</security>
</binding>
</wsHttpBinding>
</bindings>
<client>
<endpoint address="https://www.insertoursitename.com/WcfService1/Service1.svc"
behaviorConfiguration="NewBehavior0" binding="wsHttpBinding"
bindingConfiguration="NewBinding0" contract="ServiceReference1.IService1"
name="wsHttpBinding_IService1" />
</client>
</system.serviceModel>
My problem was very similar to yours, and i'll describe my scenario before answering the question.
Created a simple WCF service (using custom binding, but that's irrelevant).
Created a self-signed RootCA using makecert, and generated two certs tempCertServer.cer used for SSL encryption, configure IIS to require https, etc.
--> Tested this part, worked ok from the Browser from a different computer.
The second cert tempCertClient.cer was used as a client-cert to be presented to IIS, configure IIS to Require client-cert, etc. --> Tested this part from a browser (best to use IE since you can easily clear SSL state). I get a prompt to choose a client cert, but never connects, the error is exactly the same as per the question:
"The HTTP request was forbidden with client authentication scheme 'Anonymous'. Inner exception: The remote server returned an error: (403) Forbidden."
Replaced tempCertClient with a proper cert (from a known CA), there was no issue, connection was established and WCF page shown; No matter what i tried with the self-signed client cert, always getting above error.
Wasted a whole day++ trying various settings, reading blogs on registry changes, placing the cert server-side under different cert stores, changing config file settings, etc, with no resolution.
The answer was very simple, inspect the LocalComputer\Trusted Root Certification Authorities server-side, and remove any NON-ROOT CA's (i.e. those that should not be there, IssuedTo NOT EQUALS IssuedBy)
The client-cert itself did not need to be installed on the server, only a Root CA that can validate it has to be installed in LocalComputer\Trusted Root Certification Authorities server-side.

Add additional SSL behavior for WCF endpoint within Azure Web Role

Our azure web application already uses https port 443 with our site certificate, we have a WCF service within this webrole that has an https endpoint using our cert to authenticate (1-way ssl), this same service needs an additional https endpoint supporting 2-way auth using our cert and the third party's cert. We have uploaded the cert, updated the service definition file, and added an endpoint that we are hoping will work, but in testing we are getting the error: The SSL settings for the service 'SslRequireCert' does not match those of the IIS 'None'.
so the endpoint that does work is: https://environemnt.application.com/Services/Service.svc
the endpoint that generates the error: https://environment.application.com/Services/Service.svc/twa
The key requirement is that it is https, port 443, at the above new endpoint, without altering the SSL behavior of the rest of the role, I have seen entries to change the IIS configuration or use the role editor to add an Https Input endpoint, but as we already have an Https Input endpoint on port 443 using our site cert I don't want to alter/affect the whole role.
If it is helpful the service is a WCF Service which consumes an Mtom encoded soap 1.2 message
here are the new values that we have entered, what else do I need?
<behaviors>
<serviceBehaviors>
<behavior name="SSLServiceBehavior">
<serviceMetadata httpsGetEnabled="true" />
<serviceDebug includeExceptionDetailInFaults="true" />
<dataContractSerializer maxItemsInObjectGraph="2147483647" />
</behavior>
</serviceBehaviors>
<endpointBehaviors>
<behavior name="OneWayAuthEndpointBehavior">
</behavior>
<behavior name="TwoWayAuthEndpointBehavior">
<endpointDiscovery enabled="true"></endpointDiscovery>
<clientCredentials>
<clientCertificate findValue="thumprint..." storeLocation="LocalMachine" storeName="CertificateAuthority" x509FindType="FindByThumbprint" />
</clientCredentials>
</behavior>
</endpointBehaviors>
</behaviors>
<services>
<service behaviorConfiguration="SSLServiceBehavior" name="Service">
<endpoint address="" behaviorConfiguration="OneWayAuthEndpointBehavior"binding="wsHttpBinding" bindingConfiguration="HttpsMtomOneWay" contract="ITestService" />
<endpoint address="twa" behaviorConfiguration="TwoWayAuthEndpointBehavior" binding="wsHttpBinding" bindingConfiguration="HttpsMtomTwoWay" contract="ITestService"/>
</services>
<bindings>
<wsHttpBinding>
<binding name="HttpsMtomOneWay" messageEncoding="Mtom">
<security mode="Transport">
<transport clientCredentialType="None" />
</security>
</binding>
<binding name="HttpsMtomTwoWay" messageEncoding="Mtom">
<security mode="Transport">
<transport clientCredentialType="Certificate" />
</security>
</binding>
</wsHttpBinding>
</bindings>
thank you much
Fixed through these steps:
Added serviceCredentials.serviceCertificate (cert details for our cert) to the service behavior
Eliminated Endpoint behavior definitions
Changed the HttpsMtomTwoWay binding to securityMode=Message
Now the message handlers handle the authentication exchange and external cert validation, then pass on to the transport endpoint, and we did not need to mess with the site wide SSL or endpoint settings. Tested and verified with numerous 3rd parties.

Is TransportWithMessageCredential without certificate secure enough for a WCF service?

I have developed a WCF self-hosted service, for which I have two basic security requirements as it will be accessed over the Internet:
The transport layer should prevent tampering and sniffing, especially the retrieval of authentication credentials. This is what SSL does, but from what I have seen setting up SSL requires the installation of certificates (except maybe through this hack that uses plain certificate files), which I prefer not to have to do.
The authentication layer should consist of a username/password validator.
I configured my service to use:
<security mode="TransportWithMessageCredential">
<message clientCredentialType="UserName" />
<transport clientCredentialType="Basic" />
</security>
Even if the transport layer is HTTP (not HTTPS), does this make WCF create another security layer that is equivalent to SSL? If not, what is the difference in terms of security strength?
Also, is there any way to secure the meta data endpoint without using a SSL certificate (not essential but would be appreciated)?
Here is my full configuration code for the self-hosted service:
<?xml version="1.0"?>
<configuration>
<startup><supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.0"/></startup>
<system.serviceModel>
<services>
<service name="MyService">
<host>
<baseAddresses>
<add baseAddress = "http://localhost:8000/Services" />
</baseAddresses>
</host>
<endpoint address ="MyService" binding="wsHttpBinding" contract="IMyService">
<identity>
<dns value="localhost"/>
</identity>
</endpoint>
<endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange"/>
</service>
</services>
<bindings>
<wsHttpBinding>
<binding name="Binding1" maxReceivedMessageSize="2147483647">
<security mode="TransportWithMessageCredential">
<message clientCredentialType="UserName" />
<transport clientCredentialType="Basic" />
</security>
</binding>
</wsHttpBinding>
</bindings>
<behaviors>
<serviceBehaviors>
<behavior>
<serviceMetadata httpGetEnabled="True"/>
<serviceCredentials>
<userNameAuthentication userNamePasswordValidationMode="Custom" customUserNamePasswordValidatorType="CR.Common.Services.CustomValidator, Common" />
</serviceCredentials>
</behavior>
</serviceBehaviors>
</behaviors>
</system.serviceModel>
</configuration>
Thank you!
By default, all secure WCF bindings (like wsHttpBinding) will encrypt and sign messages.
SSL mandatory use a certificate, and the hack in the link you give is hacking wcf, not SSL. Because without SSL WCF forbid the use of the basicHttpBinding (which send xml in clear) and UserNamePasswordValidator, because in this case anyone that intercept the message can get the username/password.
With WSHttpBinding you could avoid SSL and put the security on the message level.
I strongly advise you to read this article, especially the Service Credentials and Negotiation chapter:
To support mutual authentication and message protection, services must
provide credentials to the caller. When transport security is used
(SSL), service credentials are negotiated through the transport
protocol. Service credentials for message security can also be
negotiated when Windows credentials are used; otherwise a service
certificate must be specified
With the UserNamePasswordValidator, you must configure a certificate on the server to allow the client the sign and encrypt each message (using the certificate's public key).
If you were using Windows authentication, it'll not be needed.
Why are you so worried about certificate ?

WCF Certificate Authentication with Service Only (No Client Cert)

I am currently using a netTcpBinding with Windows authentication (program written in C#). I will be moving away from the domain authentication (adding new clients that won't be on the domain) and am looking to set up a certificate security with username/pass authentication. From what I've been reading so far, I don't necessarily need a client certificate (which is good; I won't be able to install the service's certificate on every client). My thinking is the along the same lines as navigating to a secure website with a certificate from a trusted CA; it recognizes it's trusted and doesn't ask any questions or give any hassle, it just accepts the certificate!
So far I have the service certificate set up (we have a wildcard cert from GoDaddy), however I can't figure out what changes I have to make to the app.config file(s) to not require the client certificate.
Service app.config:
<serviceBehaviors>
<behavior name="">
<serviceMetadata httpGetEnabled="false" httpsGetEnabled="false" />
<serviceDebug includeExceptionDetailInFaults="false" />
<serviceCredentials>
<clientCertificate>
<authentication certificateValidationMode="None" revocationMode="NoCheck" />
</clientCertificate>
<serviceCertificate findValue="*.xxxxxx.com"
storeLocation="LocalMachine"
storeName="TrustedPublisher"
x509FindType="FindBySubjectName" />
</serviceCredentials>
</behavior>
</serviceBehaviors>
Client app.config:
<security mode="Transport">
<transport clientCredentialType="Certificate" protectionLevel="EncryptAndSign" />
<message clientCredentialType="UserName" />
</security>
I'm aware I'll have to set up a custom validator for the username portion, but I figure one step at a time. Thanks, and let me know if you need further details.
change clientCredentialType to None. Also do this on the server config.

Categories

Resources