Improving the Trustworthiness of JavaScript on the Web

blog.cloudflare.com

62 points

doomrobo

a day ago


34 comments

vader1 a day ago

> This is because app stores do a lot of heavy lifting to provide security for the app ecosystem. Specifically, they provide integrity, ensuring that apps being delivered are not tampered with, consistency, ensuring all users get the same app, and transparency, ensuring that the record of versions of an app is truthful and publicly visible.

The Google Play Store does none of this, lol. All apps created since 2021 have to make use of Google Play App Signing, which means Google holds the keys used to sign the app. They leverage this to include stuff like their Play Integrity in the builds that are served. The Android App Bundle format means that completely different versions of the app are delivered depending on the type of device, locale, etc. There is 0 transparency about this for the end-user.

jmull a day ago

It would be helpful if they included a problem statement of some sort.

I don't know what problem this solves.

While I could possibly read all this and deduce what it's for, I probably won't... (the stated premise of this, "It is as true today as it was in 2011 that Javascript cryptography is Considered Harmful." is not true.)

  • miloignis a day ago

    For me, the key problem being solved here is to have reasonably trustworthy web implementations of end-to-end-encrypted (E2EE) messaging.

    The classic problem with E2EE messaging on the web is that the point of E2EE is that you don't have to trust the server not to read your messages, but if you're using a web client you have to trust the server to serve you JS that won't just send the plain text of your messages to the admin.

    The properties of the web really exacerbate this problem, as you can serve every visitor to your site a different version of the app based on their IP, geolocation, tracking cookies, whatever. (Whereas with a mobile app everyone gets the same version you submitted to the app store).

    With this proposed system, we could actually have really trustworthy E2EE messaging apps on the web, which would be huge.

    (BTW, I do think E2EE web apps still have their place currently, if you trust the server to not be malicious (say, you or a trusted friend runs it), and you're protecting from accidental disclosure)

    • jmull a day ago

      It doesn't seem like there's much difference in the trust model between E2EE web apps and App Store apps. Either way the publisher controls the code and you essentially decide whether to trust the publisher or not.

      Perhaps there's something here that affects that dynamic, but I don't know what it is. It would help this effort to point out what that is.

      • fabrice_d a day ago
        2 more

        On the web, if your server is compromised it's game over, even if the publisher is not malicious. In app stores, you have some guarantee that the code that ends up on your device is what the publisher intended to ship (basically signed packages). On the web it's currently impossible to bootstrap the integrity verification with just SRI.

        This proposal aims at providing the same guarantees for web apps, without resorting to signed packages on the web (ie. not the same mechanism that FirefoxOS or ChromeOS apps used). It's competing with the IWA proposal from Google, which is a good thing.

        • netdevphoenix 14 hours ago

          > On the web, if your server is compromised it's game over, even if the publisher is not malicious. In app stores, you have some guarantee that the code that ends up on your device is what the publisher intended to ship (basically signed packages)

          Not quite. It is possible for an account to be taken over or bought and a new update deployed. It is also possible for the server the app gets its data from to be taken over just like in your example and serve you fake data to make you regurgitate whatever data the malicious actor wants

      • miloignis 11 hours ago

        Sorry, I should have been more concise - the two big differences are:

        1) everyone gets the same code

        2) it doesn't change too quickly

        This means you can (esp with reproducible builds) audit that the code is correct and know that everyone is getting the correct code, and that misbehavior will be identified.

    • knowitnone3 a day ago

      everyone gets the same version that sends your secure messages to another server? I'm impressed.

  • CharlesW a day ago

    > I don't know what problem this solves.

    This allows you to validate that "what you sent is what they got", meaning that the code and assets the user's browser executes are exactly what you intended to publish.

    So, this gives web apps and PWAs some of the same guarantees of native app stores, making them more trustworthy for security-sensitive use cases.

thadt a day ago

Starts reading: "fantastic, this is what we've been needing! But... where is code signing?"

> One problem that WAICT doesn’t solve is that of provenance: where did the code the user is running come from, precisely?

> ...

> The folks at the Freedom of Press Foundation (FPF) have built a solution to this, called WEBCAT. ... Users with the WEBCAT plugin can...

