paragonie / paseto

Platform-Agnostic Security Tokens
https://paseto.io
Other
3.23k stars 108 forks source link

Initial Draft for PASETO v3/v4 specifications #127

Closed paragonie-security closed 2 years ago

paragonie-security commented 3 years ago

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:

  1. You have to ensure the one-time secret k is never reused for different messages, or you leak your secret key.
  2. If you're not generating 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.
  3. The computing k^-1 (mod p) must be constant-time to avoid leaking k.
    • Most bignum libraries DO NOT provide a constant-time modular inverse function, but cryptography libraries often do. This is something a security auditor will need to verify for each implementation.

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:

  1. Don't use deterministic signatures because of your specific threat model.
  2. Hedged signatures: Inject additional randomness into the RFC 6979 step. This randomness doesn't need to be signed.

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).

  1. Is RFC 6979 supported and used by the implementation?
    1. If not, is a cryptographically secure random number generator used?
    2. If the answer to both questions is "No", fail.
  2. Is modular inversion (k^-1 (mod p)) constant-time?
    1. If not, fail.
  3. Are public keys expressed as compressed points?
    1. If not, is the public key explicitly validated to be on the correct curve (P-384)?
    2. If the answer to both questions is "No", fail.
  4. Does the underlying cryptography library use complete addition formulas for NIST P-384?
    1. If not, investigate how the library ensures that scalar multiplication is constant-time. (This affects the security of key generation.)

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 or 0x03, 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.

For example, a future PASETO-CBOR proposal might define its versions as v1c, v2c, v3c, and v4c. The underlying cryptography will be the same as v1, v2, v3, and v4 respectively. Keys SHOULD be portable across different underlying encodings, but tokens MUST NOT be transmutable between encodings without access to the symmetric key (local tokens) or secret key (public tokens).

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.

panva commented 2 years ago

👋

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.

paragonie-security commented 2 years ago

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.

paragonie-security commented 2 years ago

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.

paragonie-security commented 2 years ago

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

Mythra commented 2 years ago

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.

paragonie-security commented 2 years ago

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 in v3.public.? Is it as simple as sign followed by x 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.

panva commented 2 years ago

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 in v3.public.? Is it as simple as sign followed by x 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.

If it's unclear, why not use the uncompressed representation instead?

paragonie-security commented 2 years ago

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.

paragonie-security commented 2 years ago

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/

brycx commented 2 years ago

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.

Mythra commented 2 years ago

@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.

panva commented 2 years ago

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.

brycx commented 2 years ago

@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!

paragonie-security commented 2 years ago

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.

paragonie-security commented 2 years ago

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.

kdenhartog commented 2 years ago

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 in v3.public.? Is it as simple as sign followed by x 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.

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?

panva commented 2 years ago

Does that help as a point of reference?

@kdenhartog it sure does, thank you.

paragonie-security commented 2 years ago

I've pushed a commit (and updated the top-level comment) to explain the operational security requirements imposed by ECDSA for v3.public tokens.

paragonie-security commented 2 years ago

We just published the first draft of PASERK, a PASETO extension for key serialization and wrapping.

paragonie-security commented 2 years ago

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.

panva commented 2 years ago

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.

paragonie-security commented 2 years ago

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?

panva commented 2 years ago

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.

paragonie-security commented 2 years ago

Test vectors expanded in https://github.com/paragonie/paseto/pull/128/commits/413e469f27499f35ecf73cf781ab6f31bf970e3e

panva commented 2 years ago

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

paragonie-security commented 2 years ago

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

paragonie-security commented 2 years ago

Oh, I see the problem. I forgot to explicitly pass the nonce when generating these. Yes, your vectors are correct.

panva commented 2 years ago

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

bdemers commented 2 years ago

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):

paragonie-security commented 2 years ago

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.

paragonie-security commented 2 years ago

@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

bdemers commented 2 years ago

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.

paragonie-security commented 2 years ago

How exactly would you recommend enforcing this safely?

paragonie-security commented 2 years ago

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.)

paragonie-security commented 2 years ago

https://github.com/paragonie/paseto/pull/128/commits/2df496e0124ac54cb480ffa7f1a88de6e4422696 adds a Parser rule called FooterJSON that allows assertions to be made about the footer:

  1. Maximum number of object keys
  2. Maximum length of the JSON string
  3. Maximum recursive depth (this is the last check, because in PHP, it requires decoding the string to assert)

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?

paragonie-security commented 2 years ago

@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.

panva commented 2 years ago

@paragonie-security in the typescript example -> Step 6 is an infinite loop. Give it a whirl with the following inputs: getJsonDepth('{"foo":"bar"}'), getJsonDepth('{}')

purificant commented 2 years ago

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 commented 2 years ago

@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.

bdemers commented 2 years ago

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!

paragonie-security commented 2 years ago

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.

paragonie-security commented 2 years ago

We changed the way HKDF is used in Version 3 of PASETO: https://github.com/paragonie/paseto/pull/127/commits/6c6eb51038e830cf95e39cfffe49e66991cdb272

panva commented 2 years ago

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
paragonie-security commented 2 years ago

It looks like we agree: https://github.com/paragonie/paseto/pull/128/commits/7e72e5fe5d27e84f8538f7094cace8a5f8e1ee71

paragonie-security commented 2 years ago

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
paragonie-security commented 2 years ago

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!

paragonie-security commented 2 years ago

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.

paragonie-security commented 2 years ago

Per #78 we've moved the documentation to https://github.com/paseto-standard/paseto-spec

Please feel free to toss issues at that repository.

paragonie-security commented 2 years ago

Spec has been finalized. v2.0.0 release incoming.