3

I need to migrate from Alamofire 4 to 5 but I'm missing sessionDidReceiveChallenge callback on the delegate

I used before in version 4 something like this:

let manager = Alamofire.SessionManager(
    configuration: URLSessionConfiguration.default
)

manager.delegate.sessionDidReceiveChallenge = { session, challenge in

    let method = challenge.protectionSpace.authenticationMethod

    if method == NSURLAuthenticationMethodClientCertificate {
        return (.useCredential, self.cert.urlCredential())
    }
    if method == NSURLAuthenticationMethodServerTrust {
        let trust = challenge.protectionSpace.serverTrust!
        let credential = URLCredential(trust: trust)
        return (.useCredential, credential)
    }
    return (.performDefaultHandling, Optional.none)
}

but now is version 5 the delegate has changed to SessionDelegate class without providing a similar function

I tried to use the delegate from the URLSession like this:

let delegate = SomeSessionDelegate()

let delegateQueue: OperationQueue = .init()

delegateQueue.underlyingQueue = .global(qos: .background)

let session = URLSession(
    configuration: URLSessionConfiguration.af.default,
    delegate: delegate,
    delegateQueue: delegateQueue
)

let manager = Alamofire.Session(
    session: session,
    delegate: SessionDelegate(),
    rootQueue: .global(qos: .background)
)

class SomeSessionDelegate: NSObject, URLSessionDelegate {

    let cert = ...

    func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {

        //same impl as before
    }
}

I'm guessing that my implementation in version 5 is wrong because I stopped getting response callback

Please advise on how to manage the request challenge properly in version 5

Dhruv
  • 2,153
  • 3
  • 21
  • 45
zombie
  • 5,069
  • 3
  • 25
  • 54

2 Answers2

2

It isn't necessary to override the SessionDelegate to use client certificates. Alamofire will automatically use an attached URLCredential for client certificate challenges. Just attach the credential to the request:

AF.request(...)
    .authenticate(with: clientCertCredential)
    .response...

Also, your server trust check will return any trust as valid, which could be a security issue. I'd stop using that code immediately.

Jon Shier
  • 12,200
  • 3
  • 35
  • 37
  • Thanks for your suggestion not to accept any certificate but doesn't matter in my case since the whole app is only allowed to connect to one host – zombie May 27 '20 at 10:09
  • is there a way to add the certificate to the session instead of the single request (other than writing an extension on the request) – zombie May 27 '20 at 10:10
  • 1
    Yes, you can set up the `URLSessionConfiguration` with a custom `URLCredentialStorage` or use the `shared` `URLCredentialStorage`. You can set a default credential for the client cert space. – Jon Shier May 27 '20 at 15:52
0

For a certificate handling on the session level I used URLProtectionSpace on the URLCredentialStorage shared storage and then set that to Alamofire.Session configuration

here is an example to set it up (port 443 might be enough)

fileprivate func registerURLCredential() {

    let storage = URLCredentialStorage.shared

    do {
        let credential: URLCredential = try loadURLCredential("certificate", password: "blablabla")

        let url = URL.API
        let host = url.host ?? ""

        let ports: [Int] = [80, 443, url.port ?? 0]

        for port in ports {

            let space = URLProtectionSpace(
                host: host,
                port: port,
                protocol: url.scheme,
                realm: nil,
                authenticationMethod: NSURLAuthenticationMethodClientCertificate
            )

            storage.set(credential, for: space)
        }
    } catch {
        print(error)
    }
}


fileprivate func createSession(_ configurationHandler: ((_ configuration: URLSessionConfiguration) -> Void)? = nil) -> Alamofire.Session {

    let configuration = URLSessionConfiguration.af.default

    registerURLCredential()

    configuration.urlCredentialStorage = .shared

    configurationHandler?(configuration)

    let session = Session(
        configuration: configuration,
        requestQueue: .global(qos: .background),
        serializationQueue: .global(qos: .background)
    )

    return session
}

A simple use for that would like:

let sesstion = createSession({ configuration in

    configuration.httpMaximumConnectionsPerHost = 1
})
zombie
  • 5,069
  • 3
  • 25
  • 54
  • Currently I use hashes and certificate pinning is done. I need to figure how to replace it ! – Dhruv Jun 03 '20 at 12:48
  • can you maybe give the link to your question so I can look at it (because the delegate seems to be gone for good) – zombie Jun 03 '20 at 12:50
  • I haven't raised any question yet, but I had used exactly before using Alamofire [this approach](https://github.com/nicwise/certificatepinner) – Dhruv Jun 03 '20 at 12:57
  • it seems like you would still be able to generate a `URLCredential` from your approach and then you can use it either in the storage or on a single request – zombie Jun 04 '20 at 07:02
  • Hey, I have opened a [question](https://stackoverflow.com/questions/62194554/alamofire-5-alternative-to-sessiondidreceivechallenge). Please check – Dhruv Jun 07 '20 at 07:21