通过证书进行客户端身份验证的 Swift 3 UrlSession

Posted

技术标签:

【中文标题】通过证书进行客户端身份验证的 Swift 3 UrlSession【英文标题】:Swift 3 UrlSession with client authentication by certificate 【发布时间】:2017-05-17 11:26:57 【问题描述】:

我正在使用 URLSession 使用主包中包含的 TLS 1.2 协议和证书(都是自签名的)发出 get 请求。我设法进行了固定,但服务器还需要客户端证书进行身份验证,因此我尝试使用 UrlCredential 响应 AuthenticationChallenge 但它不起作用:我不断收到NSURLErrorDomain Code=-1206,即“服务器“my_server_domain.it”需要客户证书。”

这是我的要求:

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,我在其中响应 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)
    


服务器证书固定:

 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


这是我从 PKCS12 (.pfx) 证书获取客户端 URLCredential 的地方:

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

注意 func 'extractIdentityAndTrust' -successfully- 返回一个结构体,其中包含从 PKCS12 提取的身份、证书链和信任的指针;我知道身份和证书应该存储在钥匙串中,但目前我只是将它们包含在捆绑包中,主要是因为钥匙串的文档并不好。

我还在我的 Info.plist 文件like this中添加了应用传输安全设置@

看起来客户端甚至没有尝试进行身份验证,所以我想我错过了一些东西......

【问题讨论】:

嗨,Kashish,您设法得到答案了吗?我也对在 NSURLAuthenticationMethodServerTrust 停止的类似情况感到震惊。 【参考方案1】:

如果您的 getClientCredential() 函数被调用,那么您的客户端正在尝试进行身份验证。如果不是,那么服务器日志(例如 /var/log/nginx/access.log)可能会指出原因。

this answer 中的 PKCS12 类对我有用。

关于钥匙串,this Apple documentation 说

要在您自己的应用中使用数字身份,您需要编写代码来导入它们。这通常意味着读取 PKCS#12 格式的 blob,然后使用证书、密钥和信任服务参考中记录的函数 SecPKCS12Import 将 blob 的内容导入应用的钥匙串。

这样,您的新钥匙串项将使用您应用的钥匙串访问组创建。

【讨论】:

以上是关于通过证书进行客户端身份验证的 Swift 3 UrlSession的主要内容,如果未能解决你的问题,请参考以下文章

Apache HttpClient 4.3 和 x509 客户端证书进行身份验证

通过 HTTPS 上的客户端证书对 WCF 请求进行身份验证

如何为通过 SSL 进行双向身份验证的本地测试创建客户端证书?

为啥要使用证书对客户端进行身份验证?

使用 WinInet 的客户端身份验证(证书 + 私钥)

如何使用 Apache 进行客户端证书身份验证