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

Answered by DTS Engineer in 755599022

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"

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

Accepted Answer

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"

Thanks. I'll take a look.

What we need to validate is whether adding all IP addresses to the exception domains means that no cert validation will be done.

If so, then that's not quite what we want. Prior to iOS 17 we'd

  • get a callback with a recoverable trust failure
  • check to see that it was a hostname issue
  • see if the host name that we know is in the certificate, and continue if so, cancel if not.

Well, 0.0.0.0/0 does not work. Specifying a specific subnet or IP address works.

This is true with a lot of other Network Extension APIs. 0.0.0.0/0 isn't treated as an address, it's treated like a flag. You can't specify exclusions on the default subnet for example.

If you define both 128.0.0.0/1 and 0.0.0.0/1, which effectively covers the same address space, it works as expected.

Someone really dropped the ball on default network handling in the Network Extension code though--if it can be specified as a CIDR address it should be treated as a CIDR address, and it's not.

Well, 0.0.0.0/0 does not work

I’d appreciate you filing a bug about that; please post your bug number, just for the record.

If you define both 128.0.0.0/1 and 0.0.0.0/1, which effectively covers the same address space, it works as expected.

Cool. I’m glad you found that workaround.

Share and Enjoy

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

Well, 0.0.0.0/0 does not work

I asked about this internally — because I absolutely expected that to work — and it seems that, yep, it’s a bug (r. 114040682). I have no info to share as to when it’ll be fixed, but the workaround you’re using seems fine.

Share and Enjoy

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

Cool. Sounds like I don't need to file another issue about that, unless it would help with finding out when the problem is fixed.

I hope they're looking at fixing this issue throughout the Network Extension code, not just for the ATS exclusions.

Kevin


kjbrock icloud com

Sounds like I don't need to file another issue about that

Correct.

I hope they're looking at fixing this issue throughout the Network Extension code, not just for the ATS exclusions.

This is specific to ATS. AFAIK we don’t use CIDR notation in NE APIs.

Share and Enjoy

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

If the defaultRoute is set in IPv4Settings.includedRoutes then exclusions don't work.

In the documentation it says that defaultRoute is "A convenience method for creating the default IPv4 route", which of course is 0.0.0.0/0.

Any addresses set in excludedRoutes continue to be tunneled. If we set 1/1 & 128/1 in IPv4Settings.includedRoutes things work, and excludedAddresses are excluded properly. This is similar enough to the behavior with ATS that I'd suspect similar underlying logic.

This is similar enough to the behavior with ATS that I'd suspect similar underlying logic.

… and you’d be wrong (-: These are different subsystems at opposite ends of our networking stack.

Share and Enjoy

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

URLSession didReceiveChallenge failing on iOS 17
 
 
Q