mirror of
https://github.com/danbulant/jose
synced 2026-05-19 04:18:52 +00:00
feat: add opt-in objects to verify using embedded JWS Header public keys
This commit is contained in:
parent
5c7888869f
commit
7c1cab196e
13 changed files with 351 additions and 20 deletions
|
|
@ -52,6 +52,8 @@ If you or your business use `jose`, please consider becoming a [sponsor][support
|
|||
- [JWK.generateSync(kty[, crvOrSize[, options[, private]]])](#jwkgeneratesynckty-crvorsize-options-private)
|
||||
- [JWK.isKey(object)](#jwkiskeyobject)
|
||||
- [JWK.None](#jwknone)
|
||||
- [JWK.EmbeddedJWK](#jwkembeddedjwk)
|
||||
- [JWK.EmbeddedX5C](#jwkembeddedx5c)
|
||||
<!-- TOC JWK END -->
|
||||
|
||||
All sign and encrypt operations require `<JWK.Key>` or `JWK.asKey()` compatible input.
|
||||
|
|
@ -623,6 +625,28 @@ JWS.verify(unsecuredJWS, None)
|
|||
|
||||
---
|
||||
|
||||
#### `JWK.EmbeddedJWK`
|
||||
|
||||
`JWK.EmbeddedJWK` is a special key object that can be used with the JWS/JWT verify operations
|
||||
whenever you want to opt-in to verify signatures with a public key embedded in the JWS Header `jwk`
|
||||
parameter. It is recommended to combine this with the verify `algorithms` option to whitelist
|
||||
JWS algorithms to accept as well as the `complete` option set to `true` if you need to work with the
|
||||
instantiated `JWK.Key` from the token.
|
||||
|
||||
---
|
||||
|
||||
#### `JWK.EmbeddedX5C`
|
||||
|
||||
`JWK.EmbeddedX5C` is a special key object that can be used with the JWS/JWT verify operations
|
||||
whenever you want to opt-in to verify signatures with a public key embedded in the first JWS Header
|
||||
`x5c` parameter. It is recommended to combine this with the verify `algorithms` option to whitelist
|
||||
JWS algorithms to accept as well as the `complete` option set to `true` if you need to work with the
|
||||
instantiated `JWK.Key` from the token. ⚠️ the x5c members are all validated to be certificates but
|
||||
their chain or trust is not validated. Unfortunately Node.js does not have any good tools to do that
|
||||
reliably.
|
||||
|
||||
---
|
||||
|
||||
## JWKS (JSON Web Key Set)
|
||||
|
||||
<!-- TOC JWKS START -->
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ if (keyObjectSupported) {
|
|||
}
|
||||
|
||||
const pemToDer = pem => Buffer.from(pem.replace(/(?:-----(?:BEGIN|END)(?: (?:RSA|EC))? (?:PRIVATE|PUBLIC) KEY-----|\s)/g, ''), 'base64')
|
||||
const derToPem = (der, label) => `-----BEGIN ${label}-----${EOL}${der.toString('base64').match(/.{1,64}/g).join(EOL)}${EOL}-----END ${label}-----`
|
||||
const derToPem = (der, label) => `-----BEGIN ${label}-----${EOL}${(der.toString('base64').match(/.{1,64}/g) || []).join(EOL)}${EOL}-----END ${label}-----`
|
||||
const unsupported = (input) => {
|
||||
const label = typeof input === 'string' ? input : `OID ${input.join('.')}`
|
||||
throw new errors.JOSENotSupported(`${label} is not supported in your Node.js runtime version`)
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ const asn1 = require('./asn1')
|
|||
const computePrimes = require('./rsa_primes')
|
||||
const { OKP_CURVES, EC_CURVES } = require('../registry')
|
||||
|
||||
const formatPem = (base64pem, descriptor) => `-----BEGIN ${descriptor} KEY-----${EOL}${base64pem.match(/.{1,64}/g).join(EOL)}${EOL}-----END ${descriptor} KEY-----`
|
||||
const formatPem = (base64pem, descriptor) => `-----BEGIN ${descriptor} KEY-----${EOL}${(base64pem.match(/.{1,64}/g) || []).join(EOL)}${EOL}-----END ${descriptor} KEY-----`
|
||||
|
||||
const okpToJWK = {
|
||||
private (crv, keyObject) {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
const Key = require('./key/base')
|
||||
const None = require('./key/none')
|
||||
const EmbeddedJWK = require('./key/embedded.jwk')
|
||||
const EmbeddedX5C = require('./key/embedded.x5c')
|
||||
const importKey = require('./import')
|
||||
const generate = require('./generate')
|
||||
|
||||
|
|
@ -7,7 +9,9 @@ module.exports = {
|
|||
...generate,
|
||||
asKey: importKey,
|
||||
isKey: input => input instanceof Key,
|
||||
None
|
||||
None,
|
||||
EmbeddedJWK,
|
||||
EmbeddedX5C
|
||||
}
|
||||
|
||||
/* deprecated */
|
||||
|
|
|
|||
|
|
@ -61,7 +61,7 @@ class Key {
|
|||
let publicKey
|
||||
try {
|
||||
publicKey = createPublicKey({
|
||||
key: `-----BEGIN CERTIFICATE-----${EOL}${cert.match(/.{1,64}/g).join(EOL)}${EOL}-----END CERTIFICATE-----`, format: 'pem'
|
||||
key: `-----BEGIN CERTIFICATE-----${EOL}${(cert.match(/.{1,64}/g) || []).join(EOL)}${EOL}-----END CERTIFICATE-----`, format: 'pem'
|
||||
})
|
||||
} catch (err) {
|
||||
throw new errors.JWKInvalid(`\`x5c\` member at index ${i} is not a valid base64-encoded DER PKIX certificate`)
|
||||
|
|
|
|||
27
lib/jwk/key/embedded.jwk.js
Normal file
27
lib/jwk/key/embedded.jwk.js
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
const { inspect } = require('util')
|
||||
|
||||
const Key = require('./base')
|
||||
|
||||
class EmbeddedJWK extends Key {
|
||||
constructor () {
|
||||
super({ type: 'embedded' })
|
||||
Object.defineProperties(this, {
|
||||
kid: { value: undefined },
|
||||
kty: { value: undefined },
|
||||
thumbprint: { value: undefined },
|
||||
toJWK: { value: undefined },
|
||||
toPEM: { value: undefined }
|
||||
})
|
||||
}
|
||||
|
||||
/* c8 ignore next 3 */
|
||||
[inspect.custom] () {
|
||||
return 'Embedded.JWK {}'
|
||||
}
|
||||
|
||||
algorithms () {
|
||||
return new Set()
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = new EmbeddedJWK()
|
||||
27
lib/jwk/key/embedded.x5c.js
Normal file
27
lib/jwk/key/embedded.x5c.js
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
const { inspect } = require('util')
|
||||
|
||||
const Key = require('./base')
|
||||
|
||||
class EmbeddedX5C extends Key {
|
||||
constructor () {
|
||||
super({ type: 'embedded' })
|
||||
Object.defineProperties(this, {
|
||||
kid: { value: undefined },
|
||||
kty: { value: undefined },
|
||||
thumbprint: { value: undefined },
|
||||
toJWK: { value: undefined },
|
||||
toPEM: { value: undefined }
|
||||
})
|
||||
}
|
||||
|
||||
/* c8 ignore next 3 */
|
||||
[inspect.custom] () {
|
||||
return 'Embedded.X5C {}'
|
||||
}
|
||||
|
||||
algorithms () {
|
||||
return new Set()
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = new EmbeddedX5C()
|
||||
|
|
@ -7,6 +7,7 @@ class NoneKey extends Key {
|
|||
super({ type: 'unsecured' }, { alg: 'none' })
|
||||
Object.defineProperties(this, {
|
||||
kid: { value: undefined },
|
||||
kty: { value: undefined },
|
||||
thumbprint: { value: undefined },
|
||||
toJWK: { value: undefined },
|
||||
toPEM: { value: undefined }
|
||||
|
|
@ -30,4 +31,4 @@ class NoneKey extends Key {
|
|||
}
|
||||
}
|
||||
|
||||
module.exports = new NoneKey({ type: 'unsecured' }, { alg: 'none' })
|
||||
module.exports = new NoneKey()
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ const { deprecate, inspect } = require('util')
|
|||
const isObject = require('../help/is_object')
|
||||
const { generate, generateSync } = require('../jwk/generate')
|
||||
const { USES_MAPPING } = require('../help/consts')
|
||||
const { None, isKey, asKey: importKey } = require('../jwk')
|
||||
const { isKey, asKey: importKey } = require('../jwk')
|
||||
|
||||
const keyscore = (key, { alg, use, ops }) => {
|
||||
let score = 0
|
||||
|
|
@ -35,7 +35,7 @@ class KeyStore {
|
|||
return acc
|
||||
}, [])
|
||||
}
|
||||
if (keys.some(k => !isKey(k) || k === None)) {
|
||||
if (keys.some(k => !isKey(k) || !k.kty)) {
|
||||
throw new TypeError('all keys must be instances of a key instantiated by JWK.asKey')
|
||||
}
|
||||
|
||||
|
|
@ -107,7 +107,7 @@ class KeyStore {
|
|||
}
|
||||
|
||||
add (key) {
|
||||
if (!isKey(key) || key === None) {
|
||||
if (!isKey(key) || !key.kty) {
|
||||
throw new TypeError('key must be an instance of a key instantiated by JWK.asKey')
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,10 +1,14 @@
|
|||
const { EOL } = require('os')
|
||||
|
||||
const base64url = require('../help/base64url')
|
||||
const isDisjoint = require('../help/is_disjoint')
|
||||
const isObject = require('../help/is_object')
|
||||
let validateCrit = require('../help/validate_crit')
|
||||
const getKey = require('../help/get_key')
|
||||
const { KeyStore } = require('../jwks')
|
||||
const errors = require('../errors')
|
||||
const { check, verify } = require('../jwa')
|
||||
const JWK = require('../jwk')
|
||||
|
||||
const { detect: resolveSerialization } = require('./serializers')
|
||||
|
||||
|
|
@ -125,6 +129,24 @@ const jwsVerify = (skipDisjointCheck, serialization, jws, key, { crit = [], comp
|
|||
}
|
||||
}
|
||||
|
||||
if (key === JWK.EmbeddedJWK) {
|
||||
if (!isObject(combinedHeader.jwk)) {
|
||||
throw new errors.JWSInvalid('JWS Header Parameter "jwk" must be a JSON object')
|
||||
}
|
||||
key = JWK.asKey(combinedHeader.jwk)
|
||||
if (key.type !== 'public') {
|
||||
throw new errors.JWSInvalid('JWS Header Parameter "jwk" must be a public key')
|
||||
}
|
||||
} else if (key === JWK.EmbeddedX5C) {
|
||||
if (!Array.isArray(combinedHeader.x5c) || !combinedHeader.x5c.length || combinedHeader.x5c.some(c => typeof c !== 'string' || !c)) {
|
||||
throw new errors.JWSInvalid('JWS Header Parameter "x5c" must be a JSON array of certificate value strings')
|
||||
}
|
||||
key = JWK.asKey(
|
||||
`-----BEGIN CERTIFICATE-----${EOL}${(combinedHeader.x5c[0].match(/.{1,64}/g) || []).join(EOL)}${EOL}-----END CERTIFICATE-----`,
|
||||
{ x5c: combinedHeader.x5c }
|
||||
)
|
||||
}
|
||||
|
||||
check(key, 'verify', alg)
|
||||
|
||||
const toBeVerified = Buffer.concat([
|
||||
|
|
|
|||
189
test/jwk/embedded.test.js
Normal file
189
test/jwk/embedded.test.js
Normal file
|
|
@ -0,0 +1,189 @@
|
|||
const test = require('ava')
|
||||
|
||||
const { errors, JWK, JWS, JWT, JWKS } = require('../..')
|
||||
const { keyObjectSupported } = require('../../lib/help/runtime_support')
|
||||
|
||||
test('JWK.EmbeddedJWK', t => {
|
||||
const k = JWK.EmbeddedJWK
|
||||
t.truthy(k)
|
||||
t.true(JWK.isKey(k))
|
||||
t.is(k.kty, undefined)
|
||||
for (const prop of ['kid', 'kty', 'thumbprint', 'toJWK', 'toPEM']) {
|
||||
k[prop] = 'foo'
|
||||
t.is(k[prop], undefined)
|
||||
}
|
||||
t.deepEqual([...k.algorithms()], [])
|
||||
k.type = 'foo'
|
||||
t.is(k.type, 'embedded')
|
||||
t.throws(() => new JWKS.KeyStore(k), { instanceOf: TypeError })
|
||||
const ks = new JWKS.KeyStore()
|
||||
t.throws(() => ks.add(k), { instanceOf: TypeError })
|
||||
})
|
||||
|
||||
test('JWK.EmbeddedJWK JWS.verify pass', async t => {
|
||||
const key = await JWK.generate('EC', 'P-256')
|
||||
const { kid, ...jwk } = key.toJWK()
|
||||
const jws = JWS.sign('foo', key, { jwk })
|
||||
t.notThrows(() => JWS.verify(jws, JWK.EmbeddedJWK))
|
||||
t.throws(
|
||||
() => JWS.verify(jws, JWK.EmbeddedJWK, { algorithms: ['EdDSA'] }),
|
||||
{ instanceOf: errors.JOSEAlgNotWhitelisted, code: 'ERR_JOSE_ALG_NOT_WHITELISTED', message: 'alg not whitelisted' }
|
||||
)
|
||||
const { key: embedded } = JWS.verify(jws, JWK.EmbeddedJWK, { complete: true })
|
||||
t.false(key === embedded)
|
||||
t.deepEqual(key.toJWK(), embedded.toJWK())
|
||||
})
|
||||
|
||||
test('JWK.EmbeddedJWK JWT.verify pass', async t => {
|
||||
const key = await JWK.generate('EC', 'P-256')
|
||||
const { kid, ...jwk } = key.toJWK()
|
||||
const jws = JWT.sign({}, key, { header: { jwk } })
|
||||
t.notThrows(() => JWT.verify(jws, JWK.EmbeddedJWK))
|
||||
t.throws(
|
||||
() => JWT.verify(jws, JWK.EmbeddedJWK, { algorithms: ['EdDSA'] }),
|
||||
{ instanceOf: errors.JOSEAlgNotWhitelisted, code: 'ERR_JOSE_ALG_NOT_WHITELISTED', message: 'alg not whitelisted' }
|
||||
)
|
||||
const { key: embedded } = JWT.verify(jws, JWK.EmbeddedJWK, { complete: true })
|
||||
t.false(key === embedded)
|
||||
t.deepEqual(key.toJWK(), embedded.toJWK())
|
||||
})
|
||||
|
||||
test('JWK.EmbeddedJWK key must be a public key', async t => {
|
||||
const key = await JWK.generate('EC', 'P-256')
|
||||
const { kid, ...jwk } = key.toJWK(true)
|
||||
{
|
||||
const jws = JWS.sign('foo', key, { jwk })
|
||||
t.throws(
|
||||
() => JWS.verify(jws, JWK.EmbeddedJWK),
|
||||
{ instanceOf: errors.JWSInvalid, code: 'ERR_JWS_INVALID', message: 'JWS Header Parameter "jwk" must be a public key' }
|
||||
)
|
||||
}
|
||||
{
|
||||
const jws = JWS.sign('foo', key, { jwk: { kty: 'oct', k: 'foo' } })
|
||||
t.throws(
|
||||
() => JWS.verify(jws, JWK.EmbeddedJWK),
|
||||
{ instanceOf: errors.JWSInvalid, code: 'ERR_JWS_INVALID', message: 'JWS Header Parameter "jwk" must be a public key' }
|
||||
)
|
||||
}
|
||||
{
|
||||
const jws = JWS.sign('foo', key, { jwk: { kty: 'oct' } })
|
||||
t.throws(
|
||||
() => JWS.verify(jws, JWK.EmbeddedJWK),
|
||||
{ instanceOf: errors.JWSInvalid, code: 'ERR_JWS_INVALID', message: 'JWS Header Parameter "jwk" must be a public key' }
|
||||
)
|
||||
}
|
||||
{
|
||||
const jws = JWS.sign('foo', key, { jwk: { kty: 'foo' } })
|
||||
t.throws(
|
||||
() => JWS.verify(jws, JWK.EmbeddedJWK),
|
||||
{ instanceOf: errors.JOSENotSupported, code: 'ERR_JOSE_NOT_SUPPORTED', message: 'unsupported key type: foo' }
|
||||
)
|
||||
}
|
||||
{
|
||||
const invalidEc = key.toJWK()
|
||||
delete invalidEc.y
|
||||
const jws = JWS.sign('foo', key, { jwk: invalidEc })
|
||||
t.throws(
|
||||
() => JWS.verify(jws, JWK.EmbeddedJWK),
|
||||
{ instanceOf: errors.JWKImportFailed, code: 'ERR_JWK_IMPORT_FAILED', message: 'key import failed' }
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
test('JWK.EmbeddedJWK key invalid inputs', async t => {
|
||||
const key = await JWK.generate('EC', 'P-256')
|
||||
for (const jwk of [undefined, '', null, false, true, 1, 3.14, 'pi']) {
|
||||
const jws = JWS.sign('foo', key, { jwk })
|
||||
t.throws(
|
||||
() => JWS.verify(jws, JWK.EmbeddedJWK),
|
||||
{ instanceOf: errors.JWSInvalid, code: 'ERR_JWS_INVALID', message: 'JWS Header Parameter "jwk" must be a JSON object' }
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
if (keyObjectSupported) {
|
||||
test('JWK.EmbeddedX5C', t => {
|
||||
const k = JWK.EmbeddedX5C
|
||||
t.truthy(k)
|
||||
t.true(JWK.isKey(k))
|
||||
t.is(k.kty, undefined)
|
||||
for (const prop of ['kid', 'kty', 'thumbprint', 'toJWK', 'toPEM']) {
|
||||
k[prop] = 'foo'
|
||||
t.is(k[prop], undefined)
|
||||
}
|
||||
t.deepEqual([...k.algorithms()], [])
|
||||
k.type = 'foo'
|
||||
t.is(k.type, 'embedded')
|
||||
t.throws(() => new JWKS.KeyStore(k), { instanceOf: TypeError })
|
||||
const ks = new JWKS.KeyStore()
|
||||
t.throws(() => ks.add(k), { instanceOf: TypeError })
|
||||
})
|
||||
|
||||
const rsa = {
|
||||
e: 'AQAB',
|
||||
n: 'u4kSwZDMd93b1fvd6CXUfHa-rF0DBd03tCCpWN31giKCskP09c7VigwkyHu34X__1rA7CNMaSrXQn4ChkhulSxzQyojBc3t06AjyKe_Nzpd72zaGFjaLfN-C2U5QmmaXn_2dOiQTH3aTaHDA5I8zd7ZEwrln9G6DD9KtbAcal-RWN_XT-dD-hHUSH4X4iHIvVC1El6lOtu9yjpmQtAvU3mpvxKK6AUGEA9wCWmIEcpfosOCpgHiwVeuPwJwAmuHRFA-h5N4wWw1KQuW66ocgeTzwKZ33DuMWeLap3AEeDVErInAwPPjzLSj3i3DvtveGlGZQH10wAZMAQrcUhHS06Q',
|
||||
d: 'e_PUztXbH5snc58fBBMFCCMgUiLEHbsi108DP7atUA9pXVRnc5T7NVxjb5O-bTDCM--VhXaqmRjlRJerszvMnAH2yvdrDd5a3gcTsL5MxLEBb1nxdHsm5SmCfgkyY2tN6rShmE1BynkAY3arOCaieQyjFCWh3UCyJeI1OALWA_AJP1kOrmrM9Cpd3FYNkKN163_D7Nv5g2PuMMO1T6Zx8xdgC1C6OxXfmVpvNtOI4pnkODRcQhTspHJLzGTtp2yYPRHCsBhF7i3XsbQ4D8a8t_vZ3yLt-3ZkJnHnRQeCeyBlALUcnFFo43CA96ohOk6NhjYNbi8uJbZyxWVXllwV5Q',
|
||||
p: '5kZFOcFl6jrZ7XQWDoHipOqHyACKRCdk1V-JBi8_iOZYpDXiTWCAdl_XAqMLI8vrcceOi2TLbdFBASVQVWwvOPUkpZ5BvNI6Zpzvv7PRjQlhBBogC06zwCcMi0f1RVZrvtt1_URwMgSuc7OaJFEIcIllVaXKa2KhmBBlQnDVSXc',
|
||||
q: '0Hx9Ccd1iaESKbfv6Wsx1emCaKcIBpxYe41jNWbmtGNixaZHyCr0_FFAOwzbfe_W_kuX6Tk5rmDDivG92Rm0QcRas70CvXP8m64R3Z3qV7mKY8QZpwooPFoOaZfjl0--w_HEdtf-epm43kCIXtqgaIj4aUFEEdEe4AVnTyZpjJ8',
|
||||
dp: '1zYhiKLpXwn1lukBnDlj2wGeORvYHW473PdWlsMdvBKcEYySngJszTUxO7Opu6DfwQziegCP52jEOg_njo53a-Igh_DqO1C3aCOQJjgmxotXcoAAJtE9SX61SI7N-imUtWFiWnvV58lcSaI3k21wV8zxOiSik84wfHAGUxwlGm0',
|
||||
dq: 'Q9_DdWOSSHQ_zYUsffmAB_w1kIyQeFZ-F_s3yTLu-NtCVMaFqA0UJPDu0EqnSqDChZdmpW8T8ElgX-PDwuIzZRXf0ZQ_SB5yptxMxLGckWK-QyycjV0pLDzFZGsmlSRJHtGe_HHlT1Sscu7fdsIGZwHwnZO57XL_cj9QGtyOkFE',
|
||||
qi: 'qhNTCBRZi6zC-2nyVuleB5DAfzza_HSqa_FvSZpzbxv_cIgI52FIB2Vn6u6c8M-n0PEVpCOwVOD2VuRqWhidfOJsFbGLyGtEg9ZRE-bQoOPvRIeUqOt5jTe3bqboG84vNcmw5m0zbCw8upUmu2LK0NIFDxrjognJEwIlMoAgALE',
|
||||
kty: 'RSA'
|
||||
}
|
||||
const x5c = ['MIIDmjCCAoKgAwIBAgIJfEZch1k3018JMA0GCSqGSIb3DQEBBQUAMGkxFDASBgNVBAMTC2V4YW1wbGUub3JnMQswCQYDVQQGEwJVUzERMA8GA1UECBMIVmlyZ2luaWExEzARBgNVBAcTCkJsYWNrc2J1cmcxDTALBgNVBAoTBFRlc3QxDTALBgNVBAsTBFRlc3QwHhcNMjAwNTA1MTI0MTQ2WhcNMjEwNTA1MTI0MTQ2WjBpMRQwEgYDVQQDEwtleGFtcGxlLm9yZzELMAkGA1UEBhMCVVMxETAPBgNVBAgTCFZpcmdpbmlhMRMwEQYDVQQHEwpCbGFja3NidXJnMQ0wCwYDVQQKEwRUZXN0MQ0wCwYDVQQLEwRUZXN0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAu4kSwZDMd93b1fvd6CXUfHa+rF0DBd03tCCpWN31giKCskP09c7VigwkyHu34X//1rA7CNMaSrXQn4ChkhulSxzQyojBc3t06AjyKe/Nzpd72zaGFjaLfN+C2U5QmmaXn/2dOiQTH3aTaHDA5I8zd7ZEwrln9G6DD9KtbAcal+RWN/XT+dD+hHUSH4X4iHIvVC1El6lOtu9yjpmQtAvU3mpvxKK6AUGEA9wCWmIEcpfosOCpgHiwVeuPwJwAmuHRFA+h5N4wWw1KQuW66ocgeTzwKZ33DuMWeLap3AEeDVErInAwPPjzLSj3i3DvtveGlGZQH10wAZMAQrcUhHS06QIDAQABo0UwQzAMBgNVHRMEBTADAQH/MAsGA1UdDwQEAwIC9DAmBgNVHREEHzAdhhtodHRwOi8vZXhhbXBsZS5vcmcvd2ViaWQjbWUwDQYJKoZIhvcNAQEFBQADggEBAA3oxXuMEXdi/5ndPuoJYe1eK3KHIFRajZOrMxSz65ErlkiIt9K0weoYSywufuIdP71kc+S/x/NUpXzaZ1XvUDK3IvwKxf/dnLgtDJRABCWHBQ+81YvtxJVzDSq9grVdGby7IRzuDaayvEo0YFc5xh0r8Jc/Dlaz4ZUhXLxpKZeyT47aPyk6Ys+d/2vEFDhOwyipqKI+xtfrdPaBi9FXk/QA+Th16DKx9Uxau+sXbAVSG3YZtUUkadi1sP/oKqQcJ6VTSGt+8Yg+DJwY8ZnX+QgVyEtqpTwfJNjt3G4qOtAr8LpNTN+r2BG4XYN3bck9SVfjDky5dXJo3NY2pT9AO58=']
|
||||
|
||||
test('JWK.EmbeddedX5C JWS.verify pass', async t => {
|
||||
const key = JWK.asKey({ ...rsa, x5c })
|
||||
const jws = JWS.sign('foo', key, { x5c })
|
||||
t.notThrows(() => JWS.verify(jws, JWK.EmbeddedX5C))
|
||||
t.throws(
|
||||
() => JWS.verify(jws, JWK.EmbeddedX5C, { algorithms: ['EdDSA'] }),
|
||||
{ instanceOf: errors.JOSEAlgNotWhitelisted, code: 'ERR_JOSE_ALG_NOT_WHITELISTED', message: 'alg not whitelisted' }
|
||||
)
|
||||
const { key: embedded } = JWS.verify(jws, JWK.EmbeddedX5C, { complete: true })
|
||||
t.false(key === embedded)
|
||||
t.deepEqual(key.toJWK(), embedded.toJWK())
|
||||
})
|
||||
|
||||
test('JWK.EmbeddedX5C JWT.verify pass', async t => {
|
||||
const key = JWK.asKey({ ...rsa, x5c })
|
||||
const jws = JWT.sign({}, key, { header: { x5c } })
|
||||
t.notThrows(() => JWT.verify(jws, JWK.EmbeddedX5C))
|
||||
t.throws(
|
||||
() => JWT.verify(jws, JWK.EmbeddedX5C, { algorithms: ['EdDSA'] }),
|
||||
{ instanceOf: errors.JOSEAlgNotWhitelisted, code: 'ERR_JOSE_ALG_NOT_WHITELISTED', message: 'alg not whitelisted' }
|
||||
)
|
||||
const { key: embedded } = JWT.verify(jws, JWK.EmbeddedX5C, { complete: true })
|
||||
t.false(key === embedded)
|
||||
t.deepEqual(key.toJWK(), embedded.toJWK())
|
||||
})
|
||||
|
||||
test('JWK.EmbeddedJWK key must be a properly formatted cert value', async t => {
|
||||
const key = JWK.asKey({ ...rsa, x5c })
|
||||
const jws = JWS.sign('foo', key, { x5c: [x5c[0].slice(0, 16)] })
|
||||
t.throws(
|
||||
() => JWS.verify(jws, JWK.EmbeddedX5C),
|
||||
{ instanceOf: errors.JWKImportFailed, code: 'ERR_JWK_IMPORT_FAILED', message: 'key import failed' }
|
||||
)
|
||||
})
|
||||
|
||||
test('JWK.EmbeddedX5C key invalid inputs', async t => {
|
||||
const key = JWK.asKey({ ...rsa, x5c })
|
||||
for (const x5c of [undefined, '', null, false, true, 1, 3.14, []]) {
|
||||
{
|
||||
const jws = JWS.sign('foo', key, { x5c })
|
||||
t.throws(
|
||||
() => JWS.verify(jws, JWK.EmbeddedX5C),
|
||||
{ instanceOf: errors.JWSInvalid, code: 'ERR_JWS_INVALID', message: 'JWS Header Parameter "x5c" must be a JSON array of certificate value strings' }
|
||||
)
|
||||
}
|
||||
{
|
||||
const jws = JWS.sign('foo', key, { x5c: [x5c] })
|
||||
t.throws(
|
||||
() => JWS.verify(jws, JWK.EmbeddedX5C),
|
||||
{ instanceOf: errors.JWSInvalid, code: 'ERR_JWS_INVALID', message: 'JWS Header Parameter "x5c" must be a JSON array of certificate value strings' }
|
||||
)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
20
test/jwk/none.test.js
Normal file
20
test/jwk/none.test.js
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
const test = require('ava')
|
||||
|
||||
const { JWK, JWKS } = require('../..')
|
||||
|
||||
test('JWK.None', t => {
|
||||
const k = JWK.None
|
||||
t.truthy(k)
|
||||
t.true(JWK.isKey(k))
|
||||
t.is(k.kty, undefined)
|
||||
for (const prop of ['kid', 'kty', 'thumbprint', 'toJWK', 'toPEM']) {
|
||||
k[prop] = 'foo'
|
||||
t.is(k[prop], undefined)
|
||||
}
|
||||
t.deepEqual([...k.algorithms()], ['none'])
|
||||
k.type = 'foo'
|
||||
t.is(k.type, 'unsecured')
|
||||
t.throws(() => new JWKS.KeyStore(k), { instanceOf: TypeError })
|
||||
const ks = new JWKS.KeyStore()
|
||||
t.throws(() => ks.add(k), { instanceOf: TypeError })
|
||||
})
|
||||
41
types/index.d.ts
vendored
41
types/index.d.ts
vendored
|
|
@ -27,6 +27,9 @@ 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 EmbeddedJWK = JWK.EmbeddedJWK;
|
||||
export type EmbeddedX5C = JWK.EmbeddedX5C;
|
||||
export type EmbeddedVerifyKeys = EmbeddedJWK | EmbeddedX5C;
|
||||
export type ProduceKeyInputWithNone = ProduceKeyInput | NoneKey;
|
||||
export type ConsumeKeyInputWithNone = ConsumeKeyInput | NoneKey;
|
||||
|
||||
|
|
@ -216,6 +219,20 @@ export namespace JWK {
|
|||
|
||||
const None: NoneKey;
|
||||
|
||||
interface EmbeddedJWK {
|
||||
readonly type: 'embedded';
|
||||
algorithms(operation?: keyOperation): Set<string>;
|
||||
}
|
||||
|
||||
const EmbeddedJWK: EmbeddedJWK;
|
||||
|
||||
interface EmbeddedX5C {
|
||||
readonly type: 'embedded';
|
||||
algorithms(operation?: keyOperation): Set<string>;
|
||||
}
|
||||
|
||||
const EmbeddedX5C: EmbeddedX5C;
|
||||
|
||||
function isKey(object: any): boolean;
|
||||
|
||||
function asKey(key: KeyObject | KeyInput, parameters?: KeyParameters): RSAKey | ECKey | OKPKey | OctKey;
|
||||
|
|
@ -340,10 +357,10 @@ export namespace JWS {
|
|||
header?: object;
|
||||
}
|
||||
|
||||
function verify(jws: string | FlattenedJWS | GeneralJWS, key: ConsumeKeyInputWithNone, options?: VerifyOptions): string | object;
|
||||
function verify(jws: string | FlattenedJWS | GeneralJWS, key: ConsumeKeyInputWithNone, options?: VerifyOptions<false, false>): Buffer;
|
||||
function verify(jws: string | FlattenedJWS | GeneralJWS, key: ConsumeKeyInput, options?: VerifyOptions<true>): completeVerification<string | object, JWK.Key>;
|
||||
function verify(jws: string | FlattenedJWS | GeneralJWS, key: ConsumeKeyInput, options?: VerifyOptions<true, false>): completeVerification<Buffer, JWK.Key>;
|
||||
function verify(jws: string | FlattenedJWS | GeneralJWS, key: ConsumeKeyInputWithNone | EmbeddedVerifyKeys, options?: VerifyOptions): string | object;
|
||||
function verify(jws: string | FlattenedJWS | GeneralJWS, key: ConsumeKeyInputWithNone | EmbeddedVerifyKeys, options?: VerifyOptions<false, false>): Buffer;
|
||||
function verify(jws: string | FlattenedJWS | GeneralJWS, key: ConsumeKeyInput | EmbeddedVerifyKeys, options?: VerifyOptions<true>): completeVerification<string | object, JWK.Key>;
|
||||
function verify(jws: string | FlattenedJWS | GeneralJWS, key: ConsumeKeyInput | EmbeddedVerifyKeys, options?: VerifyOptions<true, false>): completeVerification<Buffer, JWK.Key>;
|
||||
function verify(jws: string | FlattenedJWS | GeneralJWS, key: NoneKey, options?: VerifyOptions<true>): completeVerification<string | object, NoneKey>;
|
||||
function verify(jws: string | FlattenedJWS | GeneralJWS, key: NoneKey, options?: VerifyOptions<true, false>): completeVerification<Buffer, NoneKey>;
|
||||
}
|
||||
|
|
@ -440,8 +457,8 @@ export namespace JWT {
|
|||
profile?: JWTProfiles;
|
||||
}
|
||||
|
||||
function verify(jwt: string, key: ConsumeKeyInputWithNone, options?: VerifyOptions<false>): object;
|
||||
function verify(jwt: string, key: ConsumeKeyInput, options?: VerifyOptions<true>): completeResult;
|
||||
function verify(jwt: string, key: ConsumeKeyInputWithNone | EmbeddedVerifyKeys, options?: VerifyOptions<false>): object;
|
||||
function verify(jwt: string, key: ConsumeKeyInput | EmbeddedVerifyKeys, options?: VerifyOptions<true>): completeResult;
|
||||
function verify(jwt: string, key: NoneKey, options?: VerifyOptions<true>): completeResult<NoneKey>;
|
||||
|
||||
interface SignOptions {
|
||||
|
|
@ -468,20 +485,20 @@ export namespace JWT {
|
|||
}
|
||||
|
||||
namespace IdToken {
|
||||
function verify(jwt: string, key: ConsumeKeyInputWithNone, options: VerifyOptions<false> & VerifyProfileOptions<'id_token'>): object;
|
||||
function verify(jwt: string, key: ConsumeKeyInput, options: VerifyOptions<true> & VerifyProfileOptions<'id_token'>): completeResult;
|
||||
function verify(jwt: string, key: ConsumeKeyInputWithNone | EmbeddedVerifyKeys, options: VerifyOptions<false> & VerifyProfileOptions<'id_token'>): object;
|
||||
function verify(jwt: string, key: ConsumeKeyInput | EmbeddedVerifyKeys, options: VerifyOptions<true> & VerifyProfileOptions<'id_token'>): completeResult;
|
||||
function verify(jwt: string, key: NoneKey, options: VerifyOptions<true> & VerifyProfileOptions<'id_token'>): completeResult<NoneKey>;
|
||||
}
|
||||
|
||||
namespace LogoutToken {
|
||||
function verify(jwt: string, key: ConsumeKeyInputWithNone, options: VerifyOptions<false> & VerifyProfileOptions<'logout_token'>): object;
|
||||
function verify(jwt: string, key: ConsumeKeyInput, options: VerifyOptions<true> & VerifyProfileOptions<'logout_token'>): completeResult;
|
||||
function verify(jwt: string, key: ConsumeKeyInputWithNone | EmbeddedVerifyKeys, options: VerifyOptions<false> & VerifyProfileOptions<'logout_token'>): object;
|
||||
function verify(jwt: string, key: ConsumeKeyInput | EmbeddedVerifyKeys, options: VerifyOptions<true> & VerifyProfileOptions<'logout_token'>): completeResult;
|
||||
function verify(jwt: string, key: NoneKey, options: VerifyOptions<true> & VerifyProfileOptions<'logout_token'>): completeResult<NoneKey>;
|
||||
}
|
||||
|
||||
namespace AccessToken {
|
||||
function verify(jwt: string, key: ConsumeKeyInputWithNone, options: VerifyOptions<false> & VerifyProfileOptions<'at+JWT'>): object;
|
||||
function verify(jwt: string, key: ConsumeKeyInput, options: VerifyOptions<true> & VerifyProfileOptions<'at+JWT'>): completeResult;
|
||||
function verify(jwt: string, key: ConsumeKeyInputWithNone | EmbeddedVerifyKeys, options: VerifyOptions<false> & VerifyProfileOptions<'at+JWT'>): object;
|
||||
function verify(jwt: string, key: ConsumeKeyInput | EmbeddedVerifyKeys, options: VerifyOptions<true> & VerifyProfileOptions<'at+JWT'>): completeResult;
|
||||
function verify(jwt: string, key: NoneKey, options: VerifyOptions<true> & VerifyProfileOptions<'at+JWT'>): completeResult<NoneKey>;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue