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
}
The SSL certificate is installed on your web server hosting your REST API. The clients don't need to have a certificate to securely exchange data with your server.
When SSL is enabled for the REST web services (ascd and REST), a trust relationship between the server and the client is established by sending a server certificate to the client. The client validates the certificates that are signed by a trusted CA.
An SSL authentication assures that interactions between client and server are secure by encrypting the link that connects them, making it much harder for unauthorized entities to gain access to sensitive information. With RESTful web services, SSL authentication is slightly different than other SSL authentications.
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.
Try to enable Network Tracing in App.config on the client - instructions here. That should create network.log with more debug info. In my test environment I have one pfx which works and one which doesn't.
network.log for working pfx:
SecureChannel#9343812 - We have user-provided certificates. The server has specified 34 issuer(s). Looking for certificates that match any of the issuers.
SecureChannel#9343812 - Left with 1 client certificates to choose from.
SecureChannel#9343812 - Trying to find a matching certificate in the certificate store.
SecureChannel#9343812 - Locating the private key for the certificate:
SecureChannel#9343812 - Certificate is of type X509Certificate2 and contains the private key.
network log for non-working pfx:
SecureChannel#26756241 - We have user-provided certificates. The server has specified 34 issuer(s). Looking for certificates that match any of the issuers.
SecureChannel#26756241 - Left with 0 client certificates to choose from.
So for me the problem is my non-working certificate was issued by CA not in list.
Interesting points (possible problems):
1.) Server sends the list of known issuers for client certificate.
2.) Client code is looking for certificate and private key in certificate store event though both are in pfx.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With