As far as I can tell, there seems to be a big limitation in .NET in that there is no way using C# and .NET to make an TLS connection that uses Server Name Indication (SNI). Have I missed something or is my understanding correct?
Does anybody know if and how I could make an SNI connection using OpenSSL.NET, libcurl.NET or some other 3rd party .NET library? Some sample code would be very much appreciated.
In my .Net 4.5 project the following fails for a server using SNI:
var url = "https://www.somesite.com";
System.Net.WebClient client = new System.Net.WebClient();
client.Encoding = Encoding.UTF8;
var data = client.DownloadString(url);
But it works if explicitly specifying TLS1.2 by prefixing it with:
System.Net.ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
The same applies to webrequest:
WebRequest request = WebRequest.Create("https://www.somesite.com");
and HttpRequestMessage:
var httpRequestMessage = new HttpRequestMessage(HttpMethod.Get, "https://www.google.com");
They all need the protocol explicitly set to TLS 1.2 to work with an SNI server (this may have changed in newer .Net versions)
This is a fairly old post but still this answer might help some people, at least it cost me some days.
.NET Framework does support the Server Name Indication by default. (Tested on 4.5.1 but I guess it's same at least for .NET 4.5+)
A short example:
HttpResponseMessage response;
var httpRequestMessage = new HttpRequestMessage(HttpMethod.Get, "https://www.google.com");
var handler = new HttpClientHandler
{
CookieContainer = new CookieContainer()
};
using (var client = new HttpClient(handler))
{
response = client.SendAsync(httpRequestMessage).Result;
}
This is a very standard way to create a GET request within C#. You will see, this example does run using, in my case, TLS 1.2 with SNI. If you use Wireshark to see the actual packages which are sent, you will see a Client Hello with the Server Name Indication set to www.google.com.
An issue we ran into: The SNI tag is set by the .NET Framework (or Schannel by Windows, not sure) based on the URL passed in the constructor of HttpRequestMessage. If you know initialize the request based on some URL (for example https://www.google.com) and later on you switch the RequestUri OR the Host header, the SNI tag will still be created based on the original url URL. This might be the case for example if you pass through a request and you remap all original headers to the newly created request.
Related
I am trying to connect to a website via a proxy. This is happening in an AWS Lambda with .NET Core SDK using an http client. The call looks pretty much like this:
handler = new HttpClientHandler()
{
CookieContainer = cookieContainer,
Proxy = new WebProxy(
new Uri(Environment.GetEnvironmentVariable("proxyURL"))),
ServerCertificateCustomValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator,
SslProtocols = SslProtocols.Tls12,
ClientCertificateOptions = ClientCertificateOption.Manual
};
using(var client = new HttpClient(handler))
{
var content = await client.GetAsync("https://my-website.com/");
}
I am not able to make the call to "https://my-website.com/". The call times out without an error message.
However I was able to access the website using Golang and resty in an AWS Lambda, skipping the TLS Check:
client := resty.New()
resp, err := client.
SetProxy(os.Getenv("proxyURL")).
SetRetryCount(3).SetTimeout(3*time.Second).SetTLSClientConfig(&tls.Config{InsecureSkipVerify: true}).
R().Get("https://my-website.com/")
My question is: how can I achieve the behaviour from my Golang Code in my .NET Core Code?
TL; DR: The problem lied not within certificate validation, but in a security group rule.
As Marc Gravell kindly pointed out, the DangerousAcceptAnyServerCertificateValidator already achieves the aim of ignoring certificate validation problems.
After deploying the same code in an EC2 instance in the same VPC and subnet I noticed, that the .NET Core code was making one more HTTP call than the Go code (Although I still do not understand why). The IP adress was not within the allowed IP range of outgoing traffic, thus blocking the request and causing a timeout of the HttpClient.
I am using WebClient to get data from a web resource thus:
var wc = new System.Net.WebClient();
var stream = wc.OpenRead("http://...");
// etc..
It used to work until recently, when the server would forcibly close the connection.
Based on this StackOverflow answer, I added the ServicePointManager setting thus:
var wc = new System.Net.WebClient();
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
var stream = wc.OpenRead("http://...");
// etc..
… and it once again works as it did before.
I get that TLS is relevant to HTTPS and the effect of the setting is to include TLS1.2 in the handshake, and that the host site must have been recently updated to reject the older vulnerable protocol, but why is it necessary for ordinary, non-secure HTTP?
AllowAutoRedirect is set to true by default in WebClient instances.
Therefore, the request automatically follows redirection responses from your server.
Simply set AllowAutoRedirect to false and you will not follow any redirection, so you will not have to deal with SSL/TLS handshakes.
Of course, if your server does not want to serve your request with HTTP, you will not get the content you are looking for. Anyway, setting AllowAutoRedirect to false will help you confirm that the behaviour you encounter is due to a redirect.
We've just launched a new service, which we're having trouble connecting to from a very basic c# console app when targeting the .Net 4.5 framework.
We first found the problem in an ASP MVC site, but have broken it down into the simplest of simple console apps to help isolate the problem
Code snippet (there isn't anything else):
string myURL = #"https://<myurl>.com/<myurl>";
using (var httpClient = new HttpClient())
{
var request = (HttpWebRequest)WebRequest.Create(myURL);
request.Method = "GET";
request.ContentLength = 0;
request.ContentType = "text/xml";
using (var response = (HttpWebResponse)request.GetResponse())
{
if (response.StatusCode != HttpStatusCode.OK)
{
...
}
else
{
...
}
}
}
What happens
Web Exception - Underlying Connection Closed.
Thrown on the call to GetRequest
Service Information
HTTPS service
SHA-256.
Other differences
Examining the request variable in the intermediate window before it is sent shows no difference.
Examining the request variable after the call has been attempted shows one difference - System.Net.HttpWebRequest.Pipelined is true in the successful attempt, false in the failed attempt.
Setup Essentials
Load balancer, balancing between two API's hosted in IIS.
Has it ever worked?
Yes!
If the URL is dropped into a browser - it works.
If I recompile this code targeting the .net 4.6 framework - it works.
If I connect directly to the site in IIS (over http) it works.
What am I asking
What could be causing this problem?
Have you seen similar and have suggestions for possible remedies?
What further steps would you take to help debug / solve the issue.
How would changing .Net framework version to 4.6 affect the HttpClient or the HttpWebRequest?"
Thanks,
Al.
This is usually caused by the server using TLS v1.2. I think Net4.5 defaults to TLS v1.1, so you must add this to your code:
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
I want to call a REST service that requires domain authentication from .NET 4.5. (Using Visual Studio 2012)
Searching Google leads to lots of links of people saying that HttpClient is now the way to do this.
However as far as I can tell there is no way to impersonate or attach credentials to HttpClient.
In addition, all the popular REST libraries seem to not be compatible with .NET 4.5 yet.
Over StackOverflow posts have suggested WebClient as a way around this, although this seems no longer available in .NET 4.5.
If I want to call a REST service with domain credentials from a .NET 4.5 client, what is the best method?
HttpClient in .NET 4.5 does support domain authentication. You need to insert a HttpClientHandler with the 'UseDefaultCredentials' setting set to true:
string searchResults = string.Empty;
try
{
HttpClientHandler handler = new HttpClientHandler();
handler.UseDefaultCredentials = true;
HttpClient client = new HttpClient(handler);
client.MaxResponseContentBufferSize = 100000;
string responseString = await client.GetStringAsync(RestServiceUrl);
searchResults = responseString;
}
catch (HttpRequestException e)
{
searchResults = e.Message;
}
Also it is worth noting that if you are building a Windows 8 application then you need to enable 'Enterprise Authentication' in the Package.appManifest:
I am trying to convert an existing app to a Metro UI app in VS 11 Developer Preview. This means running against the WinRT runtime (correct me if I'm wrong). This runs on the Windows 8 Developer Preview.
I need to call a REST API, which requires a specific user-agent to be set. This doesn't seem to be possible in WInRT. I have the following original code:
_request = WebRequest.CreateHttp(url);
_request.UserAgent = UserAgent;
But the UserAgent property is not defined for HttpWebRequest. I also tried:
_request.Headers["User-Agent"] = UserAgent;
This results in a runtime exception: System.ArgumentException: This header must be modified using the appropriate property or method.
How can I modify the User-Agent header ?
After some tinkering around, I have now worked out how to do this in WinRT. The HttpWebRequest API has changed in this version to be a lot poorer than in the full .NET Framework. However, I can send a request with the new HttpClient API, which will allow me to send the user-agent header:
var req = new HttpClient(handler)
var message = new HttpRequestMessage(HttpMethod.Get, url);
message.Headers.Add("User-Agent", "myCustomUserAgent");
var response = await req.SendAsync(message);
Just to note that in Windows 10 it is possible to do it exactly like in the example in your question.