Wednesday 16 September 2009

A framework for Modular AIR Applications

I've finished building a framework for securely loading signed, packaged swfs into the application sandbox at runtime, while leaving others in the non-application sandbox and rejecting modules without matched signatures.


The framework fairly neatly packages the whole process.

The origin of the signature testing process is the Adobe article here, where you can also download a compiled code swc of the necessary flex-only classes if you're working in Flash.

The air application I've built as proof-of-principle achieves the following:
  • Modules with matching signatures are loaded to the app sandbox (if requested).
  • Modules without matching signatures requesting app sandbox loading are rejected.
  • Modules can also be loaded to the non-application sandbox.

It demonstrates:
  • That app sandbox loaded modules can write to the file system (they put a directory on your desktop).
  • That non-app sandbox loaded modules are prevented from writing to the file system.
  • That both app and non-app sandbox loaded modules can pass library assets to the main application to be added to the display list.

It has a lot of user feedback / developer feedback built in. In the absence of the ability to trace in the air application itself I'm listening for this feedback and displaying it in a textarea in the main application.

Currently the information about what to load and how to load it, and where to find the .zip assets on a server to install modules, is all contained in moduleData.xml inside the applicationFlasAndCode folder. When you start the application you must browse to this file.

To use the framework:
  1. Use ModuleXMLLoader to load an xml document containing your module information.
  2. This creates a strongly typed iterator: ModuleDescriptionIterator.
  3. Instantiate ModuleChainLoader, passing it that iterator.
  4. Listen for the ModuleEvent.MODULE_LOADING_COMPLETE event.
  5. Ask the moduleChainLoader to startLoadingModules().
  6. When you handle the loading complete event, run getModuleDictionary().
  7. Access your modules from the ModuleDictionary using getModule() and getSandboxedModule(). You pass the module's unique name to those functions. They pass back the required module.

Any questions at all, post them here or email me.


How secure is secure?

I'm building an enterprise training application. It already exists in AIR, but we're moving to a modular system because our users don't have the permissions required to run the automatic updates.

Secure, for me, is secure enough not to be the preferred target for a malicious attack. Nothing is impossible, but I want to make it sufficiently difficult / tedious that anyone intending to cause trouble looks elsewhere to do it.


Notes:

In my own final application the xml will be a secure data stream coming from a server.

If you want to keep my xml structure you can use the ModuleXMLLoader class as is, but I've kept it as a separate stage in the process so that you can make changes to this.


Some possible gotchas to avoid:
  • The example module flas have classes which extend ITestableModule. You'll need to point flash to the com folder in order for this interface to be found.
  • Remember that flash keeps signing with the last certificate you used. So when creating good and bad modules, and compiling the app itself, keep an eye on which certificate you're using.
  • Module .air packages need to be renamed .zip
  • You cannot test secure loading in the flash test player. You have to create the application .air file, install it and run it to test it.
  • Don't forget to grab that SignatureUtils.swc from the Adobe link.

30 comments:

Romu said...

Very interesting, I think that's what I was looking for. My main problem is not only updating an AIR app from an online location but do it also from an offline location.

I believe I could create a zip file renamed with a custom extension that will start my AIR app.

This zip-renamed file would hold XML files and SWF files that my app will need to update itself (some kind of chapters).

The Air app would open the zip file (I think that's possible right?), find the XML and then use your classes to load the SWF files and update my AIR app.

What do you think?

Stray said...

Hi Romu,

that does sound like it would work fine. There's no reason why the resource has to be online, except that it's the most likely location.

I'm interested in the scenario you describe though, because if the assets aren't held on an online server then I'm wondering whether there might be an easier solution.

If your assets aren't being updated after the user has installed the air app then you can just include them in the air package itself.

If they are being updated after the user has installed the air app then surely they'd be delivered via some sort of online url originally anyway?

The files only get installed (copied from the online url to the offline folder in app-storage) the first time they're needed. After that they're used locally.

Feel free to write / ask more - I'm not sure I'm entirely clear how you need to use it, but I don't see a technical problem with what you're suggesting.

Romu said...

I'll basically have an AIR app (a main frame) that will have included (or not) some kind of chapter. The chapters will be external SWF files, probably in a chapter folder in the application storage.

The user can buy the app with only one chapter.

So the user can buy other chapters later. I've already made some tests to update the chapters from an online location. But they will be able to get that update from a CD they will receive (and make the update without connection).

My idea was to make a zip file (containing 1, 2 or more chapters and some XML) that I'll rename to a custom extension so it can launch the main AIR app.

The main App would handle that zip file and copy the new chapters to the main APP application storage.

How does it sound?

Stray said...

Sounds neat Romu.

The alternative way around of doing it would be for the user to launch your air app and then browse to the chapters they want to load, or to the xml file which specifies which chapters are on the CD.

You could use the file.url property to specify the resource paths required for the 'ModuleDescription' class - which would point to the CD.

I think what you're describing - the custom file that launches Air, and tells it what to load, could work as well but I don't have any experience of launching Air and passing it data from outside the Air app itself.

That might be straight forward, or it might be more like:

1) Custom launcher writes the an xml to a shared location - for example User Documents...
2) Custom launcher launches the Air app
3) Air app looks for the custom launcher file in User documents - which gives it the paths for install.

