smart-home-engine 1.3.0 → 1.4.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/dist/web/assets/{index-SmPJ7Vpm.js → index-AxQ546-B.js} +65 -65
- package/dist/web/assets/{index-BTdLG-cS.css → index-Cp5N3a9H.css} +1 -1
- package/dist/web/assets/{tsMode-XvjcqT_Z.js → tsMode-DEX2KSLC.js} +1 -1
- package/dist/web/index.html +2 -2
- package/package.json +1 -1
- package/src/lib/ca.js +112 -3
- package/src/web/broker-api.js +26 -0
|
@@ -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-AxQ546-B.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-AxQ546-B.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-Cp5N3a9H.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
|
/**
|
|
@@ -235,9 +333,18 @@ async function issueClientCert(config, { cn, days = 365 } = {}) {
|
|
|
235
333
|
// Read serial from the signed cert
|
|
236
334
|
const serial = await certSerial(crtPath);
|
|
237
335
|
|
|
238
|
-
// Bundle to .p12
|
|
336
|
+
// Bundle to .p12 — include signing chain if ca-chain.crt exists
|
|
239
337
|
const passphrase = crypto.randomBytes(10).toString('hex');
|
|
240
|
-
|
|
338
|
+
const chainPath = path.join(dir, 'ca-chain.crt');
|
|
339
|
+
let p12CertfileArg = caCrtPath;
|
|
340
|
+
let combinedChainTmp = null;
|
|
341
|
+
if (fs.existsSync(chainPath)) {
|
|
342
|
+
combinedChainTmp = path.join(dir, '.combined-chain-tmp.crt');
|
|
343
|
+
fs.writeFileSync(combinedChainTmp, fs.readFileSync(caCrtPath, 'utf8') + fs.readFileSync(chainPath, 'utf8'));
|
|
344
|
+
p12CertfileArg = combinedChainTmp;
|
|
345
|
+
}
|
|
346
|
+
await openssl(['pkcs12', '-export', '-in', crtPath, '-inkey', keyPath, '-certfile', p12CertfileArg, '-out', p12Path, '-passout', `pass:${passphrase}`, '-legacy']);
|
|
347
|
+
if (combinedChainTmp) try { fs.unlinkSync(combinedChainTmp); } catch { /* ignore */ }
|
|
241
348
|
|
|
242
349
|
const fingerprint = await certFingerprint(crtPath);
|
|
243
350
|
const expires = await certExpiry(crtPath);
|
|
@@ -460,6 +567,8 @@ module.exports = {
|
|
|
460
567
|
caCertsDir,
|
|
461
568
|
generateCA,
|
|
462
569
|
getCA,
|
|
570
|
+
importCA,
|
|
571
|
+
extractFromP12,
|
|
463
572
|
generateServerCert,
|
|
464
573
|
issueClientCert,
|
|
465
574
|
generateCRL,
|
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 {
|