How to setup iOS 12 NWConnection that uses client cert

I'm trying to set up a client connection to communicate with an MQTT broker using client side certificates. I've been able to proof-of-concept this all on the Linux side using command line. The MQTT broker is set to require client cert, and a command like:


    mosquitto_pub -h servername -p 8899 -t 1234/2/Q/8 -m myMessage --cafile myChain.crt --cert client.crt --key client.pem

does the job nicely. Now I'm trying to set up an NWConnection:


    self.connection = NWConnection(
        host: NWEndpoint.Host("servername"),
        port: NWEndpoint.Port(integerLiteral: 8899),
        using: .tls)


But I think that simple .tls class var needs to be a much more involved NWParameters object, but I'm at a complete loss (documentation is pretty sparse) as to what I create there to attach the client certs to the parameters. Nor do I know how I even move from .crt/.pem file to something the app manages programatically.


What is an example of how one would configure the NWParameters to support the client certs?

Accepted Reply

Sorry I didn’t respond sooner; somehow I missed your post when it first showed up )-:

What is an example of how one would configure the NWParameters to support the client certs?

Yeah, that’s a bit tricky. The basic structure for this as as follows:

let options = NWProtocolTLS.Options()
let securityOptions = options.securityProtocolOptions
… configure `securityOptions` here …
let params = NWParameters(tls: options)
let conn = NWConnection(host: "example.com", port: 443, using: params)

The above gives you access to the

sec_protocol_options_t
, allowing you to customise various TLS operations. For example:
  • To customise server trust evaluation, set a verify block:

    sec_protocol_options_set_verify_block(securityOptions, { (_, trust, completionHandler) in
        let isTrusted = … your code here …
        completionHandler(isTrusted)
    }, .main)

    .

  • To always supply a client identity, set it as the local identity:

    let clientIdentity: SecIdentity = … your code here …
    sec_protocol_options_set_local_identity(
       securityOptions,
       sec_identity_create(clientIdentity)!
    )

    .

  • To respond to client certificate challenges from the server — this allows you to pick an identity based on info returned to you by the server — set a challenge block:

    sec_protocol_options_set_challenge_block(securityOptions, { (_, completionHandler) in
        let clientIdentity: SecIdentity = … your code here …
        completionHandler(sec_identity_create(clientIdentity)!)
    }, .main)

    .

Share and Enjoy

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

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

Replies

Sorry I didn’t respond sooner; somehow I missed your post when it first showed up )-:

What is an example of how one would configure the NWParameters to support the client certs?

Yeah, that’s a bit tricky. The basic structure for this as as follows:

let options = NWProtocolTLS.Options()
let securityOptions = options.securityProtocolOptions
… configure `securityOptions` here …
let params = NWParameters(tls: options)
let conn = NWConnection(host: "example.com", port: 443, using: params)

The above gives you access to the

sec_protocol_options_t
, allowing you to customise various TLS operations. For example:
  • To customise server trust evaluation, set a verify block:

    sec_protocol_options_set_verify_block(securityOptions, { (_, trust, completionHandler) in
        let isTrusted = … your code here …
        completionHandler(isTrusted)
    }, .main)

    .

  • To always supply a client identity, set it as the local identity:

    let clientIdentity: SecIdentity = … your code here …
    sec_protocol_options_set_local_identity(
       securityOptions,
       sec_identity_create(clientIdentity)!
    )

    .

  • To respond to client certificate challenges from the server — this allows you to pick an identity based on info returned to you by the server — set a challenge block:

    sec_protocol_options_set_challenge_block(securityOptions, { (_, completionHandler) in
        let clientIdentity: SecIdentity = … your code here …
        completionHandler(sec_identity_create(clientIdentity)!)
    }, .main)

    .

Share and Enjoy

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

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

Thanks Quinn, I've been working through this. But I'm getting stuck at the


let clientIdentity: SecIdentity = … your code here …


part. I'm getting my cert/key pair from a prior BLE negotiaton (and they are self signed). So I can create my key using:


// private key
var error: Unmanaged?
if let rsaKey = SecKeyCreateWithData(KeyData as CFData, keyOptions as CFDictionary, &error) {
    "key \(rsaKey)".print()
}
else {
    "NO KEY \(error!.takeRetainedValue() as Error)".print()
}

AndI can create the cert using a similiar construct:


// client cert
let certificate = SecCertificateCreateWithData(nil, CertData as CFData)
if let certificate = certificate {
    "certificate \(certificate)".print()
}
else {
    "NO CERTIFICATE".print()
}


But the function that I though I would use to create an Identity

SecIdentityCreateWithCertificate(nil, certificate!, &identity)

is only availabe in macOS. I'm running on iOS. I noticed the function SecAddItem, but it doesn't talk about keys at all.


Given I have the DER bytes for both the certificate and the key, what is the right way to turn these into entites I can use the Sec framework on iOS?


(does this need to be a new question, rather than a continuation of your excellent example above)

