How to call web service using client certificates / mutual authentication?

17,362

Finally!

This question made me try using X509Certificate2 (note the 2 in class name). I used it with a PKCS#12 keystore only containing a single certificate. So here is what I did:

Create the keystore file

I used Java's keytool for this. After creating a self-signed certificate, it needed to be exported to the desired format:

keytool -importkeystore -srckeystore keystore.jks \
        -srcstoretype JKS \
        -destkeystore keystore.pfx \
        -deststoretype PKCS12

Note: Exporting the certificate to a file and using it directly does not work.

Add client certificate to proxy

Dim proxy As New SimpleFromLocal.simple
Dim cred As New NetworkCredential("usr", "passwd")
Dim cert As New X509Certificate2(certFile, "passwd")
proxy.Credentials = cred
proxy.ClientCertificates.Add(cert)

Console.WriteLine(proxy.helloWorld())

And that's it. The same setup fails using X509Certificate for whatever reasons.

Share:
17,362
TPete
Author by

TPete

Updated on June 04, 2022

Comments

  • TPete
    TPete almost 2 years

    I'm building a web service client using vb .net. The web service is secured with ssl, basic authentication and also requires client certificates. So I created a web reference in Visual Studio and provided the credentials:

    Dim cred As New System.Net.NetworkCredential("usr", "passwd")
    Dim proxy As New SimpleFromLocal.simple
    proxy.Credentials = cred
    
    Console.WriteLine(proxy.helloWorld())
    

    This works as expected, as long as I turn off mutual authentication on the server side. I added a client certificate like this:

    Dim cert As X509Certificate = X509Certificate.CreateFromCertFile(certFile)
    proxy.ClientCertificates.Add(cert)
    

    The certificate is loaded, but the web service call fails. I could post the exception, but it is in german, which is an issue on its own. It basically says: Authentication failed, because the remote site closed the connection. I tried debugging the ssl handshake on the server side and it looks like the client certificate is not transmitted.

    So what am I missing here? Is it required to install the client certificate on the local machine?

    Edit: The error thrown:

    System.Net.WebException: The underlying connection was closed: An unexpected error occurred on a send. ---> 
    System.IO.IOException: Authentication failed because the remote party has closed the transport stream. 
    at System.Net.Security.SslState.StartReadFrame(Byte[] buffer, Int32 readBytes, AsyncProtocolRequest asyncRequest)
    at System.Net.Security.SslState.StartReceiveBlob(Byte[] buffer, AsyncProtocolRequest asyncRequest)
    at System.Net.Security.SslState.CheckCompletionBeforeNextReceive(ProtocolToken message, AsyncProtocolRequest asyncRequest)
    at System.Net.Security.SslState.StartSendBlob(Byte[] incoming, Int32 count, AsyncProtocolRequest asyncRequest)
    at System.Net.Security.SslState.ProcessReceivedBlob(Byte[] buffer, Int32 count, AsyncProtocolRequest asyncRequest)
    at System.Net.Security.SslState.StartReadFrame(Byte[] buffer, Int32 readBytes, AsyncProtocolRequest asyncRequest)
    at System.Net.Security.SslState.StartReceiveBlob(Byte[] buffer, AsyncProtocolRequest asyncRequest)
    at System.Net.Security.SslState.CheckCompletionBeforeNextReceive(ProtocolToken message, AsyncProtocolRequest asyncRequest)
    at System.Net.Security.SslState.StartSendBlob(Byte[] incoming, Int32 count, AsyncProtocolRequest asyncRequest)
    at System.Net.Security.SslState.ForceAuthentication(Boolean receiveFirst, Byte[] buffer, AsyncProtocolRequest asyncRequest)
    at System.Net.Security.SslState.ProcessAuthentication(LazyAsyncResult lazyResult)
    at System.Net.TlsStream.CallProcessAuthentication(Object state)
    at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
    at System.Net.TlsStream.ProcessAuthentication(LazyAsyncResult result)
    at System.Net.TlsStream.Write(Byte[] buffer, Int32 offset, Int32 size)
    at System.Net.PooledStream.Write(Byte[] buffer, Int32 offset, Int32 size)
    at System.Net.ConnectStream.WriteHeaders(Boolean async)
    --- End of inner exception stack trace ---
    at System.Web.Services.Protocols.WebClientProtocol.GetWebResponse(WebRequest request)
    at System.Web.Services.Protocols.HttpWebClientProtocol.GetWebResponse(WebRequest request)
    at System.Web.Services.Protocols.SoapHttpClientProtocol.Invoke(String methodName, Object[] parameters)
    at CCClient.SimpleFromLocal.simple.helloWorld() in C:\TP\CCClient\CCClient\Web References\SimpleFromLocal\Reference.vb:Zeile 90.
    at CCClient.Module1.clientWithProxy(NetworkCredential cred, String certFile)
    in C:\TP\CCClient\CCClient\Module1.vb:Zeile 53.