Search code examples
iosswifthttpsalamofirecertificate-pinning

HTTPS requests implemented with Certificate pinning in Swift using AlamoFire fails


We are trying to implement ssl in our iOS application to connect to Rest webservices running in Tomcat Webserver. Facing following issue when clientAuth is set to true in tomcat configuration :

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}

Here is a code snippet that shows how we create AlamoFire Session manager:

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]
       }
}

I tried by adding following to "App Transport Security Settings" of info.plist :

<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>

Then I got following error :

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}

I generated the certificate using following command and made sure this certificate is applied to my target in Xcode:

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

I have checked several SO posts to understand what's wrong, but none of the answers given in other posts worked. What am I doing wrong?


Solution

  • Apparently, client certificates are not supported by AlamoFire! Hence this error when clientAuth is set to true in tomcat configuration.