I have discussed with one of them privately, and from what I gathered this is more about versioning than actual agility. They mean to phase out obsolete primitives, not allow the kind of dangerous negotiations that could enable downgrade attacks.
I haven’t reviewed Coze thoroughly enough to have a strong opinion on whether they made the same mistakes JWT did on that front however. It seems to be simpler at least, so there should be less room for error.
I think there’s general agreement on good practice but the definition is a little murky and that’s the source of concern. I think
Bruce Schneier’s attitude is correct: “it’s vital that our systems be able
to easily swap in new algorithms when required.” We agree that upgrading applications to new algorithms should be foreseen by
developers who should be prepared for such events.
We define cryptographic agility as not overly coupling systems to single cryptographic primitives. Instead systems need to be designed to
support future swap outs quickly even if those systems
only ever deploy a single primitive at a time. For example, we assume that ES256 may need to be
changed out soon for post-quantum. It’s a good idea to design systems to be prepared
for that change even if they only support a single algorithm at a time.
That’s the fine line: “so that they can support multiple cryptographic primitives and algorithms at the same time” is not the same as “currently deploying multiple cryptographic primitives at the same time.” An agile system may use a single primitive. The important aspect of crypto agility is being able to change out algorithms quickly if the need arises.
From your blog (great post!), Coze works along the lines of “Versioned Protocols” where there is no negotiation or intermixing. (ES256 only ever deals with ES256, and ES256 is defined rigidly.)
I think there’s general agreement on good practice but the definition is a little murky and that’s the source of concern. I think Bruce Schneier’s attitude is correct: “it’s vital that our systems be able to easily swap in new algorithms when required.” We agree that upgrading applications to new algorithms should be foreseen by developers who should be prepared for such events.
To be clear: Versioned Protocols meet the goals of Schneier’s quote. In-band negotiation does too, but comes with a lot of security foot-guns.
By having an algorithm header that isn’t “v1”, “v2”, etc. you’re opening yourself to an unnecessary amount of attack surface.
For example: ECDSA doesn’t provide exclusive ownership. This means that an attacker can generate two signatures such that:
sk1 != sk2
pk1 != pk2
sig1 := sign(sk1, msg1)
sig2 := sign(sk2, msg2)
sig1 == sig2
This property matters a lot if you either:
Depend on the uniqueness of your signatures
For example, if you’re populating a hash table based on the signatures in some weird cryptocurrency scheme
Support multiple public keys in the same context
I suspect the second condition is relevant for ES256.
If you wanted to fix this, you could do it in a version 2 of the protocol (prefix the message with the public key in the hash being signed). But that means you have to describe a new algorithm (e.g. ES256v2) and an attacker may be able to downgrade between the two.
But that means you have to describe a new algorithm (e.g. ES256v2) and an attacker may be able to downgrade between the two.
There’s a more general argument here: let’s say I’m making an awesome protocol or file format, and mark it “v1”. Put it all in production, happy users and all… and then you come in and point out a serious weakness in the format, forcing me to upgrade it to “v2”. Oops.
The problem is, transitioning from one to the other will take some time. Ideally just days, but depending on the context one might have to support both formats for weeks, months, or even years. During that period, recipients and responders need to accept this “v1” format, effectively allowing downgrade attacks.
The only safe way to handle this I know of (at least in the general case where you can’t implement specific, backward compatible mitigations), is to break users overnight, and force all providers to upgrade now. Coordinated release, CVE style.
Fair enough, I guess I should have re-read your blog post before I asked. My problem was that you were saying there’s a difference between “v1”, “v2”, and “ALG”, “ALG_v2”. In the case of Coze I don’t believe there is: it’s just a version number that happens to be descriptive.
For instance the potential weakness you describe would still be there if they replaced “ES256” by “v1.0.0”, and the same rollout steps would be needed. The increased attack surface doesn’t come from the naming, it comes from the simultaneous use of different algorithms. Which may be encouraged by the descriptive version name, but to be honest I don’t anticipate more than a marginal effect.
My problem was that you were saying there’s a difference between “v1”, “v2”, and “ALG”, “ALG_v2”. In the case of Coze I don’t believe there is: it’s just a version number that happens to be descriptive.
No, the difference is between the following choices:
("v1", "v2", "v3", ...)
("ES256", "HS256", "ES256v2", ...)
The ES256/HS256 mode has been known to break JWT. I also believe that tolerating both ES256 and ES256v2 in the same context is risky, but a necessary temporary risk for making migrations possible.
Your protocol should be easily upgradable to use newer cryptography in response to new attacks. But these upgrades should be defined by cryptographers, not users.
Has it? I’ve just read your link, and it seems to me the real culprit is the downgrade attack to “trust me bro” (the none algorithm), not ES256 itself. I’m also not aware of anything like the none algorithm in Coze, maybe @Zamicol could confirming this.
Regardless of the specifics, it clearly seems to me that your objections do not apply to descriptive names in general. You’re arguing against a specific algorithm. And that’s great, thank you for the feedback. I’m a big fan of rooting out bad algorithms before going to production.
Your protocol should be easily upgradable to use newer cryptography in response to new attacks.
Sure. Version numbers can do that. And so can descriptive names. The only qualm I would have here is that descriptive names without a version number would make it harder to know which is the latest.
But these upgrades should be defined by cryptographers, not users.
Sure. And from what I gathered those upgrades will be defined by the Coze devs, so that should cover it.
And again, I see little difference between version numbers and descriptive names. Granted, descriptive names are harder to sort, but that’s only relevant if implementers provide several algorithms and don’t set a clear default.
Don’t get me wrong, I’m not a fan of descriptive names as version numbers. I just don’t think they’re nearly as bad as you are suggesting.
Has it? I’ve just read your link, and it seems to me the real culprit is the downgrade attack to “trust me bro” (the none algorithm), not ES256 itself.
The RS256 to HS256 algorithm confusion attack also works for ECDSA keys.
Ah, I see. Didn’t read far enough. The presence of both an asymmetric signing and symmetric authentication may confuse implementers or users into validating an HMAC “signature”, letting attackers use the public key as if it was a secret key, instant pwnie.
I’m not sure Coze is currently be prone to that kind of confusion however. I’m not seeing any HMAC based authentication, and the only example I’ve seen where "alg" is different from ES256, the digest is not stored in the "sig" field, but in "id".
Hopefully the authors were aware of this problem and already planned to avoid it. (Just the fact they’re trying to replace JWT suggest they might be). Thank you for the warning regardless.
I am a co-author and I am happy to answer any questions!
Why did you decide cryptographic agility was desirable?
I have discussed with one of them privately, and from what I gathered this is more about versioning than actual agility. They mean to phase out obsolete primitives, not allow the kind of dangerous negotiations that could enable downgrade attacks.
I haven’t reviewed Coze thoroughly enough to have a strong opinion on whether they made the same mistakes JWT did on that front however. It seems to be simpler at least, so there should be less room for error.
Thanks Loup!
(And Horvski is chatting with me while we reply to questions)
I think there’s general agreement on good practice but the definition is a little murky and that’s the source of concern. I think Bruce Schneier’s attitude is correct: “it’s vital that our systems be able to easily swap in new algorithms when required.” We agree that upgrading applications to new algorithms should be foreseen by developers who should be prepared for such events.
We define cryptographic agility as not overly coupling systems to single cryptographic primitives. Instead systems need to be designed to support future swap outs quickly even if those systems only ever deploy a single primitive at a time. For example, we assume that ES256 may need to be changed out soon for post-quantum. It’s a good idea to design systems to be prepared for that change even if they only support a single algorithm at a time.
That’s the fine line: “so that they can support multiple cryptographic primitives and algorithms at the same time” is not the same as “currently deploying multiple cryptographic primitives at the same time.” An agile system may use a single primitive. The important aspect of crypto agility is being able to change out algorithms quickly if the need arises.
From your blog (great post!), Coze works along the lines of “Versioned Protocols” where there is no negotiation or intermixing. (ES256 only ever deals with ES256, and ES256 is defined rigidly.)
To be clear: Versioned Protocols meet the goals of Schneier’s quote. In-band negotiation does too, but comes with a lot of security foot-guns.
By having an algorithm header that isn’t “v1”, “v2”, etc. you’re opening yourself to an unnecessary amount of attack surface.
For example: ECDSA doesn’t provide exclusive ownership. This means that an attacker can generate two signatures such that:
This property matters a lot if you either:
I suspect the second condition is relevant for ES256.
If you wanted to fix this, you could do it in a version 2 of the protocol (prefix the message with the public key in the hash being signed). But that means you have to describe a new algorithm (e.g.
ES256v2
) and an attacker may be able to downgrade between the two.There’s a more general argument here: let’s say I’m making an awesome protocol or file format, and mark it “v1”. Put it all in production, happy users and all… and then you come in and point out a serious weakness in the format, forcing me to upgrade it to “v2”. Oops.
The problem is, transitioning from one to the other will take some time. Ideally just days, but depending on the context one might have to support both formats for weeks, months, or even years. During that period, recipients and responders need to accept this “v1” format, effectively allowing downgrade attacks.
The only safe way to handle this I know of (at least in the general case where you can’t implement specific, backward compatible mitigations), is to break users overnight, and force all providers to upgrade now. Coordinated release, CVE style.
Do you know of a better way?
As my above blog post indicates, this has a minimum number of rollout steps. I can’t envision doing it in fewer.
Fair enough, I guess I should have re-read your blog post before I asked. My problem was that you were saying there’s a difference between “v1”, “v2”, and “ALG”, “ALG_v2”. In the case of Coze I don’t believe there is: it’s just a version number that happens to be descriptive.
For instance the potential weakness you describe would still be there if they replaced “ES256” by “v1.0.0”, and the same rollout steps would be needed. The increased attack surface doesn’t come from the naming, it comes from the simultaneous use of different algorithms. Which may be encouraged by the descriptive version name, but to be honest I don’t anticipate more than a marginal effect.
No, the difference is between the following choices:
("v1", "v2", "v3", ...)
("ES256", "HS256", "ES256v2", ...)
The ES256/HS256 mode has been known to break JWT. I also believe that tolerating both
ES256
andES256v2
in the same context is risky, but a necessary temporary risk for making migrations possible.Your protocol should be easily upgradable to use newer cryptography in response to new attacks. But these upgrades should be defined by cryptographers, not users.
Has it? I’ve just read your link, and it seems to me the real culprit is the downgrade attack to “trust me bro” (the
none
algorithm), not ES256 itself. I’m also not aware of anything like thenone
algorithm in Coze, maybe @Zamicol could confirming this.Regardless of the specifics, it clearly seems to me that your objections do not apply to descriptive names in general. You’re arguing against a specific algorithm. And that’s great, thank you for the feedback. I’m a big fan of rooting out bad algorithms before going to production.
Sure. Version numbers can do that. And so can descriptive names. The only qualm I would have here is that descriptive names without a version number would make it harder to know which is the latest.
Sure. And from what I gathered those upgrades will be defined by the Coze devs, so that should cover it.
And again, I see little difference between version numbers and descriptive names. Granted, descriptive names are harder to sort, but that’s only relevant if implementers provide several algorithms and don’t set a clear default.
Don’t get me wrong, I’m not a fan of descriptive names as version numbers. I just don’t think they’re nearly as bad as you are suggesting.
The RS256 to HS256 algorithm confusion attack also works for ECDSA keys.
https://auth0.com/blog/critical-vulnerabilities-in-json-web-token-libraries/#RSA-or-HMAC
Ah, I see. Didn’t read far enough. The presence of both an asymmetric signing and symmetric authentication may confuse implementers or users into validating an HMAC “signature”, letting attackers use the public key as if it was a secret key, instant pwnie.
I’m not sure Coze is currently be prone to that kind of confusion however. I’m not seeing any HMAC based authentication, and the only example I’ve seen where
"alg"
is different fromES256
, the digest is not stored in the"sig"
field, but in"id"
.Hopefully the authors were aware of this problem and already planned to avoid it. (Just the fact they’re trying to replace JWT suggest they might be). Thank you for the warning regardless.
Coze does not have a “none” algorithm. Firstly, I don’t see the utility. Secondly yes, we were aware of the issues it caused for JWT.
Does the canonicalisation do anything with character escapes?
The only JSON aware operation canonicalization does is JSON compaction. Input JSON must be valid.