3

I'm trying to make a WCF4 service hosted on IIS which will use X.509 certificates for message security and SSL with required mutual authentication for transport security (project specification requires the use of WS-Security AND SSL, AFAIK only one is usually the way to go, but never mind).

I made my TestRootCA and used it to issue a server certificate (localhost) and a client certificate (TestUser). I first wanted to establish and test transport security only, so I configured IIS to use https, configured SSL certificate (localhost cert that I made), and configured IIS to require a client certificate from clients. I then modified service web.config, made the corresponding svcutil.conf and ran svcutil that successfully made client proxy class and app.config for my test WinForms app. I set the certificate when creating proxy client by calling:

var client = new ServiceClient();
client.ClientCredentials.ClientCertificate.SetCertificate(System.Security.Cryptography.X509Certificates.StoreLocation.CurrentUser, System.Security.Cryptography.X509Certificates.StoreName.My, System.Security.Cryptography.X509Certificates.X509FindType.FindBySubjectDistinguishedName, "CN=TestUser");
MessageBox.Show(client.GetData(42));

So far so good. But when I modify service's web.config to use TrasportWithMessageCredential (instead of Transport) and specify "Certificate" as the clientCredentialType for message security I get HTTP 403 - The HTTP request was forbidden with client authentication scheme 'Anonymous', even though I specified "Certificate".

My final web.config looks like this:

<system.serviceModel>
    <bindings>
        <wsHttpBinding>
            <binding name="wsHttpEndpointBinding">
                <security mode="TransportWithMessageCredential">
                    <transport clientCredentialType="Certificate"/>
                    <message clientCredentialType="Certificate"/>
                </security>
            </binding>
        </wsHttpBinding>
    </bindings>
    <services>
        <service behaviorConfiguration="serviceBehavior" name="Service">
            <endpoint binding="wsHttpBinding" bindingConfiguration="wsHttpEndpointBinding" name="wsHttpEndpoint" contract="IService" />
            <endpoint address="mex" binding="wsHttpBinding" bindingConfiguration="wsHttpEndpointBinding" name="mexEndpoint" contract="IMetadataExchange" />
        </service>
    </services>
    <behaviors>
        <serviceBehaviors>
            <behavior name="serviceBehavior">
                <serviceMetadata httpsGetEnabled="true" />
                <serviceDebug includeExceptionDetailInFaults="true" />
            </behavior>
        </serviceBehaviors>
    </behaviors>
</system.serviceModel>

and my client app.conf:

<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="TransportWithMessageCredential">
                    <transport clientCredentialType="Certificate"/>
                    <message clientCredentialType="Certificate"/>
                </security>
            </binding>
        </wsHttpBinding>
    </bindings>
    <client>
        <endpoint address="https://localhost/WCFTestService/Service.svc" binding="wsHttpBinding" bindingConfiguration="wsHttpEndpoint" contract="IService" name="wsHttpEndpoint" />
    </client>
</system.serviceModel>

Any ideas?

EDIT:

I've managed to get it working by changing IIS SSL settings for Client certificate from require to accept. It seems that when I introduce message security using certificates over transport security using SSL, the client uses the certificate for message signing but not for mutual authentication over SSL (it tries to log on as anonymous even though it has a certificate assigned).

Not sure why this is happening, I'd like some comment from an expert :).

Boris B.
  • 4,933
  • 1
  • 28
  • 59

2 Answers2

2

As stated in the question, I got it working by changing IIS SSL settings for Client certificates from require to accept. Then in the service entry point I programmaticaly check the remote user's certificate (if its not null and valid).

Boris B.
  • 4,933
  • 1
  • 28
  • 59
1

You must specify the certificates to use to encrypt the message in the behavior section since these could be different as the ones used to establish the https channel

it would look like this in the server

<system.serviceModel> 
 <behaviors>     
    <serviceBehaviors>     
        <behavior name="serviceBehavior">
      <serviceCredentials>
        <serviceCertificate findValue="ServerCertificate"
                            storeLocation="CurrentUser"
                            storeName="My"
                            x509FindType="FindByIssuerName" />
        <clientCertificate>
          <certificate findValue ="ClientCertificate"
                       storeLocation="CurrentUser"
                            storeName="My"
                            x509FindType="FindByIssuerName"/>
          <authentication certificateValidationMode ="PeerTrust"/>
        </clientCertificate>
      </serviceCredentials>
            <serviceMetadata httpsGetEnabled="true" />     
            <serviceDebug includeExceptionDetailInFaults="true" />     
        </behavior>     
    </serviceBehaviors>     
</behaviors>   
</system.serviceModel> 

and do the same in the client, that would look like this

<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="TransportWithMessageCredential">        
                    <transport clientCredentialType="Certificate"/>        
                    <message clientCredentialType="Certificate"/>        
                </security>        
            </binding>        
        </wsHttpBinding>        
    </bindings>
 <behaviors>      
  <endpointBehaviors>        
    <behavior name="ClientCredentialsBehavior">
      <clientCredentials>            
        <clientCertificate x509FindType="FindBySubjectName"
                           findValue="ClientCertificate"
                           storeLocation="CurrentUser"
                           storeName="My"/>
        <serviceCertificate>
          <defaultCertificate x509FindType="FindBySubjectName"
                              findValue="ServerCertificate"
                              storeLocation="CurrentUser"
                              storeName="My"/>
        </serviceCertificate>
      </clientCredentials>
    </behavior>
  </endpointBehaviors>
</behaviors>

    <client>        
        <endpoint address="https://localhost/WCFTestService/Service.svc" binding="wsHttpBinding" bindingConfiguration="wsHttpEndpoint" contract="IService" name="wsHttpEndpoint" behaviorConfiguration="ClientCredentialsBehavior" />        
    </client>        
</system.serviceModel>

Off course, you have to set the right location and name of the certificates, in the server and client.

I hope this still helps

DkAngelito
  • 1,147
  • 1
  • 13
  • 30
  • Haven't tried this, but even if it would work, the need to specify a *client* certificate in the *server* config is a deal breaker. The server does not know client certificate in advance (and there are many different clients), it uses chain trust (if the isssuer of client cert is valid then the client cert is accepted and used as identification). – Boris B. Jul 20 '12 at 08:10
  • This fixed the error I was getting "The HTTP request was forbidden with client authentication scheme 'Anonymous'". I only had the server certificates specified. Thanks – Nanook Oct 17 '13 at 16:40