3

I was wondering if anyone can help me understand how to add SSL certificate handling to synchronous connections to a https service.

I know how to do this with asynchronous connections but not synchronous.

                NSString *URLpath = @"https://mydomain.com/";
    NSURL *myURL = [[NSURL alloc] initWithString:URLpath];
    NSMutableURLRequest *myURLRequest = [NSMutableURLRequest requestWithURL:myURL cachePolicy:NSURLRequestReloadIgnoringLocalCacheData timeoutInterval:60];
    [myURL release];
    [myURLRequest setHTTPMethod:@"POST"];

    NSString *httpBodystr = @"setting1=1";
    [myURLRequest setHTTPBody:[httpBodystr dataUsingEncoding:NSUTF8StringEncoding]];

    NSHTTPURLResponse* myURLResponse; 
    NSError* myError;
    NSData* myDataResult = [NSURLConnection sendSynchronousRequest:myURLRequest returningResponse:&myURLResponse error:&myError];

            //I guess I am meant to put some SSL handling code here

Thank you.

Zigglzworth
  • 6,645
  • 9
  • 68
  • 107

3 Answers3

7

Using the static sendSynchronousRequest function is not posible, but i found an alternative.

First of all NSURLConnectionDataDelegate object like this one

FailCertificateDelegate.h

@interface FailCertificateDelegate : NSObject <NSURLConnectionDataDelegate>
   @property(atomic,retain)NSCondition *downloaded;
   @property(nonatomic,retain)NSData *dataDownloaded;
   -(NSData *)getData;
@end

FailCertificateDelegate.m

#import "FailCertificateDelegate.h"

@implementation FailCertificateDelegate
@synthesize dataDownloaded,downloaded;
-(id)init{
    self = [super init];
    if (self){
        dataDownloaded=nil;
        downloaded=[[NSCondition alloc] init];   
    }
    return self;
}


- (BOOL)connection:(NSURLConnection *)connection canAuthenticateAgainstProtectionSpace:    (NSURLProtectionSpace *)protectionSpace {
    NSLog(@"canAuthenticateAgainstProtectionSpace:");
    return [protectionSpace.authenticationMethod         isEqualToString:NSURLAuthenticationMethodServerTrust];
}


- (void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:    (NSURLAuthenticationChallenge *)challenge {
     NSLog(@"didReceiveAuthenticationChallenge:");
    [challenge.sender useCredential:[NSURLCredential     credentialForTrust:challenge.protectionSpace.serverTrust]     forAuthenticationChallenge:challenge];
}


- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
    [downloaded signal]; 
    [downloaded unlock];
    self.hasFinnishLoading = YES; 
} 
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data{
    [dataDownloaded appendData:data];
    [downloaded lock]; 
} 

-(NSData *)getData{
    if (!self.hasFinnishLoading){
        [downloaded lock]; 
        [downloaded wait]; 
        [downloaded unlock]; 
    } 

    return dataDownloaded; 
}

@end

And for use it

FailCertificateDelegate *fcd=[[FailCertificateDelegate alloc] init];
NSURLConnection *c=[[NSURLConnection alloc] initWithRequest:request delegate:fcd startImmediately:NO];
[c setDelegateQueue:[[NSOperationQueue alloc] init]];
[c start];    
NSData *d=[fcd getData];

Now you will have all benefits of have an async use of nsurlconnection and benefits of a simple sync connection, the thread will be blocked until you download all data on the delegate, but you could improve it adding some error control on FailCertificateDelegate class

EDIT: fix for big data. based on Nikolay DS comment. Thanks a lot

Rob Evans
  • 6,750
  • 4
  • 39
  • 56
