@superhero/eventflow-certificates 4.8.2 → 4.8.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +6 -0
- package/index.js +75 -18
- package/index.test.js +12 -13
- package/package.json +7 -7
package/CHANGELOG.md
CHANGED
package/index.js
CHANGED
|
@@ -10,9 +10,11 @@ import config from '@superhero/eventflow-certificates/config'
|
|
|
10
10
|
export default class Certificates
|
|
11
11
|
{
|
|
12
12
|
#db
|
|
13
|
-
#map
|
|
14
|
-
openSSL
|
|
15
|
-
config
|
|
13
|
+
#map = new Map()
|
|
14
|
+
openSSL = new OpenSSL()
|
|
15
|
+
config = structuredClone(config.eventflow.certificates)
|
|
16
|
+
#lockRef = 'eventflow:certificates'
|
|
17
|
+
rootUID = 'EVENTFLOW-ROOT-CA'
|
|
16
18
|
|
|
17
19
|
constructor(intermediateUID, leafUID, config, db)
|
|
18
20
|
{
|
|
@@ -48,7 +50,7 @@ export default class Certificates
|
|
|
48
50
|
*
|
|
49
51
|
* @returns {Promise<boolean>} true if the certificate was persisted, false if it already exists
|
|
50
52
|
*/
|
|
51
|
-
perist(id, validity, cert, key, pass)
|
|
53
|
+
#perist(id, validity, cert, key, pass)
|
|
52
54
|
{
|
|
53
55
|
const
|
|
54
56
|
encryptionKey = this.config.CERT_PASS_ENCRYPTION_KEY,
|
|
@@ -74,29 +76,85 @@ export default class Certificates
|
|
|
74
76
|
}
|
|
75
77
|
|
|
76
78
|
/**
|
|
77
|
-
*
|
|
78
|
-
* @param {string} id the certificate identifier
|
|
79
|
-
* @returns {Promise<boolean>} true if
|
|
79
|
+
* Revokes the certificate, and any certificate that depends on it.
|
|
80
|
+
* @param {string} id the certificate identifier (UID), must be one of: `this.rootUID`, `this.intermediateUID` or `this.leafUID`
|
|
81
|
+
* @returns {Promise<boolean>} true if any certificate was revoked, false if none was revoked.
|
|
80
82
|
*/
|
|
81
|
-
revoke(id)
|
|
83
|
+
async revoke(id)
|
|
82
84
|
{
|
|
83
|
-
return this.#db.
|
|
85
|
+
return await this.#db.lock(this.#lockRef, this.#revoke.bind(this, id))
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
async #revoke(id)
|
|
89
|
+
{
|
|
90
|
+
let revoked = false
|
|
91
|
+
|
|
92
|
+
switch(id)
|
|
93
|
+
{
|
|
94
|
+
case this.rootUID:
|
|
95
|
+
{
|
|
96
|
+
this.#map.delete(this.rootUID)
|
|
97
|
+
revoked = await this.#db.revokeCertificate(this.rootUID)
|
|
98
|
+
|
|
99
|
+
// fall through to also revoke the intermediate and leaf certificates, since they depends on the root certificate
|
|
100
|
+
}
|
|
101
|
+
case this.intermediateUID:
|
|
102
|
+
{
|
|
103
|
+
this.#map.delete(this.intermediateUID)
|
|
104
|
+
revoked = await this.#db.revokeCertificate(this.intermediateUID) || revoked
|
|
105
|
+
|
|
106
|
+
// fall through to also revoke the leaf certificate, since it depends on the intermediate certificate
|
|
107
|
+
}
|
|
108
|
+
case this.leafUID:
|
|
109
|
+
{
|
|
110
|
+
this.#map.delete(this.leafUID)
|
|
111
|
+
revoked = await this.#db.revokeCertificate(this.leafUID) || revoked
|
|
112
|
+
|
|
113
|
+
// returns if any of the certificates was revoked...
|
|
114
|
+
return revoked
|
|
115
|
+
}
|
|
116
|
+
default:
|
|
117
|
+
{
|
|
118
|
+
const error = new Error(`can not revoke the provided invalid certificate (${id})`)
|
|
119
|
+
error.code = 'E_EVENTFLOW_CERTIFICATES_INVALID_ID'
|
|
120
|
+
throw error
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Get the certificate chain, which includes the root, intermediate and leaf certificates.
|
|
127
|
+
* @returns {Promise
|
|
128
|
+
* <{
|
|
129
|
+
* root : {cert:string, key:string, pass:string},
|
|
130
|
+
* intermediate : {cert:string, key:string, pass:string},
|
|
131
|
+
* leaf : {cert:string, key:string, pass:string}
|
|
132
|
+
* }>}
|
|
133
|
+
*/
|
|
134
|
+
async getChain()
|
|
135
|
+
{
|
|
136
|
+
return await this.#db.lock(this.#lockRef, async () =>
|
|
137
|
+
({
|
|
138
|
+
root : await this.#getRoot(),
|
|
139
|
+
intermediate : await this.#getIntermediate(),
|
|
140
|
+
leaf : await this.#getLeaf()
|
|
141
|
+
}))
|
|
84
142
|
}
|
|
85
143
|
|
|
86
144
|
/**
|
|
87
145
|
* The root certificate authority (CA).
|
|
88
146
|
* @returns {Promise<{cert:string, key:string, pass:string}>}
|
|
89
147
|
*/
|
|
90
|
-
|
|
148
|
+
async #getRoot()
|
|
91
149
|
{
|
|
92
|
-
return this.#lazyload(
|
|
150
|
+
return this.#lazyload(this.rootUID, this.#createRoot.bind(this))
|
|
93
151
|
}
|
|
94
152
|
|
|
95
153
|
/**
|
|
96
154
|
* The intermediate certificate authority (ICA).
|
|
97
155
|
* @returns {Promise<{cert:string, key:string, pass:string}>}
|
|
98
156
|
*/
|
|
99
|
-
|
|
157
|
+
async #getIntermediate()
|
|
100
158
|
{
|
|
101
159
|
return this.#lazyload(this.intermediateUID, this.#createIntermediate.bind(this))
|
|
102
160
|
}
|
|
@@ -105,7 +163,7 @@ export default class Certificates
|
|
|
105
163
|
* The leaf end-entity certificate.
|
|
106
164
|
* @returns {Promise<{cert:string, key:string, pass:string}>}
|
|
107
165
|
*/
|
|
108
|
-
|
|
166
|
+
async #getLeaf()
|
|
109
167
|
{
|
|
110
168
|
return this.#lazyload(this.leafUID, this.#createLeaf.bind(this))
|
|
111
169
|
}
|
|
@@ -142,8 +200,7 @@ export default class Certificates
|
|
|
142
200
|
if(Date.now() + 6e5 > crt.validity)
|
|
143
201
|
{
|
|
144
202
|
this.log.warn`certificate expired ${id}`
|
|
145
|
-
this.#
|
|
146
|
-
await this.#db.revokeCertificate(id)
|
|
203
|
+
await this.#revoke(id)
|
|
147
204
|
return this.#lazyload(id, factory)
|
|
148
205
|
}
|
|
149
206
|
else
|
|
@@ -176,7 +233,7 @@ export default class Certificates
|
|
|
176
233
|
const
|
|
177
234
|
x509 = new crypto.X509Certificate(certificate.cert),
|
|
178
235
|
validity = x509.validToDate,
|
|
179
|
-
isPersisted = await this
|
|
236
|
+
isPersisted = await this.#perist(id, validity, cert, key, keyPass)
|
|
180
237
|
|
|
181
238
|
// If multiple replicas tries at the same time to creater the certificate,
|
|
182
239
|
// then only the first one to persist is valid, the rest should throw away
|
|
@@ -208,7 +265,7 @@ export default class Certificates
|
|
|
208
265
|
|
|
209
266
|
async #createIntermediate(UID, password)
|
|
210
267
|
{
|
|
211
|
-
const root = await this
|
|
268
|
+
const root = await this.#getRoot()
|
|
212
269
|
return await this.openSSL.intermediate(root,
|
|
213
270
|
{
|
|
214
271
|
days : Number(this.config.CERT_INTERMEDIATE_DAYS),
|
|
@@ -226,7 +283,7 @@ export default class Certificates
|
|
|
226
283
|
|
|
227
284
|
async #createLeaf(UID, password)
|
|
228
285
|
{
|
|
229
|
-
const ica = await this
|
|
286
|
+
const ica = await this.#getIntermediate()
|
|
230
287
|
return await this.openSSL.leaf(ica,
|
|
231
288
|
{
|
|
232
289
|
days : Number(this.config.CERT_LEAF_DAYS),
|
package/index.test.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import Locator from '@superhero/locator'
|
|
2
|
+
import config from '@superhero/eventflow-db/config'
|
|
2
3
|
import Certificates from '@superhero/eventflow-certificates'
|
|
3
4
|
import assert from 'node:assert/strict'
|
|
4
5
|
import { X509Certificate } from 'node:crypto'
|
|
@@ -6,13 +7,14 @@ import { suite, test, before, after } from 'node:test'
|
|
|
6
7
|
|
|
7
8
|
suite('@superhero/eventflow-certificates', async () =>
|
|
8
9
|
{
|
|
9
|
-
const
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
const locator = new Locator()
|
|
11
|
+
|
|
12
|
+
await locator.config.assign(config, '@superhero/eventflow-db')
|
|
12
13
|
|
|
13
|
-
await config.add('@superhero/eventflow-db')
|
|
14
14
|
const db = await locator.lazyload('@superhero/eventflow-db')
|
|
15
15
|
|
|
16
|
+
await db.createTableCertificate()
|
|
17
|
+
|
|
16
18
|
let conf, manager, icaUID = 'INTERMEDIATE-CERT-ID', leafUID = 'LEAF-CERT-ID'
|
|
17
19
|
|
|
18
20
|
before(() =>
|
|
@@ -43,7 +45,7 @@ suite('@superhero/eventflow-certificates', async () =>
|
|
|
43
45
|
|
|
44
46
|
test('Get root certificate', async (sub) =>
|
|
45
47
|
{
|
|
46
|
-
const root = await manager.
|
|
48
|
+
const { root } = await manager.getChain()
|
|
47
49
|
|
|
48
50
|
assert.ok(root.validity > Date.now())
|
|
49
51
|
assert.ok(root.cert)
|
|
@@ -55,13 +57,13 @@ suite('@superhero/eventflow-certificates', async () =>
|
|
|
55
57
|
|
|
56
58
|
await sub.test('Get same root certificate each time lazyloading it', async () =>
|
|
57
59
|
{
|
|
58
|
-
const root2 = await manager.
|
|
60
|
+
const { root: root2 } = await manager.getChain()
|
|
59
61
|
assert.deepEqual(root, root2)
|
|
60
62
|
})
|
|
61
63
|
|
|
62
64
|
await sub.test('Get intermediate certificate', async (sub) =>
|
|
63
65
|
{
|
|
64
|
-
const ica = await manager.
|
|
66
|
+
const { intermediate: ica } = await manager.getChain()
|
|
65
67
|
|
|
66
68
|
assert.ok(ica.validity > Date.now())
|
|
67
69
|
assert.ok(ica.cert)
|
|
@@ -73,7 +75,7 @@ suite('@superhero/eventflow-certificates', async () =>
|
|
|
73
75
|
|
|
74
76
|
await sub.test('Get leaf certificate', async (sub) =>
|
|
75
77
|
{
|
|
76
|
-
const leaf = await manager.
|
|
78
|
+
const { leaf } = await manager.getChain()
|
|
77
79
|
|
|
78
80
|
assert.ok(leaf.validity > Date.now())
|
|
79
81
|
assert.ok(leaf.cert)
|
|
@@ -87,10 +89,7 @@ suite('@superhero/eventflow-certificates', async () =>
|
|
|
87
89
|
{
|
|
88
90
|
manager.clearCache()
|
|
89
91
|
|
|
90
|
-
const
|
|
91
|
-
root2 = await manager.root,
|
|
92
|
-
ica2 = await manager.intermediate,
|
|
93
|
-
leaf2 = await manager.leaf
|
|
92
|
+
const{ root: root2, intermediate: ica2, leaf: leaf2 } = await manager.getChain()
|
|
94
93
|
|
|
95
94
|
assert.deepEqual(root, root2)
|
|
96
95
|
assert.deepEqual(ica, ica2)
|
|
@@ -102,7 +101,7 @@ suite('@superhero/eventflow-certificates', async () =>
|
|
|
102
101
|
await assert.doesNotReject(manager.revoke(leafUID))
|
|
103
102
|
|
|
104
103
|
const
|
|
105
|
-
leaf
|
|
104
|
+
{ leaf } = await manager.getChain(),
|
|
106
105
|
leafX509 = new X509Certificate(leaf.cert)
|
|
107
106
|
|
|
108
107
|
assert.ok(leafX509.checkIssued(icaX509))
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@superhero/eventflow-certificates",
|
|
3
|
-
"version": "4.8.
|
|
3
|
+
"version": "4.8.3",
|
|
4
4
|
"description": "Eventflow certificates is common tls certificates logic used in the eventflow ecosystem.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"eventflow"
|
|
@@ -18,15 +18,15 @@
|
|
|
18
18
|
"test": "syntax-check; node --test --test-reporter=@superhero/audit/reporter --experimental-test-coverage"
|
|
19
19
|
},
|
|
20
20
|
"dependencies": {
|
|
21
|
-
"@superhero/log": "4.8.
|
|
22
|
-
"@superhero/openssl": "4.8.
|
|
23
|
-
"@superhero/deep": "4.8.
|
|
21
|
+
"@superhero/log": "4.8.3",
|
|
22
|
+
"@superhero/openssl": "4.8.3",
|
|
23
|
+
"@superhero/deep": "4.8.3"
|
|
24
24
|
},
|
|
25
25
|
"devDependencies": {
|
|
26
|
-
"@superhero/audit": "4.8.
|
|
26
|
+
"@superhero/audit": "4.8.3",
|
|
27
27
|
"@superhero/syntax-check": "0.0.2",
|
|
28
|
-
"@superhero/eventflow-db": "4.8.
|
|
29
|
-
"@superhero/locator": "4.8.
|
|
28
|
+
"@superhero/eventflow-db": "4.8.3",
|
|
29
|
+
"@superhero/locator": "4.8.3"
|
|
30
30
|
},
|
|
31
31
|
"author": {
|
|
32
32
|
"name": "Erik Landvall",
|