NSURLSession and failed client certificate requests

From what I can tell, when using client certificate authentication with NSURLSession, it is impossible to get the actual server response if the supplied client certificate was not authorized on the server. Based on what I have found, if you supply a client certificate credential in -URLSession:task:didReceiveChallenge:completionHandler: and the server replies with a 403, then the -URLSession:task:didCompleteWithError: delegate method get called with an NSURLErrorClientCertificateRequired error.


My app needs to see the actual headers that the server responded. I know that a valid response is being sent because I can see it in Wireshark and in CFNETWORK_DIAGNOSTICS output. The response property of the task object passed into -URLSession:task:didCompleteWithError: is nil so I can't use that. Is there anyway at all that I can get the response object in this circumstance or can anyone think of a workaround here?


Thanks,

Dustin

Replies

I have the same isues.

Only current solutions are:

- change the error code if you can

- handle the NSURLErrorClientCertificateRequired error as a 403 error (if you know what you are doing)


Has someone found another solution ?

I dug into this issue as part of a DTS incident (s. 656775591) and my final conclusion was:

  • There’s no direct answer to this problem, so the other developer ended up filing an enhancement request about this (r. 29992970). Feel free to file your own bug report describing your own specific requirements here.

  • The

    NSURLErrorClientCertificateRequired
    error you see in this case is triggered by a specific sequence of events:
    1. If the request fails with 403

    2. And the delegate responded to the client identity authentication challenge

    3. And the app has multiple digital identities in its keychain (this criteria is only applied on macOS; on iOS-based platforms it’s assumed to be true)

    4. Fail the request with

      NSURLErrorClientCertificateRequired
    5. Otherwise, deliver the 403 response

  • If you have any control over the server then a good way to approach this issue is to have it set the

    certificate_authorities
    field in the CertificateRequest message (see Section 7.4.4 of RFC 5246). You can get this from the client identity authentication challenge (via the NSURLProtectionSpace’s
    distinguishedNames
    property) and use this to guide the user’s certificate choice.
  • If not, your workaround options are rather limited. The best I could come up with was to trap the

    NSURLErrorClientCertificateRequired
    error and then run a dummy request via CFHTTPStream, supplying the same digital identity as you used for the NSURLSession request. That should let you get at the 403 response, at which point you can retry the request with NSURLSession, using the 403 response to guide your digital identity choice.

IMPORTANT CFHTTPStream is missing a bunch of smarts that you’re used to from NSURLSession. Many of these don’t matter in this case (authentication challenges, caching, cookies) but there’s one that might: proxies. If you expect your app to run in an environment that uses proxies, you’ll have to get the current proxy configuration (via

<CFNetwork/CFProxySupport.h>
) and apply it to your CFHTTPStream.

Share and Enjoy

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

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

Any workaround or fix for the issue?
I am getting the same error only on macOS Catalina, it works fine on mojave or lower OS.
Even after fetching the error and converting to 403 error and retrying, I am still getting the same "Code=-1206 "The server "..." requires a client certificate"" error.
Have verified the certicates and they meet the new requirements for Catalina.