We’ve had several customers and partners ask us if it was possible to write a secure plugin architecture for AIR applications. Although we always assumed it could be done, I finally decided to give it a try just to make sure. The verdict: not only is it possible to write a secure plugin architecture for AIR applications, but it’s actually pretty straightforward.
The rest of this post provides all the information you need to write your own secure AIR plugin architecture. It also provides sample code to get you started, and discusses a few issues that authors of plugin architectures need to be cognizant of.
What Are Plugins?
First thing’s first: what exactly do I mean by the term "application plugin?" A plugin is a way for a third party to extend the functionality of an application. Two of the best examples are Firefox add-ons and Google Chrome extensions. Both browsers have architectures which enable third parties to extend application functionality and enhance the user experience through a plugin model. Want your browser to block ads, notify you of new email, or look things up on Wikipedia? Just find the right plugin and install it. Plugins let end users take a standard piece of software and customize it for his or her specific needs and tastes.
The Challenges of Plugins
So why wouldn’t AIR support plugins? We obviously knew that AIR could support some form of extensibility (loading SWFs, adding them to the display list, and calling functions on them), but what we weren’t entirely sure about was how secure the entire process could be. In other words, it’s not enough to be able to download content and run it inside your applications; it also has be to be possible to do so in a highly secure and responsible manner.
In addition to security issues, we also simply didn’t have a good idea as to the best methods for architecting a plugin model. Although our goal was not to create an official and supported plugin framework (the code offered here is sample code only), we did want to have at least one solid proof of concept that we could point developers to in order to give them a head start.
Introduction to "Pluggable SearchCentral"
Last year, I wrote an application called SearchCentral as a way of validating the new AIR 2
NativeProcess APIs. It uses a command called
mdfind to search your local hard drive using Spotlight (Mac only, obviously), and also lets you search Google and Wikipedia from the same app. When I started thinking of a good application to use to validate a plugin architecture, SearchCentral seemed like a perfect candidate. Rather than just searching three "hard-coded" sources, I decided to create a new version of SearchCentral called "Pluggable SearchCentral", or just PSC, that would support installing search plugins. Each plugin adds the ability to search a different resource, and can display search results any way it wants.
Search plugins can be installed either from the local hard drive, or over the network via a URL. All plugins must be signed, and are validated before they are installed. Additionally, end users are presented with a plugin installation dialog which contains all the information they need to make a decision about whether to install the plugin or not. With every plugin that gets installed, PSC becomes more customized, versatile, and powerful. (Note that PSC is intended to be a sample application and a proof of concept; although it is complete and fairly usable, I also intentionally excluded a great deal of functionality in order to keep the code as simple as possible.)
There are two distinct components to the PSC proof of concept: Pluggable SearchCentral itself, and the search plugins. Below are brief descriptions of each.
PSC doesn’t contain any of its own search logic. Instead, PSC provides the UI for doing a search, the ability to load plugins, and the ability to install new plugins. That’s it. The rest of the application’s functionality is left up to the plugins that the end user installs.
PSC plugins are zip files which are bundled and signed using ADT, and which contain the following:
- Any Flex application with a public
searchmethod. The plugin can search any resource (local or remote), and can display its search results any way it wants.
- A plugin.xml file which contains information about the plugin such as its name, ID, description, location, and version (more on this below).
Since PSC plugins execute code in the application sandbox, and since they may be downloaded over the internet, they have to be signed, validated, and approved by the end user before they can be installed. The section below entitled "Installing and Validating a Plugin" describes this process in detail.
How PSC Works
Below is a more detailed description of PSC, and how the plugin architecture works.
When PSC starts, it looks for a directory called "plugins" in the application storage directory (
File.applicationStorageDirectory). If the directory isn’t there (for instance, if it’s the first time the application is run, or if the plugin directory was deleted by the end user), the initialization code creates the missing directory. PSC then iterates through all the directories in the plugin directory and loads metadata about all the plugins it finds. Only the plugin metadata is loaded into memory (name, ID, path, description, and version) rather than the plugin itself in order to keep memory consumption down.
Selecting a Plugin
When the user selects a plugin, PSC uses the plugin’s path metadata and AIR’s file system APIs to load the plugin’s SWF into a
ByteArray. The bytes are then added to the display list using a
SWFLoader. During the initialization of the application, a
LoaderContext is set on the
SWFLoader with its
allowLoadBytesCodeExecution property set to true in order to allow the plugin to execute ActionScript bytecode.
Performing a Search
When a user enters a search term and either presses enter or clicks on the "Search" button, PSC calls the plugin’s public
search function and passes in the search term. If the plugin was not successfully loaded, or if the plugin does not have a public search function, an error message is displayed letting the user know that the plugin is corrupt.
Installing and Validating a Plugin
Plugins can be installed either from the local hard drive, or from the network using a URL. After a plugin is located or downloaded, it goes through the following process:
- The plugin is unzipped into a temporary directory. As soon as the plugin descriptor file is extracted from the zip archive, the plugin ID is checked against the IDs of all the existing plugins to make sure the same plugin isn’t getting installed twice.
- The plugin’s signature.xml file is loaded from the plugin’s META-INF directory. (For more information on the AIR file format — which is also the format used for PSC plugins — see Oliver Goldman’s article, Notes on the AIR File Format.)
- The signature XML files is validated to make sure it doesn’t contain any unexpected data or information. If it does, the entire plugin is immediately deleted.
- The signature in the signature.xml file is verified using the
XMLSignatureValidatorclass to make sure it’s a valid signature.
- The digests of all the files contained in the plugin are calculated and verified against the digests claimed in the signature.xml file. This process ensures that none of the files in the plugin bundle were replaced after the plugin was signed. If any of the digests don’t match, the plugin is immediately deleted.
- The number of files in the expanded plugin is compared to the number of files listed in the plugin’s manifest file. If the plugin contains any files not in the manifest (meaning they weren’t verified), the entire plugin is immediately deleted.
- The end user is presented with the data contained in both the plugin.xml file and the plugin’s signature, and asked if he or she wants to install the plugin. The data includes the name of the plugin, the description, the name of the publisher, and whether the signature was verified (meaning whether the certificate with which the plugin was signed chains to a trusted digital certificate already installed on the end user’s machine).
- If the user chooses not to install the plugin, it is immediately deleted without ever having been loaded. If the user chooses to install the plugin, it is moved to the plugin directory, then loaded so that it’s ready for use.
Uninstalling a Plugin
Uninstalling a plugin is simply a matter of deleting the plugin’s directory, then removing the plugin data object from the data provider which populates the plugin
ComboBox. After a plugin has been removed, it can be reinstalled at any time.
Creating a Plugin
A PSC plugin can be any Flex application that has a public
search function which takes in a single
String argument (the term being searched for). It can perform any type of search it wants, and can display the search results any way it wants.
Plugins for this particular example are easy to write since they can be authored and tested as independent Flex applications. They can even be created as separate AIR applications as long as the root
WindowedApplication tag is is changed to an
Application tag before the final SWF is compiled.
The following describes the process of creating and signing a PSC plugin:
- Create a new Flex project. It can be either a web or a desktop project. If your plugin needs access to AIR APIs, make sure to use a desktop project.
- Create a public search function that takes in a single
Stringargument (the term being searched for).
- Conduct your search, then display your search results any way you want.
- When your plugin is finished, compile the final SWF. If your plugin is a desktop Flex project, make sure to change the root tag from
- Create a new directory for your plugin, then copy the plugin’s SWF into the new directory. (This is the directory that will get zipped and signed.)
- Create a descriptor file for your plugin in your plugin directory (see the plugin.xml file in the plugin sample code for an example). Since we’re going to use ADT to bundle and sign the plugin, the descriptor has to be a valid AIR application descriptor file which means it needs to contain the following elements:
id: a unique ID for your plugin. This is used to ensure that the same plugin isn’t installed multiple times.
name: the name of your plugin which is displayed to the end user before the plugin is installed, and is displayed in the plugin select box after it’s installed.
description: a brief description of your plugin which is displayed to the end user before the plugin is installed.
filename: not currently used by PSC, but required by ADT. (This could be used as the name of the plugin directory, but my sample implementation uses a universally unique ID instead in order to avoid potential conflicts.)
version: the version of your plugin. This is not currently used by PSC, but in a more comprehensive implementation, this would be used to allow plugins to be updated (and to protect against unintentional downgrades which are usually considered security risks).
initialWindow.content: the path to your plugin’s SWF file.
- Use ADT to both bundle and sign your plugin. The ADT command should look something like this:
adt -package -storetype pkcs12 -keystore /path/to/code_signing_certificate my_plugin.zip my_plugin_dir/plugin.xml -C my_plugin_dir .
Your plugin is now ready to be securely loaded into PSC!
Key Tools and APIs
Below is an overview of the tools and APIs that make writing a secure plugin architecture in AIR possible.
XMLSignatureValidator is really the key to securing the plugin architecture. From the API documentation:
The XMLSignatureValidator class validates whether an XML signature file is well formed, unmodified, and, optionally, whether it is signed using a key linked to a trusted digital certificate.
XMLSignatureValidator class, it would be extremely difficult for an AIR application to determine whether plugins were signed using a certificate that chains to an installed certificate, and to extract information about the plugin’s publisher to present to the end user before the plugin gets installed. With just a few lines of code, however, your application can verify the integrity and authenticity of a plugin with the same reliability as the AIR runtime itself.
Note that the
XMLSignatureValidator does not verify the integrity of the other files in the plugin bundle. That’s something the plugin architecture has to do itself (see the PSC source code for an example). The
XMLSignatureValidator can verify that the signature file itself was not tampered with, and it provides the digests of all the files in the bundle, but the plugin architecture must compare those digests against the computed digests of the files themselves to be certain that the plugin was not tampered with. This step must not be skipped.
For more on the
XMLSignatureValidator class, and on XML signatures in general, see Joe Ward’s article, Creating and validating XML signatures.
UCFSignatureValidator is an ActionScript class originally written by Oliver Goldman which I adapted to work with this sample. It takes care of validating the structure of the signature.xml file, validating the signature itself (using the
XMLSignatureValidator class), verifying the digests of the files in the package, and making sure no additional files were slipped in. While
UCFSignatureValidator should be considered sample code, it provides a robust and comprehensive starting point for a secure plugin architecture.
ADT is by far the easiest way to get your plugin bundled and signed since it’s an integral part of the AIR SDK, and many AIR developers already have experience using it. You can use ADT to sign your plugin as though it were an AIR application assuming that your plugin descriptor has all the tags which ADT requires for AIR applications (the tags listed above are the bare minimum required). I found the AIR application descriptor format to be perfect for my plugin architecture, but if your architecture requires additional metadata about your plugins, you might need to create a second descriptor file format. Alternatively, you can use other methods to sign and bundle your plugins, but be sure to read the
XMLSignatureValidator documentation carefully to make sure that your method will generate compatible signatures.
FZip is an ActionScript project which my sample plugin architecture uses to unzip plugins. It works perfectly right out of the box for unzipping plugins bundled by ADT.
I use the as3crypto project (inside of
UCFSignatureValidator) for its SHA1 and SHA256 hash algorithm implementations in order to calculate digests of the files contained in the plugin bundle. I could have used the Flex
mx.utils.SHA256 class since ADT currently only uses SHA256, however since it’s possible for signature.xml files to use the SHA1 algorithm, I decided to use as3crypto in case another method besides ADT was used to sign the plugin.
Issues To Be Aware Of
Although writing a robust plugin architecture for AIR applications is relatively straightforward, there are some things developers and end users need to be aware of:
- Performance. Since unzipping and validating plugins is done in the main application thread, if your plugins are too big, they may cause the application to appear unresponsive during the validation process. I never ran into performance problems with PSC, but I imagine that once plugins reach a certain size, the user experience may degrade. If this happens to you, be sure to provide end users with adequate feedback so they know what’s happening. In extreme cases, you may want to consider creating a "frame loop" and doing your validation in distinct units of work between which the UI can be updated.
- Signing with ADT. As previously discussed, the easiest way to package and sign your plugins is with ADT, however since ADT’s primary job is to sign and bundle AIR applications, you have to follow some AIR application conventions (like using the standard AIR application descriptor) in order for it to work properly. Fortunately, I found the application descriptor format to be perfect for plugins, so I had no problem using ADT whatsoever, but this is something to be aware of while designing a plugin architecture. (Note that using ADT to sign application plugins is not currently explicitly supported, but I have verified that it works fine.)
- Post-installation Security. It’s important to understand that the plugin architecture described here and implemented in the PSC sample application can verify the identify of a plugin publisher at install time, and can also verify the integrity of all the files in the plugin bundle, but it cannot ensure the integrity of the plugin after it has been installed. In other words, the point of the plugin framework is to verify the plugin at the time of installation in order to protect against an attack resulting from the plugin being replaced on the server or otherwise somehow altered on its way to you, but it does not guarantee the integrity of the plugin after the plugin has been installed. It is therefore possible for a malicious process to tamper with the plugin after it has been verified and installed. Since it’s not practical to re-verify the plugin every time it’s loaded, there is currently no way to protect against this type of attack. That said, if you have already inadvertently installed malicious software on your machine capable of tampering with application plugins, it’s safe to assume that your computer has already been compromised, and that the altering of application plugins is probably the least of your worries.