7 Replies
      Latest reply: Jul 21, 2017 1:23 AM by eskimo RSS
      craigaps Level 1 Level 1 (0 points)

        Hi

        I have an iOS 10 (only) app.  The app needs to be able to support the use of self-signed certifcates for IP addresses i.e https://192.168.2.1 in a "test" mode.  The IP addresses and certifcates are spawned virtual servers and they don't have access to a DNS or a certificate authority, importantly they are for testing only.

         

        In the app info.plist I have the following entry:

        <key>NSAppTransportSecurity</key> 
          <dict> 
                <key>NSAllowsArbitraryLoads</key> 
          <true/> 
          </dict> 
        


        If the app is in "test" mode, this is the code that gets executed:

         

        class AllowSelfSignedCertificate: NSObject, URLSessionDelegate 
        { 
          private func urlSession(session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: (NSURLSession.AuthChallengeDisposition, URLCredential?) -> Void) 
          { 
            completionHandler(.useCredential, URLCredential(trust: challenge.protectionSpace.serverTrust!)) 
          } 
        } 
        
        if mode == OperationMode.test 
        { 
          let session = URLSession(configuration: URLSessionConfiguration.ephemeral, delegate: AllowSelfSignedCertificate(), delegateQueue: nil) 
        
        } 
        

        When I run the app in "test" mode I get the following error:
           The certificate for this server is invalid. You might be connecting to a server that is pretending to be "192.168.2.1" which could put your confidential information
            at risk
        When I connect the app to the same network as the test server and include NSAllowsLocalNetworking, I get the same error.  Following is the output of nsurl:

        nscurl --ats-diagnostics https:/ 
        Starting ATS Diagnostics 
        Configuring ATS Info.plist keys and displaying the result of HTTPS loads to https:/ 
        A test will "PASS" if URLSession:task:didCompleteWithError: returns a nil error. 
        Use '--verbose' to view the ATS dictionaries used and to display the error received in URLSession:task:didCompleteWithError:. 
        ================================================================================ 
        Default ATS Secure Connection 
        --- 
        ATS Default Connection 
        2016-12-28 14:52:50.763 nscurl[36062:6500411] NSURLSession/NSURLConnection HTTP load failed (kCFStreamErrorDomainSSL, -9824) 
        Result : FAIL 
        --- 
        ================================================================================ 
        Allowing Arbitrary Loads 
        --- 
        Allow All Loads 
        2016-12-28 14:52:50.794 nscurl[36062:6500411] NSURLSession/NSURLConnection HTTP load failed (kCFStreamErrorDomainSSL, -9813) 
        Result : FAIL 
        --- 
        ================================================================================ 
        Configuring TLS exceptions for 192.168.2.1 
        --- 
        TLSv1.2 
        2016-12-28 14:52:50.830 nscurl[36062:6500411] NSURLSession/NSURLConnection HTTP load failed (kCFStreamErrorDomainSSL, -9824) 
        Result : FAIL 
        --- 
        --- 
        TLSv1.1 
        2016-12-28 14:52:50.868 nscurl[36062:6500411] NSURLSession/NSURLConnection HTTP load failed (kCFStreamErrorDomainSSL, -9824) 
        Result : FAIL 
        --- 
        --- 
        TLSv1.0 
        2016-12-28 14:52:50.934 nscurl[36062:6500411] NSURLSession/NSURLConnection HTTP load failed (kCFStreamErrorDomainSSL, -9824) 
        Result : FAIL 
        --- 
        ================================================================================ 
        Configuring PFS exceptions for 192.168.2.1 
        --- 
        Disabling Perfect Forward Secrecy 
        2016-12-28 14:52:50.948 nscurl[36062:6500411] NSURLSession/NSURLConnection HTTP load failed (kCFStreamErrorDomainSSL, -9813) 
        Result : FAIL 
        --- 
        ================================================================================ 
        Configuring PFS exceptions and allowing insecure HTTP for 192.168.2.1 
        --- 
        Disabling Perfect Forward Secrecy and Allowing Insecure HTTP 
        2016-12-28 14:52:50.959 nscurl[36062:6500411] NSURLSession/NSURLConnection HTTP load failed (kCFStreamErrorDomainSSL, -9813) 
        Result : FAIL 
        --- 
        ================================================================================ 
        Configuring TLS exceptions with PFS disabled for 192.168.2.1 
        --- 
        TLSv1.2 with PFS disabled 
        2016-12-28 14:52:50.971 nscurl[36062:6500411] NSURLSession/NSURLConnection HTTP load failed (kCFStreamErrorDomainSSL, -9813) 
        Result : FAIL 
        --- 
        --- 
        TLSv1.1 with PFS disabled 
        2016-12-28 14:52:50.980 nscurl[36062:6500411] NSURLSession/NSURLConnection HTTP load failed (kCFStreamErrorDomainSSL, -9813) 
        Result : FAIL 
        --- 
        --- 
        TLSv1.0 with PFS disabled 
        2016-12-28 14:52:50.990 nscurl[36062:6500411] NSURLSession/NSURLConnection HTTP load failed (kCFStreamErrorDomainSSL, -9813) 
        Result : FAIL 
        --- 
        ================================================================================ 
        Configuring TLS exceptions with PFS disabled and insecure HTTP allowed for 192.168.2.1 
        --- 
        TLSv1.2 with PFS disabled and insecure HTTP allowed 
        2016-12-28 14:52:51.022 nscurl[36062:6500411] NSURLSession/NSURLConnection HTTP load failed (kCFStreamErrorDomainSSL, -9813) 
        Result : FAIL 
        --- 
        --- 
        TLSv1.1 with PFS disabled and insecure HTTP allowed 
        2016-12-28 14:52:51.041 nscurl[36062:6500411] NSURLSession/NSURLConnection HTTP load failed (kCFStreamErrorDomainSSL, -9813) 
        Result : FAIL 
        --- 
        --- 
        TLSv1.0 with PFS disabled and insecure HTTP allowed 
        2016-12-28 14:52:51.052 nscurl[36062:6500411] NSURLSession/NSURLConnection HTTP load failed (kCFStreamErrorDomainSSL, -9813) 
        Result : FAIL 
        --- 
        

        When I run openssl -s_client, this is the output (certificate information omitted)

        openssl s_client -connect 192.168.2.1:443 
        CONNECTED(00000003) 
        verify error:num=18:self signed certificate 
        verify return:1 
        --- 
        No client certificate CA names sent 
        --- 
        SSL handshake has read 979 bytes and written 456 bytes 
        --- 
        New, TLSv1/SSLv3, Cipher is AES128-SHA 
        Server public key is 2048 bit 
        Secure Renegotiation IS supported 
        Compression: NONE 
        Expansion: NONE 
        SSL-Session: 
            Protocol  : TLSv1 
            Cipher    : AES128-SHA 
            Session-ID: BC8C61C53D3BBBC9CEAEA468BCC54FA4C494C43192274A48E4195A80FDB36EDC 
            Session-ID-ctx: 
            Master-Key: 511F32CC76D8D7C457003B1042D9E678E159DC653D1F8AE844E6EA9A280727D0BA41E6329FFECFE628C1B8CC41EF71A0 
            Key-Arg   : None 
            Start Time: 1482901084 
            Timeout   : 300 (sec) 
            Verify return code: 18 (self signed certificate) 
        --- 
        read:errno=0 
        

         

        What ATS configuration should I adopt in the app to enable http://<ip_address> in a test operation mode?

         

        Thanks for your help

        • Re: HTTPS and IP Addresses
          eskimo Apple Staff Apple Staff (7,190 points)

          The app needs to be able to support the use of self-signed certifcates for IP addresses i.e https://192.168.2.1 in a "test" mode.

          First up, I recommend that you avoid adding a test mode to your app.  IMO, there’s a better way to do this:

          1. Create your own custom CA

          2. Use that CA to create a digital identity for your server

          3. Install that CA’s root certificate on your client devices

          This has a bunch of advantages:

          • It’s less code to write; you don’t have to override server trust evaluation on the client, disable ATS, and so on.

          • Because there’s no test code in your client, there’s no chance of you accidentally shipping an app to customers with that test code enabled.

            WARNING Don’t discount this risk.  There’s at least one ‘household name’ developer who has had to ship an emergency update to their app because they accidentally shipped it with server trust evaluation disabled.  Don’t be that guy!

          • Similarly, there’s no chance of any App Review hiccoughs because there are no ATS exceptions that you have to remember to remove in the production build.

          • Your testing is more realistic because it uses exactly the same code paths as your production app.

          If necessary you can create a test CA using facilities that are built in macOS; see Technote 2326 Creating Certificates for TLS Testing for the details.

          Also, keep in mind that most server platforms support Bonjour in some form, which allows you to use .local names to talk to the server.  And while a real CA won’t issue a certificate for a .local name, your test CA can!

          Alternatively, a CA can issue a certificate for an IP address via the Subject Alternate Name extension.


          If you ignore the above advice and continue with your current strategy, my first suggestion is that you do your testing over HTTP (rather than HTTPS).  Given that your test servers aren’t even close to your real servers, there doesn’t seem to be any drawbacks to use HTTP for this test mode.

          Note HTTP requests to an IP address will work without any ATS exceptions on iOS 10.  If you also want to test iOS 9, you’ll need to disable ATS entirely by setting NSAllowsArbitraryLoads.


          If you want to stick with HTTPS, you’ll have to take two steps to get things working:

          • Disable ATS — Again, it’s reasonable to do this using NSAllowsArbitraryLoads given that this is a dedicated test setup.

          • Disable default HTTPS server trust evaluation — You can do this using the techniques described in Technote 2232 HTTPS Server Trust Evaluation.

          Share and Enjoy

          Quinn “The Eskimo!”
          Apple Developer Relations, Developer Technical Support, Core OS/Hardware
          let myEmail = "eskimo" + "1" + "@apple.com"

            • Re: HTTPS and IP Addresses
              craigaps Level 1 Level 1 (0 points)

              Appreciate you taking the time to respond.

               

              What I would like to understand is for iOS 10 the code and configuration I posted above worked, with iOS 10.2 (14C92) the app now displays TLS errors and the session delegate on NSURLSession does not get invoked.  Are you able to explain what may have changed.

               

              Reading the Technote, it appears the simpliest approach is opening Safari in iOS and browing to the local server i.e https://192.168.0.2 and accepting the certifcate.  A dialog appears with the options "Continue", "Details" and "Cancel".  If I click "Details", "Trust" appears in the top right.  My understanding is that if I tap "Trust", the certifciate is now stored on the device but a profile is not created.


              - OR -

               

              Should I email the self-signed certificate (.cer) file so that it's accessible on the device i.e. live.com or gmail.com.  Open the attachment and follow the process to "Install" the certificate, thus creating a profile.   With a profile created my app can perform the desired network operation.  I should be able to remove App Transport Security stanza from the info.plist altogether.  And no longer need to perform server trust validation for self signed certificates (refer to my sample code in original post). Is this correct approach?

               

              Thanks again

                • Re: HTTPS and IP Addresses
                  eskimo Apple Staff Apple Staff (7,190 points)

                  What I would like to understand is for iOS 10 the code and configuration I posted above worked, with iOS 10.2 (14C92) the app now displays TLS errors and the session delegate on NSURLSession does not get invoked.

                  Which configuration?  The one with just NSAllowsArbitraryLoads set?  That should completely disable ATS, which means your session delegate callbacks should be called.  If they’re not, something else is happening.

                  Here’s how I tested this:

                  1. I created a self-signed TLS server identity and added it to my keychain.

                    Note If you don’t have such an identity handy, you can create one using the Certificate Assistant feature in Keychain Access.

                  2. I ran TLSTool to create a dummy server that uses that identity.  I’ve pasted in an example of this below.

                  3. I created a dummy app that tries to connect to that server.  I’ve pasted the source code in below.

                  4. In that app, I set NSAllowsArbitraryLoads.

                  When I tap the test button the app prints this:

                  … task session challenge NSURLAuthenticationMethodServerTrust
                  …
                  … error Error Domain=NSURLErrorDomain Code=-1202 …
                  

                  As you can see, the session delegate is called before the request fails.

                  Please try this for yourself and let me know what you see.

                  Share and Enjoy

                  Quinn “The Eskimo!”
                  Apple Developer Relations, Developer Technical Support, Core OS/Hardware
                  let myEmail = "eskimo" + "1" + "@apple.com"


                  $ TLSTool s_server -cert biff.local
                  

                  Note In this example, biff.local is the name of the TLS server identity from step 1.


                  import UIKit

                  class ViewController: UIViewController, URLSessionDelegate {

                  var session: URLSession! = nil
                  
                  @IBAction func testAction(_ sender: Any) {
                      if self.session == nil {
                          let config = URLSessionConfiguration.default
                  
                          // Don't cache, which avoids complications during testing. 
                  
                          config.requestCachePolicy = .reloadIgnoringLocalCacheData
                  
                          self.session = URLSession(configuration: config, delegate: self, delegateQueue: OperationQueue.main)
                      }
                  
                      let request = URLRequest(url: URL(string: "https://192.168.1.189:4433")!)
                      self.session!.dataTask(with: request) { (data, response, error) in
                          if let error = error {
                              NSLog("error %@", error as NSError)
                          } else {
                              NSLog("success")
                          }
                      }.resume()
                  }
                  
                  func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
                      NSLog("task session challenge %@", challenge.protectionSpace.authenticationMethod)
                      completionHandler(.performDefaultHandling, nil)
                  }
                  

                  }

                  Note In this example, 192.168.1.189 is the IP address of my Mac running `TLSTool.


                    • Re: HTTPS and IP Addresses
                      craigaps Level 1 Level 1 (0 points)

                      Hi eskimo

                       

                      I was able to reproduce your example, thanks

                       

                      The big difference between the example code and the code running in my app is the assignment of URLSessionDelegate and I was able to reproduce that in the example as well.

                       

                      class ViewController: UIViewController {
                          var ignoreSslCerts: URLSessionDelegate? = nil
                      
                          @IBAction func onSwitchClick(_ sender: UISwitch)
                          {
                              ignoreSslCerts = sender.isOn ? AllowSelfSignedCertificate() : nil
                          }
                      
                          @IBAction func onButtonClick(_ sender: UIButton)
                          {
                            let config = URLSessionConfiguration.default
                            config.requestCachePolicy = .reloadIgnoringLocalCacheData
                      
                              let session  =  URLSession(configuration: config, delegate: ignoreSslCerts, delegateQueue: OperationQueue.main)
                      
                              var request = URLRequest(url: URL(string: "https://192.168.0.11:4433"
                              request.timeoutInterval = 10
                              session.dataTask(with: request)
                              {
                                  (data, response, error) in
                                  if let error = error
                                  {
                                      NSLog("error %@", error as NSError)
                                  }
                                  else
                                  {
                                      NSLog("success")
                                  }
                              }.resume()
                          }
                      }
                      
                      class AllowSelfSignedCertificate: NSObject, URLSessionDelegate
                      {
                          private func urlSession(session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: (NSURLSession.AuthChallengeDisposition, URLCredential?) -> Void)
                          {
                              completionHandler(.useCredential, URLCredential(trust: challenge.protectionSpace.serverTrust!))
                          }
                      }
                      
                      
                      

                       

                      I added a UISwitch controll to toogle the assignement of AllowSelfSignedCertifcate to ignoreSslCerts when the "isOn" property is true.

                       

                      On the onButtonClick event, regardless if ignoreSslCerts is an instance of AllowSelfSignedCertificate or nil, the delegate does not get invoked.  In saying that, the difference is the urlSession signature.

                       

                      Your example

                      func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
                      
                      
                      

                      My example

                      private func urlSession(session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: (NSURLSession.AuthChallengeDisposition, URLCredential?) -> Void)
                      
                      
                      

                       

                      Once I updated my urlSession the function gets invoked .  I guess that was the change I was looking for.

                       

                      Is it your opinion, that I should remove the NSAppTransportSecurity stanza from info.plist and to manually get the self-signed certifcate onto the test device to prevent having to handle URLSessionDelegate?

                       

                      Many thanks

                        • Re: HTTPS and IP Addresses
                          eskimo Apple Staff Apple Staff (7,190 points)

                          Is it your opinion, that I should remove the NSAppTransportSecurity stanza from info.plist and to manually get the self-signed certifcate onto the test device to prevent having to handle URLSessionDelegate?

                          Yes.  Writing code to override HTTPS server trust evaluation is extra work and it carries with it significant security riks.  If you can avoid doing so, you should.

                          Share and Enjoy

                          Quinn “The Eskimo!”
                          Apple Developer Relations, Developer Technical Support, Core OS/Hardware
                          let myEmail = "eskimo" + "1" + "@apple.com"

                    • Re: HTTPS and IP Addresses
                      fbele Level 1 Level 1 (0 points)

                      Hi eskimo,

                       

                      I have seen many of your topics related to this issue of connecting to the server, but somehow I still don't manage to get it working.

                      I have a development server, which already has a self-signed certificate, which can be reached either through an URL or locally (when one is in local network) through the IP. I don't know if that is important, but the URL, however, uses a Subdomain and Port.

                       

                      I have tried to set Allow Arbitrary Loads setting to App Transport Security Settings in my Xcode but it or added the Exception Domains, but nothing seems to work. I always get the same error in the console: NSURLSession/NSURLConnection HTTP load failed (kCFStreamErrorDomainSSL, -9813)

                       

                      Now, I really don't want to do any hard-coding to bypass these security evaluations, I don't think it's the right way and I am sure, that there should be another solution. One of which, should be this one with the making of certificates.

                       

                      I already created a custom CA. Based on this documentation, one must then with this CA create digital identities (certificates) for client and the server.

                      But after this point, I am not exactly sure, what must I do with them and, what should be uploaded where?

                       

                      You mentioned, that the root certificate of CA should be uploaded on each test device. What should I do with digital identities? I think this part of documentation is missing and I couldn't find it anywhere.

                       

                      Many thanks in advance.

                       

                      Best regards,

                      fbele

                        • Re: HTTPS and IP Addresses
                          eskimo Apple Staff Apple Staff (7,190 points)

                          OK, let me explain how I set this up:

                          1. I use Certificate Assistant to create a custom CA per Technote 2326 Creating Certificates for TLS Testing.

                          2. I use that to issue a digital identity for my server, deep-thought.local.  TN2326 has the details on doing this as well.

                          3. I set up a Mac to act as my server.  I set its local DNS name to deep-thought.local via the Sharing preferences panel.

                          4. I set up my server software on that Mac.

                          5. I install the digital identity from step 2 as the digital identity for my server.  How I do this depends on the server I’m using.  For example:

                            • When I’m using Apache, I convert the digital identity to a PEM certificate and private key and put those values into the files referenced by SSLCertificateFile and SSLCertificateKeyFile (typically server.crt and server.key, respectively).

                            For more info on this, you should consult the Apache docs.

                            • When I’m using TLSTool, it can use the digital identity directly from my Mac’s keychain.
                          6. I install the certificate authority’s root certificate on my device (or simulator) per QA1948 HTTPS and Test Servers.

                            IMPORTANT On iOS 10.3 and later you have to explicitly enable the certificate authority in Settings > General > About > Certificate Trust Settings.  At some point I will update QA1948 to cover that but that process is more complex than you might think (specifically, QA1948 covers all of our platforms and some of those platforms — yes, I’m looking at you, tvOS! — make this hard).

                          At this point I can write NSURLSession code that fetches https://deep-thought.local without any HTTPS server trust evaluation overrides.

                          Please try this out and post back if you hit any snags.

                          Share and Enjoy

                          Quinn “The Eskimo!”
                          Apple Developer Relations, Developer Technical Support, Core OS/Hardware
                          let myEmail = "eskimo" + "1" + "@apple.com"