Handle custom CAs in iOS app


This post discusses how to handle custom CAs in a Swift 2 iOS app that uses Alamofire.

Certificates need to be in the DER encoded binary format. If they are in PEM (BASE64) encoded text format, you need to convert them to DER encoded format. This can be achieved with openssl, thus

openssl x509 -inform PEM -in infile.cer -outform DER -out outfile.cer

Then, just drag the certificate files into your Xcode project so that they are embedded as resources.

Next, we need to create a custom Alamofire manager with a custom ServerTrustPolicyManager

        let serverTrustPolicies: [String: ServerTrustPolicy] = [
            "example.com": .PinCertificates(
                certificates: ServerTrustPolicy.certificatesInBundle(),
                validateCertificateChain: true,
                validateHost: true
            )
        ]
        
        manager = Manager(
            serverTrustPolicyManager: ServerTrustPolicyManager(policies: serverTrustPolicies)
        )

All REST requests should then be created using manager.request instead of Alamofire.request.

In iOS 9, additional transport security exceptions are required to be set in info.plist.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
...
    <key>NSAppTransportSecurity</key>
    <dict>
        <key>NSExceptionDomains</key>
        <dict>
            <key>example.com</key>
            <dict>
                <key>NSExceptionAllowsInsecureHTTPLoads</key>
                <true/>
                <key>NSExceptionRequiresForwardSecrecy</key>
                <false/>
                <key>NSIncludesSubdomains</key>
                <true/>
                <!-- Optional: Specify minimum TLS version -->
                <key>NSTemporaryExceptionMinimumTLSVersion</key>
                <string>TLSv1.2</string>
            </dict>
        </dict>
    </dict>
...
</dict>
</plist>

Tackling OAuth 2.0 in iOS 9 with Swift 2


In an earlier post I discussed how to tackle OAuth 2.0 in an Android app. This post discusses how to do that in iOS 9 with Swift 2.

Step 1, in the post referenced above, is performed in a UIWebView. The view containing the UIWebView implements UIWebViewDelegate, intercepts all accessed URLs in webView:shouldStartLoadWithRequest:navigationType: function, and acquires the authorization code using regular expressions.

class ViewController: UIViewController, UIWebViewDelegate {
    @IBOutlet weak var webView: UIWebView!

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
        webView.delegate = self;
        webView.loadRequest(loginURL) // initialize loginURL
    }

    func webView(webView: UIWebView, shouldStartLoadWithRequest: NSURLRequest, navigationType: UIWebViewNavigationType) -> Bool {
        var continueToUrl = true
        do {
            let regex = try NSRegularExpression(pattern: "code=(.+?(?=&|$))", options: NSRegularExpression.Options())
            let url: String? = shouldStartLoadWithRequest.url?.absoluteString;
            let result: NSTextCheckingResult? = regex.firstMatch(in: url!, options: NSRegularExpression.MatchingOptions(), range: NSMakeRange(0, url!.characters.count))
            let codeNSRange = result?.rangeAt(1)
            if (codeNSRange != nil) {
                let startIndex = url?.index((url?.startIndex)!, offsetBy: (codeNSRange?.location)!)
                let endIndex = url?.index((url?.startIndex)!, offsetBy: (codeNSRange?.location)! + (codeNSRange?.length)!)
                let code = url?.substring(with: Range<String.Index>(startIndex!..<endIndex!))
                debugPrint(code)
                acquireAccessToken(code!)
                continueToUrl = false
            }
         } catch {
         }
         return continueToUrl;
     }

I found it challenging to use the interfaces for regular expression matching, and extracting substrings. They are quite different from those found in other popular languages, but should be familiar to Objective C developers. Step 2, to acquire the access token, can be performed thus

    func acquireAccessToken(code: String) {
        manager!.request(tokenURL, method: .post, parameters: ["code": code])
            .validate().responseJSON { response in
                switch response.result {
                case .Success:
                    if let value = response.result.value {
                        let json = JSON(value)
                        self.accessToken = json["access_token"].stringValue
                        debugPrint("Access Token: \(self.accessToken)")
                    }

                case .Failure(let error):
                    debugPrint(error)
                }
        }
    }

The code above utilizes Alamofire for performing REST calls, and SwiftyJSON to handle JSON. Obtain those two frameworks using Carthage. Create a Cartfile with

github "Alamofire/Alamofire" ~> 3.0
github "SwiftyJSON/SwiftyJSON"

Then, invoke carthage to build the frameworks

carthage update

Drag the .framework files under Carthage/Build/ into your Xcode project. The files are added by reference. Adjust project target settings to ensure that the frameworks are embedded, otherwise you’ll get an error such as

