Posts in Category "design decisions"

Platform Parity and Scalability

Confession: We’ve made a bit of a mess for ourselves in the application descriptor.

(For those not familiar with the application descriptor, it’s a short XML document that’s a required part of any AIR application. It provides essential information like the application’s unique ID and version, plus a variety of optional settings covering everything from screen orientation behavior to application marketplace filtering. It’s roughly analogous to Info.plist on Mac OS and iOS, and AndroidManifest.xml on Android.)

In AIR 1.0, we strived to keep this descriptor purely cross-platform. We almost achieved that goal, but compromised a bit with the <programMenuFolder> setting. That setting allows an AIR app to control, on Windows, where it appears in the Start Menu. Customers told us it was essential.

When we added iOS support in AIR 2.0, we realized that there was a host of options, accessible via the iOS Info.plist file, that we wanted developers to have access to and yet didn’t have any cross-platform analogue. That was hardly surprising at the time, since iOS was the first mobile platform we supported.

So, we decided to add an escape hatch: the <iPhone> element. (Why not <iOS>? Because this was before iPhone OS became iOS.) It contains iOS-specific settings, including arbitrary additions to the Info.plist file.

When we added Android support, we extended this in the obvious way, adding an <android> element with similar capabilities.

Now, about this time was when we realized we had a problem. Our list of supported platforms is continuing to grow, but adding new elements to the descriptor for each one isn’t a scalable approach. In particular, it requires that Adobe modify the descriptor schema each time a new platform comes online, rather than enabling our platform partners to make these additions on their own. Our partners don’t want to have to wait for us for such a change, and we don’t want to have to make them wait, either.

As we worked with RIM to bring AIR to the PlayBook, we asked RIM to help us fix this and store PlayBook-specific settings in a separate file, outside the application descriptor. This approach is easily scalable, as it’s trivial for each platform to add its own file. And it’s easier to use then open-ended extensions in the application descriptor itself, which can get tricky when storing XML in one schema inside XML in another schema.

At the moment, the unfortunate result of this mess is that PlayBook might appear to be a second-class citizen, in that it doesn’t get its own element in the descriptor. This is not at all the case. On the contrary, PlayBook is the first platform to move to our preferred mechanism. It’s iOS and Android that are stuck with the older, more awkward mechanism.

Although I can’t speak to the timing, as it’s not yet determined, we will be moving platform-specific settings for all platforms, including iOS and Android, to external files in the future. Then we’ll finally be where we should have been heading from the beginning: Parity between platforms, in a scalable fashion.

Approaches to Modular AIR Applications

As applications grow in size and complexity, it becomes necessary to break them up into manageable pieces. To some degree this can be handled at development time by maintaining a carefully-structured source code base.

Compiling the application into a single, monolithic deliverable may itself become a roadblock. For example, it may be desirable instead to compile and validate these pieces separately, and then assemble the resulting binaries. The pieces may be developed and shipped on different schedules, and thus different portions of the application updated independently. Or, the pieces may even be developed by other parties, as is often the case for applications supporting plugins. Regardless, it often becomes necessary to defer loading some of these pieces until runtime.

AIR currently supports two basic approaches for this kind of runtime assembly: Loading code into a network sandbox, and loading code into the application sandbox. The two approaches have different capabilities and different limitations. When designing large applications, it’s good to be aware of these trade-offs.

Network Sandbox

Code is loaded into a network sandbox via the Loader.load() API by specifying the URL of the target code. Code in this sandbox is restricted from accessing AIR-specific APIs, but has access to the same set of APIs that content running in Flash Player can access. The network sandbox essentially is Flash Player.

Interestingly, by restricting the API to the Flash Player API, the loaded content can necessarily be run in Flash Player or AIR. This may be especially useful if your application runs sometimes in the browser and sometimes in an application.

The API restrictions also mean that you don’t have to trust the code you load. The network sandbox prevent access to potentially dangerous APIs, such as the filesystem API. You can selectively open up access via the sandbox bridge capability.

Because the runtime knows where the code was loaded from, the code can use relative addressing to access other network resources, including additional network-hosted code. This allows for such code to easily be moved between different web servers, including between test and production environments.

The network sandbox is typically not suitable when the code your loading is supposed to function as an integral part of the application, with full access to other parts of the application and to the AIR APIs. For that, you want the application sandbox.

Application Sandbox

