Skip to content

Commit

Permalink
Improve clarity of documentation
Browse files Browse the repository at this point in the history
There are no significant changes as far as active implementations are concerned.

The only thing "noteworthy" is the delineation between the version (which is format-agnostic) and the cryptography protocol in naming convention.

This is so that future designers might specify PASETO over an encoding format other than JSON (Protobuf, CBOR, etc.) without having to re-define the cryptography protocols. If this happens, `v4c` would be simply, "Version 4 with CBOR".

You can think of the tokens as `v4j` if that helps (although JSON gets "no suffix" reserved for it).

If you don't care about these kinds of future-proofing discussions, then this commit will be uninteresting to you.
  • Loading branch information
paragonie-security committed Jul 29, 2021
1 parent 092629c commit ccbab6a
Show file tree
Hide file tree
Showing 4 changed files with 72 additions and 31 deletions.
47 changes: 38 additions & 9 deletions docs/01-Protocol-Versions/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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

Expand Down Expand Up @@ -104,14 +126,20 @@ 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
* **`v3.public`**: Asymmetric Authentication (Public-Key Signatures):
* 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

Expand All @@ -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).
51 changes: 30 additions & 21 deletions docs/01-Protocol-Versions/Version3.md
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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,
Expand All @@ -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:
Expand All @@ -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
Expand Down Expand Up @@ -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.
```
Expand All @@ -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
Expand All @@ -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,
Expand Down
3 changes: 3 additions & 0 deletions docs/01-Protocol-Versions/Version4.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion docs/03-Implementation-Guide/02-Validators.md
Original file line number Diff line number Diff line change
Expand Up @@ -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).

0 comments on commit ccbab6a

Please sign in to comment.