iosswiftalamofirehttp-proxycertificate-pinning

Alamofire ServerTrustPolicy Certificate Pinning Not Blocking Charles Proxy Swift 3


I've searched far and wide and have not been able to find an answer for my question. To make our app more secure, we've been told to use "certificate pinning". We already make use of the Alamofire library for all our API calls, so it seems natural to use the ServerTrustPolicyManager included as a means to implement certificate pinning. I've included the proper certificates in my app bundle, and here is the code I use to configure my SessionManager for Alamofire:

let url = "https://www.mycompany.com"

var manager: SessionManager? {

    let serverTrustPolicy = ServerTrustPolicy.pinCertificates(
        certificates: ServerTrustPolicy.certificates(),
        validateCertificateChain: true,
        validateHost: true
    )

    let serverTrustPolicies: [String: ServerTrustPolicy] = [
        url: serverTrustPolicy
    ]
    let config = URLSessionConfiguration.default

    return SessionManager(configuration: config, serverTrustPolicyManager: ServerTrustPolicyManager(policies: serverTrustPolicies))

}

Now when I want to make an API request, I have this method:

func request(routerRequest request: URLRequestConvertible) -> DataRequest {
    assert(url != "", "A base URL string must be set in order to make request")
    print("URL: \(url) : \(request)")
    return (manager ?? SessionManager.default).request(request)
}

Now the problem that I'm having is that this still works when I use something like a Charles Proxy ... all the requests are still going through. Shouldn't certificate pinning prevent something like a Charles Proxy from being able to work because the certificates won't match?

I've tested other apps that properly use certificate pinning, and they will block any kind of proxy (Charles or other) from making a connection. If I try running an app like Uber or my Wells Fargo banking app while I have Charles enabled, every request will get rejected and I'll see an error that says something like "Couldn't complete request, ssl certificate is invalid" (that's not verbatim).

I feel like there is a step that I'm missing after configuring my SessionManager. Most of the documentation that I've read and help I've come across seem to imply that after configuring the manager to enable certificate pinning, it should reject any request with an invalid certificate. Can anyone help me? What am I missing?

I appreciate any and all help. Thanks in advance!


Solution

  • I'm going to answer my own question, only because I want to possibly help anyone else with this same problem in the future. When I was configuring the serverTrustPolicies above, you create a dictionary of String : ServerTrustPolicy, my error lied in the String for the server name.

    let serverTrustPolicies: [String: ServerTrustPolicy] = [
        url: serverTrustPolicy
    ]
    

    My url property was the baseURL we use for our API, which was https://www.mycompany.com/api - this was causing all my issues. Once I adjusted that to just be the domain www.mycompany.com, the pinning worked just as expected! Now when I run Charles Proxy with pinning enabled, I get all my requests rejected, and Charles puts out an error that says "No request was made, possibly the SSL Certificate was rejected."

    Instead of having to do any serious String manipulation, I added this extension to use in the future - in case you have multiple baseURL's that you need to extract the domain out of:

    extension String {
        public func getDomain() -> String? {
            guard let url = URL(string: self) else { return nil }
            return url.host
        }
    }
    

    Now you can do something like this:

    let serverTrustPolicies: [String: ServerTrustPolicy] = [
        url.getDomain() ?? url : serverTrustPolicy
    ]
    

    Hope this helps someone else in the future! Goodluck