NSURLSession & client Certificate auth

After severals hours to make it works, I am unable to authenticate NSURLSession with client certificate whereas it works perfectly with NSURLConnection which is deprecated. Certificate is valid, but iOS reject certificate and does not send to server.


After some Google search, it seems there are some bugs with this API. Is there any working example or some advises (ephemeral or default configuration, quiche queue to use, etc) to make it work or any plan for fixing this because client certiticate auth is required for MDM protocol and NSURLSession cannot be used as replacement for deprecated NSURLConnexion.


Thanks for your help,


David

Replies

The client certificate authentication support in

NSURLSession
works fine in general; I regularly exercise this while helping folks as part of my day job. I’m not sure what’s going on in your case. It’s possible that you’ve hit a bug that’s specific to your setup, but that seems unlikely. Keep in mind that
NSURLSession
and
NSURLConnection
share a core HTTP[S] implementation, so if you got it working with
NSURLConnection
it’s very likely it will also work with
NSURLSession
.

Certificate is valid, but iOS reject certificate and does not send to server.

iOS does not do trust evaluation on the certificate in the digital identity you use to resolve the

NSURLAuthenticationMethodClientCertificate
authentication challenge. Whatever’s going on, it’s not that straight forward.

Let’s start with some basics. You should handle client certificate authentication challenges via the per-connection delegate method, that is,

-URLSession:didReceiveChallenge:completionHandler:
. What does your implementation look like?

Share and Enjoy

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

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

After further investigation, it appears that problem occurs when using PUT method instead of GET.
PUT does not works with NSURLSession and NSURLConnection, whereas it works fine with GET. Same problem with POST request.

Workaround : make an GET call before PUT to register credential but this is a security hole because Credential must persist during session lifecycle.


Note : code works fine without client authentification.


here are the logs :


