marc_s marc_s - 14 days ago 11
C# Question

Calling a REST web service over SSL

I'm struggling to connect to a REST web service that's working only over HTTPS / SSL from my .NET application.

I received the certificate and private key to use as two separate files - a

certificate.pem
file which contains the certificate, and the
webservice.key
file which contains the private key. Those are both text files with BASE64 encoded binary data contained in them.

The provider also sent me a PDF showing how to call that web service using CURL and those two files, and that works just fine:

curl.exe -k -v "https://(URL)" --cert certificate.pem --key webservice.key


I need to use the
-k
option since there seems to be a self-signed certificate somewhere in the hierarchy of certs. Without this option, the call fails.

In order to call this web service from a .NET application (a console app for now), I used OpenSSL (on Windows) to combine these two files into a
*.pfx
file using this command:

openssl pkcs12 -export -out webservice.pfx -in certificate.pem -inkey webservice.key


This seems to have worked, too - no errors were reported, the file was created and is about 3K in size and it's a totally binary file.

Now, I tried to call that web service from my .NET code something like this:

try
{
// use the SSL protocol (instead of TLS)
ServicePointManager.SecurityProtocol = SecurityProtocolType.Ssl3;

// ignore any certificate complaints
ServicePointManager.ServerCertificateValidationCallback += (sender, certificate, chain, sslPolicyErrors) => { return true; };

// create HTTP web request with proper content type
HttpWebRequest request = WebRequest.Create(url) as HttpWebRequest;
request.ContentType = "application/xml;charset=UTF8";

// grab the PFX as a X.509 certificate from disk
string certFileName = Path.Combine(certPath, "webservice.pfx");

// load the X.509 certificate and add to the web request
X509Certificate cert = new X509Certificate(certFileName, "(top-secret password)");
request.ClientCertificates.Add(cert);
request.PreAuthenticate = true;

// call the web service and get response
WebResponse response = request.GetResponse();

Stream responseStream = response.GetResponseStream();
}
catch (Exception exc)
{
// log and print out error
}


However, I can try whatever I like (fiddling around with various settings, on the
ServicePointManager
and the
HttpWebRequest
, but I just keep getting these errors:


WebException: The underlying connection was closed: An unexpected error occurred on a send.

IOException: Unable to read data from the transport connection: An existing connection was forcibly closed by the remote host.

SocketException: An existing connection was forcibly closed by the remote host


and no response - even though communicating with the service with CURL has worked just fine.....

What am I missing?? I'm a bit puzzled and mystified by all those certificates, private keys, service point manager options and so on - just waaaaay too many knob and switches to turn, set or turn off - what are the RIGHT settings here??

Update:

If I use

ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls;


then the error just simply is:


WebException: The request was aborted: Could not create SSL/TLS secure channel.


S O L U T I O N :

In the end, with looking at the output from
curl
and a lot of help from @Alexandru and @JurajMajer, I was able to get this to work with this code:

try
{
// use the TLS protocol
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls;

// create HTTP web request with proper content type
HttpWebRequest request = WebRequest.Create(url) as HttpWebRequest;
request.ContentType = "application/xml;charset=UTF8";

// grab the PFX as a X.509 certificate from disk
string certFileName = Path.Combine(certPath, "webservice.pfx");

// load the X.509 certificate and add to the web request
X509Certificate2 cert = new X509Certificate2(certFileName, "(top-secret password)");
request.ClientCertificates.Add(cert);
request.PreAuthenticate = true;

// call the web service and get response
WebResponse response = request.GetResponse();

Stream responseStream = response.GetResponseStream();

string xmlContents = new StreamReader(responseStream).ReadToEnd();
}
catch (Exception exc)
{
// log and print out error
}

Answer

You've used the X509Certificate(String, String) constructor with a PKCS#12 certificate, but that constructor only works for PKCS#7 certificates, as MSDN says it...

Initializes a new instance of the X509Certificate class using the name of a PKCS7 signed file and a password to access the certificate.

PKCS#7 does not include the private (key) part of a certificate/private-key pair, which you will need. This means you will need to use your PKCS#12 certificate given the nature of your certificate.

You may want to try the X509Certificate2(String, String) constructor with your existing PKCS#12 certificate, as this constructor is used with PKCS#12 (PFX) files that contain the certificate's private key, as MSDN says...

This constructor creates a new X509Certificate2 object using a certificate file name and a password needed to access the certificate. It is used with PKCS12 (PFX) files that contain the certificate's private key. Calling this constructor with the correct password decrypts the private key and saves it to a key container.

Comments