Code Monkey home page Code Monkey logo

paseto-spec's Introduction

PASETO: Platform-Agnostic Security Tokens

Paseto is everything you love about JOSE (JWT, JWE, JWS) without any of the many design deficits that plague the JOSE standards.

What is Paseto?

Paseto (Platform-Agnostic SEcurity TOkens) is a specification and reference implementation for secure stateless tokens.

Paseto is pronounced paw-set-oh (pɔːsɛtəʊ).

Key Differences between Paseto and JWT

Unlike JSON Web Tokens (JWT), which gives developers more than enough rope with which to hang themselves, Paseto only allows secure operations. JWT gives you "algorithm agility", Paseto gives you "versioned protocols". It's incredibly unlikely that you'll be able to use Paseto in an insecure way.

Caution: Neither JWT nor Paseto were designed for stateless session management. Paseto is suitable for tamper-proof cookies, but cannot prevent replay attacks by itself.

Paseto

Paseto Example 1

v2.local.QAxIpVe-ECVNI1z4xQbm_qQYomyT3h8FtV8bxkz8pBJWkT8f7HtlOpbroPDEZUKop_vaglyp76CzYy375cHmKCW8e1CCkV0Lflu4GTDyXMqQdpZMM1E6OaoQW27gaRSvWBrR3IgbFIa0AkuUFw.UGFyYWdvbiBJbml0aWF0aXZlIEVudGVycHJpc2Vz

This decodes to:

  • Version: v2
  • Purpose: local (shared-key authenticated encryption)
  • Payload (hex-encoded):
    400c48a557be10254d235cf8c506e6fea418a26c93de1f05b55f1bc64cfca412
    56913f1fec7b653a96eba0f0c46542a8a7fbda825ca9efa0b3632dfbe5c1e628
    25bc7b5082915d0b7e5bb81930f25cca9076964c33513a39aa105b6ee06914af
    581ad1dc881b1486b4024b9417
    
    • Nonce: 400c48a557be10254d235cf8c506e6fea418a26c93de1f05
    • Authentication tag: 6914af581ad1dc881b1486b4024b9417
  • Decrypted Payload:
    {
      "data": "this is a signed message",
      "exp": "2039-01-01T00:00:00+00:00"
    }
    • Key used in this example (hex-encoded):
      707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f  
      
  • Footer:
    Paragon Initiative Enterprises
    

Paseto Example 2

v2.public.eyJleHAiOiIyMDM5LTAxLTAxVDAwOjAwOjAwKzAwOjAwIiwiZGF0YSI6InRoaXMgaXMgYSBzaWduZWQgbWVzc2FnZSJ91gC7-jCWsN3mv4uJaZxZp0btLJgcyVwL-svJD7f4IHyGteKe3HTLjHYTGHI1MtCqJ-ESDLNoE7otkIzamFskCA

This decodes to:

  • Version: v2
  • Purpose: public (public-key digital signature)
  • Payload:
    {
      "data": "this is a signed message",
      "exp": "2039-01-01T00:00:00+00:00"
    }
  • Signature (hex-encoded):
    d600bbfa3096b0dde6bf8b89699c59a746ed2c981cc95c0bfacbc90fb7f8207c
    86b5e29edc74cb8c761318723532d0aa27e1120cb36813ba2d908cda985b2408
    
  • Public key (hex-encoded):
    11324397f535562178d53ff538e49d5a162242970556b4edd950c87c7d86648a
    

To learn what each version means, please see this page in the documentation.

JWT

An example JWT (taken from JWT.io) might look like this:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ 

This decodes to:

Header:

{
  "alg": "HS256",
  "typ": "JWT"
}

Body:

{
  "sub": "1234567890",
  "name": "John Doe",
  "admin": true
}

Signature:

TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ

Motivation

As you can see, with JWT, you get to specify an alg header. There are a lot of options to choose from (including none).

There have been ways to exploit JWT libraries by replacing RS256 with HS256 and using the known public key as the HMAC-SHA256 key, thereby allowing arbitrary token forgery.

With Paseto, your options are version and a purpose. There are two possible values for purpose:

  • local -- shared-key encryption (symmetric-key, AEAD)
  • public -- public-key digital signatures (asymmetric-key)

Paseto only allows you to use authenticated modes.

Regardless of the purpose selected, the header (and an optional footer, which is always cleartext but base64url-encoded) is included in the signature or authentication tag.

