mirror of
https://github.com/danbulant/jose
synced 2026-05-25 13:01:49 +00:00
198 lines
4.6 KiB
JavaScript
198 lines
4.6 KiB
JavaScript
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 keyscore = (key, { alg, kid, use, ops, x5t, x5t256 }) => {
|
|
let score = 0
|
|
|
|
if (alg && key.alg) {
|
|
score++
|
|
}
|
|
|
|
if (kid && key.kid) {
|
|
score++
|
|
}
|
|
|
|
if (x5t && key.x5t) {
|
|
score++
|
|
}
|
|
|
|
if (x5t256 && key['x5t#S256']) {
|
|
score++
|
|
}
|
|
|
|
if (use && key.use) {
|
|
score++
|
|
}
|
|
|
|
if (ops && key.key_ops) {
|
|
score++
|
|
}
|
|
|
|
return score
|
|
}
|
|
|
|
const map = new WeakMap()
|
|
|
|
const i = (ctx) => {
|
|
if (!map.has(ctx)) {
|
|
map.set(ctx, {})
|
|
}
|
|
return map.get(ctx)
|
|
}
|
|
|
|
class KeyStore {
|
|
constructor (...keys) {
|
|
while (keys.some(Array.isArray)) {
|
|
keys = keys.flat ? keys.flat() : keys.reduce((acc, val) => {
|
|
if (Array.isArray(val)) {
|
|
return [...acc, ...val]
|
|
}
|
|
|
|
acc.push(val)
|
|
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')
|
|
}
|
|
|
|
i(this).keys = new Set(keys)
|
|
}
|
|
|
|
all ({ alg, kid, use, kty, key_ops: ops, x5t, 'x5t#S256': x5t256 } = {}) {
|
|
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')
|
|
}
|
|
|
|
const search = { alg, kid, use, ops, x5t, x5t256 }
|
|
return [...i(this).keys]
|
|
.filter((key) => {
|
|
let candidate = true
|
|
|
|
if (alg !== undefined && !key.algorithms().has(alg)) {
|
|
candidate = false
|
|
}
|
|
|
|
if (candidate && kid !== undefined && key.kid !== kid) {
|
|
candidate = false
|
|
}
|
|
|
|
if (candidate && x5t !== undefined && key.x5t !== x5t) {
|
|
candidate = false
|
|
}
|
|
|
|
if (candidate && x5t256 !== undefined && key['x5t#S256'] !== x5t256) {
|
|
candidate = false
|
|
}
|
|
|
|
if (candidate && kty !== undefined && key.kty !== kty) {
|
|
candidate = false
|
|
}
|
|
|
|
if (candidate && use !== undefined && (key.use !== undefined && key.use !== use)) {
|
|
candidate = false
|
|
}
|
|
|
|
if (candidate && ops !== undefined && (key.key_ops !== undefined || key.use !== undefined)) {
|
|
let keyOps
|
|
if (key.key_ops) {
|
|
keyOps = new Set(key.key_ops)
|
|
} else {
|
|
keyOps = USES_MAPPING[key.use]
|
|
}
|
|
if (ops.some(x => !keyOps.has(x))) {
|
|
candidate = false
|
|
}
|
|
}
|
|
|
|
return candidate
|
|
})
|
|
.sort((first, second) => keyscore(second, search) - keyscore(first, search))
|
|
}
|
|
|
|
get (...args) {
|
|
return this.all(...args)[0]
|
|
}
|
|
|
|
add (key) {
|
|
if (!(key instanceof Key)) {
|
|
throw new TypeError('key must be an instance of a key instantiated by JWK.asKey')
|
|
}
|
|
|
|
i(this).keys.add(key)
|
|
}
|
|
|
|
remove (key) {
|
|
if (!(key instanceof Key)) {
|
|
throw new TypeError('key must be an instance of a key instantiated by JWK.asKey')
|
|
}
|
|
|
|
i(this).keys.delete(key)
|
|
}
|
|
|
|
toJWKS (priv = false) {
|
|
return {
|
|
keys: [...i(this).keys.values()].map(
|
|
key => key.toJWK(priv && (key.private || (key.secret && key.k)))
|
|
)
|
|
}
|
|
}
|
|
|
|
async generate (...args) {
|
|
i(this).keys.add(await generate(...args))
|
|
}
|
|
|
|
generateSync (...args) {
|
|
i(this).keys.add(generateSync(...args))
|
|
}
|
|
|
|
get size () {
|
|
return i(this).keys.size
|
|
}
|
|
|
|
/* c8 ignore next 8 */
|
|
[inspect.custom] () {
|
|
return `${this.constructor.name} ${inspect(this.toJWKS(false), {
|
|
depth: Infinity,
|
|
colors: process.stdout.isTTY,
|
|
compact: false,
|
|
sorted: true
|
|
})}`
|
|
}
|
|
|
|
* [Symbol.iterator] () {
|
|
for (const key of i(this).keys) {
|
|
yield key
|
|
}
|
|
}
|
|
}
|
|
|
|
function asKeyStore (jwks, { ignoreErrors = false, calculateMissingRSAPrimes = false } = {}) {
|
|
if (!isObject(jwks) || !Array.isArray(jwks.keys) || jwks.keys.some(k => !isObject(k) || !('kty' in k))) {
|
|
throw new TypeError('jwks must be a JSON Web Key Set formatted object')
|
|
}
|
|
|
|
const keys = jwks.keys.map((jwk) => {
|
|
try {
|
|
return importKey(jwk, { calculateMissingRSAPrimes })
|
|
} catch (err) {
|
|
if (!ignoreErrors) {
|
|
throw err
|
|
}
|
|
}
|
|
}).filter(Boolean)
|
|
|
|
return new KeyStore(...keys)
|
|
}
|
|
|
|
Object.defineProperty(KeyStore, 'fromJWKS', {
|
|
value: deprecate(jwks => asKeyStore(jwks, { calculateMissingRSAPrimes: true }), 'JWKS.KeyStore.fromJWKS() is deprecated, use JWKS.asKeyStore() instead'),
|
|
enumerable: false
|
|
})
|
|
|
|
module.exports = { KeyStore, asKeyStore }
|