A plugin. Sigh.

Fancy, deep transparency logs that track every asset bundle deployed are good. I like logging - this is very cool. But this is not the first thing we need.

The first thing we need, is to be able to host a public signing key somewhere that browsers can get and automatically signature verify the root hash served up in that integrity manifest. Then point a tiny boring transparency log at _that_. That's the thing I really, really care about for non-equivocation. That's the piece that lets me host my site on Cloudflare pages (or Vercel, or Fly.io, or Joe's Quick and Dirty Hosting) that ensures the software being run in my client's browser is the software I signed.

This is the pivotal thing. It needs to live in the browser. We can't leave this to a plugin.

  • doomrobo a day ago

    I'll actually argue the opposite. Transparency is _the_ pivotal thing, and code signing needs to be built on top of it (it definitely should be built into the browser, but I'm just arguing the order of operations rn).

    TL;DR you'll either re-invent transparency or end up with huge security holes.

    Suppose you have code signing and no transparency. Your site has some way of signaling to the browser to check code signatures under a certain pubkey (or OIDC identity if you're using Sigstore). Suppose now that your site is compromised. What is to prevent an attacker from changing the pubkey and re-signing under the new pubkey. Or just removing the pubkey entirely and signaling no code signing at all?

    There are a three answers off the top of my head. Lmk if there's one I missed:

    1. Websites enroll into a code signing preload list that the browser periodically pulls. Sites in the list are expected to serve valid signatures with respect to the pubkeys in the preload list.

    Problem: how do sites unenroll? They can ask to be removed from the preload list. But in the meantime, their site is unusable. So there needs to be a tombstone value recorded somewhere to show that it's been unenrolled. That place it's recorded needs to be publicly auditable, otherwise an attacker will just make a tombstone value and then remove it.

    So we've reinvented transparency.

    2. User browsers remember which sites have code signing after first access.

    Problem: This TOFU method offers no guarantees to first-time users. Also, it has the same unenrollment problem as above, so you'd still have to reinvent transparency.

    3. Users visually inspect the public key every time they visit the site to make sure it is the one they expect.

    Problem: This is famously a usability issue in e2ee apps like Signal and WhatsApp. Users have a noticeable error rate when comparing just one line of a safety number [1; Table 5]. To make any security claim, you'd have to argue that users would be motivated to do this check and get it right for the safety numbers for every security-sensitive site they access, over a long period of time. This just doesn't seem plausible

    [1] https://arxiv.org/abs/2306.04574

    • thadt a day ago

      I'll actually argue that you're arguing exactly what I'm arguing :)

      My comment near the end is that we absolutely need transparency - just that what we need tracked more than all the code ever run under a URL is that one signing key. All your points are right: users aren't going to check it. It needs to be automatic and it needs to be distributed in a way that browsers and site owners can be confident that the code being run is the code the site owner intended to be run.

      • doomrobo a day ago

        Gotcha, yeah I agree. Fwiw, with the imagined code signing setup, the pubkey will be committed to in the transparency log, without any extra work. The purpose of the plugin is to give the browser the ability to parse (really fetch, then parse) those extension values into a meaningful policy. Anyways I agree, it'd be best if this part were built into the browser too.

evbogue 18 hours ago

Much of what is described in the article can be accomplished with content addressable storage.

If we develop an internet where links describe the integrity of a file then we don't have to worry about the content changing out from underneath us. Additionally we get the benefit of being able to distribute the files we depend on anywhere.

Why make a map of hashes that correspond to human readable file urls, when we can directly link to hashes?

  • doomrobo 9 hours ago

    Yes, if every single URL in your web application has a hash in it (including <a> hrefs) then you don’t have to worry about anyone maliciously serving a webpage anymore.

    But how do you get new app versions? I argue, if you want any meaningful security guarantees, an answer to this question will require transparency and/or code signing (which itself requires transparency, per my comment below)

AndrewStephens a day ago

As a site owner, the best thing you can do for your users is to serve all your resources from a server you control. Serving javascript (or any resource) from a CDN was never a great idea and is pointless these days with browser domain isolation, you might as well just copy any third party .js in your build process.

I wrote a coincidently related rant post last week that didn't set the front page of HN on fire so I won't bother linking to it but the TL/DR is that a whole range of supply chain attacks just go away if you host the files yourself. Each third party you force your users to request from is an attack vector you don't control.

I get what this proposal is trying to achieve but it seems over complex. I would hate to have to integrate this into my build process.

  • doomrobo a day ago

    You're right that, when your own server is trustworthy, fully self-hosting removes the need for SRI and integrity manifests. But in the case that your server is compromised, you lose all guarantees.

    Transparency adds a mechanism to detect when your server has been compromised. Basically you just run a monitor on your own device occasionally (or use a third party service if you like), and you get an email notif whenever the site's manifest changes.

    I agree it's far more work than just not doing transparency. But the guarantees are real and not something you get from any existing technology afaict.

    • EGreg a day ago

      If they want to make a proposal, they should have httpc://sha-256;... URLS which are essentially constant ones, same as SRI but for top-level domains.

      Then we can really have security on the Web! Audit companies (even anonymous ones but with a good reputation) could vet certain hashes as being secure, and people and organizations could see a little padlock when M of N approved a new version.

      As it is, we need an extension for that. Because SRI is only for subresource integrity. And it doesn't even work on HTML in iframes, which is a shame!

      • ameliaquining a day ago

        The linked proposal is basically a user-friendlier version of that, unless you have some other security property in mind that I've failed to properly understand.

some_furry a day ago

This is really cool, and I'm excited to hear that it's making progress.

Binary transparency allows you to reason about the auditability of the JavaScript being delivered to your web browser. This is the first significant step towards a solution to the "JavaScript Cryptography Considered Harmful" blog post.

The remaining missing pieces here are, in my view, code signing and the corresponding notion of public key transparency.

saurik a day ago

1) It seems strange that this spec isn't an extension of the previous cache manifest mechanism, which was very similar and served a very similar purpose: it listed all of the URLs of your web app, so they could all be pre-downloaded... it just didn't include the hashes, and that could easily have been added.

