3

I am developing a WCF service based on a vendor's implementation to receive data from them. I have a working version of this service using one way SSL with the client using our server certificate. Because of a recent change to their implementation, I now need to authenticate their client with a certificate instead of username and password.

They develop in Java and I have no control over the client or the way they generate the certificate. They do not support the WSHttpBinding so I have to use the BasicHttpBinding. Their certificate is self-signed and will unfortunately be that way in production. I tried using Transport for the security and according to our server admin had their certificate installed appropriately on our server (I do not have access to our IIS server). I believe IIS was having an issue with the certificate because it is self-signed so I moved away from that implementation.

After a lot of research I determined that it would probably only work if I validated their certificate in the service itself by getting it from the OperationContext.Current.ServiceSecurityContext.AuthorizationContext.ClaimSets[0]. This is my first WCF service so I believe I have it set up correctly but am getting the following error when testing from my WCF test client: The private key is not present in the X.509 certificate. When I have the vendor test from their client they are getting An error occurred when verifying security for the message.

Do I have something configured incorrectly? Is this implementation supported?

Service Web.Config:

<?xml version="1.0"?>
    <configuration>
        <system.serviceModel>
                 <behaviors>
                <serviceBehaviors>
                    <behavior name="FileReceiverServiceBehavior">
                        <useRequestHeadersForMetadataAddress/>
                        <serviceMetadata httpsGetEnabled="true"/>
                    </behavior>
                </serviceBehaviors>
            </behaviors>
            <bindings>
                <basicHttpBinding>
                    <binding name="FileReceiverServiceBinding">
                        <security mode="TransportWithMessageCredential">
                            <message clientCredentialType="Certificate"/>
                        </security>
                        <readerQuotas maxStringContentLength="2147483647"/>
                    </binding>
                </basicHttpBinding>
            </bindings>
            <services>
                <service behaviorConfiguration="FileReceiverServiceBehavior" name="FileReceiverService.FileReceiverService">
                    <endpoint address="" binding="basicHttpBinding" bindingConfiguration="FileReceiverServiceBinding" contract="FileReceiverServiceSoap" bindingNamespace="http://www.openuri.org/" />
                    <endpoint address="mex" binding="mexHttpsBinding" contract="IMetadataExchange"/>
                </service>
            </services>
        </system.serviceModel>
    </configuration>

My WCF Test Client Config:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <system.serviceModel>
    <behaviors>
      <endpointBehaviors>
        <behavior name="test">
          <clientCredentials>
            <clientCertificate findValue="Users"
                               x509FindType="FindBySubjectName"
                               storeLocation="LocalMachine"
                               storeName="My"/>
          </clientCredentials>
        </behavior>
      </endpointBehaviors>
    </behaviors>
    <bindings>
      <basicHttpBinding>
        <binding name="BasicHttpBinding_FileReceiverServiceSoap" closeTimeout="00:01:00"
          openTimeout="00:01:00" receiveTimeout="00:10:00" sendTimeout="00:01:00"
          allowCookies="false" bypassProxyOnLocal="false" hostNameComparisonMode="StrongWildcard"
          maxBufferSize="65536" maxBufferPoolSize="524288" maxReceivedMessageSize="65536"
          messageEncoding="Text" textEncoding="utf-8" transferMode="Buffered"
          useDefaultWebProxy="true">
          <readerQuotas maxDepth="32" maxStringContentLength="8192" maxArrayLength="16384"
            maxBytesPerRead="4096" maxNameTableCharCount="16384" />
          <security mode="TransportWithMessageCredential">
            <transport clientCredentialType="None" proxyCredentialType="None"
              realm="" />
            <message clientCredentialType="Certificate" algorithmSuite="Default" />
          </security>
        </binding>
      </basicHttpBinding>
    </bindings>
    <client>
      <endpoint address="https://mydomain.com/vendor/FileReceiverService.svc"
        binding="basicHttpBinding" bindingConfiguration="BasicHttpBinding_FileReceiverServiceSoap"
        contract="ServiceReference1.FileReceiverServiceSoap" name="BasicHttpBinding_FileReceiverServiceSoap" behaviorConfiguration="test" />
    </client>
  </system.serviceModel>
