In iOS 9 Apple have turned on App Transport Security (ATS) as default. So if your iOS application is connecting to an insecure url resource then you’re out of luck as it will be blocked, an example of the most likely culprit is viewing a webpage without SSL encryption.
With the current application I’m working on I ran into trouble whilst loading a dev resource only to find it was using a self signed certificate and being blocked by the OS. I also had a requirement to inspect an Ajax web request and the WebView ShouldStartLoad delegate was not receiving these request callbacks.
First things first, if your applications requests are being blocked you will see something like:
NSURLSession/NSURLConnection HTTP load failed (kCFStreamErrorDomainSSL, -9802)
The best way to resolve this issue is to ensure all resources are securely protected in the correct way, and therefore your application, and users data, is secure!
But this is not always possible! And if you’re like me then you have no choice but to step around the default ATS setting.
Quick, change the Info.plist!
To resolve a problem with a blocked resource you can turn off ATS for you application with a single entry in your Info.plist file. Whilst this is the simplest and quickest approach, my advice would be to avoid doing this. Yes, you may just be making the change for a quick test or prototype app, but it’s easy to forget you made the change and before you know it you’ve released an insecure app to your users.
My advice is to turn off ATS for the only resources you trust. In my case a single domain.
This can be achieved by adding the following into your Info.plist file:
[sourcecode language=”xml”]
<key>NSAppTransportSecurity</key>
<dict>
<key>NSExceptionDomains</key>
<dict>
<key>[ENTER YOUR BASE URL HERE]</key>
<dict>
<key>NSTemporaryExceptionAllowsInsecureHTTPLoads</key>
<true/>
<key>NSIncludesSubdomains</key>
<true/>
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>
</dict>
</dict>
[/sourcecode]
Spying on all requests
iOS is no longer blocking my webpage but we need a way to listen in to all requests our web view is making so we can work our magic with trusting the self signed certificate (remember this part is for dev releases only and not production).
Start by creating a new class and have it inherit from NSUrlProtocol. Then override the default delegate methods so that we can add our custom logic.
[sourcecode language=”csharp”]
[Export ("canInitWithRequest:")]
public static bool canInitWithRequest (NSUrlRequest request)
{
Console.WriteLine (String.Format ("URL # { 0}. Loading URL: { 1}", counter.ToString(), request.Url.ToString()));
counter++;
if (CustomNsUrlProtocol.GetProperty(MY_KEY, request) == myObj)
{
return false; // request has already been handled
}
return true;
}
[/sourcecode]
Here we get the opportunity to check all requests before they are loaded, and make a decision if we need to do something clever or not. The default behaviour here is to return ‘true’ which means we need to do something. So let’s do something…
[sourcecode language=”csharp”]
public override void StartLoading ()
{
// start loading the URL now we have handled it
new NSUrlConnection (Request, new CustomNSUrlConnectionDataDelegate (this, new NSUrl(Constants.baseUrl)), true);
}
[/sourcecode]
The clever part is to create a new instance of a NSUrlConnectionDataDelegate and use this delegate to inspect the certificate of each request.
Finally we just need to tell our custom protocol that we have now dealt with this request and we do not want to see it again.
[sourcecode language=”csharp”]
public override NSUrlRequest Request
{
get {
NSMutableUrlRequest mutableRequest = (NSMutableUrlRequest)base.Request.MutableCopy ();
CustomNsUrlProtocol.SetProperty (myObj, MY_KEY, mutableRequest);
return mutableRequest;
}
}
[/sourcecode]
We’ve assigned an object to our request. Now when it attempts to load, via canInitWithRequest, it will recognise that we have already dealt with this request and the browser will continue to load the request as normal.
On to the magic
So far you’ve probably noticed we haven’t actually done anything with our requests other than inspect them and assign an object to say we have done so.
Create a class that inherits from NSUrlConnectionDataDelegate and override CanAuthenticateAgainstProtectionSpace. This method will be called if we receive an authentication for any of the requests we pass if from our NSUrlProtocol.
[sourcecode language=”csharp”]
public override bool CanAuthenticateAgainstProtectionSpace (NSUrlConnection connection, NSUrlProtectionSpace protectionSpace)
{
#if DEBUG || TEST
if (protectionSpace.AuthenticationMethod.Equals (Foundation.NSUrlProtectionSpace.AuthenticationMethodServerTrust))
return true;
return false;
#else
return false;
#endif
}
[/sourcecode]
The default value we return is false which means we will not authenticate against any insecure connections. However, if we receive a challenge and we are running in the development or test environment then we return true as this is something we want to handle.
Finally I use the delegate ReceivedAuthenticationChallenge to trust the request.
[sourcecode language=”csharp”]
public override void ReceivedAuthenticationChallenge (NSUrlConnection connection, NSUrlAuthenticationChallenge challenge)
{
var baseUrl = new NSUrl(Constants.baseUrl); // this is the base URL we trust
// check we trust the host – an additional layer of security
if (challenge.ProtectionSpace.Host.Equals (baseUrl.Host))
{
challenge.Sender.UseCredential (new NSUrlCredential (challenge.ProtectionSpace.ServerSecTrust), challenge);
}
else
{
Console.WriteLine ("Host not trusted: " + challenge.ProtectionSpace.Host);
}
}
[/sourcecode]
A quick check to ensure the URL is from a webpage I trust, and if so I provide my own credentials and reload the connection. Re-run the application and if all goes well there will be a heap of URLs written to the debug log as they are ‘taken care of’ and the page will load as normal.
As always, leave a comment below if you have a question. A sample project with the complete working code can be downloaded from here.
If you’re after more information or want to introduce more control over your request loading then I recommend this page as a starting point: https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/URLLoadingSystem/Articles/AuthenticationChallenges.html
I just ran into this issue. The github repo is no longer available. Any chance you can share the code from this post? Thanks.