That post is great work. Thanks again. But I think you're wrong about JWT.
The problem with JWT/JOSE is that it's too complicated for what it does. It's a meta-standard capturing basically all of cryptography which, as you've ably observed (along with Matthew Green), was not written by or with cryptographers. Crypto vulnerabilities usually occur in the joinery of a protocol. JWT was written to maximize the amount of joinery.
Good modern crypto constructions don't do complicated negotiation or algorithm selection. Look at Trevor Perrin's Noise protocol, which is the transport for Signal. Noise is instantiated statically with specific algorithms. If you're talking to a Chapoly Noise implementation, you cannot with a header convince it to switch to AES-GCM, let alone "alg:none". The ability to negotiate different ciphers dynamically is an own-goal. The ability to negotiate to no crypto, or (almost worse) to inferior crypto, is disqualifying.
A good security protocol has good defaults. But JWT doesn't even get non-replayability right; it's implicit, and there's more than one way to do it. Application data is mixed with metadata (any attribute not in the JOSE header is in the same namespace as the application's data). Anything that can possibly go wrong, JWT wants to make sure will go wrong.
It's 2017 and they still managed to drag all of X.509 into the thing, and they indirect through URLs. Some day some serverside library will implement JWK URL indirection, and we'll have managed to reconstitute an old inexplicably bad XML attack.
For that matter, something crypto people understand that I don't think the JWT people do: public key crypto isn't better than symmetric key crypto. It's certainly not a good default: if you don't absolutely need public key constructions, you shouldn't use them. They're multiplicatively more complex and dangerous than symmetric key constructions. But just in this thread someone pointed out a library --- auth0's --- that apparently defaults to public key JWT. That's because JWT practically begs you to find an excuse to use public key crypto.
These words occur in a JWT tutorial (I think, but am not sure, it's auth0's):
"For this reason encrypted JWTs are sometimes nested: an encrypted JWT serves as the container for a signed JWT. This way you get the benefits of both."
There are implementations that default to compressed.
There's a reason crypto people table flip instead of writing detailed critiques of this protocol. It's a bad protocol. You look at this and think, for what? To avoid the effort of encrypting a JSON blob with libsodium and base64ing the output? Burn it with fire.
I did wrap JWT/JWE with a library lest somebody else in my company would get the bad idea to implement directly, and yes, the first thing I've done was to limit the signing algorithm to a single specified algorithm (HS256 for everything that doesn't have to cross service boundaries).
But I admit never thought of it as a particularly bad crypto protocol. It's actually pretty good compared to most negotiable crypto standards out there. I mean, yeah, JWK allows you to embed X.509 (but you don't have to do it), the value of supporting RSA is questionable, their selection of curves is regrettable (but all the world went with the NIST curves), compression on JWE (and not JWS?!) makes me suspicious, and yeah, JSON is kinda verbose compared to MsgPack et al.
But then I remember that before that before JWT came SAML+XML-DSIG (and the whole WS-* ecosystem) and CMS/PKCS#7 (and the whole ASN.1 ecosystem). Ah, compared to these JOSE is a fresh breath of air.
What a lightweight and uncomplicated serialization format - It couldn't even be used for amplified DDOS attacks!
What a modern set of ciphers (besides 'none', sorry, missed that on there ;). No more DES, RC4, or unpopular untested ciphers just there to fill the slots.
Yeah, JOSE still has some parts which are over-engineered but for a STANDARD it's the best one there. Management demands standards, it's a fact of life. And I'd choose JWT over SAML or CMS any day.
I'm also fine with just stuffing MsgPack or Cap'n Proto data inside a libsodium secretbox or usually just crypto_auth to be honest, but corporates love standard. Perhaps libsodium algorithms should be turned into RFCs and then we could have a much simplified token metadata format (expiry, issued date and their ilk) that can be separated from a completely freeform payload. I'd gladly push that format over JOSE.
We can cry "just run an arbitrary format through HMAC-SHA256/libsodium" until the end of the days, but it's the same as asking developers to just send a list of key-value pairs during the XML overengineering heyday. Until JSON came with RFC hammer on their head, developers went with XML by default.
It's an informal standard, like Noise, or WireGuard, or Curve25519, or Nacl. It's also so simple that JWT nerds will likely believe it's missing something. It is: the JWT/JOSE vulnerabilities.
It used to be that we got things working and then standardized them. Now we build cryptosystems de novo in standards committees and spend the next 10 years writing papers about the resulting flaws. Ok, it didn't used to be that way, and we've always been writing papers about flaws in crypto standards. I don't know what to say about this, except "stop, somehow".
Yes, informal standards, but that's exactly the problem.
At my previous work, I've implemented something similar to Fernet in the past (though using AES-GCM rather than AES-CBC+HMAC), and that's dead simple. But it's not standard.
Every time I've suggested modifying our JWT implementation to use Ed25519, or using any NaCL implementation for encryption instead of the vulnerability-footgun framework better known as JCE, I get raised eyebrows.
People want standards. Fernet is nice, but it should be pushed to an RFC level and offer more metadata besides a timestamp (not hard, just copy all the JWT claim names in stick a JSON into the ciphertext :))
It's also not useful when you do need asymmetric encryption/signature, and you can't just ignore these use cases, since people will keep JWT alive just for them.
As shitty as XML-DSIG is, it's pervasively used across almost every single language and platform imaginable. The goal of standards like JWT and XML-DSIG is interoperability above security. What good is perfect security if you can only talk to yourself?
PS: libsodium is great but the fact that it requires C bindings to use from a JVM app makes it a non starter for a lot of use cases.
1 - You have not demonstrated in anyway that it's impossible to use JWT tokens in a secure manner. Just that it's easy to shoot yourself in the foot.
2 - It's not unethical to make tradeoffs, period. We all build systems that have potential attack vectors and we make tradeoffs based on threat models. That's the difference between Academics and Engineers.
Example: Hacker News allows shitty passwords, that's a security weakness. However, the data that's protected by that shitty password is pretty meaningless. Is that not a good security tradeoff? Is Hacker News unethical?
What should I use instead? I pass around JWTs attached to HTTP requests that represent an authenticated user, and contain things such as a user's email, groups, scopes etc. I've tried to keep it simple (RSA, SHA256, nothing interesting), and use the subset of JWT that seems sane (basically the bits I see Google using in their JWT based OAuth flow)
I used JWTs because
1. I like the statelessness of JWTs (though I've learnt that there are many trade offs related to this)
2. OAuth uses JWTs, Google uses OAuth, and Google usually know what they're doing
3. I can attach custom claims
4. I don't know of any alternatives, other than x509, which I have less confidence on me being able to validate correctly than JWTs.
What would you suggest? An opaque token which I then look up against a central database/api?
SPKI (RFCs 2692 & 2693) offers a well-developed, well-thought-out framework which meets all your needs: SPKI certificates can contain state, and thus support server statelessness; SPKI certificates can be used as OAuth tokens; SPKI certificates support custom claims (and in fact go so far as to define a well-formed claim calculus which can be implemented easily, and which supports just about anything one would wish to do); and SPKI certificates are far, far simpler than X.509.
I agree on all the accounts on what you said. I am probably biased by the fact I like JSON over XML.
Probably JOSE just took the wrong path and could have been designed way better than it is...
JWT begs you to use public key because it makes sense for a lot of the use cases that people implement using JWT specifically having a single token issuer while having distributed token validation.
Using a public key algorithms makes also it easier to implement a sane key rollover strategy. I suspect this is the reason that Auth0 pushes their customers to validate tokens with public keys published on their JWKS endpoints.
As for X.509, I agree it kind of sucks but what are the alternatives?
You are making the same mistake the IndexedDB haters made. The standardization effort around JOSE, as far as I can tell, is about making the browser a place where you can run crypto. They want it to be composable because that's the web way.
I can agree with your critiques but still wish "real" cryptographers would accept the inevitability of a worse-is-better approach winning here. Don't flip tables, write the jQuery of web crypto. You'll do more good in the long run going with the flow on this one.
> The standardization effort around JOSE, as far as I can tell, is about making the browser a place where you can run crypto.
The problem is that the browser is not a place where one can safely run crypto.
> Don't flip tables, write the jQuery of web crypto. You'll do more good in the long run going with the flow on this one.
That's a bit like advising a vegan to invent a better method for slaughtering cattle (n.b.: I am not a vegan and have no problem killing & eating animals). The problem is that no-one who understands security thinks that in-browser crypto is a good or safe idea, and thus no-one who understands security wishes to help it along. It should be stopped, not made slightly less bad.
The problem with JWT/JOSE is that it's too complicated for what it does. It's a meta-standard capturing basically all of cryptography which, as you've ably observed (along with Matthew Green), was not written by or with cryptographers. Crypto vulnerabilities usually occur in the joinery of a protocol. JWT was written to maximize the amount of joinery.
Good modern crypto constructions don't do complicated negotiation or algorithm selection. Look at Trevor Perrin's Noise protocol, which is the transport for Signal. Noise is instantiated statically with specific algorithms. If you're talking to a Chapoly Noise implementation, you cannot with a header convince it to switch to AES-GCM, let alone "alg:none". The ability to negotiate different ciphers dynamically is an own-goal. The ability to negotiate to no crypto, or (almost worse) to inferior crypto, is disqualifying.
A good security protocol has good defaults. But JWT doesn't even get non-replayability right; it's implicit, and there's more than one way to do it. Application data is mixed with metadata (any attribute not in the JOSE header is in the same namespace as the application's data). Anything that can possibly go wrong, JWT wants to make sure will go wrong.
It's 2017 and they still managed to drag all of X.509 into the thing, and they indirect through URLs. Some day some serverside library will implement JWK URL indirection, and we'll have managed to reconstitute an old inexplicably bad XML attack.
For that matter, something crypto people understand that I don't think the JWT people do: public key crypto isn't better than symmetric key crypto. It's certainly not a good default: if you don't absolutely need public key constructions, you shouldn't use them. They're multiplicatively more complex and dangerous than symmetric key constructions. But just in this thread someone pointed out a library --- auth0's --- that apparently defaults to public key JWT. That's because JWT practically begs you to find an excuse to use public key crypto.
These words occur in a JWT tutorial (I think, but am not sure, it's auth0's):
"For this reason encrypted JWTs are sometimes nested: an encrypted JWT serves as the container for a signed JWT. This way you get the benefits of both."
There are implementations that default to compressed.
There's a reason crypto people table flip instead of writing detailed critiques of this protocol. It's a bad protocol. You look at this and think, for what? To avoid the effort of encrypting a JSON blob with libsodium and base64ing the output? Burn it with fire.