Question

I'm working on an iOS Enterprise POS app that communicates with a server using https. I've looked at Receiving SSL error in iOS7 GM - "AddTrust External CA Root" is not trusted? and Difference between self-signed CA and self-signed certificate and generally scoured the web but I'm not getting anywhere.

The app works fine on iOS6.1 using either http or https protocols. It also works fine on iOS 7GM over http, but not over https - it fails on the first message it sends to the server. On the app side I handle the authentication challenge in:

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

after which I get a callback to:

- (void)connectionDidFinishLoading:(NSURLConnection *)connection

NOT:

- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error

I believe that means the client and server successfully negotiated a connection, agreed upon an encryption protocol, etc. Unfortunately although the return appears successful (as far as the network stack is concerned), I get back 0 bytes of data in the AMF payload.

Here's the interesting part - on the server side (JBoss4.2.3) I can breakpoint and examine the httpRequest body containing the AMFRequest. Over http I always get 384 bytes in the body. Over https, I get 384 bytes if the client is iOS 6.1, but 0 bytes if the client is iOS 7. I interpret that as the https request was accepted "normally" by the server with no errors, security violations, etc.

One more data point. If I run Charles on the client side everything works correctly over https using the iOS 7 simulator. I can see my 384 byte AMFRequest just fine in both Charles and on the server. Charles works as an http proxy - but the app doesn't know anything about that, so why does inserting Charles as an intermediary make it work? I've installed Charles' certificate so I think it is communicating to the server over SSL (don't know for sure).

Thanks for any help or suggestions.

Update: I implemented Apple's recommended approach:

- (void)connection: (NSURLConnection *)connection willSendRequestForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
{
    traceMethod();
    SecTrustRef trust = challenge.protectionSpace.serverTrust;
    NSURLCredential *credential = [NSURLCredential credentialForTrust: trust];
    [challenge.sender useCredential: credential
         forAuthenticationChallenge: challenge];
}

but it gets exactly the same result as the old (pre iOS 5) way.

Was it helpful?

Solution

After considerable research I opened a incident with Apple Developer Tech Support and eventually have an explanation.

I've been able to confirm that Apple made a change in iOS 7 as a "recommended countermeasure to the BEAST attack", which is a "man in the middle" attack against the SSL TLS protocol - see this article on CERT.

The ideal solution would be to change the JBoss (Tomcat) ssl connector to use:

sslProtocol="TLSv1.2"

Unfortunately the stock JBoss4.2.3GA implementation is unable to handle TLSv1.1 or TLSv1.2, or process messages that use this countermeasure. It appears the only solution is to upgrade the JBoss configuration. That requires significant JBoss changes - see this JBoss article.

An alternative is to re-write the networking methods to use a lower level framework (CFSocketStream instead of NSURLConnection) and disable the BEAST countermeasure. That has two downsides - it re-exposes the security vulnerability and it is a non-trivial implementation that would need thorough testing (especially simulating network edge cases).

In my case time does not permit changes of such magnitude prior the Holiday season. My client is a major retailer and their IT department locks down the environment in mid-October.

Perhaps this information will help someone else.

OTHER TIPS

There have definitely been changes to SSL in the API

I don't know much about how SSL works, but I noticed that MKNetworkKit is using a now deprecated in iOS 7 constant called kSecTrustResultConfirm (found in SecTrust.h in the Security-Framework):

@constant kSecTrustResultConfirm Indicates confirmation with the user is required before proceeding. Important: this value is no longer returned or supported by SecTrustEvaluate or the SecTrustSettings API starting in OS X 10.5; its use is deprecated in OS X 10.9 and later, as well as in iOS.

Maybe this is a point in the right direction? Anyway, good luck solving your issue!

This is the commit diff in which someone "fixed" this in MKNetworkKit: https://github.com/MugunthKumar/MKNetworkKit/commit/c28959805991bb8f0e99ede9c822e985b41f6fc9 (scroll down to L:1142)

Had this same issue getting iOS 7 to talk to Jboss eap 6.0.1 resteasy methods.

The answer is to set the ssl tags protocol to TLSv1.2. e.g.

<connector name="https" protocol="HTTP/1.1" scheme="https" socket-binding="https" secure="true">
   <ssl name="ssl" key-alias="awssink" password="keystore-password" certificate-key-file="${jboss.server.config.dir}/attimoto.jks" verify-client="false" protocol="TLSv1.2"/>
</connector>

Now we are using Bearer tokens to access the restful methods so using AFNetworking 2+ we do something like this:

NSString *secureURLString = [secureBaseURLString stringByAppendingString:BURP];
NSLog(@"the secure url is %@\n\n", secureURLString);

// set up the request manager
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
// allow invalid certificates
manager.securityPolicy.allowInvalidCertificates = YES;

//Note we dont deal with pinned certificates since we are doing bearer token    

manager.responseSerializer = [AFHTTPResponseSerializer serializer];

manager.requestSerializer = [AFHTTPRequestSerializer serializer];
[manager.requestSerializer setValue:BEARER_TOKEN forHTTPHeaderField:@"Authorization"];
//Note we do NOT use setAuthorizationHeaderFieldWithToken:BEARER_TOKEN];
//Ultimately you want just a header like this
// "Authorization: Bearer textofthebearertoken"
//So BEARER_TOKEN is literally @"Bearer thetextofthebearertoken"

[manager GET:secureURLString
  parameters:nil
     success:^(AFHTTPRequestOperation *operation, id responseObject) {
         NSLog(@" RESPONSE RETURNED %@\n\n", responseObject);

     } failure:^(AFHTTPRequestOperation *operation, NSError *error) {
         NSLog(@"error = %@", error);
     }];

Had an issue with NSURLConnection and Amazons ELB & EC2 instance, where ELB didn't have TLS v1.2 enabled, causing 15-50% of the requests not being processed properly (HTTPS PUT with JSON in the body). Turning TLS v1.2 solved this issue... Scratching my head trying to figure out the logic behind this fact.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top