2019-08-26 12:31:47.854495+0200 mobileHSM DEV[3186:973995] CFNetwork Diagnostics [3:1] 12:31:47.853 {

LoaderWhatToDo: (null)

Request: <CFURL 0x283b747e0 [0x2498d3840]>{string = https://myURL/hsm/device, encoding = 134217984, base = (null)}

CachePolicy: 0

WhatToDo: originload

CreateToNow: 0.00208s

} [3:1]

2019-08-26 12:31:47.854885+0200 mobileHSM DEV[3186:973998] CFNetwork Diagnostics [3:2] 12:31:47.854 {

AddCookies Continue: request POST https://myURL/hsm/device HTTP/1.1

HTTPProtocol: Task: 83e5ee80

} [3:2]

2019-08-26 12:31:47.855119+0200 mobileHSM DEV[3186:973998] CFNetwork Diagnostics [3:3] 12:31:47.855 {

DiskCookieStorage Construction: Binary{ Disk Cookies: { /private/var/mobile/Containers/Data/Application/2795B7DE-14D5-4429-9883-833EF2128FB7/Library/Cookies/Cookies.binarycookies, <0 cookies in 0 domains> clean not writing } }

Accessing: <CFURL 0x283450e40 [0x2498d3840]>{string = file:///private/var/mobile/Containers/Data/Application/2795B7DE-14D5-4429-9883-833EF2128FB7/Library/Cookies/Cookies.binarycookies, encoding = 134217984, base = (null)}

Path: /private/var/mobile/Containers/Data/Application/2795B7DE-14D5-4429-9883-833EF2128FB7/Library/Cookies/Cookies.binarycookies

Read from disk: <0 cookies in 0 domains>

Dirty: NO

Writing: NO

Policy: 2

} [3:3]

2019-08-26 12:31:47.855249+0200 mobileHSM DEV[3186:973998] CFNetwork Diagnostics [3:4] 12:31:47.855 {

DiskCookieStorage Journaling On: Binary{ Disk Cookies: { /private/var/mobile/Containers/Data/Application/2795B7DE-14D5-4429-9883-833EF2128FB7/Library/Cookies/Cookies.binarycookies, <0 cookies in 0 domains> clean not writing } }

File: <CFURL 0x283450e40 [0x2498d3840]>{string = file:///private/var/mobile/Containers/Data/Application/2795B7DE-14D5-4429-9883-833EF2128FB7/Library/Cookies/Cookies.binarycookies, encoding = 134217984, base = (null)}

} [3:4]

2019-08-26 12:31:47.855429+0200 mobileHSM DEV[3186:973998] CFNetwork Diagnostics [3:5] 12:31:47.855 {

HTTPCookieStorage::copyCookiesForURL: <CFHTTPCookieStorage 0x283450de0 [0x283450df0]>

Request URL: https://myURL/hsm/device

MainDocument URL: NONE

} [3:5]

2019-08-26 12:31:47.855609+0200 mobileHSM DEV[3186:973998] CFNetwork Diagnostics [3:6] 12:31:47.855 {

Protocol Enqueue: request POST https://myURL/hsm/device HTTP/1.1

Request: <CFURLRequest 0x282979180 [0x2498d3840]> {url = https://myURL/hsm/device, cs = 0x0}

Message: POST https://myURL/hsm/device HTTP/1.1

Keep-Alive: timeout=120

Content-Type: application/json

Accept: application/json

Accept-Language: fr-fr

Accept-Encoding: br, gzip, deflate

} [3:6]

2019-08-26 12:31:47.855698+0200 mobileHSM DEV[3186:973998] CFNetwork Diagnostics [3:7] 12:31:47.855 {

SocketStream IO Logging: (null)

} [3:7]

2019-08-26 12:31:47.929147+0200 mobileHSM DEV[3186:973998] CFNetwork Diagnostics [3:8] 12:31:47.928 {

TCP Connection Start: (null)

Connection: 0x28256cd80

Connection ID: 1

} [3:8]

2019-08-26 12:31:48.117055+0200 mobileHSM DEV[3186:973997] CFNetwork Diagnostics [3:9] 12:31:48.116 {

Authentication Challenge: (null)

Loader: <CFMutableURLRequest 0x282978ee0 [0x2498d3840]> {url = https://myURL/hsm/device, cs = 0x0}

Challenge: challenge space https://myURL:443/, ServerTrustEvaluationRequested (Hash e012c9d45f77ad55)

} [3:9]

2019-08-26 12:31:48.117324+0200 mobileHSM DEV[3186:974046] NSURLAuthenticationMethodServerTrust

2019-08-26 12:31:48.128603+0200 mobileHSM DEV[3186:973997] CFNetwork Diagnostics [3:10] 12:31:48.117 {

Use Credential: (null)

Loader: <CFMutableURLRequest 0x282978ee0 [0x2498d3840]> {url = https://myURL/hsm/device, cs = 0x0}

Credential: Name: *.peers.club, Persistence: session

} [3:10]

2019-08-26 12:31:48.128806+0200 mobileHSM DEV[3186:973997] CFNetwork Diagnostics [3:11] 12:31:48.128 {

touchConnection: (null)

Loader: <CFMutableURLRequest 0x282978ee0 [0x2498d3840]> {url = https://myURL/hsm/device, cs = 0x0}

Timeout Interval: 60.000 seconds

} [3:11]

2019-08-26 12:31:48.137607+0200 mobileHSM DEV[3186:973997] CFNetwork Diagnostics [3:12] 12:31:48.137 {

Authentication Challenge: (null)

Loader: <CFMutableURLRequest 0x282978ee0 [0x2498d3840]> {url = https://myURL/hsm/device, cs = 0x0}

Challenge: challenge space https://myURL:443/, ClientCertificateRequested (Hash e012c9d45f77ad55)

} [3:12]

2019-08-26 12:31:48.137942+0200 mobileHSM DEV[3186:974046] NSURLAuthenticationMethodClientCertificate

2019-08-26 12:31:48.138494+0200 mobileHSM DEV[3186:973997] CFNetwork Diagnostics [3:13] 12:31:48.138 {

Use Credential: (null)

Loader: <CFMutableURLRequest 0x282978ee0 [0x2498d3840]> {url = https://myURL/hsm/device, cs = 0x0}

Credential: Name: dscr.hsm.dev.peers.club, Persistence: session

} [3:13]

2019-08-26 12:31:48.138726+0200 mobileHSM DEV[3186:973997] CFNetwork Diagnostics [3:14] 12:31:48.138 {

touchConnection: (null)

Loader: <CFMutableURLRequest 0x282978ee0 [0x2498d3840]> {url = https://myURL/hsm/device, cs = 0x0}

Timeout Interval: 60.000 seconds

} [3:14]

2019-08-26 12:31:48.195588+0200 mobileHSM DEV[3186:973995] CFNetwork Diagnostics [3:15] 12:31:48.195 {

TCP Connection Connected: (null)

Connection: 0x28256cd80

Connection ID: 1

Error: 16

} [3:15]

2019-08-26 12:31:48.196273+0200 mobileHSM DEV[3186:973995] CFNetwork Diagnostics [3:16] 12:31:48.196 {

Prepare Transmission: (null)

} [3:16]

2019-08-26 12:31:48.210559+0200 mobileHSM DEV[3186:973995] TIC Read Status [1:0x28256cd80]: 1:57

2019-08-26 12:31:48.212807+0200 mobileHSM DEV[3186:973995] Task <F65043B4-FB7E-4919-88EC-2365670DB8C7>.<0> HTTP load failed (error code: -1005 [4:-4])

2019-08-26 12:31:48.213215+0200 mobileHSM DEV[3186:973995] CFNetwork Diagnostics [3:17] 12:31:48.212 {

Response Error: (null)

Request: <CFURLRequest 0x282979180 [0x2498d3840]> {url = https://myURL/hsm/device, cs = 0x0}

Error: Error Domain=kCFErrorDomainCFNetwork Code=-1005 "(null)" UserInfo={NSErrorPeerAddressKey=<CFData 0x283371270 [0x2498d3840]>{length = 16, capacity = 16, bytes = 0x100201bb0a2c088e0000000000000000}, _kCFStreamErrorCodeKey=-4, _kCFStreamErrorDomainKey=4}

} [3:17]

2019-08-26 12:31:48.214695+0200 mobileHSM DEV[3186:973997] CFNetwork Diagnostics [3:18] 12:31:48.214 {

Did Fail: (null)

Loader: <CFMutableURLRequest 0x282978ee0 [0x2498d3840]> {url = https://myURL/hsm/device, cs = 0x0}

Error: Error Domain=kCFErrorDomainCFNetwork Code=-1005 "(null)" UserInfo={NSErrorPeerAddressKey=<CFData 0x283371270 [0x2498d3840]>{length = 16, capacity = 16, bytes = 0x100201bb0a2c088e0000000000000000}, _kCFStreamErrorCodeKey=-4, _kCFStreamErrorDomainKey=4}

init to origin load: 0.00273502s

total time: 0.362678s

total bytes: 0

} [3:18]

2019-08-26 12:31:48.214754+0200 mobileHSM DEV[3186:973997] NSURLConnection finished with error - code -1005

2019-08-26 12:31:48.220427+0200 mobileHSM DEV[3186:974046] didFailWithError

2019-08-26 12:31:48.221147+0200 mobileHSM DEV[3186:973997] CFNetwork Diagnostics [3:19] 12:31:48.220 {

destroyReadStream: request POST https://myURL/hsm/device HTTP/1.1

Request: <CFURLRequest 0x282979180 [0x2498d3840]> {url = https://myURL/hsm/device, cs = 0x0}

sent: <CFNumber 0xa45d36c3cc6b8da2 [0x2498d3840]>{value = +320, type = kCFNumberSInt64Type}

received: <CFNumber 0xa45d36c3cc6b99a2 [0x2498d3840]>{value = +0, type = kCFNumberSInt64Type}

cell sent: <CFNumber 0xa45d36c3cc6b99a2 [0x2498d3840]>{value = +0, type = kCFNumberSInt64Type}

cell received: <CFNumber 0xa45d36c3cc6b99a2 [0x2498d3840]>{value = +0, type = kCFNumberSInt64Type}

} [3:19]

2019-08-26 12:31:48.221583+0200 mobileHSM DEV[3186:973997] CFNetwork Diagnostics [3:20] 12:31:48.221 {

~HTTPProtocol: nullptr request

Request: null

sent: 320

received: 0

cell sent: 0

cell received: 0

} [3:20]

The -1005 error in that log is

NSURLErrorNetworkConnectionLost
which can have a variety of causes. I wrote a Q&A that explains some of the background to it. Your problem definitely falls into “large number of these errors” category, which warrants further investigation.

Immediately before the -1005 error, you’ll see this log entry:

2019-08-26 12:31:48.210559+0200 mobileHSM DEV[3186:973995] TIC Read Status [1:0x28256cd80]: 1:57

Error 57 in this context is

ENOTCONN
. It seems that the connection has dropped out from underneath
NSURLSession
. To debug this further, you need to dig deeper into the networking stack. My recommendation:
  • Install the Network Diagnostics for iOS profile (from our Bug Reporting > Profiles and Logs page) and look at the system log for information from the lower-level networking components as to what’s gone wrong.

    You can view the system log using the Console app on the Mac. That yields a lot of info, so be sure to take advantage of Console’s excellent filtering support.

  • Look at an RVI packet trace (see Recording a Packet Trace) to see what’s happening on the ‘wire’.

If you need help with these, you should open a DTS tech support incident. I do not, alas, have time to do this level of investigation in the context of DevForums.

Just to set expectations here, I regularly help developers with problems like this and in a significant majority of cases (probably 9 out of 10) the root cause of the problem is located on the server side. The steps above should allow you to prove that. However, you can often shortcut such investigations but assuming that this is a server side problem and investigating it from that perspective.

Share and Enjoy

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

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

Actually this is really strange : switching PUT to GET http method make everything working...This is not a server issue because problem does not occurs when using PostMan as client.
I will open a DTS support incident.
Thanks for yout help,
David