bluey31
bluey31

Reputation: 180

iOS 9 ATS blocking HTTPS request to server with Self-Signed Certficate

This issue is all over Stack Overflow and I have spent the past 2 days trying countless combinations of ATP configurations and getting my app to work. I'm going to be thorough with my problem as it appears the tiniest thing can affect how to resolve this.

I have just recently set up an Ubuntu 14 server with SSL and TLS 1.2 enabled. On my server are my server-side scripts which my app depends on. In my app I use a NSMutableURLRequest to request my API from the server like so:

NSString * noteDataString = [NSString stringWithFormat:@"email=%@&password=%@&type=%@", companyEmail, companyPassword, @"login"];
NSData * postData = [noteDataString dataUsingEncoding:NSASCIIStringEncoding allowLossyConversion:YES];
NSString * postLength = [NSString stringWithFormat:@"%lu", (unsigned long)[postData length]];

NSMutableURLRequest * request = [[NSMutableURLRequest alloc] init];

[request setURL:[NSURL URLWithString:@"https://mydomain.me:443/path/to/file.php"]];

[request setHTTPMethod:@"POST"];

[request setValue:postLength forHTTPHeaderField:@"Content-Length"];
[request setValue:@"application/x-www-form-urlencoded" forHTTPHeaderField:@"Content-Type"];
[request setHTTPBody:postData];

NSHTTPURLResponse * urlResponse = nil;
NSError * error = nil;
NSData * responseData = [NSURLConnection sendSynchronousRequest:request returningResponse:&urlResponse error:&error];
NSString * result = [[NSString alloc] initWithData:responseData encoding:NSUTF8StringEncoding];

NSLog(@"Response Code: %ld", (long)[urlResponse statusCode]);

When I copy the url into Chrome the correct PHP return is shown. When requesting from Xcode 7 on iOS 9 I get this error:

NSURLSession/NSURLConnection HTTP load failed (kCFStreamErrorDomainSSL, -9813)

I have used countless info.plist settings as seen across similar issues. I have tried disabling the need for forward secrecy, I have tried enabling arbitrary loads, I have tried using the exception domain mydomain.me and its subdomains. The only progress I achieve is an error code -9813 switching to -9802.

I am aware of adding delegate methods for NSURLRequest but these are never called and assumed redundant for solving this problem.

I built a lot of the app off a MAMP server using localhost and http and the requests worked when I enabled arbitrary loads then, so nothing wrong with my plist.

It's mind-boggling onto why I have a special case, and I knew Stack Overflow was the place for such situations!

Thanks, and I hope that this helps many more developers beyond me when solved.

Upvotes: 0

Views: 1734

Answers (2)

bluey31
bluey31

Reputation: 180

Solution. Thank you everybody in the comments for pointing me in the right direction. The solution was to create an NSObject that handled NSURLRequests for this special case.

Credit to: https://www.cocoanetics.com/2010/12/nsurlconnection-with-self-signed-certificates/

The following is almost a direct copy from the tutorial in the link, but I figured it'd be easier to stay here.

So,

I had to create a new NSObject class with the following code:

BWWebService.h

#import <Foundation/Foundation.h>

@interface BWWebService : NSObject{
    NSMutableData *receivedData;
    NSURLConnection *connection;
    NSStringEncoding encoding;
}

- (id)initWithURL:(NSURL *)url;

@end

BWWebService.m

#import "BWWebService.h"

@implementation BWWebService

- (id)initWithURL:(NSURL *)url{
    if (self = [super init]){
        NSURLRequest * request = [NSURLRequest requestWithURL:url];

        connection = [NSURLConnection connectionWithRequest:request delegate:self];

        [connection start];
    }

    return self;
}

- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response{
    // Every response could mean a redirect
    receivedData = nil;

    CFStringEncoding cfEncoding = CFStringConvertIANACharSetNameToEncoding((CFStringRef)
                                                                               [response textEncodingName]);
encoding = CFStringConvertEncodingToNSStringEncoding(cfEncoding);
}

- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data{
    if (!receivedData){
        // No store yet, make one
        receivedData = [[NSMutableData alloc] initWithData:data];
    }else{
        // Append to previous chunks
        [receivedData appendData:data];
    }
}

// All worked
- (void)connectionDidFinishLoading:(NSURLConnection *)connection{
    NSString * xml = [[NSString alloc] initWithData:receivedData encoding:encoding];
    NSLog(@"%@", xml);
}

// And error occurred
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error{
     NSLog(@"Error retrieving data, %@", [error localizedDescription]);
}

// To deal with self-signed certificates
- (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]){
        // we only trust our own domain
        if ([challenge.protectionSpace.host isEqualToString:@"myuntrusteddomain.me"]){
           NSURLCredential * credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
            [challenge.sender useCredential:credential forAuthenticationChallenge:challenge];
        }
    }

    [challenge.sender continueWithoutCredentialForAuthenticationChallenge:challenge];
}
@end

And if that wasn't enough, to make the request I used the following in replacement of my original request:

NSURL * url = [NSURL URLWithString:@"https://myuntrusteddomain.me:443/path/to/script.php"];
BWWebService * webService;
webService = [[BWWebService alloc] initWithURL:url];

I know that this does not POST data like the original, but that comes later. I am sure it will be a matter of handling the POST in initWithURL.

Thanks everyone.

Edit: It appears this application of the solution only works with Allows Arbitrary Loads set to YES.

Upvotes: 1

gnasher729
gnasher729

Reputation: 52548

Open Xcode, Command-Shift-2, enter 9813, and you immediately find -9813 = errSSLNoRootCert, which was to be expected since your self signed certificate has no root certificate, while -9802 = errSSLFatalAlert (you really buggered it up).

The problem seems to be that for security reasons, some software doesn't like self signed certificates. This can often be fixed by creating and installing your own root certificate, and having a certificate signed by your own root certificate.

Upvotes: 0

Related Questions