Reputation: 979
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?
Upvotes: 1
Views: 1686
Reputation: 21254
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
Upvotes: 3
Reputation: 5902
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);
}
}
}
Upvotes: 2