PASETO Implementations

The curation of implementations has been moved to paseto.io. See https://github.com/paragonie/paseto-io for the website source code.

Test Vectors

See this repository for the PASETO test vectors.

PASETO Extensions

PASERK

PASERK (Platform-Agnostic SERialized Keys) is an extension to PASETO that provides key-wrapping and serialization.

PASERK is where envelope encryption, public-key encryption, and password-based key encryption are specified.

PASERK is to PASETO what JWK is to JWT.

paseto-spec's People

Contributors

aron123 avatar brycx avatar dajiaji avatar darkkirb avatar hasheddan avatar iamfromspace avatar not-my-profile avatar panva avatar paragonie-security avatar purificant avatar slamdunk avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

paseto-spec's Issues

feedback to v3.public PASETO / k3.secret PASERK

I'm attempting to do a universal javascript (Node, Electron, CloudFlare Workers, Browsers, Deno) module for all PASETO versions and purposes with local, public and secret PASERK as key inputs.

Turns out there is a lightweight JS implementation for (x)chacha20(poly1205) and ed25519 that could support everything that's not in Node's crypto module or Web Cryptography API. That's great because bundling libsodium's JS implementation is a no go.

But in the surprising turn of events, v3.public is a problem to support in browsers because the lack of support for raw (or SPKI) compressed public EC keys as well as private-only PKCS8 private EC keys.

WebKit (ergo all browsers on iOS and macOS Safari) as well as regular desktop Firefox don't support this. This wouldn't be that big of an issue for the verify operation and public key inputs as calculating y from x and the sign can be done in a few lines of code using BigInt math.

The same cannot be said about calculation of the x and y from d for the purposes of adding the exclusive ownership to preAuth when it comes to signing v3.public. tokens.

Given it took firefox 8 years to add PKCS8 EC key import support to their webcrypto I doubt this will be solved - i've opened two issues in bugzilla regardless 12 and i'll try to do the same for webkit34, with similar doubts about it being ever resolved.

If k3.secret followed similar format as k2 (and k4) does - that is, private key would be d || compress(x) - this wouldn't be that big of an issue. It would limit the browser versions supporting this to ones with BigInt support (ES2020) but it's at least something.

Leaving this here for your consideration wrt. changing PASERK (as it is rather new) or simply for your consideration wrt. to future versions utilizing EC keys.

Footnotes

  1. https://bugzilla.mozilla.org/show_bug.cgi?id=1743582

  2. https://bugzilla.mozilla.org/show_bug.cgi?id=1743583

  3. https://bugs.webkit.org/show_bug.cgi?id=233704

  4. https://bugs.webkit.org/show_bug.cgi?id=233705

Consideration for Fail-Closed DateTime Claim Validation (w/Simpler Issuing)

Currently, the spec dictates that signing libraries should default to 1 hour expiry, unless explicitly opting out. I think it would be more natural and simpler to require that validating libraries instead default received claims to 1 hour of activity and reject tokens where expiry cannot be inferred. If validating libraries are not checking a PASeTos active window, then a default by the issuer is hopeless anyway. By doing more in the validating libraries, signing libraries can safely do less.

Essentially the spec would look something like this for validation:

  • if iat, nbf, and exp claims are all missing, the token is rejected
  • if only the iat claim is present, set nbf := iat and exp := iat + 1 hour
  • if only the nbf claim is present, set iat := nbf and exp := nbf + 1 hour
  • if only the iat claim is missing, set iat := nbf
  • if only the nbf claim is missing, set nbf := iat
  • if only the exp claim is missing, set exp := nbf + 1 hour
  • check that iat <= nbf <= now < exp

If desired, library authors could allow users to alter default flow for their own (and should recommend to their users not to do so).