Code is loaded into the application sandbox via the Loader.loadBytes() API. Code in this sandbox runs as if it were installed along with your application, having full access to all of the AIR APIs. This facility provides the basic underpinnings for a complete plugin model. You can use that model to modularize your own application, or even open it up to third-party components.

Code loaded via this method is granted full API access, so it is essential that the application validates that it trusts the code before loading it. This can be accomplished in a secure fashion by using code signing, and validating the signature before loading the code. It may also be sufficient to obtain the code via a secure URL, but it should be noted that this protects the code only while in transit, and not after it is stored locally. To encourage secure use of this API, it loads the code, as the method names suggests, directly from a ByteArray and not from a URL.

Code loaded via this API does not retain its origin (URL), and therefore cannot access other code using relative URLs. Again, this was done to encourage secure use of the API. If this code can implicitly load additional code off the network, it is not sufficient to validate just the first SWF; each link of that chain requires careful validation.

Note that, because the origin of the code is removed, you can’t combine granting access to the application sandbox with the deployment ease of loading a SWF and associated RSLs from the network. If one module depends on another, some other referencing mechanism must be used.

Futures

It’s easy to imagine extending these models in a variety of ways. For example, while it is currently possible to create a plugin mechanism for an AIR application, the validation work has to be done by the application. This in turn prevents it from integrating with other runtime features, like RSLs. By expanding runtime support, we could potentially allow these features to work together.

We are actively exploring how AIR applications are using these techniques today, and how we might enhance our support for building large applications in the future. If you have feedback in this area, please let us know via a comment or via ideas.adobe.com/air.

References

For more in this area, you might want to read:

Certificate Support in AIR for Linux

In an earlier post I explained how to use TLS client authentication for AIR applications on Windows and Mac OS. Commenter Arlen asked how to do the same on Linux; unfortunately, TLS client authentication is not supported in AIR for Linux.

The first problem is that, unlike Windows and Mac OS, Linux doesn’t have a standardized, easily accessible certificate store available. Instead, AIR bundles its own certificate stores. (See this Adobe knowledge base article for information about managing those certificate stores.) Other Linux applications typically do the same. Even if client authentication was supported, it would have be configured separately for AIR applications versus other applications, thus making it much less useful than on Windows or Mac OS.

The second problem is that Linux doesn’t have a standardized, easily accessible HTTP stack that supports TLS client authentication—instead, applications have to bundle their own implementation. That, of course, doesn’t make it impossible for AIR to add this support, but it means it requires a non-trivial engineering investment.

To date, these two issues have kept us from adding TLS client authentication support on Linux. If you’d like to see it added, I encourage you to vote for it on the Adobe AIR Ideas site.

LocalConnection.isPerUser in AIR 1.5.2

New to the AIR 1.5.2 release (and the corresponding Flash Player, 10.0.32) is the LocalConnection.isPerUser property. Note that you’ll need to update your application’s namespace to …/1.5.2 to access this property. Here’s why you should do that.

LocalConnection provides local (i.e., on the same machine) communication between SWFs and AIR applications. It operates via a shared memory segment that’s visible to all processes that use the mechanism.When LocalConnection was first implemented on Mac OS, it used a memory segment that is visible to all processes running on the machine. This was reasonable at the time, but problematic now that Mac OS is a multi-user operating system. The unfortunate result is that LocalConnection can be used to communicate across user accounts on Mac OS.

To address this a new, per-user implementation has been implemented on Mac OS. You should always use this mode; it’s safer. To do that, set LocalConnection.isPerUser = true on every LocalConnection object you create.

Unfortunately, AIR can’t do this for you transparently. The problem is that, if it did, you could get into a situation where version skew breaks use of LocalConnection. For example, this can occur if an application is running on AIR 1.5.2 and attempts to communicate with a SWF in the browser running on Flash Player 9. Until both sides are updated, there’s no way to use the isPerUser = true option. By adding an API and making this an option, we’ve given you a chance to migrate to this option without breaking anything along the way.

This issue is specific to Mac OS. Windows and Linux use a user-scoped LocalConnection in all cases, regardless of the isPerUser setting. You can safely set LocalConnection.isPerUser = true everywhere and be confident that the Windows and Linux behavior won’t change.

Final note: The default setting of this property is likely to change to true in a future release, in order to be consistent with our general philosophy of defaulting to safe behavior.

Pauses When Using ELS and DRM APIs

