Swift 3 UrlSession with client authentication by certificate

孤街醉人 提交于 2019-12-10 12:13:43

问题


I'm using URLSession to make a get request with TLS 1.2 protocol and certificates (which are all self-signed) included in the main bundle. I managed to do the pinning but server also requires a client certificate for authentication so I'm trying to respond to the AuthenticationChallenge with UrlCredential but it's not working: i keep getting NSURLErrorDomain Code=-1206 which is "The server “my_server_domain.it” requires a client certificate."

Here is my request:

func makeGetRequest(){

    let configuration = URLSessionConfiguration.default
    var request = try! URLRequest(url: requestUrl, method: .get)

    let session = URLSession(configuration: configuration,
                             delegate: self,
                             delegateQueue: OperationQueue.main)

    let task = session.dataTask(with: request, completionHandler: { (data, response, error) in

        print("Data = \(data)")
        print("Response = \(response)")
        print("Error = \(error)")

    })

    task.resume()
}

URLSessionDelegate, where I respond to the AuthenticationChallenge:

func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {

    let authenticationMethod = challenge.protectionSpace.authenticationMethod
    print("authenticationMethod=\(authenticationMethod)")

    if authenticationMethod == NSURLAuthenticationMethodClientCertificate {

        completionHandler(.useCredential, getClientUrlCredential())

    } else if authenticationMethod == NSURLAuthenticationMethodServerTrust {

        let serverCredential = getServerUrlCredential(protectionSpace: challenge.protectionSpace)
        guard serverCredential != nil else {
            completionHandler(.cancelAuthenticationChallenge, nil)
            return
        }
        completionHandler(.useCredential, serverCredential)
    }

}

Server certificate pinning:

 func getServerUrlCredential(protectionSpace:URLProtectionSpace)->URLCredential?{

    if let serverTrust = protectionSpace.serverTrust {
        //Check if is valid
        var result = SecTrustResultType.invalid
        let status = SecTrustEvaluate(serverTrust, &result)
        print("SecTrustEvaluate res = \(result.rawValue)")

        if(status == errSecSuccess),
            let serverCertificate = SecTrustGetCertificateAtIndex(serverTrust, 0) {
                //Get Server Certificate Data
                let serverCertificateData = SecCertificateCopyData(serverCertificate)
                //Get Local Certificate NSData
                let localServerCertNSData = certificateHelper.getCertificateNSData(withName: "localServerCertName", andExtension: "cer")

                //Check if certificates are equals, otherwhise pinning failed and return nil
                guard serverCertificateData == localServerCertNSData else{
                    print("Certificates doesn't match.")
                    return nil
                }

                //Certificates does match, so we can trust the server
                return URLCredential(trust: serverTrust)
        }
    }

    return nil

}

And here is where i obtain the client URLCredential from the PKCS12 (.pfx) certificate:

func getClientUrlCredential()->URLCredential {

    let userCertificate = certificateHelper.getCertificateNSData(withName: "certificate",
                                                                 andExtension: "pfx")
    let userIdentityAndTrust = certificateHelper.extractIdentityAndTrust(fromCertificateData: userCertificate, certPassword: "cert_psw")
    //Create URLCredential
    let urlCredential = URLCredential(identity: userIdentityAndTrust.identityRef,
                                      certificates: userIdentityAndTrust.certArray as [AnyObject],
                                      persistence: URLCredential.Persistence.permanent)

    return urlCredential
}

Note that func 'extractIdentityAndTrust' -successfully- returns a struct with pointers to identity, certificate-chain and trust extracted from the PKCS12; I know that identity and certificates should be stored in the keychain but at the moment I'm just including them in the bundle mainly because the documentation for keychain is anything but good.

I've also added App Transport Security Settings to my Info.plist file like this

It looks like client doesn't even try to authenticate, so I'm missing something, I guess...


回答1:


If your getClientCredential() function is being called, then your client is trying to authenticate. If not, then the server logs (such as /var/log/nginx/access.log) might indicate why.

The PKCS12 class in this answer worked for me.

Regarding the keychain, this Apple documentation says

To use digital identities in your own apps, you will need to write code to import them. This typically means reading in a PKCS#12-formatted blob and then importing the contents of the blob into the app's keychain using the function SecPKCS12Import documented in Certificate, Key, and Trust Services Reference.

This way, your new keychain items are created with your app's keychain access group.



来源:https://stackoverflow.com/questions/44023540/swift-3-urlsession-with-client-authentication-by-certificate

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!