diff --git a/docs/01-Protocol-Versions/README.md b/docs/01-Protocol-Versions/README.md index 150b46fe..75f53e18 100644 --- a/docs/01-Protocol-Versions/README.md +++ b/docs/01-Protocol-Versions/README.md @@ -3,7 +3,29 @@ This document describes the cryptography and encoding rules for Paseto protocol versions, to assist in cross-platform library development. -# Rules for Current and Future Protocol Versions +## Naming Conventions + +The cryptography protocol version is named using this convention: `/^Version \d$/`. +When we discuss "Version 4" spelled out, we're talking about the cryptography +without any regard to the underlying encoding format for the payload containing +the claims. + +The token format is named using this convention: `/^v\d([a-z]+?)$/`. When +no optional suffix is provided, this describes a PASETO token using JSON +to encode claims, along with the corresponding Version (see previous paragraph) +to protect those claims. + +The intent is that the cryptographic format ("Version 3", "Version 4") can be +reused for arbitrary payloads, but the token format ("v3", "v4") refers to a +specific encoding of claims under-the-hood. + +If this is confusing, just know that most of the time, you only need to deal +with the complete token (n.b., some permutation of {`v1`, `v2`, `v3`, `v4`} +and {`local`, `public`}). +The cryptographic layer (`Version 1`, `Version 2`, `Version 3`, `Version 4`) +is mostly for cryptographers to argue about. + +## Rules for Current and Future Protocol Versions 1. Everything must be authenticated. Attackers should never be allowed the opportunity to alter messages freely. @@ -19,19 +41,19 @@ to assist in cross-platform library development. * This means no RSA with PKCS1v1.5 padding, textbook RSA, etc. 4. By default, libraries should only allow the two most recent versions in a family to be used. - * The NIST family of versions is `v1` and `v3`. - * The Sodium family of versions is `v2` and `v4`. - * If a future post-quantum `v5` (NIST) and/or `v6` (Sodium) is defined, - `v1` and `v2` should no longer be accepted. + * The NIST family of versions is `Version 1` and `Version 3`. + * The Sodium family of versions is `Version 2` and `Version 4`. + * If a future post-quantum `Version 5` (NIST) and/or `Version 6` (Sodium) is defined, + `Version 1` and `Version 2` should no longer be accepted. * This is a deviation from the **original** intent of this rule, to encapsulate the fact that we have parallel versions. In the future, we expect this to converge to one family of versions. 5. New versions will be decided and formalized by the PASETO developers. * User-defined homemade protocols are discouraged. If implementors wish to break this rule and define their own custom protocol suite, they must NOT continue - the {`v1`, `v2`, ... } series naming convention. - * Any version identifiers that match the regular expression, `/^v[0-9\-\.]+/` are - reserved by the PASETO development team. + the {`v1`, `v2`, ... } series naming convention for tokens. + * Any version identifiers that match the regular expression, + `/^v[0-9\-\.]+([a-z]+?)/` are reserved by the PASETO development team. # Versions @@ -104,7 +126,8 @@ See [the version 3 specification](Version3.md) for details. At a glance: The encryption key and implicit counter nonce are both returned from HKDF in this version. * Info for authentication key: `paseto-auth-key-for-aead` - * 32-byte nonce (no longer prehashed), passed entirely to HKDF. + * 32-byte nonce (no longer prehashed), passed entirely to HKDF + (as part of the `info` tag, rather than as a salt). * The HMAC covers the header, nonce, and ciphertext * It also covers the footer, if provided * It also covers the implicit assertions, if provided @@ -112,6 +135,11 @@ See [the version 3 specification](Version3.md) for details. At a glance: * ECDSA over NIST P-384, with SHA-384, using [RFC 6979 deterministic k-values](https://tools.ietf.org/html/rfc6979) (if reasonably practical; otherwise a CSPRNG **MUST** be used). + Hedged signatures are allowed too. + * The public key is also included in the PAE step, to ensure + `v3.public` tokens provide Exclusive Ownership. + +See also: [Common implementation details for all versions](Common.md). ## Version 4: Sodium Modern @@ -133,3 +161,4 @@ See [the version 4 specification](Version4.md) for details. At a glance: * Signing: `sodium_crypto_sign_detached()` * Verifying: `sodium_crypto_sign_verify_detached()` +See also: [Common implementation details for all versions](Common.md). diff --git a/docs/01-Protocol-Versions/Version3.md b/docs/01-Protocol-Versions/Version3.md index ca30f042..f1341903 100644 --- a/docs/01-Protocol-Versions/Version3.md +++ b/docs/01-Protocol-Versions/Version3.md @@ -6,19 +6,18 @@ Throw an exception. We don't do this in version 3. ## Encrypt -Given a message `m`, key `k`, and optional footer `f` -(which defaults to empty string), and an optional -implicit assertion `i` (which defaults to empty string): +Given a message `m`, key `k`, and optional footer `f` (which defaults to empty +string), and an optional implicit assertion `i` (which defaults to empty string): 1. Set header `h` to `v3.local.` -2. Generate 32 random bytes from the OS's CSPRNG - to get the nonce, `n`. +2. Generate 32 random bytes from the OS's CSPRNG to get the nonce, `n`. 3. Split the key into an Encryption key (`Ek`) and Authentication key (`Ak`), using HKDF-HMAC-SHA384, with `n` appended to the info rather than the salt. * The output length **MUST** be 48 for both key derivations. * The derived key will be the leftmost 32 bytes of the first HKDF derivation. - The remaining 16 bytes will be used as a counter nonce (`n2`): + The remaining 16 bytes of the first key derivation (from which `Ek` is derived) + will be used as a counter nonce (`n2`): ``` tmp = hkdf_sha384( len = 48, @@ -35,8 +34,8 @@ implicit assertion `i` (which defaults to empty string): salt = NULL ); ``` -5. Encrypt the message using `AES-256-CTR`, using `Ek` as the key and - `n2` as the nonce. We'll call this `c`: +5. Encrypt the message using `AES-256-CTR`, using `Ek` as the key and `n2` as the nonce. + We'll call the encrypted output of this step `c`: ``` c = aes256ctr_encrypt( plaintext = m, @@ -46,7 +45,7 @@ implicit assertion `i` (which defaults to empty string): ``` 6. Pack `h`, `n`, `c`, `f`, and `i` together using [PAE](https://github.com/paragonie/paseto/blob/master/docs/01-Protocol-Versions/Common.md#authentication-padding) - (pre-authentication encoding). We'll call this `preAuth` + (pre-authentication encoding). We'll call this `preAuth`. 7. Calculate HMAC-SHA384 of the output of `preAuth`, using `Ak` as the authentication key. We'll call this `t`. 8. If `f` is: @@ -64,8 +63,15 @@ implicit assertion `i` (which defaults to empty string): 1. If `f` is not empty, implementations **MAY** verify that the value appended to the token matches some expected string `f`, provided they do so using a constant-time string compare function. + * If `f` is allowed to be a JSON-encoded blob, implementations **SHOULD** allow + users to provide guardrails against invalid JSON tokens. + See [this document](../03-Implementation-Guide/01-Payload-Processing.md#optional-footer) + for specific guidance and example code. 2. Verify that the message begins with `v3.local.`, otherwise throw an exception. This constant will be referred to as `h`. + * **Future-proofing**: If a future PASETO variant allows for encodings other + than JSON (e.g., CBOR), future implementations **MAY** also permit those + values at this step (e.g. `v3c.local.`). 3. Decode the payload (`m` sans `h`, `f`, and the optional trailing period between `m` and `f`) from base64url to raw binary. Set: * `n` to the leftmost 32 bytes @@ -100,13 +106,14 @@ implicit assertion `i` (which defaults to empty string): 5. Pack `h`, `n`, `c`, `f`, and `i` together (in that order) using [PAE](https://github.com/paragonie/paseto/blob/master/docs/01-Protocol-Versions/Common.md#authentication-padding). We'll call this `preAuth`. -6. Recalculate HMAC-SHA-384 of `preAuth` using `Ak` as the key. We'll call this - `t2`. +6. Recalculate HMAC-SHA-384 of `preAuth` using `Ak` as the key. We'll call this `t2`. 7. Compare `t` with `t2` using a constant-time string compare function. If they are not identical, throw an exception. * You **MUST** use a constant-time string compare function to be compliant. If you do not have one available to you in your programming language/framework, you MUST use [Double HMAC](https://paragonie.com/blog/2015/11/preventing-timing-attacks-on-string-comparison-with-double-hmac-strategy). + * Common utilities that were not intended for cryptographic comparisons, such as + Java's `Array.equals()` or PHP's `==` operator, are explicitly forbidden. 8. Decrypt `c` using `AES-256-CTR`, using `Ek` as the key and `n2` as the nonce, then return the plaintext. ``` @@ -119,9 +126,9 @@ implicit assertion `i` (which defaults to empty string): ## Sign -Given a message `m`, 384-bit ECDSA secret key `sk`, and -optional footer `f` (which defaults to empty string), and an optional -implicit assertion `i` (which defaults to empty string): +Given a message `m`, 384-bit ECDSA secret key `sk`, an optional footer `f` +(which defaults to empty string), and an optional implicit assertion `i` +(which defaults to empty string): 1. Set `h` to `v3.public.` 2. Pack `pk`, `h`, `m`, `f`, and `i` together using @@ -133,13 +140,15 @@ implicit assertion `i` (which defaults to empty string): [the least significant bit of Y](https://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.202.2977&rep=rep1&type=pdf); section 4.3.6, step 2.2). The remaining bytes **MUST** be the X coordinate, using big-endian byte order. -3. Sign `m2` using ECDSA over P-384 with the private key `sk`. We'll call this `sig`. - The output of `sig` MUST be in the format `r || s` (where `||`means concatenate), - for a total length of 96 bytes. - Signatures **SHOULD** use deterministic nonces ([RFC 6979](https://tools.ietf.org/html/rfc6979)) - if possible, to mitigate the risk of [k-value reuse](https://blog.trailofbits.com/2020/06/11/ecdsa-handle-with-care/). - If RFC 6979 is not available in your programming language, ECDSA **MUST** use a CSPRNG - to generate the k-value. +3. Sign `m2` using ECDSA over P-384 and SHA-384 with the private key `sk`. + We'll call this `sig`. The output of `sig` MUST be in the format `r || s` + (where `||`means concatenate), for a total length of 96 bytes. + * Signatures **SHOULD** use deterministic nonces ([RFC 6979](https://tools.ietf.org/html/rfc6979)) + if possible, to mitigate the risk of [k-value reuse](https://blog.trailofbits.com/2020/06/11/ecdsa-handle-with-care/). + * If RFC 6979 is not available in your programming language, ECDSA **MUST** use a CSPRNG + to generate the k-value. + * Hedged signatures (RFC 6979 + additional randomness to provide resilience to fault attacks) + are allowed. ``` sig = crypto_sign_ecdsa_p384( message = m2, diff --git a/docs/01-Protocol-Versions/Version4.md b/docs/01-Protocol-Versions/Version4.md index 37b9dae9..5034cb7d 100644 --- a/docs/01-Protocol-Versions/Version4.md +++ b/docs/01-Protocol-Versions/Version4.md @@ -62,6 +62,9 @@ implicit assertion `i` (which defaults to empty string). constant-time string compare function. 2. Verify that the message begins with `v4.local.`, otherwise throw an exception. This constant will be referred to as `h`. + * **Future-proofing**: If a future PASETO variant allows for encodings other + than JSON (e.g., CBOR), future implementations **MAY** also permit those + values at this step (e.g. `v4c.local.`). 3. Decode the payload (`m` sans `h`, `f`, and the optional trailing period between `m` and `f`) from base64url to raw binary. Set: * `n` to the leftmost 32 bytes diff --git a/docs/03-Implementation-Guide/02-Validators.md b/docs/03-Implementation-Guide/02-Validators.md index 694f1da5..b1164ed4 100644 --- a/docs/03-Implementation-Guide/02-Validators.md +++ b/docs/03-Implementation-Guide/02-Validators.md @@ -26,4 +26,4 @@ Some examples of validation rules that libraries may wish to provide include: Example implementations of these validators are included in the PHP implementation. -Validation should fail-closed by default (e.g. if invalid data is provided). +Validation should fail-closed by default (e.g., if invalid data is provided).