From c265301acc29514c29ba163167fbeca102075253 Mon Sep 17 00:00:00 2001 From: Paragon Initiative Enterprises Date: Tue, 7 Sep 2021 12:02:16 -0400 Subject: [PATCH] Make algorithm lucidity the first step. --- docs/01-Protocol-Versions/Version1.md | 86 +++++++++++++-------------- docs/01-Protocol-Versions/Version2.md | 78 +++++++++++------------- docs/01-Protocol-Versions/Version3.md | 76 +++++++++++------------ docs/01-Protocol-Versions/Version4.md | 86 +++++++++++++-------------- 4 files changed, 151 insertions(+), 175 deletions(-) diff --git a/docs/01-Protocol-Versions/Version1.md b/docs/01-Protocol-Versions/Version1.md index 19877b8..01e99a9 100644 --- a/docs/01-Protocol-Versions/Version1.md +++ b/docs/01-Protocol-Versions/Version1.md @@ -9,22 +9,20 @@ Given a message (`m`) and a nonce (`n`): ## Encrypt -Before encrypting, first assert that the key being used is intended for use -with `v1.local` tokens. -See [Algorithm Lucidity](../02-Implementation-Guide/03-Algorithm-Lucidity.md) -for more information. - Given a message `m`, key `k`, and optional footer `f` (which defaults to empty string): -1. Set header `h` to `v1.local.` -2. Generate 32 random bytes from the OS's CSPRNG. -3. Calculate `GetNonce()` of `m` and the output of step 2 +1. Before encrypting, first assert that the key being used is intended for use + with `v1.local` tokens. See [Algorithm Lucidity](../02-Implementation-Guide/03-Algorithm-Lucidity.md) + for more information. +2. Set header `h` to `v1.local.` +3. Generate 32 random bytes from the OS's CSPRNG. +4. Calculate `GetNonce()` of `m` and the output of step 2 to get the nonce, `n`. * This step is to ensure that an RNG failure does not result in a nonce-misuse condition that breaks the security of our stream cipher. -4. Split the key into an Encryption key (`Ek`) and Authentication key (`Ak`), +5. Split the key into an Encryption key (`Ek`) and Authentication key (`Ak`), using the leftmost 16 bytes of `n` as the HKDF salt: ``` Ek = hkdf_sha384( @@ -40,7 +38,7 @@ Given a message `m`, key `k`, and optional footer `f` salt = n[0:16] ); ``` -5. Encrypt the message using `AES-256-CTR`, using `Ek` as the key and +6. Encrypt the message using `AES-256-CTR`, using `Ek` as the key and the rightmost 16 bytes of `n` as the nonce. We'll call this `c`: ``` c = aes256ctr_encrypt( @@ -49,12 +47,12 @@ Given a message `m`, key `k`, and optional footer `f` key = Ek ); ``` -6. Pack `h`, `n`, `c`, and `f` together using +7. Pack `h`, `n`, `c`, and `f` together using [PAE](Common.md#authentication-padding) (pre-authentication encoding). We'll call this `preAuth` -7. Calculate HMAC-SHA384 of the output of `preAuth`, using `Ak` as the +8. Calculate HMAC-SHA384 of the output of `preAuth`, using `Ak` as the authentication key. We'll call this `t`. -8. If `f` is: +9. If `f` is: * Empty: return "`h` || base64url(`n` || `c` || `t`)" * Non-empty: return "`h` || base64url(`n` || `c` || `t`) || `.` || base64url(`f`)" * ...where || means "concatenate" @@ -62,25 +60,23 @@ Given a message `m`, key `k`, and optional footer `f` ## Decrypt -Before decrypting, first assert that the key being used is intended for use -with `v1.local` tokens. -See [Algorithm Lucidity](../02-Implementation-Guide/03-Algorithm-Lucidity.md) -for more information. - Given a message `m`, key `k`, and optional footer `f` (which defaults to empty string): -1. If `f` is not empty, implementations **MAY** verify that the value appended +1. Before decrypting, first assert that the key being used is intended for use + with `v1.local` tokens. See [Algorithm Lucidity](../02-Implementation-Guide/03-Algorithm-Lucidity.md) + for more information. +2. 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. -2. Verify that the message begins with `v1.local.`, otherwise throw an +3. Verify that the message begins with `v1.local.`, otherwise throw an exception. This constant will be referred to as `h`. -3. Decode the payload (`m` sans `h`, `f`, and the optional trailing period +4. 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 * `t` to the rightmost 48 bytes * `c` to the middle remainder of the payload, excluding `n` and `t` -4. Split the key (`k`) into an Encryption key (`Ek`) and an Authentication key +5. Split the key (`k`) into an Encryption key (`Ek`) and an Authentication key (`Ak`), using the leftmost 16 bytes of `n` as the HKDF salt. * For encryption keys, the **info** parameter for HKDF **MUST** be set to **paseto-encryption-key**. @@ -102,14 +98,14 @@ Given a message `m`, key `k`, and optional footer `f` salt = n[0:16] ); ``` -5. Pack `h`, `n`, `c`, and `f` together (in that order) using +6. Pack `h`, `n`, `c`, and `f` together (in that order) using [PAE](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 +7. 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 +8. Compare `t` with `t2` using a constant-time string compare function. If they are not identical, throw an exception. -8. Decrypt `c` using `AES-256-CTR`, using `Ek` as the key and the rightmost 16 +9. Decrypt `c` using `AES-256-CTR`, using `Ek` as the key and the rightmost 16 bytes of `n` as the nonce, and return this value. ``` return aes256ctr_decrypt( @@ -121,19 +117,18 @@ Given a message `m`, key `k`, and optional footer `f` ## Sign -Before signing, first assert that the key being used is intended for use -with `v1.public` tokens, and is the secret key of the intended keypair. -See [Algorithm Lucidity](../02-Implementation-Guide/03-Algorithm-Lucidity.md) -for more information. - Given a message `m`, 2048-bit RSA secret key `sk`, and optional footer `f` (which defaults to empty string): -1. Set `h` to `v1.public.` -2. Pack `h`, `m`, and `f` together using +1. Before signing, first assert that the key being used is intended for use + with `v1.public` tokens, and is the secret key of the intended keypair. + See [Algorithm Lucidity](../02-Implementation-Guide/03-Algorithm-Lucidity.md) + for more information. +2. Set `h` to `v1.public.` +3. Pack `h`, `m`, and `f` together using [PAE](Common.md#authentication-padding) (pre-authentication encoding). We'll call this `m2`. -3. Sign `m2` using RSA with the private key `sk`. We'll call this `sig`. +4. Sign `m2` using RSA with the private key `sk`. We'll call this `sig`. ``` sig = crypto_sign_rsa( message = m2, @@ -145,7 +140,7 @@ optional footer `f` (which defaults to empty string): ); ``` Only the above parameters are supported. PKCS1v1.5 is explicitly forbidden. -4. If `f` is: +5. If `f` is: * Empty: return "`h` || base64url(`m` || `sig`)" * Non-empty: return "`h` || base64url(`m` || `sig`) || `.` || base64url(`f`)" * ...where || means "concatenate" @@ -153,27 +148,26 @@ optional footer `f` (which defaults to empty string): ## Verify -Before verifying, first assert that the key being used is intended for use -with `v1.public` tokens, and is the public key of the intended keypair. -See [Algorithm Lucidity](../02-Implementation-Guide/03-Algorithm-Lucidity.md) -for more information. - Given a signed message `sm`, RSA public key `pk`, and optional footer `f` (which defaults to empty string): -1. If `f` is not empty, implementations **MAY** verify that the value appended +1. Before verifying, first assert that the key being used is intended for use + with `v1.public` tokens, and is the public key of the intended keypair. + See [Algorithm Lucidity](../02-Implementation-Guide/03-Algorithm-Lucidity.md) + for more information. +2. 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. -2. Verify that the message begins with `v1.public.`, otherwise throw an +3. Verify that the message begins with `v1.public.`, otherwise throw an exception. This constant will be referred to as `h`. -3. Decode the payload (`sm` sans `h`, `f`, and the optional trailing period +4. Decode the payload (`sm` sans `h`, `f`, and the optional trailing period between `m` and `f`) from base64url to raw binary. Set: * `s` to the rightmost 256 bytes * `m` to the leftmost remainder of the payload, excluding `s` -4. Pack `h`, `m`, and `f` together (in that order) using PAE (see +5. Pack `h`, `m`, and `f` together (in that order) using PAE (see [PAE](Common.md#authentication-padding). We'll call this `m2`. -5. Use RSA to verify that the signature is valid for the message: +6. Use RSA to verify that the signature is valid for the message: ``` valid = crypto_sign_rsa_verify( signature = s, @@ -185,4 +179,4 @@ footer `f` (which defaults to empty string): mgf = "mgf1+sha384" ); ``` -6. If the signature is valid, return `m`. Otherwise, throw an exception. +7. If the signature is valid, return `m`. Otherwise, throw an exception. diff --git a/docs/01-Protocol-Versions/Version2.md b/docs/01-Protocol-Versions/Version2.md index 7d5b9a5..5cbb741 100644 --- a/docs/01-Protocol-Versions/Version2.md +++ b/docs/01-Protocol-Versions/Version2.md @@ -2,23 +2,21 @@ ## Encrypt -Before encrypting, first assert that the key being used is intended for use -with `v2.local` tokens. -See [Algorithm Lucidity](../02-Implementation-Guide/03-Algorithm-Lucidity.md) -for more information. - Given a message `m`, key `k`, and optional footer `f`. -1. Set header `h` to `v2.local.` -2. Generate 24 random bytes from the OS's CSPRNG. -3. Calculate BLAKE2b of the message `m` with the output of step 2 as the key, +1. Before encrypting, first assert that the key being used is intended for use + with `v2.local` tokens. See [Algorithm Lucidity](../02-Implementation-Guide/03-Algorithm-Lucidity.md) + for more information. +2. Set header `h` to `v2.local.` +3. Generate 24 random bytes from the OS's CSPRNG. +4. Calculate BLAKE2b of the message `m` with the output of step 2 as the key, with an output length of 24. This will be our nonce, `n`. * This step is to ensure that an RNG failure does not result in a nonce-misuse condition that breaks the security of our stream cipher. -4. Pack `h`, `n`, and `f` together (in that order) using +5. Pack `h`, `n`, and `f` together (in that order) using [PAE](Common.md#authentication-padding). We'll call this `preAuth`. -5. Encrypt the message using XChaCha20-Poly1305, using an AEAD interface such as +6. Encrypt the message using XChaCha20-Poly1305, using an AEAD interface such as the one provided in libsodium. ``` c = crypto_aead_xchacha20poly1305_encrypt( @@ -28,7 +26,7 @@ Given a message `m`, key `k`, and optional footer `f`. key = k ); ``` -6. If `f` is: +7. If `f` is: * Empty: return h || base64url(n || c) * Non-empty: return h || base64url(n || c) || `.` || base64url(f) * ...where || means "concatenate" @@ -36,26 +34,24 @@ Given a message `m`, key `k`, and optional footer `f`. ## Decrypt -Before decrypting, first assert that the key being used is intended for use -with `v2.local` tokens. -See [Algorithm Lucidity](../02-Implementation-Guide/03-Algorithm-Lucidity.md) -for more information. - Given a message `m`, key `k`, and optional footer `f`. -1. If `f` is not empty, implementations **MAY** verify that the value appended +1. Before decrypting, first assert that the key being used is intended for use + with `v2.local` tokens. See [Algorithm Lucidity](../02-Implementation-Guide/03-Algorithm-Lucidity.md) + for more information. +2. 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. -2. Verify that the message begins with `v2.local.`, otherwise throw an +3. Verify that the message begins with `v2.local.`, otherwise throw an exception. This constant will be referred to as `h`. -3. Decode the payload (`m` sans `h`, `f`, and the optional trailing period +4. 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 24 bytes * `c` to the middle remainder of the payload, excluding `n`. -4. Pack `h`, `n`, and `f` together (in that order) using +5. Pack `h`, `n`, and `f` together (in that order) using [PAE](Common.md#authentication-padding). We'll call this `preAuth` -5. Decrypt `c` using `XChaCha20-Poly1305`, store the result in `p`. +6. Decrypt `c` using `XChaCha20-Poly1305`, store the result in `p`. ``` p = crypto_aead_xchacha20poly1305_decrypt( ciphertext = c @@ -64,30 +60,29 @@ Given a message `m`, key `k`, and optional footer `f`. key = k ); ``` -6. If decryption failed, throw an exception. Otherwise, return `p`. +7. If decryption failed, throw an exception. Otherwise, return `p`. ## Sign -Before signing, first assert that the key being used is intended for use -with `v2.public` tokens, and is the secret key of the intended keypair. -See [Algorithm Lucidity](../02-Implementation-Guide/03-Algorithm-Lucidity.md) -for more information. - Given a message `m`, Ed25519 secret key `sk`, and optional footer `f` (which defaults to empty string): -1. Set `h` to `v2.public.` -2. Pack `h`, `m`, and `f` together using +1. Before signing, first assert that the key being used is intended for use + with `v2.public` tokens, and is the secret key of the intended keypair. + See [Algorithm Lucidity](../02-Implementation-Guide/03-Algorithm-Lucidity.md) + for more information. +2. Set `h` to `v2.public.` +3. Pack `h`, `m`, and `f` together using [PAE](Common.md#authentication-padding) (pre-authentication encoding). We'll call this `m2`. -3. Sign `m2` using Ed25519 `sk`. We'll call this `sig`. +4. Sign `m2` using Ed25519 `sk`. We'll call this `sig`. ``` sig = crypto_sign_detached( message = m2, private_key = sk ); ``` -4. If `f` is: +5. If `f` is: * Empty: return "`h` || base64url(`m` || `sig`)" * Non-empty: return "`h` || base64url(`m` || `sig`) || `.` || base64url(`f`)" * ...where || means "concatenate" @@ -95,27 +90,26 @@ optional footer `f` (which defaults to empty string): ## Verify -Before verifying, first assert that the key being used is intended for use -with `v2.public` tokens, and is the public key of the intended keypair. -See [Algorithm Lucidity](../02-Implementation-Guide/03-Algorithm-Lucidity.md) -for more information. - Given a signed message `sm`, public key `pk`, and optional footer `f` (which defaults to empty string): -1. If `f` is not empty, implementations **MAY** verify that the value appended +1. Before verifying, first assert that the key being used is intended for use + with `v2.public` tokens, and is the public key of the intended keypair. + See [Algorithm Lucidity](../02-Implementation-Guide/03-Algorithm-Lucidity.md) + for more information. +2. 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. -2. Verify that the message begins with `v2.public.`, otherwise throw an exception. +3. Verify that the message begins with `v2.public.`, otherwise throw an exception. This constant will be referred to as `h`. -3. Decode the payload (`sm` sans `h`, `f`, and the optional trailing period +4. Decode the payload (`sm` sans `h`, `f`, and the optional trailing period between `m` and `f`) from base64url to raw binary. Set: * `s` to the rightmost 64 bytes * `m` to the leftmost remainder of the payload, excluding `s` -4. Pack `h`, `m`, and `f` together using +5. Pack `h`, `m`, and `f` together using [PAE](Common.md#authentication-padding). We'll call this `m2`. -5. Use Ed25519 to verify that the signature is valid for the message: +6. Use Ed25519 to verify that the signature is valid for the message: ``` valid = crypto_sign_verify_detached( signature = s, @@ -123,4 +117,4 @@ Given a signed message `sm`, public key `pk`, and optional footer `f` public_key = pk ); ``` -6. If the signature is valid, return `m`. Otherwise, throw an exception. +7. If the signature is valid, return `m`. Otherwise, throw an exception. diff --git a/docs/01-Protocol-Versions/Version3.md b/docs/01-Protocol-Versions/Version3.md index 48ae8a5..a4069cc 100644 --- a/docs/01-Protocol-Versions/Version3.md +++ b/docs/01-Protocol-Versions/Version3.md @@ -6,17 +6,15 @@ Throw an exception. We don't do this in version 3. ## Encrypt -Before encrypting, first assert that the key being used is intended for use -with `v3.local` tokens. -See [Algorithm Lucidity](../02-Implementation-Guide/03-Algorithm-Lucidity.md) -for more information. - 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`. -3. Split the key into an Encryption key (`Ek`) and Authentication key (`Ak`), +1. Before encrypting, first assert that the key being used is intended for use + with `v3.local` tokens. See [Algorithm Lucidity](../02-Implementation-Guide/03-Algorithm-Lucidity.md) + for more information. +2. Set header `h` to `v3.local.` +3. Generate 32 random bytes from the OS's CSPRNG to get the nonce, `n`. +4. 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. @@ -61,33 +59,31 @@ string), and an optional implicit assertion `i` (which defaults to empty string) ## Decrypt -Before decrypting, first assert that the key being used is intended for use -with `v3.local` tokens. -See [Algorithm Lucidity](../02-Implementation-Guide/03-Algorithm-Lucidity.md) -for more information. - 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. If `f` is not empty, implementations **MAY** verify that the value appended +1. Before decrypting, first assert that the key being used is intended for use + with `v3.local` tokens. See [Algorithm Lucidity](../02-Implementation-Guide/03-Algorithm-Lucidity.md) + for more information. +2. 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](../02-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 +3. 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 +4. 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 * `t` to the rightmost 48 bytes * `c` to the middle remainder of the payload, excluding `n` and `t` -4. Split the key (`k`) into an Encryption key (`Ek`) and an Authentication key +5. Split the key (`k`) into an Encryption key (`Ek`) and an Authentication key (`Ak`), `n` appended to the HKDF info. * For encryption keys, the **info** parameter for HKDF **MUST** be set to **paseto-encryption-key**. @@ -113,18 +109,18 @@ implicit assertion `i` (which defaults to empty string): salt = NULL ); ``` -5. Pack `h`, `n`, `c`, `f`, and `i` together (in that order) using +6. Pack `h`, `n`, `c`, `f`, and `i` together (in that order) using [PAE](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`. -7. Compare `t` with `t2` using a constant-time string compare function. If they +7. Recalculate HMAC-SHA-384 of `preAuth` using `Ak` as the key. We'll call this `t2`. +8. 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, +9. Decrypt `c` using `AES-256-CTR`, using `Ek` as the key and `n2` as the nonce, then return the plaintext. ``` return aes256ctr_decrypt( @@ -136,17 +132,16 @@ implicit assertion `i` (which defaults to empty string): ## Sign -Before signing, first assert that the key being used is intended for use -with `v3.public` tokens, and is the secret key of the intended keypair. -See [Algorithm Lucidity](../02-Implementation-Guide/03-Algorithm-Lucidity.md) -for more information. - 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 +1. Before signing, first assert that the key being used is intended for use + with `v3.public` tokens, and is the secret key of the intended keypair. + See [Algorithm Lucidity](../02-Implementation-Guide/03-Algorithm-Lucidity.md) + for more information. +2. Set `h` to `v3.public.` +3. Pack `pk`, `h`, `m`, `f`, and `i` together using [PAE](Common.md#authentication-padding) (pre-authentication encoding). We'll call this `m2`. * Note: `pk` is the public key corresponding to `sk` (which **MUST** use @@ -155,7 +150,7 @@ Given a message `m`, 384-bit ECDSA secret key `sk`, an optional footer `f` [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 and SHA-384 with the private key `sk`. +4. 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)) @@ -170,7 +165,7 @@ Given a message `m`, 384-bit ECDSA secret key `sk`, an optional footer `f` private_key = sk ); ``` -4. If `f` is: +5. If `f` is: * Empty: return "`h` || base64url(`m` || `sig`)" * Non-empty: return "`h` || base64url(`m` || `sig`) || `.` || base64url(`f`)" * ...where || means "concatenate" @@ -197,32 +192,31 @@ pubKeyCompress(x, y): ## Verify -Before verifying, first assert that the key being used is intended for use -with `v3.public` tokens, and is the public key of the intended keypair. -See [Algorithm Lucidity](../02-Implementation-Guide/03-Algorithm-Lucidity.md) -for more information. - Given a signed message `sm`, ECDSA public key `pk` (which **MUST** use [point compression](https://www.secg.org/sec1-v2.pdf) (Section 2.3.3)), and optional footer `f` (which defaults to empty string), and an optional implicit assertion `i` (which defaults to empty string): -1. If `f` is not empty, implementations **MAY** verify that the value appended +1. Before verifying, first assert that the key being used is intended for use + with `v3.public` tokens, and is the public key of the intended keypair. + See [Algorithm Lucidity](../02-Implementation-Guide/03-Algorithm-Lucidity.md) + for more information. +2. 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. -2. Verify that the message begins with `v3.public.`, otherwise throw an +3. Verify that the message begins with `v3.public.`, otherwise throw an exception. This constant will be referred to as `h`. -3. Decode the payload (`sm` sans `h`, `f`, and the optional trailing period +4. Decode the payload (`sm` sans `h`, `f`, and the optional trailing period between `m` and `f`) from base64url to raw binary. Set: * `s` to the rightmost 96 bytes * `m` to the leftmost remainder of the payload, excluding `s` -4. Pack `pk`, `h`, `m`, `f`, and `i` together (in that order) using PAE (see +5. Pack `pk`, `h`, `m`, `f`, and `i` together (in that order) using PAE (see [PAE](Common.md#authentication-padding). We'll call this `m2`. * `pk` **MUST** be 49 bytes long, and the first byte **MUST** be `0x02` or `0x03` (depending on the sign of the Y coordinate). The remaining bytes **MUST** be the X coordinate, using big-endian byte order. -5. Use ECDSA to verify that the signature is valid for the message: +6. Use ECDSA to verify that the signature is valid for the message: ``` valid = crypto_sign_ecdsa_p384_verify( signature = s, @@ -230,4 +224,4 @@ implicit assertion `i` (which defaults to empty string): public_key = pk ); ``` -6. If the signature is valid, return `m`. Otherwise, throw an exception. +7. If the signature is valid, return `m`. Otherwise, throw an exception. diff --git a/docs/01-Protocol-Versions/Version4.md b/docs/01-Protocol-Versions/Version4.md index 47f110c..1665117 100644 --- a/docs/01-Protocol-Versions/Version4.md +++ b/docs/01-Protocol-Versions/Version4.md @@ -2,17 +2,15 @@ ## Encrypt -Before encrypting, first assert that the key being used is intended for use -with `v4.local` tokens. -See [Algorithm Lucidity](../02-Implementation-Guide/03-Algorithm-Lucidity.md) -for more information. - Given a message `m`, key `k`, and optional footer `f`, and an optional implicit assertion `i` (which defaults to empty string). -1. Set header `h` to `v4.local.` -2. Generate 32 random bytes from the OS's CSPRNG, `n`. -3. Split the key into an Encryption key (`Ek`) and Authentication key (`Ak`), +1. Before encrypting, first assert that the key being used is intended for use + with `v4.local` tokens. See [Algorithm Lucidity](../02-Implementation-Guide/03-Algorithm-Lucidity.md) + for more information. +2. Set header `h` to `v4.local.` +3. Generate 32 random bytes from the OS's CSPRNG, `n`. +4. Split the key into an Encryption key (`Ek`) and Authentication key (`Ak`), using keyed BLAKE2b, using the domain separation constants and `n` as the message, and the input key as the key. The first value will be 56 bytes, the second will be 32 bytes. @@ -32,7 +30,7 @@ implicit assertion `i` (which defaults to empty string). length = 32 ); ``` -4. Encrypt the message using XChaCha20, using `n2` from step 3 as the nonce and `Ek` as the key. +5. Encrypt the message using XChaCha20, using `n2` from step 3 as the nonce and `Ek` as the key. ``` c = crypto_stream_xchacha20_xor( message = m @@ -40,10 +38,10 @@ implicit assertion `i` (which defaults to empty string). key = Ek ); ``` -5. Pack `h`, `n`, `c`, `f`, and `i` together (in that order) using +6. Pack `h`, `n`, `c`, `f`, and `i` together (in that order) using [PAE](Common.md#authentication-padding). We'll call this `preAuth`. -6. Calculate BLAKE2b-MAC of the output of `preAuth`, using `Ak` as the +7. Calculate BLAKE2b-MAC of the output of `preAuth`, using `Ak` as the authentication key. We'll call this `t`. ``` t = crypto_generichash( @@ -52,7 +50,7 @@ implicit assertion `i` (which defaults to empty string). length = 32 ); ``` -7. If `f` is: +8. If `f` is: * Empty: return h || base64url(n || c || t) * Non-empty: return h || base64url(n || c || t) || `.` || base64url(f) * ...where || means "concatenate" @@ -60,28 +58,26 @@ implicit assertion `i` (which defaults to empty string). ## Decrypt -Before decrypting, first assert that the key being used is intended for use -with `v4.local` tokens. -See [Algorithm Lucidity](../02-Implementation-Guide/03-Algorithm-Lucidity.md) -for more information. - Given a message `m`, key `k`, and optional footer `f`, and an optional implicit assertion `i` (which defaults to empty string). -1. If `f` is not empty, implementations **MAY** verify that the value appended +1. Before decrypting, first assert that the key being used is intended for use + with `v4.local` tokens. See [Algorithm Lucidity](../02-Implementation-Guide/03-Algorithm-Lucidity.md) + for more information. +2. 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. -2. Verify that the message begins with `v4.local.`, otherwise throw an +3. 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 +4. 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 * `t` to the rightmost 32 bytes * `c` to the middle remainder of the payload, excluding `n` and `t`. -4. Split the key into an Encryption key (`Ek`) and Authentication key (`Ak`), +5. Split the key into an Encryption key (`Ek`) and Authentication key (`Ak`), using keyed BLAKE2b, using the domain separation constants and `n` as the message, and the input key as the key. The first value will be 56 bytes, the second will be 32 bytes. @@ -101,10 +97,10 @@ implicit assertion `i` (which defaults to empty string). length = 32 ); ``` -5. Pack `h`, `n`, `c`, `f`, and `i` together (in that order) using +6. Pack `h`, `n`, `c`, `f`, and `i` together (in that order) using [PAE](Common.md#authentication-padding). We'll call this `preAuth`. -6. Re-calculate BLAKE2b-MAC of the output of `preAuth`, using `Ak` as the +7. Re-calculate BLAKE2b-MAC of the output of `preAuth`, using `Ak` as the authentication key. We'll call this `t2`. ``` t2 = crypto_generichash( @@ -113,12 +109,12 @@ implicit assertion `i` (which defaults to empty string). length = 32 ); ``` -7. Compare `t` with `t2` using a constant-time string compare function. If they +8. 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). -8. Decrypt `c` using `XChaCha20`, store the result in `p`. +9. Decrypt `c` using `XChaCha20`, store the result in `p`. ``` p = crypto_stream_xchacha20_xor( ciphertext = c @@ -126,31 +122,30 @@ implicit assertion `i` (which defaults to empty string). key = Ek ); ``` -9. If decryption failed, throw an exception. Otherwise, return `p`. +10. If decryption failed, throw an exception. Otherwise, return `p`. ## Sign -Before signing, first assert that the key being used is intended for use -with `v4.public` tokens, and is the secret key of the intended keypair. -See [Algorithm Lucidity](../02-Implementation-Guide/03-Algorithm-Lucidity.md) -for more information. - Given a message `m`, Ed25519 secret key `sk`, and optional footer `f` (which defaults to empty string), and an optional implicit assertion `i` (which defaults to empty string): -1. Set `h` to `v4.public.` -2. Pack `h`, `m`, `f`, and `i` together using +1. Before signing, first assert that the key being used is intended for use + with `v4.public` tokens, and is the secret key of the intended keypair. + See [Algorithm Lucidity](../02-Implementation-Guide/03-Algorithm-Lucidity.md) + for more information. +2. Set `h` to `v4.public.` +3. Pack `h`, `m`, `f`, and `i` together using [PAE](Common.md#authentication-padding) (pre-authentication encoding). We'll call this `m2`. -3. Sign `m2` using Ed25519 `sk`. We'll call this `sig`. +4. Sign `m2` using Ed25519 `sk`. We'll call this `sig`. ``` sig = crypto_sign_detached( message = m2, private_key = sk ); ``` -4. If `f` is: +5. If `f` is: * Empty: return "`h` || base64url(`m` || `sig`)" * Non-empty: return "`h` || base64url(`m` || `sig`) || `.` || base64url(`f`)" * ...where || means "concatenate" @@ -158,28 +153,27 @@ implicit assertion `i` (which defaults to empty string): ## Verify -Before verifying, first assert that the key being used is intended for use -with `v4.public` tokens, and is the public key of the intended keypair. -See [Algorithm Lucidity](../02-Implementation-Guide/03-Algorithm-Lucidity.md) -for more information. - Given a signed message `sm`, public key `pk`, and optional footer `f` (which defaults to empty string), and an optional implicit assertion `i` (which defaults to empty string): -1. If `f` is not empty, implementations **MAY** verify that the value appended +1. Before verifying, first assert that the key being used is intended for use + with `v4.public` tokens, and is the public key of the intended keypair. + See [Algorithm Lucidity](../02-Implementation-Guide/03-Algorithm-Lucidity.md) + for more information. +2. 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. -2. Verify that the message begins with `v4.public.`, otherwise throw an exception. +3. Verify that the message begins with `v4.public.`, otherwise throw an exception. This constant will be referred to as `h`. -3. Decode the payload (`sm` sans `h`, `f`, and the optional trailing period +4. Decode the payload (`sm` sans `h`, `f`, and the optional trailing period between `m` and `f`) from base64url to raw binary. Set: * `s` to the rightmost 64 bytes * `m` to the leftmost remainder of the payload, excluding `s` -4. Pack `h`, `m`, `f`, `i` together using +5. Pack `h`, `m`, `f`, `i` together using [PAE](Common.md#authentication-padding). We'll call this `m2`. -5. Use Ed25519 to verify that the signature is valid for the message: +6. Use Ed25519 to verify that the signature is valid for the message: ``` valid = crypto_sign_verify_detached( signature = s, @@ -187,4 +181,4 @@ implicit assertion `i` (which defaults to empty string): public_key = pk ); ``` -6. If the signature is valid, return `m`. Otherwise, throw an exception. +7. If the signature is valid, return `m`. Otherwise, throw an exception.