Pregunta

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?

¿Fue útil?

Solución

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".

Otros consejos

Username and certificate both on transport level

Not an answer to this question, but the question title brought me here, so maybe others also:
I needed a Soap11-binding with both certificate (for the gateway) and username on transport level (for the application). (Both parts were maintained by different teams and different suppliers at the customer site.)

Despite the name a customBinding with UserNameOverTransport sends the username&password in the Soap-header (message-level), not in the http-header (transport-level), leading to this inner FaultException in SecurityRequestChannel.ProcessReply, originating from the application:

FaultException: MustUnderstand headers: [{http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd}Security] are not understood.

And using basicHttpBinding with <security mode="TransportCredentialOnly"> leads to:

The provided URI scheme 'https' is invalid; expected 'http'.
Parameter name: via

After a lot of trial and error, it turned out this can be done by combining configuration and code.

Example of certificate over transport by configuration

  <basicHttpBinding>
    <binding name="myBasicHttpBindingForTransportCertificate">
      <security mode="Transport">
        <transport clientCredentialType="Certificate" />
      </security>
    </binding>
  </basicHttpBinding>

  <endpointBehaviors>
    <behavior name="myCertificateBehavior">
      <clientCredentials>
        <clientCertificate findValue="my certificate subject" x509FindType="FindBySubjectName" storeLocation="LocalMachine" storeName="My" />
      </clientCredentials>
    </behavior>

(I dynamically loaded it, using this answer.)

Example of username&password over transport by code

using (new OperationContextScope(client.InnerChannel))
{
    …
    var httpRequestProperty = new HttpRequestMessageProperty();
    httpRequestProperty.Headers[System.Net.HttpRequestHeader.Authorization] =
           "Basic " + Convert.ToBase64String(System.Text.Encoding.ASCII.GetBytes($"{username}:{password}"));
    OperationContext.Current.OutgoingMessageProperties[HttpRequestMessageProperty.Name] = httpRequestProperty;

(Thanks to this old answer to "WCF TransportCredentialOnly not sending username and password".)

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top