Lately I’ve fielded a couple of different queries about long pauses in applications using the EncryptedLocalStore (ELS) and DRM capabilities in Adobe AIR. Two questions on the same topic in one week is usually a good indication that some additional explanation is required, so here it is.

As you are probably aware, AIR applications are protected during deployment by digital signatures. These signatures are checked at installation time in order to verify that the application has not been tampered with and, when possible, to reliably display the application’s publisher.

The signatures are preserved by the installation process but are not normally checked when an application is running. However, there are two exceptions to this: the signature is validated when the application uses the ELS or DRM APIs. This is done to prevent attacks on the application’s protected data that operate by modifying the application itself–any such modification would invalidate the signature.

When we designed this mechanism, we targeted applications in the 1 MB to 10 MB size range. This is important because checking the signature requires computing hashes over the entire application. For these sizes we determined that we could compute hashes over the entire application without significant delay, and so we went with the straightforward implementation that does just that. Larger applications, however–and I’ve seen examples of applications over 1 GB in size–will suffer painful delays during these signature validation pauses.

Our current recommendation is that you avoid making applications this big. That may sound trite, but every large application I’ve seen so far was that big because it included assets, such as videos, that consumed the majority of the space. Moving these kinds of non-code-containing assets out of the application itself–for example, downloading them separately into the application storage directory–is a straightforward way to reduce the application to a tractable size.

For completeness, I’ll note that it is possible to design a validation mechanism that works incrementally. For example, Mac OS uses a clever scheme that hashes each page of the executable separately; the kernel can then amortize validation cost across each page as it is first referenced. (If the page is never referenced, it doesn’t matter if it has been modified.) At this time, however, we have no plans to adopt such a scheme for AIR.

One final note: This signature validation usually occurs just once, the first time either the ELS or DRM capabilities are used. However, if you set “stronglyBound” to true when using the ELS API, signature validation will occur on every access. I don’t recommend using this stronglyBound feature, for this and other reasons.

Should AIR Support Application Installs Without Admin Rights?

In my previous post, I explained that installing an AIR application sometimes requires admin rights. This begs the question: Should AIR take pains to avoid those parts of application install that sometimes require admin rights? Some applications do support this, including recently Google Chrome.

We considered this when designing the AIR install experience and ultimately decided this would be a mis-feature. It breaks down to two cases:

  1. You’re the admin for the machine on which you’re installing software. (This is the typical consumer scenario.) You don’t need to install without admin rights because you’ve got them.
  2. You’re not the admin, and you don’t have admin rights. You want a non-admin-rights install because otherwise you can’t install the application. (This is a typical enterprise scenario.)

In this second case, however, your machine is locked down for a reason: the admin doesn’t want you to install anything. If they knew you were avoiding this restriction by installing without admin rights, they’d probably close that loophole, too. See, for example, this article about stopping users from installing Google Chrome.

So any specific support in AIR for installing applications without admin rights would be temporary at best, as admins could still prevent it. Worse, it makes extra works for admins who have to jump through hoops to keep their machines locked down. Since we’re interested in making sure AIR stays friendly for enterprise deployments, and this feature has no value for non-enterprises, well, it just doesn’t seem to make much sense.

Changes to HTMLLoader.loadString() in AIR 1.5

For AIR applications bound to version 1.5 and later, the default behavior of the method HTMLLoader.loadString() has changed. This may impact your application; here’s the how and why of the change. (For Flex users, the following discussion also applies to setting the HTML.htmlText property.)

AIR applications are desktop applications and, consistent with that fact, AIR does not prevent you from doing dangerous things like fetching remote content and running it locally. However, it’s rare that such things are desirable and, when they are, they should be done explicitly and carefully. AIR APIs are therefore generally designed to make doing safe things easy and dangerous things hard.

Prior to the AIR 1.5 behavior, HTML content loaded via HTMLLoader.loadString() was placed in the application sandbox. This content has full access to the local machine. Whether or not this is reasonable depends on where you get the string that you load from and what that string contains. Since it’s easy for that string to come from untrusted sources, this provides an easy path for injecting untrusted code into your application.

To address this, starting in AIR 1.5, loadString() defaults to loading content into the browser sandbox. This is the safe thing to do since untrusted code, when running in the browser sandbox, is unable to operate with the same permissions as are otherwise granted to your application.