</configuration>

Update:

The vendor sent me the correct certificate. It is a valid certificate issued by a commercial CA. When I view the certificate locally I can see the chain to the trusted root and the status shows 'OK'.

If I use security mode="Transport" and transport clientCredentialType="Certificate" I get: The HTTP request was forbidden with client authentication scheme 'Anonymous'

If I use security mode="TransportWithMessageCredential" and message clientCredentialType="Certificate" I get: The private key is not present in the X.509 certificate.

I think I may need to use strictly Transport so that it is interoperable with the Java client so I am focusing on the "The HTTP request was forbidden with client authentication scheme 'Anonymous'" error. Is that an issue with IIS settings or is it just not able to correctly validate the certificate when it gets to the server? The certificate is in Trusted People and I tried no client certificateValidationMode but also PeerTrust, ChainTrust and PeerOrChainTrust. Any help would be greatly appreciated.

Update2:

I am focusing on getting this to work with the following change to my original server configuration and matching change to my test client:

<security mode="Transport">
  <transport clientCredentialType="Certificate"/>

I still get: The HTTP request was forbidden with client authentication scheme 'Anonymous'. I found an explanation here that basically confirms that the client certificate could not be validated by the server. How do I have our server administrators configure this application in IIS and install the certificate so that this works correctly?

