使用 AlamoFire 在 Swift 中通过证书固定实现的 HTTPS 请求失败

HTTPS requests implemented with Certificate pinning in Swift using AlamoFire fails

我们正在尝试在我们的 iOS 应用程序中实施 ssl 以连接到 Tomcat Web 服务器中的 Rest Web 服务 运行。 在 tomcat 配置中将 clientAuth 设置为 true 时面临以下问题:

2018-05-08 09:28:08.442409+0530 [925:337357] XPC connection interrupted
2018-05-08 09:29:26.481465+0530 [925:336959] [Common] _BSMachError: port 9d6f; (os/kern) invalid capability (0x14) "Unable to insert COPY_SEND"
2018-05-08 09:29:26.485693+0530 [925:336959] [Common] _BSMachError: port 9d6f; (os/kern) invalid name (0xf) "Unable to deallocate send right"
2018-05-08 09:29:44.930812+0530 [925:337804] [] nw_coretls_read_one_record tls_handshake_process: [-9825]
2018-05-08 09:29:44.970760+0530 [925:337766] NSURLSession/NSURLConnection HTTP load failed (kCFStreamErrorDomainSSL, -9825)
2018-05-08 09:29:44.989 [Debug] [main] [HttpUtils.swift:351] handleResponse(response:completionHandler:) > Value: FAILURE;
2018-05-08 09:29:44.999 [Error] [main] [HttpUtils.swift:363] checkRestResponseErrorAndGetUserUnderstandableError(error:completionHandler:) > Error Domain=NSURLErrorDomain Code=-1205 "The server “example.com” did not accept the certificate." UserInfo={NSLocalizedDescription=The server “example.com” did not accept the certificate., _kCFStreamErrorDomainKey=3, NSUnderlyingError=0x16e35930 {Error Domain=kCFErrorDomainCFNetwork Code=-1205 "(null)" UserInfo={_kCFStreamPropertySSLClientCertificateState=1, _kCFNetworkCFStreamSSLErrorOriginalValue=-9825, _kCFStreamErrorCodeKey=-9825, _kCFStreamErrorDomainKey=3, kCFStreamPropertySSLPeerTrust=<SecTrustRef: 0x16e2ab90>, kCFStreamPropertySSLPeerCertificates=(
"<cert(0x17337200) s: example.com i: Go Daddy Secure Certificate Authority - G2>",
"<cert(0x172e0c00) s: Go Daddy Secure Certificate Authority - G2 i: Go Daddy Root Certificate Authority - G2>",
"<cert(0x172abe00) s: Go Daddy Root Certificate Authority - G2 i: Go Daddy Root Certificate Authority - G2>"
)}}, _kCFStreamErrorCodeKey=-9825, NSErrorFailingURLStringKey=https://example.com:8443/myserver/rest/myresource/servicepath, NSErrorPeerCertificateChainKey=(
"<cert(0x17337200) s: example.com i: Go Daddy Secure Certificate Authority - G2>",
"<cert(0x172e0c00) s: Go Daddy Secure Certificate Authority - G2 i: Go Daddy Root Certificate Authority - G2>",
"<cert(0x172abe00) s: Go Daddy Root Certificate Authority - G2 i: Go Daddy Root Certificate Authority - G2>"
), NSErrorClientCertificateStateKey=1, NSURLErrorFailingURLPeerTrustErrorKey=<SecTrustRef: 0x16e2ab90>, NSErrorFailingURLKey=https://example.com:8443/myserver/rest/myresource/servicepath}

这是一个代码片段,展示了我们如何创建 AlamoFire 会话管理器:

import Foundation
import Alamofire

class HttpUtils {
       static let utils = HttpUtils()
       var alamoFireSSLManager : SessionManager?

       public static func getJSONRequestWithBody(url: String, targetViewController: UIViewController?, params : Dictionary<String, String>,
                      handler: @escaping RiderRideRestClient.responseJSONCompletionHandler){
                      let isNetworkAvailable = Reachability.isConnectedToNetwork()
                      if isNetworkAvailable {
                                     utils.createAlamoFireManager()
                                     utils.alamoFireSSLManager!
                                                    .request(url, method: .get, parameters: params, encoding: URLEncoding.methodDependent, headers: nil)
                                                    .responseJSON(completionHandler: {
                                                                   (response) in
                                                                   handleResponse(response: response, completionHandler: handler)
                                                    })
                      }
                      else {
                                     handler(nil, Errors.NetworkConnectionNotAvailableError)
                      }
       }

       func createAlamoFireManager() {
                      if (HttpUtils.utils.alamoFireSSLManager == nil) {
                                     let mydomainCertificates = getCertificates(filename: "mydomaincertificate", type: "cer")
                                     let mydomainTrustPolicy = ServerTrustPolicy.pinCertificates(
                                                    certificates: mydomainCertificates,
                                                    validateCertificateChain: true,
                                                    validateHost: true)

                                     let sub2DomainCertificates = getCertificates(filename: "sub2domaincertificate", type: "cer")
                                     let sub2DomainTrustPolicy = ServerTrustPolicy.pinCertificates(
                                                    certificates: sub2DomainCertificates,
                                                    validateCertificateChain: true,
                                                    validateHost: true)

                                     var serverTrustPolicies = [String : ServerTrustPolicy] ()
                                     serverTrustPolicies[example.com] = mydomainTrustPolicy
                                     serverTrustPolicies[sub2.example.com] = sub2DomainTrustPolicy
                                     serverTrustPolicies[sub3.example.com] = ServerTrustPolicy.disableEvaluation

                                     HttpUtils.utils.alamoFireSSLManager =  SessionManager(configuration: URLSessionConfiguration.default,
                                                    serverTrustPolicyManager: ServerTrustPolicyManager(policies: serverTrustPolicies))
                      }
       }

