An attempt at secure AMD: caution.js

171 views
Skip to first unread message

Geraint

unread,
Nov 10, 2014, 9:54:04 AM11/10/14
to amd-im...@googlegroups.com
Hello, AMD people. :)

OK, so it's super-rough and incomplete at the moment (I only started it on Friday), but I took a swing at writing a secure AMD module loader.


The idea is that you provide your security criteria (default is a whitelisted set of known-safe SHA256 hash values):
caution.addSafe(['8208dd7d61227d3c...', ...]);
caution.addSafe(function (code, hash) {
    return hasValidSignature(text);
});
You supply some information on where to find the modules:
caution.addUrls({'some-module': 'http://my-site/js/some-module.js'});
caution.addUrls(function (moduleName, versions) {
    return [...];
});
And once you've done that, you can give it the go-ahead to start loading missing modules of its own accord:
caution.missingModules(function (moduleName) {
    caution.load(moduleName);
});

It can also generate inline JS code or HTML-page data: URLs that can bootstrap the loading of an initial set of modules using SHA256 verification:
var config = {
    load: {
        'caution': ['a675f11c3c85cc...', ...],
        'my-initial-module': ['8208dd7d61227d3c...']
    }
}; 
var inlineJs = caution.inlineJs(config);
var dataUrl = caution.dataUrl(config);

The inline JS is currently coming out around 2.3K long, so generated data URLs are coming out around 3K.  The idea is that if a user bookmarks the data URL (or just saves it as an HTML file) then they have a tamper-proof version of that page.  One of the next steps is hopefully to provide a prompt such that if the resources disappear/change at all known locations, users can provide new search locations without breaking security.

It's early work, so tips and feedback (or bugs! or code!) are of course very appreciated. :)  It's pretty hole-ridden at the moment, and I know doing a few things wrong (e.g. my define() function acts more like require(), because that was easier), but I'd love to get as far as something that can be used with actual existing AMD code.

Geraint

C Snover

unread,
Nov 10, 2014, 11:21:42 PM11/10/14
to amd-im...@googlegroups.com
I haven’t looked with significant depth at everything but here is some feedback:

My TL;DR version is, unfortunately, “this is a solved problem and this approach is not the right way to do it”, but I do want to commend you for looking at things through the lens of security. By doing so you are ahead of the majority of software developers. :)

For performance, you should not be using third-party CDNs and should build all your code + deps into a single file at production time. Coincidentally, doing this eliminates any need to try to hash and discover “known good” versions of dependencies from untrusted parties since the source code is guaranteed to be correct. (If your build/deploy part is not secure, nothing else matters, since someone could just modify your “known good” hash set, or break your copy of caution.js, or anything else.)

For security against tampering in transit, you simply need to transmit using HTTPS with HSTS. If you do this, barring breakthroughs in crypto or a major CA compromise, the code you are transmitting is secure from the server, so again there is no reason to try to hash on the client to verify the code is good—the secure transport layer ensures that it is good. If you do not transmit over HTTPS, nothing you do will prevent tampering, since your “known good” hash set can just be changed again (or the verification function changed to always return `true` or any other thing).

To guarantee someone is loading the “right” version of code given only a saved HTML file, well, first of all good luck not having a totally broken page when someone tries to do this due to `file` protocol restrictions :), but also you can just generate versioned URLs (most AMD loaders let you do this easily with a `cacheBust` property or similar) and either maintain those files or output new code that notifies users that things are out-of-date when a newer version is available on the server. (While I won’t say it’s not a valid use case, in 15 years I can’t recall ever hearing something like this, so it sounds pretty specific to whatever you’ve been working on.)

With regards to the implementation, which I briefly glossed over, one serious and unresolvable problem stands out. This script verification approach can only work by loading the code first as data via XHR and then manually evaluating the string after it has been hashed. This means:

1. This code won’t work for browsers without XHR2 (probably OK),
2. This code won’t work for CDNs without permissive CORS policies (maybe OK),
3. You’ve broken the ability of sites to enable CSP on the domain that loads caution.js (not OK).

Since we have already discussed how to easily solve the issue of code verification via the transport layer, using caution.js actually reduces the ability for sites to secure themselves since they will be unable to enable CSP. And that is a fatal problem for a library that is supposed to improve security.

Best,

Jesse Hallett

