32

I have an app that is production along with a development server that has a self signed certificate.

I am attempting to test NSURLSession and background downloading but can't seem to get past - (void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler

When I use NSURLConnection I am able to bypass it using:

- (BOOL)connection:(NSURLConnection *)connection canAuthenticateAgainstProtectionSpace:(NSURLProtectionSpace *)protectionSpace {

    NSLog(@"canAuthenticateAgainstProtectionSpace %@", [protectionSpace authenticationMethod]);

    return [protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust];
}

- (void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge {

    NSLog(@"didReceiveAuthenticationChallenge %@ %zd", [[challenge protectionSpace] authenticationMethod], (ssize_t) [challenge previousFailureCount]);

    [challenge.sender useCredential:[NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust] forAuthenticationChallenge:challenge];
}

But I can't figure out how to get this to work with NSURLSession >:(

This is what I have currently (that doesn't work):

- (void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler {
    NSLog(@"NSURLSession did receive challenge.");

    completionHandler(NSURLSessionAuthChallengeUseCredential, [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust]);
}

I also tried creating a category of NSURLSession that would allow any certificate for a host:

#import "NSURLRequest+IgnoreSSL.h"

@implementation NSURLRequest (IgnoreSSL)

+ (BOOL)allowsAnyHTTPSCertificateForHost:(NSString*)host {
    return YES;
}

+ (void)setAllowsAnyHTTPSCertificate:(BOOL)allow forHost:(NSString*)host {}

@end

Which also doesn't seem to help.


EDIT

I've updated this method to return:

- (void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler {

    //Creates credentials for logged in user (username/pass)
    NSURLCredential *cred = [[AuthController sharedController] userCredentials];

    completionHandler(NSURLSessionAuthChallengeUseCredential, cred);

}

Which still does nothing.

random
  • 8,428
  • 12
  • 46
  • 81
  • What is `credentialForTrust:` returning? – Aaron Brager Dec 02 '13 at 20:01
  • I'm not 100% sure, I've updated my question with some new code for didReceiveChallenge method that I believe to be a step closer in the right direction. – random Dec 02 '13 at 20:26
  • Does it work with defaultSessionConfiguration, i.e. foreground downloading? – Rhythmic Fistman Dec 04 '13 at 03:29
  • What do you mean by "doesn't work"? What happens? e.g. Your first attempt "works" for me (can connect) although it's insecure as you're not evaluating trust on the server's certificate. – Rhythmic Fistman Dec 04 '13 at 20:09
  • If you take out the categories and only implement URLSession:didReceiveChallenge: what does challenge.protectionSpace.authenticationMethod say? – Rhythmic Fistman Dec 04 '13 at 20:46
  • *Why* are you trying to "bypass" server trust verification at all? Didn't you mention, that's *production* code (but this code should really NOT production code!!). Then, your server apparently has a cert. So, why don't you implement correct production code *including* evaluating the certificate? – CouchDeveloper Dec 04 '13 at 21:08
  • @CouchDeveloper It's not production code, it's still in testing with a test server which doesn't have a cert. The web dev company we are working with won't put a cert on it, once we do move to the production server it will have a cert. – random Dec 05 '13 at 18:27
  • @RhythmicFistman null – random Dec 05 '13 at 18:27
  • @RhythmicFistman any by doesn't work I mean that it connects to the server, gets the challenge, responds to the challenge, then the session ends without downloading any files. – random Dec 05 '13 at 18:28

2 Answers2

41

For me your first example is working fine. I have tested with the following code without problems (it is of course very insecure since it allows any server certificate).

@implementation SessionTest

- (void) startSession
{
    NSURL *url = [NSURL URLWithString:@"https://self-signed.server.url"];

    NSURLSessionConfiguration *defaultConfigObject = [NSURLSessionConfiguration defaultSessionConfiguration];
    NSURLSession *defaultSession = [NSURLSession sessionWithConfiguration: defaultConfigObject delegate: self delegateQueue: [NSOperationQueue mainQueue]];

    NSURLSessionDataTask * dataTask = [defaultSession dataTaskWithURL:url
                                                completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
                                                    if(error == nil)
                                                    {
                                                        NSString * text = [[NSString alloc] initWithData: data encoding: NSUTF8StringEncoding];
                                                        NSLog(@"Data: %@",text);
                                                    }
                                                    else
                                                    {
                                                        NSLog(@"Error: %@", error);
                                                    }
                                                }];

    [dataTask resume];
}

- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler
{
    completionHandler(NSURLSessionAuthChallengeUseCredential, [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust]);
}

@end

Update: This is the class interface, the SessionTest class is the NSURLSessionDataDelegate, to start the data download you create a SessionTest object and call the startSession method.

@interface SessionTest : NSObject <NSURLSessionDelegate>

- (void) startSession;

@end
Julian F. Weinert
  • 7,076
  • 6
  • 56
  • 100
osanoj
  • 1,015
  • 10
  • 8
  • It has to be done in the background which has to use the delegate methods. – random Dec 05 '13 at 18:26
  • I'm not 100% sure what you mean. I have updated with the class interface to show that the delegate method URLSession:didReceiveChallenge:completionHandler: is used, as in your example. The data download is done asynchronously. – osanoj Dec 06 '13 at 12:07
  • 2
    I'd like to add some info for someone stumbling on this thread. Once the delegate method URLSession:task:didReceiveChallenge:CompletionHandler: grants trust to the self signed (or expired) certificate, it never gets called again anymore when rebuilding the developing application. That's cause the grant to that certificate gets persistently stored in the application cache folder inside the ~/Library/Cache/ one. – valeCocoa Apr 02 '17 at 22:30
1

There's not enough information to suggest a concrete solution to your problem.

Here are some principal requirements:

Disabling server trust evaluation should work as you tried in your first example. Use this for development only!

See also (https://developer.apple.com/library/ios/documentation/cocoa/Conceptual/URLLoadingSystem/Articles/UsingNSURLSession.html#//apple_ref/doc/uid/TP40013509-SW44)

jscs
  • 62,161
  • 12
  • 145
  • 186
CouchDeveloper
  • 16,175
  • 3
  • 41
  • 59