URLAuthentication challenge alternate for cert pinning using Swift Network Framework

218 Views Asked by At

I am trying to make an API call using Network framework in iOS, i am completely ignoring URLSession because of my usecase. I have to do cert pinning for the endpoint and i dont find much documentation for the same

i found the below snippet helpful from apple's developer forum,

    sec_protocol_options_set_verify_block(options.securityProtocolOptions, { (sec_protocol_metadata, sec_trust, sec_protocol_verify_complete) in
        
        let trust = sec_trust_copy_ref(sec_trust).takeRetainedValue()
        
        var error: CFError?
        if SecTrustEvaluateWithError(trust, &error) {
***I am performing my custom cert pinning implementation here
            sec_protocol_verify_complete(true)
        } else {
            if allowInsecure == true {
                sec_protocol_verify_complete(true)
            } else {
                sec_protocol_verify_complete(false)
            }
        }
        
    }, queue)

This seems to be the configuration that we set before we make the actual network request, the SecTrust is something that we use to get it in the URLSession delegate whenever a request is made, so at this point i am not able to fetch the server certificate from this sec_protocol_options_set_verify_block.

So does anyone happen to get cert pinning work using Network framework? any help would be greatly appreciated, thanks.

1

There are 1 best solutions below

0
On

The client certificate is set in the connection options. First, you need to obtain the SecIdentity. The sec_protocol_options_set_verify_block is used to check the server certificate (you can also copy the server certificate data for further usage). Then, you should use sec_protocol_options_set_challenge_block or sec_protocol_options_set_local_identity to attach your certificate. Here is a rough implementation:

    var rawItems: CFArray?
    let url = Bundle.main.url(forResource: certName, withExtension: certExtension)!
    
    let P12Data: Data = try! Data(contentsOf: url)
    let importOptions = [kSecImportExportPassphrase as String: certPassword]
    
    let status = SecPKCS12Import(P12Data as CFData, importOptions as CFDictionary, &rawItems)
    
    let items = rawItems as! Array<Dictionary<String, Any>>
    guard let cfIdentity = items.first?[kSecImportItemIdentity as String] as? CFTypeRef,
        CFGetTypeID(cfIdentity) == SecIdentityGetTypeID() else {
        return
    }
    
    // There is no way without force unwrap, but for safe force unwrap we check that cfIdentity type is SecIdentityGetTypeID
    let clientIdentity = cfIdentity as! SecIdentity
    
    guard let secIdentity: sec_identity_t = sec_identity_create(clientIdentity) else {
        return 
    }
    
    let options = NWProtocolTLS.Options()
    
    sec_protocol_options_set_verify_block(options.securityProtocolOptions, { [weak self] (_, sec_trust, completionHandler) in
        // your code
    }, queue)
    
    // This delegate methods to set your cert in connection options
    sec_protocol_options_set_local_identity(options.securityProtocolOptions, sec_identity_create(clientIdentity)!)
    sec_protocol_options_set_challenge_block(options.securityProtocolOptions, { (_, completionHandler) in
        completionHandler(secIdentity)
    }, queue)
    
    let params = NWParameters(tls: options)