diff --git a/CHANGELOG.md b/CHANGELOG.md
index 549e5d1f90..a337044fad 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,20 @@
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
+# [1.22.0](https://github.com/panva/jose/compare/v1.21.1...v1.22.0) (2020-01-29)
+
+
+### Features
+
+* keystore filtering by JWK Key thumbprint ([a9f6f71](https://github.com/panva/jose/commit/a9f6f7135005d6231d6f42d95c02414139a89d17))
+
+
+### Performance Improvements
+
+* base64url decode, JWT.verify, JWK.Key instance re-use ([470b4c7](https://github.com/panva/jose/commit/470b4c73154e1fcf8b92726d521940e5e11c9d94))
+
+
+
## [1.21.1](https://github.com/panva/jose/compare/v1.21.0...v1.21.1) (2020-01-25)
diff --git a/README.md b/README.md
index ce97129195..9e89a14809 100644
--- a/README.md
+++ b/README.md
@@ -329,7 +329,7 @@ Legend:
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
+use of a special `JWK.Key`-like object 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
@@ -399,7 +399,7 @@ in terms of performance and API (not having well defined errors).
[spec-jws]: https://tools.ietf.org/html/rfc7515
[spec-jwt]: https://tools.ietf.org/html/rfc7519
[spec-okp]: https://tools.ietf.org/html/rfc8037
-[draft-secp256k1]: https://tools.ietf.org/html/draft-ietf-cose-webauthn-algorithms-03
+[draft-secp256k1]: https://tools.ietf.org/html/draft-ietf-cose-webauthn-algorithms-04
[draft-ietf-oauth-access-token-jwt]: https://tools.ietf.org/html/draft-ietf-oauth-access-token-jwt
[draft-jarm]: https://openid.net/specs/openid-financial-api-jarm.html
[spec-thumbprint]: https://tools.ietf.org/html/rfc7638
diff --git a/docs/README.md b/docs/README.md
index 7838ecc72b..a45bae495b 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -680,6 +680,7 @@ specified by the parameters are first.
- `crv`: `` Key Curve to filter for. (for EC and OKP keys)
- `alg`: `` Key supported algorithm to filter for.
- `kid`: `` Key ID to filter for.
+ - `thumbprint`: `` JWK Key thumbprint to filter for.
- `use`: `` Filter keys with the specified use defined. Keys missing "use" parameter will
be matched but rank lower then ones with an exact match.
- `key_ops`: `string[]` Filter keys with specified key_ops defined (if key_ops is defined on the
@@ -701,6 +702,7 @@ parameters is returned.
- `crv`: `` Key Curve to filter for. (for EC and OKP keys)
- `alg`: `` Key supported algorithm to filter for.
- `kid`: `` Key ID to filter for.
+ - `thumbprint`: `` JWK Key thumbprint to filter for.
- `use`: `` Filter keys with the specified use defined. Keys missing "use" parameter will
be matched but rank lower then ones with an exact match.
- `key_ops`: `string[]` Filter keys with specified key_ops defined (if key_ops is defined on the
diff --git a/lib/help/base64url.js b/lib/help/base64url.js
index c50759978c..29467146cd 100644
--- a/lib/help/base64url.js
+++ b/lib/help/base64url.js
@@ -6,10 +6,6 @@ const fromBase64 = (base64) => {
return base64.replace(/=/g, '').replace(/\+/g, '-').replace(/\//g, '_')
}
-const toBase64 = (base64url) => {
- return base64url.replace(/-/g, '+').replace(/_/g, '/')
-}
-
const encode = (input, encoding = 'utf8') => {
return fromBase64(Buffer.from(input, encoding).toString('base64'))
}
@@ -23,7 +19,7 @@ const decodeToBuffer = (input) => {
throw new JOSEInvalidEncoding('input is not a valid base64url encoded string')
}
- return Buffer.from(toBase64(input), 'base64')
+ return Buffer.from(input, 'base64')
}
const decode = (input, encoding = 'utf8') => {
diff --git a/lib/jwa/aes_cbc_hmac_sha2.js b/lib/jwa/aes_cbc_hmac_sha2.js
index 4a4e9c2ca4..29a2fbeac1 100644
--- a/lib/jwa/aes_cbc_hmac_sha2.js
+++ b/lib/jwa/aes_cbc_hmac_sha2.js
@@ -1,4 +1,3 @@
-const { strict: assert } = require('assert')
const { createCipheriv, createDecipheriv, getCiphers } = require('crypto')
const uint64be = require('../help/uint64be')
@@ -61,10 +60,6 @@ const decrypt = (size, sign, { [KEYOBJECT]: keyObject }, ciphertext, { iv, tag =
module.exports = (JWA, JWK) => {
['A128CBC-HS256', 'A192CBC-HS384', 'A256CBC-HS512'].forEach((jwaAlg) => {
const size = parseInt(jwaAlg.substr(1, 3), 10)
-
- assert(!JWA.encrypt.has(jwaAlg), `encrypt alg ${jwaAlg} already registered`)
- assert(!JWA.decrypt.has(jwaAlg), `decrypt alg ${jwaAlg} already registered`)
-
const sign = JWA.sign.get(`HS${size * 2}`)
if (getCiphers().includes(`aes-${size}-cbc`)) {
JWA.encrypt.set(jwaAlg, encrypt.bind(undefined, size, sign))
diff --git a/lib/jwa/aes_gcm.js b/lib/jwa/aes_gcm.js
index 0fa4f5dd55..1acc5ca95f 100644
--- a/lib/jwa/aes_gcm.js
+++ b/lib/jwa/aes_gcm.js
@@ -1,4 +1,3 @@
-const { strict: assert } = require('assert')
const { createCipheriv, createDecipheriv, getCiphers } = require('crypto')
const { KEYOBJECT } = require('../help/consts')
@@ -47,10 +46,6 @@ const decrypt = (size, { [KEYOBJECT]: keyObject }, ciphertext, { iv, tag = Buffe
module.exports = (JWA, JWK) => {
['A128GCM', 'A192GCM', 'A256GCM'].forEach((jwaAlg) => {
const size = parseInt(jwaAlg.substr(1, 3), 10)
-
- assert(!JWA.encrypt.has(jwaAlg), `encrypt alg ${jwaAlg} already registered`)
- assert(!JWA.decrypt.has(jwaAlg), `decrypt alg ${jwaAlg} already registered`)
-
if (getCiphers().includes(`aes-${size}-gcm`)) {
JWA.encrypt.set(jwaAlg, encrypt.bind(undefined, size))
JWA.decrypt.set(jwaAlg, decrypt.bind(undefined, size))
diff --git a/lib/jwa/aes_gcm_kw.js b/lib/jwa/aes_gcm_kw.js
index 6375ea8e61..3a57bb9ca4 100644
--- a/lib/jwa/aes_gcm_kw.js
+++ b/lib/jwa/aes_gcm_kw.js
@@ -1,13 +1,8 @@
-const { strict: assert } = require('assert')
-
const generateIV = require('../help/generate_iv')
const base64url = require('../help/base64url')
module.exports = (JWA, JWK) => {
['A128GCMKW', 'A192GCMKW', 'A256GCMKW'].forEach((jwaAlg) => {
- assert(!JWA.keyManagementEncrypt.has(jwaAlg), `keyManagementEncrypt alg ${jwaAlg} already registered`)
- assert(!JWA.keyManagementDecrypt.has(jwaAlg), `keyManagementDecrypt alg ${jwaAlg} already registered`)
-
const encAlg = jwaAlg.substr(0, 7)
const size = parseInt(jwaAlg.substr(1, 3), 10)
const encrypt = JWA.encrypt.get(encAlg)
diff --git a/lib/jwa/aes_kw.js b/lib/jwa/aes_kw.js
index 180d225bda..145f0a275e 100644
--- a/lib/jwa/aes_kw.js
+++ b/lib/jwa/aes_kw.js
@@ -1,4 +1,3 @@
-const { strict: assert } = require('assert')
const { createCipheriv, createDecipheriv, getCiphers } = require('crypto')
const uint64be = require('../help/uint64be')
@@ -91,10 +90,6 @@ const unwrapKey = (size, { [KEYOBJECT]: keyObject }, payload) => {
module.exports = (JWA, JWK) => {
['A128KW', 'A192KW', 'A256KW'].forEach((jwaAlg) => {
const size = parseInt(jwaAlg.substr(1, 3), 10)
-
- assert(!JWA.keyManagementEncrypt.has(jwaAlg), `keyManagementEncrypt alg ${jwaAlg} already registered`)
- assert(!JWA.keyManagementDecrypt.has(jwaAlg), `keyManagementDecrypt alg ${jwaAlg} already registered`)
-
if (getCiphers().includes(`aes${size}`)) {
JWA.keyManagementEncrypt.set(jwaAlg, wrapKey.bind(undefined, size))
JWA.keyManagementDecrypt.set(jwaAlg, unwrapKey.bind(undefined, size))
diff --git a/lib/jwa/ecdh/dir.js b/lib/jwa/ecdh/dir.js
index 7e6023f198..571b9932a0 100644
--- a/lib/jwa/ecdh/dir.js
+++ b/lib/jwa/ecdh/dir.js
@@ -1,5 +1,3 @@
-const { strict: assert } = require('assert')
-
const { KEYLENGTHS } = require('../../registry')
const { generateSync } = require('../../jwk/generate')
const { name: secp256k1 } = require('../../jwk/key/secp256k1_crv')
@@ -23,9 +21,6 @@ const unwrapKey = (key, payload, header) => {
}
module.exports = (JWA, JWK) => {
- assert(!JWA.keyManagementEncrypt.has('ECDH-ES'), 'keyManagementEncrypt alg ECDH-ES already registered')
- assert(!JWA.keyManagementDecrypt.has('ECDH-ES'), 'keyManagementDecrypt alg ECDH-ES already registered')
-
JWA.keyManagementEncrypt.set('ECDH-ES', wrapKey)
JWA.keyManagementDecrypt.set('ECDH-ES', unwrapKey)
JWK.EC.deriveKey['ECDH-ES'] = key => (key.use === 'enc' || key.use === undefined) && key.crv !== secp256k1
diff --git a/lib/jwa/ecdh/kw.js b/lib/jwa/ecdh/kw.js
index 4dfcbd172d..0741410a76 100644
--- a/lib/jwa/ecdh/kw.js
+++ b/lib/jwa/ecdh/kw.js
@@ -1,5 +1,3 @@
-const { strict: assert } = require('assert')
-
const { KEYOBJECT } = require('../../help/consts')
const { generateSync } = require('../../jwk/generate')
const { name: secp256k1 } = require('../../jwk/key/secp256k1_crv')
@@ -28,9 +26,6 @@ const unwrapKey = (unwrap, derive, key, payload, header) => {
module.exports = (JWA, JWK) => {
['ECDH-ES+A128KW', 'ECDH-ES+A192KW', 'ECDH-ES+A256KW'].forEach((jwaAlg) => {
- assert(!JWA.keyManagementEncrypt.has(jwaAlg), `keyManagementEncrypt alg ${jwaAlg} already registered`)
- assert(!JWA.keyManagementDecrypt.has(jwaAlg), `keyManagementDecrypt alg ${jwaAlg} already registered`)
-
const kw = jwaAlg.substr(-6)
const kwWrap = JWA.keyManagementEncrypt.get(kw)
const kwUnwrap = JWA.keyManagementDecrypt.get(kw)
diff --git a/lib/jwa/ecdsa.js b/lib/jwa/ecdsa.js
index 91e56240d9..db9645dfa8 100644
--- a/lib/jwa/ecdsa.js
+++ b/lib/jwa/ecdsa.js
@@ -1,4 +1,3 @@
-const { strict: assert } = require('assert')
const { sign: signOneShot, verify: verifyOneShot, createSign, createVerify, getCurves } = require('crypto')
const { derToJose, joseToDer } = require('../help/ecdsa_signatures')
@@ -10,21 +9,17 @@ const { name: secp256k1 } = require('../jwk/key/secp256k1_crv')
let sign, verify
-if (dsaEncodingSupported) { // >= 13.2.0
+if (dsaEncodingSupported) {
sign = (jwaAlg, nodeAlg, { [KEYOBJECT]: keyObject }, payload) => {
return signOneShot(nodeAlg, payload, { key: asInput(keyObject, false), dsaEncoding: 'ieee-p1363' })
}
-} else if (signOneShot) { // >= 12.0.0
- sign = (jwaAlg, nodeAlg, { [KEYOBJECT]: keyObject }, payload) => {
- return derToJose(signOneShot(nodeAlg, payload, asInput(keyObject, false)), jwaAlg)
- }
} else {
sign = (jwaAlg, nodeAlg, { [KEYOBJECT]: keyObject }, payload) => {
return derToJose(createSign(nodeAlg).update(payload).sign(asInput(keyObject, false)), jwaAlg)
}
}
-if (dsaEncodingSupported) { // >= 13.2.0
+if (dsaEncodingSupported) {
verify = (jwaAlg, nodeAlg, { [KEYOBJECT]: keyObject }, payload, signature) => {
try {
return verifyOneShot(nodeAlg, payload, { key: asInput(keyObject, true), dsaEncoding: 'ieee-p1363' }, signature)
@@ -32,14 +27,6 @@ if (dsaEncodingSupported) { // >= 13.2.0
return false
}
}
-} else if (verifyOneShot) { // >= 12.0.0
- verify = (jwaAlg, nodeAlg, { [KEYOBJECT]: keyObject }, payload, signature) => {
- try {
- return verifyOneShot(nodeAlg, payload, asInput(keyObject, true), joseToDer(signature, jwaAlg))
- } catch (err) {
- return false
- }
- }
} else {
verify = (jwaAlg, nodeAlg, { [KEYOBJECT]: keyObject }, payload, signature) => {
try {
@@ -84,10 +71,6 @@ module.exports = (JWA, JWK) => {
algs.forEach((jwaAlg) => {
const nodeAlg = resolveNodeAlg(jwaAlg)
-
- 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.bind(undefined, jwaAlg, nodeAlg))
JWA.verify.set(jwaAlg, verify.bind(undefined, jwaAlg, nodeAlg))
JWK.EC.sign[jwaAlg] = key => key.private && JWK.EC.verify[jwaAlg](key)
diff --git a/lib/jwa/eddsa.js b/lib/jwa/eddsa.js
index 05012d43c6..d6736d0566 100644
--- a/lib/jwa/eddsa.js
+++ b/lib/jwa/eddsa.js
@@ -1,4 +1,3 @@
-const { strict: assert } = require('assert')
const { sign: signOneShot, verify: verifyOneShot } = require('crypto')
const { KEYOBJECT } = require('../help/consts')
@@ -13,9 +12,6 @@ const verify = ({ [KEYOBJECT]: keyObject }, payload, signature) => {
}
module.exports = (JWA, JWK) => {
- assert(!JWA.sign.has('EdDSA'), 'sign alg EdDSA already registered')
- assert(!JWA.verify.has('EdDSA'), 'verify alg EdDSA already registered')
-
if (edDSASupported) {
JWA.sign.set('EdDSA', sign)
JWA.verify.set('EdDSA', verify)
diff --git a/lib/jwa/hmac.js b/lib/jwa/hmac.js
index c6ef260929..c80f785e70 100644
--- a/lib/jwa/hmac.js
+++ b/lib/jwa/hmac.js
@@ -1,4 +1,3 @@
-const { strict: assert } = require('assert')
const { createHmac } = require('crypto')
const { KEYOBJECT } = require('../help/consts')
@@ -24,10 +23,6 @@ const verify = (jwaAlg, hmacAlg, { [KEYOBJECT]: keyObject }, payload, signature)
module.exports = (JWA, JWK) => {
['HS256', 'HS384', 'HS512'].forEach((jwaAlg) => {
const hmacAlg = resolveNodeAlg(jwaAlg)
-
- 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.bind(undefined, jwaAlg, hmacAlg))
JWA.verify.set(jwaAlg, verify.bind(undefined, jwaAlg, hmacAlg))
JWK.oct.sign[jwaAlg] = JWK.oct.verify[jwaAlg] = key => key.use === 'sig' || key.use === undefined
diff --git a/lib/jwa/index.js b/lib/jwa/index.js
index 4afd402ccd..a3b61bb7d5 100644
--- a/lib/jwa/index.js
+++ b/lib/jwa/index.js
@@ -25,7 +25,22 @@ require('./pbes2')(JWA, JWK)
require('./ecdh/dir')(JWA, JWK)
require('./ecdh/kw')(JWA, JWK)
+const map = new WeakMap()
+
+const i = (ctx) => {
+ if (!map.has(ctx)) {
+ map.set(ctx, {})
+ }
+ return map.get(ctx)
+}
+
const check = (key, op, alg) => {
+ const cache = i(key)
+
+ if (cache[`${op}${alg}`]) {
+ return true
+ }
+
let label
let keyOp
if (op === 'keyManagementEncrypt') {
@@ -43,6 +58,8 @@ const check = (key, op, alg) => {
} else {
throw new JOSENotSupported(`unsupported ${label || op} alg: ${alg}`)
}
+
+ cache[`${op}${alg}`] = true
}
module.exports = {
diff --git a/lib/jwa/none.js b/lib/jwa/none.js
index e9de26a837..d2935bb6e0 100644
--- a/lib/jwa/none.js
+++ b/lib/jwa/none.js
@@ -1,14 +1,7 @@
-const { strict: assert } = require('assert')
-
-const sign = (key, payload) => Buffer.from('')
+const sign = () => 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)
+ JWA.sign.set('none', sign)
+ JWA.verify.set('none', verify)
}
diff --git a/lib/jwa/pbes2.js b/lib/jwa/pbes2.js
index b0c7d40dfa..9468627167 100644
--- a/lib/jwa/pbes2.js
+++ b/lib/jwa/pbes2.js
@@ -1,4 +1,3 @@
-const { strict: assert } = require('assert')
const { pbkdf2Sync: pbkdf2, randomBytes } = require('crypto')
const { KEYOBJECT } = require('../help/consts')
@@ -42,9 +41,6 @@ const unwrapKey = (keylen, sha, concat, unwrap, { [KEYOBJECT]: keyObject }, payl
module.exports = (JWA, JWK) => {
['PBES2-HS256+A128KW', 'PBES2-HS384+A192KW', 'PBES2-HS512+A256KW'].forEach((jwaAlg) => {
- assert(!JWA.keyManagementEncrypt.has(jwaAlg), `keyManagementEncrypt alg ${jwaAlg} already registered`)
- assert(!JWA.keyManagementDecrypt.has(jwaAlg), `keyManagementDecrypt alg ${jwaAlg} already registered`)
-
const kw = jwaAlg.substr(-6)
const kwWrap = JWA.keyManagementEncrypt.get(kw)
const kwUnwrap = JWA.keyManagementDecrypt.get(kw)
diff --git a/lib/jwa/rsaes.js b/lib/jwa/rsaes.js
index 5ed9a56b25..a7277123f6 100644
--- a/lib/jwa/rsaes.js
+++ b/lib/jwa/rsaes.js
@@ -1,4 +1,3 @@
-const { strict: assert } = require('assert')
const { publicEncrypt, privateDecrypt, constants } = require('crypto')
const { oaepHashSupported } = require('../help/runtime_support')
@@ -52,10 +51,6 @@ module.exports = (JWA, JWK) => {
algs.forEach((jwaAlg) => {
const padding = resolvePadding(jwaAlg)
const oaepHash = resolveOaepHash(jwaAlg)
-
- assert(!JWA.keyManagementEncrypt.has(jwaAlg), `keyManagementEncrypt alg ${jwaAlg} already registered`)
- assert(!JWA.keyManagementDecrypt.has(jwaAlg), `keyManagementDecrypt alg ${jwaAlg} already registered`)
-
JWA.keyManagementEncrypt.set(jwaAlg, wrapKey.bind(undefined, padding, oaepHash))
JWA.keyManagementDecrypt.set(jwaAlg, unwrapKey.bind(undefined, padding, oaepHash))
JWK.RSA.wrapKey[jwaAlg] = key => (key.use === 'enc' || key.use === undefined) && key.length >= LENGTHS[jwaAlg]
diff --git a/lib/jwa/rsassa.js b/lib/jwa/rsassa.js
index b033a6ce52..51c6f45687 100644
--- a/lib/jwa/rsassa.js
+++ b/lib/jwa/rsassa.js
@@ -1,33 +1,18 @@
-const { strict: assert } = require('assert')
-const { sign: signOneShot, verify: verifyOneShot, createSign, createVerify } = require('crypto')
+const { createSign, createVerify } = require('crypto')
const { KEYOBJECT } = require('../help/consts')
const resolveNodeAlg = require('../help/node_alg')
const { asInput } = require('../help/key_object')
-let sign, verify
-
-if (signOneShot) {
- sign = (nodeAlg, { [KEYOBJECT]: keyObject }, payload) => {
- return signOneShot(nodeAlg, payload, keyObject)
- }
-} else {
- sign = (nodeAlg, { [KEYOBJECT]: keyObject }, payload) => {
- return createSign(nodeAlg).update(payload).sign(asInput(keyObject, false))
- }
+const sign = (nodeAlg, { [KEYOBJECT]: keyObject }, payload) => {
+ return createSign(nodeAlg).update(payload).sign(asInput(keyObject, false))
}
-if (verifyOneShot) {
- verify = (nodeAlg, { [KEYOBJECT]: keyObject }, payload, signature) => {
- return verifyOneShot(nodeAlg, payload, keyObject, signature)
- }
-} else {
- verify = (nodeAlg, { [KEYOBJECT]: keyObject }, payload, signature) => {
- try {
- return createVerify(nodeAlg).update(payload).verify(asInput(keyObject, true), signature)
- } catch (err) {
- return false
- }
+const verify = (nodeAlg, { [KEYOBJECT]: keyObject }, payload, signature) => {
+ try {
+ return createVerify(nodeAlg).update(payload).verify(asInput(keyObject, true), signature)
+ } catch (err) {
+ return false
}
}
@@ -40,10 +25,6 @@ const LENGTHS = {
module.exports = (JWA, JWK) => {
['RS256', 'RS384', 'RS512'].forEach((jwaAlg) => {
const nodeAlg = resolveNodeAlg(jwaAlg)
-
- 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.bind(undefined, nodeAlg))
JWA.verify.set(jwaAlg, verify.bind(undefined, nodeAlg))
JWK.RSA.sign[jwaAlg] = key => key.private && JWK.RSA.verify[jwaAlg](key)
diff --git a/lib/jwa/rsassa_pss.js b/lib/jwa/rsassa_pss.js
index 44095cc8ee..bdb828904f 100644
--- a/lib/jwa/rsassa_pss.js
+++ b/lib/jwa/rsassa_pss.js
@@ -1,7 +1,4 @@
-const { strict: assert } = require('assert')
const {
- sign: signOneShot,
- verify: verifyOneShot,
createSign,
createVerify,
constants
@@ -11,44 +8,22 @@ const { KEYOBJECT } = require('../help/consts')
const resolveNodeAlg = require('../help/node_alg')
const { asInput } = require('../help/key_object')
-let sign, verify
-
-if (signOneShot) {
- sign = (nodeAlg, { [KEYOBJECT]: keyObject }, payload) => {
- return signOneShot(nodeAlg, payload, {
- key: asInput(keyObject, false),
- padding: constants.RSA_PKCS1_PSS_PADDING,
- saltLength: constants.RSA_PSS_SALTLEN_DIGEST
- })
- }
-} else {
- sign = (nodeAlg, { [KEYOBJECT]: keyObject }, payload) => {
- const key = asInput(keyObject, false)
- return createSign(nodeAlg).update(payload).sign({
- key,
- padding: constants.RSA_PKCS1_PSS_PADDING,
- saltLength: constants.RSA_PSS_SALTLEN_DIGEST
- })
- }
+const sign = (nodeAlg, { [KEYOBJECT]: keyObject }, payload) => {
+ const key = asInput(keyObject, false)
+ return createSign(nodeAlg).update(payload).sign({
+ key,
+ padding: constants.RSA_PKCS1_PSS_PADDING,
+ saltLength: constants.RSA_PSS_SALTLEN_DIGEST
+ })
}
-if (verifyOneShot) {
- verify = (nodeAlg, { [KEYOBJECT]: keyObject }, payload, signature) => {
- return verifyOneShot(nodeAlg, payload, {
- key: asInput(keyObject, false),
- padding: constants.RSA_PKCS1_PSS_PADDING,
- saltLength: constants.RSA_PSS_SALTLEN_DIGEST
- }, signature)
- }
-} else {
- verify = (nodeAlg, { [KEYOBJECT]: keyObject }, payload, signature) => {
- const key = asInput(keyObject, true)
- return createVerify(nodeAlg).update(payload).verify({
- key,
- padding: constants.RSA_PKCS1_PSS_PADDING,
- saltLength: constants.RSA_PSS_SALTLEN_DIGEST
- }, signature)
- }
+const verify = (nodeAlg, { [KEYOBJECT]: keyObject }, payload, signature) => {
+ const key = asInput(keyObject, true)
+ return createVerify(nodeAlg).update(payload).verify({
+ key,
+ padding: constants.RSA_PKCS1_PSS_PADDING,
+ saltLength: constants.RSA_PSS_SALTLEN_DIGEST
+ }, signature)
}
const LENGTHS = {
@@ -60,10 +35,6 @@ const LENGTHS = {
module.exports = (JWA, JWK) => {
['PS256', 'PS384', 'PS512'].forEach((jwaAlg) => {
const nodeAlg = resolveNodeAlg(jwaAlg)
-
- 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.bind(undefined, nodeAlg))
JWA.verify.set(jwaAlg, verify.bind(undefined, nodeAlg))
JWK.RSA.sign[jwaAlg] = key => key.private && JWK.RSA.verify[jwaAlg](key)
diff --git a/lib/jwe/decrypt.js b/lib/jwe/decrypt.js
index b1adb0abe6..318bc0e577 100644
--- a/lib/jwe/decrypt.js
+++ b/lib/jwe/decrypt.js
@@ -55,8 +55,6 @@ const jweDecrypt = (skipValidateHeaders, serialization, jwe, key, { crit = [], c
if (!serialization) {
serialization = resolveSerialization(jwe)
- } else if (serialization !== resolveSerialization(jwe)) {
- throw new errors.JWEInvalid()
}
let alg, ciphertext, enc, encryptedKey, iv, opts, prot, tag, unprotected, cek, aad, header
diff --git a/lib/jwks/keystore.js b/lib/jwks/keystore.js
index 39110a82ad..dc24c4caae 100644
--- a/lib/jwks/keystore.js
+++ b/lib/jwks/keystore.js
@@ -51,7 +51,7 @@ class KeyStore {
i(this).keys = new Set(keys)
}
- all ({ alg, kid, use, kty, key_ops: ops, x5t, 'x5t#S256': x5t256, crv } = {}) {
+ all ({ alg, kid, thumbprint, use, kty, key_ops: ops, x5t, 'x5t#S256': x5t256, crv } = {}) {
if (ops !== undefined && (!Array.isArray(ops) || !ops.length || ops.some(x => typeof x !== 'string'))) {
throw new TypeError('`key_ops` must be a non-empty array of strings')
}
@@ -65,6 +65,10 @@ class KeyStore {
candidate = false
}
+ if (candidate && thumbprint !== undefined && key.thumbprint !== thumbprint) {
+ candidate = false
+ }
+
if (candidate && x5t !== undefined && key.x5t !== x5t) {
candidate = false
}
diff --git a/lib/jws/index.js b/lib/jws/index.js
index ba1fe7474b..3e87554364 100644
--- a/lib/jws/index.js
+++ b/lib/jws/index.js
@@ -1,5 +1,5 @@
const Sign = require('./sign')
-const verify = require('./verify')
+const { verify } = require('./verify')
const single = (serialization, payload, key, protectedHeader, unprotectedHeader) => {
const jws = new Sign(payload)
diff --git a/lib/jws/verify.js b/lib/jws/verify.js
index 7000e902ea..53a1fe0ec7 100644
--- a/lib/jws/verify.js
+++ b/lib/jws/verify.js
@@ -9,7 +9,7 @@ const { check, verify } = require('../jwa')
const { detect: resolveSerialization } = require('./serializers')
validateCrit = validateCrit.bind(undefined, errors.JWSInvalid)
-const SINGLE_RECIPIENT = new Set(['compact', 'flattened'])
+const SINGLE_RECIPIENT = new Set(['compact', 'flattened', 'preparsed'])
/*
* @public
@@ -29,8 +29,6 @@ const jwsVerify = (skipDisjointCheck, serialization, jws, key, { crit = [], comp
if (!serialization) {
serialization = resolveSerialization(jws)
- } else if (serialization !== resolveSerialization(jws)) {
- throw new errors.JWSInvalid()
}
let prot // protected header
@@ -47,26 +45,35 @@ const jwsVerify = (skipDisjointCheck, serialization, jws, key, { crit = [], comp
jws = { ...root, ...signatures[0] }
}
+ let decoded
+
if (SINGLE_RECIPIENT.has(serialization)) {
- if (serialization === 'compact') { // compact serialization format
- ([prot, payload, signature] = jws.split('.'))
- } else { // flattened serialization format
- ({ protected: prot, payload, signature, header } = jws)
+ let parsedProt = {}
+
+ switch (serialization) {
+ case 'compact': // compact serialization format
+ ([prot, payload, signature] = jws.split('.'))
+ break
+ case 'flattened': // flattened serialization format
+ ({ protected: prot, payload, signature, header } = jws)
+ break
+ case 'preparsed': { // from the JWT module
+ ({ decoded } = jws);
+ ([prot, payload, signature] = jws.token.split('.'))
+ break
+ }
}
if (!header) {
skipDisjointCheck = true
}
- let parsedProt = {}
- if (prot) {
+ if (decoded) {
+ parsedProt = decoded.header
+ } else if (prot) {
try {
parsedProt = base64url.JSON.decode(prot)
} catch (err) {
- if (err instanceof errors.JOSEError) {
- throw err
- }
-
throw new errors.JWSInvalid('could not parse JWS protected header')
}
} else {
@@ -125,13 +132,14 @@ const jwsVerify = (skipDisjointCheck, serialization, jws, key, { crit = [], comp
Buffer.from('.'),
Buffer.isBuffer(payload) ? payload : Buffer.from(payload)
])
+
if (!verify(alg, key, toBeVerified, base64url.decodeToBuffer(signature))) {
throw new errors.JWSVerificationFailed()
}
if (!combinedHeader.crit || !combinedHeader.crit.includes('b64') || combinedHeader.b64) {
if (parse) {
- payload = base64url.JSON.decode.try(payload, encoding)
+ payload = decoded ? decoded.payload : base64url.JSON.decode.try(payload, encoding)
} else {
payload = base64url.decodeToBuffer(payload)
}
@@ -168,4 +176,7 @@ const jwsVerify = (skipDisjointCheck, serialization, jws, key, { crit = [], comp
throw multi
}
-module.exports = jwsVerify.bind(undefined, false, undefined)
+module.exports = {
+ bare: jwsVerify,
+ verify: jwsVerify.bind(undefined, false, undefined)
+}
diff --git a/lib/jwt/verify.js b/lib/jwt/verify.js
index 364c0a31c1..dd8af88f4e 100644
--- a/lib/jwt/verify.js
+++ b/lib/jwt/verify.js
@@ -2,8 +2,7 @@ const isObject = require('../help/is_object')
const epoch = require('../help/epoch')
const secs = require('../help/secs')
const getKey = require('../help/get_key')
-const JWS = require('../jws')
-const { KeyStore } = require('../jwks')
+const { bare: verify } = require('../jws/verify')
const { JWTClaimInvalid, JWTExpired } = require('../errors')
const { isString, isNotString } = require('./shared_validations')
@@ -225,9 +224,17 @@ module.exports = (token, key, options = {}) => {
jti, maxAuthAge, maxTokenAge, nonce, now, profile, subject
} = options = validateOptions(options)
- const unix = epoch(now)
-
const decoded = decode(token, { complete: true })
+ key = getKey(key, true)
+
+ if (complete) {
+ ({ key } = verify(true, 'preparsed', { decoded, token }, key, { crit, algorithms, complete: true }))
+ decoded.key = key
+ } else {
+ verify(true, 'preparsed', { decoded, token }, key, { crit, algorithms })
+ }
+
+ const unix = epoch(now)
validateTypes(decoded, profile, options)
if (issuer && decoded.payload.iss !== issuer) {
@@ -292,13 +299,5 @@ module.exports = (token, key, options = {}) => {
throw new JWTClaimInvalid('invalid JWT typ header value for the used validation profile', 'typ', 'check_failed')
}
- key = getKey(key, true)
-
- if (complete && key instanceof KeyStore) {
- ({ key } = JWS.verify(token, key, { crit, algorithms, complete: true }))
- } else {
- JWS.verify(token, key, { crit, algorithms })
- }
-
- return complete ? { ...decoded, key } : decoded.payload
+ return complete ? decoded : decoded.payload
}
diff --git a/package.json b/package.json
index 1881c917d2..59d55e6b8e 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "jose",
- "version": "1.21.1",
+ "version": "1.22.0",
"description": "JSON Web Almost Everything - JWA, JWS, JWE, JWK, JWT, JWKS for Node.js with minimal dependencies",
"keywords": [
"access token",
@@ -8,6 +8,7 @@
"compact",
"decode",
"decrypt",
+ "detached",
"ec",
"ecdsa",
"eddsa",
@@ -30,9 +31,11 @@
"logout_token",
"oct",
"okp",
+ "payload",
"rsa",
"secp256k1",
"sign",
+ "signature",
"validate",
"verify"
],
diff --git a/test/help/base64url.test.js b/test/help/base64url.test.js
index d0e59d2ed4..9a561f0597 100644
--- a/test/help/base64url.test.js
+++ b/test/help/base64url.test.js
@@ -1,6 +1,5 @@
const test = require('ava')
-const errors = require('../../lib/errors')
const base64url = require('../../lib/help/base64url')
const testStr = 'fmkIOj+kafqtjMl+iC32a+9YGz0cKj/JT9Jt31uXR1la7FSXkjoBzg/F+huYm0udbM5z5qGlmPBNZASsixJLcA=='
@@ -41,9 +40,3 @@ test('.JSON.decode.try (valid json)', t => {
test('.JSON.decode.try (invalid json)', t => {
t.is(base64url.JSON.decode.try('Zm9v'), 'foo')
})
-
-test('decode input with invalid encoding throws', t => {
- t.throws(() => {
- base64url.decode(testStr)
- }, { instanceOf: errors.JOSEInvalidEncoding, code: 'ERR_JOSE_INVALID_ENCODING', message: 'input is not a valid base64url encoded string' })
-})
diff --git a/test/jwa/sanity.test.js b/test/jwa/sanity.test.js
index cd6cef7829..49b3be7d65 100644
--- a/test/jwa/sanity.test.js
+++ b/test/jwa/sanity.test.js
@@ -2,6 +2,7 @@ const test = require('ava')
const { errors } = require('../..')
const JWA = require('../../lib/jwa')
+const JWK = require('../../lib/jwk')
;['sign', 'verify', 'keyManagementEncrypt', 'keyManagementDecrypt', 'encrypt', 'decrypt'].forEach((op) => {
let label
@@ -10,7 +11,7 @@ const JWA = require('../../lib/jwa')
}
test(`JWA.${op} will not accept an "unimplemented" algorithm`, t => {
t.throws(() => {
- JWA[op]('foo')
+ JWA[op]('foo', JWK.generateSync('oct'))
}, { instanceOf: errors.JOSENotSupported, code: 'ERR_JOSE_NOT_SUPPORTED', message: `unsupported ${label || op} alg: foo` })
})
})
diff --git a/test/jwk/import.test.js b/test/jwk/import.test.js
index 634b3e4bc3..a9aed164a4 100644
--- a/test/jwk/import.test.js
+++ b/test/jwk/import.test.js
@@ -6,7 +6,6 @@ const { edDSASupported, keyObjectSupported } = require('../../lib/help/runtime_s
const { createSecretKey } = require('../../lib/help/key_object')
const { generateKeyPairSync } = require('../macros/generate')
const fixtures = require('../fixtures')
-const base64url = require('../../lib/help/base64url')
test('imports PrivateKeyObject and then its Key instance', t => {
const k = asKey(generateKeyPairSync('ec', { namedCurve: 'P-256' }).privateKey)
@@ -145,26 +144,6 @@ test('fails to import JWK RSA with oth', async t => {
}, { instanceOf: errors.JOSENotSupported, code: 'ERR_JOSE_NOT_SUPPORTED', message: 'Private RSA keys with more than two primes are not supported' })
})
-test('invalid encoded jwk import', async t => {
- const jwk = (await generate('oct')).toJWK(true)
-
- jwk.k = base64url.decodeToBuffer(jwk.k).toString('base64')
-
- t.throws(() => {
- asKey(jwk)
- }, { instanceOf: errors.JOSEInvalidEncoding, code: 'ERR_JOSE_INVALID_ENCODING', message: 'input is not a valid base64url encoded string' })
-})
-
-test('invalid encoded oct jwk import', async t => {
- const jwk = (await generate('EC')).toJWK(true)
-
- jwk.d = base64url.decodeToBuffer(jwk.d).toString('base64')
-
- t.throws(() => {
- asKey(jwk)
- }, { instanceOf: errors.JOSEInvalidEncoding, code: 'ERR_JOSE_INVALID_ENCODING', message: 'input is not a valid base64url encoded string' })
-})
-
const cert = `-----BEGIN CERTIFICATE-----
MIIC4DCCAcgCCQDO8JBSH914NDANBgkqhkiG9w0BAQsFADAyMQswCQYDVQQGEwJD
WjEPMA0GA1UEBwwGUHJhZ3VlMRIwEAYDVQQDDAlwa210bHN0d28wHhcNMTkwNjE4
diff --git a/test/jwks/keystore.test.js b/test/jwks/keystore.test.js
index 888e62d89b..625e69f9a1 100644
--- a/test/jwks/keystore.test.js
+++ b/test/jwks/keystore.test.js
@@ -169,6 +169,15 @@ test('.all() and .get() kid filter', t => {
t.is(ks.get({ kid: 'foobar' }), k)
})
+test('.all() and .get() thumbprint filter', t => {
+ const k = generateSync('RSA')
+ const ks = new KeyStore(k)
+ t.deepEqual(ks.all({ thumbprint: 'baz' }), [])
+ t.deepEqual(ks.all({ thumbprint: k.thumbprint }), [k])
+ t.is(ks.get({ thumbprint: 'baz' }), undefined)
+ t.is(ks.get({ thumbprint: k.thumbprint }), k)
+})
+
test('.all() and .get() x5t filter and sort', t => {
const k = asKey(withX5C)
const ks = new KeyStore(k)
diff --git a/test/jws/sanity.test.js b/test/jws/sanity.test.js
index eefe53d078..e1e14bbcc3 100644
--- a/test/jws/sanity.test.js
+++ b/test/jws/sanity.test.js
@@ -273,15 +273,6 @@ test('JWS verify algorithms whitelist (multi-recipient)', t => {
})
})
-test('invalid tokens', t => {
- t.throws(() => {
- JWS.verify(
- 'eyJ0eXAiOiJKV1QiLCJraWQiOiIyZTFkYjRmMC1mYmY5LTQxZjYtOGMxYi1hMzczYjgwZmNhYTEiLCJhbGciOiJFUzI1NiIsImlzcyI6Imh0dHBzOi8vaWRlbnRpdHktc3RhZ2luZy5kZWxpdmVyb28uY29tLyIsImNsaWVudCI6ImIyM2I0ZjM1YzIyMTI5NDQxZjMwZDMyYmI5ZmM4ZWYyIiwic2lnbmVyIjoiYXJuOmF3czplbGFzdGljbG9hZGJhbGFuY2luZzpldS13ZXN0LTE6NTE3OTAyNjYzOTE1OmxvYWRiYWxhbmNlci9hcHAvcGF5bWVudHMtZGFzaGJvYXJkLXdlYi80YzA4ZGI2NDMyMDIyOWEyIiwiZXhwIjoxNTYyNjkxNTg1fQ==.eyJlbWFpbCI6ImpvYW8udmllaXJhQGRlbGl2ZXJvby5jby51ayIsImVtYWlsX3ZlcmlmaWVkIjp0cnVlLCJmYW1pbHlfbmFtZSI6Ikd1ZXJyYSBWaWVpcmEiLCJnaXZlbl9uYW1lIjoiSm9hbyIsIm5hbWUiOiJKb2FvIEd1ZXJyYSBWaWVpcmEiLCJwaWN0dXJlIjoiaHR0cHM6Ly9saDMuZ29vZ2xldXNlcmNvbnRlbnQuY29tLy1sTUpXTXV3R1dpYy9BQUFBQUFBQUFBSS9BQUFBQUFBQUFCVS9lNGtkTDg5UjlqZy9zOTYtYy9waG90by5qcGciLCJzdWIiOiIxMWE1YmFmMGRjNzcwNWRmMzk1ZTMzYWFkZjU2MDk4OCIsImV4cCI6MTU2MjY5MTU4NSwiaXNzIjoiaHR0cHM6Ly9pZGVudGl0eS1zdGFnaW5nLmRlbGl2ZXJvby5jb20vIn0=.DSHLJXLOfLJ-ZYcX0Vlii6Ak_jcDSkKOvNRj_rvtAyY9uYXtwo798ZrR35fgut-LuCdx0aKz2SgK0KJqw5q6dA==',
- generateSync('EC')
- )
- }, { instanceOf: errors.JOSEInvalidEncoding, code: 'ERR_JOSE_INVALID_ENCODING', message: 'input is not a valid base64url encoded string' })
-})
-
test('"enc" key is not usable for signing', t => {
const k = generateSync('oct', 256, { use: 'enc' })
t.throws(() => {
diff --git a/test/jwt/decode.test.js b/test/jwt/decode.test.js
index 3c549df6e0..49251203ea 100644
--- a/test/jwt/decode.test.js
+++ b/test/jwt/decode.test.js
@@ -47,9 +47,3 @@ test('returns the payload', t => {
foo: 'bar'
})
})
-
-test('invalid tokens', t => {
- t.throws(() => {
- JWT.decode('eyJ0eXAiOiJKV1QiLCJraWQiOiIyZTFkYjRmMC1mYmY5LTQxZjYtOGMxYi1hMzczYjgwZmNhYTEiLCJhbGciOiJFUzI1NiIsImlzcyI6Imh0dHBzOi8vaWRlbnRpdHktc3RhZ2luZy5kZWxpdmVyb28uY29tLyIsImNsaWVudCI6ImIyM2I0ZjM1YzIyMTI5NDQxZjMwZDMyYmI5ZmM4ZWYyIiwic2lnbmVyIjoiYXJuOmF3czplbGFzdGljbG9hZGJhbGFuY2luZzpldS13ZXN0LTE6NTE3OTAyNjYzOTE1OmxvYWRiYWxhbmNlci9hcHAvcGF5bWVudHMtZGFzaGJvYXJkLXdlYi80YzA4ZGI2NDMyMDIyOWEyIiwiZXhwIjoxNTYyNjkxNTg1fQ==.eyJlbWFpbCI6ImpvYW8udmllaXJhQGRlbGl2ZXJvby5jby51ayIsImVtYWlsX3ZlcmlmaWVkIjp0cnVlLCJmYW1pbHlfbmFtZSI6Ikd1ZXJyYSBWaWVpcmEiLCJnaXZlbl9uYW1lIjoiSm9hbyIsIm5hbWUiOiJKb2FvIEd1ZXJyYSBWaWVpcmEiLCJwaWN0dXJlIjoiaHR0cHM6Ly9saDMuZ29vZ2xldXNlcmNvbnRlbnQuY29tLy1sTUpXTXV3R1dpYy9BQUFBQUFBQUFBSS9BQUFBQUFBQUFCVS9lNGtkTDg5UjlqZy9zOTYtYy9waG90by5qcGciLCJzdWIiOiIxMWE1YmFmMGRjNzcwNWRmMzk1ZTMzYWFkZjU2MDk4OCIsImV4cCI6MTU2MjY5MTU4NSwiaXNzIjoiaHR0cHM6Ly9pZGVudGl0eS1zdGFnaW5nLmRlbGl2ZXJvby5jb20vIn0=.DSHLJXLOfLJ-ZYcX0Vlii6Ak_jcDSkKOvNRj_rvtAyY9uYXtwo798ZrR35fgut-LuCdx0aKz2SgK0KJqw5q6dA==')
- }, { instanceOf: errors.JOSEInvalidEncoding, code: 'ERR_JOSE_INVALID_ENCODING', message: 'input is not a valid base64url encoded string' })
-})
diff --git a/test/jwt/verify.test.js b/test/jwt/verify.test.js
index 8a8f228620..844c51a4dd 100644
--- a/test/jwt/verify.test.js
+++ b/test/jwt/verify.test.js
@@ -1,7 +1,6 @@
const test = require('ava')
-const { JWT, JWK, JWKS, errors } = require('../..')
-const base64url = require('../../lib/help/base64url')
+const { JWS, JWT, JWK, JWKS, errors } = require('../..')
const key = JWK.generateSync('oct')
const token = JWT.sign({}, key, { iat: false })
@@ -107,7 +106,7 @@ test('options.ignoreIat & options.maxTokenAge may not be used together', t => {
test(`"${claim} must be a timestamp when provided"`, t => {
;['', 'foo', true, null, [], {}].forEach((val) => {
const err = t.throws(() => {
- const invalid = `eyJhbGciOiJub25lIn0.${base64url.JSON.encode({ [claim]: val })}.`
+ const invalid = JWS.sign({ [claim]: val }, key)
JWT.verify(invalid, key)
}, { instanceOf: errors.JWTClaimInvalid, message: `"${claim}" claim must be a unix timestamp` })
@@ -121,7 +120,7 @@ test('options.ignoreIat & options.maxTokenAge may not be used together', t => {
test(`"${claim} must be a string when provided"`, t => {
;['', 0, 1, true, null, [], {}].forEach((val) => {
const err = t.throws(() => {
- const invalid = `eyJhbGciOiJub25lIn0.${base64url.JSON.encode({ [claim]: val })}.`
+ const invalid = JWS.sign({ [claim]: val }, key)
JWT.verify(invalid, key)
}, { instanceOf: errors.JWTClaimInvalid, message: `"${claim}" claim must be a string` })
@@ -136,14 +135,14 @@ test('options.ignoreIat & options.maxTokenAge may not be used together', t => {
;['', 0, 1, true, null, [], {}].forEach((val) => {
let err
err = t.throws(() => {
- const invalid = `eyJhbGciOiJub25lIn0.${base64url.JSON.encode({ [claim]: val })}.`
+ const invalid = JWS.sign({ [claim]: val }, key)
JWT.verify(invalid, key)
}, { instanceOf: errors.JWTClaimInvalid, message: `"${claim}" claim must be a string or array of strings` })
t.is(err.claim, claim)
t.is(err.reason, 'invalid')
err = t.throws(() => {
- const invalid = `eyJhbGciOiJub25lIn0.${base64url.JSON.encode({ [claim]: [val] })}.`
+ const invalid = JWS.sign({ [claim]: [val] }, key)
JWT.verify(invalid, key)
}, { instanceOf: errors.JWTClaimInvalid, message: `"${claim}" claim must be a string or array of strings` })
t.is(err.claim, claim)
@@ -161,14 +160,14 @@ Object.entries({
test(`option.${option} validation fails`, t => {
let err
err = t.throws(() => {
- const invalid = `eyJhbGciOiJub25lIn0.${base64url.JSON.encode({ [claim]: 'foo' })}.`
+ const invalid = JWS.sign({ [claim]: 'foo' }, key)
JWT.verify(invalid, key, { [option]: 'bar' })
}, { instanceOf: errors.JWTClaimInvalid, message: `unexpected "${claim}" claim value` })
t.is(err.claim, claim)
t.is(err.reason, 'check_failed')
err = t.throws(() => {
- const invalid = `eyJhbGciOiJub25lIn0.${base64url.JSON.encode({ [claim]: undefined })}.`
+ const invalid = JWS.sign({ [claim]: undefined }, key)
JWT.verify(invalid, key, { [option]: 'bar' })
}, { instanceOf: errors.JWTClaimInvalid, message: `"${claim}" claim is missing` })
t.is(err.claim, claim)
@@ -185,14 +184,14 @@ Object.entries({
test('option.audience validation fails', t => {
let err
err = t.throws(() => {
- const invalid = `eyJhbGciOiJub25lIn0.${base64url.JSON.encode({ aud: 'foo' })}.`
+ const invalid = JWS.sign({ aud: 'foo' }, key)
JWT.verify(invalid, key, { audience: 'bar' })
}, { instanceOf: errors.JWTClaimInvalid, message: 'unexpected "aud" claim value' })
t.is(err.claim, 'aud')
t.is(err.reason, 'check_failed')
err = t.throws(() => {
- const invalid = `eyJhbGciOiJub25lIn0.${base64url.JSON.encode({ aud: ['foo'] })}.`
+ const invalid = JWS.sign({ aud: ['foo'] }, key)
JWT.verify(invalid, key, { audience: 'bar' })
}, { instanceOf: errors.JWTClaimInvalid, message: 'unexpected "aud" claim value' })
t.is(err.claim, 'aud')
@@ -218,7 +217,7 @@ test('option.audience validation success', t => {
test('option.maxAuthAge requires iat to be in the payload', t => {
const err = t.throws(() => {
- const invalid = `eyJhbGciOiJub25lIn0.${base64url.JSON.encode({})}.`
+ const invalid = JWS.sign({}, key)
JWT.verify(invalid, key, { maxAuthAge: '30s' })
}, { instanceOf: errors.JWTClaimInvalid, message: '"auth_time" claim is missing' })
t.is(err.claim, 'auth_time')
@@ -230,7 +229,7 @@ const now = new Date(epoch * 1000)
test('option.maxAuthAge checks auth_time', t => {
const err = t.throws(() => {
- const invalid = `eyJhbGciOiJub25lIn0.${base64url.JSON.encode({ auth_time: epoch - 31 })}.`
+ const invalid = JWS.sign({ auth_time: epoch - 31 }, key)
JWT.verify(invalid, key, { maxAuthAge: '30s', now })
}, { instanceOf: errors.JWTClaimInvalid, message: '"auth_time" claim timestamp check failed (too much time has elapsed since the last End-User authentication)' })
t.is(err.claim, 'auth_time')
@@ -245,7 +244,7 @@ test('option.maxAuthAge checks auth_time (with tolerance)', t => {
test('option.maxTokenAge requires iat to be in the payload', t => {
const err = t.throws(() => {
- const invalid = `eyJhbGciOiJub25lIn0.${base64url.JSON.encode({})}.`
+ const invalid = JWS.sign({}, key)
JWT.verify(invalid, key, { maxTokenAge: '30s' })
}, { instanceOf: errors.JWTClaimInvalid, message: '"iat" claim is missing' })
t.is(err.claim, 'iat')
@@ -254,7 +253,7 @@ test('option.maxTokenAge requires iat to be in the payload', t => {
test('option.maxTokenAge checks iat elapsed time', t => {
const err = t.throws(() => {
- const invalid = `eyJhbGciOiJub25lIn0.${base64url.JSON.encode({ iat: epoch - 31 })}.`
+ const invalid = JWS.sign({ iat: epoch - 31 }, key)
JWT.verify(invalid, key, { maxTokenAge: '30s', now })
}, { instanceOf: errors.JWTExpired, code: 'ERR_JWT_EXPIRED', message: '"iat" claim timestamp check failed (too far in the past)' })
t.true(err instanceof errors.JWTClaimInvalid)
@@ -828,12 +827,3 @@ test('must be a supported value', t => {
t.is(err.reason, 'check_failed')
})
}
-
-test('invalid tokens', t => {
- t.throws(() => {
- JWT.verify(
- 'eyJ0eXAiOiJKV1QiLCJraWQiOiIyZTFkYjRmMC1mYmY5LTQxZjYtOGMxYi1hMzczYjgwZmNhYTEiLCJhbGciOiJFUzI1NiIsImlzcyI6Imh0dHBzOi8vaWRlbnRpdHktc3RhZ2luZy5kZWxpdmVyb28uY29tLyIsImNsaWVudCI6ImIyM2I0ZjM1YzIyMTI5NDQxZjMwZDMyYmI5ZmM4ZWYyIiwic2lnbmVyIjoiYXJuOmF3czplbGFzdGljbG9hZGJhbGFuY2luZzpldS13ZXN0LTE6NTE3OTAyNjYzOTE1OmxvYWRiYWxhbmNlci9hcHAvcGF5bWVudHMtZGFzaGJvYXJkLXdlYi80YzA4ZGI2NDMyMDIyOWEyIiwiZXhwIjoxNTYyNjkxNTg1fQ==.eyJlbWFpbCI6ImpvYW8udmllaXJhQGRlbGl2ZXJvby5jby51ayIsImVtYWlsX3ZlcmlmaWVkIjp0cnVlLCJmYW1pbHlfbmFtZSI6Ikd1ZXJyYSBWaWVpcmEiLCJnaXZlbl9uYW1lIjoiSm9hbyIsIm5hbWUiOiJKb2FvIEd1ZXJyYSBWaWVpcmEiLCJwaWN0dXJlIjoiaHR0cHM6Ly9saDMuZ29vZ2xldXNlcmNvbnRlbnQuY29tLy1sTUpXTXV3R1dpYy9BQUFBQUFBQUFBSS9BQUFBQUFBQUFCVS9lNGtkTDg5UjlqZy9zOTYtYy9waG90by5qcGciLCJzdWIiOiIxMWE1YmFmMGRjNzcwNWRmMzk1ZTMzYWFkZjU2MDk4OCIsImV4cCI6MTU2MjY5MTU4NSwiaXNzIjoiaHR0cHM6Ly9pZGVudGl0eS1zdGFnaW5nLmRlbGl2ZXJvby5jb20vIn0=.DSHLJXLOfLJ-ZYcX0Vlii6Ak_jcDSkKOvNRj_rvtAyY9uYXtwo798ZrR35fgut-LuCdx0aKz2SgK0KJqw5q6dA==',
- key
- )
- }, { instanceOf: errors.JOSEInvalidEncoding, code: 'ERR_JOSE_INVALID_ENCODING', message: 'input is not a valid base64url encoded string' })
-})
diff --git a/types/index.d.ts b/types/index.d.ts
index df330cfe07..ba5639ba15 100644
--- a/types/index.d.ts
+++ b/types/index.d.ts
@@ -193,6 +193,7 @@ export namespace JWKS {
x5t?: string;
'x5t#S256'?: string;
crv?: string;
+ thumbprint?: string;
}
class KeyStore {