NSURLSession error with ATS

Hi,


I have an IOS App connecting to a server (implemented in a web container) using NSURLSession. All communications are using HTTPs, the server is using TLSV1.2 with an accepted ciphers (see below for details).


I'm using IOS 9 beta3 (on iPad Air) and XCode 7 beta 3.


When starting the communication with the server I have always the same error:


2015-07-21 22:35:47.452 CellMonitoring[990:31106] Session download has failed : Error Domain=NSURLErrorDomain Code=-1200 "An SSL error has occurred and a secure connection to the server cannot be made." UserInfo=0x7fa96d004cb0 {NSErrorFailingURLStringKey=https://localhost:8443/MyServer/App, NSLocalizedRecoverySuggestion=Would you like to connect to the server anyway?, _kCFStreamErrorDomainKey=3, NSErrorFailingURLKey=https://localhost:8443/MyServer/App, _kCFStreamErrorCodeKey=-9802, NSLocalizedDescription=An SSL error has occurred and a secure connection to the server cannot be made.}


Below is the result of the openSSL command to display information about my server.


$ openssl s_client -host localhost -port 8443 -status

...

-----END CERTIFICATE-----

subject=XXXXX

issuer=YYYY

---

No client certificate CA names sent

---

SSL handshake has read 1465 bytes and written 510 bytes

---

New, TLSv1/SSLv3, Cipher is ECDHE-RSA-AES128-GCM-SHA256

Server public key is 2048 bit

Secure Renegotiation IS supported

Compression: NONE

Expansion: NONE

SSL-Session:

Protocol : TLSv1.2

Cipher : ECDHE-RSA-AES128-GCM-SHA256

Session-ID: 55AEB2F8B99041A2B5975777FE793EB837AFB20BD7D0E00AA05A3F7ACB40BD38

Session-ID-ctx:

Master-Key: 6D77D7F9FAD54CFBD202806C927E188E0043306CB36F405FAA6F15E75F494BAA50F0B5E5D3BA2060D14BB14EE0EA239E

Key-Arg : None

PSK identity: None

PSK identity hint: None

SRP username: None

Start Time: 1437512440

Timeout : 300 (sec)

Verify return code: 18 (self signed certificate)

---

closed


Below is the code I'm using for didReceiveChallenge, is there anything wrong or missing? I'm lost, I don't know how to progress :-(


#pragma mark - NSURLSessionDelegate Protocol
- (void)URLSession:(NSURLSession *)session
didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler {
    [RequestURLUtilities dumpChallenge:challenge];
    if (challenge.protectionSpace != Nil) {
        NSURLProtectionSpace *protection = challenge.protectionSpace;
        SecTrustRef sslState = protection.serverTrust;
        if (sslState == Nil) {
            NSLog(@"%s Warning: empty serverTrust",__PRETTY_FUNCTION__);
        }
        if ([protection.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
            NSLog(@"%s => NSURLAuthenticationMethodServerTrust", __PRETTY_FUNCTION__);
           
            NSURLCredential* credential = [NSURLCredential credentialForTrust:sslState];
           
           
           completionHandler(NSURLSessionAuthChallengeUseCredential, credential);
        } else {
            NSLog(@"%s => Called for another challenge", __PRETTY_FUNCTION__);
           completionHandler(NSURLSessionAuthChallengePerformDefaultHandling, NULL);
        }
      /
    }
}
Answered by DTS Engineer in 31673022

The App I'm developping is only for internal use in our Company and the server is also running in our private network. As a consequence we don't plan to get a certificate issued by a trusted CA.

Ah, that's a different story. In situations like this you should create a CA for your company, if you don't already have one, and have the server's certificate issued by a CA. You can then install the company CA's root certificate on the device as a system-trusted root (typically via MDM). Once you do that, ATS will trust your company CA's issued certificates, just like it trusts certificate's issued by any other CA whose root is installed on, or built in to, the system.

I tested this approach on a device here in my office, using a CA I maintain for my own personal server, and it worked just fine.

if I use an NSExceptionDomains the following it doesn't work, what is wrong?

192.168.***.YYY

I presume that the Info.plist doesn't contain "***" and "YYY" literally, but rather it has the actual numbers of your IP address.

Regardless, last I checked NSExceptionDomains is supposed to support IP addresses but does not due to a bug. I typically work around this by using the .local name.

Share and Enjoy

Quinn "The Eskimo!"
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

You seem to be using a self-signed certificate. Is that your final plan? Or is that just for initial bring up?

Share and Enjoy

Quinn "The Eskimo!"
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

I also started to experience SSL problem after installing XCode 7 Beta 4. The server is AWS S3, which has been supporting https (TLS 1.2) ... here is some info from openssl:


$ openssl s_client -host s3.amazonaws.com -port 443
....
subject=/C=US/ST=Washington/L=Seattle/O=Amazon.com, Inc./CN=s3.amazonaws.com
issuer=/C=US/O=VeriSign, Inc./OU=VeriSign Trust Network/OU=Terms of use at https : //www.verisign.com/rpa  (c)10/CN=VeriSign Class 3 Secure Server CA - G3
---
No client certificate CA names sent
---
SSL handshake has read 4588 bytes and written 457 bytes
---
New, TLSv1/SSLv3, Cipher is ECDHE-RSA-AES128-SHA
Server public key is 2048 bit
Secure Renegotiation IS supported
Compression: NONE
Expansion: NONE
SSL-Session:
    Protocol  : TLSv1.2
    Cipher    : ECDHE-RSA-AES128-SHA
    Session-ID: 9E644402F21A56BD299E13DCC7290D5D350E5F934D6F91EC0E9062BB08C85E2B
    Session-ID-ctx:
    Master-Key: B402C7AB986247BFCC4313585494658683345CEA05B6A2187FFB434BE96C761C8E1A3EEBB20BE9368C08C96310AA0053
    Key-Arg  : None
    PSK identity: None
    PSK identity hint: None
    SRP username: None
    Start Time: 1437570827
    Timeout  : 300 (sec)
    Verify return code: 20 (unable to get local issuer certificate)
---
closed


the error I am getting (actual URL masked):


[Optional(Error Domain=NSURLErrorDomain Code=-1200 "An SSL error has occurred and a secure connection to the server cannot be made."
UserInfo={NSURLErrorFailingURLPeerTrustErrorKey=<SecTrustRef: 0x5b0c20f80>,
NSLocalizedRecoverySuggestion=Would you like to connect to the server anyway?,
_kCFStreamErrorCodeKey=-9802,
NSUnderlyingError=0x5b15a5fd0 {Error Domain=kCFErrorDomainCFNetwork Code=-1200 "(null)" UserInfo={_kCFStreamPropertySSLClientCertificateState=0,
_kCFNetworkCFStreamSSLErrorOriginalValue=-9802,
kCFStreamPropertySSLPeerTrust=<SecTrustRef: 0x5b0c20f80>,
_kCFStreamErrorDomainKey=3,
_kCFStreamErrorCodeKey=-9802}},
NSLocalizedDescription=An SSL error has occurred and a secure connection to the server cannot be made.,
NSErrorFailingURLKey=https : //s3.amazonaws.com/...,
NSErrorFailingURLStringKey=https : //s3.amazonaws.com/...,
_kCFStreamErrorDomainKey=3})]


this used to work with xcode7 beta3. The error goes away if I disable ATS.


Insight?

Yes your're right, I'm using a self signed certificate. Is there an issue due with this?


It's not in my immediate plan to use a "signed certificate" but if it's the root cause of the issue then I could try to build my own CA and create a signed certificate.


How can I use my own CA and signed certificate with IOS? Is there some documentation/tutorial?

Yup. Since iOS 9 B4 (OS X 10.11 B4 as well), I get the same -1200 error, even though my server has a CA issued certificate and supports TLSv1.2. This used to work just fine on B3 (both OSes).

Please see rdar://21946711, which includes a sample project that recreates the issue.

Yes your're right, I'm using a self signed certificate. Is there an issue due with this?

Yes. ATS is pretty much fundamentally incompatible with self-signed certificates.

In itself, that's no big loss IMO. Self-signed certificates are a bad idea and you're much better off avoiding them and instead using a custom CA instead (Technotes 2232 and 2326 have the details of this). However, that approach is also incompatible with ATS as things currently stand.

In short, to work with ATS you will have to get a certificate issued by a trusted CA. The alternative is to disable ATS (only for the domain you care about, of course) and then, if necessary, do your own custom trust evaluation.

ps borgiscool and Lucky7, please start a new thread for the b4 regression; I'd like to use this thread to help sebastien78 with the more fundamental problem.

Share and Enjoy

Quinn "The Eskimo!"
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

The App I'm developping is only for internal use in our Company and the server is also running in our private network. As a consequence we don't plan to get a certificate issued by a trusted CA.


Do you plan to support custom CA?


My understanding is the only solution I have is to set the NSAllowsArbitraryLoads to YES. When I set this property at the top level under NSAppTransportSecurity it's working but if I use an NSExceptionDomains the following it doesn't work, what is wrong?


<key>NSAppTransportSecurity</key>

<dict>

<key>NSExceptionDomains</key>

<dict>

<key>192.168.***.YYY</key>

<dict>

<key>NSAllowsArbitraryLoads</key>

<true/>

</dict>

</dict>

</dict>


Thanks for your help and support.


Sébastien.

Accepted Answer

The App I'm developping is only for internal use in our Company and the server is also running in our private network. As a consequence we don't plan to get a certificate issued by a trusted CA.

Ah, that's a different story. In situations like this you should create a CA for your company, if you don't already have one, and have the server's certificate issued by a CA. You can then install the company CA's root certificate on the device as a system-trusted root (typically via MDM). Once you do that, ATS will trust your company CA's issued certificates, just like it trusts certificate's issued by any other CA whose root is installed on, or built in to, the system.

I tested this approach on a device here in my office, using a CA I maintain for my own personal server, and it worked just fine.

if I use an NSExceptionDomains the following it doesn't work, what is wrong?

192.168.***.YYY

I presume that the Info.plist doesn't contain "***" and "YYY" literally, but rather it has the actual numbers of your IP address.

Regardless, last I checked NSExceptionDomains is supposed to support IP addresses but does not due to a bug. I typically work around this by using the .local name.

Share and Enjoy

Quinn "The Eskimo!"
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

When I'm configuring the NSExceptionDomains with a domain it's working.


I'm using the following exception.


<key>NSAppTransportSecurity</key>

<dict>

<key>NSExceptionDomains</key>

<dict>

<key>MyServer.MyDomain.com</key>

<dict>

<key>NSTemporaryExceptionAllowsInsecureHTTPLoads</key>

<true/>

</dict>

</dict>

</dict>

I'm getting the same thing when using CloudFront. TLS is 1.2 so I'm not sure why its failing. I guess I'll have to diable ATS and update the app? Any other suggestions would be greatly appreciated.

It's a little late on this, but I was getting my testing app working on iOS9, and found that this was necessary:


<!-- This was needed for iOS9. Oddly, wasn't needed for iOS10.

Also: only localhost works, not 127.0.0.1, and both of these keys are needed.

-->

<key>localhost</key>

<dict>

<key>NSTemporaryExceptionAllowsInsecureHTTPLoads</key>

<true/>

<key>NSThirdPartyExceptionRequiresForwardSecrecy</key>

<false/>

</dict>

NSURLSession error with ATS
 
 
Q