2) That the hashes are the primary key and the path is the value makes no sense, as it means that files can only have exactly one path. I have often ended up with the same file mapped to two places in my website for various reasons, such as collisions in purpose over time (but the URL is a primary key) or degenerate objects. Now, yes: I can navigate avoiding that, but why do I have to? The only thing this seems to be buying is the idea that the same path can have more than one hash, and even if we really want that, it seems like it would make a million times more sense to just make the value be an array of hashes, as that will make this file a billion times more auditable: "what hashes can this path have?" should be more clear than "I did a search of the file to check and I realized we had a typo with the same path in two places". No one -- including the browser implementing this -- is trying to do the inverse operation (map a hash to a path).

3) That this signs only the content of the file and not the HTTP status or any of the headers seems like an inexcusable omission and is going to end up resulting in some kind of security flaw some day (which isn't an issue for subresource integrity, as those cases don't have headers the app might want and only comes into play for successful status). We even have another specification in play for how and what to sign (which includes the ability to lock in only a subset of the headers): Signed HTTP Messages. That should be consulted and re-used somehow.

4) Since they want to be able to allow the site to be hosted in more than one place anyway, they really should bite the bullet and make the identity of the site be a key, not a hostname, and the origin of a site should then become the public key. This would let the same site hosted by multiple places share the same local browser storage and just act like the exact same site, and it would also immediately fix all of the problems with "what if someone hacks into my server and just unenrolls me from the thing", as if they do that they wouldn't have the signing key (which you can keep very very offline) and, when a user hits reload, the new site they see would be considered unrelated to the one they were previously on. You also get provenance for free, and no longer have to worry about how to deal with unenrollment: the site just stops serving a manifest and it is immediately back to being the normal boring website, and can't access any of the content the user gave to the trusted key origin.

  • doomrobo a day ago

    1. I didn't know about this [1] actually! It looks like it's been unsupported for a few years now. The format looks pretty barebones, and we'd still need hashes like you said, as well as "wildcard" entries. I reckon the JSON solution might still be the better choice, but this is good to have as a reference.

    2. I agree, and this is something we have gone back and forth on. The nice thing about hashes as primary keys is you can easily represent a single path having many possible values, and you can represent "occurs anywhere" hashes by giving them the empty string. But the downside like you mention is that a hash cannot occur at multiple paths, which is far from ideal. I'll make an issue in the Github about this, because I don't think it's near settled.

    3. I had read the spec [2] but never made this connection! You're right that it's not hard to imagine malleability sneaking in via headers and status codes. I'll make an issue for this.

    4. I wanted to veer a bit from requiring sites to hold yet more cryptographic material than they already do. Yes you can keep signing keys "very very offline", but this requires a level of practice that I'm not sure most people would achieve. Also you run into key rotation annoyances as well. The current route to something like you describe is have every site have their own transparency log entry (though they can share manifests and even asset hosts), and use code signing to link their instance to the source of truth.

    [1] https://en.wikipedia.org/wiki/Cache_manifest_in_HTML5

    [2] https://www.rfc-editor.org/rfc/rfc9421.html

    • saurik a day ago

      <3 FWIW, I know Hacker News discussions often go stale after a bit and later responses might never be seen, and I don't really have time until later tonight to work on this (I used up my minutes earlier frantically reading this article to leave that other comment), so I thought I'd leave a quick comment here saying that I will be doing some further explanation in a comment here later tonight into what I thought is so interesting with cache manifests and some further thoughts viz-a-viz shared origins. (And, if you also happen to think any of my commentary is somewhat useful, I'd love to have a call or something with you at some point to talk about some of your vision for this work: I have a number of use cases for this kind of verification, and I think I was one of the more serious users of cache manifests back a decade ago.)

    • saurik 19 hours ago

      So, the other standard that relates to this is Signed HTTP Exchanges (which, I only, just now, learned that Cloudflare I guess decided to, also just now, withdraw support of, lol), and I think when you fit all of these together, and look at the existing practice from both Android and Web3, a really amazing possible vision starts to form.

      AFAIK, the goal there was the idea that you should be able to implement stuff like a caching peer-to-peer proxy network in a secure manner, as you'd be able to prove that a response came from the official server... and, since that could have a Date header and associated cache control information, that it was even "up to date"/valid content.

      The way this works is that the signed exchange is considered part of the origin that signed the exchange, rather than the origin from which you received it; this totally changes the game for distributing censorship resistant applications... except that I believe it all ends up tied back to the TLS ecosystem, to establish the correct key.

      Essentially, what I've seen a lot of people want to do -- primarily very much in the decentralized crypto / Web3 ecosystem -- is to be able to have an application where the "server" is a blockchain (accessed via a commodity server that just tracks access to blockchain state), identity is handled by keys, and all data is in local storage...

      ...but the problem is: where do you get this app from? It ends up having to be on a random web host, and, even with separate wallets managed by browser extensions or even full hardware, you end up in a terrifying place if the server can keep modifying the app you are using as you are using it. This is like, your core use case for this! ;P

      Only, these people are trying to simultaneously solve this: they don't want the site to be tied to the existing website infrastructure at all. If I disappear, you should be able to continue to use the website forever, as long as someone has a backup copy of it and they are willing to host it, which is similar to the signed exchanges case.

      The closest we have for this currently is to host the app on IPFS, but: 1) the browser doesn't have a supported way to verify anything that comes from IPFS, so we are reliant on the security of some random proxy server (which we could run local, but it is super expensive); and 2) the only way to get updates re-exposes the original issue.

      To me then, "table stakes" for any attempt to solve this problem has to cover this use case, as this is where I see the vast majority of people, today, actively trying to pull this off. In fact, with the exception of WhatsApp, the only people I've ever even heard of with a need for this right now are these people who are going all the way.

      One way to approach this would be to try to stare at IPFS and figure out how to make it better, but I totally appreciate that that might be a lost cause: IPFS is extremely inefficient and intricate... what I feel we need is for a standard to be put forward that allowed for stuff on top of IPFS--or all competing mechanisms--to be compliant.

      Like, the thing I actually want to be able to do is to host a website "out there somewhere", maybe on a bunch of mirror sites, maybe on IPFS, maybe in .zip files that people distribute on USB keys... just, somewhere/somehow, and then when they use that website it is considered just as secure as any other way that they might get the app.

      This is how it works with Android apps, for example: no matter how I found the app, or an upgrade to the app (even if that upgrade came from a different path than how I got to the app originally), the app gets access to its private/secure storage on a user's device because of its identity, not because of the path by which I'd found the app.

      And this is where the whole problem just starts sounding like the same thing that we were doing with app cache manifests: the ostensible reason those existed is that one would like to be able to download a full copy of a web application so you aren't using it on a train and suddenly realize you are missing some key file for it to operate.

      What that means, though, is that the app cache manifest essentially becomes a bill of materials for an entire web app: with a link to just the app cache manifest, what you are able to do is download and "install" the app. (iOS then used this heavily for their personal web app install to homescreen flow, so you really downloaded the app.)

      The mechanism also had a kind of atomicity to it, wherein it would download the entire new application and only switch to the new app as a single unified unit: either it had an updated copy of all of the files in the new manifest, or it is going to keep using the old app; once ready, a script event fired and you could notify the user to reload.

      So like, in my mind, there is this amazing intersection ready to be found, wherein the goals of the app cache manifest and Signed HTTP Exchanges -- but with a root of trust that is more like an Android app than the TLS authorities -- are combined together to let the ecosystem trying to build decentralized peer-to-peer e2ee web applications.

      And like, viewed from that perspective, the "more cryptographic material" that people need for their app is just the same thing as the signing key they'd use for an Android app, a concept that people built workflows around and I don't think is such a big lift: somehow, Android was fine, despite this being how it worked for a dozen+ years.

      That said, I think that this still has a lot of value even if it is tied back down to the centralized hostname ecosystem, but what I like about HTTP Message Signatures (not Signed HTTP Exchanges) is that I think they have a trust root that is more like DANE, using a DNS entry to anchor the public key rather than tying it to a TLS certificate.

      Regardless, in my perfect world, what I want is for all of these copies of the app that might exist in the world -- whether signed by the same key or tied back through Signed HTTP Exchanges to an underlying host (which, maybe some day, can be on a fully decentralized registrar) and a key furnished by DNS -- to share the same web origin.

      And like, the reason I keep coming back to this, is because most of the people I have seen in the wild trying to solve this problem barely have a server at all and want to be able to write an application that entirely uses local storage and distributed hash tables or blockchains or even manual message passing to connect up, peer-to-peer.

      But so, yeah: I've now spent a bunch more time typing a giant manifesto for a thing that you are already working on and I get that that is an awkward thing if the vision doesn't align at all (or maybe you are actively trying not to solve the use case of the people I keep pointing at)... but, if you do, I'd love to chat about this (a lot).

zb3 a day ago

Ok (let's pretend I didn't see the word "blockchain" there), but none of this should interfere with browser extensions that need to modify the application code.

  • some_furry a day ago

    EDIT: Disregard this comment. I think there was a technical issue on my computer. Keeping the original comment below.

    -----

    > let's pretend I didn't see the word "blockchain" there

    There's nothing blockchain about this blog post.

    I think this might be a rectangles vs squares thing. While it's true that all blockchains use chains of hashes (e.g., via Merkle trees), it's not true that all uses of append-only data structures are cryptocurrency.

    See also: Certificate transparency.

    • JimDabell a day ago

      They specifically suggest using a blockchain for Tor:

      > A paranoid Tor user may not trust existing transparency services or witnesses, and there might not be any other trusted party with the resources to self-host these functionalities. For this use case, it may be reasonable to put the prefix tree on a blockchain somewhere. This makes the usual domain validation impossible (there’s no validator server to speak of), but this is fine for onion services. Since an onion address is just a public key, a signature is sufficient to prove ownership of the domain.

      • some_furry a day ago

        Oh, weird. I didn't see that (and a subsequent Ctrl+F showed 0 results) but now it's showing up for me?

everdrive a day ago

I improve the trustworthiness of js by blocking it by default.