dyld: Library not loaded: @rpath/SwiftyJSON.framework/SwiftyJSON
  Referenced from: ...

If you use git for version control, remember to adjust .gitignore to exclude Carthage/ folder.

Dealing with .NET’s messy WebBrowser control


I’ve been trying hard to coax .NET’s WebBrowser control to log in using PingFederate federation server. I particularly don’t want to mess with the registry to change Internet Explorer’s browser emulation settings due to a single application. Something that with Android is amazingly simple, requires a lot of extra effort with .NET for the Desktop.

The WebBrowser control defaults to IE7 emulation as seen by the following User-Agent header, discovered using Fiddler.

User-Agent: Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.2; WOW64; Trident/7.0; .NET4.0C; .NET4.0E; .NET CLR 2.0.50727; .NET CLR 3.0.30729; .NET CLR 3.5.30729)

There’s a bug in the browser control that sends a trailing null character in POST data.

pf.username=xxx&pf.pass=xxx&pf.ok=clicked&pf.cancel=<NULL>

I had to extend the WebBrowser control, gain access to its internal ActiveX control, and use events of that control to modify the behavior just enough to be able to log in using PingFederate. The code that does that is reproduced below.

    public class ExtendedWebBrowser : WebBrowser
    {
        bool renavigating = false;

        public string UserAgent { get; set; }

        public delegate void BeforeNavigateDelegate(string url, ref bool cancel);

        public event BeforeNavigateDelegate HandleBeforeNavigate;

        public delegate void NavigateErrorDelegate(string url, ref bool cancel);

        public event NavigateErrorDelegate HandleNavigateError;

        public ExtendedWebBrowser()
        {
            DocumentCompleted += SetupBrowser;

            //this will cause SetupBrowser to run (we need a document object)
            Navigate("about:blank");
        }

        void SetupBrowser(object sender, WebBrowserDocumentCompletedEventArgs e)
        {
            DocumentCompleted -= SetupBrowser;
            SHDocVw.WebBrowser xBrowser = (SHDocVw.WebBrowser)ActiveXInstance;
            xBrowser.BeforeNavigate2 += BeforeNavigate;
            xBrowser.NavigateError += NavigateError;
        }

        private void NavigateError(object pDisp, ref object URL, ref object Frame, ref object StatusCode, ref bool Cancel)
        {
            if (HandleNavigateError != null)
                HandleNavigateError.Invoke((string)URL, ref Cancel);
        }

        void BeforeNavigate(object pDisp, ref object url, ref object flags, ref object targetFrameName,
            ref object postData, ref object headers, ref bool cancel)
        {
            if (renavigating)
            {
                renavigating = false;
                if (HandleBeforeNavigate != null)
                {
                    HandleBeforeNavigate.Invoke((string)url, ref cancel);
                }
            }
            else
            {
                byte[] pSrc = (byte[])postData;
                byte[] p = pSrc;

                if (pSrc != null && pSrc[pSrc.Length - 1] == 0)
                {
                    // remove trailing null from POST data
                    p = new byte[((byte[])postData).Length - 1];
                    Array.Copy(((byte[])postData), p, p.Length);
                    renavigating = true;
                }

                if (!string.IsNullOrEmpty(UserAgent))
                {
                    headers += string.Format("User-Agent: {0}\r\n", UserAgent);
                    renavigating = true;
                }

                if (renavigating)
                {
                    Navigate((string)url, (string)targetFrameName, p, (string)headers);
                    cancel = true;
                }
            }
        }
    }

The authorization code returned by PingFederate can be obtained by registering for HandleNavigateError event. Using the HandleBeforeNavigate event handler does not work, because it is not invoked when the browser control is redirected after a 302 Not Found response.

            extendedWebBrowser1.HandleNavigateError += delegate (string url,
                ref bool cancel)
            {
                cancel = ExtractAuthorizationCode(url);
            };

Tackling OAuth 2.0 in an Android app


This post shows how to perform OAuth 2.0 authorization in an Android app using WebView. It is meant for those who need to tackle OAuth 2.0 themselves, probably because their identity provider does not provide an Android library.

OAuth 2.0 defines a two-step process for obtaining an access token from an authorization server (aka identity service), that can subsequently be used to obtain resources from resource servers that trust the authorization server.

  1. Obtain authorization code from authorization server. This step is usually carried out once, within the browser, to mitigate the need for user credentials to be handled by clients. The user authenticates with the identity service, and authorizes requested scopes. The authorization service grants an authorization code as a result, and redirects the browser to a redirect URI specified by the client.
  2. Client uses the authorization code obtained through previous step, and performs a token request to authorization server with its own credentials such as client_id and client_secret. This step usually happens in a server application, but it’s done here on device. Some OAuth 2.0 providers will enable a simpler implicit grant flow, where step 1 above returns an access token, dispensing the need for step 2.

