I'm writing a .NET Core app to poll a remote server and transfer data as it appears. This is working perfectly in PHP because the PHP is ignoring the certificate (which is also a problem in browsers) but we want to move this to C# .NET CORE because this is the only remaining PHP in the system.
We know the server is good, but for various reasons the certificate can't / won't be updated any time soon.
The request is using HttpClient:
HttpClient httpClient = new HttpClient();
try
{
string url = "https://URLGoesHere.php";
MyData md = new MyData(); // this is some data we need to pass as a json
string postBody = JsonConvert.SerializeObject(md);
httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
HttpResponseMessage wcfResponse = await httpClient.PostAsync(url, new StringContent(postBody, Encoding.UTF8, "application/json"));
Console.WriteLine(wcfResponse.Content);
}
catch (HttpRequestException hre)
{
// This exception is being triggered
}
Having researched this it seems the universal recommendation is to use ServicePointManager, but this is not available in .NET Core and I'm having trouble finding the recommended replacement.
Is there a simple or better way to do this in .NET Core?
Instead of new HttpClient() you want something akin to
var handler = new System.Net.Http.HttpClientHandler();
using (var httpClient = new System.Net.Http.HttpClient(handler))
{
handler.ServerCertificateCustomValidationCallback = (request, cert, chain, errors) =>
{
// Log it, then use the same answer it would have had if we didn't make a callback.
Console.WriteLine(cert);
return errors == SslPolicyErrors.None;
};
...
}
That should work on Windows, and on Linux where libcurl is compiled to use openssl. With other curl backends Linux will throw an exception.
Getting Linux and macOS to work
If you are working in Linux or macOS you may encounter a scenario in which HttpClient will not allow you to access a self signed cert even if it is in your trusted store. You will likely get the following:
System.Net.Http.CurlException: Peer certificate cannot be authenticated with given CA certificates environment variable
of if you are implementing (as shown in the other answer)
handler.ServerCertificateCustomValidationCallback = (request, cert, chain, errors) =>
{
// Log it, then use the same answer it would have had if we didn't make a callback.
Console.WriteLine(cert);
return errors == SslPolicyErrors.None;
};
This is due to the version of libcurl on the machine not supporting the appropriate callbacks that .Net Core needs to in order to gather the appropriate data to call the ServerCertificateCustomValidationCallback. For example, there isn't a way for the framework to create the cert object or another one of the parameters. More information can be found in the discussion for the workaround that was provided in .NET Core at issue in dotnet core's github repo:
https://github.com/dotnet/corefx/issues/19709
The workaround (which should only be used for testing or specific internal applications) is the following:
using System;
using System.Net.Http;
using System.Runtime.InteropServices;
namespace netcurl
{
class Program
{
static void Main(string[] args)
{
var url = "https://localhost:5001/.well-known/openid-configuration";
var handler = new HttpClientHandler();
using (var httpClient = new HttpClient(handler))
{
// Only do this for testing and potentially on linux/mac machines
if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX) && IsTestUrl(url))
{
handler.ServerCertificateCustomValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator;
}
var output = httpClient.GetStringAsync(url).Result;
Console.WriteLine(output);
}
}
static bool IsTestUrl(string url) => url.Contains("localhost");
}
}
There is another avenue for fixing this problem, and that is using a version of libcurl that is compiled with openssl support. For macOS, here is a good tutorial on how to do that:
https://spin.atomicobject.com/2017/09/28/net-core-osx-libcurl-openssl/
For the short version, grab a copy of the latest libcurl compiled with openssl support:
brew install curl --with-openssl
You probably don't want to force the entire OS to use the non-Apple version of libcurl, so you'll likely want to use the DYLD_LIBRARY_PATH environment variable instead of using brew to force link the binaries into the regular path of the OS.
export DYLD_LIBRARY_PATH=/usr/local/opt/curl/lib${DYLD_LIBRARY_PATH:+:$DYLD_LIBRARY_PATH}
The above command can be used to set the appropriate environment variable when running dotnet in a terminal. This doesn't really apply to GUI applications though. If you're using Visual Studio for Mac, you can set the environment variable in the project run settings:
The second approach was necessary for me when using IdentityServer4 and token authorization. The .NET Core 2.0 authorization pipeline was making a call to the token authority using an HttpClient instance. Since I didn't have access to the HttpClient or its HttpClientHandler object, I needed to force the HttpClient instance to use the appropriate version of libcurl that would look into my KeyChain system roots for my trusted certificate. Otherwise, I would get the System.Net.Http.CurlException: Peer certificate cannot be authenticated with given CA certificates environment variable when trying to secure a webapi endpoint using the Authorize(AuthenticationSchemes = IdentityServerAuthenticationDefaults.AuthenticationScheme)] attribute.
I spent hours researching this before finding work arounds. My whole goal was to use a self-signed certificate during development for macOs using IdentityServer4 to secure my webapi. Hope this helps.
//at startup configure services add the following code
services.AddHttpClient(settings.HttpClientName, client => {
// code to configure headers etc..
}).ConfigurePrimaryHttpMessageHandler(() => {
var handler = new HttpClientHandler();
if (hostingEnvironment.IsDevelopment())
{
handler.ServerCertificateCustomValidationCallback = (message, cert, chain, errors) => { return true; };
}
return handler;
});
now you can use IHttpClientFactory CreateClient method within your service
Just to add another variation, you could add in your thumbprint and check it in the callback to make things a bit more secure such as:
if (!string.IsNullOrEmpty(adminConfiguration.DevIdentityServerCertThumbprint))
{
options.BackchannelHttpHandler = new HttpClientHandler
{
ServerCertificateCustomValidationCallback = (sender, certificate, chain, sslPolicyErrors) => certificate.Thumbprint.Equals(adminConfiguration.DevIdentityServerCertThumbprint, StringComparison.InvariantCultureIgnoreCase)
};
}
adminConfiguration.DevIdentityServerCertThumbprint is the configuration that you would set with your self signed cert's thumbrint.
Related
I'd like to ask if there's a way to bypass Ubuntu's security checks so that I could be able to fetch a website with a small key in my .NET Core Client app? I am getting error:141A318A:SSL routines:tls_process_ske_dhe:dh key too small exception.
The issue is that in Ubuntu 20.04 openSSL has security level set to 2 and (currently, hopefully someone will come up with an answer for my question on Ask Ubuntu) I have no idea how to set it to a lower value.
The same error occurs using curl unless --ciphers 'DEFAULT:!DH' parameter is provided, so I assume the root cause of the problem is within the operating sysem itself.
I do not control the website's server, so changing its security settings is a no go.
What I've tried so far from C# side:
serviceCollection.AddHttpClient<IInterface, IImplementation>()
.ConfigureHttpMessageHandlerBuilder(messageHandlerBuilder =>
{
messageHandlerBuilder.PrimaryHandler = new HttpClientHandler
{
ServerCertificateCustomValidationCallback = (m, c, ch, e) => true
};
});
and
using var httpClient = new HttpClient();
ServicePointManager.ServerCertificateValidationCallback = delegate { return true; };
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
ServicePointManager.ServerCertificateValidationCallback += (sender, cert, chain, sslPolicyErrors) => true;
var websiteContent = await httpClient.GetStreamAsync(url);
Security is not much of an issue in this case so I'm ok with using any dirty hack here.
Any help would be much appreciated.
Thanks to the answer received on Ask Ubuntu I managed to fix the issue by:
copying openssl.cnf file
Adding openssl_conf = default_conf at the top of the copied file
Adding at the end:
[ default_conf ]
ssl_conf = ssl_sect
[ssl_sect]
system_default = ssl_default_sect
[ssl_default_sect]
MinProtocol = TLSv1.2
CipherString = DEFAULT:#SECLEVEL=1
Running the project with OPENSSL_CONF environmental variable set to path to the altered config file
I am using a third party library (Splunk c# SDK ) in my ASP.NET core application. I am trying to connect to my localhost Splunk service via this SDK, but I get an exception saying:
System.Net.Http.HttpRequestException: The SSL connection could not be established, see inner exception.
And The inner exception says:
The remote certificate is invalid according to the validation procedure.
This SDK uses HTTP client under the hood, but I don't have access to this object to configure HttpClientHandler.
All my search on google ends up using ServicePointManager to bypass the SSL validation, but this solution doesn't work in Asp.Net core.
ServicePointManager.ServerCertificateValidationCallback = delegate { return true; };
Is there any way to bypass this validation in asp.Net core?
Yes, you can Bypass the certificate using below code...
HttpClientHandler clientHandler = new HttpClientHandler();
clientHandler.ServerCertificateCustomValidationCallback = (sender, cert, chain, sslPolicyErrors) => { return true; };
// Pass the handler to httpclient(from you are calling api)
HttpClient client = new HttpClient(clientHandler);
As I worked with the identity server (.net core) and a web api (.net core) on my developer machine, I realized, that I need to trust the ssl certification of localhost. That command does the job for me:
dotnet dev-certs https --trust
If you are adding an IHttpClient and injecting through DI, u can add the configuration on the Startup.cs class.
public void ConfigureServices(IServiceCollection services)
{
services.AddHttpClient("yourServerName").ConfigurePrimaryHttpMessageHandler(_ => new HttpClientHandler
{
ServerCertificateCustomValidationCallback = (sender, cert, chain, sslPolicyErrors) => { return true; }
});
}
And then call it from your dependency injected class.
public class MyServiceClass
{
private readonly IHttpClientFactory _clientFactory;
public MyServiceClass (IConfiguration configuration, IHttpClientFactory clientFactory)
{
_clientFactory = clientFactory;
}
public async Task<int> DoSomething()
{
var url = "yoururl.com";
var client = _clientFactory.CreateClient("yourServerName");
var result = await client.GetAsync(url);
}
ServicePointManager.ServerCertificateValidationCallback += (sender, certificate, chain, errors) =>
{
// local dev, just approve all certs
if (development) return true;
return errors == SslPolicyErrors.None ;
};
This blog helped me
https://www.khalidabuhakmeh.com/validate-ssl-certificate-with-servicepointmanager
This worked for me,
Create a Splunk.Client.Context by providing custom HttpClientHandler, that will bypass SSL invalid cert errors.
HttpClientHandler handler = new HttpClientHandler();
handler.ServerCertificateCustomValidationCallback = (sender, cert, chain, sslPolicyErrors) => { return true; };
// Create Context
Context context = new Context(Scheme.Https, "localhost", 8089, default(TimeSpan), handler);
// Create Service
service = new Service(context);
You get this error because your app isn't able to validate the certificate of the connection, and it's especially common to use this for the API that creates the session/login tokens. You can bypass it in a dangerous way as shown above, but obviously that's not a good solution unless you're just testing.
The best and easiest solution is to use the "modernhttpclient-updated" Nuget package, whose code is shared in this GitHub repo where there's also a lot of documentation.
As soon as you add the Nuget package, pass in a NativeMessageHandler into you HttpClient() as shown and build:
var httpClient = new HttpClient(new NativeMessageHandler());
Now you will notice that you got rid of that error and will get a different error message like this Certificate pinning failure: chain error. ---> Javax.Net.Ssl.SSLPeerUnverifiedException: Hostname abcdef.ghij.kl.mn not verified: certificate: sha256/9+L...C4Dw=
To get rid of this new error message, you have to do add the hostname and certificate key from the error to a Pin and add that to the TLSConfig of your NativeMessageHandler as shown:
var pin = new Pin();
pin.Hostname = "abcdef.ghij.kl.mn";
pin.PublicKeys = new string[] { "sha256/9+L...C4Dw=" };
var config = new TLSConfig();
config.Pins = new List<Pin>();
config.Pins.Add(pin);
httpClient = new HttpClient(new NativeMessageHandler(true, config)
Keep in mind that your other (non token generating) API calls may not implement certificate pinning so they may not need this, and frequently they may use a different Hostname. In that case you will need to register them as pins too, or just use a different HttpClient for them!
Installing the .NET Core SDK installs the ASP.NET Core HTTPS development certificate to the local user certificate store. The certificate has been installed, but it's not trusted. To trust the certificate, perform the one-time step to run the dotnet dev-certs tool:
dotnet dev-certs https --trust
for more information visit this link
I had to turn off my vpn to get rid off this error
We like to enable some hidden features of our software only if it is run inside of the company network. The key requirements are:
no need for a third party library outside of DotNet 4.5.1
easy to implement (should not be more than some dozens of lines .. I don't want to reimplement a crypto library)
It should be reasonable safe:
at least: hard to reverse engineer
at best: "impossible" to break even with read-access to the source code
low maintenance overhead
Win2012-Server is available for installation of additional software (open source or own implementation prefered - server can be assumed to be safe)
What I have thought about:
Check if a specific PC is available with a known MAC or IP (current implementation, not really secure and some other flaws)
Test, if a service is available on a specific response (i.e. I send 'Hello' to MyServer:12345 - server responses with 'World')
Similar to 2nd but a more complex challenge (i.e. send a seed for a RNG to the server, verify the response)
Set up an apache with HTTPS and verify the certificate
If you use ActiveDirectory, you could add a reference to the System.DirectoryServices namespace and check
ActiveDirectorySite currentSite = ActiveDirectorySite.GetComputerSite();
then you can get a bit of information from the currentSite object and check against that. That's how I enable/disable features of an application I'm developing currently.
I also grab:
var client = Dns.GetHostEntry(Dns.GetHostName());
foreach (var ip in client.AddressList)
{
if(ip.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork)
{
ipAddress = ip;
}
}
Which you can check to make sure the client is connected with the proper protocol.
I've choosen the last option: Set up a webserver in the intranet and verify the certificate.
It was easier than expected. There are enough tutorials for setting up an apache with https for every supported OS. The self-signed certificate have a lifetime of 9999 days - should be okay until 2042. The C#-part is also reasonable small:
private static bool m_isHomeLocation = false;
public static bool IsHomeLocation
{
get
{
if (m_isHomeLocation)
return true;
try
{
HttpWebRequest request = (HttpWebRequest)WebRequest.Create("https://yourLicenseServer:yourConfiguredPort");
request.ServerCertificateValidationCallback += ((s, certificate, chain, sslPolicyErrors) => true);
HttpWebResponse response = (HttpWebResponse)request.GetResponse();
response.Close();
var thumbprint = new X509Certificate2(request.ServicePoint.Certificate).Thumbprint;
m_isHomeLocation = (thumbprint == "WhateverThumbprintYourCertificateHave");
}
catch
{
// pass - maybe next time
}
return m_isHomeLocation;
}
}
I am working on a build a tool to test all my SSL certificates in my environment.
I am using the standard SSLStream implementation to connect to the remote servers, authenticate as a client and then use the ssl.RemoteCertificate method to retrieve the Cert.
The code works fine, I get the cert. I build a chain from the cert, loop through the chain and enumerate all the certs in the chain.
I thought that I had gotten it working until I compared my output to DigiCerts SSL Utility.
I noticed that the chains that I got from it are different than the chain that I built through C#.
Digging a little more, I realized that, in my local store where the C# code is executed, there are multiple intermediate certs with the same subject name.
The only difference is there expiry.....
it appears that C# takes the newest cert with the oldest expiry....
4/29/2017
Where as the DigiCert tool displays the intermediate cert that is older with the expiry that is closer to expiring IE 4/29/2014
Is there a way to control how the chain is built? (inside of C#)
using (TcpClient client = new TcpClient())
{
try
{
client.Connect("servername", 443);
SslStream ssl = new SslStream(client.GetStream(), false, new RemoteCertificatValidationCallback(ValidateServerCertificate), null);
ssl.AuthenticateAsClient("servername");
cert = new X509Certificate2(ssl.RemoteCertificate);
PrintChain(cert);
}
catch(Exception ex)......
}
private static void PrintChain(X509Certificate cert)
{
X509Chain ch = new X509Chain();
ch.Build(cert);
ch.ChainPolicy.RevocationMode = X509RevocationMode.Online;
foreach (X509ChainElement element in ch.ChainElements)
{
Console.WriteLine(element.Certificate.SerialNumber);
// Go thru and print all my details and continue the loop
}
I have also overridden the ValidateServerCertificate method and used the chain directly from that override instead of using the X509Cahin.Build();
they print out the same.....
Thank you
My Android application is supposed to communicate with a ASP.net web api which is written in C#.Net. The data which is sent from the phone contains data that should not be exposed to the public. So I'm trying to use the https protocol. On my serverside I require all requests to be HTTPS, like this:
RequireRegisteredImei
public class RequireRegisteredImeiAttribute : AuthorizationFilterAttribute
{
public override void OnAuthorization(HttpActionContext actionContext)
{
var request = actionContext.ControllerContext.Request;
if (request.RequestUri.Scheme == Uri.UriSchemeHttps)
{
//OKAY
}
else
{
actionContext.Response = new HttpResponseMessage(HttpStatusCode.Forbidden);
}
}
}
And in the Controller:
[RequireRegisteredImei]
public string Post()
{
}
I debugged this code by sending a simple http request from my phone, and this code works quite well, it will deny me.
So, I started looking at how I could send requests over https from my android phone. I came up with something like this:
public static DefaultHttpClient getSecureHttpClient() {
SchemeRegistry schemeRegistry = new SchemeRegistry();
schemeRegistry.register(new Scheme("https", SSLSocketFactory.getSocketFactory(), 443));
schemeRegistry.register(new Scheme("http", SSLSocketFactory.getSocketFactory(), 80));
BasicHttpParams params = new BasicHttpParams();
SingleClientConnManager mgr = new SingleClientConnManager(params, schemeRegistry);
return new DefaultHttpClient(mgr, params);
}
I'm using this method this way:
HttpClient httpClient = CustomHttpClient.getSecureHttpClient();
This will only result in an IOException: No peer certificate
I've read several threads regarding this:
Problems with https (No peer certificate) in android
Android SSL - No Peer Certificate
'No peer certificate' error in Android 2.3 but NOT in 4
But there has to be a simpler way to post data over HTTPS from android?
If you have a custom certificate or a certificate issued by a CA that is not included in all Android versions you can include the certificate into your app and use it directly.
To do so you have to import your server certificate (without the key of course) into a BKS key-store which then can be used as custom trust store.
A very good tutorial which describes how to do so is Using a Custom Certificate Trust Store on Android.
In difference to the standard solutions like EasyTrustManager or DummyTrustManager you find of Stackoverflow this solution doesn't disable the authentication of SSL and is therefore not insecure like the other solutions.
You can also configure the HttpClient to accept all certificates like this :
SSLSocketFactory sslSocketFactory=SSLSocketFactory.getSocketFactory();
HostnameVerifier hostnameVerifier=org.apache.http.conn.ssl.SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER;
sslSocketFactory.setHostnameVerifier((X509HostnameVerifier) hostnameVerifier);
SchemeRegistry schemeRegistry = new SchemeRegistry();
schemeRegistry.register(
new Scheme("http", PlainSocketFactory.getSocketFactory(), 80));
schemeRegistry.register(
new Scheme("https", sslSocketFactory, 443));
if you think that this may be a solution for you.