How do I use SSL certificates with HttpWebRequest in C#? - c#

Currently I'm writing a utility application that will connect to a given IP and port and check the information in the SSL certificate using HttpWebRequest. When I try to extract the certificate I get an error that an exception was thrown. The exception seems to be because the act of coping the SSL certificate seems to trigger yet another validation check.
Here is the code, and maybe someone can either show me a better way to do this or if I am missing something. I don't care if the SSL Certificate is expired or doesn't match the URL. None of that is relevant for what I'm doing.
When I assign the X509Certificate in the delegate to a new variable, and look at the variable in the debugger, all the properties show SSLCert.Issuer threw an exception of type 'System.Security.Cryptography.CyrptographicException'
When I try to access a property of SSLCert, I get the following Exception thrown: m_safeCertContext is an invalid handle
I'be searched for that exception, but everything points to an invalid certificate, which might be true if the certificate is expired, and might be true for the IP and port combination I am connecting to. But since I am connecting to the IP using the IP and not anything that would match the common name, I expect that and could care less as I still need the information.
The code is below, I put some comments in as well for what doesn't work and what does work.
// To get around the SSL validation in the HttpWebRequest
System.Net.ServicePointManager.ServerCertificateValidationCallback =
delegate(object sender, System.Security.Cryptography.X509Certificates.X509Certificate certificate,
System.Security.Cryptography.X509Certificates.X509Chain chain,
System.Net.Security.SslPolicyErrors sslPolicyErrors)
{
// The below works but isn't what I want. CertName and ExpireDate are both Strings
this.CertName = ProcessSubject(certificate.Subject);
this.ExpireDate = certificate.GetExpirationDateString();
// The below works but the X509Certificate SSLCert shows exceptions in the debugger for most of the properties.
this.SSLCert = certificate;
return true; // **** Always accept
};
HttpWebRequest myRequest = (HttpWebRequest)System.Net.WebRequest.Create("https://" + this.IP + ":" + this.Port + "/SSLCheck.html");
myRequest.KeepAlive = false;
myRequest.Method = "GET";
try
{
HttpWebResponse myResponse = (HttpWebResponse)myRequest.GetResponse();
}
catch (Exception e)
{
if (e.Message != "The remote server returned an error: (404) Not Found.")
{
throw Exception("Error");
}
}
// THE BELOW FAILS
this.CertName = this.SSLCert.Subject;

This is just a guess on my part. WHat I think is happening is that the certificate info coming through in the SSL handshake is used by .NET to create a cert object that is passed to you in the delegate. The delegate is only to be used for the purpose of validation. After the call completes, .NET closes the safe handle of the certificate. THat is why the call fails when trying to access the certificate outside the callback.
TO get around this, you can serialize the certificate inside the delegate, and deserialize it outside the delegate.

(Edit from our comments below)
I suppose what you need to do is create a custom class and store the data you need in it inside the delegate code, rather than pass around the actual certificate reference.

Related

How to detect SSL Policy errors with .net?

I'm totally new to handling policy errors when making web requests so I'm a little bit confused at this point..
I have this task to call a web service but not allow the call to be made if the server I'm calling has an invalid certificate.
So I created a method to call a site with invalid cerificate and using ServerCertificateValidationCallback to prevent the call to be made if the certificate is invalid.
What I need is a quick walkthrough in how to detect the invalid certificate inside my handler. I would have thought that the call to "revoked.badssl.com" would have caused something in the sslPolicyErrors object to be something other than "none" but is this not the case? I see no difference at this point in calling badssl or my other url that has a valid certificate.
For example: if https://pinning-test.badssl.com/ is opened in chrome it shows a "ERR_SSL_PINNED_KEY_NOT_IN_CERT_CHAIN" (although IE shows the page). How do I find this information that chrome deems as an invalid certificate so I can, if I want to, also handle it as invalid in my code??
This is my code I'm trying with at the moment:
ServicePointManager.ServerCertificateValidationCallback += (sender, cert, chain, sslPolicyErrors) =>
{
if(someError?!)
return false;
return true;
};
using (HttpClient client = DefaultHttpClient())
{
Uri uri = new Uri("https://revoked.badssl.com/");
string jsonObj = "{}";
var content = new StringContent(jsonObj, Encoding.UTF8, "application/json");
HttpResponseMessage response = client.PostAsync(uri, content).Result;
}
By default the revocation check is not performed. You need to set it on the ServicePointManger Class for your application to check it.
System.Net.ServicePointManager.CheckCertificateRevocationList = true;

