Search code examples
iosuiwebviewios9cdnapp-transport-security

Files from cdnjs.cloudflare.com blocked in UIWebView in iOS9


I'm using a UIWebView to load a website in my iOS app. After updating to iOS 9 and building the app accordingly the website still loads fine from my server, but fails to load the css and js files of bootstrap from cdnjs (e.g. https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.5/css/bootstrap.min.css).

I assume this is related to the new ATS but so far I failed to figure out why it fails to load files from cdnjs (they support TLS 1.2 and plenty of modern ciphers) and I don't have an El Capitan with nscurl available to test cdnjs (see https://developer.apple.com/library/prerelease/ios/technotes/App-Transport-Security-Technote/).


Solution

  • I ran a nscurl on El Capitan for you. Based on the result the issue is that cdnjs does not suport Forward Secrecy and therefor you'll have to add an exception for cdnjs.cloudflare.com and set NSAppTransportSecurity to false.

    Update: The following lines should be enough:

      <key>NSAppTransportSecurity</key>
      <dict>
        <key>NSExceptionDomains</key>  
        <dict>  
          <key>cdnjs.cloudflare.com</key>  
          <dict>  
            <key>NSExceptionRequiresForwardSecrecy</key><false/>  
          </dict>  
        </dict>
      </dict>
    

    See the log below for more details:

    nscurl --ats-diagnostics --verbose https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.5/css/bootstrap.min.css
    Starting ATS Diagnostics
    
    Configuring ATS Info.plist keys and displaying the result of HTTPS loads to https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.5/css/bootstrap.min.css.
    A test will "PASS" if URLSession:task:didCompleteWithError: returns a nil error.
    ================================================================================
    
    Default ATS Secure Connection
    ---
    ATS Default Connection
    ATS Dictionary:
    {
    }
    2015-09-29 14:59:16.983 nscurl[490:10186] NSURLSession/NSURLConnection HTTP load failed (kCFStreamErrorDomainSSL, -9802)
    Result : FAIL
    Error : Error Domain=NSURLErrorDomain Code=-1200 "An SSL error has occurred and a secure connection to the server cannot be made." UserInfo={NSURLErrorFailingURLPeerTrustErrorKey=<SecTrust 0x7f7f89e052f0 [0x7fff7c50f890]>, NSLocalizedRecoverySuggestion=Would you like to connect to the server anyway?, _kCFStreamErrorDomainKey=3, _kCFStreamErrorCodeKey=-9802, NSErrorPeerCertificateChainKey=(
        "<SecCertificate 0x7f7f89e04570 [0x7fff7c50f890]>",
        "<SecCertificate 0x7f7f89e04950 [0x7fff7c50f890]>"
    ), NSUnderlyingError=0x7f7f8b8059b0 {Error Domain=kCFErrorDomainCFNetwork Code=-1200 "(null)" UserInfo={_kCFStreamPropertySSLClientCertificateState=0, kCFStreamPropertySSLPeerTrust=<SecTrust 0x7f7f89e052f0 [0x7fff7c50f890]>, _kCFNetworkCFStreamSSLErrorOriginalValue=-9802, _kCFStreamErrorDomainKey=3, _kCFStreamErrorCodeKey=-9802, kCFStreamPropertySSLPeerCertificates=(
        "<SecCertificate 0x7f7f89e04570 [0x7fff7c50f890]>",
        "<SecCertificate 0x7f7f89e04950 [0x7fff7c50f890]>"
    )}}, NSLocalizedDescription=An SSL error has occurred and a secure connection to the server cannot be made., NSErrorFailingURLKey=https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.5/css/bootstrap.min.css, NSErrorFailingURLStringKey=https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.5/css/bootstrap.min.css, NSErrorClientCertificateStateKey=0}
    ---
    
    ================================================================================
    
    Allowing Arbitrary Loads
    
    ---
    Allow All Loads
    ATS Dictionary:
    {
        NSAllowsArbitraryLoads = true;
    }
    Result : PASS
    ---
    
    ================================================================================
    
    Configuring TLS exceptions for cdnjs.cloudflare.com
    
    ---
    TLSv1.2
    ATS Dictionary:
    {
        NSExceptionDomains =     {
            "cdnjs.cloudflare.com" =         {
                NSExceptionMinimumTLSVersion = "TLSv1.2";
            };
        };
    }
    2015-09-29 14:59:17.140 nscurl[490:10186] NSURLSession/NSURLConnection HTTP load failed (kCFStreamErrorDomainSSL, -9802)
    Result : FAIL
    Error : Error Domain=NSURLErrorDomain Code=-1200 "An SSL error has occurred and a secure connection to the server cannot be made." UserInfo={NSURLErrorFailingURLPeerTrustErrorKey=<SecTrust 0x7f7f89f136f0 [0x7fff7c50f890]>, NSLocalizedRecoverySuggestion=Would you like to connect to the server anyway?, _kCFStreamErrorDomainKey=3, _kCFStreamErrorCodeKey=-9802, NSErrorPeerCertificateChainKey=(
        "<SecCertificate 0x7f7f89f12b20 [0x7fff7c50f890]>",
        "<SecCertificate 0x7f7f89f12d60 [0x7fff7c50f890]>"
    ), NSUnderlyingError=0x7f7f8b804160 {Error Domain=kCFErrorDomainCFNetwork Code=-1200 "(null)" UserInfo={_kCFStreamPropertySSLClientCertificateState=0, kCFStreamPropertySSLPeerTrust=<SecTrust 0x7f7f89f136f0 [0x7fff7c50f890]>, _kCFNetworkCFStreamSSLErrorOriginalValue=-9802, _kCFStreamErrorDomainKey=3, _kCFStreamErrorCodeKey=-9802, kCFStreamPropertySSLPeerCertificates=(
        "<SecCertificate 0x7f7f89f12b20 [0x7fff7c50f890]>",
        "<SecCertificate 0x7f7f89f12d60 [0x7fff7c50f890]>"
    )}}, NSLocalizedDescription=An SSL error has occurred and a secure connection to the server cannot be made., NSErrorFailingURLKey=https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.5/css/bootstrap.min.css, NSErrorFailingURLStringKey=https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.5/css/bootstrap.min.css, NSErrorClientCertificateStateKey=0}
    ---
    
    ---
    TLSv1.1
    ATS Dictionary:
    {
        NSExceptionDomains =     {
            "cdnjs.cloudflare.com" =         {
                NSExceptionMinimumTLSVersion = "TLSv1.1";
            };
        };
    }
    2015-09-29 14:59:17.170 nscurl[490:10186] NSURLSession/NSURLConnection HTTP load failed (kCFStreamErrorDomainSSL, -9802)
    Result : FAIL
    Error : Error Domain=NSURLErrorDomain Code=-1200 "An SSL error has occurred and a secure connection to the server cannot be made." UserInfo={NSURLErrorFailingURLPeerTrustErrorKey=<SecTrust 0x7f7f89c2a2a0 [0x7fff7c50f890]>, NSLocalizedRecoverySuggestion=Would you like to connect to the server anyway?, _kCFStreamErrorDomainKey=3, _kCFStreamErrorCodeKey=-9802, NSErrorPeerCertificateChainKey=(
        "<SecCertificate 0x7f7f89c47750 [0x7fff7c50f890]>",
        "<SecCertificate 0x7f7f89c45c90 [0x7fff7c50f890]>"
    ), NSUnderlyingError=0x7f7f8b9029d0 {Error Domain=kCFErrorDomainCFNetwork Code=-1200 "(null)" UserInfo={_kCFStreamPropertySSLClientCertificateState=0, kCFStreamPropertySSLPeerTrust=<SecTrust 0x7f7f89c2a2a0 [0x7fff7c50f890]>, _kCFNetworkCFStreamSSLErrorOriginalValue=-9802, _kCFStreamErrorDomainKey=3, _kCFStreamErrorCodeKey=-9802, kCFStreamPropertySSLPeerCertificates=(
        "<SecCertificate 0x7f7f89c47750 [0x7fff7c50f890]>",
        "<SecCertificate 0x7f7f89c45c90 [0x7fff7c50f890]>"
    )}}, NSLocalizedDescription=An SSL error has occurred and a secure connection to the server cannot be made., NSErrorFailingURLKey=https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.5/css/bootstrap.min.css, NSErrorFailingURLStringKey=https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.5/css/bootstrap.min.css, NSErrorClientCertificateStateKey=0}
    ---
    
    ---
    TLSv1.0
    ATS Dictionary:
    {
        NSExceptionDomains =     {
            "cdnjs.cloudflare.com" =         {
                NSExceptionMinimumTLSVersion = "TLSv1.0";
            };
        };
    }
    2015-09-29 14:59:17.206 nscurl[490:10186] NSURLSession/NSURLConnection HTTP load failed (kCFStreamErrorDomainSSL, -9802)
    Result : FAIL
    Error : Error Domain=NSURLErrorDomain Code=-1200 "An SSL error has occurred and a secure connection to the server cannot be made." UserInfo={NSURLErrorFailingURLPeerTrustErrorKey=<SecTrust 0x7f7f8b905230 [0x7fff7c50f890]>, NSLocalizedRecoverySuggestion=Would you like to connect to the server anyway?, _kCFStreamErrorDomainKey=3, _kCFStreamErrorCodeKey=-9802, NSErrorPeerCertificateChainKey=(
        "<SecCertificate 0x7f7f8b904590 [0x7fff7c50f890]>",
        "<SecCertificate 0x7f7f8b9047d0 [0x7fff7c50f890]>"
    ), NSUnderlyingError=0x7f7f89d5fce0 {Error Domain=kCFErrorDomainCFNetwork Code=-1200 "(null)" UserInfo={_kCFStreamPropertySSLClientCertificateState=0, kCFStreamPropertySSLPeerTrust=<SecTrust 0x7f7f8b905230 [0x7fff7c50f890]>, _kCFNetworkCFStreamSSLErrorOriginalValue=-9802, _kCFStreamErrorDomainKey=3, _kCFStreamErrorCodeKey=-9802, kCFStreamPropertySSLPeerCertificates=(
        "<SecCertificate 0x7f7f8b904590 [0x7fff7c50f890]>",
        "<SecCertificate 0x7f7f8b9047d0 [0x7fff7c50f890]>"
    )}}, NSLocalizedDescription=An SSL error has occurred and a secure connection to the server cannot be made., NSErrorFailingURLKey=https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.5/css/bootstrap.min.css, NSErrorFailingURLStringKey=https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.5/css/bootstrap.min.css, NSErrorClientCertificateStateKey=0}
    ---
    
    ================================================================================
    
    Configuring PFS exceptions for cdnjs.cloudflare.com
    
    ---
    Disabling Perfect Forward Secrecy
    ATS Dictionary:
    {
        NSExceptionDomains =     {
            "cdnjs.cloudflare.com" =         {
                NSExceptionRequiresForwardSecrecy = false;
            };
        };
    }
    Result : PASS
    ---
    
    ================================================================================
    
    Configuring PFS exceptions and allowing insecure HTTP for cdnjs.cloudflare.com
    
    ---
    Disabling Perfect Forward Secrecy and Allowing Insecure HTTP
    ATS Dictionary:
    {
        NSExceptionDomains =     {
            "cdnjs.cloudflare.com" =         {
                NSExceptionAllowsInsecureHTTPLoads = true;
                NSExceptionRequiresForwardSecrecy = false;
            };
        };
    }
    Result : PASS
    ---
    
    ================================================================================
    
    Configuring TLS exceptions with PFS disabled for cdnjs.cloudflare.com
    
    ---
    TLSv1.2 with PFS disabled
    ATS Dictionary:
    {
        NSExceptionDomains =     {
            "cdnjs.cloudflare.com" =         {
                NSExceptionMinimumTLSVersion = "TLSv1.2";
                NSExceptionRequiresForwardSecrecy = false;
            };
        };
    }
    Result : PASS
    ---
    
    ---
    TLSv1.1 with PFS disabled
    ATS Dictionary:
    {
        NSExceptionDomains =     {
            "cdnjs.cloudflare.com" =         {
                NSExceptionMinimumTLSVersion = "TLSv1.1";
                NSExceptionRequiresForwardSecrecy = false;
            };
        };
    }
    Result : PASS
    ---
    
    ---
    TLSv1.0 with PFS disabled
    ATS Dictionary:
    {
        NSExceptionDomains =     {
            "cdnjs.cloudflare.com" =         {
                NSExceptionMinimumTLSVersion = "TLSv1.0";
                NSExceptionRequiresForwardSecrecy = false;
            };
        };
    }
    Result : PASS
    ---
    
    ================================================================================
    
    Configuring TLS exceptions with PFS disabled and insecure HTTP allowed for cdnjs.cloudflare.com
    
    ---
    TLSv1.2 with PFS disabled and insecure HTTP allowed
    ATS Dictionary:
    {
        NSExceptionDomains =     {
            "cdnjs.cloudflare.com" =         {
                NSExceptionAllowsInsecureHTTPLoads = true;
                NSExceptionMinimumTLSVersion = "TLSv1.2";
                NSExceptionRequiresForwardSecrecy = false;
            };
        };
    }
    Result : PASS
    ---
    
    ---
    TLSv1.1 with PFS disabled and insecure HTTP allowed
    ATS Dictionary:
    {
        NSExceptionDomains =     {
            "cdnjs.cloudflare.com" =         {
                NSExceptionAllowsInsecureHTTPLoads = true;
                NSExceptionMinimumTLSVersion = "TLSv1.1";
                NSExceptionRequiresForwardSecrecy = false;
            };
        };
    }
    Result : PASS
    ---
    
    ---
    TLSv1.0 with PFS disabled and insecure HTTP allowed
    ATS Dictionary:
    {
        NSExceptionDomains =     {
            "cdnjs.cloudflare.com" =         {
                NSExceptionAllowsInsecureHTTPLoads = true;
                NSExceptionMinimumTLSVersion = "TLSv1.0";
                NSExceptionRequiresForwardSecrecy = false;
            };
        };
    }
    Result : PASS
    ---
    
    ================================================================================