Trust a self signed certificate using Httpclient

前端 未结 1 696
粉色の甜心
粉色の甜心 2020-12-06 03:40

I\'m attempting to make a web request that\'s failing because of a self signed certificate :

Client = new HttpClient(); 
HttpResponseMessage Response = await         


        
相关标签:
1条回答
  • 2020-12-06 03:48

    I have seen so many question regarding this I figured I write up as a complete answer and example as I can.

    Note: Using WKWebView with self-sign certs, see this answer

    HttpClient Implementation

    Note: Using badssl.com in this example

    Managed (Default)

    System.Net.Http.HttpRequestException: An error occurred while sending the request ---> System.Net.WebException: Error: TrustFailure (One or more errors occurred.) ---> System.AggregateException: One or more errors occurred. ---> System.Security.Authentication.AuthenticationException: A call to SSPI failed, see inner exception. ---> Mono.Security.Interface.Tl

    The original Mono Managed provider is getting really long in the tooth and only supports TLS1.0, in terms of security & performance I would move to using the NSUrlSession implementation.

    CFNetwork (iOS 6+)

    Note: As this iOS version is fairly old now and I personally do not target it anymore, so I leave this blank... (unless someone really needs me to lookup my notes for it ;-)

    NSUrlSession (iOS 7+)

    Xamarin provides a HttpMessageHandler subclass (NSUrlSessionHandler) that is based upon iOS' NSUrlSession.

    Using it by itself against a self-signed cert will result in:

    System.Net.WebException: An SSL error has occurred and a secure connection to the server cannot be made. ---> Foundation.NSErrorException: Exception of type 'Foundation.NSErrorException' was thrown.

    The problem is that a self-sign cert is considered insecure and non-trusted by iOS, thus you have to apply an ATS exception to your app so iOS knows that your app is untrusted in the 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 that your app is making untrusted calls, a HttpClient request will now result in this error:

    System.Net.WebException: The certificate for this server is invalid. You might be connecting to a server that is pretending to be‚ self-signed.badssl.com‚ which could put your confidential information at risk. ---> Foundation.NSErrorException: Exception of type 'Foundation.NSErrorException' was thrown.

    This error is due to the fact that even though the ATS exception has been allow, the default NSUrlSession provided by iOS will apply its standard NSUrlAuthenticationChallenge to the certificate and fail since a self-signed cert can never be truly authenticated (even via client pinning) since it does not include a root certificate authority (CA) in its chain that is trusted by iOS.

    Thus you need to intercept and bypass the certificate security checking provided by iOS (Yes, a big security alert, flashing red lights, etc...)

    But, you can do this via creating a NSUrlSessionDataDelegate subclass that does the bypass.

    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 that NSUrlSessionDataDelegate to a NSUrlSession and use that new session in the creation of your NSUrlSessionHandler that will be provided in the constructor of the 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: Example only, normally you would create a single Delegate, NSUrlSession, HttpClient, NSUrlSessionHandler and re-use it for all your requests (i.e. Singleton pattern)

    Your request now works:

    <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>
    

    Note: The option to supply your own custom NSUrlSession to Xamarin's NSUrlSessionHandler is really new (Nov. 2017) and not currently in a release build (alpha, beta or stable), but of course, source is available at:

    • xamarin-macios/src/Foundation/NSUrlSessionHandler.cs

    Using NSUrlSession instead of HttpClient:

    You can also directly use a NSUrlSession instead of HttpClient against a self-signed cert.

    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: Example only, normally you would create a single Delegate and NSUrlSession and re-use it for all your requests, i.e. Singleton pattern

    Real Solution? Use Free Secure Certificates:

    IHMO, avoid self-signed certs all together, even in a development environment and use one of the free certificate services and avoid all the headaches of applying ATS exceptions, custom code to intercept/bypass iOS security, etc... and make your app web services actually secure.

    I personally use Let’s Encrypt:

    • https://letsencrypt.org
    0 讨论(0)
提交回复
热议问题