307

I have the following simple code to connect to a SSL webpage

NSMutableURLRequest *urlRequest=[NSMutableURLRequest requestWithURL:url];
[ NSURLConnection sendSynchronousRequest: urlRequest returningResponse: nil error: &error ];

Except it gives an error if the cert is a self signed one Error Domain=NSURLErrorDomain Code=-1202 UserInfo=0xd29930 "untrusted server certificate". Is there a way to set it to accept connections anyway (just like in a browser you can press accept) or a way to bypass it?

soulshined
  • 9,612
  • 5
  • 44
  • 79
erotsppa
  • 14,248
  • 33
  • 123
  • 181

13 Answers13

419

There is a supported API for accomplishing this! Add something like this to your NSURLConnection delegate:

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

- (void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge {
  if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust])
    if ([trustedHosts containsObject:challenge.protectionSpace.host])
      [challenge.sender useCredential:[NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust] forAuthenticationChallenge:challenge];

  [challenge.sender continueWithoutCredentialForAuthenticationChallenge:challenge];
}

Note that connection:didReceiveAuthenticationChallenge: can send its message to challenge.sender (much) later, after presenting a dialog box to the user if necessary, etc.

Paras Joshi
  • 20,427
  • 11
  • 57
  • 70
Gordon Henriksen
  • 4,365
  • 1
  • 18
  • 4
  • 31
    Thanks a lot, it works perfectly. Just remove the the two ifs and keep only the useCendential part in the didReceiveAuthentificationChallenge callback if you want to accept any https site. – yonel Jan 27 '10 at 10:40
  • 1
    This is awesome! However, it only works on iPhone and 10.6. Is there a similar work-around for 10.5? – Dave DeLong May 12 '10 at 16:22
  • Any thoughts on how to send a authenticated credential (created with "credentialWithUser") along with this "credentialForTrust" credential? Specifically... http://stackoverflow.com/questions/2949640/nsurlconnection-nsurlrequest-untrusted-cert-and-user-authentication – Staros Jun 01 '10 at 12:21
  • 19
    what is a trustedHosts, where n how is the object defined – Ameya Aug 26 '10 at 18:26
  • 7
    Ameya, it would be an NSArray of NSString objects. The strings are the host names like @"google.com". – William Denniss Nov 30 '10 at 14:45
  • 19
    This code works well. But note that the entire point of having valid certificates is to prevent man-in-the-middle attacks. So be aware if you use this code, someone can spoof the so-called "trusted host". You still get the data encryption features of SSL but you lose the host identify validation features. – William Denniss Nov 30 '10 at 14:48
  • 1
    Keep in mind that, without additional code, this approach will also check against valid certs. – Wayne Hartman Jan 28 '11 at 02:51
  • 1
    For those using Gowalla's [AFNetworking](https://github.com/gowalla/AFNetworking) library, you can get this feature by pasting that code at the bottom of [AFURLConnectionOperation.m](https://github.com/gowalla/AFNetworking/blob/master/AFNetworking/AFURLConnectionOperation.m#L387) – Derek Dahmer Nov 05 '11 at 22:28
  • Works well. Just added the domain of my server to the trustedServers array and bang. Funnily enough the issue only arose on the iPhone 3G – Jeremy Bassett Jan 31 '12 at 14:36
  • how can this be applied with sendAsynchronousRequest (as we cannot set a delegate for that one)? – Filipe Pina Feb 07 '12 at 12:37
  • 2
    Just want to chime in that these delegate methods aren't called on the sendSynchronous class method. See: [link](http://stackoverflow.com/questions/2679944/objective-c-ssl-synchronous-connection) – p.pad Mar 14 '12 at 18:42
  • 42
    These methods are now considered deprecated as of iOS 5.0 and Mac OS X 10.6. The `-(void)connection:(NSURLConnection *)connection willSendRequestForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge` method should be used instead. – Andrew R. Apr 17 '12 at 21:47
  • 4
    true but the older methods will still be called "If connection:willSendRequestForAuthenticationChallenge: is not implemented, the older, deprecated methods connection:canAuthenticateAgainstProtectionSpace:, connection:didReceiveAuthenticationChallenge:, and connection:didCancelAuthenticationChallenge: are called instead." – Max MacLeod May 31 '12 at 09:06
  • I surround this kind of code with #ifdef DEBUG/#endif so that it doesn't end up in production. Would be nice to see a version that works using non-deprecated code. – Erik Jul 04 '12 at 02:23
  • 1
    @Gordon If I am using [NSURLConnection sendSynchronousRequest:request] how can I achieve the same output? Its not possible to change it to asynchronous now. – Franklin Jul 11 '12 at 13:44
  • @Gordon...What dialog box are you talking about..is it that "connection is not secure" or "not valid certificate"...Please help me as want to certificate validation in my iOS code.Where should i place that dialog box in - (void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge { – iAmitWagh Aug 06 '12 at 09:33
  • @AndrewR. When I add that line I get two errors, one saying that it expects to see (, and another saying it expects to see ). You said I'm supposed to add it to the NSURLRequest delegate, but I only have the delegate for the project itself, am I confusing something here? – Marcel Marino Oct 03 '12 at 17:30
  • @AndrewR. It seems like you have to add a "- " to the beginning of that method, and an empty body. – Marcel Marino Oct 03 '12 at 17:35
  • This does not help if you are trying to use MPMoviePlayerController. I have no problem in the simulator but does not work on the iPhone itself. – Rob Oct 30 '12 at 17:55
  • Is there a solution for those using the class method +sendAsync...? – Adam Carter Jun 04 '13 at 13:44
  • I have implemented the delegate methods but why aren't they being called? – jsetting32 Jul 24 '13 at 17:32
  • @jsetting32 may be you forgot to set the NSURLConnection delegate – Hlung Sep 05 '13 at 05:03
  • While this works, but sometimes it just fails. It's pretty weird. All those delegate method is called just like when it works, but I got "The certificate for this server is invalid. You might be connecting to a server that is pretending to be “mysite.com” which could put your confidential information at risk." I used NSURLConnection `+sendAsynchronousRequest:queue:completionHandler:` method. Any ideas? – Hlung Sep 05 '13 at 07:52
  • Fixed it, Looks like NSURLConnection `+sendAsynchronousRequest:queue:completionHandler:` doesn't work well when we skip certificate check (in `-connection:willSendRequestForAuthenticationChallenge:` NSURLConnection delegate method). I suspect that it's the use of NSOperationQueue that breaks the flow. Solution: I change it to setup NSURLConnection with BlocksKit and call `-start` manually, now it works perfectly. – Hlung Sep 05 '13 at 09:07
  • This answer only works for async requests. If using `sendSynchronousRequest:` instead, you'll want to see the top-voted answer here: http://stackoverflow.com/questions/3766755/ignoring-certificate-errors-with-nsurlconnection – aroth Nov 25 '14 at 05:46
  • 4
    Works perfect for NSURLConnection. Any advice for NSURLSession? – Daniyar Dec 08 '14 at 12:23
  • Is it indended that in case both conditions are satsified you're doing both `useCredential` and `continueWithoutCredential…`? – Michael Krelin - hacker May 03 '17 at 12:13
  • I'm confused now by this answer. We are getting this error `: Connection 818: TLS Trust encountered error 3:-9807` occurring on devices and changing `allowsAnyHTTPSCertificateForHost` now throws `Failed to fetch URL configuration, error: Error Domain=NSURLErrorDomain Code=-1202`. The answer doesn't fix the issue it. It seems like things have changed since its initial posting, but trying to use the updates in the comments is creating build errors. Can anyone post a full answer using the updates in the comments? – mtpultz Dec 10 '20 at 21:29
36

If you're unwilling (or unable) to use private APIs, there's an open source (BSD license) library called ASIHTTPRequest that provides a wrapper around the lower-level CFNetwork APIs. They recently introduced the ability to allow HTTPS connections using self-signed or untrusted certificates with the -setValidatesSecureCertificate: API. If you don't want to pull in the whole library, you could use the source as a reference for implementing the same functionality yourself.

Paras Joshi
  • 20,427
  • 11
  • 57
  • 70
Nathan de Vries
  • 15,481
  • 4
  • 49
  • 55
  • 2
    Tim, you may find yourself wanting to use async for other reasons anyway (like being able to show a progress bar), I find for all but the most simple of requests that's the way I go. So maybe you should just implement Async now and save the hassle later. – William Denniss Nov 30 '10 at 07:59
  • See this for the implementation (but use [r setValidatesSecureCertificate:NO]; ): http://stackoverflow.com/questions/7657786/asihttprequest-ignores-setvalidatessecurecertificate-parameter – Sam Brodkin Jan 16 '12 at 14:45
  • Sorry that I brought this topic back up. But since the iOS 5 introduced the ARC features. How can I make this work now? – Melvin Lai Feb 02 '12 at 02:53
  • Could you please check this: https://stackoverflow.com/q/56627757/1364053 – nr5 Jun 17 '19 at 08:46
33

Ideally, there should only be two scenarios of when an iOS application would need to accept an un-trusted certificate.

Scenario A: You are connected to a test environment which is using a self-signed certificate.

Scenario B: You are Proxying HTTPS traffic using a MITM Proxy like Burp Suite, Fiddler, OWASP ZAP, etc. The Proxies will return a certificate signed by a self-signed CA so that the proxy is able to capture HTTPS traffic.

Production hosts should never use un-trusted certificates for obvious reasons.

If you need to have the iOS simulator accept an un-trusted certificate for testing purposes it is highly recommended that you do not change application logic in order disable the built in certificate validation provided by the NSURLConnection APIs. If the application is released to the public without removing this logic, it will be susceptible to man-in-the-middle attacks.

The recommended way to accept un-trusted certificates for testing purposes is to import the Certificate Authority(CA) certificate which signed the certificate onto your iOS Simulator or iOS device. I wrote up a quick blog post which demonstrates how to do this which an iOS Simulator at:

accepting untrusted certificates using the ios simulator

NANNAV
  • 4,875
  • 4
  • 32
  • 50
user890103
  • 339
  • 3
  • 2
  • 1
    Awesome stuff man. I agree, it's so easy to forget about disabling this special app logic to accept any untrusted certificate. – Tomasz Nov 08 '11 at 04:02
  • "Ideally, there should only be two scenarios of when an iOS application would need to accept an un-trusted certificate." - How about rejecting a 'claimed' good certifcate when pinning a certifcate? Confer: Dignotar (pwn'd) and Trustwave (MitM fame). – jww Jun 11 '12 at 22:39
  • Totally agree with your statement about forgetting to remove the code. The irony is that it's much easier to make this change in the code than getting the simulator to accept self-signed certs. – devios1 May 17 '13 at 19:01
12

To complement the accepted answer, for much better security, you could add your server certificate or your own root CA certificate to keychain( https://stackoverflow.com/a/9941559/1432048), however doing this alone won't make NSURLConnection authenticate your self-signed server automatically. You still need to add the below code to your NSURLConnection delegate, it's copied from Apple sample code AdvancedURLConnections, and you need to add two files(Credentials.h, Credentials.m) from apple sample code to your projects.

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

- (void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge {
if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
//        if ([trustedHosts containsObject:challenge.protectionSpace.host])

    OSStatus                err;
    NSURLProtectionSpace *  protectionSpace;
    SecTrustRef             trust;
    SecTrustResultType      trustResult;
    BOOL                    trusted;

    protectionSpace = [challenge protectionSpace];
    assert(protectionSpace != nil);

    trust = [protectionSpace serverTrust];
    assert(trust != NULL);
    err = SecTrustEvaluate(trust, &trustResult);
    trusted = (err == noErr) && ((trustResult == kSecTrustResultProceed) || (trustResult == kSecTrustResultUnspecified));

    // If that fails, apply our certificates as anchors and see if that helps.
    //
    // It's perfectly acceptable to apply all of our certificates to the SecTrust
    // object, and let the SecTrust object sort out the mess.  Of course, this assumes
    // that the user trusts all certificates equally in all situations, which is implicit
    // in our user interface; you could provide a more sophisticated user interface
    // to allow the user to trust certain certificates for certain sites and so on).

    if ( ! trusted ) {
        err = SecTrustSetAnchorCertificates(trust, (CFArrayRef) [Credentials sharedCredentials].certificates);
        if (err == noErr) {
            err = SecTrustEvaluate(trust, &trustResult);
        }
        trusted = (err == noErr) && ((trustResult == kSecTrustResultProceed) || (trustResult == kSecTrustResultUnspecified));
    }
    if(trusted)
        [challenge.sender useCredential:[NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust] forAuthenticationChallenge:challenge];
}

[challenge.sender continueWithoutCredentialForAuthenticationChallenge:challenge];
}
Community
  • 1
  • 1
xiang
  • 251
  • 4
  • 9
12

NSURLRequest has a private method called setAllowsAnyHTTPSCertificate:forHost:, which will do exactly what you'd like. You could define the allowsAnyHTTPSCertificateForHost: method on NSURLRequest via a category, and set it to return YES for the host that you'd like to override.

Nathan de Vries
  • 15,481
  • 4
  • 49
  • 55
11

I can't take any credit for this, but this one I found worked really well for my needs. shouldAllowSelfSignedCert is my BOOL variable. Just add to your NSURLConnection delegate and you should be rockin for a quick bypass on a per connection basis.

- (BOOL)connection:(NSURLConnection *)connection canAuthenticateAgainstProtectionSpace:(NSURLProtectionSpace *)space {
     if([[space authenticationMethod] isEqualToString:NSURLAuthenticationMethodServerTrust]) {
          if(shouldAllowSelfSignedCert) {
               return YES; // Self-signed cert will be accepted
          } else {
               return NO;  // Self-signed cert will be rejected
          }
          // Note: it doesn't seem to matter what you return for a proper SSL cert
          //       only self-signed certs
     }
     // If no other authentication is required, return NO for everything else
     // Otherwise maybe YES for NSURLAuthenticationMethodDefault and etc.
     return NO;
}
jww
  • 97,681
  • 90
  • 411
  • 885
Ryna
  • 984
  • 8
  • 7
11

In iOS 9, SSL connections will fail for all invalid or self-signed certificates. This is the default behavior of the new App Transport Security feature in iOS 9.0 or later, and on OS X 10.11 and later.

You can override this behavior in the Info.plist, by setting NSAllowsArbitraryLoads to YES in the NSAppTransportSecurity dictionary. However, I recommend overriding this setting for testing purposes only.

enter image description here

For information see App Transport Technote here.

johnnieb
  • 3,982
  • 4
  • 29
  • 32
  • The only solution worked for me, I have no way to change the Firebase framework to suite my needs, that solved it, thanks! – Yitzchak Aug 20 '16 at 20:40
  • Now I saw that Google ask for NSAllowArbitraryLoads = YES for Admob (in Firebase). https://firebase.google.com/docs/admob/ios/ios9 – Yitzchak Aug 20 '16 at 21:42
7

The category workaround posted by Nathan de Vries will pass the AppStore private API checks, and is useful in cases where you do not have control of the NSUrlConnection object. One example is NSXMLParser which will open the URL you supply, but does not expose the NSURLRequest or NSURLConnection.

In iOS 4 the workaround still seems to work, but only on the device, the Simulator does not invoke the allowsAnyHTTPSCertificateForHost: method anymore.

DShah
  • 9,768
  • 11
  • 71
  • 127
Alex Suzuki
  • 71
  • 1
  • 1
6

You have to use NSURLConnectionDelegate to allow HTTPS connections and there are new callbacks with iOS8.

Deprecated:

connection:canAuthenticateAgainstProtectionSpace:
connection:didCancelAuthenticationChallenge:
connection:didReceiveAuthenticationChallenge:

Instead those, you need to declare:

connectionShouldUseCredentialStorage: - Sent to determine whether the URL loader should use the credential storage for authenticating the connection.

connection:willSendRequestForAuthenticationChallenge: - Tells the delegate that the connection will send a request for an authentication challenge.

With willSendRequestForAuthenticationChallenge you can use challenge like you did with the deprecated methods, for example:

// Trusting and not trusting connection to host: Self-signed certificate
[challenge.sender useCredential:[NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust] forAuthenticationChallenge:challenge];
[challenge.sender continueWithoutCredentialForAuthenticationChallenge:challenge];
Community
  • 1
  • 1
ricardopereira
  • 11,118
  • 5
  • 63
  • 81
3

You can use this Code

-(void)connection:(NSURLConnection *)connection willSendRequestForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
{
     if ([[challenge protectionSpace] authenticationMethod] == NSURLAuthenticationMethodServerTrust)
     {
         [[challenge sender] useCredential:[NSURLCredential credentialForTrust:[[challenge protectionSpace] serverTrust]] forAuthenticationChallenge:challenge];
     }
}

Use -connection:willSendRequestForAuthenticationChallenge: instead of these Deprecated Methods

Deprecated:

-(BOOL)connection:(NSURLConnection *)connection canAuthenticateAgainstProtectionSpace:(NSURLProtectionSpace *)protectionSpace  
-(void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge 
-(void)connection:(NSURLConnection *)connection didCancelAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
Vaibhav Sharma
  • 1,123
  • 10
  • 22
3

I posted some gist code (based on someone else's work which I note) that lets you properly authenticate against a self generated certificate (and how to get a free certificate - see comments bottom of Cocoanetics)

My code is here github

David H
  • 40,852
  • 12
  • 92
  • 138
2

If you want to keep using sendSynchronousRequest i work in this solution:

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];

you can see it here: Objective-C SSL Synchronous Connection

Community
  • 1
  • 1
jgorozco
  • 593
  • 10
  • 11
1

With AFNetworking I have successfully consumed https webservice with below code,

NSString *aStrServerUrl = WS_URL;

// Initialize AFHTTPRequestOperationManager...
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
manager.requestSerializer = [AFJSONRequestSerializer serializer];
manager.responseSerializer = [AFJSONResponseSerializer serializer];

[manager.requestSerializer setValue:@"application/json" forHTTPHeaderField:@"Content-Type"];
manager.securityPolicy.allowInvalidCertificates = YES; 
[manager POST:aStrServerUrl parameters:parameters success:^(AFHTTPRequestOperation *operation, id responseObject)
{
    successBlock(operation, responseObject);

} failure:^(AFHTTPRequestOperation *operation, NSError *error)
{
    errorBlock(operation, error);
}];
cjd
  • 549
  • 3
  • 11