With this flow, non-expiring PASeTos can only be simulated with extremely far reaching dates (it's trivial to make them last thousands of years). This means that libraries and users that do things wrong by accident either make tokens that only last an hour or are never accepted at all. Users who do things wrong must purposefully make errors otherwise.

If this validation is accidentally skipped by a library or user, then all is lost anyway.

Curious to get thoughts on this, and again happy to take a stab at a PR here. Cheers!

Payload Compression

I think it would be useful if payload compression was possible with e.g. using a c suffix (v1c.[purpose].).

One compression algorithm (raw inflate/deflate v1c, brotli v1b, gzip v1g) would be used?

Payload processing for these versions would change to

  • serialize JSON
  • deflate
  • sign or encrypt

or

  • verify or decrypt
  • inflate
  • JSON parse

This is technically possible today if a library exposes a raw payload construct but this approach lacks negotioation between the issuer and recipient that compression is used.

Feel free to take liberty with the suggested scheme.

XChaCha20-BLAKE2b design clarification

Please could you clarify why you've opted for deriving a subnonce and why you've put the nonce in the message of the authentication tag calculation when it's used to derive the authentication key.

I have an implementation that doesn't derive a subnonce and includes the nonce in the authentication key calculation but not in the tag message.

Clarify payload destructuring

For consistency, should the spec specify how to handle cases where a PASETO payload is received with an insufficient length?

Language is largely similar across the board, but using v4 decrypt as a reference, see step 4:

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

The reference implementation notably does not make length checks here, which can result in negative lengths in the calculation of $ciphertext, or negative offsets in the calculation of $mac:

https://github.com/paragonie/paseto/blob/6e3ce7fb5dfb2da512f38877e0b6e987ab519d8b/src/Protocol/Version4.php#L467-L474

        $len = Binary::safeStrlen($decoded);
        $nonce = Binary::safeSubstr($decoded, 0, self::NONCE_SIZE);
        $ciphertext = Binary::safeSubstr(
            $decoded,
            self::NONCE_SIZE,
            $len - (self::NONCE_SIZE + self::MAC_SIZE)
        );
        $mac = Binary::safeSubstr($decoded, $len - self::MAC_SIZE);

Based on the implementation of Binary::safeSubstr when encountering negative offsets, this is ultimately identical to PHP's (i.e. counting backwards from the end of the string). This can lead to curious situations where (dependent on length) the bytes from the mac can be overlapped with the nonce, and a subset of the mac used as the ciphertext.

e.g.

$decoded = str_repeat(1, 31) . str_repeat(3, 32);           // 111111111111111111111111111111133333333333333333333333333333333
$len = Binary::safeStrlen($decoded); // 63
$nonce = Binary::safeSubstr($decoded, 0, self::NONCE_SIZE); // 11111111111111111111111111111113
$ciphertext = Binary::safeSubstr(                           // 333333333333333333333333333333
    $decoded,
    self::NONCE_SIZE,
    $len - (self::NONCE_SIZE + self::MAC_SIZE)
);
$mac = Binary::safeSubstr($decoded, $len - self::MAC_SIZE); // 33333333333333333333333333333333

Aside from overlap (which in theory means that certain payloads are parsed identically even though they are different lengths), lengths of the interpreted ciphertext and mac are also fairly unintuitive here around certain boundaries (with lengths decreasing, and then increasing again). I don't think that this is abusable in practice, but it is probably an odd enough behaviour that it should be avoided?

Would a sensible behaviour in this example be to abort the decryption if $len <= self::NONCE_SIZE + self::MAC_SIZE? Obviously having a zero length ciphertext isn't likely to be legitimate, but we at least dispense of negative offsets here. I don't believe this early abort will have any security impact because the decision is made by examining the ciphertext and associated data alone, and is not interacting with the plaintext (e.g. so wouldn't be abusable in the same way as padding errors in a padding oracle attack). Still, I think it probably makes sense to formalise the early abort here as part of the spec?

For public modes, when calculating the signature offset based on the length subtraction from the end of the string, I think for similar reasons it would make sense to ensure that the signature offset is non-negative (i.e. that the payload is at least "signatureLength" long?)

Document Non-Features?

Is it worthwhile for us to specify non-goals and features we will not consider for inclusion?

For example: At the top of the list is something analogous to JWT's jku claim. This can have a devastating impact on security, as Ryan Sleevi points out with OIDC-Discovery. We're generally better off leaving a jku equivalent out of PASETO entirely.

(What we may do instead is write a future PASETO extension that piggybacks on Gossamer to distribute public keys in an auditable and transparent way.)

Specification is too Broad for Reserved Claims of DateTime Type

Hello, I've been looking into implementing the PASeTo spec in Haskell, and I noticed that this line is impractical in its current form:

...DateTime means an ISO 8601 compliant DateTime string.

ISO 8601 is very broad and flexible, so truly implement this would be a fairly major burden (if not impossible, since arbitrary formatting is possible). On top of this, ISO 8601 allows for specifying dates in ways that simply don't make sense, like local date times, in which case one must know where the signer was in the world. It seems clear that none of this is intentional, and that tightening this down to a specific profile of ISO 8601 would better.

Personally, I would recommend that in PASeTo's spirit of preventing misuse, the only format allowed is "yyyy-mm-ddThh:mm:ss[.sss]Z" from ISO 8601:2004(E) section 4.3.2. This is a common format for an unambiguous instant or moment in time that is independent from a location in the world. Since Time Zones are a frequent area of misunderstanding, and this is an area where they can be avoided all together, they should be avoided.

Since current spec examples do include TZ offsets, if folks feel that needs to be preserved, then it makes sense to also allow for "yyyy-mm-ddThh:mm:ss[.sss]±hh:mm" (from the same section of the standard). Ideally, where only "±00:00" is allowed. Should other offsets be allowed, it would be worth considering to explicitly state that the offset itself should carry no meaning beyond determining the correct instant in time.

I'd be glad to make a PR if a general approach was nailed down.

Cheers!

Implicit length requirement of symmetric key

In v2.local the symmetric key, provided by the user, is passed directly to XChaCha20-Poly1305. This gives the key an implicit requirement of being 32 bytes in length. In v4.local, the symmetric key is instead passed to BLAKE2b which supports keys in range of 1..=64.

I have so far not been able to find any place where the expected length of a symmetric key for v4.local is defined. Is this intended to remain 32 as with v2.local or should an implementer support that which BLAKE2b does?

Checking the PHP implementation, it seems to expect 32:

https://github.com/paragonie/paseto/blob/0d3558824bf77af36ad0cd6e4bb69dbd90ace3c9/src/Protocol/Version4.php#L52

In any case, I believe this could be worthwhile mentioning in the spec of Version 4. Or, perhaps, I've missed something obvious?

Fail when base64 data contains nonzero padding bits

If the data in a base64 field of a PASETO token isn't a multiple of 6 bits, the last base64 character will be padded with 2 or 4 "zero" bits. But when decoding, most base64 libraries will ignore those last 4 bits.

eg. base64_encode(b"\xff") (11111111) == "_w" (111111 110000). But during decoding base64_decode("_w") (111111 110000) == base64_decode("_y") (111111 110001).

So given a valid PASETO token, you can construct a slightly different token with the same content but a different base64 representation. For example, these two public tokens both decode to the same content, despite having different final characters.

  • v4.public.eyJleHAiOiIyMDIyLTA2LTA5VDE4OjI0OjA4LTA0OjAwIiwidXNlciI6Inplcm8gY29vbCJ9CaoTvJwHuqZgEE8bQVpaIo3sTdZMEtuAXicK_mB2km98GkxvHYc_7nAzCsWjsnvI7OiwUW9aVyS5UnPOXYBnCw
  • v4.public.eyJleHAiOiIyMDIyLTA2LTA5VDE4OjI0OjA4LTA0OjAwIiwidXNlciI6Inplcm8gY29vbCJ9CaoTvJwHuqZgEE8bQVpaIo3sTdZMEtuAXicK_mB2km98GkxvHYc_7nAzCsWjsnvI7OiwUW9aVyS5UnPOXYBnCx

It seems weird for PASETO libraries to accept tokens that they clearly didn't generate.

Question

What are the pros/cons to use base 64 instead of base 58 for encoding/decoding?

Deprecate `v3.public.`

In https://github.com/paseto-standard/paseto-spec/blob/master/docs/Rationale-V3-V4.md there are numerous claims that Ed25519 should be preferred over P-384.

ECDSA is much more dangerous to implement than Ed25519

If you're concerned about NSA backdoors, don't use v3 (which only uses NIST-approved algorithms). Use v4 instead.

At the bottom, it states

If you want smaller tokens or better performance than P-384, make sure Ed25519 lands in FIPS 186-5 and use v4.public instead.

Ed25519 did land in FIPS 186-5 and therefore v4.public. features only NIST-approved algorithms. Since v3 exists only for NIST-dependant applications, it is now redundant.

Unclear how to handle empty messages

Before the new test-vectors repository was created (which is very nice to have btw), I think I remember some tests in the reference PHP implementation, that dealt with tokens that were created where message m was empty.

I haven't been able to find a related test-vector or a clarification in the spec on whether m can be empty or not. What is expected here? If it can be empty, perhaps this is worthwhile as a test for the test-vectors collection.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.