Skip to content

Commit

Permalink
feat: removed the libsodium dependency
Browse files Browse the repository at this point in the history
BREAKING CHANGE: removed v2.local encrypt, decrypt, and key generation

BREAKING CHANGE: requires Node.js version ^12.19.0 || >=14.15.0
  • Loading branch information
panva committed Feb 23, 2021
1 parent ede6112 commit 0fe5de6
Show file tree
Hide file tree
Showing 14 changed files with 20 additions and 720 deletions.
6 changes: 2 additions & 4 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,9 @@ jobs:
strategy:
matrix:
node-version:
- 12.0.0
- 12.19.0
- 12
- 13.7.0
- 13
- 14.0.0
- 14.15.0
- 14
- 15.0.1
- 15
Expand Down
30 changes: 4 additions & 26 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ main thread's I/O is not blocked.

| | v1.local | v1.public | v2.local | v2.public |
| -- | -- | -- | -- | -- |
| supported? ||| ||
| supported? ||| ||

## Support

Expand Down Expand Up @@ -45,7 +45,7 @@ const { decode } = paseto
const { V1 } = paseto // { sign, verify, encrypt, decrypt, generateKey }

// PASETO Protocol Version v2 specific API
const { V2 } = paseto // { sign, verify, encrypt, decrypt, generateKey }
const { V2 } = paseto // { sign, verify, generateKey }

// errors utilized by paseto
const { errors } = paseto
Expand All @@ -54,13 +54,9 @@ const { errors } = paseto
#### Producing tokens

```js
const { V2: { encrypt, sign } } = paseto
const { V2: { sign } } = paseto

(async () => {
{
const token = await encrypt({ sub: 'johndoe' }, secretKey)
// v2.local.rRfHP25HDj5Pda40FwdTsGcsEMoQAKM6ElH6OhCon6YzG1Pzmj1ZPAHORhPaxKQo0XLM5LPYgaevWGrkEy2Os3N68Xee_Me9A0LmbMlV6MNVt-UZMos7ETha
}
{
const token = await sign({ sub: 'johndoe' }, privateKey)
// v2.public.eyJzdWIiOiJqb2huZG9lIiwiaWF0IjoiMjAxOS0wNy0wMVQxNToyMTozMS40OTJaIn0tpEwuwb-loL652KAZhmCYdDUNW8YbF6UYCFCYLk-fexhzs2ofL4AyHTqIk0HzIxawufEibT1ZyJ7MPBJUVpsF
Expand All @@ -71,13 +67,9 @@ const { V2: { encrypt, sign } } = paseto
#### Consuming tokens

```js
const { V2: { decrypt, verify } } = paseto
const { V2: { verify } } = paseto

