I appreciate the effort, but this is one of those cases in which there isn't much security added by encryption.
If I assume the website owner is malicious (or subverted by powers that be), I cannot trust the JS code provided by the website. Key disclosure is as trivial as one line hidden somewhere in megabytes of JS code delievered from the server. Which makes "end-to-end" part nearly meaningless.
I agree, not intractable but there are meaningful differences:
* A native app can be installed once. A browser app is effectively downloaded fresh each time you use it. That give more exposure to attack.
* A native app is downloaded, then run. A browser app does both in one step. Separation gives the user a chance to verify using hashes, signatures, etc, ideally cross checked from multiple different domains incase only the download server/primary domain is hacked. If there are sensitive user cookies, malicious javascript can steal them immediately on page load.
Native apps that update themselves can be a mess, but OS or package manager updates are generally carefully managed (I.E. apt).
For the CDN concern, sub resource integrity should work well (of course you need to verify that the version you hash is actually safe, which people sometimes skip). I think browser support is good these days.
As a website it's trivial to serve different content to different users. Each page load can or cannot contain a compromised payload. And since the website also identifies the users it's also easy to target specific users.
To reduce that risk, and temptation, we want to be in a model where the likelihood of detecting a compromised payload is increased. This is why software distributions like Debian are important, because they serve the same content to all their users, and there is a chain of verification that is established. One vigilant user is enough to detect that breach.
An even better solution is if the source code is available because it makes it easier for vigilant users to find those undesirable changes. This, paired with reproducible builds also allow to independently verify that the compiled output is indeed produced from that given source code.
Somewhere on that line we also have App stores which don't gives much more guarantees than the website but transfer that trust from the publishers to the application distribution platform. A compromised user still has the opportunity to capture the binary and submit it for analysis.
How do you hash your browser downloads? Also you need to perform that on every request. It should be possible with additional addons, but definitely not out of the box.
You can easily open the dev tools, view the payloads, and copy them locally. It would be trickier to prevent evaluation before you've done so, but all you have to do is not enter anything sensitive until you've checked
Have fun trying to inspect Javascript code after it has gone through Webpack, Babel or anything else that may have been used for transpiling. Not saying it's impossible but it's still really annoying to do.
To add on to that, when there's an update, you usually can't diff it properly because large parts of the transpiled Javascript may change because of a one line change in the actual source code.
This is all assuming the website isn't actively trying to make it difficult for you to analyze their code.
At least if it's open source, you can inspect the source code then verify that the output is the same.
> In any E2E context you have to trust the client code.
You have to trust it not to exfiltrate your local plaintext data, sure; but encryption and key management in a native app might be outsourced to a TPM chip, in a way where the native app can't steal the keys, nor decrypt anything "behind your back", in practical terms meaning there's a smaller surface-area of code to audit.
It differs from a native app in that every time they change the JS code behind the website, you would not know. You could have thoroughly inspected the source, and then when you go to use it tomorrow, that one line of spyware could be inserted.
For native apps, you can install a particular version from source and not change it.
>As the maintainer of Excalidraw, I now sleep much better at night. If the hosting service gets compromised, it doesn’t really matter as none of the content can be decrypted without the key.
Seems the maintainer maded the point of protecting himself more than anything.
Right. "Javascript cryptography considered harmful" is a trope at this point, but that doesn't make it an axiom. The author is using this to _not store users' private data_ on their own infrastructure.
Even if Excalidraw _was_ compromised and someone put malicious JS code in the app, the total scope of the breach would be very limited. Records could only be exfiltrated on an individual basis, and only if the users opened their URLs and exposed the decryption key to the malicious Javascript payload.
This is a smart way to offer a free service without worrying too much about liability or compliance with the ever-expanding set of regional privacy laws.
Are there any current browser standards for validating that the served JS is ‘safe’? I can see such a thing being also useful for applications like ProtonMail, for example?
What about something like a browser extension that queries an audit server for a list of signed hashes of ‘safe’ JS?
- Well-known code auditors could perform reviews of JS
- They could sign JS they find safe with their PGP keys and upload it to some server
- Users could choose to trust certain auditors
- Every time you visit a site that you choose to require this kind of validation, you could check that the hashed JS matches the key
I guess we’re going the way of PKI+SHA hashes of distributed binaries all over again though. Also, if the website updates JS, you’d need to wait for auditors to review it, and there’s a whole mess there (websites would probably have to serve beta versions of their code ahead of release so auditors could have time to review them). Finally, JS would have to be static across all users and I’m not sure how feasible this is.
There is some benefit, though? Now you are distributing the trust over ProtonMail and your trusted auditors. This could be useful if we find ProtonMail to be compromised one day. This might even spawn businesses aimed solely at reviewing websites’ code.
There has to be a better way to do this. How can we bring ‘code review’ to web applications?
You need to sign an entire chain of HTML+JS+CSS+everything else, as you can build keylogger with CSS. Web if weird. I wouldn't be surprised to find out that one can build keylogger with some tricky font file. But it definitely should be possible to build an addon like that. Although it would require some good cryptographers as not to make a mistakes.
I don't think there are any browser standards for that. I guess that such a webapp is too niche and this threat is extremely niche, so very few people would care for it to be a general purpose standard.
It's using end-to-end encryption to address a slightly different threat model than the usual one: the website operator doesn't want the liability/danger of holding cleartext data. E2E does solve this even in the browser scenario.
Now an attacker who dumps the server's disks doesn't compromise user data, they'd have to activate modify the website. This raises the bar of a successful targeted attack and aldosterone basically eliminates risk from untargeted attacks, which is well worth doing.
(Also now warrants that can compel disclosure have nothing they can target, eliminating Lavabit-style attacks. There's still the possibility of court orders making you write software like the proposed use of the All Writs Act against Apple, but that's on much less certain legal ground.)
It's true that this doesn't let you avoid trusting the provider, but you're not going to get that anyway - and this scheme is certainly no worse. (Arguably you're not going to get that on native apps either these days, thanks to closed-source app stores and automatic updates, and automatic updates are a very good thing.)
> It's true that this doesn't let you avoid trusting the provider, but you're not going to get that anyway - and this scheme is certainly no worse. (Arguably you're not going to get that on native apps either these days, thanks to closed-source app stores and automatic updates, and automatic updates are a very good thing.)
There is no guarantee, but security researchers often check the contents of apps like WhatsApp, Threema, etc. However, that doesn't help you if you specifically get a special version of the app that sends your content to the app writers. For websites, how hard this is depends on your infrastructure, but it is more or less trivial. For app stores like Google Play or apple app store, there is no such feature to push a special version to a subset of the population specified by name. You can only push it to entire classes of devices.
So suddenly Google, Apple, etc. have to be in on the attack which drastically reduces the number of people who can pull off supply chain attacks. Maybe you should be still worried about the US government, but while Saudi princes can bribe the Threema creator, they can't compel Apple to push a malicious update the Threema creator signed to select people only. So they'll have to hack the device via other means.
AFAIK Google Play serves apk signed by a developer. So it can't just serve another apk without possessing developer private key. So this attack is possible but would require coordinating few parties. On the contrary, Apple AppStore signs the binary with its own key, so they can serve whatever they want to whoever they want and nobody will notice.
> For app stores like Google Play or apple app store, there is no such feature to push a special version to a subset of the population specified by name. So suddenly Google, Apple, etc. have to be in on the attack...
With the Android ecosystem specifically, because the OS base-image is customized by the device OEM, an interested state actor can inject a rootkit into devices in their own local market merely by suborning their domestic OEMs, and then manipulating trade tariffs to ensure that domestic citizens are incentivized to buy domestic OEM phone brands.
(Thankfully, this doesn't apply to devices sold into foreign markets, as nobody can predict what brand of phone an arbitrary foreign-citizen person-of-interest is going to choose to buy. Even if they contain the rootkit, there's low likelihood of there being a foreign surveillance system set up specifically with the hopes of seeing what foreign buyers of domestic-OEM devices are up to.)
I'm not refuting your point here but it feels like on HN everytime we hit any web topic there are the kind of comments about "wait but this could happen / what if the website owner is malicious / included some wonky code he doesn't know about, etc".
They're not wrong, but also not the point of the article.
>As the maintainer of Excalidraw, I now sleep much better at night. If the hosting service gets compromised, it doesn’t really matter as none of the content can be decrypted without the key.
This seems to be the point of the article, and valid IMO. Yet on HN more and more we get dragged off on these larger state of the web topics. They're not wrong either, but I feel like the volume sort of drown out the point / valid topics too.
I think the key problem here is where and how do we establish a root of trust for Web applications.
Currently we have some form of root of trust for HTTPS/TLS, code signing, trusted execution, that the OS or browser or chipsets distribute a set of "trusted" root certificates. For the consumers, they are implicitly backed by the big companies that manage the screening, auditing, and distribution of these certificates. But of course, these certificates have limited use cases that not yet covering the end-to-end encryption application for Web.
One way or another, we have to start our root of trust at some layer, either it's hardware, OS, drivers, or applications. But in general, the lower the layer gets, the lower the risk would be. Because it's easier for an evil actor to target specific user in higher layers.
There are more to be solved than just the root of trust for E2E on Web. For example, even if we can use trusted execution environment on a Web application to ensure secure key generation and key escore, we still have to face the problem of how to input or present the cleartext data with the user. If we still let the JS code to handle the cleartext in any way, there could still be a chance that the distributor of the JS code might steal them.
With that said, not only should we have a root of trust, but we also have to trust the UI provider that operates on the sensitive data for user input or display. The lowest UI layer is usually the OS, so even we have hardware root of trust, but if the trusted UI is in the OS layer, we would still be throttled at the level of trust on the OS layer.
So just load the JS locally from a browser extension. I know MEGA (end-to-end encrypted cloud storage) has an extension for that. I'm sure other end-to-end encrypted web apps (ProtonMail, Bitwarden, etc) could do the same.
While there are ways the encryption can be removed or subverted that's a far cry from adding no benefit. Compared to just using HTTPS for client to server encryption, this protects the user from a bunch of server-based attacks. Certainly not all, but it does meaningfully raise the bar.
Keep in mind that security needs to be usable, and needs to exist in tools users actually use. If I'm a user if a web app, then browser based e2e encryption helps me. Downloading the diagram, installing PGP tools, figuring out how to use it, sending the file via a different mechanism... probably not something an average user wants to try.
Modifying source to extract keys runs a risk of detection; it is overt. So it's preferable to passively snoop. If someone has a way to passively snoop TLS contents, the extra encryption protects data from them.
Agreed. If it stored data on a different server (allowed via CORS) then there could be some additional security, since then as a long as the JavaScript isn't subverted on the same site, you're storing encrypted data on a different server.
But yes, if you run JavaScript, you're always trusting that site to not do a "quiet update" of the code to something malicious. I don't see any obvious way to counter that, short of downloading the JavaScript code & running it yourself.
I like this kind of design in conjunction with delivering the code over IPFS. That way you know the code has not been tampered with, as long as you trust your IPFS gateway.
All JS is executed in the browser. If the malicious site wanted to steal the data, it must send the key to the server.
With enough inspecting, debugging, and network watching you would be able to see what they're doing and how.
While I agree you can obfuscate this in the JS payload, it doesn't make e2e encryption in web apps "meaningless". It would just take one user doing some due diligence to expose the malice.
If the JavaScript bundle that is being served is built on a public CI/CD service, it could be possible to do the following, for transparency and verification:
- Include in a header comment: the build URL, Git SHA-1 of the commit, and other metadata
- Sign the bundle using public/secret key cryptography
Having the build URL and sources URL help with discoverability and transparency, while integrity can be verified with the signature.
Adversary models now shift from the bundle provider to the CI/CD platform that runs the build, and any PKI used for the public key for signature verification. If the public key is versioned with the code, it can help reduce trust to a single entity (where the code is stored).
Yes, but there's no provision in the browser to do these kinds of integrity checks. If the browser isn't verifying it there's no point in adding any of this info, because it can be substituted covertly. In principle such 'version-pinning' could be added to the browser, but no-one has done so yet.
In theory you can generate the code locally and compare it with the deployed version to see that it's one to one.. But maybe we could do something in order to improve the said security check.
Not even in theory: the version you download to "check" and the version served to your web browser may not be the same content, as the webserver can respond with different content for the same URL, on a per request basis, for example serving the exploit code only to a specific ip + user-agent header combination, so that it steals your keys in your browser but shows the safe version to `curl`.
Subresource Integrity allows the HTML file to insist that the js file hasn't changed. I guess it would be possible to download and run the html file from your own machine.
Alternatively, it would be possible to create a service worker that uses a local copy and makes much more of a deal about files changing - it could always confirm changes with the user before allowing a change. Security sensitive apps should probably be doing this.
JS allows overriding overriding any object or method with separately loaded code. So even your “trusted” code could be compromised by separate “trusted” code.
Even native app packagers and languages can suffer from this when loading libraries dynamically (from search-path or symlink manipulation for example).
If browsers supported it, then it could be done much the way Linux distro packages work: include a signature from a trusted party together with the code, as a guarantee of being verified to match some standard (e.g. same as CI, or not malicious).
Don't this still apply to a native client? You can be sure unless you build the client from source, but you can also build the website from source too right?
This looks wrong. The iv should be a randomly generated string that is only used once. I'm not super familiar with modern Javascript, but I think you're just initializing a bunch of null bytes.
Honestly, I feel like whoever designed AES intended for people to make this mistake, because half of the sample code I see has this error, and it would have been easily avoided by specifying the IV length in the standard and requiring libraries to automatically generate it and prepend it to the ciphertext rather than letting callers have control over how it is initialized and stored.
1. It wouldn't add very much complexity to implement random IV's. You can send it with the ciphertext, or in the generated URL. In my own project, I use btoa(JSON.stringify({ key: generatedKey, iv: randomIv })) and reverse the operation when the URL is accessed:
Edit: I'm also converting the key/iv to hexadecimal. Don't remember why, but that's not necessary lol.
Edit #2: Ah-ha, and now I've learned something else from the Excalidraw team; I can skip my hex/uint & base64 conversions if I export the key as jwk.
2. There are a _lot_ of tutorials out there that incorrectly reuse the key with a fixed IV. I was more concerned about this being yet another tutorial that someone blindly follows and ends up reusing the key regardless.
It is not wrong. In AES-GCM, the "iv" is a nonce — any value (random, counter, cat picture) that is different for different encryptions with the same key. If you use the key only for one encryption, as it is used here, it can be static (e.g. all zeros).
BTW, regarding the AES comment: the authors of AES designed the block cipher, they didn't design the mode (GCM, which is CTR and GMAC). Nonce is a standard requirement for a stream cipher/stream mode of a block cipher/AEAD. In WebCrypto it's actually hard to skip setting the iv. And again, the author didn't make a mistake here.
"We encrypt the content with that random key. In this case, we only encrypt the content once with the random key so we don’t need an iv and can leave it filled with 0 (I hope…)."
It's a good idea if you encrypt with the same key _once_ — you can avoid attaching nonces to your ciphertext (less code and data), and have only 16-byte key in the URL.
In fact, using a random IV with AES-GCM is not exactly safe: 12-byte nonce is too small to avoid collisions with many encryptions. The recommendation is to not encrypt more than 2^32 messages with the same key if you use the random nonce.
If the key is securely random AND only used once, it won't compromise the encryption. But it's a bad idea, since it requires enforcing that the key is a nonce, instead of just a key. It's a bad habit, and can easily lead to compromise (when someone inevitably uses it as example code in a situation where those guarantees don't hold, for instance.)
Super excited to see that excalidraw made the front page! We published an article yesterday explaining how the end to end encryption works when sharing a link: https://blog.excalidraw.com/end-to-end-encryption/
Any chance you could add actual pen-like drawing? That would make it much more versatile, and make it fit its description better ("Excalidraw is a whiteboard tool that lets you easily sketch diagrams that have a hand-drawn feel to them."). Bonus points for using PointerEvents to change the line width with the pen's pressure.
Saving the diagram automatically and restoring when the site is revisited is a nice touch.
As with every in-browser encryption deployment - what's the threat model here ?
> I now sleep much better at night. If the hosting service gets compromised, it doesn’t really matter as none of the content can be decrypted without the key.
Which can be easily exfiltrated by the compromiser as they are now in a position to deliver and run arbitrary javascript in your users browser where the keys reside
Also, why can't I draw an actual non-flawed circle ?
> As with every in-browser encryption deployment - what's the threat model here ?
I believe the biggest win is that existing content will not be accessible to a hacker even if they fully compromise the website, unless users re-open them. So, sure, plenty of content may get compromised if the site gets hacked, but some large percentage of old content will not be.
> Also, why can't I draw an actual non-flawed circle ?
Adding sloppiness when white boarding is a common pattern to indicate roughness. It subtly cues the viewer not to treat it as a completed product, and allows them more freedom to make changes. If you wanted clean lines, there are many web drawing tools for that too.
> I believe the biggest win is that existing content will not be accessible to a hacker even if they fully compromise the website, unless users re-open them. So, sure, plenty of content may get compromised if the site gets hacked, but some large percentage of old content will not be.
The attacker won't need to wait for the users to open a specific drawing - just browse to the website, from there they can grab the keys for all drawings they have assuming that not that many of them exist in the first place and the attacker has the list of key ids from the compromised backend.
It does call for a much more noisy and visible attack which is by itself a valuable mitigation.
Either I'm not understanding you, or you're not understanding the tech. The website does not store any of the keys. They exist as a url fragment that only the user has ever seen.
So if I create an image today and encrypt it, only I see the key. If you hack the website tomorrow, you have access to my encrypted content, but not the key.
Now that you control the website, you can modify the code so that the key gets sent up every time a user encrypts or decrypts something. But you don't have any access to anything created before you hacked it, if no user decrypts it.
Well it helped one of those password manager companies during cloudbleed. There is a couple of threat models where it does help. Mitigates bitsquatting too.
Depends on the attack, really. Some realistic threats:
- The hosting service doesn't wipe physical disks they discard.
- The hosting service doesn't wipe virtual disks between customers.
- Your offsite backup provider gets compromised.
- Someone conducts a non- targeted attack, dumps whatever SQL database they see, and leaves before they get noticed.
- Someone conducts a targeted attack but gets noticed before they can develop a working patch to compromise the service.
- Someone (e.g., a government) pressures your hosting provider for data but doesn't want you to know. Modifying your JS files would risk being noticed.
Let's say someone vacuums up all the packets going over the wire passively and has some way of breaking TLS. Redundant encryption would be a further obstacle to reading the contents. TLS gets MITM'd in some environments, so it's useful there.
Good point, but what's the relative difficulty of recording all your MITM'd TLS traffic org-wide as plaintext versus attacking specific sites with custom JS to exflitrate JS encryption keys? It's additional friction for lazy spying, mostly, and for a situation where TLS traffic is passively snooped and broken by other means than MITM.
End-to-end encryption in the browser is not perfect security, as other comments are saying, but it’s significantly better than having user data in clear on a MySQL database somewhere.
PS. I’m the founder of Userbase.com — an open source service to help you build end-to-end encrypted web apps like this.
One of the problems with hacking the hashtag (#) in the URL is that this fragment identifier is supposed to be used to identify a portion of the document. As such unexpected things can happen when you try to exploit the hash tag for other purposes.
For example, with older of versions of Microsoft Office you cannot use a pound character in a hyperlink:
Some email applications may unexpectedly strip the tag, attempting to be smart about handling it in the RFC 3986 sense (not anticipating this use-case at all). When the data appended to the # is a dependency to view the link, it can more easily render the link unusable.
It's not just "some out of date bits", 90% of this post is ridiculously outdated.
Making an end-to-end encrypted web app with WebAssembly, WebCrypto, Service Workers and free TLS by Let's Encrypt is totally viable nowadays. Also, the Web userbase is largely using evergreen browsers like Firefox and Chrome.
Very little of that article is applicable. The website is served over HTTPS, the browser has a crypto API now. The encryption in this project decreases the impact of any future compromise of the server (if a share link is never visited post-compromise, it’s safe) and makes it necessary for an attacker to show their hand by serving malicious code to the client. (With browsers as they are now, that can be made hard to detect, but it’s a start. There’s always the risk of serving it up to someone who’s logging every response.)
All the other js resources on the page can still backdoor the app, though, and exfiltrate your keys. SRI and no longer executing javascript served with the right content-type in an img src is helpful along these lines, but even with SRI on every script tag, the sub-scripts pulled in from the integrity-checked third party js can pull in arbitrary further resources which can then overwrite your functions to steal your keys.
Even a strict CSP doesn't totally close the hole.
It's sort of crazy how difficult (that's a euphemism—I would term it "impossible") it is to even attempt to secure browser-based crypto, over and above the normal challenges involved in crypto implementation. I wouldn't use clientside js crypto for anything more secret than a grocery list.
I like that this is nice and simple and intuitive: if I want a bazillion menus and options there are many other tools. I also like the hand-drawn font.
URLs are encrypted. Hostnames aren't encrypted in HTTPS, unless TLS SNI is encrypted.
The problem with URLs is that a lot of products will log the URL to disk, and many operators forget or aren't aware they might need to have security plans around data in the logs.
A worse and impossible to mitigate problem in other scenarios is that user agents will cheerfully "bookmark" or "favourite" a URL or just let you "share" it and it's often not obvious to the end user that this shares their current state which they probably don't want not just the thing they're looking at. Example: you try to share the forum help link but instead give the recipient your logged in account...
This is the reason not to put authentication tokens into URLs for example.
When the state users want to bookmark or share literally is the state that's encrypted anyway this mismatch isn't a threat. If I send you the URL for this drawing of a cat and then I'm astonished you can now see the drawing of the cat I've got real problems technology can't fix.
Why? It makes it easy to share a sketch with someone else while still preventing the server from decrypting the data. Anything stored in a location hash (after the # symbol) does not get passed to the server.
The threat model the author tried to work around is not the user dumbly compromising their own work. The author wants to prevent proprietary and PII data from being stored on their own server. End-to-end encryption significantly reduces the author's potential liability hosting something like this in case something goes wrong.
> Why? It makes it easy to share a sketch with someone else while still preventing the server from decrypting the data. Anything stored in a location hash (after the # symbol) does not get passed to the server.
I'll hit the URL bar and hit control-C. That's way better than any javashit which wants to touch my clipboard.
Copying the current URL also copies the key in that case. Now I'm accidentally sharing my encryption information with another user.
That's literally the use-case of pasting the link to your sketch to someone. The app itself is not touching your clipboard. Take a careful look at how this works, it's perfectly innocuous.
The entire URL is encrypted, the router doesn't need to know anything about the URL to route the packets.
The only thing that can leak when you make an HTTPS connection is the DNS query.
This works well for sharing stuff like notes, drawings, etc. But what if your platform was more like a forum or a chat room? How could you share the keys?
Almost certainly not. EARN IT doesn't technically forbid encryption, it just lets an unelected board decide the legal requirements for communication without being sued into oblivion. But the unelected board will be under the control of those who object to encryption they can't break.
It would probably be legal if the program also sent the crypto keys somewhere for later use.
JavaScript "encryption" is like sending someone a letter in a package with a TSA lock on it, asking them to communicate secretly with you. They send you back the package with the lock, and instructions on how to communicate securely. You send the package back with the lock and your message encrypted according to the instructions.
It doesn't matter what your encryption scheme is. The TSA lock means the TSA can control the content. But the user assumes their communication isn't controlled by the TSA, because they are receiving instructions that appear normal.
If I assume the website owner is malicious (or subverted by powers that be), I cannot trust the JS code provided by the website. Key disclosure is as trivial as one line hidden somewhere in megabytes of JS code delievered from the server. Which makes "end-to-end" part nearly meaningless.