美文网首页
[Swift] URLSessionDelegate Https

[Swift] URLSessionDelegate Https

作者: 巨馍蘸酱 | 来源:发表于2023-03-14 16:47 被阅读0次

异常信息

没有客户端校验 (解决方式配置 info.plist)

An SSL error has occurred and a secure connection to the server cannot be made.

URLSessionDelegate 证书认证!
URLSessionDelegate 服务端证书认证!
URLSessionDelegate 服务端证书认证! is equal
2023-03-15 16:33:21.470219+0800 chat[4748:412091] ATS failed system trust
2023-03-15 16:33:21.470308+0800 chat[4748:412091] Connection 1: system TLS Trust evaluation failed(-9802)
2023-03-15 16:33:21.470447+0800 chat[4748:412091] Connection 1: TLS Trust encountered error 3:-9802
2023-03-15 16:33:21.470514+0800 chat[4748:412091] Connection 1: encountered error(3:-9802)
2023-03-15 16:33:21.472186+0800 chat[4748:412091] Task <AFA49683-E1F5-445C-BD56-BF6EA6341C34>.<1> HTTP load failed, 0/0 bytes (error code: -1200 [3:-9802])
2023-03-15 16:33:21.472856+0800 chat[4748:412091] [tcp] tcp_output [C1.1.1:3] flags=[R.] seq=1741325076, ack=1322275982, win=4093 state=CLOSED rcv_nxt=1322275982, snd_una=1741325076
2023-03-15 16:33:21.524408+0800 chat[4748:411894] Task <AFA49683-E1F5-445C-BD56-BF6EA6341C34>.<1> finished with error [-1200] Error Domain=NSURLErrorDomain Code=-1200 "An SSL error has occurred and a secure connection to the server cannot be made." UserInfo={NSLocalizedRecoverySuggestion=Would you like to connect to the server anyway?, _kCFStreamErrorDomainKey=3, NSErrorPeerCertificateChainKey=(
    "<cert(0x10800c200) s: chat.api.com i: chat.api.com>"
), NSErrorClientCertificateStateKey=0, NSErrorFailingURLKey=https://chat.api.com/message/joined, NSErrorFailingURLStringKey=https://chat.api.com/message/joined, NSUnderlyingError=0x282b60420 {Error Domain=kCFErrorDomainCFNetwork Code=-1200 "(null)" UserInfo={_kCFStreamPropertySSLClientCertificateState=0, kCFStreamPropertySSLPeerTrust=<SecTrustRef: 0x2814081e0>, _kCFNetworkCFStreamSSLErrorOriginalValue=-9802, _kCFStreamErrorDomainKey=3, _kCFStreamErrorCodeKey=-9802, kCFStreamPropertySSLPeerCertificates=(
    "<cert(0x10800c200) s: chat.api.com i: chat.api.com>"
)}}, _NSURLErrorRelatedURLSessionTaskErrorKey=(
    "LocalDataTask <AFA49683-E1F5-445C-BD56-BF6EA6341C34>.<1>"
), _kCFStreamErrorCodeKey=-9802, _NSURLErrorFailingURLSessionTaskErrorKey=LocalDataTask <AFA49683-E1F5-445C-BD56-BF6EA6341C34>.<1>, NSURLErrorFailingURLPeerTrustErrorKey=<SecTrustRef: 0x2814081e0>, NSLocalizedDescription=An SSL error has occurred and a secure connection to the server cannot be made.}

完整代码


import UIKit

class ViewController: UIViewController {
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        self.view.backgroundColor = UIColor.systemTeal
        
        
        //双向认证
        httpGet(request: URLRequest(url: URL(string: "https://chat.api.com/message/joined")!))

    }
}

// MARK: URLSessionDelegate
extension ViewController: URLSessionDelegate {
    
    // 使用URLSession请求数据
    func httpGet(request: URLRequest) {
        
        let configuration = URLSessionConfiguration.default
        
        let session = URLSession(configuration: configuration, delegate: self, delegateQueue:OperationQueue.main)
        
        let dataTask = session.dataTask(with: request, completionHandler: { (data, response, error) -> Void in
            if let data = data, let str = String(data: data, encoding: String.Encoding.utf8) {
                print("URLSessionDelegate 访问成功,数据:" + str)
            }
            print("URLSessionDelegate error:" + (error?.localizedDescription ?? "none"))
        })
        
        //使用resume方法启动任务
        dataTask.resume()
    }
    
