I am trying to use REST services from corporative (inner) site. That system is outsourcing (we can't change anything in it) and uses TLS 1.3. Issuer is
RapidSSL Global TLS RSA4096 SHA256 2022 CA1
and algorithm is SHA256withRSA
I have tried all the possible libraries including RestSharp, Flurl and HttpClient. Passing Tls13 protocol as parameter. The same problem exists with .NET Core 7 preview (which should have better TLS support).
My code looks like this:
var options = new RestClientOptions("https://xx.xxx.com/auth/login")
{
ThrowOnAnyError = true,
Timeout = 1000,
Expect100Continue = true,
};
var client = new RestClient(options);
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls13;
ServicePointManager.ServerCertificateValidationCallback += (sender, cert, chain, sslPolicyErrors) => true;
var response = client.Post(new RestRequest().AddJsonBody(new { user = "xxx#xxx.com", password = "123456" }));
or this code :
var tokenrequest = new TokenRequest() { User = "xxx", Password = "123" };
HttpClient _httpClient;
var httpClientHandler = new HttpClientHandler();
httpClientHandler.ServerCertificateCustomValidationCallback = (message, cert, chain, errors) => true;
_httpClient = new HttpClient(httpClientHandler);
ServicePointManager.SecurityProtocol |= SecurityProtocolType.Tls13;
var response = await _httpClient.PostAsync("https://yyy.com", CreateHttpContent<TokenRequest>(tokenrequest));
response.EnsureSuccessStatusCode();
var data = await response.Content.ReadAsStringAsync();
Console.WriteLine(data);
In all cases I get the same error:
Authentication failed because the remote party sent a TLS alert:
'HandshakeFailure'.
The message received was unexpected or badly formatted.
Fiddler detailed error message says:
HTTPS handshake to yyy.com (for #7) failed.
System.Security.Authentication.AuthenticationException A call to SSPI
failed, see inner exception. < The message received was unexpected or
badly formatted
Win32 (SChannel) Native Error Code: 0x80090326
What OS are you using to run your app?
Because TLS 1.3 Clients are supported only on Windows 11/Server 2022, that is it's OS level error you have.
Most probably your machine is sending TLS handshake specifing it's max supported TLS version is 1.2 and it fails here because server is requiring 1.3 so you get 'HandshakeFailure'.
My question relates to the following posts:
HttpClient does not send client certificate on Windows using .NET Core
WebApi HttpClient not sending client certificate
My env:
Mac osx ( 10.15.6 )
Visual studio code for Mac ( 8.3.2 )
Goal:
I want to use self-signed certificates ( as an experiment ), to test TLS with c# and a nginx server. I can use this curl command to confirm that the crt and key allow me to access a resource:
curl --cert client.crt --key client.key -k https://localhost:443
I read the p12 keystore using this code
public X509Certificate2 readCertificates(String path, String password)
{
return new X509Certificate2(File.ReadAllBytes(path), password);
}
I use this code to make a Https request. The loaded certificate has a subject, identifier, and private key that I expect. This confirms the above step.
public async System.Threading.Tasks.Task validateRequest(X509Certificate2 certificate)
{
try
{
WebRequestHandler handler = new WebRequestHandler();
handler.ClientCertificates.Add(certificate);
handler.SslProtocols = SslProtocols.Tls12 | SslProtocols.Tls11 | SslProtocols.Tls;
ServicePointManager.ServerCertificateValidationCallback = (a, b, c, d) => { return true; };
handler.ClientCertificateOptions = ClientCertificateOption.Manual;
HttpClient client = new HttpClient(handler);
HttpRequestMessage httpContent = new HttpRequestMessage(HttpMethod.Get, new Uri("https://localhost:443"));
HttpResponseMessage response = await client.SendAsync(httpContent);
string resultContent = response.Content.ReadAsStringAsync().Result;
if (response.IsSuccessStatusCode)
{
Console.WriteLine("## we authenticated...");
}
else {
Console.WriteLine("### we have an issue");
}
response.Dispose();
client.Dispose();
httpContent.Dispose();
}
catch (Exception e) {
Console.WriteLine("##### error:: " + e);
}
}
After executing this code, my ngix server states:
client sent no required SSL certificate while reading client request header
The c# side evalutes $resultContent$ to:
400 No required SSL certificate was sent
If I replace the below lines, I get a different error:
// take this line
ServicePointManager.ServerCertificateValidationCallback = (a, b, c, d) => { return true; };
// replace it with the below
handler.ServerCertificateValidationCallback = (a, b, c, d) => { return true; };
I can observe an error on the ngix server with:
peer closed connection in SSL handshake while SSL handshaking
I can observe this error on the c# side:
Mono.Security.Interface.TlsException: CertificateUnknown
In reading the aforementioned stack posts, I was under the impression that I could read and ignore self-signed certificates by modifying ServerCertificateValidationCallback. This experiment is leading me to believe otherwise. Am I missing a key step in using self-signed certificates in making a https request?
I added WCF connected service reference in my project and set ServicePointManager.ServerCertificateValidationCallback function. For some reason, this callback function is ignored when i am requesting server. I have to notify user about certificate problems and proceed request if user confirms.
static async Task Main(string[] args)
{
ServicePointManager.ServerCertificateValidationCallback = MyServerCertificateValidationCallback;
var data = new DataSoapClient(DataSoapClient.EndpointConfiguration.DataSoap);
data.Endpoint.Address = new EndpointAddress("https://open.helios.eu/demo/Data.asmx");
(data.Endpoint.Binding as BasicHttpBinding).Security.Mode = BasicHttpSecurityMode.Transport;
var result = (await data.GetInfoAsync("GETREDIRECTINFO", string.Empty)).Body.GetInfoResult;
Console.WriteLine(result);
}
private static bool MyServerCertificateValidationCallback(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors)
{
// function won't execute
return true;
}
Finally i found a solution for my problem. As Abraham Quian mentioned, callback is not working in .net core so I had to use different approach and use X509CertificateValidator. Here is a code snippet:
static async Task Main(string[] args)
{
var data = new ServiceReference1.Service1Client(Service1Client.EndpointConfiguration.BasicHttpsBinding_IService1);
data.Endpoint.Address = new EndpointAddress("https://localhost:5035/Service1.svc");
(data.Endpoint.Binding as BasicHttpBinding).Security.Mode = BasicHttpSecurityMode.Transport;
data.ClientCredentials.ServiceCertificate.SslCertificateAuthentication = new X509ServiceCertificateAuthentication();
data.ClientCredentials.ServiceCertificate.SslCertificateAuthentication.CertificateValidationMode = X509CertificateValidationMode.Custom;
data.ClientCredentials.ServiceCertificate.SslCertificateAuthentication.CustomCertificateValidator = new Validator();
var result = await data.GetDataAsync(1);
Console.WriteLine(result);
}
And there is validator:
internal class Validator : X509CertificateValidator
{
public override void Validate(X509Certificate2 certificate)
{
X509Chain chain = new X509Chain();
if (!chain.Build(certificate))
{
Console.WriteLine($"{chain.ChainStatus.FirstOrDefault().StatusInformation}. Press y to proceed...");
if(Console.ReadKey().KeyChar != 'y')
throw new SecurityTokenValidationException("Service certification is not valid.");
}
}
}
ServicePointManager.ServerCertificateValidationCallback += delegate
{
return true;
};
This code snippet is valid in the Dotnetframework project, it is invalid in the Dotnet Core project.
Generally, in the case of ensuring that the certificate can be trusted, we should install the certificate provided by the server to the Root CA certificate store on the client-side.
In addition, the following code snippet applies to both the DotNet Core project and the Dotnetframework project.
ServiceReference1.TestServiceClient client = new ServiceReference1.TestServiceClient();
client.ClientCredentials.ServiceCertificate.SslCertificateAuthentication = new System.ServiceModel.Security.X509ServiceCertificateAuthentication
{
CertificateValidationMode = System.ServiceModel.Security.X509CertificateValidationMode.None,
RevocationMode = System.Security.Cryptography.X509Certificates.X509RevocationMode.NoCheck
};
Feel free to let me know if the problem still exists.
I talk to my server using SSL, but have self-signed cert.
In Android I use this code to pass my SSL cert to system to be able make requests to my server:
KeyStore ks = KeyStore.getInstance("BKS");
InputStream in = new ByteArrayInputStream(<byte [] my_ssl_data>);
ks.load(in, <string mypassword>.toCharArray());
in.close();
TrustManagerFactory Main_TMF = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
Main_TMF.init(ks);
X509TrustManager Cur_Trust_Manager = new X509TrustManager()
{
public void checkClientTrusted(X509Certificate [] chain, String authType) throws CertificateException { }
public void checkServerTrusted(X509Certificate [] chain, String authType) throws CertificateException { }
public X509Certificate [] getAcceptedIssuers() { return null; }
};
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, new TrustManager[] { Cur_Trust_Manager }, new SecureRandom());
HttpsURLConnection.setDefaultSSLSocketFactory(sslContext.getSocketFactory());
HttpsURLConnection.setDefaultHostnameVerifier(new HostnameVerifier()
{
#Override
public boolean verify(String hostname, SSLSession session)
{
try
{
Cur_Trust_Manager.checkServerTrusted((X509Certificate []) session.getPeerCertificates(), session.getCipherSuite());
return true;
}
catch (Exception e) {}
return false;
}
});
Now I need something like this in Windows Universal App 8.1+ (Windows+WindowsPhone) and iOS 7.0+.
For network requests I use System.Net.Http.HttpClient which works both with UWP and Xamarin.iOS. I have cert from my server in DER format but still can't add a handler to HttpClient.
Xamarin.iOS version says "Not implemented"
HttpClientHandler myHandler = new HttpClientHandler();
X509Certificate2 certificate = new X509Certificate2();
certificate.Import(my_cert_der_bytes);
myHandler.ClientCertificates.Add(certificate);
myHandler.ClientCertificateOptions = ClientCertificateOption.Manual;
myHandler.AllowAutoRedirect = false;
HttpClient c = new HttpClient(myHandler);
....
UWP version unfortunately (and why the hell?) doesn't know X509Certificate2 and all that stuff. I tried to use WinRtHttpClientHandler but didn't understand where should I pass my cert. I tried to skip errors (started to work), yet that's not a solution, because I don't want my requests to be redirected to another untrasted server.
var filter = new HttpBaseProtocolFilter();
Certificate cer = new Certificate(my_cert_bytes.AsBuffer());
filter.IgnorableServerCertificateErrors.Add(ChainValidationResult.Untrusted);
filter.IgnorableServerCertificateErrors.Add(ChainValidationResult.Expired);
WinRtHttpClientHandler myHandler = new WinRtHttpClientHandler(filter);
HttpClient c = new HttpClient(myHandler);
....
I suspect this is common task for most indies, certs are seldom for tests and small apps. But it seems task is made very difficult by platform devs. Is there any reliable solution?
UWP apps cannot work with invalid (and self-signed) certificates. You may use Fiddler as proxy with fiddler's certificate for tests https.
Telerik Fiddler options - HTTPS - Capture HTTPS connects.
This work only on desktop not a mobile emulator.
I'm struggling to get my Windows 8 application to communicate with my test web API over SSL.
It seems that HttpClient/HttpClientHandler does not provide and option to ignore untrusted certificates like WebRequest enables you to (albeit in a "hacky" way with ServerCertificateValidationCallback).
Any help would be much appreciated!
A quick and dirty solution is to use the ServicePointManager.ServerCertificateValidationCallback delegate. This allows you to provide your own certificate validation. The validation is applied globally across the whole App Domain.
ServicePointManager.ServerCertificateValidationCallback +=
(sender, cert, chain, sslPolicyErrors) => true;
I use this mainly for unit testing in situations where I want to run against an endpoint that I am hosting in process and am trying to hit it with a WCF client or the HttpClient.
For production code you may want more fine grained control and would be better off using the WebRequestHandler and its ServerCertificateValidationCallback delegate property (See dtb's answer below). Or ctacke answer using the HttpClientHandler. I am preferring either of these two now even with my integration tests over how I used to do it unless I cannot find any other hook.
If you're attempting to do this in a .NET Standard library, here's a simple solution, with all of the risks of just returning true in your handler. I leave safety up to you.
var handler = new HttpClientHandler();
handler.ClientCertificateOptions = ClientCertificateOption.Manual;
handler.ServerCertificateCustomValidationCallback =
(httpRequestMessage, cert, cetChain, policyErrors) =>
{
return true;
};
var client = new HttpClient(handler);
Have a look at the WebRequestHandler Class and its ServerCertificateValidationCallback Property:
using (var handler = new WebRequestHandler())
{
handler.ServerCertificateValidationCallback = ...
using (var client = new HttpClient(handler))
{
...
}
}
If you are using System.Net.Http.HttpClient I believe correct pattern is
var handler = new HttpClientHandler()
{
ServerCertificateCustomValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator
};
var http = new HttpClient(handler);
var res = http.GetAsync(url);
Link to official doc
Most answers here suggest to use the typical pattern:
using (var httpClient = new HttpClient())
{
// do something
}
because of the IDisposable interface. Please don't!
Microsoft tells you why:
Improper Instantiation antipattern
HttpClient, HttpClientHandler, and WebRequestHandler Explained
And here you can find a detailed analysis whats going on behind the scenes:
You're using HttpClient wrong and it is destabilizing your software
Official Microsoft link: HttpClient
HttpClient is intended to be instantiated once and re-used throughout the life of an application. Instantiating an HttpClient class for every request will exhaust the number of sockets available under heavy loads. This will result in SocketException errors.
Regarding your SSL question and based on Improper Instantiation antipattern # How to fix the problem
Here is your pattern:
class HttpInterface
{
// https://learn.microsoft.com/en-us/azure/architecture/antipatterns/improper-instantiation/#how-to-fix-the-problem
// https://learn.microsoft.com/en-us/dotnet/api/system.net.http.httpclient#remarks
private static readonly HttpClient client;
// static initialize
static HttpInterface()
{
// choose one of these depending on your framework
// HttpClientHandler is an HttpMessageHandler with a common set of properties
var handler = new HttpClientHandler()
{
ServerCertificateCustomValidationCallback = delegate { return true; },
};
// derives from HttpClientHandler but adds properties that generally only are available on full .NET
var handler = new WebRequestHandler()
{
ServerCertificateValidationCallback = delegate { return true; },
ServerCertificateCustomValidationCallback = delegate { return true; },
};
client = new HttpClient(handler);
}
.....
// in your code use the static client to do your stuff
var jsonEncoded = new StringContent(someJsonString, Encoding.UTF8, "application/json");
// here in sync
using (HttpResponseMessage resultMsg = client.PostAsync(someRequestUrl, jsonEncoded).Result)
{
using (HttpContent respContent = resultMsg.Content)
{
return respContent.ReadAsStringAsync().Result;
}
}
}
Or you can use for the HttpClient in the Windows.Web.Http namespace:
var filter = new HttpBaseProtocolFilter();
#if DEBUG
filter.IgnorableServerCertificateErrors.Add(ChainValidationResult.Expired);
filter.IgnorableServerCertificateErrors.Add(ChainValidationResult.Untrusted);
filter.IgnorableServerCertificateErrors.Add(ChainValidationResult.InvalidName);
#endif
using (var httpClient = new HttpClient(filter)) {
...
}
With Windows 8.1, you can now trust invalid SSL certs. You have to either use the Windows.Web.HttpClient or if you want to use the System.Net.Http.HttpClient, you can use the message handler adapter I wrote:
http://www.nuget.org/packages/WinRtHttpClientHandler
Docs are on the GitHub:
https://github.com/onovotny/WinRtHttpClientHandler
If this is for a Windows Runtime application, then you have to add the self-signed certificate to the project and reference it in the appxmanifest.
The docs are here:
http://msdn.microsoft.com/en-us/library/windows/apps/hh465031.aspx
Same thing if it's from a CA that's not trusted (like a private CA that the machine itself doesn't trust) -- you need to get the CA's public cert, add it as content to the app then add it to the manifest.
Once that's done, the app will see it as a correctly signed cert.
Use this in Startup.cs for ASP.NET Core project:
public void ConfigureServices(IServiceCollection services)
{
// other code
services
.AddHttpClient<IMyService, MyService>(client =>
{
client.BaseAddress = new Uri(myConfiguration.BaseUrl);
})
.ConfigurePrimaryHttpMessageHandler(() =>
{
// Allowing Untrusted SSL Certificates
var handler = new HttpClientHandler();
handler.ClientCertificateOptions = ClientCertificateOption.Manual;
handler.ServerCertificateCustomValidationCallback =
(httpRequestMessage, cert, cetChain, policyErrors) => true;
return handler;
});
}
I found an example in this Kubernetes client where they were using X509VerificationFlags.AllowUnknownCertificateAuthority to trust self-signed root certificates. I slightly reworked their example to work with our own PEM encoded root certificates. Hopefully this helps someone.
namespace Utils
{
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Security;
using System.Security.Cryptography.X509Certificates;
/// <summary>
/// Verifies that specific self signed root certificates are trusted.
/// </summary>
public class HttpClientHandler : System.Net.Http.HttpClientHandler
{
/// <summary>
/// Initializes a new instance of the <see cref="HttpClientHandler"/> class.
/// </summary>
/// <param name="pemRootCerts">The PEM encoded root certificates to trust.</param>
public HttpClientHandler(IEnumerable<string> pemRootCerts)
{
foreach (var pemRootCert in pemRootCerts)
{
var text = pemRootCert.Trim();
text = text.Replace("-----BEGIN CERTIFICATE-----", string.Empty);
text = text.Replace("-----END CERTIFICATE-----", string.Empty);
this.rootCerts.Add(new X509Certificate2(Convert.FromBase64String(text)));
}
this.ServerCertificateCustomValidationCallback = this.VerifyServerCertificate;
}
private bool VerifyServerCertificate(
object sender,
X509Certificate certificate,
X509Chain chain,
SslPolicyErrors sslPolicyErrors)
{
// If the certificate is a valid, signed certificate, return true.
if (sslPolicyErrors == SslPolicyErrors.None)
{
return true;
}
// If there are errors in the certificate chain, look at each error to determine the cause.
if ((sslPolicyErrors & SslPolicyErrors.RemoteCertificateChainErrors) != 0)
{
chain.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck;
// add all your extra certificate chain
foreach (var rootCert in this.rootCerts)
{
chain.ChainPolicy.ExtraStore.Add(rootCert);
}
chain.ChainPolicy.VerificationFlags = X509VerificationFlags.AllowUnknownCertificateAuthority;
var isValid = chain.Build((X509Certificate2)certificate);
var rootCertActual = chain.ChainElements[chain.ChainElements.Count - 1].Certificate;
var rootCertExpected = this.rootCerts[this.rootCerts.Count - 1];
isValid = isValid && rootCertActual.RawData.SequenceEqual(rootCertExpected.RawData);
return isValid;
}
// In all other cases, return false.
return false;
}
private readonly IList<X509Certificate2> rootCerts = new List<X509Certificate2>();
}
}
I don't have an answer, but I do have an alternative.
If you use Fiddler2 to monitor traffic AND enable HTTPS Decryption, your development environment will not complain. This will not work on WinRT devices, such as Microsoft Surface, because you cannot install standard apps on them. But your development Win8 computer will be fine.
To enable HTTPS encryption in Fiddler2, go to Tools > Fiddler Options > HTTPS (Tab) > Check "Decrypt HTTPS Traffic".
I'm going to keep my eye on this thread hoping for someone to have an elegant solution.
I found an example online which seems to work well:
First you create a new ICertificatePolicy
using System.Security.Cryptography.X509Certificates;
using System.Net;
public class MyPolicy : ICertificatePolicy
{
public bool CheckValidationResult(ServicePoint srvPoint, X509Certificate certificate, WebRequest request,
int certificateProblem)
{
//Return True to force the certificate to be accepted.
return true;
}
}
Then just use this prior to sending your http request like so:
System.Net.ServicePointManager.CertificatePolicy = new MyPolicy();
http://www.terminally-incoherent.com/blog/2008/05/05/send-a-https-post-request-with-c/
For Xamarin Android this was the only solution that worked for me: another stack overflow post
If you are using AndroidClientHandler, you need to supply a SSLSocketFactory and a custom implementation of HostnameVerifier with all checks disabled. To do this, you’ll need to subclass AndroidClientHandler and override the appropriate methods.
internal class BypassHostnameVerifier : Java.Lang.Object, IHostnameVerifier
{
public bool Verify(string hostname, ISSLSession session)
{
return true;
}
}
internal class InsecureAndroidClientHandler : AndroidClientHandler
{
protected override SSLSocketFactory ConfigureCustomSSLSocketFactory(HttpsURLConnection connection)
{
return SSLCertificateSocketFactory.GetInsecure(1000, null);
}
protected override IHostnameVerifier GetSSLHostnameVerifier(HttpsURLConnection connection)
{
return new BypassHostnameVerifier();
}
}
And then
var httpClient = new System.Net.Http.HttpClient(new InsecureAndroidClientHandler());