Search code examples
c#wcfsecurityclient-certificates

WCF Client Certificate AND UserName Credentials forbidden


I'm having issues getting a WCF client to connect to a server that requires a client certificate and also a username / password.

I have verified that using JUST the client certificate works using this:

        var binding = new WSHttpBinding(SecurityMode.Transport);
        binding.Security.Transport = new HttpTransportSecurity();
        binding.Security.Transport.ClientCredentialType = HttpClientCredentialType.Certificate;

        var ea = new EndpointAddress(EndpointUrl);

        using (var p = new ServiceReference1.AAAClient(binding, ea))
        {
            try
            {
                p.ClientCredentials.ClientCertificate.SetCertificate(
                        System.Security.Cryptography.X509Certificates.StoreLocation.CurrentUser,
                        System.Security.Cryptography.X509Certificates.StoreName.My,
                        System.Security.Cryptography.X509Certificates.X509FindType.FindBySubjectName,
                        CertificateSubject);

                var x = p.RequiresClientCertificateOnly();
                Console.WriteLine("Status: " + x.status);
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.ToString());
            }

            Console.WriteLine();
        }

The next logical step was to add the username / password parts, but it comes back with 403 unauthorised. I know the credentials and the certificate are valid as I have used fiddler in the middle to provide the client certificate and username / password and it works fine, just when using all through WCF it doesnt seem to work, or use send both the client certificate and the username / password.

The code trying to use both is below (tried with WSHttpBinding and BasicHttpBinding - same result for both):

        var binding = new BasicHttpBinding(BasicHttpSecurityMode.TransportWithMessageCredential);
        binding.Security.Transport.ClientCredentialType = HttpClientCredentialType.Certificate;
        binding.Security.Message.ClientCredentialType = BasicHttpMessageCredentialType.UserName;

        var ea = new EndpointAddress(EndpointUrl);

        using (var p = new ServiceReference1.PingPortTypeClient(binding, ea))
        {
            try
            {
                p.ClientCredentials.ClientCertificate.SetCertificate(
                        System.Security.Cryptography.X509Certificates.StoreLocation.CurrentUser,
                        System.Security.Cryptography.X509Certificates.StoreName.My,
                        System.Security.Cryptography.X509Certificates.X509FindType.FindBySubjectName,
                        CertificateSubject);

                p.ClientCredentials.UserName.UserName = Username;
                p.ClientCredentials.UserName.Password = Password;

                var x = p.pingDatabase();
                Console.WriteLine("Status: " + x.status);
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.ToString());
            }

            Console.WriteLine();

When I goto the URL directly in a browser - I get the error:

HTTP Error 403.7 - Forbidden The page you are attempting to access requires your browser to have a Secure Sockets Layer (SSL) client certificate that the Web server recognizes.

This to suggests that the certificate is not being sent in the second example above as it is the same error (403) being received.

Can someone please help me with the configuration for WCF to provide the client certificate AND the username / password? I have trawled the web, and the 'similar questions' on here have not helped at all. Getting very frustrate by it now - what am i missing?


Solution

  • You have to use a custom binding if you want both username (message level) and certificate (transport level). For example:

            <customBinding>
                <binding name="NewBinding0">
                    <textMessageEncoding messageVersion="Default" />
                    <security authenticationMode="UserNameOverTransport">
                        <secureConversationBootstrap />
                    </security>
                    <httpsTransport requireClientCertificate="true" />
                </binding>
            </customBinding>
    

    The value of messageVersion depends if you want to use WSHttp or BasicHttp. Currently I've set it to "Default" which is WSHttp, for Basic set it to "Soap11".