Get server certificate chain for NSURLSession task

I'm running into a problem where my -URLSession:task:didReceiveChallenge:completionHandler: delegate method is not getting called for every request I make. I use this delegate method to get the server HTTPS certificate chain from the serverTrust property on the protection space. I've seen other threads about this delegate method not getting called that have mentioned setting NSURLSessionConfiguration.URLCredentialStorage to nil. I've tried that and it didn't help.


My question is: how can I make sure that the delegate method is called for every request OR how can get the certificate chain from an NSURLSessionDataTask if that delegate method isn't called?


I'm currently using macOS 10.11 but need to support 10.9-10-12.


Thanks,

Dustin

Replies

I'm running into a problem where my

-URLSession:task:didReceiveChallenge:completionHandler:
delegate method is not getting called for every request I make.

This is expected behaviour, and it speaks to a fundamental disconnect between HTTP and TLS authentication. HTTP authentication, like Basic and Digest, is per request; the authentication information is carried in headers in the request and response. TLS authentication is per connection; authentication is done when you open the connection. The disconnect is that multiple HTTP requests can run over the same connection. This is common in HTTP 1.1 (with HTTP 1.1 persistent connections) and almost guaranteed by HTTP 2 (where there’s a single connection over which all requests and responses travel).

NSURLSession exposes this difference via its delegate callbacks:

  • Per connection authentication challenges are delivered via

    -URLSession:didReceiveChallenge:completionHandler:
  • Per request authentication challenges are delivered via

    -URLSession:task:didReceiveChallenge:completionHandler:
    .

Now, if you don’t implement both handlers then, as a convenience, NSURLSession can deliver authentication challenges to the ‘wrong’ handler. The table below shows all the combinations.

Delegate CallbacksRequest ChallengeConnection Challenge
bothtasksession
tasktasktask
session-session

However, in my experience it’s a bad idea to rely on this because you’re ignoring a key element of how challenges are structured.

how can I make sure that the delegate method is called for every request OR how can get the certificate chain from an NSURLSessionDataTask if that delegate method isn't called?

You can’t. Once a session has established a connection to a host, any request to that host may be sent over that connection. In fact, it’s possible for a request’s transactions to be split over multiple connections. For example, if you make a request that’s redirected, the initial transaction might go over connection A while the redirected transaction might go over connection B.

The way to avoid problems with this is to make sure that all of the connections to a given host within a given session are equivalent from a security perspective. You can then authenticate the session’s connections and know that all subsequent requests in that session are running over an authenticated connection.

In my experience this design is fine in most circumstances. However, there are certain situations where you need multiple connections to a server that are not equivalent security-wise. For example, you might want to make sure that some requests go over a connection that’s been authenticated with a client identity while other requests go over a connection without that that client identity. Fortunately it’s possible to do that: create two sessions, one for each type of connection.

Share and Enjoy

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

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