Certificate pinning in Alamofire
Asked Answered
D

3

14

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/})
Diluvial answered 5/1, 2016 at 11:53 Comment(7)
What certificate did you include? Have you have checked what ServerTrustPolicy.certificatesInBundle() returns (i.e. if it actually includes the certificate)? Wouldn't be too surprised if Google used multiple certificates for the same domain.Unconformable
ServerTrustPolicy.certificatesInBundle() returns 0, which I don't understand. Is there a specific way in which I should add the .cer certificates into the bundle? Regarding Google, that is an example site to replace the one I am developing, but the above problem is the same.Diluvial
You mean it returns an empty array? The return value of that call should be an array, not a number. You may want to try NSBundle.mainBundle().pathsForResourcesOfType(".cer", inDirectory: nil) to see if the file is found in the bundle (if it is, it would then probably point to an issue with the certificate file contents). Anything showing up in the logs when ServerTrustPolicy.certificatesInBundle() is called?Unconformable
Yes that is correct, I meant an array of 0 values. On closer inspection, that line of code can be found inside linkcertificatesInBundle(). In the for loop there, the certificate is found, but line 138 is preventing the certificate from being added to the array. I suspect it might also be the contents of the .cer, although it opens fine in Xcode (any suggestions on how I should save the certificate, instead of the method linked in the question?). Also, how could I check those logs?Diluvial
When you export a certificate in Firefox, you have a "format" pop-up at the bottom of the file browser. What format did you pick? I believe you need "X.509 Certificate (DER)" (per the documentation of SecCertificateCreateWithData which states "Returns NULL if the data passed in the data parameter is not a valid DER-encoded X.509 certificate" (emphasis mine).Unconformable
Using the .der format certificate seems to have solved the problem, the success code is now reached when making the request. Thanks for your help.Diluvial
I have the same problem ... any thing help me . could you please tell me the solution to yours ?Jemadar
U
10

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.

Unconformable answered 5/1, 2016 at 16:11 Comment(0)
T
10

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")
        }
}
}
Tool answered 29/4, 2019 at 11:39 Comment(1)
your code is not working in Alamofire 5. Can you please update your code to Alamofire 5 or can you share a link to get alamofire 5 code please?Tendon
O
1

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://mcmap.net/q/810194/-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)
    }
}
Operculum answered 30/5, 2021 at 8:48 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.