HttpTwo client on server 2008 (APNS with JWT)

EDIT - Due to probable misintepretation:
This is not about the server side of HTTP/2 - its about a client HTTP/2 request from an older server OS. Also, i got it to work using python (gobiko.apns) , so it seems to me it should be possible.
EDIT 2
It seems this question has not so much to do with HTTP2, but rather the cipher required by apple. TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 is not used by the SslStream in versions pre-win10. However, since it can be done using python, it seems to me that it should be possible. Any help would be appreciated.
We found some code here and there to get our connection to the APNS working on our development environment. We are using the .p8 certificate and sign a token as authorization (not the 'old' interface).
This works on my dev pc (win10) but when i transfer it to a server 2008 R2 it gives a weird warning. It seems having to do with the setup of the tls connection, however, i'm not too familiar with that area. I really searched but the only thing i can come up with is that server 2008R2 will not support it due to ciphers or something (which seems unreasonable to me).
The code that is working from my pc (using nuget HttpTwo and Newtonsoft):
public static async void Send2(string jwt, string deviceToken)
{
var uri = new Uri($"https://{host}:443/3/device/{deviceToken}");
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
ServicePointManager.ServerCertificateValidationCallback = delegate
{
Console.WriteLine("ServerCertificateValidationCallback");
return true;
};
string payloadData = Newtonsoft.Json.JsonConvert.SerializeObject(new
{
aps = new
{
alert = new
{
title = "hi",
body = "works"
}
}
});
//PayloadData always in UTF8 encoding
byte[] data = System.Text.Encoding.UTF8.GetBytes(payloadData);
var httpClient = new Http2Client(uri);
var headers = new NameValueCollection();
headers.Add("authorization", string.Format("bearer {0}", jwt));
headers.Add("apns-id", Guid.NewGuid().ToString());
headers.Add("apns-expiration", "0");
headers.Add("apns-priority", "10");
headers.Add("apns-topic", bundleId);
try
{
var responseMessage = await httpClient.Send(uri, HttpMethod.Post, headers, data);
if (responseMessage.Status == System.Net.HttpStatusCode.OK)
{
Console.WriteLine("Send Success");
return;
}
else
{
Console.WriteLine("failure {0}", responseMessage.Status);
return;
}
}
catch (Exception ex)
{
Console.WriteLine("ex");
Console.WriteLine(ex.ToString());
return;
}
}
is throwing
System.Security.Authentication.AuthenticationException: A call to SSPI failed, see inner exception. ---> System.ComponentModel.Win32Exception: The message received was unexpected or badly formatted
from server 2008R2.
I also tried it with a WinHttpHandler, which also works from my PC, but throws
System.Net.Http.HttpRequestException: An error occurred while sending the request. ---> System.Net.Http.WinHttpException: A security error occurred
Stacktraces are mostly async thingies, but it boils down to HttpTwo.Http2Connection.<Connect> for the HttpTwo implementation and System.Net.Http.WinHttpHandler.<StartRequest> for the WinHttpHandler.
Is there something i have to add to the server in order to work / will we get it to work?
UPDATE
I included the sourcefiles from HttpTwo in my project and debugged it. The exception occurs on
await sslStream.AuthenticateAsClientAsync (
ConnectionSettings.Host,
ConnectionSettings.Certificates ?? new X509CertificateCollection (),
System.Security.Authentication.SslProtocols.Tls12,
false).ConfigureAwait (false);
on my Win8 test pc. Now, when i use the method overload with only the host argument on my own PC it throws the same exception, i guess because the tls protocol is off then.
According to this Github issue it could have to do with the ciphers. I had some problems before in that area, but it seems to me that at least a WIN8 PC must be able to agree upon secure enough ciphers, right?
Schannel is complaining about "A fatal alert was received from the remote endpoint. The TLS protocol defined fatal alert code is 40.", so that points into that direction also afaik.
It's the cipher... The ciphers Apple uses are not included into SChannel below Win10/Server2016.
(see Edit 2)