unread,
Nov 11, 2014, 3:30:04 PM11/11/14
to amd-im...@googlegroups.com
I think that this is very interesting.  A problem with web apps comes up when security requirements call for an app to be audited.  Since web apps are loaded over the internet every time the app is opened, it is difficult for users to know that the code they are running is the same code that they audited.  Or perhaps users just do not want to have to trust that the code on the app server has not been tampered with.  (Think of the Lavabit incident, for instance.)  Putting the bootstrapping code and hashes into a bookmark seems like a novel way to assure users that they are getting the code that they expect every time.

As C Snover points out, this may not be the best approach for general cases where performance is very important, and where security requirements are lower.  But I disagree that other approaches are adequate for providing the security properties that caution.js appears to be intended to address.

Rawld Gill

unread,
Nov 11, 2014, 11:30:48 PM11/11/14
to amd-im...@googlegroups.com
> But I disagree that other approaches are adequate for providing the
> security properties that caution.js appears to be intended to address.

Snover is right. If you disagree, then offer some proof of your proposition.

Geraint

unread,
Nov 12, 2014, 5:31:18 AM11/12/14
to amd-im...@googlegroups.com
Hi - thanks for taking the time to reply! I appreciate it.

Looking back, I didn't explain my motivation at all (and just calling it "secure" isn't particularly descriptive), which definitely makes it harder to see where I'm coming from. :)

I'll tackle some things inline below, but the crux of it is this: you talked about "security against tampering in transit", which is indeed tackled by using a secure connection.  However, I think it's more likely for a server to be compromised than a secure connection.  I think you can protect against that using offline storage (file or data: URL).

An example might be a secure messaging app.  Say I visit a site and create a public-key based "vault" which I can decode, but others can post to, where the server stores the messages in encrypted form.  If the server can change the JS, then it can provide a broken client that posts messages in the clear, or uploads my private key once I've unlocked it.  However, if the user has the ability to watch for server changes, then the server could of course remove the service

On Tuesday, November 11, 2014 4:21:42 AM UTC, C Snover wrote:
I haven’t looked with significant depth at everything but here is some feedback:

My TL;DR version is, unfortunately, “this is a solved problem and this approach is not the right way to do it”, but I do want to commend you for looking at things through the lens of security. By doing so you are ahead of the majority of software developers. :)

Yeah - in various places, there's a scary amount of "we'll add security later if the idea gets bigger".

This is a recreational tin-foil-hat idea, and although it looks like I'm doing something fairly stupid on the face of it, I think I'm aiming for something a bit different (and slightly weirder) than my previous summary implied.
 
For performance, you should not be using third-party CDNs and should build all your code + deps into a single file at production time. Coincidentally, doing this eliminates any need to try to hash and discover “known good” versions of dependencies from untrusted parties since the source code is guaranteed to be correct. (If your build/deploy part is not secure, nothing else matters, since someone could just modify your “known good” hash set, or break your copy of caution.js, or anything else.)

An interesting thing to talk about here is Mega's secureboot.js - while they messed up initially by using a plain MAC instead of a strong hash function (!), there are interesting ideas in there.  The initial set of resources are loaded over strong (2048-bit RSA) HTTPS from a central server, whereas the remaining resources are served via lower-security connections to off-site CDNs (while they still use HTTPS, it's a weaker configuration).

The apparent advantage for Mega's setup is that they can run a smaller set of high-security servers, while pushing the processing power onto less secure ones, and pushing the verification into the JavaScript app.  I have no idea whether that works out better for them or not, but it might well do, and it's certainly cool. :p

Another thing I'm thinking about is fallback options.  I could bundle all my code into once JS file and verify the hash of that (in fact, that's exactly what my first attempt in this area did), but if my server becomes unavailable for some reason, it's unlikely that anybody else will be mirroring that exact code.  However, if I pull in a whole bunch of libraries that make my application logic really small (small enough to include inline, maybe?) then the app could still work by using public CDNs, depending of course on what API servers it relies on.
 
For security against tampering in transit, you simply need to transmit using HTTPS with HSTS. If you do this, barring breakthroughs in crypto or a major CA compromise, the code you are transmitting is secure from the server, so again there is no reason to try to hash on the client to verify the code is good—the secure transport layer ensures that it is good. If you do not transmit over HTTPS, nothing you do will prevent tampering, since your “known good” hash set can just be changed again (or the verification function changed to always return `true` or any other thing).

This is the main point.  I think it's more likely for a server to be compromised than the connection, and that's what the goal is to protect against here, sensible or not. :p
 
