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.
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.
Related
In my app I need to call some REST API service calls. The certificate on the target development server where REST API services are deployed is self signed. So when I am running app I am getting error like:
Failed to load resource: The certificate for this server is invalid. You might be connecting to a server that is pretending to be “192.168.10.20:8080” invalid.....which could put your confidential information at risk.
As this server is only for dev/testing purpose so I simply wants to ignore ssl check ... How can I achieve it? I tried following way:
[AppDelegate.m file] but didn't succeeded as below code is not working in iOS 11 ...
#implementation NSURLRequest(DataController)
+ (BOOL)allowsAnyHTTPSCertificateForHost:(NSString *)host
{
return YES;
}
#end
I am using ionic 3 & Cordova 7 in my app.
Thanks #peter I have found one more workaround for checking app in ios11 for testing purpose whether API's are getting hit properly or not. You can forcefully change the webview from WKWebView to UIWebview by adding below tag in config.xml
<preference name="CordovaWebViewEngine" value="CDVUIWebViewEngine" />
now add following code in Appdelegate.m file
#implementation NSURLRequest(DataController)
+ (BOOL)allowsAnyHTTPSCertificateForHost:(NSString *)host
{
return YES;
}
#end
it worked for me..!
Note:only for dev/testing purpose.not recommended for production deployments
Interestingly, I am just researching the same problem. Looks like in iOS 11 things are a bit more restricted. I am answering here for WKWebView.
In essence you need to do:
place custom auth code to the WKWebView plugin code
load resource directly from Cordova (then WKWebView events gets properly triggered)
disable ATS (NSAppTransportSecurity)
Detail description
What you should do in detail is the following (if you are using WKWebView):
You need to modify CDVWKWebViewEngine.m (plugin code). You need to add there:
- (void)webView:(WKWebView *)webView
didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge
*)challenge completionHandler:(void (^)
(NSURLSessionAuthChallengeDisposition
disposition, NSURLCredential *credential))completionHandler {
SecTrustRef serverTrust = challenge.protectionSpace.serverTrust;
completionHandler(NSURLSessionAuthChallengeUseCredential,
[NSURLCredential credentialForTrust:serverTrust]);
}
However, please note - this only works when WKWebView is initialized (i.e. loaded via cordova framework).
So you need to load your application also from that URI where API is. I presume you have local network (self signed certificate), so this should not be a problem. If you will load application locally (i.e. from index.html) then this wont work!
Additionally you need to disable iOS ATS in application *.plist setting file like:
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>
This is what works for me.
Additional resources:
Very nice testing site for different SSL scenarios (including self signed): https://badssl.com/
Apple forum discussion on this topic: https://forums.developer.apple.com/message/269452#269452
Disclamer: Disabling certificate check should be avoided, use this only if you have a very good reason or other restrictions. You still have communication security, but you have no trust. Hence men in the middle attack is possible! If you decide to go with this option, you should also use certificate pinning to make things more secure.
I recently had a problem associated with an invalid SSL certificate on a server.
In my application, I am using the WKWebView plugin, which has become a requirement to send applications to an App Store. It is in this plug-in that an adjustment needs to be made to ignore invalid SSL certificates, in the file "plugins/cordova-plugin-ionic-webview/src/ios/CDVWKWebViewEngine.m", includes:
- (void)webView:(WKWebView *)webView didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler {
NSLog(#"Allow all");
SecTrustRef serverTrust = challenge.protectionSpace.serverTrust;
CFDataRef exceptions = SecTrustCopyExceptions (serverTrust);
SecTrustSetExceptions (serverTrust, exceptions);
CFRelease (exceptions);
completionHandler (NSURLSessionAuthChallengeUseCredential, [NSURLCredential credentialForTrust:serverTrust]);
}
As a result, the application will not take into account invalid SSL certificates.
References of https://developer.apple.com/forums/thread/15610
Remembering that it is not recommended to do this in applications in production, because invalid SSL certificates can compromise the security of the application.
one of my apps cannot connect to it's API server. The logged reason is NSURLSession/NSURLConnection HTTP load failed (kCFStreamErrorDomainSSL, -9813). So far no luck on finding why or what, but the most frustrating thing is, that this problem appears on some of my test devices. On other everything is fine. I am using NSURLConnection:sendSynchronousRequest:returningResponse:error:, but after some refactoring and trying with NSURLSessionDataTask:dataTaskWithRequest:completionHandler:, the problem still occurs.
Also added
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler
{
completionHandler(NSURLSessionAuthChallengeUseCredential, [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust]);
}
and still nothing.....
p.s. even though my first problem is duplicate of many others, my main concern is why this error appears only on ONE of my testing devices (after Settings -> General -> Reset it works fine too, but how to tell Apple "please, reset your testing device"?). The other work perfectly fine.
Error -9813 is errSSLNoRootCert. This means that the certificate chain provided by the server does not reach a trusted root (anchor) cert, and is usually caused by missing certificates in the chain.
The reason for the difference from one device to another could be because some devices were able to successfully fetch the missing certificates (OCSP?), because some devices have additional trusted anchor certs that are not present on the other devices for whatever reason, or because some of the certificates in the chain are signed using a key signing algorithm that is no longer accepted on iOS 9 and thus are being ignored.
I would suggest that you contact your TLS certificate vendor and get a new copy of the TLS chain file, update the chain file on the server, restart the server, and see if the problem goes away.
If that doesn't help, please edit your question with a URL that we can test, along with the results of running openssl s_client -connect yourhostname:443 against the host in question.
More tips here:
iOS HTTPS requests 101
https://developer.apple.com/library/ios/technotes/tn2232/_index.html
[manager setSecurityPolicy:[AFSecurityPolicy policyWithPinningMode:AFSSLPinningModeCertificate]];
NSLog(#"Using %lu pinned certificates", (unsigned long)[requestManager.securityPolicy.pinnedCertificates count]);
I am using AFNetworking 2.0, and I have enabled certificate authentication on the server. I have placed two .crt files in the xcode project. and whenever I try to establish a https connection to the server I get the NSURLErrorDomain error -1012. Interesting thing to note here is that in my logs I can see the count of loaded certificates as 2. So, I guess pinning is successful, can someone help me with whats is wrong here?
Also, instead of pinning if I write - [sec setAllowInvalidCertificates:YES] then everything works fine.
I'm trying SPDY for iOS app, which currently uses AFNetworking (2.1.0) to handle HTTP requests. Server side, I'm using Google App Engine (Checked with SPDYCheck) it's SPDY-friendly.
Here's how I integrated SPDY in my code.
I'm using AFHTTPRequestOperationManager
#interface MyClient : AFHTTPRequestOperationManager
And I embedded SPDY in initWithBaseURL:
- (id)initWithBaseURL:(NSURL *)url {
self = [super initWithBaseURL:url];
if (!self) {
return nil;
}
// SPDY Config
NSString *spdyURL = [NSString stringWithFormat:#"%#://%#:443",url.scheme, url.host];
[SPDYURLConnectionProtocol registerOrigin:spdyURL];
...
}
Note that my url is with format https://myapp.appspot.com/_ah/ So I reformat it when passing to SPDY registerOrigin: spdyURL will look like https://myapp.appspot.com:443
I suppose that's all I need? But I wasn't able to send request after adding the SPDY code. Debug message showed the following error:
error response from api/users/v1/is_token_valid :
Error Domain=NSURLErrorDomain
Code=-1001
"The request timed out."
UserInfo=0xdc2c250 {NSErrorFailingURLStringKey=https://myapp.appspot.com/_ah/api/users/v1/is_token_valid?
user_token=[a_token],
NSErrorFailingURLKey=https://myapp.appspot.com/_ah/api/users/v1/is_token_valid?
user_token=[a_token],
NSLocalizedDescription=The request timed out., NSUnderlyingError=0xf270e60 "The request timed out."}
I have no clue at all. Hope someone with experience with SPDY on iOS could help!!
It is true that Apple does not support NPN in default TLS implementation. However there are some SPDY libs using OpenSSL (which supports NPN).
Here is one on them:
https://github.com/locationlabs/SPDY-for-iPhone
Unfortunately Apple's TLS implementation doesn't support NPN which is used by Google App Engine. You can read more about it in the CocoaSPDY GitHub README.
You can't use CocoaSPDY with Google App Engine at this time.
Is it ok for you?
I see that SPDY protocol support is now available in NSURLSession on OS X Yosemite and iOS 8 , and is Supported transparently by NSURLSession.
see details: https://developer.apple.com/videos/wwdc/2014/ [What's New in Foundation Networking]
But why it not work?
When i use CocoaSPDY, set manager.session.configuration.protocolClasses=#[[SPDYURLSessionProtocol class]]; it's ok;
I have an app that connects to my home routers web interface. I want to convert this to use https instead of just http.
I was originally using ASIHttpRequest, but as it's no longer supported i'm switching over to AFNetworking.
The problem is, whenever I try to connect, I get this error message:
_block_invoke_0220 [Line 243] ERROR: Error Domain=NSURLErrorDomain Code=-1202 "The certificate for this server is invalid. You might be connecting to a server that is pretending to be “192.168.1.1” which could put your confidential information at risk." UserInfo=0x9792ad0 {NSErrorFailingURLStringKey=https://192.168.1.1/Info.live.htm, NSLocalizedRecoverySuggestion=Would you like to connect to the server anyway?, NSErrorFailingURLKey=https://192.168.1.1/Info.live.htm, NSLocalizedDescription=The certificate for this server is invalid. You might be connecting to a server that is pretending to be “192.168.1.1” which could put your confidential information at risk., NSUnderlyingError=0xa6a3560 "The certificate for this server is invalid. You might be connecting to a server that is pretending to be “192.168.1.1” which could put your confidential information at risk.", NSURLErrorFailingURLPeerTrustErrorKey=< SecTrustRef:
If I navigate to the url i safari, I get a message that Safari can't verify the identity.... and I have to click continue to carry on.
How can I achieve this? I don't really know anything about ssl or https unfortunately.
Here is the code i'm currently using:
NSString *urlString = #"https://192.168.1.1/";
NSURL *url = [NSURL URLWithString:urlString];
// Set authorization
AFHTTPClient *httpClient = [[AFHTTPClient alloc] initWithBaseURL:url];
[httpClient setAuthorizationHeaderWithUsername:user password:pass];
NSURLRequest *request = [httpClient requestWithMethod:#"POST" path:#"Info.live.htm" parameters:nil];
AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request];
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
NSString *responceString = [operation responseString];
// NSLog(#"%#",responceString);
if ([self parseInfoLive:responceString])
[[NSNotificationCenter defaultCenter] postNotificationName:#"downloadsComplete" object:nil];
}
failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"ERROR: %#",error.description);
}];
[operation start];
For getting around the validity check of the host certificate, add the following code.
First add an interface for the setter method that is already within the SDK but not exposed into public:
#interface NSURLRequest(Private)
+(void)setAllowsAnyHTTPSCertificate:(BOOL)inAllow forHost:(NSString *)inHost;
#end
Now, whenever you are rendering a new request, invoke that setter:
[NSURLRequest setAllowsAnyHTTPSCertificate:YES forHost:[inURL host]];
Warning
Do not use this code for production but only while developing your app in cases where the certificate is not yet approved/submitted/installed. Typical would be the use of a development server that does not have a trusted certificate installed.
The use of this code will get your App rejected from distribution via iTunes as it uses a private API method.
For making sure that things work smoothly in a production environment, you will have to get a trusted SSL certificate for your host. There are various authoritative companies providing such thing. To mention at least one (there are MANY more), you could use GoDaddy.
Update (31st May 2013)
AFNetworking got updated to support invalid certificates out of the box, without using any private API's. Kudos to Peter Steinberger!
For enabling that feature, the most convenient solution is to add the following to your prefix header (.pch):
#ifdef DEBUG
#define _AFNETWORKING_ALLOW_INVALID_SSL_CERTIFICATES_
#endif
Once again, I can not emphasize enough that you should refrain from enabling that feature in production code - you would pretty much invalidate the entire point of SSL connections and render them vulnerable.
This URL from Apple documentation might help Check this link
In the above document read Introduction section.
I am not familiar with AFNetworking, but there is a solution here that works around the error you are seeing. Till's answer is reputed to keep you from being able to submit your app to the app store.