Here’s one way to carry out Step 1 in a WebView

// define REDIRECT_URI
final WebView webView = (WebView)findViewById(R.id.webView);
WebSettings webSettings = webView.getSettings();
webSettings.setJavaScriptEnabled(true);
WebViewClient webViewClient = new WebViewClient() {
    @Override
    public boolean shouldOverrideUrlLoading(WebView view, String url) {
        if (url.startsWith(REDIRECT_URI)) {
            Pattern p = Pattern.compile(".+code=(.+?(?=&|$))");
            Matcher m = p.matcher(url);
            if (m.matches()) {
                acquireAccessToken(m.group(1));
                // update UI
            }
            return true; // we've handled the url
        } else {
            return false;
        }
    }
};
webView.setWebViewClient(webViewClient);
// Prepare loginURL
webView.loadUrl(loginURL);

I intercept browser navigation using shouldOverrideUrlLoading. Upon detecting the redirect URI, I look for the authorization code, extract it using a simple regular expression, and initiate the procedure to obtain the access token, as described in step 2. It can be carried out using a simple REST request such as

    private void acquireAccessToken(String code) {
        // prepare url

        AsyncTask task = new AsyncTask<Object, Integer, String>() {
            @Override
            protected String doInBackground(Object[] urls) {
                return executeRequest((String) urls[0], "POST", "");
            }

            @Override
            protected void onPostExecute(String result) {
                try {
                    JSONObject json = new JSONObject(result);
                    String accessToken = (String)json.get("access_token");
                } catch(Exception ex) {
                    Log.e(TAG, "Request failed.", ex);
                }
            }
        };
        task.execute(url);
    }

To perform the HTTP POST request above, I use the HttpsURLConnection class, in the executeRequest convenience method, implemented as follows

    private String executeRequest(String url, String method, String content) {
        StringBuilder buffer = new StringBuilder();
        try {
            URL connUrl = new URL(url);

            HttpsURLConnection conn = (HttpsURLConnection)connUrl.openConnection();
            conn.setSSLSocketFactory(sslContext.getSocketFactory());

            if (content != null) {
                conn.setRequestMethod(method);
                conn.setRequestProperty("Content-Type", "application/json");
                conn.setRequestProperty("Content-Length", String.valueOf(content.length()));
                OutputStreamWriter writer = new OutputStreamWriter(conn.getOutputStream());
                for (int i = 0; i < content.length(); i++)
                    writer.write(content.charAt(i));
            }

            InputStreamReader reader = new InputStreamReader(conn.getInputStream());
            int c = reader.read();
            while (c != -1) {
                buffer.append((char)c);
                c = reader.read();
            }

            conn.disconnect();
        } catch (Exception ex) {
            Log.e(TAG, "Request failed.", ex);
        }
        return buffer.toString();
    }

Since my authorization server uses SSL certificates with custom CAs, I have need for a customized SSLContext that can perform SSL handshake using the custom CAs (certificate pinning). This is how sslContext above may be initialized

        // Create a KeyStore containing our trusted CAs,
        // see http://developer.android.com/training/articles/security-ssl.html
        String keyStoreType = KeyStore.getDefaultType();
        KeyStore keyStore = KeyStore.getInstance(keyStoreType);
        keyStore.load(null, null);
        CertificateFactory cf = CertificateFactory.getInstance("X.509");
        Certificate ca = cf.generateCertificate(getResources().openRawResource(R.raw.cert_1));
        keyStore.setCertificateEntry("ca1", ca);
        ca = cf.generateCertificate(getResources().openRawResource(R.raw.cert_2));
        keyStore.setCertificateEntry("ca2", ca);

        // Create a TrustManager that trusts the CAs in our KeyStore
        String tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm();
        TrustManagerFactory tmf = TrustManagerFactory.getInstance(tmfAlgorithm);
        tmf.init(keyStore);

        TrustManager[] trustManagers = tmf.getTrustManagers();
        sslContext = SSLContext.getInstance("TLS");
        sslContext.init(null, trustManagers, null);

Certificates are packaged as raw resources under res folder. Android has special naming restrictions for raw resource file names; it only allows lower-case letters and underscores. Binary DER or corresponding textual PEM certificates work fine.