1

I have a server with a self-signed SSL certificate installed. However, once I call the following method it doesn't get any response. Once I change the URL back to http, it works.

- (void)getAccountInfoWithCompletion:(void (^)(NSDictionary *json_response, NSError *error))completion
{
    NSURLRequest *request = [NSURLRequest requestWithURL:
                             [NSURL URLWithString:
                              [NSString stringWithFormat:@"%@/api/account/%d/get_info", BASE_HOST_URL_IP, [self getUserID]]
                            ]];

    [NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
        if (error)
        {
            if (completion)
            {
                //completion(@"error", error);
            }
        } else {
            NSString *response_string = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
            NSDictionary *json_object = [NSJSONSerialization JSONObjectWithData:[response_string dataUsingEncoding:NSUTF8StringEncoding] options:0 error:nil];

            if (completion)
            {
                completion(json_object, error);
            }
        }
    }];
}

My reason for the delegate is so I can use the self-signed certificate in my app. The following tutorial is what I was using, but then I realized I couldn't use the delegate with the completionHandler method. I need to keep the completionHandler method though.

http://www.cocoanetics.com/2010/12/nsurlconnection-with-self-signed-certificates/

What could I do in order to receive a response from the SSL site?

Alec
  • 919
  • 2
  • 10
  • 25
  • Side note - do not check `error` to know if there is an error. Check to see if `data` is `nil` or not. If it's `nil`, then there was an error. – rmaddy Jul 23 '14 at 00:32
  • To make this work, you can't use this convenience method. Use a "regular" `NSURLConnection` and implement all of the needed delegate methods. – rmaddy Jul 23 '14 at 00:34
  • @rmaddy There's absolutely no way? – Alec Jul 23 '14 at 00:37
  • You need to use the `NSURLSession` equivalent/replacement, which will give you a delegate method to handle the SSL validation. I recently had to do the same thing :) – klcjr89 Jul 23 '14 at 00:38
  • @troop231 So NSURLSession would be like the completionHandler method? Could you point me in that direction? – Alec Jul 23 '14 at 00:41
  • Typing my answer meow (now in SuperTroopers movie) :) – klcjr89 Jul 23 '14 at 00:41
  • Just to reiterate: You are almost certainly getting an error back when you call this. Your code isn't doing anything with the error, so you don't see it. The localizedDescription of the error should look a lot like the message you would see in Safari if you connected to the same untrusted site. – quellish Jul 23 '14 at 01:02

2 Answers2

3

In the case you describe, you (pretty much) have to use a delegate.

What's happening here is that sendAsynchronousRequest:queue:completion: uses the default behavior of the URL loading system. The URL loading system sees your self signed certificate, can't verify it, so it can't trust it - and won't connect. You should see the NSError passed in to the completion handler populated with information about the problem.

This is all described in depth in Technote 2232: HTTPS Server Trust Evaluation

To allow your self signed certificate, you can't use sendAsynchronousRequest:queue:completion: unless you have a way to make your self-signed certificate trusted and stored in the keychain - on iOS this is only practical in managed devices. For testing, and ONLY for testing, you can use a private Apple API to alter the default trust behavior.

For production code, you must implement a NSURLConnectionDelegate that handles evaluating the server provided credentials and allows your self-signed certificate. This is also described (at a high level) in Technote 2232. If you do not implement this correctly you may create a security vulnerability in your app - and that would be bad, mmmmmk?

I would not suggest following the guidance of the Cocoanetics post you reference. The material is outdated and of questionable quality. Refer to the documentation for NSURLConnectionDelegate and the mentioned Technote 2232 instead. If you would like more information on transport level security for mobile applications in general, there are plenty of resources available.

If you STILL want to use a self signed certificate, you can implement SSL public key pinning to match the remote (self signed) public key against a known local value stored inside your application. This is much better than attempting to match just the hostname. Some example code to get you started is here

Community
  • 1
  • 1
quellish
  • 21,123
  • 4
  • 76
  • 83
2

ViewController.h:

@interface ViewController : UIViewController <NSURLSessionDelegate>

@end

ViewController.m:

