No Way, JOSE! Javascript Object Signing and Encryption is a Bad Standard That Everyone Should Avoid

If you've already decided to implement Javascript Object Signing and Encryption (JOSE), whether you want JSON Web Tokens, JSON Web Encryption (JWE), or JSON Web Signatures (JWS), you should question this decision. You're probably making a mistake.

Everything in this blog post was written to be accurate as of RFC 7519, RFC 7515, and RFC 7516. It's possible that new RFCs in the future could supersede the flaws detailed within.

If you're not already familiar with basic cryptography terms and concepts, start here. Hyperlinks are provided throughout the article to explain things in detail, for the sake of brevity.

Why You Don't Want Javascript Object Signing and Encryption

There are several problems with the JOSE family of standards. These are not flaws specific to any particular implementation, and in fact many libraries work around the deficits of the standards.

JSON Web Tokens are Often Misused

A lot of developers try to use JWT to avoid server-side storage for sessions. This is almost always a terrible mistake and invites developers to come up with clever explanations and workarounds instead of careful engineering.

The two linked posts explain succinctly why this is a bad move, so I won't delve further into the systems architecture issues. There are more pressing issues at stake: The standard itself is bad and leads to insecurity.

JSON Web Signatures Makes Forgery Trivial

JSON Web Signatures (JWS) is a standard that is supposed to provide message authentication or digital signatures.

In general terms: When a cryptography protocol includes digital signatures or message authentication, if an attacker can successfully forge arbitrary messages at a low cost, we consider the protocol to be completely broken.

Quoting RFC 7515, section 4.1.1:

The "alg" (algorithm) Header Parameter identifies the cryptographic algorithm used to secure the JWS. The JWS Signature value is not valid if the "alg" value does not represent a supported algorithm or if there is not a key for use with that algorithm associated with the party that digitally signed or MACed the content. "alg" values should either be registered in the IANA "JSON Web Signature and Encryption Algorithms" registry established by [JWA] or be a value that contains a Collision-Resistant Name. The "alg" value is a case- sensitive ASCII string containing a StringOrURI value. This Header Parameter MUST be present and MUST be understood and processed by implementations.

We've seen this bite JWS users before, in the form of critical vulnerabilities in most JWT libraries.

There were two ways to attack a standards-compliant JWS library to achieve trivial token forgery:

Send a header that specifies the "none" algorithm be used Send a header that specifies the "HS256" algorithm when the application normally signs messages with an RSA public key.

This isn't just an implementation bug, this is the result of a failed standard that shouldn't be relied on for security. If you adhere to the standard, you must process and "understand" the header. You are explicitly forbidden, by the standard, to just disregard the header that an attacker provides.

What does "understood and processed" mean?

This phrase is repeated throughout the JSON and JOSE RFCs, but a formal definition is not provided.

Today, many libraries interpret "validate the alg header against a whitelist" as satisfying the "processed" requirement. Historically, given the previous token forgery vulnerability enabled by changing the alg header, this does not appear to have always been the case.

While it's tempting to applaud developers for exploiting an ambiguity in a security protocol to improve the security of their implementations, that doesn't fix the standard.

Encryption leaves a lot of room for potential implementation errors, especially when asymmetric (a.k.a. public-key) encryption is involved.

The encryption algorithms permitted by JWE are spelled out in RFC 7518, which comes in two sections:

Key encryption, which gives you options such as: RSA with PKCS #1v1.5 padding RSA with OAEP padding ECDH AES-GCM

Message encryption, which only allows you to choose between: AES-CBC + HMAC AES-GCM



Let's look at some of the key encryption choices in detail. The message encryption part is OK (assuming you actually have a solid GCM implementation and adequate hardware support).

If you're not familiar with cryptography, this is somewhat like pointing a gun with 5 out of 6 loaded chambers directly at your foot and expecting to not end up with a bullet wound. Almost all of these options have one or more security issues.

RSA with PKCS #1v1.5 Padding

RSA with PKCS #1v1.5 padding is vulnerable to a type of chosen-ciphertext attack, called a padding oracle. We discussed RSA attacks in detail here.

RSA with OAEP Padding

RSA with OAEP padding is probably secure. OAEP has a bogus security proof but is far safer than PKCS1v1.5. However, there are serious doubts to the long-term security of RSA itself.

Most cryptographers recommend migrating away from RSA.

ECDH

JWT only allows Elliptic Curve Diffie-Hellman (ECDH) over one of the NIST curves (Weierstrass curves, which introduce the risk of invalid-curve attacks that attackers to steal your secret keys).

If you attempt to avoid invalid curve attacks by using one of the elliptic curves for security, you're no longer JWT standards-compliant.

AES-GCM

Because no list of questionable public-key encryption modes could be complete without shoehorning a shared-key encryption mode, the JOSE standards also allow you to use AES-GCM to possibly exchange an AES-GCM key.

Cryptography algorithm selections should be made by professional cryptographers, not developers. Letting developers mix-and-match protocols and algorithms is the hallmark of error-prone cryptographic designs.

Aside: the recent invalid curve attacks in JOSE mentioned in this section are definitely worth reading, if you haven't already done so.

A Better Solution than JOSE

A better standard would only let you choose two parameters:

ver - Which version of the protocol to use, which defines "one true ciphersuite" for each version; what follows are pure examples: v1: Ed25519, X25519, XSalsa20poly1305, HMAC-SHA-512-256 v2: Ed448, X448, XChaCha20Poly1305, keyed BLAKE2b v3: Something Post-Quantum with AEAD

- Which version of the protocol to use, which defines "one true ciphersuite" for each version; what follows are pure examples: op - What operation to perform: Authenticated encryption Message authentication Public-key authenticated encryption (for server-to-server communications only) Public-key digital signatures (for server-to-server communications only)

- What operation to perform:

Public-key cryptography should be avoided if possible.

The closest we have to a better standard today is Fernet.

We're developing a simply secure alternative, called PASETO (Platform-Agnostic Security Tokens).

Wrap-Up

So in conclusion:

Don't use JWT for session management, as discussed in other articles

The JWS standard is completely broken, and total RFC compliance renders your applications vulnerable

The JWE standard is a minefield that non-cryptographers shouldn't be forced to navigate

JOSE is a needlessly complex suite of standards with security deficits baked in

What to use instead of JOSE / JWT / JWE / JWS?

As stated above, Fernet is an option for most JWT use-cases. In the near future, PASETO will hopefully replace JOSE in the modern web. That being said:

For secure sessions: Just use cookies over HTTPS. Cookies should only store a random identifier which is paired with a server-side persistent storage mechanism.

For signatures: Libsodium's crypto_sign() or crypto_auth() APIs (depending on use-case).

or APIs (depending on use-case). For encryption: Libsodium's crypto_secretbox() and crypto_box() APIs (depending on use-case).

and APIs (depending on use-case). If you need some bizarre stateless property for horizontal scaling your sessions (and are certain you're not making a mistake), see the previous recommendation about signatures. It is overwhelmingly likely that you do not needed crazy horizontal scaling schemes at all, and that a single server-side session management system is sufficient for your use case. We've included this list item for the sake of completeness. However, if by some bizarre twist of fate your project is in the <0.1% of systems, your organization is probably big enough to know how to solve this problem without JWT.



We've previously published material about secure token-based authentication and cross-domain user authentication which totally obviates the need for JSON Web Tokens.

TL;DR

JOSE / JWT / JWS / JWE: Not even once.