VerifyJS demo <--- Drag to bookmarks toolbar

2012-08-02

This is a proof-of-concept for code signing and loading in Javascript using bookmarklets that link to data:text/html URIs. I was inspired by @kaepora's Cryptocat and the resulting discussions with @moxie and @ioerror whether it's possible to deploy secure apps in the browser.

There are many obstacles to such, but the big one is this: Every time you open the app, it is effectively reinstalling fresh from the server. Even if you deliver over HTTPS, this means your app can be no more secure than HTTPS. And a server compromise will also compromise your users by allowing an attacker to deliver malicious code.

Native apps improve on this situation by holding their code signing keys offline, and having discrete, numbered releases that can at least in theory be audited individually.

VerifyJS takes a similar approach. You save a bookmarklet once, and afterwards always access the app via bookmarklet. The bookmarklet contains an ECDSA implementation borrowed from Cryptocat. It loads an iframe on a provider host, and that iframe uses window.postMessage to send a signed JS payload. The bookmarklet only evaluates the payload if the signature validates according to the public key that is baked into it. At this point the evaluated JS takes over the page context to do with what it likes.

This means that you only have to trust the network at one point: when you install the bookmarklet. After that the provider host is completely untrusted, but it is still possible for the author to deploy new versions of their app, so long as they sign it.

It's possible to build complex apps this way. HTML and CSS can be compiled into JS strings to be inserted in the page dynamically. Images can be compiled into data: URIs. It is important to remember, of course, that you must bundle all of your dependencies into the signed JS.

This approach works on (at least) the latest versions of Chrome, Firefox, and Safari. It does not work on Internet Explorer.

Caveat: Since the app is hosted on a data: URI, it cannot store data in cookies or localStorage. User-specific information (like keypairs) can be baked into the bookmarklet at install time, but cannot be changed afterwards.

Caveat: All communication with remote origin must be handled using an iframe and postMessage. The host document isn't on any origin so can't use XmlHTTPRequest, and JSONP is unsafe.

Rejected approach: Using HTML5 appcache to cache the app's contents offline and never update them except with signed code. This doesn't work because appcache contents can get reloaded any time the cache manifest changes, which leaves you with the same old server vulnerabilities. The cache manifest must be same-origin with the app page, so the manifest URL cannot be a data: URI.

Below is a textarea containing the exact code the will be inserted into your bookmarklet. It's live, so if you want to make tweaks and try them out, just edit inline and drag the link above onto your bookmarks toolbar. See also the signer and provider pages.

Feedback welcome. I'm @j4cob on Twitter. Or comment on github.

Update May 2014: Here's the Twitter thread that resulted. Note: The crypto code below was taken from an old version of CryptoCat. Make sure to use up-to-date and valid crypto code.