UNPKG

4.63 kBJavaScriptView Raw
1const { deprecate, inspect } = require('util')
2
3const isObject = require('../help/is_object')
4const { generate, generateSync } = require('../jwk/generate')
5const { USES_MAPPING } = require('../help/consts')
6const { isKey, asKey: importKey } = require('../jwk')
7
8const keyscore = (key, { alg, use, ops }) => {
9 let score = 0
10
11 if (alg && key.alg) {
12 score++
13 }
14
15 if (use && key.use) {
16 score++
17 }
18
19 if (ops && key.key_ops) {
20 score++
21 }
22
23 return score
24}
25
26class KeyStore {
27 constructor (...keys) {
28 while (keys.some(Array.isArray)) {
29 keys = keys.flat ? keys.flat() : keys.reduce((acc, val) => {
30 if (Array.isArray(val)) {
31 return [...acc, ...val]
32 }
33
34 acc.push(val)
35 return acc
36 }, [])
37 }
38 if (keys.some(k => !isKey(k) || !k.kty)) {
39 throw new TypeError('all keys must be instances of a key instantiated by JWK.asKey')
40 }
41
42 this._keys = new Set(keys)
43 }
44
45 all ({ alg, kid, thumbprint, use, kty, key_ops: ops, x5t, 'x5t#S256': x5t256, crv } = {}) {
46 if (ops !== undefined && (!Array.isArray(ops) || !ops.length || ops.some(x => typeof x !== 'string'))) {
47 throw new TypeError('`key_ops` must be a non-empty array of strings')
48 }
49
50 const search = { alg, use, ops }
51 return [...this._keys]
52 .filter((key) => {
53 let candidate = true
54
55 if (candidate && kid !== undefined && key.kid !== kid) {
56 candidate = false
57 }
58
59 if (candidate && thumbprint !== undefined && key.thumbprint !== thumbprint) {
60 candidate = false
61 }
62
63 if (candidate && x5t !== undefined && key.x5t !== x5t) {
64 candidate = false
65 }
66
67 if (candidate && x5t256 !== undefined && key['x5t#S256'] !== x5t256) {
68 candidate = false
69 }
70
71 if (candidate && kty !== undefined && key.kty !== kty) {
72 candidate = false
73 }
74
75 if (candidate && crv !== undefined && (key.crv !== crv)) {
76 candidate = false
77 }
78
79 if (alg !== undefined && !key.algorithms().has(alg)) {
80 candidate = false
81 }
82
83 if (candidate && use !== undefined && (key.use !== undefined && key.use !== use)) {
84 candidate = false
85 }
86
87 // TODO:
88 if (candidate && ops !== undefined && (key.key_ops !== undefined || key.use !== undefined)) {
89 let keyOps
90 if (key.key_ops) {
91 keyOps = new Set(key.key_ops)
92 } else {
93 keyOps = USES_MAPPING[key.use]
94 }
95 if (ops.some(x => !keyOps.has(x))) {
96 candidate = false
97 }
98 }
99
100 return candidate
101 })
102 .sort((first, second) => keyscore(second, search) - keyscore(first, search))
103 }
104
105 get (...args) {
106 return this.all(...args)[0]
107 }
108
109 add (key) {
110 if (!isKey(key) || !key.kty) {
111 throw new TypeError('key must be an instance of a key instantiated by JWK.asKey')
112 }
113
114 this._keys.add(key)
115 }
116
117 remove (key) {
118 if (!isKey(key)) {
119 throw new TypeError('key must be an instance of a key instantiated by JWK.asKey')
120 }
121
122 this._keys.delete(key)
123 }
124
125 toJWKS (priv = false) {
126 return {
127 keys: [...this._keys.values()].map(
128 key => key.toJWK(priv && (key.private || (key.secret && key.k)))
129 )
130 }
131 }
132
133 async generate (...args) {
134 this._keys.add(await generate(...args))
135 }
136
137 generateSync (...args) {
138 this._keys.add(generateSync(...args))
139 }
140
141 get size () {
142 return this._keys.size
143 }
144
145 /* c8 ignore next 8 */
146 [inspect.custom] () {
147 return `${this.constructor.name} ${inspect(this.toJWKS(false), {
148 depth: Infinity,
149 colors: process.stdout.isTTY,
150 compact: false,
151 sorted: true
152 })}`
153 }
154
155 * [Symbol.iterator] () {
156 for (const key of this._keys) {
157 yield key
158 }
159 }
160}
161
162function asKeyStore (jwks, { ignoreErrors = false, calculateMissingRSAPrimes = false } = {}) {
163 if (!isObject(jwks) || !Array.isArray(jwks.keys) || jwks.keys.some(k => !isObject(k) || !('kty' in k))) {
164 throw new TypeError('jwks must be a JSON Web Key Set formatted object')
165 }
166
167 const keys = jwks.keys.map((jwk) => {
168 try {
169 return importKey(jwk, { calculateMissingRSAPrimes })
170 } catch (err) {
171 if (!ignoreErrors) {
172 throw err
173 }
174 }
175 }).filter(Boolean)
176
177 return new KeyStore(...keys)
178}
179
180Object.defineProperty(KeyStore, 'fromJWKS', {
181 value: deprecate(jwks => asKeyStore(jwks, { calculateMissingRSAPrimes: true }), 'JWKS.KeyStore.fromJWKS() is deprecated, use JWKS.asKeyStore() instead'),
182 enumerable: false
183})
184
185module.exports = { KeyStore, asKeyStore }