diff --git a/README.md b/README.md
index b0a1232944..ffddddcf4e 100644
--- a/README.md
+++ b/README.md
@@ -28,82 +28,6 @@ Available JWT validation profiles
- OAuth 2.0 JWT Access Tokens (`at+JWT`) - [JWT Profile for OAuth 2.0 Access Tokens][draft-ietf-oauth-access-token-jwt]
- OIDC Logout Token (`logout_token`) - [OpenID Connect Back-Channel Logout 1.0][spec-oidc-logout_token]
-
- Detailed feature matrix (Click to expand)
-
-Legend:
-- **✓** Implemented
-- **✕** Missing node crypto support / won't implement
-- **◯** TBD
-
-| JWK Key Types | Supported | `kty` |
-| -- | -- | -- |
-| RSA | ✓ | RSA |
-| Elliptic Curve | ✓ | EC (P-256, secp256k1, P-384, P-521) |
-| Octet Key Pair | ✓ | OKP (Ed25519, Ed448, X25519, X448) |
-| Octet sequence | ✓ | oct |
-
-| Serialization | JWS Sign | JWS Verify | JWE Encrypt | JWE Decrypt |
-| -- | -- | -- | -- | -- |
-| Compact | ✓ | ✓ | ✓ | ✓ |
-| General JSON | ✓ | ✓ | ✓ | ✓ |
-| Flattened JSON | ✓ | ✓ | ✓ | ✓ |
-
-| JWS Algorithms | Supported ||
-| -- | -- | -- |
-| RSASSA-PKCS1-v1_5 | ✓ | RS256, RS384, RS512 |
-| RSASSA-PSS | ✓ | PS256, PS384, PS512 |
-| ECDSA | ✓ | ES256, ES256K, ES384, ES512 |
-| Edwards-curve DSA | ✓ | EdDSA |
-| HMAC with SHA-2 | ✓ | HS256, HS384, HS512 |
-
-| JWE Key Management Algorithms | Supported ||
-| -- | -- | -- |
-| AES | ✓ | A128KW, A192KW, A256KW |
-| AES GCM | ✓ | A128GCMKW, A192GCMKW, A256GCMKW |
-| Direct Key Agreement | ✓ | dir |
-| RSAES OAEP | ✓ | RSA-OAEP, RSA-OAEP-256 |
-| RSAES-PKCS1-v1_5 | ✓ | RSA1_5 |
-| PBES2 | ✓ | PBES2-HS256+A128KW, PBES2-HS384+A192KW, PBES2-HS512+A256KW |
-| ECDH-ES (for all EC keys) | ✓ | ECDH-ES, ECDH-ES+A128KW, ECDH-ES+A192KW, ECDH-ES+A256KW |
-| ECDH-ES (for OKP X25519) | ✓ via [plugin][plugin-x25519] | ECDH-ES, ECDH-ES+A128KW, ECDH-ES+A192KW, ECDH-ES+A256KW |
-| ECDH-ES (for OKP X448) | ✕ ||
-| (X)ChaCha | ✓ via [plugin][plugin-chacha] | C20PKW, X20CPKW, ECDH-ES+C20PKW, ECDH-ES+XC20PKW |
-
-| JWE Content Encryption Algorithms | Supported ||
-| -- | -- | -- |
-| AES GCM | ✓ | A128GCM, A192GCM, A256GCM |
-| AES_CBC_HMAC_SHA2 | ✓ | A128CBC-HS256, A192CBC-HS384, A256CBC-HS512 |
-| (X)ChaCha | ✓ via [plugin][plugin-chacha] | C20P, X20CP |
-
-| JWT profile validation | Supported | profile option value |
-| -- | -- | -- |
-| ID Token - [OpenID Connect Core 1.0][spec-oidc-id_token] | ✓ | `id_token` |
-| JWT Access Tokens [JWT Profile for OAuth 2.0 Access Tokens][draft-ietf-oauth-access-token-jwt] | ✓ | `at+JWT` |
-| Logout Token - [OpenID Connect Back-Channel Logout 1.0][spec-oidc-logout_token] | ✓ | `logout_token` |
-| JARM - [JWT Secured Authorization Response Mode for OAuth 2.0][draft-jarm] | ◯ ||
-
-Notes
-- RSA-OAEP-256 JWE algorithm is only supported when Node.js >= 12.9.0 runtime is detected
-- Importing X.509 certificates and handling `x5c` is only supported when Node.js >= 12.0.0 runtime is detected
-- OKP keys are only supported when Node.js >= 12.0.0 runtime is detected
-- See [#electron-support](#electron-support) for electron exceptions
-
----
-
-Pending Node.js Support 🤞:
-- ECDH-ES with X25519 and X448 - see [nodejs/node#26626](https://github.com/nodejs/node/pull/26626)
-
-Won't implement:
-- ✕ JWS embedded key / referenced verification
- - one can decode the header and pass the (`x5c`, `jwk`) to `JWK.asKey` and validate with that
- key, similarly the application can handle fetching and then instantiating the referenced `x5u`
- or `jku` in its own code. This way you opt-in to these behaviours.
-- ✕ "none" alg support
- - no crypto, no use
-
-
-
Have a question about using `jose`? - [ask][ask].
@@ -128,15 +52,6 @@ If you or your business use `jose`, please consider becoming a [sponsor][support
- [JWS (JSON Web Signature)][documentation-jws]
- [JWE (JSON Web Encryption)][documentation-jwe]
-## Plugins
-
-There are two plugin extensions with functionality which is either not available in Node.js `crypto`
-module yet and therefore needs a crypto polyfill (libsodium), or are not IETF WG standards/drafts
-"worthy" of landing in the core library.
-
-- [jose-chacha][plugin-chacha] adds aead_chacha20_poly1305 and aead_xchacha20_poly1305 based algorithms (individual draft)
-- [jose-x25519-ecdh][plugin-x25519] adds OKP X25519 curve keys ECDH-ES support (missing Node.js `crypto` support)
-
## Usage
For the best performance Node.js version **>=12.0.0** is recommended, but **^10.13.0** lts/dubnium
@@ -356,17 +271,66 @@ jose.JWE.decrypt(
)
```
-#### Electron Support
+## Detailed Support Matrix
+
+| JWK Key Types | Supported | `kty` value | `crv` values |
+| -- | -- | -- | -- |
+| RSA | ✓ | RSA ||
+| Elliptic Curve | ✓ | EC | P-256, secp256k1[1], P-384, P-521 |
+| Octet Key Pair | ✓ | OKP | Ed25519, Ed448[1], X25519[1], X448[1] |
+| Octet sequence | ✓ | oct ||
+
+| Serialization | JWS Sign | JWS Verify | JWE Encrypt | JWE Decrypt |
+| -- | -- | -- | -- | -- |
+| Compact | ✓ | ✓ | ✓ | ✓ |
+| General JSON | ✓ | ✓ | ✓ | ✓ |
+| Flattened JSON | ✓ | ✓ | ✓ | ✓ |
+
+| JWS Algorithms | Supported ||
+| -- | -- | -- |
+| RSASSA-PKCS1-v1_5 | ✓ | RS256, RS384, RS512 |
+| RSASSA-PSS | ✓ | PS256, PS384, PS512 |
+| ECDSA | ✓ | ES256, ES256K[1], ES384, ES512 |
+| Edwards-curve DSA | ✓ | EdDSA |
+| HMAC with SHA-2 | ✓ | HS256, HS384, HS512 |
+| Unsecured JWS | ✓ | none[2] |
-Electron >=6.0.0 runtime is supported to the extent of the crypto engine BoringSSL feature parity
-with standard Node.js OpenSSL. The following is disabled in Electron runtime because of its lack of
-[support](https://github.com/panva/jose/blob/master/test/electron/electron.test.js).
+| JWE Key Management Algorithms | Supported ||
+| -- | -- | -- |
+| AES | ✓ | A128KW[1], A192KW[1], A256KW[1] |
+| AES GCM | ✓ | A128GCMKW, A192GCMKW, A256GCMKW |
+| Direct Key Agreement | ✓ | dir |
+| RSAES OAEP | ✓ | RSA-OAEP, RSA-OAEP-256[3] |
+| RSAES-PKCS1-v1_5 | ✓ | RSA1_5 |
+| PBES2 | ✓ | PBES2-HS256+A128KW[1], PBES2-HS384+A192KW[1], PBES2-HS512+A256KW[1] |
+| ECDH-ES (for all EC keys) | ✓ | ECDH-ES, ECDH-ES+A128KW[1], ECDH-ES+A192KW[1], ECDH-ES+A256KW[1] |
+| ECDH-ES (for OKP X25519) | ✓ via [plugin][plugin-x25519] | ECDH-ES, ECDH-ES+A128KW, ECDH-ES+A192KW, ECDH-ES+A256KW |
+| ECDH-ES (for OKP X448) | ✕ ||
+| (X)ChaCha | ✓ via [plugin][plugin-chacha] | C20PKW, XC20PKW, ECDH-ES+C20PKW, ECDH-ES+XC20PKW |
+
+| JWE Content Encryption Algorithms | Supported ||
+| -- | -- | -- |
+| AES GCM | ✓ | A128GCM, A192GCM, A256GCM |
+| AES_CBC_HMAC_SHA2 | ✓ | A128CBC-HS256, A192CBC-HS384, A256CBC-HS512 |
+| (X)ChaCha | ✓ via [plugin][plugin-chacha] | C20P, X20CP |
+
+| JWT profile validation | Supported | profile option value |
+| -- | -- | -- |
+| ID Token - [OpenID Connect Core 1.0][spec-oidc-id_token] | ✓ | `id_token` |
+| JWT Access Tokens [JWT Profile for OAuth 2.0 Access Tokens][draft-ietf-oauth-access-token-jwt] | ✓ | `at+JWT` |
+| Logout Token - [OpenID Connect Back-Channel Logout 1.0][spec-oidc-logout_token] | ✓ | `logout_token` |
+| JARM - [JWT Secured Authorization Response Mode for OAuth 2.0][draft-jarm] | ◯ ||
+
+Legend:
+- **✓** Implemented
+- **✕** Missing node crypto support / won't implement
+- **◯** TBD
-- JWE `A128KW`, `A192KW` and `A256KW` algorithms are not available, this also means that other JWAs
- depending on those are not working, those are `ECDH-ES+A128KW`, `ECDH-ES+A192KW`,
- `ECDH-ES+A256KW`, `PBES2-HS256+A128KW`, `PBES2-HS384+A192KW`, `PBES2-HS512+A256KW`)
-- OKP curves `Ed448`, `X25519` and `X448` are not supported
-- EC curve `secp256k1` is not supported
+1 Not supported in Electron due to Electron's use of BoringSSL
+2 Unsecured JWS is [supported][documentation-none] for the JWS and JWT sign and verify
+operations but it is an entirely opt-in behaviour, downgrade attacks are prevented by the required
+use of a special `JWK.Key` instance that cannot be instantiated through the key import API.
+3 RSA-OAEP-256 is only supported when Node.js >= 12.9.0 runtime is detected
## FAQ
@@ -418,12 +382,13 @@ in terms of performance and API (not having well defined errors).
[ask]: https://github.com/panva/jose/issues/new?labels=question&template=question.md&title=question%3A+
[bug]: https://github.com/panva/jose/issues/new?labels=bug&template=bug-report.md&title=bug%3A+
-[documentation-jwe]: https://github.com/panva/jose/blob/master/docs/README.md#jwe-json-web-encryption
-[documentation-jwk]: https://github.com/panva/jose/blob/master/docs/README.md#jwk-json-web-key
-[documentation-jwks]: https://github.com/panva/jose/blob/master/docs/README.md#jwks-json-web-key-set
-[documentation-jws]: https://github.com/panva/jose/blob/master/docs/README.md#jws-json-web-signature
-[documentation-jwt]: https://github.com/panva/jose/blob/master/docs/README.md#jwt-json-web-token
-[documentation]: https://github.com/panva/jose/blob/master/docs/README.md
+[documentation-jwe]: /docs/README.md#jwe-json-web-encryption
+[documentation-jwk]: /docs/README.md#jwk-json-web-key
+[documentation-jwks]: /docs/README.md#jwks-json-web-key-set
+[documentation-jws]: /docs/README.md#jws-json-web-signature
+[documentation-jwt]: /docs/README.md#jwt-json-web-token
+[documentation-none]: /docs/README.md#jwknone
+[documentation]: /docs/README.md
[node-jose]: https://github.com/cisco/node-jose
[security-vulnerability]: https://github.com/panva/jose/issues/new?template=security-vulnerability.md
[spec-b64]: https://tools.ietf.org/html/rfc7797
diff --git a/docs/README.md b/docs/README.md
index b16698c5ad..7838ecc72b 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -51,6 +51,7 @@ If you or your business use `jose`, please consider becoming a [sponsor][support
- [JWK.generate(kty[, crvOrSize[, options[, private]]]) generating new keys](#jwkgeneratekty-crvorsize-options-private-generating-new-keys)
- [JWK.generateSync(kty[, crvOrSize[, options[, private]]])](#jwkgeneratesynckty-crvorsize-options-private)
- [JWK.isKey(object)](#jwkiskeyobject)
+- [JWK.None](#jwknone)
All sign and encrypt operations require `` or `JWK.asKey()` compatible input.
@@ -574,6 +575,54 @@ Returns 'true' if the value is an instance of ``.
---
+#### `JWK.None`
+
+`JWK.None` is a special key object that can be used with JWS/JWT sign and verify whenever you want
+to opt-in for the `none` Unsecured JWS algorithm. Using this key fulfills the requirements given by
+the [specification](https://tools.ietf.org/html/rfc7518#section-3.6), namely:
+
+- Implementations MUST NOT accept Unsecured JWSs by default.
+- Implementations that support Unsecured JWSs MUST NOT accept such objects as valid unless the
+application specifies that it is acceptable for a specific object to not be integrity protected.
+
+```js
+const { JWK: { None, generateSync }, JWT, JWS } = require('jose')
+const anActualKey = generateSync('RSA')
+
+const signedJWT = JWT.sign({ sub: 'John Doe' }, anActualKey)
+JWT.verify(signedJWT, None)
+// Thrown:
+// JWKKeySupport: the key does not support PS256 verify algorithm
+// name: 'JWKKeySupport',
+// code: 'ERR_JWK_KEY_SUPPORT'
+
+const unsecuredJWT = JWT.sign({ sub: 'John Doe' }, None)
+// eyJhbGciOiJub25lIn0.eyJzdWIiOiJKb2huIERvZSIsImlhdCI6MTU3OTc5NDM2Mn0.
+
+JWT.verify(unsecuredJWT, anActualKey)
+// Thrown:
+// JWKKeySupport: the key does not support none verify algorithm
+// name: 'JWKKeySupport',
+// code: 'ERR_JWK_KEY_SUPPORT'
+
+JWT.verify(unsecuredJWT, None)
+// { sub: 'John Doe', iat: 1579794362 }
+
+const unsecuredJWS = JWS.sign('foobar', None)
+// eyJhbGciOiJub25lIn0.Zm9vYmFy.
+
+JWS.verify(unsecuredJWS, anActualKey)
+// Thrown:
+// JWKKeySupport: the key does not support none verify algorithm
+// name: 'JWKKeySupport',
+// code: 'ERR_JWK_KEY_SUPPORT'
+
+JWS.verify(unsecuredJWS, None)
+// foobar
+```
+
+---
+
## JWKS (JSON Web Key Set)
diff --git a/lib/jwa/index.js b/lib/jwa/index.js
index 0505a0adee..4afd402ccd 100644
--- a/lib/jwa/index.js
+++ b/lib/jwa/index.js
@@ -9,6 +9,7 @@ require('./ecdsa')(JWA, JWK)
require('./eddsa')(JWA, JWK)
require('./rsassa_pss')(JWA, JWK)
require('./rsassa')(JWA, JWK)
+require('./none')(JWA)
// encrypt, decrypt
require('./aes_cbc_hmac_sha2')(JWA, JWK)
diff --git a/lib/jwa/none.js b/lib/jwa/none.js
new file mode 100644
index 0000000000..e9de26a837
--- /dev/null
+++ b/lib/jwa/none.js
@@ -0,0 +1,14 @@
+const { strict: assert } = require('assert')
+
+const sign = (key, payload) => Buffer.from('')
+const verify = (key, payload, signature) => !signature.length
+
+module.exports = (JWA, JWK) => {
+ const jwaAlg = 'none'
+
+ assert(!JWA.sign.has(jwaAlg), `sign alg ${jwaAlg} already registered`)
+ assert(!JWA.verify.has(jwaAlg), `verify alg ${jwaAlg} already registered`)
+
+ JWA.sign.set(jwaAlg, sign)
+ JWA.verify.set(jwaAlg, verify)
+}
diff --git a/lib/jwk/index.js b/lib/jwk/index.js
index 580908dc78..e42b738332 100644
--- a/lib/jwk/index.js
+++ b/lib/jwk/index.js
@@ -1,11 +1,14 @@
const Key = require('./key/base')
+const None = require('./key/none')
const importKey = require('./import')
-const { generate, generateSync } = require('./generate')
+const generate = require('./generate')
-module.exports.asKey = importKey
-module.exports.generate = generate
-module.exports.generateSync = generateSync
-module.exports.isKey = input => input instanceof Key
+module.exports = {
+ ...generate,
+ asKey: importKey,
+ isKey: input => input instanceof Key,
+ None
+}
/* deprecated */
Object.defineProperty(module.exports, 'importKey', {
diff --git a/lib/jwk/key/none.js b/lib/jwk/key/none.js
new file mode 100644
index 0000000000..257b4e3c2f
--- /dev/null
+++ b/lib/jwk/key/none.js
@@ -0,0 +1,33 @@
+const { inspect } = require('util')
+
+const Key = require('./base')
+
+class NoneKey extends Key {
+ constructor () {
+ super({ type: 'unsecured' }, { alg: 'none' })
+ Object.defineProperties(this, {
+ kid: { value: undefined },
+ thumbprint: { value: undefined },
+ toJWK: { value: undefined },
+ toPEM: { value: undefined }
+ })
+ }
+
+ /* c8 ignore next 3 */
+ [inspect.custom] () {
+ return 'None {}'
+ }
+
+ algorithms (operation) {
+ switch (operation) {
+ case 'sign':
+ case 'verify':
+ case undefined:
+ return new Set(['none'])
+ default:
+ return new Set()
+ }
+ }
+}
+
+module.exports = new NoneKey({ type: 'unsecured' }, { alg: 'none' })
diff --git a/lib/jwks/keystore.js b/lib/jwks/keystore.js
index 4ab1b5b6fe..39110a82ad 100644
--- a/lib/jwks/keystore.js
+++ b/lib/jwks/keystore.js
@@ -2,9 +2,8 @@ const { deprecate, inspect } = require('util')
const isObject = require('../help/is_object')
const { generate, generateSync } = require('../jwk/generate')
-const Key = require('../jwk/key/base')
-const importKey = require('../jwk/import')
const { USES_MAPPING } = require('../help/consts')
+const { None, isKey, asKey: importKey } = require('../jwk')
const keyscore = (key, { alg, use, ops }) => {
let score = 0
@@ -45,8 +44,8 @@ class KeyStore {
return acc
}, [])
}
- if (keys.some(k => !(k instanceof Key))) {
- throw new TypeError('all keys must be an instances of a key instantiated by JWK.asKey')
+ if (keys.some(k => !isKey(k) || k === None)) {
+ throw new TypeError('all keys must be instances of a key instantiated by JWK.asKey')
}
i(this).keys = new Set(keys)
@@ -113,7 +112,7 @@ class KeyStore {
}
add (key) {
- if (!(key instanceof Key)) {
+ if (!isKey(key) || key === None) {
throw new TypeError('key must be an instance of a key instantiated by JWK.asKey')
}
@@ -121,7 +120,7 @@ class KeyStore {
}
remove (key) {
- if (!(key instanceof Key)) {
+ if (!isKey(key)) {
throw new TypeError('key must be an instance of a key instantiated by JWK.asKey')
}
diff --git a/test/jwks/keystore.test.js b/test/jwks/keystore.test.js
index 3654bc5ea0..888e62d89b 100644
--- a/test/jwks/keystore.test.js
+++ b/test/jwks/keystore.test.js
@@ -33,7 +33,7 @@ test('constructor', t => {
test('constructor only accepts Key instances created through JWK.asKey', t => {
t.throws(() => {
new KeyStore({}) // eslint-disable-line no-new
- }, { instanceOf: TypeError, message: 'all keys must be an instances of a key instantiated by JWK.asKey' })
+ }, { instanceOf: TypeError, message: 'all keys must be instances of a key instantiated by JWK.asKey' })
})
test('.generate()', async t => {
diff --git a/test/jws/unsecured.test.js b/test/jws/unsecured.test.js
new file mode 100644
index 0000000000..5a18d1ee1b
--- /dev/null
+++ b/test/jws/unsecured.test.js
@@ -0,0 +1,111 @@
+const test = require('ava')
+
+const { errors, JWK: { generateSync, None, isKey }, JWS } = require('../..')
+
+const properKey = generateSync('oct')
+
+test('JWS.None is an instance of a key but not really', t => {
+ t.true(isKey(None))
+ t.is(None.alg, 'none')
+ t.is(None.type, 'unsecured')
+ t.true(None.algorithms() instanceof Set)
+ t.deepEqual([...None.algorithms()], ['none'])
+ t.deepEqual([...None.algorithms('sign')], ['none'])
+ t.deepEqual([...None.algorithms('verify')], ['none'])
+ t.deepEqual([...None.algorithms('encrypt')], [])
+ t.deepEqual([...None.algorithms('foobar')], [])
+ t.is(None.thumbprint, undefined)
+ t.is(None.kid, undefined)
+})
+
+test('JWS.None "signs"', t => {
+ const unsignedJWS = JWS.sign('foo', None)
+ t.deepEqual(
+ JWS.verify(unsignedJWS, None, { complete: true }),
+ {
+ key: None,
+ payload: 'foo',
+ protected: {
+ alg: 'none'
+ }
+ }
+ )
+})
+
+test('JWS.None "signs" flattened', t => {
+ const unsignedJWS = JWS.sign.flattened('foo', None)
+ t.deepEqual(
+ unsignedJWS,
+ {
+ payload: 'Zm9v',
+ protected: 'eyJhbGciOiJub25lIn0',
+ signature: ''
+ }
+ )
+ t.deepEqual(
+ JWS.verify(unsignedJWS, None, { complete: true }),
+ {
+ key: None,
+ payload: 'foo',
+ protected: {
+ alg: 'none'
+ }
+ }
+ )
+})
+
+test('JWS.None "signs" general', t => {
+ const sign = new JWS.Sign('foo')
+ sign.recipient(None)
+ sign.recipient(None)
+ const unsignedJWS = sign.sign('general')
+
+ t.deepEqual(
+ unsignedJWS,
+ {
+ payload: 'Zm9v',
+ signatures: [
+ {
+ protected: 'eyJhbGciOiJub25lIn0',
+ signature: ''
+ },
+ {
+ protected: 'eyJhbGciOiJub25lIn0',
+ signature: ''
+ }
+ ]
+ }
+ )
+ t.deepEqual(
+ JWS.verify(unsignedJWS, None, { complete: true }),
+ {
+ key: None,
+ payload: 'foo',
+ protected: {
+ alg: 'none'
+ }
+ }
+ )
+})
+
+test('JWS.None fails to verify real tokens', t => {
+ const signedToken = JWS.sign('foo', properKey)
+ t.throws(() => {
+ JWS.verify(signedToken, None)
+ }, {
+ instanceOf: errors.JWKKeySupport,
+ code: 'ERR_JWK_KEY_SUPPORT',
+ message: 'the key does not support HS256 verify algorithm'
+ })
+})
+
+test('JWS.None fails to verify None signed tokens with a signature', t => {
+ const unsignedJWS = JWS.sign('foo', None)
+ t.throws(() => {
+ JWS.verify(`${unsignedJWS}fooba`, None)
+ }, {
+ instanceOf: errors.JWSVerificationFailed,
+ code: 'ERR_JWS_VERIFICATION_FAILED',
+ message: 'signature verification failed'
+ })
+})
diff --git a/test/jwt/unsecured.test.js b/test/jwt/unsecured.test.js
new file mode 100644
index 0000000000..71d529ba1f
--- /dev/null
+++ b/test/jwt/unsecured.test.js
@@ -0,0 +1,46 @@
+const test = require('ava')
+
+const { errors, JWK: { generateSync, None }, JWT } = require('../..')
+
+const properKey = generateSync('oct')
+
+const PAYLOAD = { sub: 'foobar', iat: 0 }
+
+test('JWS.None "signs" (JWT)', t => {
+ const unsignedJWS = JWT.sign(PAYLOAD, None, { iat: false })
+ t.deepEqual(
+ JWT.verify(unsignedJWS, None, { complete: true }),
+ {
+ key: None,
+ payload: {
+ ...PAYLOAD
+ },
+ header: {
+ alg: 'none'
+ },
+ signature: ''
+ }
+ )
+})
+
+test('JWS.None fails to verify real tokens (JWT)', t => {
+ const signedToken = JWT.sign(PAYLOAD, properKey)
+ t.throws(() => {
+ JWT.verify(signedToken, None)
+ }, {
+ instanceOf: errors.JWKKeySupport,
+ code: 'ERR_JWK_KEY_SUPPORT',
+ message: 'the key does not support HS256 verify algorithm'
+ })
+})
+
+test('JWS.None fails to verify None signed tokens with a signature (JWT)', t => {
+ const unsignedJWS = JWT.sign(PAYLOAD, None)
+ t.throws(() => {
+ JWT.verify(`${unsignedJWS}fooba`, None)
+ }, {
+ instanceOf: errors.JWSVerificationFailed,
+ code: 'ERR_JWS_VERIFICATION_FAILED',
+ message: 'signature verification failed'
+ })
+})
diff --git a/types/index.d.ts b/types/index.d.ts
index 0320335cab..df330cfe07 100644
--- a/types/index.d.ts
+++ b/types/index.d.ts
@@ -27,6 +27,9 @@ export type JWTProfiles = 'id_token' | 'at+JWT' | 'logout_token';
export type KeyInput = PrivateKeyInput | PublicKeyInput | string | Buffer;
export type ProduceKeyInput = JWK.Key | KeyObject | KeyInput | JWKOctKey | JWKRSAKey | JWKECKey | JWKOKPKey;
export type ConsumeKeyInput = ProduceKeyInput | JWKS.KeyStore;
+export type NoneKey = JWK.NoneKey;
+export type ProduceKeyInputWithNone = ProduceKeyInput | NoneKey;
+export type ConsumeKeyInputWithNone = ConsumeKeyInput | NoneKey;
export interface JWKOctKey extends BasicParameters { // no x5c
kty: 'oct';
@@ -148,6 +151,14 @@ export namespace JWK {
toJWK(private?: boolean): JWKOctKey;
}
+ interface NoneKey {
+ type: 'unsecured';
+ alg: 'none';
+ algorithms(operation?: keyOperation): Set;
+ }
+
+ const None: NoneKey;
+
function isKey(object: any): boolean;
function asKey(key: KeyObject | KeyInput, parameters?: KeyParameters): RSAKey | ECKey | OKPKey | OctKey;
@@ -239,17 +250,17 @@ export namespace JWS {
class Sign {
constructor(payload: string | Buffer | object);
- recipient(key: ProduceKeyInput, protected?: object, header?: object): void;
+ recipient(key: ProduceKeyInputWithNone, protected?: object, header?: object): void;
sign(serialization: 'compact'): string;
sign(serialization: 'flattened'): FlattenedJWS;
sign(serialization: 'general'): GeneralJWS;
}
- function sign(payload: string | Buffer | object, key: ProduceKeyInput, protected?: object): string;
+ function sign(payload: string | Buffer | object, key: ProduceKeyInputWithNone, protected?: object): string;
namespace sign {
- function flattened(payload: string | Buffer | object, key: ProduceKeyInput, protected?: object, header?: object): FlattenedJWS;
- function general(payload: string | Buffer | object, key: ProduceKeyInput, protected?: object, header?: object): GeneralJWS;
+ function flattened(payload: string | Buffer | object, key: ProduceKeyInputWithNone, protected?: object, header?: object): FlattenedJWS;
+ function general(payload: string | Buffer | object, key: ProduceKeyInputWithNone, protected?: object, header?: object): GeneralJWS;
}
interface VerifyOptions {
@@ -260,17 +271,19 @@ export namespace JWS {
algorithms?: string[];
}
- interface completeVerification {
+ interface completeVerification {
payload: T;
- key: JWK.Key;
+ key: T2;
protected?: object;
header?: object;
}
- function verify(jws: string | FlattenedJWS | GeneralJWS, key: ConsumeKeyInput, options?: VerifyOptions): string | object;
- function verify(jws: string | FlattenedJWS | GeneralJWS, key: ConsumeKeyInput, options?: VerifyOptions): Buffer;
- function verify(jws: string | FlattenedJWS | GeneralJWS, key: ConsumeKeyInput, options?: VerifyOptions): completeVerification;
- function verify(jws: string | FlattenedJWS | GeneralJWS, key: ConsumeKeyInput, options?: VerifyOptions): completeVerification;
+ function verify(jws: string | FlattenedJWS | GeneralJWS, key: ConsumeKeyInputWithNone, options?: VerifyOptions): string | object;
+ function verify(jws: string | FlattenedJWS | GeneralJWS, key: ConsumeKeyInputWithNone, options?: VerifyOptions): Buffer;
+ function verify(jws: string | FlattenedJWS | GeneralJWS, key: ConsumeKeyInput, options?: VerifyOptions): completeVerification;
+ function verify(jws: string | FlattenedJWS | GeneralJWS, key: ConsumeKeyInput, options?: VerifyOptions): completeVerification;
+ function verify(jws: string | FlattenedJWS | GeneralJWS, key: NoneKey, options?: VerifyOptions): completeVerification;
+ function verify(jws: string | FlattenedJWS | GeneralJWS, key: NoneKey, options?: VerifyOptions): completeVerification;
}
export namespace JWE {
@@ -364,8 +377,9 @@ export namespace JWT {
profile?: JWTProfiles;
}
- function verify(jwt: string, key: ConsumeKeyInput, options?: VerifyOptions): object;
+ function verify(jwt: string, key: ConsumeKeyInputWithNone, options?: VerifyOptions): object;
function verify(jwt: string, key: ConsumeKeyInput, options?: VerifyOptions): completeResult;
+ function verify(jwt: string, key: NoneKey, options?: VerifyOptions): completeResult;
interface SignOptions {
iat?: boolean;
@@ -382,7 +396,7 @@ export namespace JWT {
now?: Date;
}
- function sign(payload: object, key: ProduceKeyInput, options?: SignOptions): string;
+ function sign(payload: object, key: ProduceKeyInputWithNone, options?: SignOptions): string;
interface VerifyProfileOptions {
issuer: string;
@@ -391,18 +405,21 @@ export namespace JWT {
}
namespace IdToken {
- function verify(jwt: string, key: ConsumeKeyInput, options: VerifyOptions & VerifyProfileOptions<'id_token'>): object;
+ function verify(jwt: string, key: ConsumeKeyInputWithNone, options: VerifyOptions & VerifyProfileOptions<'id_token'>): object;
function verify(jwt: string, key: ConsumeKeyInput, options: VerifyOptions & VerifyProfileOptions<'id_token'>): completeResult;
+ function verify(jwt: string, key: NoneKey, options: VerifyOptions & VerifyProfileOptions<'id_token'>): completeResult;
}
namespace LogoutToken {
- function verify(jwt: string, key: ConsumeKeyInput, options: VerifyOptions & VerifyProfileOptions<'logout_token'>): object;
+ function verify(jwt: string, key: ConsumeKeyInputWithNone, options: VerifyOptions & VerifyProfileOptions<'logout_token'>): object;
function verify(jwt: string, key: ConsumeKeyInput, options: VerifyOptions & VerifyProfileOptions<'logout_token'>): completeResult;
+ function verify(jwt: string, key: NoneKey, options: VerifyOptions & VerifyProfileOptions<'logout_token'>): completeResult;
}
namespace AccessToken {
- function verify(jwt: string, key: ConsumeKeyInput, options: VerifyOptions & VerifyProfileOptions<'at+JWT'>): object;
+ function verify(jwt: string, key: ConsumeKeyInputWithNone, options: VerifyOptions & VerifyProfileOptions<'at+JWT'>): object;
function verify(jwt: string, key: ConsumeKeyInput, options: VerifyOptions & VerifyProfileOptions<'at+JWT'>): completeResult;
+ function verify(jwt: string, key: NoneKey, options: VerifyOptions & VerifyProfileOptions<'at+JWT'>): completeResult;
}
}