    // 在访问资源的时候,如果服务器返回需要授权(提供一个URLCredential对象)
    // 那么该方法就回被调用(这个是URLSessionDelegate代理方法)
    func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
        
        print("URLSessionDelegate 证书认证!")
        
        //认证服务器证书
        if challenge.protectionSpace.authenticationMethod == (NSURLAuthenticationMethodServerTrust) {
            print("URLSessionDelegate 服务端证书认证!")
            guard let serverTrust:SecTrust = challenge.protectionSpace.serverTrust,
                  let certificate = SecTrustGetCertificateAtIndex(serverTrust, 0),
                  let remoteCertificateData = CFBridgingRetain(SecCertificateCopyData(certificate)) else {
                
                completionHandler(.cancelAuthenticationChallenge, nil)
                return
            }
            
            guard let cerPath = Bundle.main.path(forResource: "client_trust", ofType: "cer") else {
                completionHandler(.cancelAuthenticationChallenge, nil)
                return
            }
            let cerUrl = URL(fileURLWithPath:cerPath)
            guard let localCertificateData = try? Data(contentsOf: cerUrl) else {
                completionHandler(.cancelAuthenticationChallenge, nil)
                return
            }
            
            if (remoteCertificateData.isEqual(localCertificateData)) {
                let credential = URLCredential(trust: serverTrust)
                challenge.sender?.use(credential, for: challenge)
                completionHandler(URLSession.AuthChallengeDisposition.useCredential, credential)
                
            } else {
                //completionHandler(.cancelAuthenticationChallenge, nil)
                completionHandler(.performDefaultHandling, nil)
            }
            
        } else if challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodClientCertificate {
            //认证客户端证书
            print("URLSessionDelegate 客户端证书认证!")
            //获取客户端证书相关信息
            let identityAndTrust:IdentityAndTrust = self.extractIdentity()
            
            let urlCredential:URLCredential = URLCredential(identity: identityAndTrust.identityRef,
                                                            certificates: identityAndTrust.certArray as? [AnyObject],
                                                            persistence: URLCredential.Persistence.forSession)
            
            completionHandler(.useCredential, urlCredential)
            
        } else {
            // 其它情况(不接受认证)
            print("URLSessionDelegate 其它情况(不接受认证)")
            completionHandler(.cancelAuthenticationChallenge, nil);
        }
    }
    
    //获取客户端证书相关信息
    func extractIdentity() -> IdentityAndTrust {
        print("URLSessionDelegate 获取客户端证书相关信息")
        var identityAndTrust:IdentityAndTrust!
        var securityError:OSStatus = errSecSuccess
        
        let path: String = Bundle.main.path(forResource: "client", ofType: "p12")!
        let PKCS12Data = NSData(contentsOfFile:path)!
        let key = kSecImportExportPassphrase as NSString
        let options: NSDictionary = [key : "123456"] //客户端证书密码
        //create variable for holding security information
        //var privateKeyRef: SecKeyRef? = nil
        
        var items : CFArray?
        
        securityError = SecPKCS12Import(PKCS12Data, options, &items)
        
        if securityError == errSecSuccess, let certItems:CFArray = items {
            let certItemsArray:Array = certItems as Array
            let dict:AnyObject? = certItemsArray.first
            if let certEntry:Dictionary = dict as? Dictionary<String, AnyObject> {
                // grab the identity
                let identityPointer:AnyObject? = certEntry["identity"];
                let secIdentityRef:SecIdentity = identityPointer as! SecIdentity
                print("URLSessionDelegate \(identityPointer)  :::: \(secIdentityRef)")
                // grab the trust
                let trustPointer:AnyObject? = certEntry["trust"]
                let trustRef:SecTrust = trustPointer as! SecTrust
                print("URLSessionDelegate \(trustPointer)  :::: \(trustRef)")
                // grab the cert
                let chainPointer:AnyObject? = certEntry["chain"]
                identityAndTrust = IdentityAndTrust(identityRef: secIdentityRef,
                                                    trust: trustRef, certArray:  chainPointer!)
            }
        }
        return identityAndTrust;
    }
}

