URLSession didReceiveChallenge failing on iOS 17

We're seeing server trust failures with iOS 17 that we don't see with iOS 16, particularly in debugging, when we build with Xcode 15. We handle

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

and choose to deal with recoverable server trust failures of particular kinds ourselves. After our checks we end up calling the completion handler:

  let credential = URLCredential(trust: serverTrust)
  completionHandler(.useCredential, credential)

And everything continues.

This is working on macOS Venture and earlier and iOS 16 and earlier. It also works if we install our current release build on iOS 17. If we build with Xcode 15 and test on iOS 17 then calling the completion handler with .useCredential ends up failing with a -1200 error, and a message about a recoverable trust failure.

Has anyone else seen this behavior? Does anyone know if this is related to just Xcode 15, or to Xcode 15 + an interaction with iOS 17? Maybe the SDKs used with Xcode 15 are being stricter? In any case it would seem that saying .useCredential should cause it to .use the credential...

Accepted Reply

Are you perhaps being bitten by the ATS change documented in the iOS & iPadOS 17 Beta Release Notes?

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

Replies

Are you requests targeted at an IP address (as opposed to a DNS name)?

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

Sometimes, but we take that into account in our code that handles the server cert validation when there's a trust question.

It's one reason we have a handler for the server certs.

Kevin

Are you perhaps being bitten by the ATS change documented in the iOS & iPadOS 17 Beta Release Notes?

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

Looks like it might be. I'll take a look at using NSExceptionDomains to get around this when needed.

The main use case is testing, not distribution--we really don't want to let people loosen up too much on production setups--so if this is the issue we should be able to build in specific exceptions for test builds.

Thanks for pointing out that entry.

Kevin

Yes, that was it. Setting Allow Arbitrary Loads for testing worked. Thanks.

Kevin

On other threads I see that in the past there was no way to tell a URLSession to check for a particular hostname during TLS. Is this still true?

It seems like an omission that it wouldn't take much to fix.

On other threads I see that in the past there was no way to tell a URLSession to check for a particular hostname during TLS. Is this still true?

I don’t know what you mean by this. Can you elaborate? Or perhaps reference the previous threads that you saw about it?

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

Our problem is basically that at one specific point in our connection process we're using URLSession with an IP address instead of an FQDN. Before that we use FQDNs, after that we use NWConnection, but at that specific point we need to connect by IP address.

With the stricter security checking on the beta versions of the OSes we can no longer programmatically override the trust when we validate the host name internally. This means that we need to get the OS to do it for us. It looks like sec_protocol_options_set_tls_server_name will work for the NWConnections (although we still need to do more testing on that), but we have no solution for the case of a URLSession connecting by IP address.

Hostname support for TLS:

https://developer.apple.com/forums/thread/708005 - "NWConnection TLSParameters does not provide API to set SNI"

https://developer.apple.com/forums/thread/680012 - "Set SNI using nw_endpoint_t or nw_parameters_t?"

No support on URLSession

https://developer.apple.com/forums/thread/82179 - "Setting SNI hostname for URLProtocol with URLSession"

https://developer.apple.com/forums/thread/105057 - "NSURLSession's SNI Support"

With the stricter security checking on the beta versions of the OSes we can no longer programmatically override the trust when we validate the host name internally.

I think I’m missing something here. The new change relates to ATS, and you can opt out of that using NSExceptionDomains. And once ATS is out of the way, you can run a custom trust evaluation as before, that is, create a trust object with a custom name.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

My understanding was that the NSExceptionDomains value had to be set in the info.plist file, and thus at build time as mentioned in

https://developer.apple.com/documentation/bundleresources/information_property_list/nsapptransportsecurity

We don't know which domains need to be handled differently at that point. Each customer could have multiple domains configured about which we know nothing.

We could turn ATS off, but that seems extreme (-ly unsafe). What we're looking to do is validate by hostname when we're connecting to an IP address for which we know the hostname. We can't just connect by hostname at certain stages, because it will break in the case of load-balanced hostnames which can resolve to multiple IPs.

We don't know which domains need to be handled differently at that point.

But you’re not configuring a domain exception but rather an IP address exception. That requires just one NSExceptionDomains entry.

We could turn ATS off

Disabling ATS entirely shouldn’t be necessary. You just want to disable it for requests made using an IP address. So all your ‘normal’ requests, those using a host name, will still benefit from ATS.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

We don't know the IP address in advance either.

The domain name(s) and the IP address(es) are all dynamic--customers configure all of those.

I see no way in the documentation to say "don't apply ATS to connections made by any IP address" in the general case, just ways to configure either

  • Exceptions for specific domains/IP addresses
  • Bypass ATS altogether

Are you thinking of NSAllowsLocalNetworking? The description of its behavior in the documentation is really unclear, and sounds like it contradicts the ATS changes and observed behavior in the betas when it speaks about .local domains, unqualified domains, and IP addresses ("In iOS 10 and macOS 10.12 and later, ATS allows all three of these connections by default, so you no longer need an exception for any of them").

We don't know the IP address in advance either.

That’s fine. The beta OS releases extended NSExceptionDomains to suport CIDR notation. Go back and re-read the iOS & iPadOS 17 Beta Release Notes doc that I linked to upthread.

Share and Enjoy

Quinn “The Eskimo!” @ DTS @ Apple

I did know that. It's still a compile time setting, and we don't know what needs to be excepted at compile time.

The only way to make that work would be to specify 0.0.0.0/0, which is pretty close to turning ATS off.

which is pretty close to turning ATS off.

No, it’s turning ATS off for all connections made with an IP address. That gets you back to the state you were on iOS 16. ATS will still be applied to connections made with a DNS name.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"