(async () => {
{
const payload = await decrypt(token, secretKey)
// { sub: 'johndoe', iat: '2019-07-01T15:22:47.982Z' }
}
{
const payload = await verify(token, publicKey)
// { sub: 'johndoe', iat: '2019-07-01T15:22:47.982Z' }
Expand Down Expand Up @@ -123,15 +115,6 @@ const { V1, V2 } = paseto
console.log(key.asymmetricKeyType === 'rsa')
// true
}
{
const key = await V2.generateKey('local')
console.log(key instanceof crypto.KeyObject)
// true
console.log(key.type === 'secret')
// true
console.log(key.symmetricKeySize === 32)
// true
}
{
const key = await V2.generateKey('public')
console.log(key instanceof crypto.KeyObject)
Expand All @@ -158,11 +141,6 @@ private API and is subject to change between any versions.
It is **only built for Node.js** environment - it builds on top of the `crypto` module and requires
the KeyObject API that was added in Node.js v11.6.0 and one-shot sign/verify API added in v12.0.0

#### What is the ultimate goal?

- **No dependencies**, the moment `XChaCha20-Poly1305` is supported by OpenSSL and therefore node's
`crypto` the direct dependency count will go down from 1 to 0. 🚀


[documentation]: https://github.com/panva/paseto/blob/master/docs/README.md
[documentation-v2]: https://github.com/panva/paseto/blob/master/docs/README.md#v2-paseto-protocol-version-v2
Expand Down
120 changes: 2 additions & 118 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,6 @@ If you or your business use paseto, please consider becoming a [sponsor][support
<!-- TOC V2 START -->
- [V2.sign(payload, key[, options])](#v2signpayload-key-options)
- [V2.verify(token, key[, options])](#v2verifytoken-key-options)
- [V2.encrypt(payload, key[, options])](#v2encryptpayload-key-options)
- [V2.decrypt(token, key[, options])](#v2decrypttoken-key-options)
- [V2.generateKey(purpose)](#v2generatekeypurpose)
<!-- TOC V2 END -->

Expand All @@ -33,8 +31,6 @@ const { V2 } = require('paseto')
// {
// sign: [AsyncFunction: v2Sign],
// verify: [AsyncFunction: v2Verify],
// encrypt: [AsyncFunction: v2Encrypt],
// decrypt: [AsyncFunction: v2Decrypt],
// generateKey: [AsyncFunction: generateKey]
// }
```
Expand Down Expand Up @@ -152,123 +148,11 @@ const token = 'v2.public.eyJ1cm46ZXhhbXBsZTpjbGFpbSI6ImZvbyIsImlhdCI6IjIwMTktMDc

---

#### V2.encrypt(payload, key[, options])

Serializes and encrypts the payload as a PASETO using the provided secret key.

- `payload`: `<Object>` PASETO Payload claims
- `key`: `<KeyObject>` The secret key to encrypt with. Alternatively any input that works for `crypto.createSecretKey`
- `options`: `<Object>`
- `audience`: `<string>` PASETO Audience, "aud" claim value, if provided it will replace
"aud" found in the payload
- `expiresIn`: `<string>` PASETO Expiration Time, "exp" claim value, specified as string which is
added to the current unix epoch timestamp e.g. `24 hours`, `20 m`, `60s`, etc., if provided it
will replace Expiration Time found in the payload
- `footer`: `<Object>` &vert; `<string>` &vert; `<Buffer>` PASETO footer
- `iat`: `<Boolean>` When true it pushes the "iat" to the PASETO payload. **Default:** 'true'
- `issuer`: `<string>` PASETO Issuer, "iss" claim value, if provided it will replace "iss" found in
the payload
- `jti`: `<string>` Token ID, "jti" claim value, if provided it will replace "jti" found in the
payload
- `kid`: `<string>` Key ID, "kid" claim value, if provided it will replace "kid" found in the
payload
- `notBefore`: `<string>` PASETO Not Before, "nbf" claim value, specified as string which is added to
the current unix epoch timestamp e.g. `24 hours`, `20 m`, `60s`, etc., if provided it will
replace Not Before found in the payload
- `now`: `<Date>` Date object to be used instead of the current unix epoch timestamp.
**Default:** 'new Date()'
- `subject`: `<string>` PASETO subject, "sub" claim value, if provided it will replace "sub" found in
the payload
- Returns: `Promise<string>`

<details>
<summary><em><strong>Example</strong></em> (Click to expand)</summary>

```js
const { createSecretKey } = require('crypto')
const { V2 } = require('paseto')

const key = createSecretKey(secret)

const payload = {
'urn:example:claim': 'foo'
}

(async () => {
const token = await V2.encrypt(payload, key, {
audience: 'urn:example:client',
issuer: 'https://op.example.com',
expiresIn: '2 hours'
})
// v2.local.qPegA36ANA_Q4GnR6BCBQqW2O6N3UVE0X0cJEzPHguKv8YeUlhMz7mRqp-LqCJ0hMDwsvoJ1xYrx2sT5yf_T-WiBj_gVddUyL4HEUPyYxWVWQtl8CZ-YTDsw1adQetScDvg91P-IK-1FlRfp2lZE8BOYnGgUKTjbtnpy3XOsgnqc4K4K0KDTURXQgs2-FDcfRm3bjxTlRoOnetNEyabmnB1od3wOesyrqNv7migvgq-nvxZi-7rv1qVATgXFyFQ
})()
```
</details>

---

#### V2.decrypt(token, key[, options])

Decrypts and validates the claims of a PASETO

- `token`: `<String>` PASETO to decrypt and validate
- `key`: `<KeyObject>` The secret key to decrypt with. Alternatively any input that works for `crypto.createSecretKey`
- `options`: `<Object>`
- `audience`: `<string>` Expected audience value. An exact match must be found in the payload.
- `clockTolerance`: `<string>` Clock Tolerance for comparing timestamps, provided as timespan
string e.g. `120s`, `2 minutes`, etc. **Default:** no clock tolerance
- `complete`: `<Boolean>` When false only the parsed payload is returned, otherwise an object with
a parsed payload and footer (as a Buffer) will be returned.
**Default:** 'false'
- `ignoreExp`: `<Boolean>` When true will not be validating the "exp" claim value to be in the
future from now. **Default:** 'false'
- `ignoreIat`: `<Boolean>` When true will not be validating the "iat" claim value to be in the
past from now. **Default:** 'false'
- `ignoreNbf`: `<Boolean>` When true will not be validating the "nbf" claim value to be in the
past from now. **Default:** 'false'
- `issuer`: `<string>` Expected issuer value. An exact match must be found in the payload.
- `maxTokenAge`: `<string>` When provided the payload is checked to have the "iat" claim and its
value is validated not to be older than the provided timespan string e.g. `30m`, `24 hours`.
- `now`: `<Date>` Date object to be used instead of the current unix epoch timestamp.
**Default:** 'new Date()'
- `subject`: `<string>` Expected subject value. An exact match must be found in the payload.
- Returns: `Promise<Object>`

<details>
<summary><em><strong>Example</strong></em> (Click to expand)</summary>

```js
const { createSecretKey } = require('crypto')
const { V2 } = require('paseto')

const key = createSecretKey(secret)

const token = 'v2.local.qPegA36ANA_Q4GnR6BCBQqW2O6N3UVE0X0cJEzPHguKv8YeUlhMz7mRqp-LqCJ0hMDwsvoJ1xYrx2sT5yf_T-WiBj_gVddUyL4HEUPyYxWVWQtl8CZ-YTDsw1adQetScDvg91P-IK-1FlRfp2lZE8BOYnGgUKTjbtnpy3XOsgnqc4K4K0KDTURXQgs2-FDcfRm3bjxTlRoOnetNEyabmnB1od3wOesyrqNv7migvgq-nvxZi-7rv1qVATgXFyFQ'

(async () => {
await V2.decrypt(token, key, {
audience: 'urn:example:client',
issuer: 'https://op.example.com',
clockTolerance: '1 min'
})
// {
// 'urn:example:claim': 'foo',
// iat: '2019-07-02T13:50:39.735Z',
// exp: '2019-07-02T15:50:39.735Z',
// aud: 'urn:example:client',
// iss: 'https://op.example.com'
// }
})()
```
</details>

---

#### V2.generateKey(purpose)

Generates a new secret or private key for a given purpose.

- `purpose`: `<string>` PASETO purpose, either 'local' or 'public'
- `purpose`: `<string>` PASETO purpose, only 'public' is supported.
- Returns: `Promise<KeyObject>`

---
Expand Down Expand Up @@ -499,7 +383,7 @@ const { V1 } = require('paseto')

const key = createSecretKey(secret)

const token = 'v2.local.qPegA36ANA_Q4GnR6BCBQqW2O6N3UVE0X0cJEzPHguKv8YeUlhMz7mRqp-LqCJ0hMDwsvoJ1xYrx2sT5yf_T-WiBj_gVddUyL4HEUPyYxWVWQtl8CZ-YTDsw1adQetScDvg91P-IK-1FlRfp2lZE8BOYnGgUKTjbtnpy3XOsgnqc4K4K0KDTURXQgs2-FDcfRm3bjxTlRoOnetNEyabmnB1od3wOesyrqNv7migvgq-nvxZi-7rv1qVATgXFyFQ'
const token = 'v1.local.1X8AshBYnBXTevpH6s21lTZzPL8k-pVaRBsfU5uFfpDWAoG8NZAB5LwQgUpcsgAbZj-wpDMix1Mzw_viBbntWjqEZAVOe-BTMhVKSe43u3fUM2EfRcNFHzPVY_2I_CqGjhW2qs6twNvgv5kEhOiUnTSgZMtCn9h6L_KlKz8YrWcGdGypBYcs5ooMClKvOhb2_M8wHqG_PCgAkgO5PBbHk1g6UnTgGgztuEMrcchLd7UJqNDU2I7TyQ9x7ofvndE35ODYaf-SefrJb72tuXaUqFbkAwKPs77EwvnWE5dgo6bbsp5KMdxq'

(async () => {
await V1.decrypt(token, key, {
Expand Down
61 changes: 3 additions & 58 deletions lib/help/crypto_worker.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,8 @@
const { parentPort, Worker, isMainThread } = require('worker_threads')
const crypto = require('crypto')

const [major, minor] = process.version.substr(1).split('.').map((x) => parseInt(x, 10))
const supportsKeyObjectInPostMessage = major > 14 || (major === 14 && minor >= 5) || (major === 12 && minor >= 19)

if (isMainThread) {
const tasks = new Map()
const exportArgs = {
public: [{ format: 'der', type: 'spki' }],
private: [{ format: 'der', type: 'pkcs8' }]
}

let worker
let taskId = 0
Expand Down Expand Up @@ -37,28 +30,6 @@ if (isMainThread) {
spawn()
}

if (!supportsKeyObjectInPostMessage) {
let key

let keyObject = args[2]

if (keyObject instanceof crypto.KeyObject) {
key = {
key: keyObject.export.apply(keyObject, exportArgs[keyObject.type]),
...exportArgs[keyObject.type][0]
}
} else if (Buffer.isBuffer(keyObject)) {
key = keyObject
} else {
key = keyObject
keyObject = key.key
key.key = keyObject.export.apply(keyObject, exportArgs[keyObject.type])
Object.assign(key, exportArgs[keyObject.type][0])
}

args[2] = key
}

worker.ref()
worker.postMessage({ id, method, args })
})
Expand All @@ -73,8 +44,6 @@ if (isMainThread) {
}
/* c8 ignore next 114 */
} else {
const sodium = require('libsodium-wrappers')

const pae = require('./pae')
const hkdf = (key, length, salt, info) => {
const prk = methods.hmac('sha384', key, salt)
Expand Down Expand Up @@ -142,15 +111,9 @@ if (isMainThread) {
return hmac.digest()
},
verify (alg, payload, key, signature) {
if (!supportsKeyObjectInPostMessage) {
key.key = Buffer.from(key.key)
}
return crypto.verify(alg, payload, key, signature)
},
sign (alg, payload, key) {
if (!supportsKeyObjectInPostMessage) {
key.key = Buffer.from(key.key)
}
return crypto.sign(alg, payload, key)
},
encrypt (cipher, cleartext, key, iv) {
Expand All @@ -164,29 +127,11 @@ if (isMainThread) {
} catch (err) {
return false
}
},
'xchacha20-poly1305-encrypt' (cleartext, nonce, key, footer) {
const n = sodium.crypto_generichash(24, cleartext, nonce)
const preAuth = pae('v2.local.', n, footer)

return {
n,
c: sodium.crypto_aead_xchacha20poly1305_ietf_encrypt(cleartext, preAuth, undefined, n, key)
}
},
'xchacha20-poly1305-decrypt' (ciphertext, nonce, key, preAuth) {
try {
return sodium.crypto_aead_xchacha20poly1305_ietf_decrypt(undefined, ciphertext, preAuth, nonce, key)
} catch (err) {
return false
}
}
}

sodium.ready.then(() => {
parentPort.on('message', function ({ id, method, args }) {
const value = methods[method](...args)
parentPort.postMessage({ id, value })
})
parentPort.on('message', function ({ id, method, args }) {
const value = methods[method](...args)
parentPort.postMessage({ id, value })
})
}
48 changes: 0 additions & 48 deletions lib/v2/decrypt.js

This file was deleted.

Loading

0 comments on commit 0fe5de6

Please sign in to comment.