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.
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.