jgorozco
  • 593
  • 10
  • 11
  • Your "SSL certificate handling" is more than wrong, in fact it is a security flaw. Then, forcing an inherently asynchronous NSURLConnection into a synchronous one makes no sense at all (even though the OP requested that), and your approach won't work when it is invoked on the main thread. Seriously, you should consider to delete this answer. – CouchDeveloper Jan 01 '14 at 21:50
  • as i said, is not a perfect solution, and i know that is a security flaw but if you have a server with an invalid ssl certificate and you need to access it (and you cant modify the server) and you cant use an async approach, this is the only way i find to do that. Of course this will not work in main thread, this solution will be use instead "nsurlconnection sendsynchronousrequest". If you have a better solution for this problem, dont hesitate to propose it! – jgorozco Jan 02 '14 at 09:19
  • It's always possible to wrap a NSURLConnection using the delegate approach into a asynchronous method with a completion handler. There's no reasonable approach for a synchronous method. So, strictly - the answer to the OP's question *"how to add SSL certificate handling to synchronous connections to a https service"* is: "You can't with NSURLConnection's convenient class methods, and you shouldn't make it synchronous.". – CouchDeveloper Jan 02 '14 at 10:16
  • 1
    Failing for big data chunks. Change to: - (void)connectionDidFinishLoading:(NSURLConnection *)connection { [downloaded signal]; [downloaded unlock]; self.hasFinnishLoading = YES; } - (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data{ [dataDownloaded appendData:data]; [downloaded lock]; } -(NSData *)getData{ if (!self.hasFinnishLoading){ [downloaded lock]; [downloaded wait]; [downloaded unlock]; } return dataDownloaded; } – Nikolay DS Jun 20 '14 at 21:52
-1

I had a similar issue. In my case i had an a-synchronous connection working with ssl as required using the two delegate methods that allowed me to accept any certificate:

- (BOOL)connection:(NSURLConnection *)connection canAuthenticateAgainstProtectionSpace:(NSURLProtectionSpace *)protectionSpace {
    return [protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust];
}

- (void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge {
    [challenge.sender useCredential:[NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust] forAuthenticationChallenge:challenge];
}

But i was stuck on doing the same in a synchronous manner. I searched the web until i found your post and unfortunately another stackoverflow post where it is hinted that you cannot perform synch calls on NSURLConnection and work with ssl (because of the lack of a delegate to handle the ssl authentication process). What i ended up doing is getting ASIHTTPRequest and using that. It was painless to do and took me about an hour to set up and its working perfectly. here is how i use it.

+ (NSString *) getSynchronously:(NSDictionary *)parameters {
    NSURL *url = [NSURL URLWithString:@"https://localhost:8443/MyApp/";
    ASIFormDataRequest *request = [ASIFormDataRequest requestWithURL:url];
    NSString *parameterJSONString = [parameters JSONRepresentation];
    [request appendPostString:parameterJSONString];
    [request addRequestHeader:@"User-Agent" value:@"MyAgent"];
    request.timeOutSeconds = CONNECTION_TIME_OUT_INTERVAL;
    [request setValidatesSecureCertificate:NO];
    [request startSynchronous];

    NSString *responseString = [request responseString];
    if (request.error) {
        NSLog(@"Server connection failed: %@", [request.error localizedDescription]);
    } else {
        NSLog(@"Server response: %@", responseString);
    }

    return responseString;
}

The important part of course is the

[request setValidatesSecureCertificate:NO];

Another alternative for you is to handle the download in another thread with an a-synch connection using the two methods above and block the thread from which you want the synch connection until the request is complete

Community
  • 1
  • 1
nsof
  • 2,299
  • 1
  • 20
  • 28
  • The answer is about synchronous. Next time read the answer before saying its not correct – nsof May 24 '13 at 06:09
-2

Im close to finding the solution for this with the code below. This works but often crashes probably because I am doing something wrong in the way I code this and I don't have a strong understanding of the methods used. But if anyone has any suggestions on how to improve this than please post.

Just after the line:

NSError* myError;

and just before the line:

NSData* myDataResult = [NSURLConnection sendSynchronousRequest:myURLRequest       
returningResponse:&myURLResponse error:&myError];

add:

    int failureCount = 0;
    NSURLProtectionSpace *protectionSpace = [[NSURLProtectionSpace alloc]      
    initWithHost:@"mydomain.com" port:443 protocol:@"https"  realm:nil  
    authenticationMethod:NSURLAuthenticationMethodServerTrust];

    NSURLResponse *response = [[NSURLResponse alloc] initWithURL:myURL MIMEType:@"text/html" 
    expectedContentLength:-1 textEncodingName:nil]; 

    NSURLAuthenticationChallenge *challange = [[NSURLAuthenticationChallenge alloc] 
    initWithProtectionSpace:protectionSpace proposedCredential:[NSURLCredential 
    credentialForTrust:protectionSpace.serverTrust] previousFailureCount:failureCount 
    failureResponse:response error:myError sender:nil];
Zigglzworth
  • 6,645
  • 9
  • 68
  • 107