Let me know how you get on though - the modular framework should handle almost everything you need without much modification at all.

Romu said...

An important factor is: this offline update will have to be done by people that barely knows how to browse to something, and surely does not know what the hell is an XML file ;)

If yes, I believe I can hold a signature in this file? and use this to trust the content? Maybe it doesn't make sense and this zip need to be an AIR updater app renamed to zip?

That's why I thought about the custom extension, I've never done that though. But I wouldn't have to build an AIR updater app in this case right?

I might make the 2 options possible: update from a CD (double click on a single file) and check CD location from the main AIR app (if possible).

What I need to clear up is: if I renamed a file to a custom extension so it launches my main app to update. Does this zip file need to be an AIR app renamed (as in your samples) or can I just create a normal zip file?

This in a matter of trusted content of course.

Thanks again for the help.

Romu

Stray said...

Only content that is going to be loading into your AIR app needs to be signed, so that's what needs to be a .air package renamed .zip.

I *think* that your initial launcher can be anything you like - if the purpose of it is simply to launch the actual AIR app and pass it some info about the CD contents.

I think it's a good idea to do it both ways though - allow the user to open the AIR app and browse to the CD.

You can filter file type in the AIR file browser, and you can also get a folder listing. Both those things might help to make it even easier for the user to point the AIR app at the correct location to load the data.

Good luck!

Philoo said...

Hi Stray,
Great post! Can we use the source without restrictions?
- Breizo

Stray said...

Absolutely!

Go for it - I just haven't put in the MIT license blah stuff, but yes - it's completely free to use for whatever you like.

Just - if you find any bugs / problems I'd appreciate it if you let me know.

Good luck!

Philoo said...

Hi Stray,
Thanks, hope you enjoyed the Flex version I sent.
There might be one gotcha. If a modules fails verification, it is still written in the app-storage folder and may overwrite a valid one. On the next application launch, the corrupted module will fail again.

Have I missed something?

Stray said...

Hi Philoo - thanks for the flex files - I'll put them up later today.

That's a good catch - there are only limited situations in which it could happen but writing over a good module with a bad one definitely isn't desirable.

It wouldn't be a bad idea to do something clever though with renaming to keep a backup of the original - or keep the unpacked on in a quarantined file til it's verified. I'll add that to my list. Thanks!

Philoo said...

I agree. I think the best way is to download a new module in a temporary folder and then copy in the final directory if the certificate is OK and then always delete the temporary folder.
Also, I realized after working a couple of days on the framework that having a way to check which version is installed vs. which version is on the server is a must. Pretty much like what the Air app updater does. This adds quite some complexity but avoid downloading the modules each time the application is launched. When I tested the original framework, the download of a 600kB modules took >10s (I have a local server...) so after attempts to understand, I resolved to first check the module version and then download only if a newer version is available.

Stray said...

Hey Philoo, now you've got me thinking...

When I'm testing it, it definitely only downloads the xml - then it only grabs modules that are actually missing - so they should already be being detected and skipped if they're present.

However - I've noticed that when testing air, sometimes Flash decides to create a new application storage folder with a long name like myapp-2374839742328493. I expect that what's happening is that your test is creating a new temporary app storage folder with this bizarre name - and of course your previously downloaded modules are in an app-storage directory with a different name. If you actually compile and install the app you should see this stop happening. It definitely only grabs what you need once.

We haven't gone for version control via the xml, instead we're using a naming convention so NavControlModule1_2 becomes NavControlModule1_3 with a new package name. It makes it possible for the tech support guys to instantly see which versions of which modules are installed - but I'm guessing that adding a digit check in there isn't a major job (though may not be necessary given what I said above).

Let me know if your installed app is also downloading the modules multiple times... that would be a problem!

Romu said...

Hi Stray
I sent you an email about an issue about the manifest validation.
Basically it works fine but take way too long because of the _stream.readBYtes, about 20 sec for 30 Mb.
My Main need is to validate that the SWF file of the module is valid, so it won't load it if a third-application is replacing the SWF with another.
Any solution?
Romu

Philoo said...

