Mark Tickner
Mark Tickner

Reputation: 1053

Certificate pinning in Alamofire

I am creating an iPad app that accesses HTTPS web services. I want to implement pinning, but am having issues.

This class creates the Alamofire Manager (mostly taken from documentation):

class NetworkManager {

    var manager: Manager?

    init() {
        let serverTrustPolicies: [String: ServerTrustPolicy] = [
            "www.google.co.uk": .PinCertificates(
                certificates: ServerTrustPolicy.certificatesInBundle(),
                validateCertificateChain: true,
                validateHost: true
            ),
            "insecure.expired-apis.com": .DisableEvaluation
        ]

        manager = Alamofire.Manager(
            configuration: NSURLSessionConfiguration.defaultSessionConfiguration(),
            serverTrustPolicyManager: ServerTrustPolicyManager(policies: serverTrustPolicies)
        )
    }
}

This function makes the call:

static let networkManager = NetworkManager()

public static func testPinning() {
    networkManager.manager!.request(.GET, "https://www.google.co.uk").response { response in
        if response.1 != nil {
            print("Success")
            print(response.1)
            print(response.1?.statusCode)
        } else {
            print("Error")
            print(response.3)
        }
    }
}

The certificate is saved in the project and shows under 'Targets > Build Phases > Copy Bundle Resources'.

I am currently receiving the following error every time I make the request (from the else block in testPinning()):

Optional(Error Domain=NSURLErrorDomain Code=-999 "cancelled" UserInfo={NSErrorFailingURLKey=https://www.google.co.uk/, NSLocalizedDescription=cancelled, NSErrorFailingURLStringKey=https://www.google.co.uk/})

Upvotes: 14

Views: 19184

Answers (3)

Mohamed Obaya
Mohamed Obaya

Reputation: 2593

After Alamofire version +5 there was a big change I'll list here what I did to do a certificate pinning. I was targeting Moya and since Moya as a layer above Alamofire what works for Alamofire should work with Moya.

First To get a server certificate you need to open it in the browser then click on the lock icon Then you need to click on the certificate as the screenshot shows. enter image description here after that you need to drag this certificate to your desktop or wherever download it look at the screenshot below. enter image description here Then you need to add it to your project using add file to in Xcode check screenshoot. enter image description here Here is my code For Alamofire:

var session: Session!

class ViewController: UIViewController {

func testPinning() {


let evaluators: [String: ServerTrustEvaluating] = [
    "stackoverflow.com": PublicKeysTrustEvaluator()
]

let manager = ServerTrustManager(evaluators: evaluators)

session = Session(serverTrustManager: manager)


session
    .request("https://stackoverflow.com/questions/34611112/certificate-pinning-in-alamofire/55902588#55902588", method: .get)
    .validate()
    .response(completionHandler: { [weak self] response in
        switch response.result {
        case .success:
            print(response.data)
        case .failure(let error):
            switch error {
            case .serverTrustEvaluationFailed(let reason):
                // The reason here is a place where you might fine-tune your
                // error handling and possibly deduce if it's an actualy MITM
                // or just another error, like certificate issue.
                //
                // In this case, this will show `noRequiredEvaluator` if you try
                // testing against a domain not in the evaluators list which is
                // the closest I'm willing to setting up a MITM. In production,
                // it will most likely be one of the other evaluation errors.
                print(reason)
            default:
                print("default")
            }
        }
    })

}

For Moya you will need to add that session to your moya provider.

let evaluators: [String: ServerTrustEvaluating] = [
    "stackoverflow.com": PublicKeysTrustEvaluator()
]

let manager = ServerTrustManager(evaluators: evaluators)

session = Session(serverTrustManager: manager)


let provider = MoyaProvider<YourStackOVerflowProvider>(
    session: session
)


provider.request(.pluginManger) { response in
    switch response {
    case .failure(let err):
        print(err)
    case .success(let response):
        print(response)
    }
}

Upvotes: 1

Mudith Chathuranga Silva
Mudith Chathuranga Silva

Reputation: 7434

First, you need to download the certificate. The best way is to download the certificate on the Firefox browser.

Step 1

Go to your webpage/ API and click the lock icon to get a certificate.

enter image description here

Step 2

Click View Certificate

enter image description here

Step 3

Click Certificate Fields tab's first section and click export

enter image description here

Step 4

Select the Format:- DER

enter image description here

Step 5

Drag and drop the file into your XCode Project

enter image description here

Step 6

Add Certificate under 'Targets > Build Phases > Copy Bundle Resources'

enter image description here

Step 7

Add Network Manager File. Replace your URL with google.com

 import Foundation
 import Alamofire
 import SwiftyJSON

 class MYPNetworkManager {


     var Manager: SessionManager?

     init() {
         let serverTrustPolicies: [String: ServerTrustPolicy] = [
             "https://google.com": .pinCertificates(
                 certificates: ServerTrustPolicy.certificates(),
                 validateCertificateChain: true,
                 validateHost: true
             ),
             "insecure.expired-apis.com": .disableEvaluation
         ]

         Manager = SessionManager(
             serverTrustPolicyManager: ServerTrustPolicyManager(policies: 
 serverTrustPolicies)
         )

     }
 }

Step 8

Add a file to get the session manager

import Foundation
import Alamofire
import SwiftyJSON

class APIPinning {

    private static let NetworkManager = MYPNetworkManager()

    public static func getManager() -> SessionManager {
        return NetworkManager.Manager!
    }
 }

Step 9

Use this session manager on Alamofire eg:-

 public static func testPinning() {
NetworkManager.Manager!.request("YourURL", method: .get, encoding: URLEncoding.httpBody, headers: MConnect.headersWithToken)
    .validate()
    .responseJSON { response in

        print(response)
        switch response.result {
        case .success:

            if let value = response.result.value {
                let json = JSON(value)
                print(json)
            } else {

            }

        case .failure:
            print("Error")
        }
}
}

Upvotes: 10

jcaron
jcaron

Reputation: 17710

So, the issue was that the certificate was saved in the wrong format.

ServerTrustPolicy.certificatesInBundle() finds all certificates in the bundle based on a list of extensions, then tries to load them using SecCertificateCreateWithData. Per its documentation, this function:

Returns NULL if the data passed in the data parameter is not a valid DER-encoded X.509 certificate

When you export a certificate in Firefox, you have a "format" pop-up at the bottom of the file browser. Select "X.509 Certificate (DER)", and you should get a certificate in the right format for this purpose.

Upvotes: 10

Related Questions