If you want the old behavior, you can set HTMLLoader.placeLoadStringContentInApplicationSandbox = true. If you do this, remember that you are taking on responsibility for dynamically loading code into the application sandbox and all of the risks that entails. Ethan Malasky’s blog has some great posts in this area. Still, I don’t generally recommend it—it’s hard to get right.

One final note: This change will not break your existing applications. The new behavior is bound to the namespace used in your application descriptor file. Applications using the 1.0 or 1.1 namespaces will operate as before. You will have to take this change into account when you update to the 1.5 (or later) namespace.

Why AIR Applications Auto-Exit on Mac OS

A user on the Adobe AIR forums recently requested that we change the default behavior of AIR applications running on Mac OS so that they don’t exit when the last window is closed. This changed behavior would, of course, be consistent with the majority of Mac OS applications.

Contrary to what you might suspect, the current behavior was not an oversight on our part; nor was it a result of porting a Windows design to Mac OS. (We have several individuals on our team with significant Mac OS experience and I assure you they wouldn’t stand for that kind of thing.)

The decision makes more sense if you think about it in light of some observations about actual AIR application development:

  • Although the default behavior of Windows applications is to exit when all windows are closed, many developers want to write Windows applications that keep running in this case.
  • Although the default behavior of Mac OS applications is to keep running when all windows are closed, many developers want to write Mac OS applications that exit in this case.
  • An application running without windows open is often a stumbling block for developers coming up to speed on the platform because, when they try to launch the application again, nothing happens by default.

From this one can, I claim, reasonably conclude that defaulting to platform-specific behavior in this case is not worthwhile: most applications either want to keep running on all platforms or exit on all platforms. Second, to ease that developer stumbling block, the default had better be to exit. Thus, NativeApplication.autoExit defaults to “true” on Mac OS, Windows, and Linux.

Any chance we’ll change this in a future release? Not likely. Although patterns of development change, to insure forward compatibility we’d have to keep the current behavior for old applications and introduce the new behavior only for new applications. Even just the switch would introduce yet another thing for developers to learn—and another potential stumbling block. In practice the cost of changing an API’s behavior is quite high, and I don’t think this change would make the cut.

AIR API Tip: Don’t write to File.applicationDirectory

As a number of AIR developers have discovered, it’s hard—but not impossible—to write files in the application’s install directory. That’s the same location given by File.applicationDirectory.

Granted, it’s often a tempting thing to do. For example, many applications include a database or configuration file with some pre-filled data. Once installed, they often want to update that file, say with user-specific data or preferences.

The first problem with this is that it’s not reliable. On some operating systems—Vista, for example—the installation directory is protected by the operating system. Even if AIR lets you write to this location Vista won’t. So if you want to write portable applications, don’t do this.

The second problem with this is that it’s not safe. Any code written into this directory runs with application privilege, which can compromise your application, which can compromise the user’s machine. That’s why Vista protects these directories.

The third problem is that it invalidates your application’s signature. Among other things, that means you won’t have access to the encrypted local store any more.

It’s also unnecessary. A safe and allowable alternative is to write somewhere into the user’s directory. If you still want a starter file, it’s easy to copy one from your install directory to the per-user location. Note that this also avoid problems if there are multiple users on the same machine.

In order to help developers avoid this pattern, AIR will prevent write access to this directory in most cases, and even when the underlying operating system allows it. There are currently ways around this, as it’s advisory—not mandatory. However, the ability to go around this restriction is often misinterpreted as a defect, and we may close this off in a future release. You’ve been warned.

Why AIR Doesn’t Ask for Permission

Commenter Tek suggests that AIR should warn the user each time an application tries to do something potentially dangerous, like access the file system.

Here’s just a few reasons why we don’t do this:

  • It’s annoying. No one likes to deal with these dialogs all the time. And if everyone just suppresses them, then what’s the point?
  • It’s insufficient. Accessing the file system alone or the network alone is often ok, but approving the combination is sufficient to let someone upload all your data.
  • It’s unanswerable. Unless you wrote the application or studied its source code, you probably don’t have enough information to answer, anyway.
  • It’s broken. If you say no to any of these dialogs, the application will most likely stop working.

Ultimately, choosing to run a desktop application is about trust. Virus scanners, firewalls, and the like don’t ask you about what applications are doing because they’re trying to protect you from those applications. On the contrary, they’re notifying you about behavior that might be the result of software you didn’t install. That’s an altogether different question: if you didn’t install the software, say no!