Recently, I have been working on a project concerning digital signatures. One very important part of this project involves setting a signature from a mobile device. Since our users are divided between iOS and Android, both platforms had to be supported. From the start of this project, it was clear that we would need to access some of the native functionality of both platforms.
We chose Cordova as a suitable solution, knowing it would allow us to access native functionality with relative ease. The most challenging functionality we needed was a way to access all the certificates (also known as identities) a device has installed, because these certificates contain the private key needed to place a signature. Cordova offers access to native functionality by allowing us to create native plugins and using them through a JavaScript API.
Some of the developers reading this post might already be familiar with Cordova, and know that most of the sought-after functionality is already supported by existing Cordova plugins. Unfortunately, you are out of luck when your application needs access to the certificates installed on a device. The main reason why there is no general plugin for this are most likely the security restrictions of each of the respective operating systems. Thanks to the difference in restrictions it becomes hard to write a plugin that gives you a single shared workflow for every operating system.
This blog post will elaborate on the general setup of a Cordova plugin and the Android implementation for accessing a device's certificates. Another post will follow to explain the implementation details for iOS systems, a more difficult story requiring it's own post. The Cordova plugin will contain the following components:
Let's take a look at these parts:
The Android implementation actually turns out to be quite simple. The following code snippet shows the full implementation:
public class MyPlugin extends CordovaPlugin
implements KeyChainAliasCallback {
private static String[] KEYTYPES = { "RSA" };
CallbackContext callbackContext = null ;
@Override
public boolean execute(String action, JSONArray data, CallbackContext
callbackContext) throws JSONException {
this .callbackContext = callbackContext;
if ( "selectCert" .equals(action)) {
return selectCertificate();
}
return false ;
}
private boolean selectCertificate() {
Activity activity = this .cordova.getActivity();
KeyChain.choosePrivateKeyAlias(activity, this , KEYTYPES, null ,
null , - 1 , null );
return true ;
}
@Override
public void alias(String alias) {
callbackContext.success(alias);
}
Let's break this implementation down and look at the important bits.
public class MyPlugin extends CordovaPlugin
implements KeyChainAliasCallback
It should be fairly straightforward why the class is an extension of CordovaPlugin. Selecting a certificate will open a new dialog (in a different thread), by implementing the KeyChainAliasCallback interface we can have our class serve as a callback for the dialog.
this .callbackContext = callbackContext;
When the plugin executes, we get a CallbackContext object. You can use this object to return data from your plugin to your project's JavaScript codebase. Since we're working with callbacks we need to temporarily save this object.
KeyChain.choosePrivateKeyAlias(activity, this , KEYTYPES, null , null , - 1 ,
null );
This method is the sole reason why our Android implementation is so simple. This one method call opens a new dialog, lets the user select one of his installed certificates, gives the application permission to use the selected certificate and returns the corresponding alias to the callback method.
public void alias(String alias) {
callbackContext.success(alias);
}
This function (inherited from the KeyChainAliasCallback interface) is called when the certificate dialog is closed, we get the alias of the selected certificate or NULL if the user cancelled. The CallbackContext object that we saved earlier is used to return the certificate alias to our JavaScript code.
So at this point, a certificate has been selected and the application has permissions to use it. How can we get the private key from this certificate? In order to do this we use the alias we retrieved earlier, the private key can be retrieved using the following line of code:
PrivateKey pk = KeyChain.getPrivateKey(cordova.getActivity().getApplicationContext(),
certificateAlias);
The native implementation is done, save the file to a directory of your choice (for this example I use src/Android/MyPlugin.java).
In order to use your native implementation from JavaScript you need to define the JavaScript interface. We do this in a seperate JavaScript file, we export our function and specify the required arguments (callback functions):
module.exports = {
selectCert: function (successCallback, errorCallback) {
cordova.exec(successCallback, errorCallback,
"MyPlugin" , "selectCert" , []);
}
};
Save this file in a directory of your choice (for this example I use www/MyPlugin.js).
The last element every Cordova plugin needs is a plugin specification. Create a new file called plugin.xml in the root of your plugin folder and add the following:
<?xml version= "1.0" encoding= "utf-8" ?>
<plugin xmlns= "http://apache.org/cordova/ns/plugins/1.0"
id= "myplugin.certificates"
version= "0.7.0" >
<name>Certificate plugin</name> <!-- Readable name -->
<engines>
<engine name= "cordova" version= ">=3.4.0" />
</engines>
<asset src= "www/MyPlugin.js" target= "js/MyPlugin.js" />
<js-module src= "www/MyPlugin.js" > <!--Include JavaScript interface-->
<clobbers target= "CertificatePlugin" />
</js-module>
<platform name= "android" >
<config-file target= "res/xml/config.xml" parent= "/*" >
<feature name= "MyPlugin" > <!--Register your plugin -->
<param name= "android-package"
value= "plugins.security.MyPlugin" />
</feature>
</config-file>
<source-file src= "src/android/MyPlugin.java"
target-dir= "src/certificateplugin/plugin/" />
</platform>
</plugin>
The plugin can now be used for Android devices. To install the plugin to our Cordova project we use the following command:
cordova plugin add /path/to/plugin
We can now use the plugin by calling the following method from
JavaScript:
CertificatePlugin.selectCert();
If everything went according to plan, this method should open a dialog where a user can select one of his installed certificates (or install new ones).
Hopefully this post has shown how to create a Cordova plugin from scratch and how easy it is to access the certificates installed on an Android device. In the next post we will look at achieving (somewhat) similar functionality for iOS.