tldr; A long, passionate discussion about JS crypto. Use slides for an overview.
On top of that, there's a lot of fresh, new user-facing applications (
Let me give you a quick summary of JS crypto debate:
- XSSOMFG (and DOM is a mess). Code injections are common. JS crypto is as insecure as JS itself.
- There's no CSPRNG, no bytes, no mlock(), no BigNums and no good libraries to use. JS crypto is hard, because the language does not help you.
For years, these two articles dominated the views on the subject. Meanwhile, the world evolved - we've seen browser extensions written in JS,
was born and
arrived. Some of the arguments got simply outdated.
Someone was wrong on the internet.
Last year the discussion started again.
noted that in-browser crypto is dangerous mostly because the code can be backdoored by the server/attacker at anytime. Web is an
environment. He noted that browser extensions can solve this problem, as these behave more like a installable, auditable desktop applications.
made a great post, noticing that Thomas simplified his arguments and did not specify the threat model (in short - security is not a bit value. You're not secure
, you're secure
an attacker with certain capabilities).
admitted that JS crypto is hard, but doable. He provided some tips on how to overcome JS quirks, and noted examples when JS crypto is better than server-side approaches. He also dropped a super-cool vuln, but that went unnoticed.
Here's where I jump in.
Looking at the code
I have the unique opportunity to both participate in the (sometimes theoretical)
and deal with
of crypto applications written in JS. I took part in audits of several of them, and now I even develop one too. So I asked myself a few questions:
- What are the real challenges of JS crypto applications?
- What vulnerabilities are actually discovered, what are their root causes?
- Should we really worry about the issues raised a few years ago?
- What level of security can be achieved? Who can we be secure against?
- Can the JS crypto be somehow compared to "native" crypto?
The answers to all these are in these slides, but let me expand a bit about some key points.
It's all the same
- Web platform is really inconvenient for cryptographic operations
To give you an example of language quirks, you can still trigger weird behaviour in a lot of JS libraries by using Unicode characters, because JS developers use String.fromCharCode() assuming it returns 0-255. Weak typing often causes vulnerable code too.
XSS is an example of a web platform issue that breaks cryptographic code completely. Lack of good CSPRNG (and relying on Math.random() ) is a web platform issue as well.
Surprisingly, similar problems are present in native crypto applications, and these also result in spectacular vulnerabilities. For example, sometimes the language gives you poor support for reporting error conditions and you end up having a
certificate validation bypass
. Or out-of-bound read is possible and Heartbleed happens. You might also kill your entropy like
years ago. Skilful attacker might upgrade a buffer overflow in your
beloved crypto library
into RCE vulnerability, which is an equivalent of XSS in web security.
On both sides of the fence we face all the same issues, we're just not used to look at them this way.
Use extensions only
In-website cryptography is doomed. Let me repeat - don't implement cryptography on any document which location starts with
http. It's a trust problem, well understood and it's a dead end.
The only sane model for JS crypto is a server-side application or a browser extension. As a bonus, for these environments XSS is much easier to tackle as you can control your inputs more easily and sometimes the platform helps you as well (e.g. obligatory CSP for Chrome Extensions). In practice, XSS in your crypto application can be a marginal, manageable problem, and not a fundamental issue.
It's all fun and games...
Once we established a ground rule of only supplying code in an installable browser extension, it's time to dig deeper. Are browser extensions
We can only be secure in a given threat model, so let's look at our potential adversaries
. We solved XSS (which any kid could exploit) and we solved server trust issues. Unlike websites, extensions need to be installed, so you only need to trust the server once, not every time you run the code. By doing this we're secure against man-in-the-middle attackers backdooring the code (or the service owner being pressured to "enhance" the code later on). At a glance, it looks like we achieved the level of security of native crypto applications. Yay!
Until someone gets hurt.
Autoupdates Not so fast. For once, browser extensions
autoupdate. Silently. So the only way to trust the extension is to review it and install it manually (i.e. outside extension marketplace).
But - is this a problem unique to web? Most desktop systems autoupdate software packages nowadays, and the updates are being pushed aggressively (and for a good reason!). That includes your precious OpenSSL too.
Timing is another open problem. It's most likely impossible to guarantee constant-time execution in a modern JS compiler. Even when your code is a perfect port of a constant-time C routine, you'll have timing sidechannels in places you wouldn't ever expect, because your JS engine decided at some point it would be a good idea to optimize the loop. The timing difference will be
probably non-exploitable, but I've heard that statement falsified so many times, I'd rather not rely on it.
Again, this is something native crypto applications struggle with as well. Timing issues were discovered in GnuTLS, Java SSE, and OpenSSL to name the few. When fixing timing leaks, you sometimes even need to worry about CPU
, you know, and your precious crypto extension can be pwned with them. All you need is to do is visit a website that triggers the exploit code. Luckily, here we assume a much more skilful (or rich) attacker, so you might just get away with claiming it's
outside your threat model
Arguing that implementing crypto in a browser extension is insecure because a browser might get pwned, is similar to pointing out the insecurity of OpenSSL because kernel exploits exist.
Host security is still essential. Any binary you run can just read your precious ~./ssh/id_rsa and it's working-as-intended. What changed with JS is that now the attack surface is much greater, because:
- The browser is now your OS, and origins are just different users in this OS.
- Everyone in the world can execute a program on your OS. To put it boldly, web platform is just a gigantic drive-by-download marketplace. Visiting a page is equivalent to
wget http://kittenz; tar; ./configure; make; ./dist/kittenz
Assuming the adversaries are powerful enough, some of those kittenz might bite your code badly.
But then again, are browser exploits that much easier to buy/develop than any other popular software and/or kernel exploits? Because these would have no problems with circumventing native crypto applications. After all, GnuPG was thwarted by a simple
JS crypto is not doomed. We made some real progress in the last few years, and will continue to do so. It's just that when facing some classes of attackers you're defenceless and we all need to understand that.