smart-home-engine 1.3.0 → 1.5.0
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/README.md +1 -1
- package/dist/web/assets/{index-SmPJ7Vpm.js → index-76-mgKLK.js} +72 -72
- package/dist/web/assets/{index-BTdLG-cS.css → index-CiYcuYTG.css} +1 -1
- package/dist/web/assets/{tsMode-XvjcqT_Z.js → tsMode-DHTnwPoK.js} +1 -1
- package/dist/web/index.html +2 -2
- package/package.json +1 -1
- package/src/lib/ca.js +209 -4
- package/src/lib/dynsec.js +4 -1
- package/src/web/broker-api.js +71 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{m as O}from"./monaco-langs-BW2J83t5.js";import{t as I}from"./index-
|
|
1
|
+
import{m as O}from"./monaco-langs-BW2J83t5.js";import{t as I}from"./index-76-mgKLK.js";/*!-----------------------------------------------------------------------------
|
|
2
2
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
|
3
3
|
* Version: 0.52.2(404545bded1df6ffa41ea0af4e8ddb219018c6c1)
|
|
4
4
|
* Released under the MIT license
|
package/dist/web/index.html
CHANGED
|
@@ -172,10 +172,10 @@
|
|
|
172
172
|
}
|
|
173
173
|
})();
|
|
174
174
|
</script>
|
|
175
|
-
<script type="module" crossorigin src="/assets/index-
|
|
175
|
+
<script type="module" crossorigin src="/assets/index-76-mgKLK.js"></script>
|
|
176
176
|
<link rel="modulepreload" crossorigin href="/assets/monaco-langs-BW2J83t5.js">
|
|
177
177
|
<link rel="stylesheet" crossorigin href="/assets/monaco-langs-DyX1CsEw.css">
|
|
178
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
178
|
+
<link rel="stylesheet" crossorigin href="/assets/index-CiYcuYTG.css">
|
|
179
179
|
</head>
|
|
180
180
|
<body>
|
|
181
181
|
<div id="app"></div>
|
package/package.json
CHANGED
package/src/lib/ca.js
CHANGED
|
@@ -104,18 +104,116 @@ async function generateCA(config, { cn = 'she-broker-ca', days = 365 } = {}) {
|
|
|
104
104
|
async function getCA(config) {
|
|
105
105
|
const dir = caDir(config);
|
|
106
106
|
const crtPath = path.join(dir, 'ca.crt');
|
|
107
|
+
const chainPath = path.join(dir, 'ca-chain.crt');
|
|
107
108
|
if (!fs.existsSync(crtPath)) return null;
|
|
108
109
|
try {
|
|
109
110
|
const fingerprint = await certFingerprint(crtPath);
|
|
110
111
|
const expires = await certExpiry(crtPath);
|
|
111
112
|
const cn = await certCN(crtPath);
|
|
112
113
|
const crt = fs.readFileSync(crtPath, 'utf8');
|
|
113
|
-
|
|
114
|
+
const hasChain = fs.existsSync(chainPath);
|
|
115
|
+
const chainCn = hasChain ? await certCN(chainPath).catch(() => null) : null;
|
|
116
|
+
return { crt, fingerprint, expires, cn, hasChain, chainCn };
|
|
114
117
|
} catch {
|
|
115
118
|
return null;
|
|
116
119
|
}
|
|
117
120
|
}
|
|
118
121
|
|
|
122
|
+
/**
|
|
123
|
+
* Import an existing CA keypair (PEM format) into the CA directory.
|
|
124
|
+
* Overwrites any existing ca.crt / ca.key.
|
|
125
|
+
* Optionally accepts a PEM chain (intermediate/root certs above this CA) stored
|
|
126
|
+
* as ca-chain.crt; used to build complete certificate chains in issued P12 bundles.
|
|
127
|
+
*
|
|
128
|
+
* @param {object} config
|
|
129
|
+
* @param {{ certPem: string, keyPem: string, chainPem?: string|null }} options
|
|
130
|
+
*/
|
|
131
|
+
async function importCA(config, { certPem, keyPem, chainPem = null }) {
|
|
132
|
+
const dir = caDir(config);
|
|
133
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
134
|
+
|
|
135
|
+
const tmpCert = path.join(dir, '_import_cert.tmp');
|
|
136
|
+
const tmpKey = path.join(dir, '_import_key.tmp');
|
|
137
|
+
fs.writeFileSync(tmpCert, certPem.trim() + '\n', 'utf8');
|
|
138
|
+
fs.writeFileSync(tmpKey, keyPem.trim() + '\n', 'utf8');
|
|
139
|
+
|
|
140
|
+
try {
|
|
141
|
+
await openssl(['x509', '-in', tmpCert, '-noout']);
|
|
142
|
+
await openssl(['pkey', '-in', tmpKey, '-noout']);
|
|
143
|
+
} catch (e) {
|
|
144
|
+
try { fs.unlinkSync(tmpCert); } catch { /* ignore */ }
|
|
145
|
+
try { fs.unlinkSync(tmpKey); } catch { /* ignore */ }
|
|
146
|
+
throw new Error(`Invalid certificate or key: ${e.message}`);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const keyPath = path.join(dir, 'ca.key');
|
|
150
|
+
const crtPath = path.join(dir, 'ca.crt');
|
|
151
|
+
const srlPath = path.join(dir, 'ca.srl');
|
|
152
|
+
const chainPath = path.join(dir, 'ca-chain.crt');
|
|
153
|
+
|
|
154
|
+
fs.renameSync(tmpCert, crtPath);
|
|
155
|
+
fs.renameSync(tmpKey, keyPath);
|
|
156
|
+
try { fs.chmodSync(keyPath, 0o600); } catch { /* ignore */ }
|
|
157
|
+
|
|
158
|
+
if (!fs.existsSync(srlPath)) {
|
|
159
|
+
fs.writeFileSync(srlPath, '01\n', 'utf8');
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (chainPem && chainPem.trim()) {
|
|
163
|
+
const tmpChain = path.join(dir, '_import_chain.tmp');
|
|
164
|
+
fs.writeFileSync(tmpChain, chainPem.trim() + '\n', 'utf8');
|
|
165
|
+
try {
|
|
166
|
+
await openssl(['x509', '-in', tmpChain, '-noout']);
|
|
167
|
+
} catch (e) {
|
|
168
|
+
try { fs.unlinkSync(tmpChain); } catch { /* ignore */ }
|
|
169
|
+
throw new Error(`Invalid chain certificate: ${e.message}`);
|
|
170
|
+
}
|
|
171
|
+
fs.renameSync(tmpChain, chainPath);
|
|
172
|
+
} else if (fs.existsSync(chainPath)) {
|
|
173
|
+
fs.unlinkSync(chainPath);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const fingerprint = await certFingerprint(crtPath);
|
|
177
|
+
const expires = await certExpiry(crtPath);
|
|
178
|
+
const cn = await certCN(crtPath);
|
|
179
|
+
const crt = fs.readFileSync(crtPath, 'utf8');
|
|
180
|
+
return { crt, fingerprint, expires, cn };
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Extract certificate and private key PEM strings from a PKCS#12 file.
|
|
185
|
+
* Tries OpenSSL 3 defaults first, falls back to -legacy for older P12 files.
|
|
186
|
+
*
|
|
187
|
+
* @param {Buffer} p12Buffer raw .p12/.pfx file bytes
|
|
188
|
+
* @param {string} passphrase decryption passphrase (may be empty string)
|
|
189
|
+
* @returns {Promise<{ certPem: string, keyPem: string }>}
|
|
190
|
+
*/
|
|
191
|
+
async function extractFromP12(p12Buffer, passphrase) {
|
|
192
|
+
const tmpP12 = path.join(os.tmpdir(), `she_p12_${Date.now()}.tmp`);
|
|
193
|
+
fs.writeFileSync(tmpP12, p12Buffer);
|
|
194
|
+
try {
|
|
195
|
+
const passArg = `pass:${passphrase}`;
|
|
196
|
+
const tryExtract = async (extraFlags = []) => {
|
|
197
|
+
const { stdout: certOut } = await openssl(['pkcs12', '-in', tmpP12, '-clcerts', '-nokeys', '-passin', passArg, '-nodes', ...extraFlags]);
|
|
198
|
+
const { stdout: keyOut } = await openssl(['pkcs12', '-in', tmpP12, '-nocerts', '-nodes', '-passin', passArg, ...extraFlags]);
|
|
199
|
+
return { certOut, keyOut };
|
|
200
|
+
};
|
|
201
|
+
let certOut, keyOut;
|
|
202
|
+
try {
|
|
203
|
+
({ certOut, keyOut } = await tryExtract());
|
|
204
|
+
} catch {
|
|
205
|
+
({ certOut, keyOut } = await tryExtract(['-legacy']));
|
|
206
|
+
}
|
|
207
|
+
const certMatch = certOut.match(/-----BEGIN CERTIFICATE-----[\s\S]+?-----END CERTIFICATE-----/);
|
|
208
|
+
const keyMatch = keyOut.match(/-----BEGIN [\w ]+ KEY-----[\s\S]+?-----END [\w ]+ KEY-----/);
|
|
209
|
+
if (!certMatch) throw new Error('No certificate found in P12 file');
|
|
210
|
+
if (!keyMatch) throw new Error('No private key found in P12 file');
|
|
211
|
+
return { certPem: certMatch[0] + '\n', keyPem: keyMatch[0] + '\n' };
|
|
212
|
+
} finally {
|
|
213
|
+
try { fs.unlinkSync(tmpP12); } catch { /* ignore */ }
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
119
217
|
// ── Server certificate ─────────────────────────────────────────────────────────
|
|
120
218
|
|
|
121
219
|
/**
|
|
@@ -130,7 +228,10 @@ async function generateServerCert(config, { cn, san = [], days = 365 } = {}) {
|
|
|
130
228
|
const caCrtPath = path.join(dir, 'ca.crt');
|
|
131
229
|
const srlPath = path.join(dir, 'ca.srl');
|
|
132
230
|
|
|
133
|
-
|
|
231
|
+
// Auto-generate an internal CA if none exists (used for self-signed server certs)
|
|
232
|
+
if (!fs.existsSync(caCrtPath)) {
|
|
233
|
+
await generateCA(config, { cn: 'she-internal-ca', days: 3650 });
|
|
234
|
+
}
|
|
134
235
|
|
|
135
236
|
const serverDir = path.join(dir, 'server');
|
|
136
237
|
fs.mkdirSync(serverDir, { recursive: true });
|
|
@@ -190,6 +291,97 @@ async function generateServerCert(config, { cn, san = [], days = 365 } = {}) {
|
|
|
190
291
|
|
|
191
292
|
// ── Client certificate issuance ────────────────────────────────────────────────
|
|
192
293
|
|
|
294
|
+
/**
|
|
295
|
+
* Generate a server private key and CSR without signing it.
|
|
296
|
+
* The key is stored on disk as server/server.key; the CSR is returned as PEM.
|
|
297
|
+
* The user takes the CSR to an external CA to get it signed, then calls
|
|
298
|
+
* importServerCert() to install the resulting certificate.
|
|
299
|
+
*
|
|
300
|
+
* @param {object} config
|
|
301
|
+
* @param {{ cn?: string, san?: string[], days?: number }} options (days ignored — for future use)
|
|
302
|
+
* @returns {Promise<{ csrPem: string }>}
|
|
303
|
+
*/
|
|
304
|
+
async function generateServerCSR(config, { cn, san = [] } = {}) {
|
|
305
|
+
const dir = caDir(config);
|
|
306
|
+
const serverDir = path.join(dir, 'server');
|
|
307
|
+
fs.mkdirSync(serverDir, { recursive: true });
|
|
308
|
+
|
|
309
|
+
const keyPath = path.join(serverDir, 'server.key');
|
|
310
|
+
const csrPath = path.join(serverDir, 'server.csr');
|
|
311
|
+
|
|
312
|
+
// Generate key
|
|
313
|
+
await openssl(['genpkey', '-algorithm', 'ed25519', '-out', keyPath]);
|
|
314
|
+
try { fs.chmodSync(keyPath, 0o600); } catch { /* ignore */ }
|
|
315
|
+
|
|
316
|
+
// Build subject and optional SAN extension for the CSR
|
|
317
|
+
const sanEntries = [cn, ...san].filter(Boolean).map((s, i) => {
|
|
318
|
+
return /^\d+\.\d+\.\d+\.\d+$/.test(s) ? `IP.${i + 1}:${s}` : `DNS.${i + 1}:${s}`;
|
|
319
|
+
});
|
|
320
|
+
const subjArgs = ['-subj', `/CN=${cn || 'mosquitto'}`];
|
|
321
|
+
let reqArgs = ['req', '-new', '-key', keyPath, '-out', csrPath, ...subjArgs];
|
|
322
|
+
if (sanEntries.length) {
|
|
323
|
+
const extPath = path.join(serverDir, 'server-csr.conf');
|
|
324
|
+
fs.writeFileSync(extPath, `[req]\ndistinguished_name=dn\nreq_extensions=SAN\n[dn]\n[SAN]\nsubjectAltName=${sanEntries.join(',')}\n`, 'utf8');
|
|
325
|
+
reqArgs = ['req', '-new', '-key', keyPath, '-out', csrPath, ...subjArgs, '-config', extPath];
|
|
326
|
+
}
|
|
327
|
+
await openssl(reqArgs);
|
|
328
|
+
|
|
329
|
+
const csrPem = fs.readFileSync(csrPath, 'utf8');
|
|
330
|
+
return { csrPem };
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
/**
|
|
334
|
+
* Install an externally-signed certificate as the server certificate.
|
|
335
|
+
* If keyPem is provided it is written as server.key; otherwise the existing
|
|
336
|
+
* server.key on disk (e.g. from a prior generateServerCSR call) is used.
|
|
337
|
+
*
|
|
338
|
+
* @param {object} config
|
|
339
|
+
* @param {{ certPem: string, keyPem?: string|null }} options
|
|
340
|
+
* @returns {Promise<{ fingerprint: string, expires: string, cn: string }>}
|
|
341
|
+
*/
|
|
342
|
+
async function importServerCert(config, { certPem, keyPem = null } = {}) {
|
|
343
|
+
const dir = caDir(config);
|
|
344
|
+
const serverDir = path.join(dir, 'server');
|
|
345
|
+
fs.mkdirSync(serverDir, { recursive: true });
|
|
346
|
+
|
|
347
|
+
const crtPath = path.join(serverDir, 'server.crt');
|
|
348
|
+
const keyPath = path.join(serverDir, 'server.key');
|
|
349
|
+
|
|
350
|
+
// Validate cert
|
|
351
|
+
const tmpCrt = crtPath + '.tmp';
|
|
352
|
+
fs.writeFileSync(tmpCrt, certPem.trim() + '\n', 'utf8');
|
|
353
|
+
try {
|
|
354
|
+
await openssl(['x509', '-in', tmpCrt, '-noout']);
|
|
355
|
+
} catch (e) {
|
|
356
|
+
try { fs.unlinkSync(tmpCrt); } catch { /* ignore */ }
|
|
357
|
+
throw new Error(`Invalid certificate: ${e.message}`);
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
if (keyPem) {
|
|
361
|
+
const tmpKey = keyPath + '.tmp';
|
|
362
|
+
fs.writeFileSync(tmpKey, keyPem.trim() + '\n', 'utf8');
|
|
363
|
+
try {
|
|
364
|
+
await openssl(['pkey', '-in', tmpKey, '-noout']);
|
|
365
|
+
} catch (e) {
|
|
366
|
+
try { fs.unlinkSync(tmpCrt); } catch { /* ignore */ }
|
|
367
|
+
try { fs.unlinkSync(tmpKey); } catch { /* ignore */ }
|
|
368
|
+
throw new Error(`Invalid private key: ${e.message}`);
|
|
369
|
+
}
|
|
370
|
+
fs.renameSync(tmpKey, keyPath);
|
|
371
|
+
try { fs.chmodSync(keyPath, 0o600); } catch { /* ignore */ }
|
|
372
|
+
} else if (!fs.existsSync(keyPath)) {
|
|
373
|
+
try { fs.unlinkSync(tmpCrt); } catch { /* ignore */ }
|
|
374
|
+
throw new Error('No private key provided and no existing server.key on disk');
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
fs.renameSync(tmpCrt, crtPath);
|
|
378
|
+
|
|
379
|
+
const fingerprint = await certFingerprint(crtPath);
|
|
380
|
+
const expires = await certExpiry(crtPath);
|
|
381
|
+
const cn = await certCN(crtPath);
|
|
382
|
+
return { fingerprint, expires, cn };
|
|
383
|
+
}
|
|
384
|
+
|
|
193
385
|
/**
|
|
194
386
|
* Issue a client certificate signed by the local CA.
|
|
195
387
|
* Returns paths to .p12, .crt, .key and the p12 passphrase.
|
|
@@ -235,9 +427,18 @@ async function issueClientCert(config, { cn, days = 365 } = {}) {
|
|
|
235
427
|
// Read serial from the signed cert
|
|
236
428
|
const serial = await certSerial(crtPath);
|
|
237
429
|
|
|
238
|
-
// Bundle to .p12
|
|
430
|
+
// Bundle to .p12 — include signing chain if ca-chain.crt exists
|
|
239
431
|
const passphrase = crypto.randomBytes(10).toString('hex');
|
|
240
|
-
|
|
432
|
+
const chainPath = path.join(dir, 'ca-chain.crt');
|
|
433
|
+
let p12CertfileArg = caCrtPath;
|
|
434
|
+
let combinedChainTmp = null;
|
|
435
|
+
if (fs.existsSync(chainPath)) {
|
|
436
|
+
combinedChainTmp = path.join(dir, '.combined-chain-tmp.crt');
|
|
437
|
+
fs.writeFileSync(combinedChainTmp, fs.readFileSync(caCrtPath, 'utf8') + fs.readFileSync(chainPath, 'utf8'));
|
|
438
|
+
p12CertfileArg = combinedChainTmp;
|
|
439
|
+
}
|
|
440
|
+
await openssl(['pkcs12', '-export', '-in', crtPath, '-inkey', keyPath, '-certfile', p12CertfileArg, '-out', p12Path, '-passout', `pass:${passphrase}`, '-legacy']);
|
|
441
|
+
if (combinedChainTmp) try { fs.unlinkSync(combinedChainTmp); } catch { /* ignore */ }
|
|
241
442
|
|
|
242
443
|
const fingerprint = await certFingerprint(crtPath);
|
|
243
444
|
const expires = await certExpiry(crtPath);
|
|
@@ -460,7 +661,11 @@ module.exports = {
|
|
|
460
661
|
caCertsDir,
|
|
461
662
|
generateCA,
|
|
462
663
|
getCA,
|
|
664
|
+
importCA,
|
|
665
|
+
extractFromP12,
|
|
463
666
|
generateServerCert,
|
|
667
|
+
generateServerCSR,
|
|
668
|
+
importServerCert,
|
|
464
669
|
issueClientCert,
|
|
465
670
|
generateCRL,
|
|
466
671
|
listTrustedCerts,
|
package/src/lib/dynsec.js
CHANGED
|
@@ -346,7 +346,10 @@ function setDefaultACLAccess(acls) {
|
|
|
346
346
|
// ── Anonymous group ────────────────────────────────────────────────────────────
|
|
347
347
|
|
|
348
348
|
function getAnonymousGroup() {
|
|
349
|
-
return _request('getAnonymousGroup').then((r) =>
|
|
349
|
+
return _request('getAnonymousGroup').then((r) => {
|
|
350
|
+
const g = r.data?.group ?? r.group ?? null;
|
|
351
|
+
return g?.groupname ?? g ?? null;
|
|
352
|
+
});
|
|
350
353
|
}
|
|
351
354
|
|
|
352
355
|
function setAnonymousGroup(groupname) {
|
package/src/web/broker-api.js
CHANGED
|
@@ -551,6 +551,32 @@ router.post('/ca/generate', async (req, res) => {
|
|
|
551
551
|
}
|
|
552
552
|
});
|
|
553
553
|
|
|
554
|
+
/**
|
|
555
|
+
* POST /she/broker/ca/import
|
|
556
|
+
* Import an existing CA from PEM text or a PKCS#12 file.
|
|
557
|
+
* Body (PEM mode): { cert: string, key: string, chain?: string }
|
|
558
|
+
* Body (P12 mode): { p12base64: string, passphrase: string, chain?: string }
|
|
559
|
+
*/
|
|
560
|
+
router.post('/ca/import', async (req, res) => {
|
|
561
|
+
try {
|
|
562
|
+
const bc = getBrokerConfig(req);
|
|
563
|
+
const { cert, key, chain, p12base64, passphrase } = req.body;
|
|
564
|
+
let certPem, keyPem;
|
|
565
|
+
if (p12base64 !== undefined) {
|
|
566
|
+
const p12Buffer = Buffer.from(p12base64, 'base64');
|
|
567
|
+
({ certPem, keyPem } = await ca.extractFromP12(p12Buffer, passphrase ?? ''));
|
|
568
|
+
} else {
|
|
569
|
+
if (!cert || !key) return res.status(400).json({ error: 'cert and key are required' });
|
|
570
|
+
certPem = cert;
|
|
571
|
+
keyPem = key;
|
|
572
|
+
}
|
|
573
|
+
const result = await ca.importCA(bc, { certPem, keyPem, chainPem: chain || null });
|
|
574
|
+
res.json({ ok: true, ca: result });
|
|
575
|
+
} catch (err) {
|
|
576
|
+
handleError(res, err);
|
|
577
|
+
}
|
|
578
|
+
});
|
|
579
|
+
|
|
554
580
|
/** GET /she/broker/ca/server — Server cert info */
|
|
555
581
|
router.get('/ca/server', async (req, res) => {
|
|
556
582
|
try {
|
|
@@ -567,7 +593,7 @@ router.get('/ca/server', async (req, res) => {
|
|
|
567
593
|
}
|
|
568
594
|
});
|
|
569
595
|
|
|
570
|
-
/** POST /she/broker/ca/server/generate — Generate server cert */
|
|
596
|
+
/** POST /she/broker/ca/server/generate — Generate self-signed server cert (auto-creates internal CA) */
|
|
571
597
|
router.post('/ca/server/generate', async (req, res) => {
|
|
572
598
|
try {
|
|
573
599
|
const bc = getBrokerConfig(req);
|
|
@@ -579,6 +605,50 @@ router.post('/ca/server/generate', async (req, res) => {
|
|
|
579
605
|
}
|
|
580
606
|
});
|
|
581
607
|
|
|
608
|
+
/**
|
|
609
|
+
* POST /she/broker/ca/server/csr
|
|
610
|
+
* Generate a private key + CSR for the server cert.
|
|
611
|
+
* The CSR PEM is returned for external signing; the key is kept on disk.
|
|
612
|
+
* Body: { cn?, san?: string[], days?: number }
|
|
613
|
+
*/
|
|
614
|
+
router.post('/ca/server/csr', async (req, res) => {
|
|
615
|
+
try {
|
|
616
|
+
const bc = getBrokerConfig(req);
|
|
617
|
+
const { cn, san, days } = req.body;
|
|
618
|
+
const result = await ca.generateServerCSR(bc, { cn, san, days });
|
|
619
|
+
res.json({ ok: true, csrPem: result.csrPem });
|
|
620
|
+
} catch (err) {
|
|
621
|
+
handleError(res, err);
|
|
622
|
+
}
|
|
623
|
+
});
|
|
624
|
+
|
|
625
|
+
/**
|
|
626
|
+
* POST /she/broker/ca/server/import
|
|
627
|
+
* Install an externally-signed server certificate (PEM or PKCS#12).
|
|
628
|
+
* Body (PEM): { cert: string, key?: string }
|
|
629
|
+
* key is optional when a key already exists on disk (e.g. from /csr).
|
|
630
|
+
* Body (P12): { p12base64: string, passphrase?: string }
|
|
631
|
+
*/
|
|
632
|
+
router.post('/ca/server/import', async (req, res) => {
|
|
633
|
+
try {
|
|
634
|
+
const bc = getBrokerConfig(req);
|
|
635
|
+
const { cert, key, p12base64, passphrase } = req.body;
|
|
636
|
+
let certPem, keyPem;
|
|
637
|
+
if (p12base64 !== undefined) {
|
|
638
|
+
const p12Buffer = Buffer.from(p12base64, 'base64');
|
|
639
|
+
({ certPem, keyPem } = await ca.extractFromP12(p12Buffer, passphrase ?? ''));
|
|
640
|
+
} else {
|
|
641
|
+
if (!cert) return res.status(400).json({ error: 'cert is required' });
|
|
642
|
+
certPem = cert;
|
|
643
|
+
keyPem = key || null;
|
|
644
|
+
}
|
|
645
|
+
const result = await ca.importServerCert(bc, { certPem, keyPem });
|
|
646
|
+
res.json({ ok: true, server: result });
|
|
647
|
+
} catch (err) {
|
|
648
|
+
handleError(res, err);
|
|
649
|
+
}
|
|
650
|
+
});
|
|
651
|
+
|
|
582
652
|
/** GET /she/broker/ca/certs — List issued client certs (from sheDB) */
|
|
583
653
|
router.get('/ca/certs', async (req, res) => {
|
|
584
654
|
try {
|