user3897968
user3897968

Reputation: 41

NSURLSession with backgroundSessionConfiguration can not work with user certificate

I found a wired thing about NSURLSession when using background session configuration. We use a self asigned certificate when contact with server, an implement:

- (void)URLSession:(NSURLSession *)session
              task:(NSURLSessionTask *)task
didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
 completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler
{
    NSURLSessionAuthChallengeDisposition disposition = NSURLSessionAuthChallengePerformDefaultHandling;
    __block NSURLCredential *credential = nil;

    if (self.taskDidReceiveAuthenticationChallenge) {
        disposition = self.taskDidReceiveAuthenticationChallenge(session, task, challenge, &credential);
    } else {
        if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
            if ([self.securityPolicy evaluateServerTrust:challenge.protectionSpace.serverTrust forDomain:challenge.protectionSpace.host]) {
                disposition = NSURLSessionAuthChallengeUseCredential;
                credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
            } else {
                disposition = NSURLSessionAuthChallengeCancelAuthenticationChallenge;
            }
            NSLog(@"ServerTrust:%@", task.originalRequest.URL);
        }  else if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodClientCertificate]) {
            if (self.clientCertCredential && [challenge previousFailureCount] == 0) {
                credential = self.clientCertCredential;
                disposition = NSURLSessionAuthChallengeUseCredential;
                NSLog(@"ClientCert:%@", task.originalRequest.URL);
            } else {
                disposition = NSURLSessionAuthChallengePerformDefaultHandling;
            }
        } else {
            disposition = NSURLSessionAuthChallengePerformDefaultHandling;
        }
    }

    if (completionHandler) {
        completionHandler(disposition, credential);
    }
}

When using defaultSessionConfiguration it works perfect, but when I change the session configuration to background session configration, This delegate method will be called in a loop, an none of other delegate method will be called, and this request will never complete.

Here is the console output:

2014-08-11 15:36:01.204 OneBox[1736:a413] ServerTrust:https://demo.mycompany.com/api/v1/files/351/1/9cc69106455d11599a08ed978fbdbe1d/contents 2014-08-11 15:36:01.232 OneBox[1736:1413] ClientCert:https://demo.mycompany.com/api/v1/files/351/1/9cc69106455d11599a08ed978fbdbe1d/contents 2014-08-11 15:36:02.068 OneBox[1736:8c03] ServerTrust:https://demo.mycompany.com/api/v1/files/351/1/9cc69106455d11599a08ed978fbdbe1d/contents 2014-08-11 15:36:02.076 OneBox[1736:1413] ClientCert:https://demo.mycompany.com/api/v1/files/351/1/9cc69106455d11599a08ed978fbdbe1d/contents 2014-08-11 15:36:12.728 OneBox[1736:1413] ServerTrust:https://demo.mycompany.com/api/v1/files/351/1/9cc69106455d11599a08ed978fbdbe1d/contents 2014-08-11 15:36:12.735 OneBox[1736:1413] ClientCert:https://demo.mycompany.com/api/v1/files/351/1/9cc69106455d11599a08ed978fbdbe1d/contents

Upvotes: 2

Views: 688

Answers (1)

Vejja
Vejja

Reputation: 81

OK I know this is an old question but I've been banging my head over the walls on this issue also, so hoping this will help somebody, here is my solution to the problem. To be fair, my "solution" feels more like a convoluted workaround to me than a proper solution, but at least it works.

The short answer is that the key to make it work is to use NSURLProtectionSpace to set a default permanent credential for all sessions. This prevents the delegate from being called when presented with a challenge of type NSURLAuthenticationMethodClientCertificate.

The long answer follows below.

In your code, this will not work :

credential = self.clientCertCredential;
disposition = NSURLSessionAuthChallengeUseCredential;
//
// Redacted for clarity
//
completionHandler(disposition, credential);

Because in a background session, the delegate cannot have access to self.clientCertCredential (God only knows why).

However I found out that the background session will not try to call the delegate if you have previously defined a default credential in an NSURLProtectionSpace.

So scratch all of your else if block and instead do the following :

NSURLProtectionSpace *space = [NSURLProtectionSpace 
    initWithHost:@"your_address"
    port:your_port
    protocol:@"https"
    realm:@"your_realm"
    authenticationMethod:NSURLAuthenticationMethodClientCertificate];
[[NSURLCredentialStorage sharedCredentialStorage] 
    setDefaultCredential:self.clientCertCredential
    forProtectionSpace:space];

If the host, port and realm parameters match exactly those of your server, then when the challenge is presented to the background session, the challenge.protectionSpace will find the default credential automagically.

In order for it to work, this code will need to be executed before you try to make any request with the background session. You can do it whenever you load the client certificate into self.clientCertCredential for instance.

But beware !!! There is one more subtlety here. Whenever you do so, make sure you load the certificate using the persistence option NSURLCredentialPersistencePermanent. Otherwise it will not work.

One last note. Depending on your use case, the drawback of using this hack is that you might find yourself with a bunch of permanently persisted credentials if several NSURLProtectionSpaces. You might then have to do some housekeeping after setting defaultCredential for the NSURLCredentialStorage class. This is beyond the scope of this answer, but the class has some convenience methods such as -removeCredential:forProtectionSpace: which are documented here https://developer.apple.com/library/mac/documentation/Cocoa/Reference/Foundation/Classes/NSURLCredentialStorage_Class/

Upvotes: 1

Related Questions