wcfsslx509certificatews-securitytransport-security

WCF 4 - TransportWithMessageCredential using X.509 certificates for transport and message security


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 :).


Solution

  • 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).