Stray,
Sorry for the late reply...
You are correct, it only grabs modules if they are not present. Which is good (I want the remote module when I don't have a local one) and bad (if I have a newer remote version, I can't get it because I already have a local one).
In the version I sent to you, I did not include the version check, so the weird Flash folder name must come from the Air runtime. I haven't investigated yet.

McQuillens said...

Stray,

Thanks for this really helpful code!! I'm trying to create a way that an AIR client can download graphic symbol libraries from a server. Your framework really gets me going in the right direction. Thanks for making this available.

One problem I'll still need to solve is how to make sure 1) the client is allowed to download the modules they want and 2) only the client that downloaded the modules can use them -- that way somebody couldn't just start passing the modules around to those who shouldn't have access to the modules.

Have you tackled this issue, and if so, any thoughts?

Thanks again.

Daniel McQuillen
mcquilleninteractive.com

Stray said...

Hi Daniel,

I sort of have dealt with that issue. In our case all the actual module zip files are only loaded after the user has logged in.

Attached to their account is a 'module profile' which is basically a list of which versions of which modules should be loaded - whether they're held locally or need to be downloaded from the server.

The server delivers that xml with all the module data in that the whole system relies on. So - user A shouldn't be able to load user B's modules.

In addition, we have lesson content which we definitely didn't want to be usable outside of the software. The lessons are .swf files.

In theory the lessons can still be decompiled and looked at that way, but at runtime they basically implode unless they're being loaded into the actual e-learning software, or they're being run by a developer, with a registration file on our dev machines.

So - hopefully that gives you some clues?

Stray said...

Flex files finally up (sorry for the delay there). Thanks Philoo!

Anonymous said...

hi,

flex files link is broken, could You fix it? thx in advance.

best

Stray said...

Ah - sorry about that, I must have accidentally dragged the zip with something else. Reinstated now so it should be working again.

Jacob said...

Does the framework support versioning of modules at all? As far as I can tell it doesn't, but I wanted to ask to make sure. What we are looking to do is only pull the module from the remote location if the remote module is of a newer version than the one previously loaded to the users system.

Unknown said...

You'll need to build a database or XML to hold your version numbers. That's what I've done. I handled the download myself, it was a requirement. I'm not sure you can automate that with the air updater.

Romu

Jacob said...

Stray,
When I asked about versioning I had somehow missed your earlier comment about the naming convention you use on modules to handle versions. I kind of like this idea, but my question is what happens to the outdated module files? Do they just sit in application storage forever since they are not being replaced?

Thanks!

Stray said...

@ Jacob - yes, they currently just sit in the app storage directory - for my purposes this is ideal as the IT dept want to know the history, but you could delete them if they're taking up space and it's causing a problem.

Glad you managed to answer your previous question re versioning - I have to say I like the approach we've taken, but it's made more viable by a ruby script that does the renaming and bundling of the modules with their new version numbers, and uploads them to the server. I'll put that up on github with the actual code shortly - let me know if you think you could use it before I do that.

@Soundstep - I just realised you asked a question about validation times a while back... and I didn't answer it. Any joy there?

Unknown said...

I had time problems validating files over 150 Mb, I disabled this part and add other checks.

Beside that everything worked great.

If you're curious, the app was "The Land of Me", we were finalist at the MAX Awards in case you were there.

Thanks again for sharing the code :)

Jacob said...

That would be great if you don't mind posting it.

Thanks for all your great work on this, I would probably be lost without your framework.

Stray said...

Up on github at https://github.com/Stray/Air-Secure-Module-Loading now.

Jacob said...

In the Flex version of the code, in AirResourceUpdateManager.as, the second line of the checkForFile function has a return false before it even checks for the local module swf, therefore it never finds it and always downloads from the server. I just wanted to note that for anyone trying to use the Flex version that if they want to use the local file if it is there they will need to remove that return.

Thanks again for this great resource.

Adrian said...

Hi Stray

first of all, thanks for this great part of software. It works really well. Now, communication from the host application to the module app can be done using a shared Interface.

But I am interested in communication from the module to the host application. So assume I have a property called "sessionId" in the main application. How can a loaded module access this property?

Thanks for your answer.

Stray said...

Hi Adrian,

just in the normal way - I'd expose a method for the shell to use to set it in the module.

If you want the module to pull it from the shell then pass a function that the module can use to get it instead.

I actually use the robotlegs framework for everything I build now - the modular version allows you to pass shared data around via a shared event dispatcher.

It integrates with this framework really nicely.

Unknown said...

Stray, absolutely great work!! Very interesting and usefull stuff.

Hope to have some free time to test it and try to integrate in a project that we are working on.

Thanks a lot for your contributions!