Use Httpclient to trust self-signed certificates
I'm trying to make a web request that fails due to a self-signed certificate:
Client = new HttpClient();
HttpResponseMessage Response = await Client.GetAsync(Uri)//defined elsewhere
This will throw a trust failure exception.
I tried allowing untrusted SSL certificates again using HttpClient httpclienthandler
as suggested here :
var handler = new HttpClientHandler();
handler.ServerCertificateCustomValidationCallback =
(
HttpRequestMessage message,
X509Certificate2 cert,
X509Chain chain,
SslPolicyErrors errors
) =>{return true; };//remove if this makes it to production
Client = new HttpClient(handler);
This throws an exception not implemented by the system.
Is there any other way to trust a self-signed certificate? I even installed the certificate on the machine making the request, but no luck.
I've seen a lot of questions about this and thought I'd write an answer and example as complete as possible.
Note: See this answer when working with WKWebView
self-signed certificates
HttpClient implementation
Note: badssl.com is used in this example
managed (default)
System.Net.Http.HttpRequestException: Error sending request ---> System.Net.WebException: Error: TrustFailure(One or more errors occurred.) ---> System.AggregateException: One or more errors occurred . ---> System.Security.Authentication.AuthenticationException: The call to SSPI failed, see inner exception. ---> Mono.Security.Interface.Tl
From a security and performance standpoint, the original Mono Managed
provider has gone into real long -term and only supports TLS1.0, I'm going to move to using the NSUrlSession implementation.
CFNetwork(iOS 6+)
NOTE: Since this iOS version is pretty old now, I personally don't target it anymore, so I leave it blank... (unless someone really needs me to look up notes for it ;-)
NSUrlSession(iOS 7+)
Xamarin provides a subclass HttpMessageHandler
based on iOS' ( NSUrlSessionHandler
) NSUrlSession
.
Using it alone for self-signed certificates results in:
System.Net.WebException: An SSL error occurred and a secure connection to the server could not be established. ---> Foundation.NSErrorException: An exception of type 'Foundation.NSErrorException' was thrown.
The problem is that self-signed certificates are considered insecure and not trusted by iOS, so you must apply an ATS exception to your app so that iOS knows your app is not trusted Info.plist
.
<key>NSAppTransportSecurity</key>
<dict>
<key>NSExceptionDomains</key>
<dict>
<key>self-signed.badssl.com</key>
<dict>
<key>NSExceptionAllowsInsecureHTTPLoads</key>
<true/>
</dict>
</dict>
</dict>
Now that iOS knows your app is making an untrusted call, a request will HttpClient
now result in this error:
System.Net.WebException: The server's certificate is invalid. You may be connecting to a server pretending to be self-signed.badssl.com, which could put your confidential information at risk. ---> Foundation.NSErrorException: An exception of type 'Foundation.NSErrorException' was thrown.
This error is due to the fact that even if the ATS exception is allowed , the default settings provided by iOS NSUrlSession
will apply its criteria NSUrlAuthenticationChallenge
to the certificate and will fail because a self-signed certificate can never be truly authenticated (even with client pinning ) because it doesn't include an iOS-trusted root certificate authority (CA) in its chain.
So you need to intercept and bypass the certificate security checks provided by iOS (yes, big security alerts, flashing red lights, etc.)
However , you can do this by creating a subclass that NSUrlSessionDataDelegate
bypasses .
public class SelfSignedSessionDataDelegate : NSUrlSessionDataDelegate, INSUrlSessionDelegate
{
const string host = "self-signed.badssl.com";
public override void DidReceiveChallenge(NSUrlSession session, NSUrlAuthenticationChallenge challenge, Action<NSUrlSessionAuthChallengeDisposition, NSUrlCredential> completionHandler)
{
switch (challenge.ProtectionSpace.Host)
{
case host:
using (var cred = NSUrlCredential.FromTrust(challenge.ProtectionSpace.ServerSecTrust))
{
completionHandler.Invoke(NSUrlSessionAuthChallengeDisposition.UseCredential, cred);
}
break;
default:
completionHandler.Invoke(NSUrlSessionAuthChallengeDisposition.PerformDefaultHandling, null);
break;
}
}
}
Now you need to apply it to NSUrlSessionDataDelegate
, NSUrlSession
and use that new session in the creation, which NSUrlSessionHandler
will be provided in the constructor of HttpClient
.
var url = "https://self-signed.badssl.com";
using (var selfSignedDelegate = new SelfSignedSessionDataDelegate())
using (var session = NSUrlSession.FromConfiguration(NSUrlSession.SharedSession.Configuration, (INSUrlSessionDelegate)selfSignedDelegate, NSOperationQueue.MainQueue))
using (var handler = new NSUrlSessionHandler(session))
using (var httpClient = new HttpClient(handler))
using (var response = await httpClient.GetAsync(url))
using (var content = response.Content)
{
var result = await content.ReadAsStringAsync();
Console.WriteLine(result);
}
Note: Just as an example, typically you would create a Delegate, NSUrlSession, HttpClient, NSUrlSessionHandler and reuse it for all requests (ie. Singleton pattern)
Your request is now valid:
<html>
<head>
<title>self-signed.badssl.com</title>
</head>
<body><div id="content"><h1 style="font-size: 12vw;">
self-signed.<br>badssl.com
</h1></div>
</body>
</html>
Notice:NSUrlSession
Providing your own customization options to Xamarin NSUrlSessionHandler
is brand new (November 2017) and is not currently in a release version (alpha, beta or stable), but of course sources are available from:
Use NSUrlSession
instead HttpClient
:
You can also use a directly NSUrlSession
instead HttpClient
of a certificate for a self-signed certificate.
var url = "https://self-signed.badssl.com";
using (var selfSignedDelegate = new SelfSignedSessionDataDelegate())
using (var session = NSUrlSession.FromConfiguration(NSUrlSession.SharedSession.Configuration, (INSUrlSessionDelegate)selfSignedDelegate, NSOperationQueue.MainQueue))
{
var request = await session.CreateDataTaskAsync(new NSUrl(url));
var cSharpString = NSString.FromData(request.Data, NSStringEncoding.UTF8).ToString();
Console.WriteLine(cSharpString);
}
Note: Just as an example, typically you would create a Delegate and NSUrlSession and reuse it for all requests, i.e. Singleton pattern
real solution? Use a free security certificate:
IHMO, even in a development environment, avoid using self-signed certificates all together and use one of the free certificate services and avoid all the hassle of applying ATS exceptions, custom code to intercept/bypass iOS security, and make your Application web services are actually safe.
I personally use Let's Encrypt: