Thursday, 29 November 2012

How to bypass SSL certificate checking in Apache Cordova 2.2 on Android



How to bypass SSL certificate checking in Apache Cordova 2.2 on Android

(or how to override CordovaWebViewClient in versions of Cordova 1.9+)

I came across a strange issue where I was unable to access a secure URL using HTTPS.  At first I thought it was due to the Cordova whitelist since there was an issue with it in Cordova 2.1.  In order to access an external URL you have to whitelist them eg
<access origin="https://www.wikipedia.org" subdomains="true"/>
This didn't help though and even if I allowed all URLS using
<access origin = "*"/>
it still didn't work.  Strangely though it was only one secure URL I couldn't access, not all of them

As it turns out, it was probably to do with the way Cordova assesses valid certificates.  When an Android app is signed and deployed it is deployed in 'production mode' which means that any secure URL accessed must have a valid certificate signed by a certificate authority (CA), so cannot be self-signed.  Something about this particular certificate, though signed by a CA, was being rejected by Cordova, which led me to raise this bug: https://issues.apache.org/jira/browse/CB-1947

Since I'm impatient, I decided to try and bypass the certificate checking.  Searching led me to various solutions, all based on extending CordovaWebViewClient and overriding the onReceivedSslError() method. For example:

public class SSLAcceptingWebViewClient extends CordovaWebViewClient {
    public SSLAcceptingWebViewClient(DroidGap ctx) {
        super(ctx);
    }
    @Override
    public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
            handler.proceed();
    }
 
}

This was the easy part.  I then needed to tell Cordova to use my new class, rather than the default one.  This seemed simple as the advice I found on stackoverflow was to set it like this:
this.setWebViewClient(this.appView, new MyWebViewClient(this));
Unfortunately, this method signature no longer existed in Cordova 2.2.  Altering it to
this.appView.setWebViewClient( new SSLAcceptingWebViewClient(this));
compiled fine.  However, it resulted in a NullPointerException in the onPageLoad() method as seen by various others with no resolution.  I couldn't find any solution to this on the web, so I had to dig into the source code for Cordova.  Since it was a NullPointerException something had to be null, and the offending line was
if (!this.appView.useBrowserHistory) {
Since 'this' couldn't be null and useBrowserHistory is a boolean, it could only be appView that was null.  Some messing around finally led me to this solution in my main activity class

public class MyActivity extends DroidGap {
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        super.init();
     
        CordovaWebViewClient webViewClient = new SSLAcceptingWebViewClient(this);
        webViewClient.setWebView(this.appView);
        this.appView.setWebViewClient(webViewClient);
     
        super.loadUrl("file:///android_asset/www/index.html");
    }
}

And it works! I've successfully bypassed the SSL certificate checking!  Obviously this is inherently insecure, so I can't recommend it as a permanent solution.  I'm hoping that the above bug will be resolved in the next version of Cordova and I can switch my bypass solutions off.

If you need to override CordovaWebViewClient, this seems to be the way to do it.  If you know a better way, please do let me know

Tuesday, 20 November 2012

Creating an Android Jelly Bean Daydream from a WebView


One of the new nifty features on Android Jelly Bean 4.2 is the concept of daydreams (basically a screensaver but also displays when docked etc).  I wanted to create one that displayed an HTML page, which makes for prety easy development and this is how

First of all, this assumes you have API 17 (Android 4.2) installed, otherwise you won't have access to the relevant Android class

Android Daydream option


You need to create a new class that extends android.service.dreams.DreamService and override the onAttachedToWindow() method which is what is called when the service starts up.  In here all you really need to do is create a new WebView as normal and attach it.  Example code below

import android.annotation.TargetApi;
import android.service.dreams.DreamService;
import android.webkit.WebView;
@TargetApi(17)
public class MyDreamService extends DreamService {
    @Override
    public void onAttachedToWindow() {
        super.onAttachedToWindow();
        // Allow user touch
        setInteractive(true);
        // Hide system UI
        setFullscreen(true);
        // Set the dream layout
        WebView webView = new WebView(this);
        setContentView(webView);
        webView.loadUrl("file:///android_asset/www/test.html");
    }
}

The last line is loading my local HTML file but you could load any page here (remember to set the INTERNET permission for external pages though)

The service needs to be declared in the manifest, for example

<service
    android:name=".MyDreamService"
    android:exported="true"
    android:label="@string/short_app_name" >
    <intent-filter>
        <action android:name="android.service.dreams.DreamService" />
        <category android:name="android.intent.category.DEFAULT" />
    </intent-filter>
</service>

That is pretty much all you need to do! Install your app and try it! The dream setting are under Settings > Display > Daydream

There are various options you can set (eg I set the fullscreen option above) that are documented in the API here: http://developer.android.com/reference/android/service/dreams/DreamService.html

Not a very exciting screenshot but this is my html page displayed as a daydream in the 4.2 emulator
And there you have it, easy! Hope this helps someone