@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 CHANGED
@@ -1,3 +1,9 @@
1
+ ---
2
+ #### v4.8.3
3
+ ---
4
+
5
+ ...
6
+
1
7
  ---
2
8
  #### v4.8.2
3
9
  ---\n\nVersion alignment...
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 = new Map()
14
- openSSL = new OpenSSL()
15
- config = structuredClone(config.eventflow.certificates)
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
- * Revoke a specific certificate by ID in the database.
78
- * @param {string} id the certificate identifier
79
- * @returns {Promise<boolean>} true if the certificate was revoked, false if it does not exist
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.revokeCertificate(id)
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
- get root()
148
+ async #getRoot()
91
149
  {
92
- return this.#lazyload('EVENTFLOW-ROOT-CA', this.#createRoot.bind(this))
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
- get intermediate()
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
- get leaf()
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.#map.delete(id)
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.perist(id, validity, cert, key, keyPass)
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.root
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.intermediate
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
- locator = new Locator(),
11
- config = locator.config
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.root
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.root
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.intermediate
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.leaf
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 = await manager.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.2",
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.2",
22
- "@superhero/openssl": "4.8.2",
23
- "@superhero/deep": "4.8.2"
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.2",
26
+ "@superhero/audit": "4.8.3",
27
27
  "@superhero/syntax-check": "0.0.2",
28
- "@superhero/eventflow-db": "4.8.2",
29
- "@superhero/locator": "4.8.2"
28
+ "@superhero/eventflow-db": "4.8.3",
29
+ "@superhero/locator": "4.8.3"
30
30
  },
31
31
  "author": {
32
32
  "name": "Erik Landvall",