- (void)getAccountInfoWithCompletion:(void (^)(NSDictionary *json_response, NSError *error))completion
{
    NSURLRequest *request = [NSURLRequest requestWithURL:
                             [NSURL URLWithString:
                              [NSString stringWithFormat:@"%@/api/account/%d/get_info", BASE_HOST_URL_IP, [self getUserID]]
                              ]];

    NSURLSessionConfiguration *defaultConfigObject = [NSURLSessionConfiguration ephemeralSessionConfiguration];
    defaultConfigObject.requestCachePolicy = NSURLRequestReloadIgnoringLocalAndRemoteCacheData;

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

    [UIApplication sharedApplication].networkActivityIndicatorVisible = YES;

    NSURLSessionDataTask *dataTask = [defaultSession dataTaskWithRequest:request
                                                       completionHandler:^(NSData *data, NSURLResponse *response, NSError *error)
    {
        [UIApplication sharedApplication].networkActivityIndicatorVisible = NO;

        if (error == nil && data != nil)
        {
            NSString *response_string = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
            NSDictionary *json_object = [NSJSONSerialization JSONObjectWithData:[response_string dataUsingEncoding:NSUTF8StringEncoding] options:0 error:nil];

            if (completion)
            {
                completion(json_object, error);
            }
        }
    }];
    [dataTask resume];
}

The new beautiful delegate method which lets us replace NSURLConnection's sendAsynchronousRequest method (which couldn't handle SSL)

- (void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential *))completionHandler
{
    NSString *host = challenge.protectionSpace.host;

    if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust])
    {
        if ([host rangeOfString:@"yourHost.net"].location != NSNotFound)
        {
            completionHandler(NSURLSessionAuthChallengeUseCredential, [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust]);
        }
        else
        {
            completionHandler(NSURLSessionAuthChallengeCancelAuthenticationChallenge,nil);
        }
    }
}
klcjr89
  • 5,862
  • 10
  • 58
  • 91
  • 1
    You are checking that the server-provided credential contains the expected string. This unfortunately opens you up to a number of easy to execute man in the middle attacks :( – quellish Jul 23 '14 at 00:49
  • I am not sure what you mean by "Still SSL". The delegate is responsible for determining the trust of the server. You are trusting any server that provides a credential that contains the string "yourHost.net". That is extremely dangerous. And as of iOS 5 and later, willSendRequestForAuthenticationChallenge: is the preferred place to customize authentication. The documentation is quite clear about that. – quellish Jul 23 '14 at 00:57
  • Can you post some code on how to fix it quickly? Do you have to decrypt the incoming certificate or something? – klcjr89 Jul 23 '14 at 00:58
  • Look at the links in my answer. You should be implementing "SSL Pinning", comparing a local copy of the public key against the public key provided by the server. There isn't a "quick" fix, but I can show how to do it. It should probably be in a separate question though. – quellish Jul 23 '14 at 01:00
  • Ok cool, Ill have to type up a question, as Ive been doing it completely wrong in my apps. :( gonna go throw up now – klcjr89 Jul 23 '14 at 01:02
  • @quellish Nevermind, I think I figured it out by comparing a specific component of the public key, as comparing the whole key is always false because it changes every time. – klcjr89 Jul 23 '14 at 03:02
  • Public key should not change every time - that would be bad. Perhaps when you are comparing them you are comparing the memory address (==) and not using isEqual? Take a look at the code I linked to in my answer. – quellish Jul 23 '14 at 20:34
  • I guarantee it is changing and I'm also using isEqualToString. I was using it like this: `SecTrustRef server = challenge.protectionSpace.serverTrust; SecKeyRef key = SecTrustCopyPublicKey(server); size_t keySize = SecKeyGetBlockSize(key); NSData *serverKey = [NSData dataWithBytes:key length:keySize]; NSString *serverKeyString = [serverKey base64EncodedStringWithOptions:0];` – klcjr89 Jul 23 '14 at 22:24
  • @quellish Why would the above be changing? – klcjr89 Jul 23 '14 at 22:28
  • **I don't see where in the above that you're making a comparison between the remote and local data.** HOW you are comparing them matters. You are, however, reading the *block* size of the key and using that as your NSData's length. The block size is used for encrypting and decrypting using that key, for block ciphers - it's DEFINITELY not something you should be using as part of a comparison. Not sure why you're then base 64 encoding it. Again, I suggest looking at the code I linked to. – quellish Jul 23 '14 at 22:59
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/57883/discussion-between-troop231-and-quellish). – klcjr89 Jul 24 '14 at 01:51
  • @quellish Really struggling with this. I'm getting `kSecTrustResultRecoverableTrustFailure` every time and I included the .der in my app's bundle. Can you do a chat? Thank you – klcjr89 Jul 24 '14 at 02:42
  • Yup, you should be getting that. I've been in chat. You should see the updates there. – quellish Jul 24 '14 at 02:52
  • @quellish I just logged back into chat and I feel I'm getting close, but still need help. – klcjr89 Jul 24 '14 at 03:13