An easy enough mantra: sign bytes, not semantics. I've trudged through the bowels of SAML long enough to know that canonicalization is thoroughly tricky and annoying to deal with.
I don't care what you throw in the bytes; I can figure that out easily enough. I want to know who generated those bytes and that they haven't changed.
For this reason also, I think the "reach for symmetric first" is bad advice. The primary benefit I can think of for HMAC is if you want to store client-side state for more stateless browser-targeted services. It makes it easy to throw an obscure cookie to your client and then validate it on the way back. It seriously complicates things if you want to allow other people to validate provenance of those bytes. With symmetric crypto, as soon as you can validate you can also generate. That's not the system we want.
You say that's not the system you want but that's based on a pretty big "if": you need "someone else" to validate something, and those people can't talk to you directly and ask you if it's valid or not. That's not in practice how we see this being used.
If you do need third parties to validate, a much simpler API is to just ask the thing that holds the HMAC credential over TLS, and have it return true/false. That makes it harder to demonstrate non-repudiation, but I'm not convinced that's a property you generally care about. Even in OIDC (which mandates a JWT, but for reasons that defy understanding doesn't mandate cryptographic domain separation between (IdP, RP) pairs), leading OIDC providers have long recommended you just talk to them over TLS to get userinfo. (GSuite has recently muddled this in their docs, which irks me.)
> holds the HMAC credential over TLS, and have it return true/false
And if you're connecting to the original party anyway might as well not even use HMAC or any crypto at all. When "signed" data needs to be sent to the client don't send it at all, instead store the data locally under a GUID and send that GUID to the client. When client takes their GUID to another party, that other party connects to the original party and retrieves perfectly authentic data. Free bonus - faster client performance.
Re: OIDC providers: to be clear, you absolutely can just parse the JWT. I'm saying that (up until recently, if memory serves) e.g. the GSuite documentation told you that while you can validate a token, that's hard, and instead you can just hit this HTTPS API and we'll tell you what's in it.
(They seem to have flipped that in the last, I dunno, 3 months or so?)
Unless my memory is failing me, in the Google case it was really /userinfo (as in the OIDC Core UserInfo endpoint) and not token introspection (a "post-auth" endpoint taking a token, not a "pre-auth" one you pass a JWT to). Though I agree that from the perspective of a consumer the two are basically just as good :-)
But what about myself wanting to validate the payload on a system I don't necessarily trust? In the symmetric case I would grant that system the ability to sign.
What's the model where you don't trust that system not to exfiltrate data but you do trust that system not to flip the boolean that says the signature is valid at all?
I worked on system that used HMAC to authenticate (similar to what AWS signature does, which has the nice priority of never transmitting the secret), and the reason why I feel it would be better with asymmetric signature is that HMAC forces you to have the secret in clear server side, which means they can be stolen, while with an asymmetric system that would not be possible.
Of course the ideal system probably use asymmetric signature to get a short lived secret used to sign all the requests of the session :)
Signing your updates for example.
If the system is breached, theres nothing you can do, but that access does not translate to forging updates for other users.
Oh, sure, yes, absolutely, software updates is one of the cases where you probably just want an (asymmetric) signature. (And probably TUF.)
But my alternative suggestion also addresses that: in a model where you don't trust the host with the key but you do need to help it make a determination if a particular thing is valid or not, you could just go ask the server you got it from instead. Basically: the key doesn't have to live on the machine doing the validation.
I thought the point of doing validation locally with the key was mainly performance? You do this mostly to AVOID the round trip of having the 3rd party do the validation (sub-ms to do validation with a shared secret or rsa key; vs multi-ms and complexity for an HTTPS API call).
If you get a malicious update from a compromised update server and then ask said server if the hash you got is correct, you might as well just roll a dice. You'd need a pinned certificate (e.g. for HTTPS) to prevent MITM and thus asymmetric crypto anyway.
Partially yes. You don't need a pinned certificate, just working certificate validation. I am not suggesting you should never use asymmetric crypto: you should definitely use HTTPS. The question wasn't "is asymmetric crypto morally good or bad", it's "should I use asymmetric crypto in my code to do bespoke signing things". The asymmetric crypto in TLS is fine because you're not doing bespoke signing things: you're doing the most well-understood handshake + protocol on the planet.
Pinned or validated, you need to know that you can trust the server locally. I was just addressing the "ask the server you got it from instead. Basically: the key doesn't have to live on the machine doing the validation." part in your alternative, signatures seem kind of moot if you cannot verify the server is who it is supposed to be from the client's point of view. There's no benefit of signatures over any regular hash here, right?
Right. As I mentioned, you're just moving the signature from the blob itself to HTTPS, and if all you care about is validating a file you'd just make that a hash. Analogously to a SHASUM file, but the authentication coming from HTTPS/WebPKI instead of something ending in .asc.
One critical point is that the two servers do not have to be the same. You might distribute your files via a CDN like CloudFront (or if you're in the 90s and a Linux distribution, a ragtag team of servers that don't generally implement https). The server responsible for delivery can lie all it wants; the server responding if something is valid is what actually matters.
It's a good mantra--just not a wish that is applicable to the foundation of XML as a tree/document thing that self-describes its encoding and is capable of including its own signature within itself. I mean, it gets crazy complex, but the alternatives sort of require going on to some message format that isn't XML. One of the design goals of XML is that you could zip an XML document along through different systems, one of which supports only UTF-16LE, the next demands UTF-8, the next demands ISO-8859-1, and each could do the right thing. I mean, it's almost inconceivable that all the systems involved in such a quagmire would actually do the right thing, but they could, in theory. I'm sure you're already dreadfully familiar with this having looked at XML-DSIG.
OTOH: base64(jsonString(envelope)) '.' base64(jsonString(claims)) '.' base64(sig) works fine for JWT, because JSON has a very much limited choice of encoding (one option, zero alternatives!) and a JWT has no requirement to still be JSON on both sides of the transformation. It's just an opaque 7-bit-safe string to any party that doesn't know what the contents are.
I do think whoever is coming up with the scheme for assembling those bytes should care a lot. Specifically, whenever you're signing something, you have to pay attention to whether that something still has exploitable ambiguities. Most of those come from how you delineate the fields in the thing you're signing, which is a classic "you find out what you missed when someone sends you a CVE" type of problem. So you're right back to the problem of "if not JSON or XML then you're still rolling your own."
I agree with "reach for symmetric" in this sense: what are you trying to accomplish? Prove the data came from a specific source or that they are who they claim to be? Can you just use TLS with mutual auth? Prove the data came from yourself? Symmetric is probably okay.
When you need verifiable attestations of data from parties that don't talk to each other... welcome to the jungle.
I recall this plaguing the Secure Scuttlebutt project -- they were signing JSON objects that were encoded however Node saw fit to encode them, which meant that in order to write an alternative implementation of the protocol, you had to encode all the various objects exactly how Node encodes them, which is (unsurprisingly) non-trivial. I wonder if they addressed this in a later version.
If you're serializing the JSON, that is UTF8. You're signing UTF8 bytes... don't mess with them, and the signature will be the same... also, for JWT, the UTF8 is then converted to base64 representation and tethered to the signature. You're signing the UTF8 bytes, not JSON. It doesn't matter how it's serialized, if it isn't UTF8, you're doing it wrong. The order or properties doesn't matter, the signature is on the bytes.
which means that you have to be careful about how you remove the signature in order to verify the original. The "main" node implementation does this by parsing the json, removing the field, and then re serializing, forcing any alternate implementation to exactly match the node serialization in order to be compatible
as far as I can tell, it's cause they are hardcore JS devs, where JSON is seen as almost a part of the language, and little to no effort was put into things like future-proofing. A lot of the failings have been fixed as the community grows, but this one is core to how the whole thing works, so changing it at this point is fairly non-trivial (would basically involve completely breaking any kind of backwards compat, and potentially even some forwards compat)
probably they did that, then Roy Fielding came in, unplugged their router and wouldn't let them back on the internet til they stopped polluting their resources with metadata
At least when I implemented it, it was the full message, as a complete valid json object. The signature would get added as another field, and then the object reserialized.
This is why JWT is better... JWT signs "header.body" with each part as base64 from the JSON, and joined with a period. The content in the body and header are immaterial.
It's not "better", it solves a materially different problem. The article already acknowledges that if you can afford to, you should just stick a tag on the outside. (That's what JWT does, but the actual thing recommended, just HMAC, is better still, for reasons mentioned elsewhere in the comments.)
This way you have full control over the raw bytes you want to sign (by forcing them into Base64 where other systems can't get their dirty paws on them).
I guess the problem here is if intermediate systems want to do stuff based on the payload (but without validating it), they won't like this.
But if the problem is just intermediate systems barfing on non-json, this might work!
p.s. enjoyable blog post - as they always are! ;-)
Yep! That works, but it's essentially the first option ("How to sign a JSON object") but with JSON as the outer serialization format instead of a comma in the middle.
You also correctly identified why that is different from the other schemes: they don't change the structure of the outer object.
You could also serialize the json with a placeholder string (All spaces or zeroes or something), calculate the HMAC, and substitute the string. You could then do that in reverse on the receiving end. The deserialization could easily note the offset of the hmac, which could then easily be verified against the original bytes.
The original spec said UTF-8/16/32, but unaware of any reference implementation that used anything other than UTF-8 ... though, who knows with hand rolled crap, and windows in the mix.
That's true if you have the luxury of a traditional tag on the outside, but falls apart when you have systems where signatures are in-band, like SAML, which is where canonicalization shows up. (Both for UTF-8 itself, which isn't canonical, but especially for your serialization format e.g. XML/JSON, which is usually far hairier.)
If you treat the original UTF8 as bytes, then you still have that as bytes... this is part of why JWT uses base64 around the JSON string's bytes. In any case, you don't create a signature on a string per se, you create it on bytes, even if those bytes are derived from a string. Also, you don't validate a signature by re-creating the bytes in question... that's a flawed approach, and not the approach for example JWT takes.
> In any case, you don't create a signature on a string per se, you create it on bytes, even if those bytes are derived from a string.
Everyone is on the same page that at some point bytes go into a hash function. That's not the problem.
You mention JWT, but JWT only does external signing, which doesn't trigger the problematic case several people are describing to you. Perhaps an example would be more useful. If you start with a JSON like:
{"a": 1}
how do you build a JSON like:
{"a": 1, "tag": "deadbeefdeadbeefdeadbeef"}
with a signing and verification algorithm that works?
> Also, you don't validate a signature by re-creating the bytes in question... that's a flawed approach, and not the approach for example JWT takes.
Can you describe an HMAC validation process that doesn't involve recreating the bytes in the HMAC tag?
That is literally the first thing suggested in the post. You can say the other thing is a "flawed approach" but that's the design problem being solved, so your answer is simply not responsive to the question.
Don't worry, multiple people (including me) have told the community in question that it's a flawed approach. Doesn't seem to have done much to fix it though... :shrug:
As one of the people that built a alternative implementation, this is completely true, but also much worse, cause there were 2 things around crypto, signing and hashing, and one encoded it one way, and the other encoded it a different way (encoding as far as utf16 and such) which meant that for ascii, everything just worked fine, but as soon as you started using non-ascii, everything got 2x as complex.
I'm sort of skeptical of the idea that a bunch of people hand-rolling HMAC stuff is going to contain fewer security issues in the long term than widely-used, tested JWT libraries. Sure if they do exactly what you say here, and nothing else, that might work. But what happens when they realize they need some minor tweak to it…
That is a healthy skepticism! I definitely would not counsel people to do it with primitives in general: it just turns out that a PRF doesn't have a complicated API and HMAC is bulletproof. More or less the only design criterium that comes to mind that is critical is "don't do anything before you validate the MAC", and that design guideline is hard to break when you use the suggested approach.
Beyond that, hand-rolling JWT isn't so hard at all. When I read about some of the exploits in certain libraries, I didn't have to worry about it, as I only supported the known authority's public key in the system I was working on.
Of course, that makes the header effectively useless in practice.
There are two obvious problems with this approach to engineering:
1. You don't know what you don't know, and there is a lot to know about cryptography beyond the minimum needed to interoperate with other systems.
2. If you're engineering seriously, you know people are going to inherit your code and your design down the road, and if you're relying solely on a minimal feature set without a coherent, informed design, those people are building on sand.
It's not like he is rolling out his own crypto library. He's just rolling his own token but encrypted/signed with industry standard crypto.
It's not terrible to "roll your own JWT" if you don't actually care about interoperability. And he's right, it does sidestep a lot of issues because JWT and corresponding libraries are designed to handle far more use-cases than what he may need it for and therefore if you don't fully understand it all, you may be shipping with unsecure configuration.
If you write your own library to do it, there are strictly more things that can go wrong because at some point you're implementing a HMAC validation thingy. If you use a third party library, you're exposed to bugs that stem from JWT's design flaws even if you only allow a specific key, like the RS256 v HS256 bug I mentioned.
You are definitely right that if for some reason you must do JWT, the way to do that is to strip as much of it away as possible. In particular, if you wanted to do safe HS256-only, you'd ignore the header, decode the body and tag, and validate the tag.
To be clear, I didn't write a cryptography library... I wrote a JWT wrapper for validating JWT for the system in question. This was also before there were JWT libraries for a number of platforms. Beyond all of this, the JWT validation worked across a number of tokens generated from an alien (to the app in question) application, and rejected when it didn't validate.
Also, it was literally only validating the tag and ignoring the header... it was using an asymmetric key for signing
A fun read and primer on the subject. However, I would like to see more emphasis on including a time stamp as part of the signed payload. The reason is multifold: (a) Most importantly prevents ‘replay attacks’ [1]. Systems that need to send send the same command will do so with a unique time stamp and, therefore, a unique signature. (b) The time stamp + signature is an idempotency key. Your key cache’s TTL is the same as the timeout for your time stamp rejections. (c) To a lesser extent bolsters the system against a brute force length extension attack (see the Flickr API flaw in the reading) by reducing the time the middle-man has to correctly calculate the glue bytes.
It's amazing to me that the whole article glosses over issues around key storage for symmetrical encryption, not to mention now you have an O(n) liability issue with all of your readers.
I think the scenario they're envisioning is one where the server generates an object, and some time later, wants to make sure it's getting back an object that the server has previously generated. HTTP cookies are a good example of this. Anything where you'd reach for JWT is probably also this use case.
> his post is mostly about authenticating consumers to an API. ... you’re trying to differentiate between a legitimate user and an attacker, usually by getting the legitimate user to prove that they know a credential that the attacker doesn’t.
The recipient of the API key doesn't need to verify their object. There's no attack from being able to give someone a fake API key - any attacker in a position to modify the API key in transit, which would just be a DoS, is also in a position to drop the connection, which is also a DoS. Such an attacker is probably also in a position to steal the API key silently, which is a bigger problem. (If a client is really curious whether they have a valid API key, they can just make an API call with it and see if it works, they still don't need to actually check the signature.)
I don’t think that’s a fair assumption. Google IAP puts JWTs in headers to attest the end user’s identity in a way you can’t just forge from inside the firewall. Other use cases have you obtain a JWT from a dedicated auth service, maybe even a 3rd party provider, so that other random services don’t have to know about passwords and 2FA. In both cases it would be prohibitively expensive to call back to the auth service to verify each token. The point of using a standard like JWT is to make it intelligible in other codebases, otherwise you could make your own format and serialization.
+1 on this. With asymmetric signing, you don't have to spread a secret far and wide. You could go a step farther with client certs, but still would need a more heavy management interface and a CA setup against your applications which means quite a bit more complexity.
We should write the "The JWT Problem" post one day, but this post isn't that, so I really don't want to derail the discussion.
The short version is that there are flaws in the JWT specification that make certain bugs likely. A classic example of the "you have to parse a header to use the JWT" problem is the HS256 vs RS256 confusion bug, where your JWT library would interpret an allegedly-HS256 (HMAC) JWT using RS256 (RSA) key material. The JWT would get validated using the public key of the RSA pair, interpreted as an HMAC key. But the public key is, you know, public! So the impact of the bug is that everyone can forge JWTs. That is not a problem that can happen in well-designed schemes.
Well, it does makes sense to me that if JWT allows for both symmetric and asymmetric tokens that's a complexity detail that can be gotten wrong when implementing.
Thankfully, JWT being quite standard and having momentum behind it I think there's a lower risk in recommending someone to pick a popular JWT library than telling them to roll his own simple scheme on top of HMAC (which is the article's recommendation and what JWT will end up doing regardless), specially when scale is considered.
I know, I know I'm arguing for "worse is better", but I honestly can't imagine a clean solution to this that would be so good it justifies dropping a seemingly decent standard, leaving mountains of legacy and requiring the entire developer world to learn about yet another crypto scheme. But then again I'm no expert in that area, and I would love to read a post about it from someone who is.
The nice thing about HMAC is that it's so bulletproof and easy to use. There are no footguns you introduce by using HMAC directly instead of using HS256, and there are plenty you introduce by using HS256 instead of plain HMAC.
Assuming a JWT implementation accepts only a fixed header (all header fields must be present and match, no additional fields can be present), are there any other issues with "just use jwt"?
I do like using JWT.
But its point is to offer flexibility. If you fix the entire header i.e. use a single signature method, you might just as well concat that signature directly.
In other words if you stop utilizing JWT, you won’t have JWT specific problems.
I have a need to provide an update service where client software (that I control) can request download files, but any other clients cannot download the files.
Currently using a terrible method with hashed query strings based on date, path, and a secret, which are then validated and have an expiration. Also, HTTP, so yeah it’s broken, but it at least prevents drive by scraping.
At this point we have no need for assymetric (don’t need to identify the requester, just need to prevent spoofing). What method of securing would you recommend?
1. Step one is HTTPS. Since you control the client, ideally just one ciphersuite. Maybe pinning? But given that it's HTTP now, maybe this this is running in a production environment that's hostile to good ideas? (Of course, plain HTTPS as usually configured doesn't authenticate the client.)
2. Step two is a bit more complex. I assume your clients hold the secret, know what path they want to hit, and compute the key that way?
Tell me a bit more about the clients: what are they implemented in? What do they run on? Can you reliably ship updates?
(I'd prefer to have this discussion here but if you can't discuss publicly I'd be happy to take a look. My contact info is in my HN profile.)
> But given that it's HTTP now, maybe this this is running in a production environment that's hostile to good ideas?
How did you know! ;)
I’ll probably hit you up on the side, but there are multiple clients each with their own technical debt, and it’s an old solution, but I’m putting things in place now to make changes more possible, maybe per client app using separate hostnames, for example, so that we can transition to the new without breaking the old.
I think most of the client apps actually hit a manifest API first that gives out signed urls. This already happens over https in most cases. Some may generate their own, but I’m not sure all the usage scenarios.
I can’t give more details on the clients here, but let’s just assume they are diverse and difficult but not impossible to upgrade. It’s the kind of thing we want to get right the first time and has to work for a decade.
Okiedokie. That sounds like catnip to me, so please do. Happy to sign (non-silly) mNDAs if that helps.
Probably the most useful thing for me to know is: what's the algorithm for signing a URL? If everything uses a manifest API, can you just make that a random token instead of a signature, and store that token in a database somewhere with an expiry?
Why not just use client certificates? (Not a rhetorical question. Client certificates seem like the “obvious” solution so I assume there’s a reason you’re not recommending them.)
It's a fair question, but in my experience client certs run into all manner of roadblocks in corporate or complex environments.
TLS terminating LBs/WAFs/<things> that cant authenticate the client cert or pass the public key through to something that can, dealing with key/cert expiry, nobody to run the PKI infra with any interest managing identities of things that aren't AD computers, you name it.
Yeah, my default would absolutely be client certs. That's where my question about "an environment hostile to good ideas" comes from -- I was assuming the lack of HTTPS was there for a reason.
Mmm. I really like mutual auth (TLS client certs) but one caveat:
Now somebody needs to manage the resulting PKI.
Even what looks like the no effort case, where you punt to the Web PKI and have all the clients use Web PKI certs (ie client1.example.com needs a cert with SAN dnsName client1.example.com like a web server would have but making sure the EKU for client auth is asserted) means now you have to either keep pace with us, or risk an impedance mismatch if our policies change in a way you're not OK with.
If you use one or more private CAs there's a tension between on the one hand the convenience of it not being your problem, and on the other hand the risk that it turns out you were just engaged in theatre and there's, for example, an unsecured SCEP server that will happily give any adversary with network access an authorised certificate with whatever identity they ask for...
Simpler: just let everyone download an encrypted version of the files. Then only allow your client software that you control to decrypt the downloaded files.
Encrypt the files using AES-GCM. If you trust that your own client software is distributed securely and won't run in hostile environments and be reverse engineered, just ship that AES key with your software. Otherwise it will get complicated fast.
> We do have a blog post from last year that tells you what we think you should do if you want to be safe and you know what kind of abstract thing (e.g. signing, MACing, etc) you need
A little bit off-topic, but what is your opinion on NSS versus OpenSSL? Or LibreSSL and BoringSSL?
What do you want to accomplish? Do you want good implementations of cryptographic primitives, or something else?
As a general rule: OpenSSL, BoringSSL if you can get away with it, don't use NSS or LibreSSL. But that presupposes that using a library like OpenSSL was the right answer to begin with :)
I used to be a proponent of NSS, when people who were much more closely involved with TLS implementations were quite down on it. NSS was the first TLS library I really read "cryptographically" (it hosted the Bleichenbacher e=3 signature verification bug, which made Firefox vulnerable to signature forgery) and I found the code really easy to follow, unlike OpenSSL's. Ultimately, they were right, and I was wrong: in this space, you want a lot of attention and effort on your TLS library, and OpenSSL is the most seriously maintained TLS library. For the same reason: OpenSSL over LibreSSL.
I would be interested to hear your thoughts on PASETO, which I increasingly see as a replacement for JWT -- I just haven't had a chance to use it in production yet, myself.
PASETO is a marked improvement and doesn't have the specific problem mentioned here, by using versions instead of an attacker-controlled alg field.
One of the things that makes "The JWT Problem" such a hard post to write (and not one we've already just written) is that the cryptographic flaws are but one problem; architecture implications are another. So, to say "yes go use PASETO" instead implies that something "like" PASETO or JWT is even the right answer, and in the vast majority of cases where we've seen JWTs applied at Latacora that has not been the case.
How is the (attacker-controlled, no?) version & purpose field in PASETO not subject to the same issues as the JWT header? (Are we just lucky that none of the currently existing version/purposes can conflict?)
The issues folks have around JWT's header field always seem to be interface issues: libraries need to understand what keys they can use to validate, and what types those keys are, and validate them against the header.
(Though, I do not know why there is a "none" algo. That does seem like a folly. Could we recon that in the RFC?)
actually you can just never send the header and always assume that the header is `eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9`
{
"alg": "HS256",
"typ": "JWT"
}
(or whatever algorithm you wish)
of course only "internal" clients would understand the jwt that isn't according to the spec.
You can do that, but you carry a risk of someone developing an internal client one day that does actually trust the remote header, because they're using an off-the-shelf library. At just about everywhere I've worked, there have been efforts to do some sort of new implementation of a client to existing APIs, either because a new language / framework got cool since the API was originally designed, or simply because there's also a new server but you need backwards compatibility with old servers (The only exception I can think of is a brand-new startup.)
"JWT, but you must roll your own implementation to avoid security risks" is strictly more dangerous than "Our custom signing system," because at least with the custom signing system, people are going to read your spec and not JWT's.
well you can use most JWT libraries with alg: none but specify the alg (so not none alg) it still is against the spec, but it works in over 70% of all libraries.
Not for nothing, but JWT support in C is really bad. I have an embedded device that I considered JWT, but it didn’t look great for supported libs in C.
So knowing JWT exists, this is still of interest to me.
> Canonicalization is a quagnet, which is a term of art in vulnerability research meaning quagmire and vulnerability magnet. You can tell it’s bad just by how hard it is to type ‘canonicalization’.
The funniest line from this article and my word of the day.
I'd go one step further: If you're in the situation that allows for HMAC over asymmetric signatures, you might as well go all the way with authenticated encryption instead of purely a MAC. Even if you have TLS, you still gain a minor benefit of having black box tokens to everything outside the system, in particular if you have to let untrusted or semi-trusted clients hold on to signed data that they needn't know the contents of. Yes, security through obscurity is not sufficient on its own, but can help stall analysis and exploitation efforts,
While integrating with a large company's SSO protocol (with a cleanroom implementation), I found that the off-the-shelf "standard" XML DSIG canonicalization code they were using (from a major vendor) actually was not compliant with the W3C spec. That was unpleasant to have to discover and then explain.
More about use case than how to do it: client side certificate authentication is not a rocket science.
Unless you 100% need to use signing for the use case like a client side ACL, there is genuinely no need to overengineer your web app with your own authentication scheme.
But if your scale already mandates doing operations without server side ACL lookup for performance reasons, doing it over HTTP and web stack might already be inefficient by itself for the task.
Client certs are great if you control the client. If, say, Slack told me that to use the Slack API I'd need to get a client cert, I would laugh.
One thing that comes to mind is that either you have to check revocation on the server side, or you have to re-provision client certs frequently, and in most client libraries that's actually difficult / annoying. If the server just sends me an updated token, I can just put that in a local variable and call it done.
There's a reason most APIs have moved to "you can just put the token in the request payload". It's hard enough to ask people to set HTTP headers, asking them to set client certs will be super complicated.
Two, I'm talking about client libraries, not web browsers. Every client library (including those used by web browsers) is perfectly capable of passing a query parameter. Most can pass cookies or custom headers. Not all of them can pass client certs.
> Maybe you don’t need request signing? A bearer token header is fine
If you're going to insist on symmetric key signatures, yeah. Otherwise a symmetric signature would be the same as using symmetric encryption to store user passwords, wouldn't it? You have to have the secret key to verify the user's signature.
The article appears to say that you must sign your requests with a symmetric key. To verify the request, the server needs the same key, right? So the server needs a copy of every single user's encryption key.
The server _could_ just store the keys plaintext in a database but I'm assuming we can agree that's a horrible idea. The best it can do is encrypt them symmetrically before storing them, using an extra-special secret key that needs to be protected very carefully.
With username/password authentication, we don't store symmetrically encrypted passwords, do we? We store a one-way hash of the passwords instead, because symmetric is deemed not-good-enough.
The server is trying to validate that the thing it gets back is the thing it previously sent out, so that's a different model. What concretely are you suggesting we store as if it was a password? The serialized JSON object?
I'm beginning to think you're trolling, but... I'll make one last attempt to explain: How does the server verify the signature? Does it just wave a magic wand? Doesn't it need the same key the user used to sign the request?
As I mentioned in that comment: the thing sending is also the thing receiving, just at a later point. So "the same key the user used to sign the request" doesn't seem to apply.
It seems like you're describing a specific use case that is different from what most readers think of when they see a post about cryptographic signatures.
If the server generates the token, and all it's effectively doing is verifying that the client has the correct token, then what is gained by making that token be a signature of any sort?
You no longer have O(n) server-side storage proportional to your number of clients, the way you would if you put each token in a database. You also no longer need to worry about consistency + availability of that database if you have multiple web servers. If one web server in your cluster generates a signed token, and another one verifies it, they have the same key (they have exactly one key, among all of them) and it works without any communication or storage between the web servers.
That's fair, although it doesn't allow tokens to be revoked or to expire. For that you'd need to store at least some unique part of the payload you're signing, and then you have O(n) storage requirements again, right?
Add an expiry time to your signed payload. Tokens can have metadata.
> If the server generates the token, and all it's effectively doing is verifying that the client has the correct token
I think you might be understanding "token" as the API tokens that are just a random bunch of bytes that are later matched to authenticate like they were a password (let's call them "passtokens", I don't know if they have a name).
Tokens can be far broader than that. For example, JWTs contain arbitrary data. Verifying that the token is valid is just verifying its signature. You wouldn't check that the passtoken matches (in fact, you wouldn't have a passtoken at all). The payload is where the sauce is at.
A payload can include anything, from an user id (which is similar to the passtoken use case, authentication) to a list of grants (so you wouldn't even have to hit the "users" table to check for permissions... or even have access to it! As long as you can verify the signature.)
> to be revoked
That's true though. This is usually handled with short-lived tokens that must be renewed periodically.
Alternatively you could have a Token Revocation List of some sort (which isn't O(n) storage since 1. not all tokens will be revoked and 2. you can purge expired tokens). But then you get the problem of synchronizing the TRL across services (or centralize the token verification in a service which IMHO kinda defeats the purpose).
The way I'd approach this is to treat "revocation" as meaning "after this user's current tokens expire, they can't refresh them," and make the token lifetime short enough that you're comfortable with it. Document that any API call can also include a "new_token": {...} field, and the client should update. On the server side, only try to refresh the token when it's close to expiring, and don't fail the API call if you can't reach the revocation server (just skip refreshing).
You still have a central server to track account status, but now it can be more like "a text file with a list of usernames on a single box running Apache, if it crashes we reboot it" and less like "a distributed, high performance, highly-available in-memory K/V store that's in the critical path for every request," which is going to make you a lot happier operationally.
Or you can push the list of usernames to revoke to each server, or something.
the user isn't signing anything. the server is signing something it sends to the user. when the user sends it back to the server, the server checks the sig to make sure the user didn't alter/forge it.
> The article appears to say that you must sign your requests with a symmetric key. To verify the request, the server needs the same key, right? So the server needs a copy of every single user's encryption key.
Correct. Usually if this was in a app and they use a symmetric key and they wanted to sign requests from a first-party app, in the real world this key is heavily obfuscated to ward off reverse-engineers from lifting the secret. The server will then decrypt this request with the same symmetric key to determine if it is indeed from the client and not a third-party.
As the author has outlined in the article, some of these API services use standard algorithms to do this such as HMAC while other services go to the extreme to use whitebox crypto + obfuscation. This is just security by obscurity, but it is for the purpose to slow down the attacker.
For example, when a app developer release a new app that uses a new API version, they can rotate the keys to slow the attacker down and can keep compatibility with the v1/v2/v3 versions with different keys and can choose to deprecate a endpoint without breaking the app.
It's not JSON, but I had a scenario like this where I wanted an in-band checksum on an archive of files. In the end it was indeed a file with the signature included in the archive itself, and the formula for computing it was basically the shasum of the shasums of `file .` sorted alphabetically, with the signature file itself excluded.
That worked out just fine, but I can see the argument that it's much harder to get to a canonical JSON representation than it is to get to a canonical "tree of files" representation. Indeed, it was easy enough in my case that the repo contained a shell script one-liner that would compute it, and that was the reference against which the "real" python implementation was validated.
That sounds like you found an alternative format where finding something canonical to sign is easy (files, the shasum CLI) and punted on canonicalizing the in-band signature (which is good!).
A super-awesome related project I didn't get a chance to work into this post (because it's entirely deserving of its own): https://github.com/benlaurie/objecthash
To be clear: I think that's a niche use case and while I think ObjectHash does a great job of exploring it, I don't expect the median startup to need an ObjectHash implementation.
If there's anything I said that made you think otherwise, let me know: I would like to amend that so no-one else thinks I could possibly mean that. The initially recommended (unless you can do otherwise) approach in the blog post is clearly "tag at the end" and every other approach also validates first. If you're referring to ObjectHash: like I said, it's a very niche application, I don't expect people to use it, and yeah, it enables new use cases.
(I expect you'd still really be authenticating the ObjectHash somehow -- e.g. by sending it over TLS -- but that's out of scope for ObjectHash itself.)
Yes, I was referring to ObjectHash of course, since it's in the sub-thread about ObjectHash.
"I don't expect people to use it" just seems like the sort of awful excuse you'd usually be jumping on people for. It's like someone built a github project with a bunch of crypto red flags to check whether their new "Search github for projects with crypto red flags" idea works.
Don't get me wrong, it's clever, and I like clever. But I have learned in cryptography to only accept clever when it is clearly in the service of a specific pre-identified goal, and not just for its own sake. Isn't that normally a philosophy you'd subscribe to? What's the _pre-identified goal_ for this thing?
While we're here, another red flag. Mentioning Certificate Transparency as a model for some other X Transparency. Certificate Transparency isn't a model for anything. People have been saying to themselves almost from the dawn of CT "Oooh, this is clever, I should do the same for X" and it's always a bad idea. Someone might need a Merkle Tree. I'd argue they shouldn't use ObjectHash anyway. But the chance they need all the other paraphernalia from CT? Basically non-existent.
Seems that you just need to go ahead and build a canonical JSON encoder in JS. It would be slower but hey, you need the same consistent algorithm for signing. The encoder would be part of the spec.
Not in the sense that the term of art "canonical", as in canonicalization, is used. UTF-8 is not canonical: different byte sequences can map to identical meanings, as covered in the post.
I'm really confused by some of the assumptions in this whole thread, sorry. In what scenario would a client touch the string it authenticates and parsed JSON from before sending it back to the server later? This argument seems to assume that I have to throw away the string I've parsed or somehow reconstruct the same JSON and create the HMAC myself locally, which seems odd.
I think I may have answered your confusion in a different thread (I'm not sure I parsed your comment correctly though), but: this is about a problem that specifically occurs when you have JSON (or some other structured format) that needs to have the signature in-band. You're right that it's way easier (the first list of three bullet points, as you mentioned inthe other comment thread) if you can just shiv the tag on the outside.
Perhaps a more familiar case where this happens is SAML assertions with inline signatures?
Yeah, thanks, since you likely encountered these scenarios I guess we're just looking at it from very different viewpoints and some of mine might be lost in translation.
Luckily the number of times I've had to invent signing schemes or even integrate SAML is limited. :)
The OP notes that there are multiple competing (well, probably?) specifications for a canonical JSON and they are annoying to implement. JSON is neither well-designed nor well-specified [1] and it is likely that we will never see the official canonical JSON format anyway.
I actually prefer JWT with an asymmetric key. Anyone can confirm the payload's providence. HTTPS takes care of encryption for the payload. Of course with JWT, only allow trusted keys or signed keys from a trusted CA.
There have been some poor implementations, but the method is pretty sound.
{"msg":"I am a God. My name is Bob.",
"sha256":"78A873E..."}
where the hash is the checksum of the file including the checksum. It would be equal to finding fixed point in cryptographic hash function that happens to be checksum to your message.
A fatally flawed post that dooms it to the category of SEO rather than what should be useful information. I did especially like:
> Canonicalization is a quagnet, which is a term of art in vulnerability research meaning quagmire and vulnerability magnet. You can tell it’s bad just by how hard it is to type ‘canonicalization’.
The flaw?
There's nothing here not already well known, so this isn't an insightful piece for those already well versed. Therefore, this is a piece written for those that aren't so practiced, for the sake of discussion: junior dev or devops, or non-security devs.
The content itself is a good discussion, and digestible (lol).
But, as a piece that junior folks are expected to get a takeaway from, the introduction is a disaster:
> This post is mostly about authenticating consumers to an API.
ie, not service-to-service auth.
> Unless you have a good reason why you need an (asymmetric) signature, you want a MAC.
A MAC/HMAC requires the signer and all verifiers to have the key. As stated just prior, this is about "frontend" signing. A novice reader might not realize they have to guard the key very well, and might even send it to the client browser. "Unless you have a good reason" is not a sufficiently strong warning for a post that is written as an instructable, more or less.
More architectural introduction (bonus with diagrams) is required. As is, this post is a footgun.
Not sure why you are downvoted, about 10% of the article addresses it's headline and I frankly do not get the point either. The proposed solution is in the first numbered list and between that and the conclusion list is a bunch of prose on non-JSON payload.
The only slightly JSON related content in there is a constructed scenario for 'in-band' signatures (the regex thing) which can just as well be achieved by a bit of string processing. Any JSON object will start with {, end with } and have some more information between it. Replacing the initial curly brace with '{"hmac": "foo",' gives you a valid JSON document. You can remove that easily before parsing and place no restrictions on the object's keys. You can handle edge cases like JSON literals or arrays by wrapping the whole thing in {"hmac": "foo", "payload": yourstring} if you feel like it.
> Not sure why you are downvoted, about 10% of the article addresses it's headline and I frankly do not get the point either. The proposed solution is in the first numbered list and between that and the conclusion list is a bunch of prose on non-JSON payload.
The proposed solution is not in the numbered list. The numbered list describes how to sign a JSON blob from the outside. The rest of the doucment describes what you do if that's not an option, and you need to sign the blob in-line. The very next paragraph after said numbered list describes how to do that.
Ah! So you're saying I should say "Full disclosure" and not "Disclaimer"? (Or nothing at all, as I do here, I suppose :-)) Yes, you're absolutely right and I'll try to do better in the future :)
'Disclosure' is something journalists use to point out a potential external conflict/relationship the reader might unaware of. But you don't need it to point out authorship either. You're really saying 'I want you to be aware I wrote the thing we are discussing' not 'Please don't think I'm sockpuppetting for myself'.
This is a bit like an even more persnickety version 'nation state' in that the trivial fix is just dropping the pointless ceremony.
I don't care what you throw in the bytes; I can figure that out easily enough. I want to know who generated those bytes and that they haven't changed.
For this reason also, I think the "reach for symmetric first" is bad advice. The primary benefit I can think of for HMAC is if you want to store client-side state for more stateless browser-targeted services. It makes it easy to throw an obscure cookie to your client and then validate it on the way back. It seriously complicates things if you want to allow other people to validate provenance of those bytes. With symmetric crypto, as soon as you can validate you can also generate. That's not the system we want.