I found a single comment below a StackOverflow answer that ended up being the solution: SecPKCS12Import().


XCA lets me bundle up my cert/chain/privkey files as .p12 just as well. Using those bytes, I can import it and extract the SecIdentity.


let importOptions = [ kSecImportExportPassphrase as String: "" ]
var rawItems: CFArray?
let status = SecPKCS12Import(P12Data as CFData, importOptions as CFDictionary, &rawItems)
let items = rawItems! as! Array<Dictionary<String, Any>>
let firstItem = items[0]
let clientIdentity = firstItem[kSecImportItemIdentity as String]! as! SecIdentity
print("clientIdentity \(clientIdentity)")

XCA lets me bundle up my cert/chain/privkey files as

.p12
just as well.

Excellent news. If you start with a

.p12
, this process is simple. However, if you start with a private key and certificate pair, things get complex. On iOS, the absence of
SecIdentityCreateWithCertificate
means that you have to bounce through the keychain.

Share and Enjoy

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

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

We need to create TLS connection with root CA certificate. We have .pem and .der files of root certificate.

Here is the steps :

Create parameters for TLS connection using NWProtocolTLS

func createTLSParameters(allowInsecure: Bool, queue: DispatchQueue) -> NWParameters {

  let options = NWProtocolTLS.Options()
  sec_protocol_options_set_local_identity(options.securityProtocolOptions, sec_identity_create(clientIdentity)!)

  sec_protocol_options_set_challenge_block(options.securityProtocolOptions, { (_, completionHandler) in
  completionHandler(sec_identity_create(clientIdentity)!)
  }, .main)

  let parameters = NWParameters(tls: options)


  return parameters
  }

Create Socket connection with TLS parameter by NWConnection

func createSocket() {
  let parameters = createTLSParameters(allowInsecure: true, queue: self.queue) 
  parameters.prohibitedInterfaceTypes = [.wifi]
  guard let endPoint = NWEndpoint.Port(rawValue: 4420) else {
  return
  }

  con = NWConnection(to: .hostPort(host: NWEndpoint.Host("somehostname.com"), port: endPoint), using: parameters)

  con?.stateUpdateHandler = { [weak self] status in
  if case .ready = status {
  self?.receiveData()
  }
  }

  con?.start(queue: queue)
  }

Send HTTPS request. Here Required Info is the uri for GET.

func sendData(for groupUrl: String, atk: String) {
  con?.send(content: "GET **Required Info**".data(using: .utf8), contentContext: .defaultMessage, isComplete: false, completion: NWConnection.SendCompletion.contentProcessed({ (error) in
  print("send Data callback---")
  if let err = error {
  print("send Data failed \(err)")
  }
  }))
  }

In response we have got.

HTTP/1.0 200 OK
Cache-Control: no-cache
Pragma: no-cache
Connection: close
Content-Length: 188

The requested URL was rejected. Please consult with your administrator.


Your support ID is: 7*********769
I think something happened for loading root CA info, during creating TLS parameter. Can you help us the way of loading root CA during TLS connection?
Best regards
Arup Sarker
Email: arupcsedu@gmail.com

It looks like you are wanting to know more about setting up a challenge to evaluate a server's certificate, possibly with something like sec_protocol_options_set_verify_block. However, it looks like you are seeing a 200 response in your example, so did you evaluation succeed? Can you tell me a bit more about what you are looking to achieve?


Matt Eaton

DTS Engineering, CoreOS

meaton3 at apple.com

Hi Meaton,

Thank you for quick response. We have server certificate with ".der" format. We want to create TLS connection connection and perform HTTP GET operation.

Previously, We have used URLSession API and it was working fine.

But We want make a connection specific with LTE, although phone is connected with WiFi. That's why We have used

parameters.prohibitedInterfaceTypes = [.wifi]


with tcp/udp, we are able to make connection with NWConnection api. But we need TLS connection with server certificate.

Below is the function to create TLS parameters.



func createTLSParameters(allowInsecure: Bool, queue: DispatchQueue) -> NWParameters {  

  let options = NWProtocolTLS.Options()  
  sec_protocol_options_set_local_identity(options.securityProtocolOptions, sec_identity_create(clientIdentity)!)  

  sec_protocol_options_set_challenge_block(options.securityProtocolOptions, { (_, completionHandler) in  
  completionHandler(sec_identity_create(clientIdentity)!)  
  }, .main)  

  let parameters = NWParameters(tls: options)  


  return parameters  
  }

How can we create "clientIdentity" (line no 7) from server certificate?

Please correct me, if my concept is wrong.


Best regards

Arup Sarker

Email : arupcsedu@gmail.com

One thing to look into would be still using NWPathMonitor to disable/enable NSURLSession requests if on a desired network path. See this thread for more.


As far as getting the client SecIdentity in line 7, try looking at loading a p12 for this using the SecPKCS12Import. For more information on how to use SecPKCS12Import, checkout this walkthrough here.


Matt Eaton

DTS Engineering, CoreOS

meaton3 at apple.com