To guarantee someone is loading the “right” version of code given only a saved HTML file, well, first of all good luck not having a totally broken page when someone tries to do this due to `file` protocol restrictions :), but also you can just generate versioned URLs (most AMD loaders let you do this easily with a `cacheBust` property or similar) and either maintain those files or output new code that notifies users that things are out-of-date when a newer version is available on the server. (While I won’t say it’s not a valid use case, in 15 years I can’t recall ever hearing something like this, so it sounds pretty specific to whatever you’ve been working on.)

Slightly-related point: the browser I see mentioned when people talk about file:// URLs and AJAX is Chrome (which often seems to have decent security assumptions).  However, as far as I've seen, Chrome blocks AJAX requests *to* file:// URLs (as well it should!), but it doesn't seem to have any issues fetching HTTP resources *from* a file URL (or data: URLs).
 
With regards to the implementation, which I briefly glossed over, one serious and unresolvable problem stands out. This script verification approach can only work by loading the code first as data via XHR and then manually evaluating the string after it has been hashed. This means:

1. This code won’t work for browsers without XHR2 (probably OK),
2. This code won’t work for CDNs without permissive CORS policies (maybe OK),
3. You’ve broken the ability of sites to enable CSP on the domain that loads caution.js (not OK).

I'm actually expecting to fetch caution.js from a CDN as well.  The "inline code" (including sha256) that can load and verify modules in a limited fashion should be small enough when minified to serve in the HTML itself, inside a <script> tag.  This means you only need one secure resource, the HTML file.

In fact, the interesting one for me is to represent this HTML file as a data: URL.  Security-wise, this is similar to downloading the HTML file, only that the local copy of the page lives in your browser's bookmarks instead of as a file.

Since we have already discussed how to easily solve the issue of code verification via the transport layer, using caution.js actually reduces the ability for sites to secure themselves since they will be unable to enable CSP. And that is a fatal problem for a library that is supposed to improve security.

Hopefully my reasoning's a bit clearer now. :)  Sorry for being misleading.

Geraint 

Geraint

unread,
Nov 12, 2014, 5:37:14 AM11/12/14
to amd-im...@googlegroups.com
On Wednesday, November 12, 2014 10:31:18 AM UTC, Geraint wrote:

An example might be a secure messaging app.  Say I visit a site and create a public-key based "vault" which I can decode, but others can post to, where the server stores the messages in encrypted form.  If the server can change the JS, then it can provide a broken client that posts messages in the clear, or uploads my private key once I've unlocked it.  However, if the user has the ability to watch for server changes, then the server could of course remove the service

Sorry, 206 Partial Content there. :p

 If the user has the ability to watch for server changes, then the server could of course remove the service, but it couldn't break the security without anybody noticing.  This is what I meant by "tamper-proof" - I guess a more accurate description would be tamper-evident.
 
G

Guy Bedford

unread,
Nov 13, 2014, 4:54:09 AM11/13/14
to amd-im...@googlegroups.com
Geraint - I'm really interested in this from the perspective of security for the new browser loader spec.

You see, the browser loader directly handles sources and can be extended to do checks like this. The previous issues of XHR, CORS, etc melt away when dealing with the spec loader as well.

It could be really interesting to see this work as an extension of the module loader (https://github.com/ModuleLoader/es6-module-loader / https://github.com/ModuleLoader/es6-module-loader/wiki/Extending-the-ES6-Loader).

--
You received this message because you are subscribed to the Google Groups "amd-implement" group.
To unsubscribe from this group and stop receiving emails from it, send an email to amd-implemen...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Geraint

unread,
Nov 13, 2014, 6:33:50 AM11/13/14
to amd-im...@googlegroups.com
Sounds good, definitely.  I think there are two things I'd like to see:
* an AMD/CommonJS module that allowed caution.js to load ES6 modules (based on syntax translation)
* an ES6 module that hooked verification logic and fallback locations (perhaps even based on caution.js itself) into the ES6 module loader

Both of these seem very possible.

I was already thinking about adding CommonJS support (e.g. scanning the source for require('...') clauses like Browserify does) as a module - given that, the first one is a logical extension.

For the second one, you have to make sure the security checks are added before anything.  This makes sense, though - it would go in the "inline code", and the full module could hook into this initial seed, the same way caution.js currently does.

Geraint

P.S. - about XHR/CORS: I haven't read into it at all, but I'd assumed that cross-origin constraints would also apply to ES6 modules, given that there's a source-transform step that would allow you to inspect remote content.  I could be completely off-base there, though.
Reply all
Reply to author
Forward
0 new messages