       func getCertificates(filename : String, type : String) -> [SecCertificate] {
                      let url = Bundle.main.url(forResource: filename, withExtension: type)!
                      let localCertificate = try! Data(contentsOf: url) as CFData
                      guard let certificate = SecCertificateCreateWithData(nil, localCertificate)
                                     else { return [] }
                      return [certificate]
       }
}

我尝试将以下内容添加到 info.plist 的 "App Transport Security Settings" 中:

<dict>
    <key>NSAllowsArbitraryLoads</key>
    <true/>
    <key>NSExceptionDomains</key>
    <dict>
        <key>sub2.example.com</key>
        <dict>
            <key>NSExceptionAllowsInsecureHTTPLoads</key>
            <true/>
            <key>NSExceptionRequiresForwardSecrecy</key>
            <false/>
            <key>NSIncludesSubdomains</key>
            <true/>
        </dict>
        <key>example.com</key>
        <dict>
            <key>NSExceptionAllowsInsecureHTTPLoads</key>
            <true/>
            <key>NSExceptionRequiresForwardSecrecy</key>
            <false/>
            <key>NSIncludesSubdomains</key>
            <true/>
        </dict>
    </dict>
</dict>

然后我得到以下错误:

2018-05-08 12:36:26.761020+0530[82215:62448515] CredStore - copyIdentPrefs - Error copying Identity cred. Error=-25300, query={
class = idnt;
labl = "https://example.com:8443/";
"r_Ref" = 1;
}
2018-05-08 12:36:26.834384+0530[82215:62448514] [BoringSSL] Function boringssl_session_handshake_error_print: line 3108 boringssl ctx 0x105767600: 4505905920:error:10000412:SSL routines:OPENSSL_internal:SSLV3_ALERT_BAD_CERTIFICATE:/BuildRoot/Library/Caches/com.apple.xbs/Sources/boringssl/boringssl-109.20.5/ssl/tls_record.c:547:SSL alert number 42
2018-05-08 12:36:26.838433+0530[82215:62448514] TIC TCP Conn Failed [1:0x1c017b900]: 3:-9802 Err(-9802)
2018-05-08 12:36:27.044962+0530[82215:62447873] CredStore - copyIdentPrefs - Error copying Identity cred. Error=-25300, query={
class = idnt;
labl = "https://example.com:8443/";
"r_Ref" = 1;
}
2018-05-08 12:36:27.112478+0530[82215:62448515] TIC TCP Conn Failed [2:0x1c417e240]: 3:-9802 Err(-9802)
2018-05-08 12:36:27.179865+0530[82215:62448515] TIC TCP Conn Failed [3:0x1c417dac0]: 3:-9800 Err(-9800)
2018-05-08 12:36:27.180800+0530[82215:62448515] NSURLSession/NSURLConnection HTTP load failed (kCFStreamErrorDomainSSL, -9800)
2018-05-08 12:36:27.180898+0530[82215:62448515] Task .<1> HTTP load failed (error code: -1200 [3:-9800])
2018-05-08 12:36:27.182153+0530[82215:62448514] Task .<1> finished with error - code: -1200
2018-05-08 12:36:27.204 [Error] [main] Error Domain=NSURLErrorDomain Code=-1200 "An SSL error has occurred and a secure connection to the server cannot be made." UserInfo={_kCFStreamErrorCodeKey=-9800, NSLocalizedRecoverySuggestion=Would you like to connect to the server anyway?, NSUnderlyingError=0x1c025d2b0 {Error Domain=kCFErrorDomainCFNetwork Code=-1200 "(null)" UserInfo={_kCFStreamPropertySSLClientCertificateState=0, _kCFNetworkCFStreamSSLErrorOriginalValue=-9800, _kCFStreamErrorDomainKey=3, _kCFStreamErrorCodeKey=-9800}}, NSLocalizedDescription=An SSL error has occurred and a secure connection to the server cannot be made., NSErrorFailingURLKey=https://example.com:8443/myserver/rest/myresource/servicepath, NSErrorFailingURLStringKey=https://example.com:8443/myserver/rest/myresource/servicepath, _kCFStreamErrorDomainKey=3}

我使用以下命令生成了证书,并确保该证书应用于我在 Xcode 中的目标:

openssl s_client -connect example.com:8443 -servername example.com < /dev/null | openssl x509 -outform DER > mydomaincertificate.cer

我检查了几个 SO 帖子以了解问题所在,但 none 其他帖子中给出的答案有效。我做错了什么?

显然,AlamoFire 不支持客户端证书!因此,在 tomcat 配置中将 clientAuth 设置为 true 时出现此错误。