How to port a curl command to RestSharp? How to troubleshoot?

I have some working curl commands, to a web service, and now I want to move them to a C# program. I am using RestSharp, and trying with the simplest of the web service calls, but just keep getting a generic error message, and I am a bit stumped how to troubleshoot it.
Is there a way to see the headers, and exact URL, that is being sent, and the headers being received?
The curl example is basically this:
curl --user user:pw https://example.com/api/version
And my C# code is:
var client = new RestClient("https://example.com");
client.Authenticator = new HttpBasicAuthenticator("user", "pw");
var request = new RestRequest ("api/version");
var response = client.Execute(request);
Console.WriteLine (response.Content);
Console.WriteLine (response.StatusCode);
Console.WriteLine (response.ErrorMessage);
This gives me:
RestSharp.RestRequest
0
Error getting response stream (Write: The authentication or decryption has failed.): SendFailure
I am using Mono, on Linux. Would that be related? But I could find a few (more advanced) questions with the mono tag on StackOverflow, so it should work. (?)
If it was actually a problem with the username/password, I would get a 403 status, instead of a zero status, I assume?
P.S. In case it matters, the rest of my script is:
using System;
using System.Net;
using RestSharp;
namespace webtest
{
class MainClass
{
public static void Main (string[] args)
{
...(above code)
}
}
}
Regarding troubleshooting
So far I can suggest:
Try commenting out the Authenticator line to see if anything changes (in my case it did not)
Try http://google.com
Try https://google.com
That was enough for me to see that http URLs work, https URLs fail.
(If you need more troubleshooting, and are using https, the sender parameter shown below contains various fields about the request being sent to the remote server.)
Regarding porting curl commands
By default curl on linux uses the certificates it finds in /etc/ssl/certs. The blanket equivalent for Mono is to do mozroots --import --ask-remove, which will import all certificates (see Mono security FAQ).
Another way to do it is by putting this at the very top of your program:
ServicePointManager.ServerCertificateValidationCallback +=
(sender, certificate, chain, sslPolicyErrors) => {
//Console.WriteLine(certificate.ToString());
return true;
};
The commented line can be used to report the certificate to the user, interactively get their approval, or to check the certificate fingerprint against the expected one. By simply returning true it means all certificates are trusted and unchecked.
Bonus: Cert checks
Here is one way to check for a specific certificate:
ServicePointManager.ServerCertificateValidationCallback +=
(sender,certificate,chain,sslPolicyErrors) => {
if(((System.Net.HttpWebRequest)sender).Host.EndsWith("google.com") ){
if(certificate.GetCertHashString() == "83BD2426329B0B69892D227B27FD7FBFB08E3B5E"){
return true;
}
Console.WriteLine("Uh-oh, google.com cert fingerprint ({0}) is unexpected. Cannot continue.",certificate.GetCertHashString());
return false;
}
Console.WriteLine("Unexpected SSL host, not continuing.");
return false;
}

Ignore invalid SSL (version 3) certificate with XmlTextReader https request