//定义一个结构体,存储认证相关信息
struct IdentityAndTrust {
    var identityRef:SecIdentity
    var trust:SecTrust
    var certArray:AnyObject
}

info.plist

<key>NSAppTransportSecurity</key>
<dict>
    <key>NSExceptionDomains</key>
    <dict>
        <key>chat.api.com</key>
        <dict>
            <key>NSExceptionAllowsInsecureHTTPLoads</key>
            <true/>
            <key>NSExceptionRequiresForwardSecrecy</key>
            <false/>
            <key>NSIncludesSubdomains</key>
            <true/>
            <key>NSAllowsArbitraryLoads</key>
            <true/>
        </dict>
    </dict>
</dict>

客户端证书另一种实现 ( 异常信息是业务错误)

The server “chat.api.com” requires a client certificate.

URLSessionDelegate 证书认证!
URLSessionDelegate 服务端证书认证!
URLSessionDelegate 证书认证!
URLSessionDelegate 客户端证书认证!
URLSessionDelegate 获取客户端证书相关信息
URLSessionDelegate Optional(<SecIdentityRef: 0x2833eda00>)  :::: <SecIdentityRef: 0x2833eda00>
URLSessionDelegate Optional(<SecTrustRef: 0x2802c41e0>)  :::: <SecTrustRef: 0x2802c41e0>
2023-03-15 17:35:31.813096+0800 aichat[5173:433850] Task <B7354C74-EB9D-4EEE-BE58-617E5B83F408>.<1> finished with error [-1206] Error Domain=NSURLErrorDomain Code=-1206 "The server “chat.api.com” requires a client certificate." UserInfo={NSLocalizedDescription=The server “chat.api.com” requires a client certificate., NSErrorFailingURLStringKey=https://chat.api.com/message/joined, NSErrorFailingURLKey=https://chat.api.com/message/joined, _NSURLErrorRelatedURLSessionTaskErrorKey=(
    "LocalDataTask <B7354C74-EB9D-4EEE-BE58-617E5B83F408>.<1>"
), _NSURLErrorFailingURLSessionTaskErrorKey=LocalDataTask <B7354C74-EB9D-4EEE-BE58-617E5B83F408>.<1>, NSUnderlyingError=0x283db06c0 {Error Domain=kCFErrorDomainCFNetwork Code=-1206 "(null)" UserInfo={NSErrorPeerAddressKey=<CFData 0x2810f4690 [0x1fbf7b6c8]>{length = 16, capacity = 16, bytes = 0x100201bb2b8b8de80000000000000000}}}}
URLSessionDelegate error:The server “chat.api.com” requires a client certificate.
2023-03-15 17:35:33.413266+0800 aichat[5173:433885] [connection] nw_resolver_start_query_timer_block_invoke [C1.1] Query fired: did not receive all answers in time for chat.api.com:443

修改 NSURLAuthenticationMethodClientCertificate 校验

        if challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodClientCertificate {
            //认证客户端证书
            print("URLSessionDelegate 客户端证书认证!")
            //获取项目中P12证书文件的路径
            let path: String = Bundle.main.path(forResource: "client", ofType: "p12")!
            let PKCS12Data = NSData(contentsOfFile:path)!
            let key : NSString = kSecImportExportPassphrase as NSString
            let options : NSDictionary = [key : "123456"] //客户端证书密码
            var items: CFArray?
            let error = SecPKCS12Import(PKCS12Data, options, &items)
            
            if error == errSecSuccess {
                let itemArr = items! as Array
                let item = itemArr.first!
                let identityPointer = item["identity"];
                let secIdentityRef = identityPointer as! SecIdentity
                let chainPointer = item["chain"]
                let chainRef = chainPointer as? [Any]
                let credential = URLCredential.init(identity: secIdentityRef, certificates: chainRef, persistence: URLCredential.Persistence.forSession)
                challenge.sender?.use(credential, for: challenge)
                completionHandler(.useCredential, credential)
            } else {
                completionHandler(.cancelAuthenticationChallenge, nil)
            }
        } 

鸣谢

相关文章

网友评论

      本文标题:[Swift] URLSessionDelegate Https

      本文链接:https://www.haomeiwen.com/subject/auohrdtx.html