1

I have just shifted to Alamofire 5.

Earlier I used URLSession and Certificate Pinner and to handle auth challenge I used delegate method of URLSessionDelegate with hash values

func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge,
                completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
    print("being challanged! for \(challenge.protectionSpace.host)")
    guard let trust = challenge.protectionSpace.serverTrust else {
        print("invalid trust!")
        completionHandler(.cancelAuthenticationChallenge, nil)
        return
    }

    let credential = URLCredential(trust: trust)

    let pinner = setupCertificatePinner(host: challenge.protectionSpace.host)

    if (!pinner.validateCertificateTrustChain(trust)) {
        print("failed: invalid certificate chain!")
        challenge.sender?.cancel(challenge)
    }

    if (pinner.validateTrustPublicKeys(trust)) {
        completionHandler(.useCredential, credential)
    } else {
        didPinningFailed = true
        print("couldn't validate trust for \(challenge.protectionSpace.host)")
        completionHandler(.cancelAuthenticationChallenge, nil)
    }

}

Having moved to Alamofire 5, there is no method sessionDidReceiveChallenge which was available in earlier version.

I tried:

private let session: Session = {
    let manager = ServerTrustManager(allHostsMustBeEvaluated: true, evaluators:
        ["devDomain.com": DisabledTrustEvaluator(),
         "prodDomain.com": PublicKeysTrustEvaluator()])
    let configuration = URLSessionConfiguration.af.default

    return Session(configuration: configuration, serverTrustManager: manager)
}()

But I get error:

Error Domain=Alamofire.AFError Code=11 "Server trust evaluation failed due to reason: No public keys were found or provided for evaluation."

Update: I'd still prefer a way to parse it using 256 fingerprint only, as we get domains and its hashes in first api call.

Dhruv
  • 2,153
  • 3
  • 21
  • 45

2 Answers2

1

First you need a ServerTrustEvaluating that handle the certificate pinning a simple implement would be something similar to

public final class CertificatePinnerTrustEvaluator: ServerTrustEvaluating {

    public init() {}

    func setupCertificatePinner(host: String) -> CertificatePinner {

        //get the CertificatePinner
    }

    public func evaluate(_ trust: SecTrust, forHost host: String) throws {

        let pinner = setupCertificatePinner(host: host)

        if (!pinner.validateCertificateTrustChain(trust)) {
            print("failed: invalid certificate chain!")
            throw AFError.serverTrustEvaluationFailed(reason: .noCertificatesFound)
        }

        if (!pinner.validateTrustPublicKeys(trust)) {
            print ("couldn't validate trust for \(host)")

            throw AFError.serverTrustEvaluationFailed(reason: .noCertificatesFound)
        }
    }
}

To be able to use the same evaluator I would suggest to subclass ServerTrustManager to return the same evaluator I did it like this:

class CertificatePinnerServerTrustManager: ServerTrustManager {

    let evaluator = CertificatePinnerTrustEvaluator()

    init() {
        super.init(allHostsMustBeEvaluated: true, evaluators: [:])
    }

    open override func serverTrustEvaluator(forHost host: String) throws -> ServerTrustEvaluating? {

        return evaluator
    }
}

after that you should be ready to go by creating the session and passing the manager to it

private let session: Session = {

    let trustManager = CertificatePinnerServerTrustManager()

    return Session(serverTrustManager: trustManager)
}()

My reference was the method urlSession(_:task:didReceive:completionHandler:) in Alamofire source in SessionDelegate.swift at line 86 (Alamofire V5.2.1)

zombie
  • 5,069
  • 3
  • 25
  • 54
  • Hi, the answer looks almost perfect, but as I have mentioned in question, in very first api response, I get certain domains which I need also need to evaluate. – Dhruv Jun 09 '20 at 10:23
  • lets say I get one domain like `testDomain.com` and its hashes, I should be able to validate that as well. – Dhruv Jun 09 '20 at 10:25
  • but as I checked CertificatePinner takes only one host or am I mistaken? and I think you can add as many evaluators as you want – zombie Jun 09 '20 at 10:27
  • I've updated the method of `didReceive challenge` in original question. In that I used to get the host and used to validate it. – Dhruv Jun 09 '20 at 10:35
  • @Dhruv I updated my answer. Would you please check it – zombie Jun 09 '20 at 11:52
  • I really appreciate and we are kind of nearby, but what I mean to say is, `testDomain.com` is something that I'm not aware. I get some other url apart from the ones which we have already evaluated. I get domain and hash, which I may not be aware. So Is there a way to evaluate new domain runtime ? – Dhruv Jun 09 '20 at 12:30
  • 1
    @Dhruv well I re-wrote the whole answer lets see if this is working – zombie Jun 09 '20 at 13:03
  • I tested the code and it works perfectly. :) Thank you so much for support. – Dhruv Jun 10 '20 at 05:11
0

If you want to pin with public keys you need to provide the certificates from which to parse those public keys in the bundle of your app, or otherwise provide them to PublicKeysTrustEvaluator.

Jon Shier
  • 12,200
  • 3
  • 35
  • 37
  • It'd have been helpful if you could provide a snippet as on how to provide value to `PublicKeysTrustEvaluator`. Anyways thanks for help, I'll try it out. – Dhruv Jun 05 '20 at 04:01
  • On all Alamofire question I see your answers, but not in details. Hope it had better documentation on site and how to use example ! – Dhruv Jun 05 '20 at 04:50
  • If you provide the certificate in your app bundle it will work automatically. Otherwise you need to provide your own keys, which you can do through the `PublicKeysTrustEvaluator`'s initializer. Manually providing a key that's not part of a certificate is complex and not something I can provide an example of. – Jon Shier Jun 05 '20 at 06:34
  • Is there any information as on why was hash pinning not included in Alamofire 5 ? Also, why `sessionDidReceiveChallenge` was removed ? Please help me understand this, as how effective is this approach? – Dhruv Jun 06 '20 at 04:42
  • Manually parsed hashes are complex to implement and we just haven't had time to do it. If you want to implement that code yourself you can create your own `ServerTrustEvaluating` type for pinning, no need to manually implement challenge handling. – Jon Shier Jun 06 '20 at 16:24
  • Hashing was useful as we get used to fetch hashes in first api calls, we have different domains to call the api and that may change. I added certificate for initial call it worked, but now for other calls its getting complex for me to do now. Again please at least provide me some code to create my own `ServerTrustEvaluating ` it will be really helpful, Sir. I'm kind of blocked at this point. Frankly speaking, your one liner answers are really not helping me. – Dhruv Jun 07 '20 at 05:32
  • `ServerTrustEvaluating` is a simple protocol, but you can read [our documentation](https://github.com/Alamofire/Alamofire/blob/master/Documentation/AdvancedUsage.md#evaluating-server-trusts-with-servertrustmanager-and-servertrustevaluating). You can also look at the code of the various evaluators implemented in Alamofire. – Jon Shier Jun 07 '20 at 19:44