I am trying to create an XML document from an https web request, but I am having trouble getting it to work when the site has an invalid certificate. I want my application to not care about the certificate, I want it to live it's life on the edge without fear!
Here is the initial code I had, which I have used before may times to get what I want from a standard http (non SSL) request:
XmlDocument xml = new XmlDocument();
XmlTextReader reader = new XmlTextReader("https://www.example.com");
xml.Load(reader);
With the site having an invalid SSL certificate I am now getting the following error:
The request was aborted: Could not create SSL/TLS secure channel.
Now I have done my Google-ing and tried a number of promising solutions but it seems to be of no help.
One I tried here on SO looked good but didn't seem to work, I added the 'accepted answer' line of code directly before my code above as it wasn't too clear as where it should go.
In case it makes any difference, my code is in a class library and I am testing via a console app. I am also using .Net 4.
Here is my latest attempt (which does not work):
XmlDocument xml = new XmlDocument();
ServicePointManager.ServerCertificateValidationCallback += new System.Net.Security.RemoteCertificateValidationCallback((s, ce, ch, ssl) => true);
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(urlCommand);
request.Credentials = CredentialCache.DefaultCredentials;
using (HttpWebResponse response = (HttpWebResponse)request.GetResponse())
{
using (Stream receiveStream = response.GetResponseStream())
{
XmlTextReader reader = new XmlTextReader(receiveStream);
xml.Load(reader);
}
}
OK so I have found the solution. We had opted to try and disable the SSL on the server for testing and noticed it was using SSL 3. After another Google search I found some additional code to fix the issue (important to set the SecurityProtocolType):
ServicePointManager.SecurityProtocol = SecurityProtocolType.Ssl3;
ServicePointManager.ServerCertificateValidationCallback += new System.Net.Security.RemoteCertificateValidationCallback((s, ce, ch, ssl) => true);
XmlDocument xml = new XmlDocument();
XmlTextReader reader = new XmlTextReader(urlCommand);
xml.Load(reader);
Hm, maybe the XmlTextReader uses a different way of accessing the HTTPS.
Try making the web request with a HttpWebRequest, and pass response.GetResponseStream() to the XML text reader (and leave the ServicePointManager.ServerCertificateValidationCallback override where you had it)
Reason for this error:
You are not using valid client certificate on your website.
You could try below:
For quick turn around you could try access the URL with http://_____ * - it's not recommended but for testing you could try.*
You are not using valid client certificate hence, you could write something like below:
Add this line where you are requesting to download XML or making some http request.
//Add Mock certificate validation
ServicePointManager.ServerCertificateValidationCallback = new RemoteCertificateValidationCallback(OnValidationCallback);
Add below as a global method:
public static bool OnValidationCallback(object sender, X509Certificate cert, X509Chain chain, SslPolicyErrors errors)
{
return true;
}

How to perform a fast web request in C#

I have a HTTP based API which I potentially need to call many times. The problem is that I can't get the request to take less than about 20 seconds, though the same request made through a browser is near instantaneous. The following code illustrates how I have implemented it so far.
WebRequest r = HttpWebRequest.Create("https://example.com/http/command?param=blabla");
var response = r.GetResponse();
One solution would be to make an asynchronous request but I would like to know why it takes so long and if I can avoid it. I have also tried using the WebClient class but I suspect it uses a WebRequest internally.
Update:
Running the following code took about 40 seconds in Release Mode (measured with Stopwatch):
WebRequest g = HttpWebRequest.Create("http://www.google.com");
var response = g.GetResponse();
I'm working at a university where there might be different things in the network configuration affecting the performance, but the direct use of the browser illustrates that it should be near instant.
Update 2:
I uploaded the code to a remote machine and it worked fine so the conclusion must be that the .NET code does something extra compared to the browser or it has problems resolving the address through the university network (proxy issues or something?!).
This problem is similar to another post on StackOverflow:
Stackoverflow-2519655(HttpWebrequest is extremely slow)
Most of the time the problem is the Proxy server property. You should set this property to null, otherwise the object will attempt to search for an appropriate proxy server to use before going directly to the source. Note: this property is turn on by default, so you have to explicitly tell the object not to perform this proxy search.
request.Proxy = null;
using (var response = (HttpWebResponse)request.GetResponse())
{
}
I was having the 30 second delay on 'first' attempt - JamesR's reference to the other post mentioning setting proxy to null solved it instantly!
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(_site.url);
request.Proxy = null; // <-- this is the good stuff
...
HttpWebResponse response = (HttpWebResponse)request.GetResponse();
Does your site have an invalid SSL cert? Try adding this
ServicePointManager.ServerCertificateValidationCallback = new System.Net.Security.RemoteCertificateValidationCallback(AlwaysAccept);
//... somewhere AlwaysAccept is defined as:
using System.Security.Cryptography.X509Certificates;
using System.Net.Security;
public bool AlwaysAccept(object sender, X509Certificate certification, X509Chain chain, SslPolicyErrors sslPolicyErrors)
{
return true;
}
You don't close your Request. As soon as you hit the number of allowed connections, you have to wait for the earlier ones to time out. Try
using (var response = g.GetResponse())
{
// do stuff with your response
}

Categories