How to use NSURLConnection to connect with SSL for an untrusted cert?
Asked Answered
M

13

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?

Medic answered 1/6, 2009 at 2:9 Comment(0)
A
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.

Ardell answered 9/1, 2010 at 15:21 Comment(28)
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.Wiggins
This is awesome! However, it only works on iPhone and 10.6. Is there a similar work-around for 10.5?Ted
Any thoughts on how to send a authenticated credential (created with "credentialWithUser") along with this "credentialForTrust" credential? Specifically... #2950140Apostil
what is a trustedHosts, where n how is the object definedPropend
Ameya, it would be an NSArray of NSString objects. The strings are the host names like @"google.com".Zebrass
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.Zebrass
Keep in mind that, without additional code, this approach will also check against valid certs.Disagreeable
For those using Gowalla's AFNetworking library, you can get this feature by pasting that code at the bottom of AFURLConnectionOperation.mIlluminator
Works well. Just added the domain of my server to the trustedServers array and bang. Funnily enough the issue only arose on the iPhone 3GIpoh
how can this be applied with sendAsynchronousRequest (as we cannot set a delegate for that one)?Archives
Just want to chime in that these delegate methods aren't called on the sendSynchronous class method. See: linkBrisbane
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.Collegium
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."Ashford
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.Enos
@Gordon If I am using [NSURLConnection sendSynchronousRequest:request] how can I achieve the same output? Its not possible to change it to asynchronous now.Usurp
@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 {Certain
@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?Pave
@AndrewR. It seems like you have to add a "- " to the beginning of that method, and an empty body.Pave
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.Disruptive
Is there a solution for those using the class method +sendAsync...?Hairspring
I have implemented the delegate methods but why aren't they being called?Insulting
@Insulting may be you forgot to set the NSURLConnection delegateFishmonger
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?Fishmonger
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.Fishmonger
This answer only works for async requests. If using sendSynchronousRequest: instead, you'll want to see the top-voted answer here: #3767255Salina
Works perfect for NSURLConnection. Any advice for NSURLSession?Bohannon
Is it indended that in case both conditions are satsified you're doing both useCredential and continueWithoutCredential…?Burcham
I'm confused now by this answer. We are getting this error <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?Frothy
S
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.

Squinch answered 10/6, 2009 at 1:19 Comment(4)
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.Zebrass
See this for the implementation (but use [r setValidatesSecureCertificate:NO]; ): #7658286Docket
Sorry that I brought this topic back up. But since the iOS 5 introduced the ARC features. How can I make this work now?Soothsay
Could you please check this: https://mcmap.net/q/101523/-mitm-attack-reported-on-deprecated-nsurlconnectiondelegate/1364053Tyne
H
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

Havenot answered 11/8, 2011 at 14:36 Comment(3)
Awesome stuff man. I agree, it's so easy to forget about disabling this special app logic to accept any untrusted certificate.Pubis
"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).Teenateenage
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.Disparage
S
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.

Squinch answered 1/6, 2009 at 10:53 Comment(3)
Usual caveats about undocumented APIs apply... but good to know that it's possible.Exhilarant
Yeah, absolutely. I've added another answer which doesn't involve the use of private APIs.Squinch
Does that work when you use "NSURLConnection sendSynchronousRequest:"?Banebrudge
B
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://mcmap.net/q/101524/-ios-pre-install-ssl-certificate-in-keychain-programmatically), 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];
}
Bunkum answered 7/2, 2014 at 7:15 Comment(0)
C
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;
}
Cytochrome answered 2/2, 2010 at 19:50 Comment(0)
S
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.

Steelmaker answered 24/8, 2015 at 19:5 Comment(2)
The only solution worked for me, I have no way to change the Firebase framework to suite my needs, that solved it, thanks!Potto
Now I saw that Google ask for NSAllowArbitraryLoads = YES for Admob (in Firebase). firebase.google.com/docs/admob/ios/ios9Potto
R
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.

Redolent answered 21/7, 2010 at 12:58 Comment(0)
E
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];
Eliott answered 13/2, 2015 at 0:46 Comment(1)
C
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

Carin answered 16/3, 2012 at 23:25 Comment(1)
C
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
Centering answered 7/1, 2017 at 12:31 Comment(0)
J
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

Jedediah answered 17/9, 2013 at 7:36 Comment(0)
D
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);
}];
Derris answered 7/9, 2015 at 9:37 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.