Building a Stateless API Proxy
blog.thea.codes
blog.thea.codes
Some critique of the crypto bits, since I'm a crypto person.
1. Do you actually need asymmetric cryptography for this? It seems like at some point the proxy has full authority, and it could just encrypt the token for you symmetrically? (This is valuable because symmetric crypto is a lot less precarious than asymmetric crypto, see next point)
2. Please don't use PKCS1v15 padding for encryption in new systems. It's been known to be busted for about 20 years now. We have workarounds, and they may well be deployed in the exact context you're using it. But they keep breaking, because we just have them to keep the infinite amount of already-deployed software running, not because we think it's fundamentally fixable. This is also the textbook example of a vulnerable service: one that takes ciphertexts and decrypts them on demand. With PKCSv15, I can modify the ciphertext so that _the way you treat the modified ciphertext_ tells me something about how to make private key operations. And in this setup, that means I get the real token, so that sounds bad. The good news is that you've successfully designed around it by adding a signature, so I don't think it's mountable... but. Please, no more PKCS1v15 :-(
3. It feels a little awkward to use JWT for the outside signature but not the inside encryption. But less JWT is a good thing :)
Concrete suggestion: use Fernet (you're already using pyca/cryptography) or libsodium's secretbox and then all of the crypto problems go away. You keep the security engineering dilemma of stateful v stateless proxy (do I want the real token in the proxy at all?) -- but that's another argument.
Thank you!
pycrypto is terrible. The pycryptodome fork fixes most problems in it.
Also, maybe worth sharing you are listed as fourth contributor [1] to the cryptography library, and your web book is prominent on the project homepage, so this piece of opinion may be biased.
>> Concrete suggestion: use Fernet
Please don't. Don't use boutique protocols with informal specs and without test vectors generated by a sufficient number of independent implementations. Stick to RFC-backed protocols. Use JWT with rigid parameters. Even other cryptography author states that just supporting JWT and not Fernet would have been better [2].
[1] https://github.com/pyca/cryptography/blob/master/AUTHORS.rst
[0]: https://github.com/Legrandin/pycryptodome/commit/8675e6f03fc... [1]: https://github.com/Legrandin/pycryptodome/commit/87c2d6aedb3...
The number of cryptographers willing to do hours and hours of free, often thankless, open source work is pretty small, so no, I'm also not going to write up a disclaimer every time I tell someone to use a library. Of course I'm going to work on the projects that I think are doing the right thing.
If the length of the token was important, and you wanted to issue the shortest possible (yet secure) token: what would you recommend? I looked at Fernet but the ciphered text is... massive.
Any kind of encryption is going to make the ctext be effectively random bits, and is going to add a MAC tag that's some number of bits wide, and introduce some randomization (IV or nonce). You can tweak the size of some of those, but I feel the biggest cost you're paying is probably the b64 encoding.
What are you encapsulating this thing in? An HTTP response?
I'm in awe of folks who make the time for the kind of effort this requires.
JWT also has its fair share of security issues in itself: https://paragonie.com/blog/2017/03/jwt-json-web-tokens-is-ba...
Can anybody chime in on whether JWT is absolutely broken as stated in the article, or, while it has some issues, the author likes being a bit too dramatic?
The idea that I need to read the header, which is unauthenticated, to parse the token violates the Cryptographic Doom Principle. Has that led to vulnerabilities? Of course it has: I just said it violates the Cryptographic Doom Principle.
The idea that it has everything plus the kitchen sink -- even for drastically different behavior and opinions on how the world works, from symmetric encryption to asymmetric signing and multiple implementations of each at that, is anathema to modern cryptographic design. Wireguard has one scheme and it does a lot more complicated stuff than "encrypt a session token".
JWT's saving grace here is that few people implement all of it. And ... that's ... cool? Until they do, of course.
You can argue that something is an implementation problem and not a spec problem. Some issues definitely are, but if every major implementation has the same damn bug, then I think it's a spec problem. Unauthenticated headers are a spec problem. PKCS1V15 enc is a spec problem. The fact that an implementation can patch around it doesn't make it not a spec problem. I'm sitting on several more vulns in ~every JWT library that are, to cryptographers, literally too boring to publish even though one of them is _key recovery_.
Other posters have said that it's silly to say that merely the ability to use it unsafely is a problem. But good crypto looks exactly like bad crypto while you're doing it, and there's good crypto that doesn't have that set of problems, so why would you ever choose the poor design?
(Don't use JWT.)
Have you ever exploited a JWT vuln? Which one? Because odds are there's a way it boils back down to the JWT header design choice being silly.
I mean there's an easier way to have this conversation: if the header is "protected", how did the alg=none bug ever work?
The alg=none substitution issues happened because of bad usage of mediocre libraries. Other algorithm substitution can arise for the same reason. The invalid curve attacks were the ones that the spec didn't call out as a security consideration.
I support the arguments that say algorithmic agility is a bad idea and a new protocol with algorithmic agility shouldn't have come out at a time when other protocols (like TLS) were finally starting to catch on to this fact. But the JWT cat is out of the bag, and won't go back in: it's widely deployed and people are using it thinking it's solving problems they actually have. Education is the proper remedy.
The PASETO effort attempts to provide better answers and better design to an audience familiar with JWT, but there's also been an uptick in the kind of advice that heavily condemns JWT without supplying some migration paths. That latter brand of advice is harmful.
And, finally: we’ve put together an extensive list of recommendations, repeatedly, both in general and in the articles on this thread.
Yes, it’d be a smaller payload and less CPU to use EC over RSA, but EC still isn’t the least common denominator. I speculate the author is optimizing for comparability over performance which is a perfectly valid trade off to make in a blog post :)
My team is successfully using it to track ~200k issues/PRs across ~300 repos. We wrapped our deployment in a minimal API to give our infra blazing fast access to that data without needing to worry about GitHub auth or rate limits (since Maintner handles that).
And, we use the proxy described in the article.
Of course, in your case, it might be debatable in terms of utility - if you're trying to replace a scan, you probably are trying to get results as "up to date" as you can - which would be prevented if the cache avoided doing that! The cache would have the same rate limits, and so you would be just as well off adjusting the frequency you currently scan. Of course, if you can't control that frequency (perhaps multiple uncoordinated scanners) a proxy is one way to give you that control - so maybe useful!
This would remove the needs of a signature altogether.
If you are encrypting using an AEAD cipher like AES-GCM or ChaCha20-Poly1305 then it is already built in. But AES-CBC and others need an explicit verification on top.
Edit: nevermind, I think I've just misunderstood the process outlined in the article. I confused the tokens.