Closed paragonie-security closed 2 years ago
👋
v3.public
should have the signature encoding specified, whether it's DER-encoded ASN.1 or the more compact (and imho more fitting) IEEE-P1363 proposed r || s
representation. The former is already encoded which we then encode again with base64url, bloating the final token size as a result. 6-8 bytes of the signature itself can be shaved off when using IEEE-P1363 proposed r || s
representation. Furthermore the signature lengths when using DER differ, making it more error-prone to split m
and s
when verifying.crypto
module. Two signatures over the same data will always be different, there's no option to make it otherwise.Edit: it seems IEEE-P1363 style signature is already implied given that the verify step 3 says to the rightmost 96 bytes
Edit2: RFC 6979 is indeed not supported by openssl yet. It is unfortunate that because of this yet another version.purpose can't be supported without bundling additional crypto runtimes like libsodium into openssl-capable environments.
The intent of RFC 6979 is to be compatible with FIPS 186-5 when it's no longer a draft.
However, there is also the argument that randomness makes some side-channel attacks more difficult in certain environments. Consequently, the IETF has shown interest in so-called hedged signatures.
Relaxing this restriction may be pragmatic but it's still dangerous.
We've relaxed the RFC 6979 requirement from MUST to SHOULD. When it's not possible, a CSPRNG MUST be used for k-value generation.
I'm going to quickly tag everyone who has contributed a PASETO implementation so they're aware of v3/v4 and have ample opportunity to provide feedback.
@sjudson @peter-evans @nbaars @atholbro @sethbattin @minus7 @eislambey @bdemers @scottbrady91 @shuLhan @brycx @vk-rv @o1egl @Ianleeclark @shuLhan @daviddesmet @dustinsoftware @bricej13 @rlittlefield @mguymon @AnIrishDuck @aidantwoods
I've also contributed an implementation (the first rust one) and luckily just happened to check my email 😅 to see this.
I don't see any problems with this on the start -- for rust it might be a little tough since: crypto_stream_xchacha20_xor is from libsodium (whose currently in a murky state looking for maintainers: https://github.com/sodiumoxide/sodiumoxide/issues/442 -- and we don't have any crate in the Rust-Crypto/dalek-cryptography/etc. that does this particular flavor of XOR'ing xchacha , just normal XChaCha). Though I'm not going to say this shouldn't move forward just because one language doesn't have a good way to use a solid proven crypto library that we honestly should support.
Otherwise this all seems fairly cut and dry. Thanks for getting this out.
Given a JWK representation of a public key, e.g.
{ crv: 'P-384', kty: 'EC', x: 'lkB6sw4ohIlg00fBle-TzK8_OyUGNpE74vhi1BQ45NkCJtscEV1BUdrzhk_OTrvP', y: 'fH7l2JEKPnR3pywmodMy6pBIi_h7JxwuQoPYhF5REvGwuJUxPlNSWBCj7IvxKdmq' }
The public key, when hex-encoded, will look like this:
0296407ab30e28848960d347c195ef93ccaf3f3b250636913be2f862d41438e4d90226db1c115d4151daf3864fce4ebbcf
What is the
pk
to pack inv3.public.
? Is it as simple assign
followed byx
where sign is 0x02 if y is even, 0x03 if y is odd?
Online documentation for this appears to be unclear about which bit of y
to use to determine 02
vs 03
, but every library we've studied just uses odd/even as the determinant, and this makes sense.
Given a JWK representation of a public key, e.g.
{ crv: 'P-384', kty: 'EC', x: 'lkB6sw4ohIlg00fBle-TzK8_OyUGNpE74vhi1BQ45NkCJtscEV1BUdrzhk_OTrvP', y: 'fH7l2JEKPnR3pywmodMy6pBIi_h7JxwuQoPYhF5REvGwuJUxPlNSWBCj7IvxKdmq' }
The public key, when hex-encoded, will look like this:
0296407ab30e28848960d347c195ef93ccaf3f3b250636913be2f862d41438e4d90226db1c115d4151daf3864fce4ebbcf
What is the
pk
to pack inv3.public.
? Is it as simple assign
followed byx
where sign is 0x02 if y is even, 0x03 if y is odd?Online documentation for this appears to be unclear about which bit of
y
to use to determine02
vs03
, but every library we've studied just uses odd/even as the determinant, and this makes sense.
If it's unclear, why not use the uncompressed representation instead?
I've also contributed an implementation (the first rust one) and luckily just happened to check my email 😅 to see this.
Sorry about the oversight; I was gauging based on pull requests. I'll make sure I didn't miss anyone.
I don't see any problems with this on the start -- for rust it might be a little tough since: crypto_stream_xchacha20xor is from libsodium (whose currently in a murky state looking for maintainers: sodiumoxide/sodiumoxide#442 -- and we don't have any crate in the Rust-Crypto/dalek-cryptography/etc. that does this particular flavor of XOR'ing xchacha , just normal XChaCha). Though I'm not going to say this shouldn't move forward just because one language doesn't have a good way to use a solid proven crypto library that we honestly should support._
Otherwise this all seems fairly cut and dry. Thanks for getting this out.
I think this should be an easy patch to write. Let me follow up with the crypto rustaceans I know and hopefully we can prevent this from making PASETO painful to implement.
If it's unclear, why not use the uncompressed representation instead?
What's unclear is the informal answers you find on forums like Stack Exchange, not the SECGv2 paper. Its definition is very precise.
Compressed points prevent implementations from accidentally becoming susceptible to off-curve attacks. e.g. https://auth0.com/blog/critical-vulnerability-in-json-web-encryption/
and we don't have any crate in the Rust-Crypto/dalek-cryptography/etc. that does this particular flavor of XOR'ing xchacha , just normal XChaCha)
@Mythra
Just FYI, the difference is simply that the XOR'ing flavor XORs the XChaCha pseduo-random stream with the input plaintext. Should be straight-forward if plain XChaCha is available.
Orion (disclaimer: I'm the maintainer) supports this XORing flavor, for inspiration. Not as battle-tested as libsodium though.
@brycx thanks. I had figured it wouldn't be too hard but to be honest I don't trust myself to make those determinations.
I didn't see Orion -- guess my GoogleFu was weak. But it's good to know there's some prior art.
Compressed points prevent implementations from accidentally becoming susceptible to off-curve attacks. e.g. auth0.com/blog/critical-vulnerability-in-json-web-encryption
I'm aware of that, but it makes little difference to the Exclusive Ownership enhancement itself, doesn't it?
For the record I'm okay with either, I am just looking at ways of making this less error-prone when it comes to interoperability of implementations.
As a bit of feedback, when it comes to vectors it would be amazing if key representations were provided in various formats - compressed public, SPKI PEM as well as JWK.
@paragonie-security Thanks for the ping. I'll be taking a closer look when implementing, but seems good to me so far. Glad to see the focus on key-commitment.
Re the XChaCha20-Blake2b construction: Are there existing test vectors for this combination available, outside the context of PASETO, or will implementers have to rely on the implicit test vectors through PASETO only?
Thanks for the work you've put into this!
Compressed points prevent implementations from accidentally becoming susceptible to off-curve attacks. e.g. auth0.com/blog/critical-vulnerability-in-json-web-encryption
I'm aware of that, but it makes little difference to the Exclusive Ownership enhancement itself, doesn't it?
They're not just used in the EO enhancement, but also a MUST for the actual public key encoding for PASETO v3.
For the record I'm okay with either, I am just looking at ways of making this less error-prone when it comes to interoperability of implementations.
As a bit of feedback, when it comes to vectors it would be amazing if key representations were provided in various formats - compressed public, SPKI PEM as well as JWK.
There will be test vectors. We're also working on a related specification for key serialization right now to obviate JWK, but we have no problem building test vectors from (x, y) pairs that can be JWK-alike.
Re the XChaCha20-Blake2b construction: Are there existing test vectors for this combination available, outside the context of PASETO, or will implementers have to rely on the implicit test vectors through PASETO only?
Halite has been using this construction for years (albeit with Salsa20 instead of ChaCha). Halite v5 will almost certainly use XChaCha.
Given a JWK representation of a public key, e.g.
{ crv: 'P-384', kty: 'EC', x: 'lkB6sw4ohIlg00fBle-TzK8_OyUGNpE74vhi1BQ45NkCJtscEV1BUdrzhk_OTrvP', y: 'fH7l2JEKPnR3pywmodMy6pBIi_h7JxwuQoPYhF5REvGwuJUxPlNSWBCj7IvxKdmq' }
The public key, when hex-encoded, will look like this:
0296407ab30e28848960d347c195ef93ccaf3f3b250636913be2f862d41438e4d90226db1c115d4151daf3864fce4ebbcf
What is the
pk
to pack inv3.public.
? Is it as simple assign
followed byx
where sign is 0x02 if y is even, 0x03 if y is odd?Online documentation for this appears to be unclear about which bit of
y
to use to determine02
vs03
, but every library we've studied just uses odd/even as the determinant, and this makes sense.
I typically reference section 4.3.6 step 2.2 of this document 1 for point compression. It seems to be the normative way that sec2 originally defined it and has been reliably interoperable for us when dealing with compressed key formats from the secg curves. It aligns with the way you pointed out as well which is sign based on if it's even or odd. Does that help as a point of reference?
Does that help as a point of reference?
@kdenhartog it sure does, thank you.
I've pushed a commit (and updated the top-level comment) to explain the operational security requirements imposed by ECDSA for v3.public tokens.
We just published the first draft of PASERK, a PASETO extension for key serialization and wrapping.
PASETO implementations should be sure they're aware of this: https://github.com/paragonie/paseto/pull/127/commits/fa5bd6632b75961f3f8780c8eb5ec51f5b5259b5
See #107 for the discussion that preceded this decision.
ad fa5bd66) "JSON string" is not a concrete enough description. I understand the intention, but its describing the already encoded value which is always a string. e.g. "foo"
is also JSON string. And when parsed it comes out a string.
In versions 3 and 4, if a footer is provided, it MUST decode to a valid JSON string. This ensures that footer processing is consistent across PASETO implementations.
> JSON.parse('"foo"')
'foo'
// ✅
I think it should be "JSON Object", "Serialized JSON Object", ... something to that end...
And does it make sense hardcoded like so? If in the future there may be CBOR PASETO versions, will those have the footer as JSON? or as CBOR?
If you wish to use Versions 3 or 4 with arbitrary data, it MUST be serialized into a JSON string and its value SHOULD be base64url-encoded.
I'm not sure this is clear. Is the arbitrary data (under SHOULD) expected to be base64url encoded, put in a JSON object, which is then the footer to be base64url encoded into the final token again?
Going back and forth between the changes I start to question I actually know the original intention now - and I think footer should remain free-form (aside of its base64url encoding in the final token).
Furthermore this is a breaking change in the token's contents that complicates transition between v1/v2 and v3/v4 for parties that utilize a freeform footer.
I think it should be "JSON Object", "Serialized JSON Object", ... something to that end...
JSON stands for "JavaScript Object Notation" so saying that then "Object" sounds a bit redundant.
I'm not sure this is clear. Is the arbitrary data (under SHOULD) expected to be base64url encoded
If you're dropping raw binary (e.g. the output of random_bytes()
) into a token, you're expected to base64url-encode it so it's valid JSON.
Going back and forth between the changes I start to question I actually know the original intention now - and I think footer should remain free-form (aside of its base64url encoding in the final token).
All of the test vectors already use a JSON-encoded example for footers. If we decided to NOT do this, we should probably include a non-JSON footer example so our intent is captured.
Do you agree with that?
JSON stands for "JavaScript Object Notation" so saying that then "Object" sounds a bit redundant.
Plenty of developers are not aware that JSON.parse('true')
is also a valid JSON. Any JSON primitive can be the top level JSON type returned by a parser. Because of that, if what you expect serialized is an object, it's "JSON that must be a top-level JSON Object" or something to that end.
All of the test vectors already use a JSON-encoded example for footers. If we decided to NOT do this, we should probably include a non-JSON footer example so our intent is captured. Do you agree with that?
I agree, let's extend the vectors.
Test vectors expanded in https://github.com/paragonie/paseto/pull/128/commits/413e469f27499f35ecf73cf781ab6f31bf970e3e
Test vectors expanded in 413e469
Are you sure they're correct? I can only test 1-E-9 and 3-E-9 with my implementation but they're failing, despite the other vectors with footers passing.
I'm getting
v1.local.IddlRQmpk6ojcD10z1EYdLexXvYiadtY0MrYQaRnq3dnqKIWcbbpOcgXdMIkm3_3gksirTj81bvWrWkQwcUHilt-tQo7LZK8I6HCK1V78B9YeEqGNeeWXOyWWHoJQIe0d5nTdvdgNpe3vI21jV2YL7WVG5p63_JxxzLckBu9azQ0GlDMdPxNAxoyvmU1wbpSbRB9Iw4.YXJiaXRyYXJ5LXN0cmluZy10aGF0LWlzbid0LWpzb24
and
v3.local.JvdVM1RIKh2R1HhGJ4VLjaa4BCp5ZlI8K0BOjbvn9_Kageg3IiCFuPpx6-8kGD1SdE0duzUdq5ySu4DeN1zq9HtEhw4IU0o_kdeWV-J_U7rJinQ5eCsyDoeGDlyRBoJ5cz3hNks-m5Cvwe_NjI-lXaQLJWj0p1ck66xYdF_hTFR_1NIG2rht6CwM047F5qQueJikyvU.YXJiaXRyYXJ5LXN0cmluZy10aGF0LWlzbid0LWpzb24
Are you sure they're correct? I can only test 1-E-9 and 3-E-9 with my implementation but they're failing, despite the other vectors with footers passing.
They're verified as part of CI. https://github.com/paragonie/paseto/blob/new-versions-implementation/tests/KnownAnswerTest.php
Oh, I see the problem. I forgot to explicitly pass the nonce when generating these. Yes, your vectors are correct.
Oh, I see the problem. I forgot to explicitly pass the nonce when generating these. Yes, your vectors are correct.
v2 and v4 I cannot check though
Selfishly, I'd love to see a mechanism to get a key-id from token without first needing to parse unvalidated JSON from the footer.
Common Java-based JSON parsers suffer from stack overflows when attempting to parse strings that start with a few thousand right brackets or something like this: {[[[[[[[[[[[[[[[[[[[[[[....
And any potential security issues from the underlying JSON parser. (these risks are somewhat mitigated after verifying the token and before parsing the body).
That said, there are workarounds for them on the implementation side (it would be nice if implementors didn't need to worry about them though):
A mechanism that should remain uncontroversial is to enforce a maximum depth of N (where N is very small; for example, 2). If you're storing expressive claims, that probably doesn't belong in the footer.
@panva
v2 and v4 I cannot check though
https://github.com/paragonie/paseto/pull/128/commits/a355cfbd776a76909a8d5999cfe2008d54c23527 corrects v2/v4 and adds PHP code to verify the JSON as part of CI
I agree, but setting the max depth of a JSON parser may not be possible depending on the JSON lib used (and resolving it after the fact is too late)
This section in the spec implies the footer could be used to store data for "unencrypted, but authenticated, tokens" https://github.com/paragonie/paseto/pull/127/files#diff-0b5ca119d2be595aa307d34512d9679e49186307ef94201e4b3dfa079aa89938R45-R47
If a kid
was also contained in the footer for this scenario, that might make it more challenging to guard against depth size as well.
How exactly would you recommend enforcing this safely?
To expand our previous query with a little more context:
One of the mechanisms we're introducing with PASERK is the ability to wrap a single token key against multiple wrapping keys (local-seal
, local-wrap.pie.
, local-wrap.customwrapper.
, etc.).
(PASERK is one motivation for why V3/V4 must be RKR-secure and why V3 always supports point compression for P-384 keys.)
It's very possible that someone will want to use PASETO with PASERK such that there is no one kid
in the footer, but rather, a bundle of 2 or more encrypted representations of the same plaintext symmetric key. Visually:
footer = JSON.stringify({
"wrapped-keys": [
"k4.seal.AAAAAAAAAAAAA...",
"k4.local-wrap.pie.AAA..."
]
})
Alternatively, if there's only one wrapped key, it's reasonable to only use that as the footer.
footer = "k4.local-wrap.pie.AAA..."
If JSON in the footer is a security problem for Java libraries, then PASETO needs to try to mitigate it at a design level. (That doesn't guarantee that we'll succeed, but it's a good use of our time.)
https://github.com/paragonie/paseto/pull/128/commits/2df496e0124ac54cb480ffa7f1a88de6e4422696 adds a Parser rule called FooterJSON that allows assertions to be made about the footer:
EDIT: https://github.com/paragonie/paseto/pull/128/commits/1e2daa21967dff1f3c77867f5521cb993f996ee8 makes it reject tokens that are too deep without needing to actually parse it with a JSON library.
This rule should make it more difficult to target PASETO with Hash-DoS attacks. @bdemers what do you think?
@bdemers We provided an algorithm for validating the maximum stack size of an arbitrary JSON string without depending on a JSON parser in https://github.com/paragonie/paseto/pull/127/commits/190e1b8efc6c0501befe3fedfa36fcbef7c54a57
An example implementation can be found in #128.
@paragonie-security in the typescript example -> Step 6 is an infinite loop. Give it a whirl with the following inputs: getJsonDepth('{"foo":"bar"}')
, getJsonDepth('{}')
All 12 v2 test vectors are working fine against my implementation, 3 for v2.public plus 9 for v2.local as seen here
Do I understand correctly that implicit assertions are a new v3 / v4 feature and won't be a feature in v1 / v2? I noticed that implicit-assertion
field is also included in the v2 test vectors.
@paragonie-security in the typescript example -> Step 6 is an infinite loop. Give it a whirl with the following inputs:
getJsonDepth('{"foo":"bar"}')
,getJsonDepth('{}')
I must've made a mistake with my regex. Thanks for checking, I'll fix it.
All 12 v2 test vectors are working fine against my implementation, 3 for v2.public plus 9 for v2.local as seen here
Excellent!
Do I understand correctly that implicit assertions are a new v3 / v4 feature and won't be a feature in v1 / v2? I noticed that
implicit-assertion
field is also included in the v2 test vectors.
Yes, their inclusion in the test vectors was to signal that they are not supported in v1/v2. The main reason is that there are over a dozen v1/v2 implementations in the wild and none of them supported this feature, so it would be breaking backwards compatibility.
I had been thinking about using the footer text as the kid
, but I hadn't thought about the complex object keys use case (thanks for adding that example above).
An additional challenge with any option is there is no standard key rotation mechanism. This isn't a PASETO spec problem though, I'm guessing that would/should be defined in any spec that depends on PASETO.
But this does mean the implementations need to pass this flexibility down to the user/developer of a PASETO lib (this is already spelled out in the guides). Ideally, there would be a single definitive way to get a key-id. That said, my musing on this is probably short-sighted given the complex keys mentioned above and the simple fact any sort of key-id representation is optional.
I'm mostly thinking out loud anyway, but your resulting JSON bits are a great addition to the guide!
I had been thinking about using the footer text as the
kid
, but I hadn't thought about the complex object keys use case (thanks for adding that example above).An additional challenge with any option is there is no standard key rotation mechanism. This isn't a PASETO spec problem though, I'm guessing that would/should be defined in any spec that depends on PASETO.
But this does mean the implementations need to pass this flexibility down to the user/developer of a PASETO lib (this is already spelled out in the guides). Ideally, there would be a single definitive way to get a key-id. That said, my musing on this is probably short-sighted given the complex keys mentioned above and the simple fact any sort of key-id representation is optional.
You can use PASERK to serialize local or public keys and either stick them in the footer or send them out-of-band (and include them as an implicit assertion in v3/v4).
I'm mostly thinking out loud anyway, but your resulting JSON bits are a great addition to the guide!
Thanks! I hope it helps.
We changed the way HKDF is used in Version 3 of PASETO: https://github.com/paragonie/paseto/pull/127/commits/6c6eb51038e830cf95e39cfffe49e66991cdb272
We changed the way HKDF is used in Version 3 of PASETO: 6c6eb51
Updated 3-E-* vectors
v3.local.AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADbfcIURX_0pVZVU1mAESUzrKZAsRm2EsD6yBoZYn6cpVZNzSJOhSDN-sRaWjfLU-yn9OJH1J_B8GKtOQ9gSQlb8yk9IzbzzgMcrDxhuj1pG3ise-XA3D1jiGQlW0rCMMFoOXbejl9Hfbg4pIiGw7FZH-2nDPI
v3.local.AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADbfcIURX_0pVZVU1mAESUzrKZAqhWxBMDgyBoZYn6cpVZNzSJOhSDN-sRaWjfLU-yn9OJH1J_B8GKtOQ9gSQlb8yk9IzbUtxJRXsRcaF6JZZ4aMMSmenSnEEZaqHz4gCAGFu41847FQSRTkMClQxmZUYk51So
v3.local.JvdVM1RIKh2R1HhGJ4VLjaa4BCp5ZlI8K0BOjbvn9_LwY78vQnDait-Q-sjhF88dG2B0ROIIykcrGHn8wzPbTrqObHhyoKpjy3cwZQzLdiwRsdEK5SDvl02_HjWKJW2oqGMOQJlwsimt2qKsqDM3k0bW7MAZHZZ3DwyndDd_Fv5a3yrDP2nWbkkCm40ZiFbQ6pU3O44
v3.local.JvdVM1RIKh2R1HhGJ4VLjaa4BCp5ZlI8K0BOjbvn9_LwY78vQnDait-Q-sjhF88dG2B0X-4P3EcxGHn8wzPbTrqObHhyoKpjy3cwZQzLdiwRsdEK5SDvl02_HjWKJW2oqGMOQJlGjHamYbS6Tp5YkB6EGH_dvzKzWOrxGVl0HD8Nh7DEC9XX0CXOrVm_eda1dWnxi8A
v3.local.JvdVM1RIKh2R1HhGJ4VLjaa4BCp5ZlI8K0BOjbvn9_LwY78vQnDait-Q-sjhF88dG2B0ROIIykcrGHn8wzPbTrqObHhyoKpjy3cwZQzLdiwRsdEK5SDvl02_HjWKJW2oqGMOQJmfSWLQOS8wfy8kO9cjM0RiGh3X4VNev7uiMo9elqV2cKTNcG2-9AVqEcx5lTJqSBM.eyJraWQiOiJVYmtLOFk2aXY0R1poRnA2VHgzSVdMV0xmTlhTRXZKY2RUM3pkUjY1WVp4byJ9
v3.local.JvdVM1RIKh2R1HhGJ4VLjaa4BCp5ZlI8K0BOjbvn9_LwY78vQnDait-Q-sjhF88dG2B0X-4P3EcxGHn8wzPbTrqObHhyoKpjy3cwZQzLdiwRsdEK5SDvl02_HjWKJW2oqGMOQJkGF0zrfdykirmpjpVcgpIWntFSESlQZR7aoYlg_vREEepxOmacdxRvAjLbVshFeUs.eyJraWQiOiJVYmtLOFk2aXY0R1poRnA2VHgzSVdMV0xmTlhTRXZKY2RUM3pkUjY1WVp4byJ9
v3.local.JvdVM1RIKh2R1HhGJ4VLjaa4BCp5ZlI8K0BOjbvn9_LwY78vQnDait-Q-sjhF88dG2B0ROIIykcrGHn8wzPbTrqObHhyoKpjy3cwZQzLdiwRsdEK5SDvl02_HjWKJW2oqGMOQJkvnotYbXpUICkKfoMEK5kCeXcbFcSJCjbQEvYDNX2IS7EQDHiHb7RqpfcjgqYnPVw.eyJraWQiOiJVYmtLOFk2aXY0R1poRnA2VHgzSVdMV0xmTlhTRXZKY2RUM3pkUjY1WVp4byJ9
v3.local.JvdVM1RIKh2R1HhGJ4VLjaa4BCp5ZlI8K0BOjbvn9_LwY78vQnDait-Q-sjhF88dG2B0X-4P3EcxGHn8wzPbTrqObHhyoKpjy3cwZQzLdiwRsdEK5SDvl02_HjWKJW2oqGMOQJn5IfmIsSxeSCBBl_tklEcYw6Cr8ErCUexR-258f3tTWieacJeUhCEz23DX75j2ia0.eyJraWQiOiJVYmtLOFk2aXY0R1poRnA2VHgzSVdMV0xmTlhTRXZKY2RUM3pkUjY1WVp4byJ9
v3.local.JvdVM1RIKh2R1HhGJ4VLjaa4BCp5ZlI8K0BOjbvn9_LwY78vQnDait-Q-sjhF88dG2B0X-4P3EcxGHn8wzPbTrqObHhyoKpjy3cwZQzLdiwRsdEK5SDvl02_HjWKJW2oqGMOQJknAux2nLsAtei06Hmo1VF8MV_QZ1DW2EQE2v8XeAOlaV6zoldDbymzXJHVXMyqkPg.YXJiaXRyYXJ5LXN0cmluZy10aGF0LWlzbid0LWpzb24
It looks like we agree: https://github.com/paragonie/paseto/pull/128/commits/7e72e5fe5d27e84f8538f7094cace8a5f8e1ee71
Updated 3-E-* test vectors with https://github.com/paragonie/paseto/pull/127/commits/092629c322317c381de71a06f7964d08bd087e9f
v3.local.AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADbfcIURX_0pVZVU1mAESUzrKZAsRm2EsD6yBoZYn6cpVZNzSJOhSDN-sRaWjfLU-yn9OJH1J_B8GKtOQ9gSQlb8yk9Iza7teRdkiR89ZFyvPPsVjjFiepFUVcMa-LP18zV77f_crJrVXWa5PDNRkCSeHfBBeg
v3.local.AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADbfcIURX_0pVZVU1mAESUzrKZAqhWxBMDgyBoZYn6cpVZNzSJOhSDN-sRaWjfLU-yn9OJH1J_B8GKtOQ9gSQlb8yk9IzZfaZpReVpHlDSwfuygx1riVXYVs-UjcrG_apl9oz3jCVmmJbRuKn5ZfD8mHz2db0A
v3.local.JvdVM1RIKh2R1HhGJ4VLjaa4BCp5ZlI8K0BOjbvn9_LwY78vQnDait-Q-sjhF88dG2B0ROIIykcrGHn8wzPbTrqObHhyoKpjy3cwZQzLdiwRsdEK5SDvl02_HjWKJW2oqGMOQJlxnt5xyhQjFJomwnt7WW_7r2VT0G704ifult011-TgLCyQ2X8imQhniG_hAQ4BydM
v3.local.JvdVM1RIKh2R1HhGJ4VLjaa4BCp5ZlI8K0BOjbvn9_LwY78vQnDait-Q-sjhF88dG2B0X-4P3EcxGHn8wzPbTrqObHhyoKpjy3cwZQzLdiwRsdEK5SDvl02_HjWKJW2oqGMOQJlBZa_gOpVj4gv0M9lV6Pwjp8JS_MmaZaTA1LLTULXybOBZ2S4xMbYqYmDRhh3IgEk
v3.local.JvdVM1RIKh2R1HhGJ4VLjaa4BCp5ZlI8K0BOjbvn9_LwY78vQnDait-Q-sjhF88dG2B0ROIIykcrGHn8wzPbTrqObHhyoKpjy3cwZQzLdiwRsdEK5SDvl02_HjWKJW2oqGMOQJlkYSIbXOgVuIQL65UMdW9WcjOpmqvjqD40NNzed-XPqn1T3w-bJvitYpUJL_rmihc.eyJraWQiOiJVYmtLOFk2aXY0R1poRnA2VHgzSVdMV0xmTlhTRXZKY2RUM3pkUjY1WVp4byJ9
v3.local.JvdVM1RIKh2R1HhGJ4VLjaa4BCp5ZlI8K0BOjbvn9_LwY78vQnDait-Q-sjhF88dG2B0X-4P3EcxGHn8wzPbTrqObHhyoKpjy3cwZQzLdiwRsdEK5SDvl02_HjWKJW2oqGMOQJmSeEMphEWHiwtDKJftg41O1F8Hat-8kQ82ZIAMFqkx9q5VkWlxZke9ZzMBbb3Znfo.eyJraWQiOiJVYmtLOFk2aXY0R1poRnA2VHgzSVdMV0xmTlhTRXZKY2RUM3pkUjY1WVp4byJ9
v3.local.JvdVM1RIKh2R1HhGJ4VLjaa4BCp5ZlI8K0BOjbvn9_LwY78vQnDait-Q-sjhF88dG2B0ROIIykcrGHn8wzPbTrqObHhyoKpjy3cwZQzLdiwRsdEK5SDvl02_HjWKJW2oqGMOQJkzWACWAIoVa0bz7EWSBoTEnS8MvGBYHHo6t6mJunPrFR9JKXFCc0obwz5N-pxFLOc.eyJraWQiOiJVYmtLOFk2aXY0R1poRnA2VHgzSVdMV0xmTlhTRXZKY2RUM3pkUjY1WVp4byJ9
v3.local.JvdVM1RIKh2R1HhGJ4VLjaa4BCp5ZlI8K0BOjbvn9_LwY78vQnDait-Q-sjhF88dG2B0X-4P3EcxGHn8wzPbTrqObHhyoKpjy3cwZQzLdiwRsdEK5SDvl02_HjWKJW2oqGMOQJmZHSSKYR6AnPYJV6gpHtx6dLakIG_AOPhu8vKexNyrv5_1qoom6_NaPGecoiz6fR8.eyJraWQiOiJVYmtLOFk2aXY0R1poRnA2VHgzSVdMV0xmTlhTRXZKY2RUM3pkUjY1WVp4byJ9
v3.local.JvdVM1RIKh2R1HhGJ4VLjaa4BCp5ZlI8K0BOjbvn9_LwY78vQnDait-Q-sjhF88dG2B0X-4P3EcxGHn8wzPbTrqObHhyoKpjy3cwZQzLdiwRsdEK5SDvl02_HjWKJW2oqGMOQJlk1nli0_wijTH_vCuRwckEDc82QWK8-lG2fT9wQF271sgbVRVPjm0LwMQZkvvamqU.YXJiaXRyYXJ5LXN0cmluZy10aGF0LWlzbid0LWpzb24
Barring any discoveries from cryptography experts, that should be the last change we've made to V3/V4. Thanks for your patience and diligence @panva!
Due to the feedback we've received since this PR was opened (and a UX problem with Github), we're doing to merge this, but don't take that as a signal that discussion has closed prematurely. If you have any additional feedback, questions, concerns, etc. please raise them here or in a new issue (whichever you prefer).
Per #128, the comment period is open until at least 2021-08-02, but you should take us releasing v2.0.0
of this library as the signal that the design is final.
Per #78 we've moved the documentation to https://github.com/paseto-standard/paseto-spec
Please feel free to toss issues at that repository.
Spec has been finalized. v2.0.0
release incoming.
No Code Changes; only specification
See #128 for the implementation.
(Copied from
Rationale-V3-V4.md
)Primary Motivations for New Versions
v4.local
v2.local was originally specified to use XChaCha20-Poly1305, a boring AEAD mode that's obviously secure. However, we've since learned about key- and message-commitment, which is an important security property in systems with multiple possible symmetric keys.
Since PASETO added footers to support key-ids and key rotation strategies, this means we MUST take attacks that depend on random-key robustness seriously.
PASETO v4.local uses XChaCha20 to encrypt the message, but then uses a keyed BLAKE2b hash (which acts as HMAC) for the authentication tag.
v3.public
We specified RSA for PASETO v1.public tokens, under the assumption that applications that must ONLY support NIST algorithms (e.g. because they MUST only use FIPS 140-2 validated modules to maintain compliance) would be adequately served by RSA signatures. This assumption turned out to be incorrect, and elliptic curve cryptography is now preferred.
To better meet the needs of applications that are NIST-dependent, PASETO v3.public tokens will support ECDSA over NIST's P-384 curve, with SHA-384, and (preferably) using RFC 6979 deterministic signatures. (RFC 6979 is a SHOULD, not a MUST, due to library availability issues and fault attacks.)
ECDSA Security
ECDSA is much more dangerous to implement than Ed25519:
k
is never reused for different messages, or you leak your secret key.k
deterministically, you have to take extra care to ensure your random number generator isn't biased. If you fail to ensure this, attackers can determine your secret key through lattice attacks.k^-1 (mod p)
must be constant-time to avoid leakingk
.There are additional worries with ECDSA with different curves, but we side-step most of these problems by hard-coding one NIST curve and refusing to support any others. The outstanding problems are:
There are additional protocol-level security concerns for ECDSA, namely:
Because of these concerns, we previously forbid any implementation of ECDSA without RFC 6979 deterministic k-values in a future version.
However, given the real-world requirements of applications and systems that must comply with NIST guidance on cryptography algorithms, we've relaxed this requirement.
Additionally, deterministic k-values make signers more susceptible to fault attacks than randomized signatures. If you're implementing PASETO signing in embedded devices, or environments where fault injection may be a practical risk, there are two things you can do:
Questions For Security Auditors
Due to the risks inherent to ECDSA, security assessors should take care to cover the following questions in any review of a PASETO implementation that supports
v3.public
tokens (in addition to their own investigations).k^-1 (mod p)
) constant-time?Affirmative answers to these questions should provide assurance that the ECDSA implementation is safe to use with P-384, and security auditors can focus their attention on other topics of interest.
v3.local / v4.public
No specific changes were needed from (v1.local, v2.public) respectively. See below for some broader changes.
Beneficial Changes to V3/V4
No More Nonce-Hashing (Change)
The initial motivation for hashing the random nonce with the message was to create an SIV-like construction to mitigate the consequences of weak random number generators, such as OpenSSL's (which isn't fork-safe).
However, this creates an unfortunate failure mode: If your RNG fails, the resultant nonce is a hash of your message, which can be used to perform offline attacks on the plaintext. This was first discovered by Thái Dương.
To avoid this failure mode, neither v3.local nor v4.local will pre-hash the message and random value to derive a nonce. Instead, it will trust the CSPRNG to be secure.
Implicit Assertions (Feature)
PASETO v3 and v4 tokens will support optional additional authenticated data that IS NOT stored in the token, but IS USED to calculate the authentication tag (local) or signature (public).
These are called implicit assertions. These can be any application-specific data that must be provided when validating tokens, but isn't appropriate to store in the token itself (e.g. sensitive internal values).
One example where implicit assertions might be desirable is ensuring that a PASETO is only used by a specific user in a multi-tenant system. Simply providing the user's account ID when minting and consuming PASETOs will bind the token to the desired context.
Better Use of HKDF Salts (Change)
With v1.local, half of the 32-byte random value was used as an HKDF salt and half was used as an AES-CTR nonce. This is tricky to analyze and didn't extend well for the v4.local proposal.
For the sake of consistency and easy-to-analyze security designs, in both v3.local and v4.local, we now use the entire 32-byte random value in the HKDF step.
Instead of being used as a salt, however, it will be appended to the info tag. This subtle change allows us to use the standard security definition for HKDF in arguments for PASETO's security, rather than treating it as just a pseudo-random function (PRF). This security definition requires only one salt to be used, but for many contexts (info tags).
The nonce used by AES-256-CTR and XChaCha20 will be derived from the HKDF output (which is now 48 bytes for v3.local and 56 bytes for v4.local). The first 32 bytes of each HKDF output will be used as the key. The remaining bytes will be used as the nonce for the underlying cipher.
Local PASETOs in v3 and v4 will always have a predictable storage size, and the security of these constructions is more obvious:
V3 Signatures Prove Exclusive Ownership (Enhancement)
RSA and ECDSA signatures do not prove Exclusive Ownership. This is almost never a problem for most protocols, unless you expect this property to hold when it doesn't.
Section 3.3 of the paper linked above describes how to achieve Universal Exclusive Ownership (UEO) without increasing the signature size: Always include the public key in the message that's being signed.
Consequently,
v3.public
PASETOs will include the raw bytes of the public key in the PAE step for calculating signatures. The public key is always a compressed point (0x02
or0x03
, followed by the X coordinate, for a total of 49 bytes).We decided to use point compression in the construction of the tokens as a forcing function so that all PASETO implementations support compressed points (and don't just phone it in with PEM-encoded uncompressed points).
Ed25519, by design, does not suffer from this, since Ed25519 already includes public key with the hash function when signing messages. Therefore, we can safely omit this extra step in
v4.public
tokens.Miscellaneous Changes
Define Mechanism for Extending PASETO for non-JSON Encodings
PASETO serializes its payload as a JSON string. Future documents MAY specify using PASETO with non-JSON encoding. When this happens, a suffix will be appended to the version tag when a non-JSON encoding rule is used.
Questions and Answers
Why Not AES-GCM in
v3.local
?While it's true that AES-GCM is more broadly supported in environments that use NIST and FIPS-approved cryptography, GMAC is neither message-committing nor key-committing.
The techniques for turning an AEAD scheme into an AEAD scheme is well known, but it requires publishing an additional SHA2 hash (or KDF output) of the key being used.
Using GCM would require us to also publish an additional hash anyway. At that point, it doesn't offer any clear advantage over CTR+HMAC.
CTR+HMAC (with separate keys and PAE) is a secure construction and provides the cryptographic properties we need to use PASETO in threat models where multiple keys are used or partitioning oracles are possible.
Why P-384 in
v3.public
instead of P-256 or P-521?Security experts that work heavily with NIST algorithms expressed a slight preference for P-384 over P-521 and P-256 when we asked. This is also congruent for our choice of SHA-384 as a hash function over SHA-256 or SHA-512.
The security considerations for the NIST curves are mostly congruent (albeit the ECDLP security and performance differs a bit).
If you want smaller tokens or better performance than P-384, make sure Ed25519 lands in FIPS 186-5 and use
v4.public
instead.