According to this article when communicating with hardware accessories in your local network over HTTPS securely, you should be pinning your certificates like this:
In the URLSessionDelegate implement this method:
func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge) async -> (URLSession.AuthChallengeDisposition, URLCredential?)
Then do your verification via SecTrustEvaluateWithError and return (.useCredential, URLCredential(trust: trust)) if it was successful and (.cancelAuthenticationChallenge, nil) if it was not.
However, the error returned when rejecting the challenge in try await URLSession(...).data(from:) is a URLError with the code -999 which is URLError.Code.cancelled.
How would I distinguish between other cancellations, so I could tell the user that the request failed because of an invalid certificate? The documentation of URLError.Code.cancelled is very generic, so I cannot solely rely on this error (or can I?).
This article suggests that we should be showing the error right in the URLSessionDelegate which does not seem like a good solution to me since user interface code would leak into networking code.
Are there any other techniques, I could apply here to specifically catch the error from URLSession when the certificate was declined by my own validation logic?
You need to solve this issue with a custom "HTTPClient" which internally uses a URLSession, and a URLSession API where you can specify a task specific delegate.
The idea is, to use a dedicated task specific delegate, where you store the error of that request. Then, later when the URLSession data tasks returns with a cancellation error, check the delegate's error property if there is more information.
The below code give you an idea to start with:
First, you need a class conforming to
URLSessionTaskDelegate, which can be instantiated as a separate object. In your case it specifies delegate method which handles the authentication challenge.Define the delegate:
Note, that the delegate has a property
error.Second, you want to use a dedicated instance of this delegate for executing a single request. Here, you can use the following URLSession APIs which have a parameter for a dedicated URLSessionTaskDelegate. For example:
You can then use it like below:
Of course, you should improve this above code in your custom "HTTPClient" implementation.