user1769388
  • 43
  • 1
  • 4
  • I guess you just need to install the client certificate in your trusted store on the IIS server. Since its a self signed certificate you might encounter an exception thrown by IIS that it cannot validate the certificate. You can try the below setting in your serviceBehaviour that does not validate the chaintrust of the client certificate: – Rajesh Oct 24 '12 at 09:31
  • @Rajesh Thank you for the reply. I tried that setting with security mode = Transport and Transport clientCredentialType = Certificate and received the following error: "The HTTP request was forbidden with client authentication scheme 'Anonymous'." This is the error I was receiving with that setup without your suggested setting change. I may have a solution. The vendor may have sent me the wrong certificate. I am testing with the "correct" certificate to see if it works. – user1769388 Oct 24 '12 at 19:10
  • @Rajesh I am getting the same errors with the correct certificate. Please see my update for full information. – user1769388 Oct 26 '12 at 16:01
  • @user1769388 I do not see where you are adding your custom validation in the web.config (you mention that up top). [Check out MSDN on custom cert validation](http://msdn.microsoft.com/en-us/library/ms733806.aspx) – iMortalitySX Oct 26 '12 at 16:22
  • @user1769388 Also, private key not present error might indicate a problem in the choice of the "algorithmSuite". Try to set it specifically on both sides, some certs do not support all different suites. I had to set mine to DES only first and move up from there. A good way to test this is to set a pin (or password) on the cert, it should prompt you when access the signature (through the client), if it does not then your sign algorithm is not supported by that cert. – iMortalitySX Oct 26 '12 at 16:24
  • @iMortalitySX I was trying to do the custom validation as part of the service using `System.IdentityModel.Claims` and `System.IdentityModel.Selectors` and the following code: `var cert = ((X509CertificateClaimSet)OperationContext.Current.ServiceSecurityContext.AuthorizationContext.ClaimSets[0]).X509Certificate; if (cert.Thumbprint != CertificateThumbprint)` Will it not work in this case? I was thinking it would based on the comment on the first answer [here](http://stackoverflow.com/questions/4991882/access-client-certificate-properties-from-wcf-service) – user1769388 Oct 26 '12 at 18:31
  • @iMortalitySX I will try setting "algorithmSuite". If that ends up working for my test client with their certificate, in theory it should work with their client even though I do not know their implementation, correct? – user1769388 Oct 26 '12 at 18:37
  • @user1769388 Er, no, to actually do custom validation you will need to extend some base classes [Check out this CodeProject](http://www.codeproject.com/Articles/33872/Custom-Authorization-in-WCF) for that info. But I don't think you were trying to go that far, were you? – iMortalitySX Oct 26 '12 at 18:43
  • @iMortalitySX I added a custom validator but am now getting "Failed to open System.ServiceModel.ServiceHost". I am still looking into this. I also tried every algorithmSuite option and received the private key not present error for each one. I was not sure about setting a password on the cert since I did not create it. Can I do that with makecert? Is that part of it required for the algorithmSuite testing to work? Note: we do not have a private key for this cert. It does not have the key icon in MMC. That makes sense though, right? They would not send us their public and private key? – user1769388 Oct 29 '12 at 14:40
  • @user1769388 Hmm... I do not believe that will work. You will need some sort of private key to sign the messages from the client, and a corrisponding public key to read the signed bits. Also, setting a password is just a suggestion since it would give you an indication of when the key is being accessed, but if you do not have a private key in the cert, then no it obviously doesn't exist. WCF wont work with just public keys, unless you ONLY do SSL, no client credentials. – iMortalitySX Oct 29 '12 at 16:09
  • @user1769388 The question of if you should have both is a matter of configuration, if you only encrypt from server (SSL) then yes, public is all you need, but if you end up setting up service credentials and have to sign messages from service, then you server actually needs a private key to sign with as well, and each client needs a public key matching that (more complicated but secure). – iMortalitySX Oct 29 '12 at 16:10

1 Answers1

3

After a long discussion in our comments, I would point you to the article Nine simple steps to X.509 certificates in WCF. I know it is just a link, but it is a very long article.

Seems that your issue is actually that your clients do not have a certificate that you can authenticate them with. In the scheme that you are shooting for, each client will need to have a private key that identifies them, and the server/service needs to have the corrisponding public key to verify the signature (at least installed in the key store).

Your current setup seems that each client has the public key for the server as a trusted server. So you can get them to connect and encrypt over SSL, and the client will trust the server, but the server actually doesn't know who the client is, or at least can't authenticate them without something to identify them. Your previous way, of username and password, is how they were identified before, but now with x.509 certificates, each person that had a username-password combo will need a unique certificate with a private key, in order to do the authentication.

Then, you will need to either map those users to a windows/LDAP account for easy administration and access control, or you will need to implement a custom validator (and possibly IIdentity, principal, and serivce credentials) in order to validate the private certificates and "log in" the user.

I hope this has been a bit more helpful to you. Here is one more on x509 custom auth and val

EDIT: In response to Update2, try setting the IIS site/application to "IGNORE User Certificates" under the SSL settings, and see if you get a different error, and vis-versa. Chances are if you have it set to require, but do not have ASP compatibility mode on in code, IIS is not passing the IIdentity through, and unless you have some sort of authentication scheme set up in IIS, it wont do anything for you. If you set this to ignore, you can quickly set up a custom validator and see the certificates come through, on which you can then build the chain and create some sort of authorization on them.

iMortalitySX
  • 1,478
  • 1
  • 9
  • 23
  • I came across your first link early on when trying to set this up. The problem is, he is creating the service, client and both certificates. I am developing the service based on the WSDL I received from our trading partner so they can consume it from their Java client. They do not support the scenario where I create a client certificate for them to use. They only support giving me a certificate of theirs to validate with the incoming request after they validate our server certificate. – user1769388 Oct 29 '12 at 21:04
  • Your third paragraph is basically correct, other than they will only have one client for a web app, passing their company certificate. With that in mind, is there any way to set up the SSL with our server certificate and then do authentication with their certificate? As I said in my update, I think I will probably have to use Transport security for interoperability. Is there no way to have IIS validate their certificate if we have it installed in our store? – user1769388 Oct 29 '12 at 21:27
  • Changing IIS to ignore but leaving my service gives me _The SSL settings for the service 'SslRequireCert' does not match those of the IIS 'Ssl'_. IIS is not passing it through but I am trying to get IIS set up to do the authentication because I recently verified that they are expecting the authentication to occur during the handshake and not actually passing the certificate with the request. I think I also realized (which I should have sooner) that I cannot test this properly with their certificate since it does not have their private key and the handshake will fail. – user1769388 Nov 01 '12 at 16:57
  • Thank you for all of your help. Our server admins decided that this was not required for this service. – user1769388 Nov 14 '12 at 14:49