node-opcua-pki 6.20.0 → 6.22.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/bin/pki.mjs +1627 -821
- package/dist/bin/pki.mjs.map +1 -1
- package/dist/chunk-EQ4DGI4M.mjs +1917 -0
- package/dist/chunk-EQ4DGI4M.mjs.map +1 -0
- package/dist/core-BwqSJPHc.d.mts +1041 -0
- package/dist/core-BwqSJPHc.d.ts +1041 -0
- package/dist/core.d.mts +2 -0
- package/dist/core.d.ts +2 -0
- package/dist/core.js +1902 -0
- package/dist/core.js.map +1 -0
- package/dist/core.mjs +21 -0
- package/dist/core.mjs.map +1 -0
- package/dist/index.d.mts +136 -833
- package/dist/index.d.ts +136 -833
- package/dist/index.js +1890 -1086
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +890 -1922
- package/dist/index.mjs.map +1 -1
- package/package.json +14 -3
package/dist/index.js
CHANGED
|
@@ -31,11 +31,15 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
31
31
|
var lib_exports = {};
|
|
32
32
|
__export(lib_exports, {
|
|
33
33
|
CertificateAuthority: () => CertificateAuthority,
|
|
34
|
+
CertificateAuthorityCore: () => CertificateAuthorityCore,
|
|
34
35
|
CertificateManager: () => CertificateManager,
|
|
35
36
|
CertificateManagerState: () => CertificateManagerState,
|
|
36
37
|
ChainCompletionStatus: () => ChainCompletionStatus,
|
|
37
|
-
|
|
38
|
-
|
|
38
|
+
NativeCaBackend: () => NativeCaBackend,
|
|
39
|
+
OpenSslCaBackend: () => OpenSslCaBackend,
|
|
40
|
+
PrivateKeyPassphraseRequiredError: () => import_node_opcua_crypto10.PrivateKeyPassphraseRequiredError,
|
|
41
|
+
PrivateKeyUnavailableError: () => import_node_opcua_crypto10.PrivateKeyUnavailableError,
|
|
42
|
+
Subject: () => import_node_opcua_crypto3.Subject,
|
|
39
43
|
VerificationStatus: () => VerificationStatus,
|
|
40
44
|
adjustApplicationUri: () => adjustApplicationUri,
|
|
41
45
|
adjustDate: () => adjustDate,
|
|
@@ -51,28 +55,61 @@ __export(lib_exports, {
|
|
|
51
55
|
install_prerequisite: () => install_prerequisite,
|
|
52
56
|
isIntermediateIssuer: () => isIntermediateIssuer,
|
|
53
57
|
isIssuer: () => isIssuer,
|
|
58
|
+
isOpaqueSigner: () => isOpaqueSigner,
|
|
54
59
|
isRootIssuer: () => isRootIssuer,
|
|
55
60
|
makeFingerprint: () => makeFingerprint,
|
|
56
61
|
quote: () => quote,
|
|
57
62
|
resolvePrivateKeyPassphrase: () => resolvePrivateKeyPassphrase
|
|
58
63
|
});
|
|
59
64
|
module.exports = __toCommonJS(lib_exports);
|
|
60
|
-
var
|
|
65
|
+
var import_node_opcua_crypto10 = require("node-opcua-crypto");
|
|
61
66
|
|
|
62
|
-
// packages/node-opcua-pki/lib/ca/
|
|
63
|
-
var
|
|
64
|
-
var
|
|
65
|
-
var import_node_os3 = __toESM(require("os"));
|
|
66
|
-
var import_node_path5 = __toESM(require("path"));
|
|
67
|
-
var import_chalk5 = __toESM(require("chalk"));
|
|
67
|
+
// packages/node-opcua-pki/lib/ca/backends/native_ca_backend.ts
|
|
68
|
+
var import_node_fs3 = __toESM(require("fs"));
|
|
69
|
+
var import_node_path3 = __toESM(require("path"));
|
|
68
70
|
var import_node_opcua_crypto2 = require("node-opcua-crypto");
|
|
69
71
|
|
|
70
|
-
// packages/node-opcua-pki/lib/
|
|
71
|
-
var
|
|
72
|
-
|
|
72
|
+
// packages/node-opcua-pki/lib/toolbox/common.ts
|
|
73
|
+
var import_node_assert = __toESM(require("assert"));
|
|
74
|
+
function quote(str) {
|
|
75
|
+
return `"${str || ""}"`;
|
|
76
|
+
}
|
|
77
|
+
function isOpaqueSigner(privateKey) {
|
|
78
|
+
return typeof privateKey !== "string" && typeof privateKey.sign === "function";
|
|
79
|
+
}
|
|
80
|
+
async function resolvePrivateKeyPassphrase(passphrase) {
|
|
81
|
+
if (passphrase === void 0) {
|
|
82
|
+
return void 0;
|
|
83
|
+
}
|
|
84
|
+
return typeof passphrase === "function" ? await passphrase() : passphrase;
|
|
85
|
+
}
|
|
86
|
+
function adjustDate(params) {
|
|
87
|
+
(0, import_node_assert.default)(params instanceof Object);
|
|
88
|
+
params.startDate = params.startDate || /* @__PURE__ */ new Date();
|
|
89
|
+
(0, import_node_assert.default)(params.startDate instanceof Date);
|
|
90
|
+
if (params.validityMs !== void 0) {
|
|
91
|
+
if (params.validityMs <= 0) {
|
|
92
|
+
throw new RangeError(`validityMs must be > 0 (got ${params.validityMs})`);
|
|
93
|
+
}
|
|
94
|
+
params.endDate = new Date(params.startDate.getTime() + params.validityMs);
|
|
95
|
+
params.validity = Math.ceil(params.validityMs / 864e5);
|
|
96
|
+
} else {
|
|
97
|
+
params.validity = params.validity || 365;
|
|
98
|
+
params.endDate = new Date(params.startDate.getTime());
|
|
99
|
+
params.endDate.setDate(params.startDate.getDate() + params.validity);
|
|
100
|
+
}
|
|
101
|
+
(0, import_node_assert.default)(params.endDate instanceof Date);
|
|
102
|
+
(0, import_node_assert.default)(params.startDate instanceof Date);
|
|
103
|
+
}
|
|
104
|
+
function adjustApplicationUri(params) {
|
|
105
|
+
const applicationUri = params.applicationUri || "";
|
|
106
|
+
if (applicationUri.length > 200) {
|
|
107
|
+
throw new Error(`Openssl doesn't support urn with length greater than 200${applicationUri}`);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
73
110
|
|
|
74
111
|
// packages/node-opcua-pki/lib/toolbox/common2.ts
|
|
75
|
-
var
|
|
112
|
+
var import_node_assert2 = __toESM(require("assert"));
|
|
76
113
|
var import_node_fs = __toESM(require("fs"));
|
|
77
114
|
var import_node_path = __toESM(require("path"));
|
|
78
115
|
var import_chalk = __toESM(require("chalk"));
|
|
@@ -137,20 +174,515 @@ function makePath(folderName, filename) {
|
|
|
137
174
|
if (filename) {
|
|
138
175
|
s = import_node_path.default.join(import_node_path.default.normalize(folderName), filename);
|
|
139
176
|
} else {
|
|
140
|
-
(0,
|
|
177
|
+
(0, import_node_assert2.default)(folderName);
|
|
141
178
|
s = folderName;
|
|
142
179
|
}
|
|
143
180
|
s = s.replace(/\\/g, "/");
|
|
144
181
|
return s;
|
|
145
182
|
}
|
|
146
183
|
|
|
147
|
-
// packages/node-opcua-pki/lib/toolbox/
|
|
148
|
-
var
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
184
|
+
// packages/node-opcua-pki/lib/toolbox/display.ts
|
|
185
|
+
var import_chalk2 = __toESM(require("chalk"));
|
|
186
|
+
function displayTitle(str) {
|
|
187
|
+
if (!g_config.silent) {
|
|
188
|
+
warningLog("");
|
|
189
|
+
warningLog(import_chalk2.default.yellowBright(str));
|
|
190
|
+
warningLog(import_chalk2.default.yellow(new Array(str.length + 1).join("=")), "\n");
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
function displaySubtitle(str) {
|
|
194
|
+
if (!g_config.silent) {
|
|
195
|
+
warningLog("");
|
|
196
|
+
warningLog(` ${import_chalk2.default.yellowBright(str)}`);
|
|
197
|
+
warningLog(` ${import_chalk2.default.white(new Array(str.length + 1).join("-"))}`, "\n");
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
function display(str) {
|
|
201
|
+
if (!g_config.silent) {
|
|
202
|
+
warningLog(` ${str}`);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// packages/node-opcua-pki/lib/ca/core/ca_database.ts
|
|
207
|
+
var import_node_fs2 = __toESM(require("fs"));
|
|
208
|
+
var import_node_path2 = __toESM(require("path"));
|
|
209
|
+
var import_node_opcua_crypto = require("node-opcua-crypto");
|
|
210
|
+
function parseOpenSSLDate(dateStr) {
|
|
211
|
+
const raw = dateStr?.split(",")[0] ?? "";
|
|
212
|
+
if (raw.length < 12) return "";
|
|
213
|
+
const yy = parseInt(raw.substring(0, 2), 10);
|
|
214
|
+
const year = yy >= 70 ? 1900 + yy : 2e3 + yy;
|
|
215
|
+
const month = raw.substring(2, 4);
|
|
216
|
+
const day = raw.substring(4, 6);
|
|
217
|
+
const hour = raw.substring(6, 8);
|
|
218
|
+
const min = raw.substring(8, 10);
|
|
219
|
+
const sec = raw.substring(10, 12);
|
|
220
|
+
return `${year}-${month}-${day}T${hour}:${min}:${sec}Z`;
|
|
221
|
+
}
|
|
222
|
+
function formatOpenSSLDate(date) {
|
|
223
|
+
const yy = String(date.getUTCFullYear() % 100).padStart(2, "0");
|
|
224
|
+
const mm = String(date.getUTCMonth() + 1).padStart(2, "0");
|
|
225
|
+
const dd = String(date.getUTCDate()).padStart(2, "0");
|
|
226
|
+
const hh = String(date.getUTCHours()).padStart(2, "0");
|
|
227
|
+
const mi = String(date.getUTCMinutes()).padStart(2, "0");
|
|
228
|
+
const ss = String(date.getUTCSeconds()).padStart(2, "0");
|
|
229
|
+
return `${yy}${mm}${dd}${hh}${mi}${ss}Z`;
|
|
230
|
+
}
|
|
231
|
+
function parseRevocationReason(dateStr) {
|
|
232
|
+
const parts = dateStr?.split(",");
|
|
233
|
+
return parts && parts.length > 1 ? parts[1] : void 0;
|
|
234
|
+
}
|
|
235
|
+
function evenLengthHex(value) {
|
|
236
|
+
const hex = value.toString(16).toUpperCase();
|
|
237
|
+
return hex.length % 2 === 0 ? hex : `0${hex}`;
|
|
238
|
+
}
|
|
239
|
+
function escapeIndexField(value) {
|
|
240
|
+
return value.replace(/[\x00-\x1f\x7f\\]/g, (c) => `\\x${c.charCodeAt(0).toString(16).toUpperCase().padStart(2, "0")}`);
|
|
241
|
+
}
|
|
242
|
+
function assertHexSerial(serial) {
|
|
243
|
+
if (!/^[0-9A-Fa-f]+$/.test(serial)) {
|
|
244
|
+
throw new Error(`Invalid certificate serial number: ${JSON.stringify(serial)}`);
|
|
245
|
+
}
|
|
246
|
+
return serial.toUpperCase();
|
|
247
|
+
}
|
|
248
|
+
var CaDatabase = class {
|
|
249
|
+
#rootDir;
|
|
250
|
+
constructor(rootDir) {
|
|
251
|
+
this.#rootDir = rootDir;
|
|
252
|
+
}
|
|
253
|
+
/** Path to the OpenSSL certificate database file (`index.txt`). */
|
|
254
|
+
get indexFile() {
|
|
255
|
+
return import_node_path2.default.join(this.#rootDir, "index.txt");
|
|
256
|
+
}
|
|
257
|
+
/**
|
|
258
|
+
* Parse the OpenSSL `index.txt` certificate database.
|
|
259
|
+
*
|
|
260
|
+
* Each line has tab-separated fields:
|
|
261
|
+
* ```
|
|
262
|
+
* status expiry [revocationDate] serial unknown subject
|
|
263
|
+
* ```
|
|
264
|
+
*
|
|
265
|
+
* - status: `V` (valid), `R` (revoked), `E` (expired)
|
|
266
|
+
* - expiry: `YYMMDDHHmmssZ`
|
|
267
|
+
* - revocationDate: present only for revoked certs
|
|
268
|
+
* - serial: hex string
|
|
269
|
+
* - unknown: always `"unknown"`
|
|
270
|
+
* - subject: X.500 slash-delimited string
|
|
271
|
+
*/
|
|
272
|
+
readIndex() {
|
|
273
|
+
const indexPath = this.indexFile;
|
|
274
|
+
if (!import_node_fs2.default.existsSync(indexPath)) {
|
|
275
|
+
return [];
|
|
276
|
+
}
|
|
277
|
+
const content = import_node_fs2.default.readFileSync(indexPath, "utf-8");
|
|
278
|
+
const lines = content.split("\n").filter((l) => l.trim().length > 0);
|
|
279
|
+
const records = [];
|
|
280
|
+
for (const line of lines) {
|
|
281
|
+
const fields = line.split(" ");
|
|
282
|
+
if (fields.length < 4) continue;
|
|
283
|
+
const statusChar = fields[0];
|
|
284
|
+
const expiryStr = fields[1];
|
|
285
|
+
let serial;
|
|
286
|
+
let subject;
|
|
287
|
+
let revocationDate;
|
|
288
|
+
if (statusChar === "R") {
|
|
289
|
+
revocationDate = fields[2];
|
|
290
|
+
serial = fields[3];
|
|
291
|
+
subject = fields.length >= 6 ? fields[5] : "";
|
|
292
|
+
} else {
|
|
293
|
+
serial = fields[3];
|
|
294
|
+
subject = fields.length >= 6 ? fields[5] : "";
|
|
295
|
+
}
|
|
296
|
+
let status;
|
|
297
|
+
switch (statusChar) {
|
|
298
|
+
case "V":
|
|
299
|
+
status = "valid";
|
|
300
|
+
break;
|
|
301
|
+
case "R":
|
|
302
|
+
status = "revoked";
|
|
303
|
+
break;
|
|
304
|
+
case "E":
|
|
305
|
+
status = "expired";
|
|
306
|
+
break;
|
|
307
|
+
default:
|
|
308
|
+
continue;
|
|
309
|
+
}
|
|
310
|
+
records.push({
|
|
311
|
+
serial,
|
|
312
|
+
status,
|
|
313
|
+
subject,
|
|
314
|
+
expiryDate: parseOpenSSLDate(expiryStr),
|
|
315
|
+
revocationDate: revocationDate ? parseOpenSSLDate(revocationDate) : void 0,
|
|
316
|
+
reason: revocationDate ? parseRevocationReason(revocationDate) : void 0
|
|
317
|
+
});
|
|
318
|
+
}
|
|
319
|
+
return records;
|
|
320
|
+
}
|
|
321
|
+
/** Look up one record by serial number (case-insensitive). */
|
|
322
|
+
findBySerial(serial) {
|
|
323
|
+
const upper = serial.toUpperCase();
|
|
324
|
+
return this.readIndex().find((r) => r.serial.toUpperCase() === upper);
|
|
325
|
+
}
|
|
326
|
+
/**
|
|
327
|
+
* Read a specific issued certificate by serial number.
|
|
328
|
+
*
|
|
329
|
+
* OpenSSL stores signed certificates in the `certs/` directory using
|
|
330
|
+
* the naming convention `<SERIAL>.pem`.
|
|
331
|
+
*
|
|
332
|
+
* @param serial - hex-encoded serial number (e.g. `"1000"`)
|
|
333
|
+
* @returns the DER buffer, or `undefined` if not found
|
|
334
|
+
*/
|
|
335
|
+
getCertificateBySerial(serial) {
|
|
336
|
+
const upper = serial.toUpperCase();
|
|
337
|
+
const certFile = import_node_path2.default.join(this.#rootDir, "certs", `${upper}.pem`);
|
|
338
|
+
if (!import_node_fs2.default.existsSync(certFile)) {
|
|
339
|
+
return void 0;
|
|
340
|
+
}
|
|
341
|
+
const pem = (0, import_node_opcua_crypto.readCertificatePEM)(certFile);
|
|
342
|
+
return (0, import_node_opcua_crypto.convertPEMtoDER)(pem);
|
|
343
|
+
}
|
|
344
|
+
/**
|
|
345
|
+
* Read-increment-write a hex counter file (`serial`/`crlnumber`),
|
|
346
|
+
* returning the value to hand out — matching `openssl ca`'s own
|
|
347
|
+
* behavior: the file holds the *next* value to assign, and is bumped
|
|
348
|
+
* to the following one immediately after being read. A `.old` backup
|
|
349
|
+
* of the pre-bump content is kept, as `openssl` itself does.
|
|
350
|
+
*/
|
|
351
|
+
#nextHexCounter(fileName) {
|
|
352
|
+
const counterFile = import_node_path2.default.join(this.#rootDir, fileName);
|
|
353
|
+
const current = BigInt(`0x${import_node_fs2.default.readFileSync(counterFile, "utf-8").trim()}`);
|
|
354
|
+
import_node_fs2.default.copyFileSync(counterFile, `${counterFile}.old`);
|
|
355
|
+
import_node_fs2.default.writeFileSync(counterFile, evenLengthHex(current + 1n));
|
|
356
|
+
return evenLengthHex(current);
|
|
357
|
+
}
|
|
358
|
+
/** Hand out the next certificate serial number, bumping the `serial` file. */
|
|
359
|
+
nextSerial() {
|
|
360
|
+
return this.#nextHexCounter("serial");
|
|
361
|
+
}
|
|
362
|
+
/** Hand out the next CRL number, bumping the `crlnumber` file. */
|
|
363
|
+
nextCrlNumber() {
|
|
364
|
+
return this.#nextHexCounter("crlnumber");
|
|
365
|
+
}
|
|
366
|
+
/** Append a newly-issued certificate's `V` (valid) row to `index.txt`. */
|
|
367
|
+
appendIssued(record) {
|
|
368
|
+
const serial = assertHexSerial(record.serial);
|
|
369
|
+
const subject = escapeIndexField(record.subject);
|
|
370
|
+
const line = `V ${formatOpenSSLDate(record.expiryDate)} ${serial} unknown ${subject}
|
|
371
|
+
`;
|
|
372
|
+
import_node_fs2.default.appendFileSync(this.indexFile, line);
|
|
373
|
+
}
|
|
374
|
+
/**
|
|
375
|
+
* Rewrite a certificate's `index.txt` row from `V` to `R` (revoked).
|
|
376
|
+
* Throws if the serial is not found, or is already revoked — the same
|
|
377
|
+
* "already revoked" case `openssl ca -revoke` itself rejects.
|
|
378
|
+
*/
|
|
379
|
+
markRevoked(serial, revocationDate, reason) {
|
|
380
|
+
const upper = assertHexSerial(serial);
|
|
381
|
+
if (!/^[A-Za-z]+$/.test(reason)) {
|
|
382
|
+
throw new Error(`Invalid CRL reason: ${JSON.stringify(reason)}`);
|
|
383
|
+
}
|
|
384
|
+
const lines = import_node_fs2.default.readFileSync(this.indexFile, "utf-8").split("\n").filter((l) => l.trim().length > 0);
|
|
385
|
+
let found = false;
|
|
386
|
+
const rewritten = lines.map((line) => {
|
|
387
|
+
const fields = line.split(" ");
|
|
388
|
+
if (fields.length < 4 || fields[3]?.toUpperCase() !== upper) {
|
|
389
|
+
return line;
|
|
390
|
+
}
|
|
391
|
+
if (fields[0] === "R") {
|
|
392
|
+
throw new Error(`Certificate ${upper} is already revoked`);
|
|
393
|
+
}
|
|
394
|
+
found = true;
|
|
395
|
+
const subject = fields.length >= 6 ? fields[5] : "";
|
|
396
|
+
return `R ${fields[1]} ${formatOpenSSLDate(revocationDate)},${reason} ${upper} unknown ${subject}`;
|
|
397
|
+
});
|
|
398
|
+
if (!found) {
|
|
399
|
+
throw new Error(`Certificate ${upper} not found in the certificate database`);
|
|
400
|
+
}
|
|
401
|
+
import_node_fs2.default.writeFileSync(this.indexFile, `${rewritten.join("\n")}
|
|
402
|
+
`);
|
|
403
|
+
}
|
|
404
|
+
/** Store a signed certificate's PEM under `certs/<SERIAL>.pem`, as `openssl ca` does. */
|
|
405
|
+
storeCertificate(serial, pem) {
|
|
406
|
+
const certFile = import_node_path2.default.join(this.#rootDir, "certs", `${assertHexSerial(serial)}.pem`);
|
|
407
|
+
import_node_fs2.default.writeFileSync(certFile, pem);
|
|
408
|
+
}
|
|
409
|
+
};
|
|
410
|
+
|
|
411
|
+
// packages/node-opcua-pki/lib/ca/backends/native_ca_backend.ts
|
|
412
|
+
var CA_CERT_VALIDITY_DAYS = 3650;
|
|
413
|
+
var RSA_SHA256 = { name: "RSASSA-PKCS1-v1_5", hash: { name: "SHA-256" } };
|
|
414
|
+
function signingAlgorithmOf(key) {
|
|
415
|
+
return (0, import_node_opcua_crypto2.isCaSigner)(key) ? key.algorithm : RSA_SHA256;
|
|
416
|
+
}
|
|
417
|
+
function daysFromNow(notBefore, days) {
|
|
418
|
+
return new Date(notBefore.getTime() + days * 24 * 60 * 60 * 1e3);
|
|
419
|
+
}
|
|
420
|
+
function caSanUri(ca) {
|
|
421
|
+
return `urn:${ca.subject.commonName || "NodeOPCUA-CA"}`;
|
|
422
|
+
}
|
|
423
|
+
function sanFromCsr(csrPem) {
|
|
424
|
+
const request = new import_node_opcua_crypto2.x509.Pkcs10CertificateRequest(csrPem);
|
|
425
|
+
const extension = request.getExtension("2.5.29.17");
|
|
426
|
+
const dns = [];
|
|
427
|
+
const ip = [];
|
|
428
|
+
let applicationUri;
|
|
429
|
+
for (const name of extension?.names.toJSON() ?? []) {
|
|
430
|
+
if (name.type === "dns") {
|
|
431
|
+
dns.push(name.value);
|
|
432
|
+
} else if (name.type === "ip") {
|
|
433
|
+
ip.push(name.value);
|
|
434
|
+
} else if (name.type === "url" && applicationUri === void 0) {
|
|
435
|
+
applicationUri = name.value;
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
return { dns, ip, applicationUri };
|
|
439
|
+
}
|
|
440
|
+
var REASON_TO_X509_CRL_REASON = {
|
|
441
|
+
unspecified: import_node_opcua_crypto2.x509.X509CrlReason.unspecified,
|
|
442
|
+
keyCompromise: import_node_opcua_crypto2.x509.X509CrlReason.keyCompromise,
|
|
443
|
+
CACompromise: import_node_opcua_crypto2.x509.X509CrlReason.cACompromise,
|
|
444
|
+
affiliationChanged: import_node_opcua_crypto2.x509.X509CrlReason.affiliationChanged,
|
|
445
|
+
superseded: import_node_opcua_crypto2.x509.X509CrlReason.superseded,
|
|
446
|
+
cessationOfOperation: import_node_opcua_crypto2.x509.X509CrlReason.cessationOfOperation,
|
|
447
|
+
certificateHold: import_node_opcua_crypto2.x509.X509CrlReason.certificateHold,
|
|
448
|
+
removeFromCRL: import_node_opcua_crypto2.x509.X509CrlReason.removeFromCRL
|
|
449
|
+
};
|
|
450
|
+
function toOpenSslSubjectString(subjectName) {
|
|
451
|
+
const parts = [];
|
|
452
|
+
for (const rdn of subjectName.toJSON()) {
|
|
453
|
+
for (const [type, values] of Object.entries(rdn)) {
|
|
454
|
+
for (const value of values) {
|
|
455
|
+
parts.push(`${type}=${value}`);
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
return `/${parts.join("/")}`;
|
|
460
|
+
}
|
|
461
|
+
var NativeCaBackend = class {
|
|
462
|
+
/** Nothing to check: this backend spawns no process and needs no tool on PATH. */
|
|
463
|
+
/** Signing goes through `ca._getSigningKey()`, which may be an external signer. */
|
|
464
|
+
supportsExternalSigner = true;
|
|
465
|
+
async preflight() {
|
|
466
|
+
}
|
|
467
|
+
/**
|
|
468
|
+
* Write a CSR for the CA's own key, the `[v3_ca_req]` equivalent. The
|
|
469
|
+
* key comes from the CA rather than from `privateKeyFile`: on a
|
|
470
|
+
* signer-backed CA that file does not exist.
|
|
471
|
+
*/
|
|
472
|
+
async generateCaCsr(ca, _caRootDir, _privateKeyFile, csrFile) {
|
|
473
|
+
displayTitle("Generate a certificate request for the CA key");
|
|
474
|
+
const signingKey = await ca._getSigningKey();
|
|
475
|
+
const { csr } = await (0, import_node_opcua_crypto2.createCertificateSigningRequest)({
|
|
476
|
+
privateKey: signingKey,
|
|
477
|
+
subject: ca.subject.toString(),
|
|
478
|
+
applicationUri: caSanUri(ca),
|
|
479
|
+
purpose: import_node_opcua_crypto2.CertificatePurpose.ForCertificateAuthority
|
|
480
|
+
});
|
|
481
|
+
await import_node_fs3.default.promises.writeFile(csrFile, csr);
|
|
482
|
+
}
|
|
483
|
+
async bootstrap(ca) {
|
|
484
|
+
const caRootDir = import_node_path3.default.resolve(ca.rootDir);
|
|
485
|
+
const csrFile = import_node_path3.default.join(caRootDir, "private/cakey.csr");
|
|
486
|
+
await this.generateCaCsr(ca, caRootDir, import_node_path3.default.join(caRootDir, "private/cakey.pem"), csrFile);
|
|
487
|
+
const issuerCA = ca._issuerCA;
|
|
488
|
+
if (issuerCA) {
|
|
489
|
+
displayTitle("Generate CA Certificate (signed by issuer CA)");
|
|
490
|
+
const signWithIssuer = () => this.signSubordinateCsr(issuerCA, csrFile, ca.caCertificate, CA_CERT_VALIDITY_DAYS);
|
|
491
|
+
if (import_node_path3.default.resolve(issuerCA.rootDir) === caRootDir) {
|
|
492
|
+
await signWithIssuer();
|
|
493
|
+
} else {
|
|
494
|
+
await issuerCA._withCaDirectoryLock(signWithIssuer);
|
|
495
|
+
}
|
|
496
|
+
} else {
|
|
497
|
+
displayTitle("Generate CA Certificate (self-signed)");
|
|
498
|
+
const csrPem = await import_node_fs3.default.promises.readFile(csrFile, "utf-8");
|
|
499
|
+
const signingKey = await ca._getSigningKey();
|
|
500
|
+
const request = new import_node_opcua_crypto2.x509.Pkcs10CertificateRequest(csrPem);
|
|
501
|
+
const notBefore = /* @__PURE__ */ new Date();
|
|
502
|
+
const { cert } = await (0, import_node_opcua_crypto2.createCertificateFromCsr)({
|
|
503
|
+
csr: csrPem,
|
|
504
|
+
// the CSR's own parsed subject, so issuer and subject are
|
|
505
|
+
// byte-identical on a self-signed root: re-encoding a string
|
|
506
|
+
// form could differ and break chain building
|
|
507
|
+
issuerName: request.subjectName,
|
|
508
|
+
issuerPublicKey: request.publicKey,
|
|
509
|
+
signingKey,
|
|
510
|
+
signingAlgorithm: signingAlgorithmOf(signingKey),
|
|
511
|
+
notBefore,
|
|
512
|
+
notAfter: daysFromNow(notBefore, CA_CERT_VALIDITY_DAYS),
|
|
513
|
+
purpose: import_node_opcua_crypto2.CertificatePurpose.ForCertificateAuthority,
|
|
514
|
+
applicationUri: caSanUri(ca),
|
|
515
|
+
revocation: {
|
|
516
|
+
crlDistributionUrl: ca.crlDistributionUrl,
|
|
517
|
+
ocspResponderUrl: ca.ocspResponderUrl,
|
|
518
|
+
caIssuersUrl: ca.caIssuersUrl
|
|
519
|
+
}
|
|
520
|
+
});
|
|
521
|
+
await import_node_fs3.default.promises.writeFile(ca.caCertificate, cert);
|
|
522
|
+
}
|
|
523
|
+
displaySubtitle("generate initial CRL (Certificate Revocation List)");
|
|
524
|
+
await this.#regenerateCrlLocked(ca, new CaDatabase(ca.rootDir));
|
|
525
|
+
displayTitle("Create Certificate Authority (CA) ---> DONE");
|
|
526
|
+
}
|
|
527
|
+
/** Sign a subordinate CA's request with this CA's key, the `[v3_ca]` equivalent. */
|
|
528
|
+
async signSubordinateCsr(ca, csrFile, certFile, validityDays) {
|
|
529
|
+
const { signingKey, signingAlgorithm, issuerPublicKey, issuerName } = await this.#issuerContext(ca);
|
|
530
|
+
const csrPem = await import_node_fs3.default.promises.readFile(csrFile, "utf-8");
|
|
531
|
+
const db = new CaDatabase(ca.rootDir);
|
|
532
|
+
const serialNumber = db.nextSerial();
|
|
533
|
+
const notBefore = /* @__PURE__ */ new Date();
|
|
534
|
+
const notAfter = daysFromNow(notBefore, validityDays);
|
|
535
|
+
const { cert } = await (0, import_node_opcua_crypto2.createCertificateFromCsr)({
|
|
536
|
+
csr: csrPem,
|
|
537
|
+
issuerName,
|
|
538
|
+
issuerPublicKey,
|
|
539
|
+
signingKey,
|
|
540
|
+
signingAlgorithm,
|
|
541
|
+
serialNumber,
|
|
542
|
+
notBefore,
|
|
543
|
+
notAfter,
|
|
544
|
+
purpose: import_node_opcua_crypto2.CertificatePurpose.ForCertificateAuthority,
|
|
545
|
+
// the subordinate's own SAN, not the issuer's
|
|
546
|
+
...sanFromCsr(csrPem),
|
|
547
|
+
revocation: {
|
|
548
|
+
crlDistributionUrl: ca.crlDistributionUrl,
|
|
549
|
+
ocspResponderUrl: ca.ocspResponderUrl,
|
|
550
|
+
caIssuersUrl: ca.caIssuersUrl
|
|
551
|
+
}
|
|
552
|
+
});
|
|
553
|
+
await import_node_fs3.default.promises.writeFile(certFile, cert);
|
|
554
|
+
this.#record(db, serialNumber, cert, notAfter);
|
|
555
|
+
}
|
|
556
|
+
/**
|
|
557
|
+
* Self-sign a certificate with a caller-supplied key file and record it
|
|
558
|
+
* in this CA's database: the native equivalent of `openssl req -new`
|
|
559
|
+
* followed by `openssl ca -selfsign`, which likewise applies the
|
|
560
|
+
* end-entity profile and updates `index.txt`.
|
|
561
|
+
*/
|
|
562
|
+
async createSelfSignedCertificate(ca, certificateFile, privateKeyFile, params) {
|
|
563
|
+
displaySubtitle("- the certificate signing request");
|
|
564
|
+
const key = await (0, import_node_opcua_crypto2.privateKeyToCryptoKey)((0, import_node_opcua_crypto2.readPrivateKey)(privateKeyFile, await ca._privateKeyPassphrase()));
|
|
565
|
+
const { csr } = await (0, import_node_opcua_crypto2.createCertificateSigningRequest)({
|
|
566
|
+
privateKey: key,
|
|
567
|
+
subject: params.subject ? new import_node_opcua_crypto2.Subject(params.subject).toString() : ca.subject.toString(),
|
|
568
|
+
dns: params.dns,
|
|
569
|
+
ip: params.ip,
|
|
570
|
+
applicationUri: params.applicationUri,
|
|
571
|
+
purpose: import_node_opcua_crypto2.CertificatePurpose.ForApplication
|
|
572
|
+
});
|
|
573
|
+
displaySubtitle("- creating the self-signed certificate");
|
|
574
|
+
const request = new import_node_opcua_crypto2.x509.Pkcs10CertificateRequest(csr);
|
|
575
|
+
const db = new CaDatabase(ca.rootDir);
|
|
576
|
+
const serialNumber = db.nextSerial();
|
|
577
|
+
const notBefore = params.startDate ?? /* @__PURE__ */ new Date();
|
|
578
|
+
const notAfter = params.endDate ?? daysFromNow(notBefore, params.validity ?? 365);
|
|
579
|
+
const { cert } = await (0, import_node_opcua_crypto2.createCertificateFromCsr)({
|
|
580
|
+
csr,
|
|
581
|
+
issuerName: request.subjectName,
|
|
582
|
+
issuerPublicKey: request.publicKey,
|
|
583
|
+
signingKey: key,
|
|
584
|
+
signingAlgorithm: RSA_SHA256,
|
|
585
|
+
serialNumber,
|
|
586
|
+
notBefore,
|
|
587
|
+
notAfter,
|
|
588
|
+
purpose: import_node_opcua_crypto2.CertificatePurpose.ForApplication,
|
|
589
|
+
...sanFromCsr(csr)
|
|
590
|
+
});
|
|
591
|
+
await import_node_fs3.default.promises.writeFile(certificateFile, cert);
|
|
592
|
+
this.#record(db, serialNumber, cert, notAfter);
|
|
593
|
+
}
|
|
594
|
+
/** `certs/<SERIAL>.pem` plus the `V` row, exactly as `openssl ca` writes them. */
|
|
595
|
+
#record(db, serialNumber, certPem, notAfter) {
|
|
596
|
+
db.storeCertificate(serialNumber, certPem);
|
|
597
|
+
db.appendIssued({
|
|
598
|
+
serial: serialNumber,
|
|
599
|
+
expiryDate: notAfter,
|
|
600
|
+
subject: toOpenSslSubjectString(new import_node_opcua_crypto2.x509.X509Certificate(certPem).subjectName)
|
|
601
|
+
});
|
|
602
|
+
}
|
|
603
|
+
/**
|
|
604
|
+
* The CA's signing key and issuer identity, resolved fresh on every
|
|
605
|
+
* call and never cached across operations. The identity is read back
|
|
606
|
+
* from the CA certificate on disk so that the issuer field of anything
|
|
607
|
+
* this CA signs is byte-identical to that certificate's subject.
|
|
608
|
+
*/
|
|
609
|
+
async #issuerContext(ca) {
|
|
610
|
+
const signingKey = await ca._getSigningKey();
|
|
611
|
+
const caCertPem = await import_node_fs3.default.promises.readFile(ca.caCertificate, "utf-8");
|
|
612
|
+
const caCert = new import_node_opcua_crypto2.x509.X509Certificate(caCertPem);
|
|
613
|
+
return {
|
|
614
|
+
signingKey,
|
|
615
|
+
signingAlgorithm: signingAlgorithmOf(signingKey),
|
|
616
|
+
issuerPublicKey: caCert.publicKey,
|
|
617
|
+
issuerName: caCert.subjectName
|
|
618
|
+
};
|
|
619
|
+
}
|
|
620
|
+
async signEndEntityCsr(ca, certificate, csr, params, sanOverride) {
|
|
621
|
+
const { signingKey, signingAlgorithm, issuerPublicKey, issuerName } = await this.#issuerContext(ca);
|
|
622
|
+
const db = new CaDatabase(ca.rootDir);
|
|
623
|
+
const serialNumber = db.nextSerial();
|
|
624
|
+
const csrPem = await import_node_fs3.default.promises.readFile(csr, "utf-8");
|
|
625
|
+
const notBefore = params.startDate ?? /* @__PURE__ */ new Date();
|
|
626
|
+
const notAfter = params.endDate ?? new Date(notBefore.getTime() + (params.validity ?? 365) * 24 * 60 * 60 * 1e3);
|
|
627
|
+
const { cert } = await (0, import_node_opcua_crypto2.createCertificateFromCsr)({
|
|
628
|
+
csr: csrPem,
|
|
629
|
+
issuerName,
|
|
630
|
+
issuerPublicKey,
|
|
631
|
+
signingKey,
|
|
632
|
+
signingAlgorithm,
|
|
633
|
+
serialNumber,
|
|
634
|
+
notBefore,
|
|
635
|
+
notAfter,
|
|
636
|
+
purpose: import_node_opcua_crypto2.CertificatePurpose.ForApplication,
|
|
637
|
+
dns: sanOverride.dns,
|
|
638
|
+
ip: sanOverride.ip,
|
|
639
|
+
applicationUri: sanOverride.applicationUri,
|
|
640
|
+
revocation: {
|
|
641
|
+
crlDistributionUrl: ca.crlDistributionUrl,
|
|
642
|
+
ocspResponderUrl: ca.ocspResponderUrl,
|
|
643
|
+
caIssuersUrl: ca.caIssuersUrl
|
|
644
|
+
}
|
|
645
|
+
});
|
|
646
|
+
await import_node_fs3.default.promises.writeFile(certificate, cert);
|
|
647
|
+
this.#record(db, serialNumber, cert, notAfter);
|
|
648
|
+
}
|
|
649
|
+
async #regenerateCrlLocked(ca, db) {
|
|
650
|
+
const { signingKey, signingAlgorithm, issuerPublicKey, issuerName } = await this.#issuerContext(ca);
|
|
651
|
+
const crlNumber = db.nextCrlNumber();
|
|
652
|
+
const entries = db.readIndex().filter((r) => r.status === "revoked").map((r) => ({
|
|
653
|
+
serialNumber: r.serial,
|
|
654
|
+
revocationDate: r.revocationDate ? new Date(r.revocationDate) : /* @__PURE__ */ new Date(),
|
|
655
|
+
reason: r.reason ? REASON_TO_X509_CRL_REASON[r.reason] : void 0
|
|
656
|
+
}));
|
|
657
|
+
const { crl } = await (0, import_node_opcua_crypto2.createCrl)({
|
|
658
|
+
issuerName,
|
|
659
|
+
issuerPublicKey,
|
|
660
|
+
signingKey,
|
|
661
|
+
signingAlgorithm,
|
|
662
|
+
crlNumber: BigInt(`0x${crlNumber}`),
|
|
663
|
+
entries
|
|
664
|
+
});
|
|
665
|
+
await import_node_fs3.default.promises.writeFile(ca.revocationList, crl);
|
|
666
|
+
const der = new import_node_opcua_crypto2.x509.X509Crl(crl).rawData;
|
|
667
|
+
await import_node_fs3.default.promises.writeFile(ca.revocationListDER, Buffer.from(der));
|
|
668
|
+
}
|
|
669
|
+
async regenerateCrl(ca) {
|
|
670
|
+
const db = new CaDatabase(ca.rootDir);
|
|
671
|
+
await this.#regenerateCrlLocked(ca, db);
|
|
672
|
+
}
|
|
673
|
+
async revoke(ca, certificate, reason) {
|
|
674
|
+
const certPem = await import_node_fs3.default.promises.readFile(certificate, "utf-8");
|
|
675
|
+
const cert = new import_node_opcua_crypto2.x509.X509Certificate(certPem);
|
|
676
|
+
const db = new CaDatabase(ca.rootDir);
|
|
677
|
+
db.markRevoked(cert.serialNumber, /* @__PURE__ */ new Date(), reason);
|
|
678
|
+
await this.#regenerateCrlLocked(ca, db);
|
|
679
|
+
}
|
|
680
|
+
};
|
|
681
|
+
|
|
682
|
+
// packages/node-opcua-pki/lib/ca/backends/openssl_ca_backend.ts
|
|
683
|
+
var import_node_fs7 = __toESM(require("fs"));
|
|
684
|
+
var import_node_path6 = __toESM(require("path"));
|
|
685
|
+
var import_node_opcua_crypto4 = require("node-opcua-crypto");
|
|
154
686
|
|
|
155
687
|
// packages/node-opcua-pki/lib/toolbox/with_openssl/_env.ts
|
|
156
688
|
var SAFE_ENV_PASSTHROUGH = /* @__PURE__ */ new Set([
|
|
@@ -224,31 +756,33 @@ function getEnvironmentVarNames() {
|
|
|
224
756
|
return { key: varName, pattern: `\\$ENV\\:\\:${varName}` };
|
|
225
757
|
});
|
|
226
758
|
}
|
|
227
|
-
function
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
subjectAltName,
|
|
234
|
-
params.dns.map((d) => `DNS:${d}`)
|
|
235
|
-
);
|
|
236
|
-
subjectAltName = [].concat(
|
|
237
|
-
subjectAltName,
|
|
238
|
-
params.ip.map((d) => `IP:${d}`)
|
|
239
|
-
);
|
|
240
|
-
const subjectAltNameString = subjectAltName.join(", ");
|
|
241
|
-
setEnv("ALTNAME", subjectAltNameString);
|
|
759
|
+
function buildSubjectAltNameString(params) {
|
|
760
|
+
return [
|
|
761
|
+
`URI:${params.applicationUri}`,
|
|
762
|
+
...(params.dns ?? []).map((d) => `DNS:${d}`),
|
|
763
|
+
...(params.ip ?? []).map((d) => `IP:${d}`)
|
|
764
|
+
].join(", ");
|
|
242
765
|
}
|
|
243
766
|
|
|
767
|
+
// packages/node-opcua-pki/lib/misc/subject.ts
|
|
768
|
+
var import_node_opcua_crypto3 = require("node-opcua-crypto");
|
|
769
|
+
|
|
770
|
+
// packages/node-opcua-pki/lib/toolbox/with_openssl/execute_openssl.ts
|
|
771
|
+
var import_node_assert3 = __toESM(require("assert"));
|
|
772
|
+
var import_node_child_process2 = __toESM(require("child_process"));
|
|
773
|
+
var import_node_fs5 = __toESM(require("fs"));
|
|
774
|
+
var import_node_os2 = __toESM(require("os"));
|
|
775
|
+
var import_byline2 = __toESM(require("byline"));
|
|
776
|
+
var import_chalk4 = __toESM(require("chalk"));
|
|
777
|
+
|
|
244
778
|
// packages/node-opcua-pki/lib/toolbox/with_openssl/install_prerequisite.ts
|
|
245
779
|
var import_node_child_process = __toESM(require("child_process"));
|
|
246
|
-
var
|
|
780
|
+
var import_node_fs4 = __toESM(require("fs"));
|
|
247
781
|
var import_node_os = __toESM(require("os"));
|
|
248
|
-
var
|
|
782
|
+
var import_node_path4 = __toESM(require("path"));
|
|
249
783
|
var import_promises = require("stream/promises");
|
|
250
784
|
var import_byline = __toESM(require("byline"));
|
|
251
|
-
var
|
|
785
|
+
var import_chalk3 = __toESM(require("chalk"));
|
|
252
786
|
var import_yauzl = __toESM(require("yauzl"));
|
|
253
787
|
var doDebug2 = process.env.NODEOPCUAPKIDEBUG || false;
|
|
254
788
|
async function execute(file, args, cwd) {
|
|
@@ -267,7 +801,7 @@ async function execute(file, args, cwd) {
|
|
|
267
801
|
output += `${line}
|
|
268
802
|
`;
|
|
269
803
|
if (doDebug2) {
|
|
270
|
-
process.stdout.write(` stdout ${
|
|
804
|
+
process.stdout.write(` stdout ${import_chalk3.default.yellow(line)}
|
|
271
805
|
`);
|
|
272
806
|
}
|
|
273
807
|
});
|
|
@@ -287,8 +821,8 @@ async function getopensslExecPath() {
|
|
|
287
821
|
const exitCode = result1?.exitCode;
|
|
288
822
|
const output = result1?.output;
|
|
289
823
|
if (exitCode !== 0) {
|
|
290
|
-
warningLog(
|
|
291
|
-
warningLog(
|
|
824
|
+
warningLog(import_chalk3.default.yellow(" it seems that ") + import_chalk3.default.cyan("openssl") + import_chalk3.default.yellow(" is not installed on your computer "));
|
|
825
|
+
warningLog(import_chalk3.default.yellow("Please install it before running this programs"));
|
|
292
826
|
throw new Error("Cannot find openssl");
|
|
293
827
|
}
|
|
294
828
|
const opensslExecPath = output.replace(/\n\r/g, "").trim();
|
|
@@ -297,7 +831,7 @@ async function getopensslExecPath() {
|
|
|
297
831
|
async function check_system_openssl_version() {
|
|
298
832
|
const opensslExecPath = await getopensslExecPath();
|
|
299
833
|
if (doDebug2) {
|
|
300
|
-
warningLog(` OpenSSL found in : ${
|
|
834
|
+
warningLog(` OpenSSL found in : ${import_chalk3.default.yellow(opensslExecPath)}`);
|
|
301
835
|
}
|
|
302
836
|
const result = await execute(opensslExecPath, ["version"]);
|
|
303
837
|
const exitCode = result?.exitCode;
|
|
@@ -305,35 +839,35 @@ async function check_system_openssl_version() {
|
|
|
305
839
|
const version = output.trim();
|
|
306
840
|
const versionOK = exitCode === 0 && is_expected_openssl_version(version);
|
|
307
841
|
if (!versionOK) {
|
|
308
|
-
let message =
|
|
842
|
+
let message = import_chalk3.default.whiteBright("Warning !!!!!!!!!!!! ") + "\nyour version of openssl is " + version + ". It doesn't match the expected version";
|
|
309
843
|
if (process.platform === "darwin") {
|
|
310
|
-
message +=
|
|
844
|
+
message += import_chalk3.default.cyan("\nplease refer to :") + import_chalk3.default.yellow(" https://github.com/node-opcua/node-opcua/wiki/installing-node-opcua-or-node-red-on-MacOS");
|
|
311
845
|
}
|
|
312
846
|
console.log(message);
|
|
313
847
|
}
|
|
314
848
|
return output;
|
|
315
849
|
}
|
|
316
850
|
async function install_and_check_win32_openssl_version() {
|
|
317
|
-
const downloadFolder =
|
|
851
|
+
const downloadFolder = import_node_path4.default.join(import_node_os.default.tmpdir(), ".");
|
|
318
852
|
function get_openssl_folder_win32() {
|
|
319
853
|
if (process.env.LOCALAPPDATA) {
|
|
320
|
-
const userProgramFolder =
|
|
321
|
-
if (
|
|
322
|
-
return
|
|
854
|
+
const userProgramFolder = import_node_path4.default.join(process.env.LOCALAPPDATA, "Programs");
|
|
855
|
+
if (import_node_fs4.default.existsSync(userProgramFolder)) {
|
|
856
|
+
return import_node_path4.default.join(userProgramFolder, "openssl");
|
|
323
857
|
}
|
|
324
858
|
}
|
|
325
|
-
return
|
|
859
|
+
return import_node_path4.default.join(process.cwd(), "openssl");
|
|
326
860
|
}
|
|
327
861
|
function get_openssl_exec_path_win32() {
|
|
328
862
|
const opensslFolder2 = get_openssl_folder_win32();
|
|
329
|
-
return
|
|
863
|
+
return import_node_path4.default.join(opensslFolder2, "openssl.exe");
|
|
330
864
|
}
|
|
331
865
|
async function check_openssl_win32() {
|
|
332
866
|
const opensslExecPath2 = get_openssl_exec_path_win32();
|
|
333
|
-
const exists =
|
|
867
|
+
const exists = import_node_fs4.default.existsSync(opensslExecPath2);
|
|
334
868
|
if (!exists) {
|
|
335
869
|
warningLog("checking presence of ", opensslExecPath2);
|
|
336
|
-
warningLog(
|
|
870
|
+
warningLog(import_chalk3.default.red(" cannot find file ") + opensslExecPath2);
|
|
337
871
|
return {
|
|
338
872
|
opensslOk: false,
|
|
339
873
|
version: `cannot find file ${opensslExecPath2}`
|
|
@@ -358,18 +892,18 @@ async function install_and_check_win32_openssl_version() {
|
|
|
358
892
|
return void 0;
|
|
359
893
|
}
|
|
360
894
|
const opensslPath2 = result.output.split(/\r?\n/)[0].trim();
|
|
361
|
-
if (!opensslPath2 || !
|
|
895
|
+
if (!opensslPath2 || !import_node_fs4.default.existsSync(opensslPath2)) {
|
|
362
896
|
return void 0;
|
|
363
897
|
}
|
|
364
898
|
const versionResult = await execute(opensslPath2, ["version"]);
|
|
365
899
|
const version = versionResult.output.trim();
|
|
366
900
|
if (versionResult.exitCode === 0 && is_expected_openssl_version(version)) {
|
|
367
901
|
warningLog(
|
|
368
|
-
|
|
902
|
+
import_chalk3.default.green("Using system OpenSSL: ") + import_chalk3.default.cyan(version) + import_chalk3.default.green(" at ") + import_chalk3.default.cyan(opensslPath2)
|
|
369
903
|
);
|
|
370
904
|
return opensslPath2;
|
|
371
905
|
}
|
|
372
|
-
warningLog(
|
|
906
|
+
warningLog(import_chalk3.default.yellow("System OpenSSL found but version not accepted: ") + version);
|
|
373
907
|
return void 0;
|
|
374
908
|
} catch (_err) {
|
|
375
909
|
return void 0;
|
|
@@ -389,9 +923,9 @@ async function install_and_check_win32_openssl_version() {
|
|
|
389
923
|
}
|
|
390
924
|
async function download_openssl() {
|
|
391
925
|
const url = win32or64() === 64 ? "https://github.com/node-opcua/node-opcua-pki/releases/download/2.14.2/openssl-1.0.2u-x64_86-win64.zip" : "https://github.com/node-opcua/node-opcua-pki/releases/download/2.14.2/openssl-1.0.2u-i386-win32.zip";
|
|
392
|
-
const outputFilename =
|
|
393
|
-
warningLog(`downloading ${
|
|
394
|
-
if (
|
|
926
|
+
const outputFilename = import_node_path4.default.join(downloadFolder, import_node_path4.default.basename(url));
|
|
927
|
+
warningLog(`downloading ${import_chalk3.default.yellow(url)} to ${outputFilename}`);
|
|
928
|
+
if (import_node_fs4.default.existsSync(outputFilename)) {
|
|
395
929
|
return { downloadedFile: outputFilename };
|
|
396
930
|
}
|
|
397
931
|
const response = await fetch(url, { redirect: "follow" });
|
|
@@ -401,7 +935,7 @@ async function install_and_check_win32_openssl_version() {
|
|
|
401
935
|
const contentLength = parseInt(response.headers.get("content-length") || "0", 10);
|
|
402
936
|
let downloaded = 0;
|
|
403
937
|
let lastPercent = -1;
|
|
404
|
-
const fileStream =
|
|
938
|
+
const fileStream = import_node_fs4.default.createWriteStream(outputFilename);
|
|
405
939
|
const body = response.body;
|
|
406
940
|
body.on("data", (chunk) => {
|
|
407
941
|
downloaded += chunk.length;
|
|
@@ -414,12 +948,12 @@ async function install_and_check_win32_openssl_version() {
|
|
|
414
948
|
}
|
|
415
949
|
});
|
|
416
950
|
await (0, import_promises.pipeline)(body, fileStream);
|
|
417
|
-
const stat =
|
|
951
|
+
const stat = import_node_fs4.default.statSync(outputFilename);
|
|
418
952
|
if (stat.size === 0) {
|
|
419
|
-
|
|
953
|
+
import_node_fs4.default.unlinkSync(outputFilename);
|
|
420
954
|
throw new Error(`Downloaded file is empty: ${outputFilename}`);
|
|
421
955
|
}
|
|
422
|
-
warningLog(
|
|
956
|
+
warningLog(import_chalk3.default.green("Download complete: ") + `${stat.size} bytes`);
|
|
423
957
|
return { downloadedFile: outputFilename };
|
|
424
958
|
}
|
|
425
959
|
async function unzip_openssl(zipFilename) {
|
|
@@ -456,11 +990,11 @@ async function install_and_check_win32_openssl_version() {
|
|
|
456
990
|
if (err) {
|
|
457
991
|
return reject(err);
|
|
458
992
|
}
|
|
459
|
-
const file =
|
|
993
|
+
const file = import_node_path4.default.join(opensslFolder2, entry.fileName);
|
|
460
994
|
if (doDebug2) {
|
|
461
995
|
warningLog(" unzipping :", file);
|
|
462
996
|
}
|
|
463
|
-
const writeStream =
|
|
997
|
+
const writeStream = import_node_fs4.default.createWriteStream(file, "binary");
|
|
464
998
|
readStream?.pipe(writeStream);
|
|
465
999
|
writeStream.on("close", () => {
|
|
466
1000
|
zipFile.readEntry();
|
|
@@ -475,29 +1009,29 @@ async function install_and_check_win32_openssl_version() {
|
|
|
475
1009
|
}
|
|
476
1010
|
const opensslFolder = get_openssl_folder_win32();
|
|
477
1011
|
const opensslExecPath = get_openssl_exec_path_win32();
|
|
478
|
-
if (!
|
|
1012
|
+
if (!import_node_fs4.default.existsSync(opensslFolder)) {
|
|
479
1013
|
if (doDebug2) {
|
|
480
1014
|
warningLog("creating openssl_folder", opensslFolder);
|
|
481
1015
|
}
|
|
482
|
-
|
|
1016
|
+
import_node_fs4.default.mkdirSync(opensslFolder);
|
|
483
1017
|
}
|
|
484
1018
|
const { opensslOk, version: _version } = await check_openssl_win32();
|
|
485
1019
|
if (!opensslOk) {
|
|
486
|
-
warningLog(
|
|
1020
|
+
warningLog(import_chalk3.default.yellow("openssl seems to be missing and need to be installed"));
|
|
487
1021
|
const { downloadedFile } = await download_openssl();
|
|
488
1022
|
if (doDebug2) {
|
|
489
|
-
warningLog("deflating ",
|
|
1023
|
+
warningLog("deflating ", import_chalk3.default.yellow(downloadedFile));
|
|
490
1024
|
}
|
|
491
1025
|
await unzip_openssl(downloadedFile);
|
|
492
|
-
const opensslExists = !!
|
|
1026
|
+
const opensslExists = !!import_node_fs4.default.existsSync(opensslExecPath);
|
|
493
1027
|
if (doDebug2) {
|
|
494
|
-
warningLog("verifying ", opensslExists, opensslExists ?
|
|
1028
|
+
warningLog("verifying ", opensslExists, opensslExists ? import_chalk3.default.green("OK ") : import_chalk3.default.red(" Error"), opensslExecPath);
|
|
495
1029
|
}
|
|
496
1030
|
const _opensslExecPath2 = await check_openssl_win32();
|
|
497
1031
|
return opensslExecPath;
|
|
498
1032
|
} else {
|
|
499
1033
|
if (doDebug2) {
|
|
500
|
-
warningLog(
|
|
1034
|
+
warningLog(import_chalk3.default.green("openssl is already installed and have the expected version."));
|
|
501
1035
|
}
|
|
502
1036
|
return opensslExecPath;
|
|
503
1037
|
}
|
|
@@ -512,7 +1046,7 @@ async function install_prerequisite() {
|
|
|
512
1046
|
async function get_openssl_exec_path() {
|
|
513
1047
|
if (process.platform === "win32") {
|
|
514
1048
|
const opensslExecPath = await install_prerequisite();
|
|
515
|
-
if (!
|
|
1049
|
+
if (!import_node_fs4.default.existsSync(opensslExecPath)) {
|
|
516
1050
|
throw new Error(`internal error cannot find ${opensslExecPath}`);
|
|
517
1051
|
}
|
|
518
1052
|
return opensslExecPath;
|
|
@@ -525,13 +1059,9 @@ async function get_openssl_exec_path() {
|
|
|
525
1059
|
var opensslPath;
|
|
526
1060
|
var n = makePath;
|
|
527
1061
|
var PASSIN_ENV_VAR = "NODE_OPCUA_PKI_OPENSSL_PASSIN";
|
|
528
|
-
var PASSOUT_ENV_VAR = "NODE_OPCUA_PKI_OPENSSL_PASSOUT";
|
|
529
1062
|
function passinArg(passphrase = "") {
|
|
530
1063
|
return { args: ["-passin", `env:${PASSIN_ENV_VAR}`], env: { [PASSIN_ENV_VAR]: passphrase } };
|
|
531
1064
|
}
|
|
532
|
-
function passoutArg(passphrase = "") {
|
|
533
|
-
return { args: ["-passout", `env:${PASSOUT_ENV_VAR}`], env: { [PASSOUT_ENV_VAR]: passphrase } };
|
|
534
|
-
}
|
|
535
1065
|
function renderForDisplay(file, args) {
|
|
536
1066
|
return [file, ...args].map((a) => a === "" || /[\s"'`$\\]/.test(a) ? JSON.stringify(a) : a).join(" ");
|
|
537
1067
|
}
|
|
@@ -539,7 +1069,7 @@ async function execute2(file, args, options) {
|
|
|
539
1069
|
const from = new Error();
|
|
540
1070
|
options.cwd = options.cwd || process.cwd();
|
|
541
1071
|
if (!g_config.silent) {
|
|
542
|
-
warningLog(
|
|
1072
|
+
warningLog(import_chalk4.default.cyan(" CWD "), options.cwd);
|
|
543
1073
|
}
|
|
544
1074
|
const outputs = [];
|
|
545
1075
|
const errorOutputs = [];
|
|
@@ -552,12 +1082,12 @@ async function execute2(file, args, options) {
|
|
|
552
1082
|
stdio: ["ignore", "pipe", "pipe"]
|
|
553
1083
|
});
|
|
554
1084
|
const fail = (message) => {
|
|
555
|
-
if (!options.hideErrorMessage) {
|
|
1085
|
+
if (!options.hideErrorMessage && !g_config.silent) {
|
|
556
1086
|
const fence = "###########################################";
|
|
557
|
-
console.error(
|
|
558
|
-
console.error(
|
|
559
|
-
console.error(
|
|
560
|
-
console.error(
|
|
1087
|
+
console.error(import_chalk4.default.bgWhiteBright.redBright(`${fence} OPENSSL ERROR ${fence}`));
|
|
1088
|
+
console.error(import_chalk4.default.bgWhiteBright.redBright(`CWD = ${options.cwd}`));
|
|
1089
|
+
console.error(import_chalk4.default.bgWhiteBright.redBright(message));
|
|
1090
|
+
console.error(import_chalk4.default.bgWhiteBright.redBright(`${fence} OPENSSL ERROR ${fence}`));
|
|
561
1091
|
console.error(from.stack);
|
|
562
1092
|
}
|
|
563
1093
|
reject(new Error(message));
|
|
@@ -583,7 +1113,7 @@ ${errorOutputs.join("")}(${why})`);
|
|
|
583
1113
|
stream2.on("data", (line) => {
|
|
584
1114
|
line = line.toString();
|
|
585
1115
|
if (doDebug) {
|
|
586
|
-
process.stdout.write(`${
|
|
1116
|
+
process.stdout.write(`${import_chalk4.default.white(" stdout ") + import_chalk4.default.whiteBright(line)}
|
|
587
1117
|
`);
|
|
588
1118
|
}
|
|
589
1119
|
});
|
|
@@ -596,7 +1126,7 @@ ${errorOutputs.join("")}(${why})`);
|
|
|
596
1126
|
errorOutputs.push(`${line}
|
|
597
1127
|
`);
|
|
598
1128
|
if (!g_config.silent && displayError) {
|
|
599
|
-
process.stdout.write(`${
|
|
1129
|
+
process.stdout.write(`${import_chalk4.default.white(" stderr ") + import_chalk4.default.red(line)}
|
|
600
1130
|
`);
|
|
601
1131
|
}
|
|
602
1132
|
});
|
|
@@ -631,154 +1161,26 @@ function getTempFolder() {
|
|
|
631
1161
|
async function execute_openssl(args, options) {
|
|
632
1162
|
debugLog("execute_openssl", args, redactEnvForLog(options));
|
|
633
1163
|
const empty_config_file = n(getTempFolder(), "empty_config.cnf");
|
|
634
|
-
if (!
|
|
635
|
-
await
|
|
1164
|
+
if (!import_node_fs5.default.existsSync(empty_config_file)) {
|
|
1165
|
+
await import_node_fs5.default.promises.writeFile(empty_config_file, "# empty config file");
|
|
636
1166
|
}
|
|
637
1167
|
options = options || {};
|
|
638
1168
|
options.openssl_conf = options.openssl_conf || empty_config_file;
|
|
639
|
-
(0,
|
|
1169
|
+
(0, import_node_assert3.default)(options.openssl_conf);
|
|
640
1170
|
setEnv("OPENSSL_CONF", options.openssl_conf);
|
|
641
1171
|
if (!g_config.silent) {
|
|
642
|
-
warningLog(
|
|
643
|
-
warningLog(
|
|
644
|
-
warningLog(
|
|
1172
|
+
warningLog(import_chalk4.default.cyan(" OPENSSL_CONF"), process.env.OPENSSL_CONF);
|
|
1173
|
+
warningLog(import_chalk4.default.cyan(" RANDFILE "), process.env.RANDFILE);
|
|
1174
|
+
warningLog(import_chalk4.default.cyan(" CMD "), import_chalk4.default.cyanBright(renderForDisplay("openssl", args)));
|
|
645
1175
|
}
|
|
646
1176
|
await ensure_openssl_installed();
|
|
647
1177
|
return await execute2(opensslPath, args, options);
|
|
648
1178
|
}
|
|
649
1179
|
|
|
650
|
-
// packages/node-opcua-pki/lib/
|
|
651
|
-
var n2 = makePath;
|
|
652
|
-
async function createPFX(options) {
|
|
653
|
-
const { certificateFile, privateKeyFile, privateKeyPassphrase, outputFile, passphrase = "", caCertificateFiles } = options;
|
|
654
|
-
(0, import_node_assert3.default)(import_node_fs4.default.existsSync(certificateFile), `Certificate file does not exist: ${certificateFile}`);
|
|
655
|
-
(0, import_node_assert3.default)(import_node_fs4.default.existsSync(privateKeyFile), `Private key file does not exist: ${privateKeyFile}`);
|
|
656
|
-
const args = ["pkcs12", "-export", "-in", n2(certificateFile), "-inkey", n2(privateKeyFile)];
|
|
657
|
-
if (caCertificateFiles) {
|
|
658
|
-
for (const caFile of caCertificateFiles) {
|
|
659
|
-
(0, import_node_assert3.default)(import_node_fs4.default.existsSync(caFile), `CA certificate file does not exist: ${caFile}`);
|
|
660
|
-
args.push("-certfile", n2(caFile));
|
|
661
|
-
}
|
|
662
|
-
}
|
|
663
|
-
const passin = passinArg(privateKeyPassphrase);
|
|
664
|
-
const passout = passoutArg(passphrase);
|
|
665
|
-
args.push("-out", n2(outputFile), ...passin.args, ...passout.args);
|
|
666
|
-
await execute_openssl(args, { env: { ...passin.env, ...passout.env } });
|
|
667
|
-
}
|
|
668
|
-
async function extractCertificateFromPFX(options) {
|
|
669
|
-
const { pfxFile, passphrase = "" } = options;
|
|
670
|
-
(0, import_node_assert3.default)(import_node_fs4.default.existsSync(pfxFile), `PFX file does not exist: ${pfxFile}`);
|
|
671
|
-
const passin = passinArg(passphrase);
|
|
672
|
-
return await execute_openssl(["pkcs12", "-in", n2(pfxFile), "-clcerts", "-nokeys", "-nodes", ...passin.args], {
|
|
673
|
-
env: passin.env
|
|
674
|
-
});
|
|
675
|
-
}
|
|
676
|
-
async function extractPrivateKeyFromPFX(options) {
|
|
677
|
-
const { pfxFile, passphrase = "" } = options;
|
|
678
|
-
(0, import_node_assert3.default)(import_node_fs4.default.existsSync(pfxFile), `PFX file does not exist: ${pfxFile}`);
|
|
679
|
-
const passin = passinArg(passphrase);
|
|
680
|
-
return await execute_openssl(["pkcs12", "-in", n2(pfxFile), "-nocerts", "-nodes", ...passin.args], { env: passin.env });
|
|
681
|
-
}
|
|
682
|
-
async function extractCACertificatesFromPFX(options) {
|
|
683
|
-
const { pfxFile, passphrase = "" } = options;
|
|
684
|
-
(0, import_node_assert3.default)(import_node_fs4.default.existsSync(pfxFile), `PFX file does not exist: ${pfxFile}`);
|
|
685
|
-
const passin = passinArg(passphrase);
|
|
686
|
-
return await execute_openssl(["pkcs12", "-in", n2(pfxFile), "-cacerts", "-nokeys", "-nodes", ...passin.args], {
|
|
687
|
-
env: passin.env
|
|
688
|
-
});
|
|
689
|
-
}
|
|
690
|
-
async function extractAllFromPFX(options) {
|
|
691
|
-
const [certificate, privateKey, caCertificates] = await Promise.all([
|
|
692
|
-
extractCertificateFromPFX(options),
|
|
693
|
-
extractPrivateKeyFromPFX(options),
|
|
694
|
-
extractCACertificatesFromPFX(options)
|
|
695
|
-
]);
|
|
696
|
-
return { certificate, privateKey, caCertificates };
|
|
697
|
-
}
|
|
698
|
-
async function convertPFXtoPEM(pfxFile, pemFile, passphrase = "") {
|
|
699
|
-
(0, import_node_assert3.default)(import_node_fs4.default.existsSync(pfxFile), `PFX file does not exist: ${pfxFile}`);
|
|
700
|
-
const passin = passinArg(passphrase);
|
|
701
|
-
await execute_openssl(["pkcs12", "-in", n2(pfxFile), "-out", n2(pemFile), "-nodes", ...passin.args], { env: passin.env });
|
|
702
|
-
}
|
|
703
|
-
async function dumpPFX(pfxFile, passphrase = "") {
|
|
704
|
-
(0, import_node_assert3.default)(import_node_fs4.default.existsSync(pfxFile), `PFX file does not exist: ${pfxFile}`);
|
|
705
|
-
const passin = passinArg(passphrase);
|
|
706
|
-
return await execute_openssl(["pkcs12", "-in", n2(pfxFile), "-info", "-nodes", ...passin.args], { env: passin.env });
|
|
707
|
-
}
|
|
708
|
-
|
|
709
|
-
// packages/node-opcua-pki/lib/toolbox/common.ts
|
|
1180
|
+
// packages/node-opcua-pki/lib/toolbox/with_openssl/toolbox.ts
|
|
710
1181
|
var import_node_assert4 = __toESM(require("assert"));
|
|
711
|
-
function quote(str) {
|
|
712
|
-
return `"${str || ""}"`;
|
|
713
|
-
}
|
|
714
|
-
async function resolvePrivateKeyPassphrase(passphrase) {
|
|
715
|
-
if (passphrase === void 0) {
|
|
716
|
-
return void 0;
|
|
717
|
-
}
|
|
718
|
-
return typeof passphrase === "function" ? await passphrase() : passphrase;
|
|
719
|
-
}
|
|
720
|
-
function adjustDate(params) {
|
|
721
|
-
(0, import_node_assert4.default)(params instanceof Object);
|
|
722
|
-
params.startDate = params.startDate || /* @__PURE__ */ new Date();
|
|
723
|
-
(0, import_node_assert4.default)(params.startDate instanceof Date);
|
|
724
|
-
if (params.validityMs !== void 0) {
|
|
725
|
-
if (params.validityMs <= 0) {
|
|
726
|
-
throw new RangeError(`validityMs must be > 0 (got ${params.validityMs})`);
|
|
727
|
-
}
|
|
728
|
-
params.endDate = new Date(params.startDate.getTime() + params.validityMs);
|
|
729
|
-
params.validity = Math.ceil(params.validityMs / 864e5);
|
|
730
|
-
} else {
|
|
731
|
-
params.validity = params.validity || 365;
|
|
732
|
-
params.endDate = new Date(params.startDate.getTime());
|
|
733
|
-
params.endDate.setDate(params.startDate.getDate() + params.validity);
|
|
734
|
-
}
|
|
735
|
-
(0, import_node_assert4.default)(params.endDate instanceof Date);
|
|
736
|
-
(0, import_node_assert4.default)(params.startDate instanceof Date);
|
|
737
|
-
}
|
|
738
|
-
function adjustApplicationUri(params) {
|
|
739
|
-
const applicationUri = params.applicationUri || "";
|
|
740
|
-
if (applicationUri.length > 200) {
|
|
741
|
-
throw new Error(`Openssl doesn't support urn with length greater than 200${applicationUri}`);
|
|
742
|
-
}
|
|
743
|
-
}
|
|
744
|
-
|
|
745
|
-
// packages/node-opcua-pki/lib/toolbox/display.ts
|
|
746
|
-
var import_chalk4 = __toESM(require("chalk"));
|
|
747
|
-
function displayTitle(str) {
|
|
748
|
-
if (!g_config.silent) {
|
|
749
|
-
warningLog("");
|
|
750
|
-
warningLog(import_chalk4.default.yellowBright(str));
|
|
751
|
-
warningLog(import_chalk4.default.yellow(new Array(str.length + 1).join("=")), "\n");
|
|
752
|
-
}
|
|
753
|
-
}
|
|
754
|
-
function displaySubtitle(str) {
|
|
755
|
-
if (!g_config.silent) {
|
|
756
|
-
warningLog("");
|
|
757
|
-
warningLog(` ${import_chalk4.default.yellowBright(str)}`);
|
|
758
|
-
warningLog(` ${import_chalk4.default.white(new Array(str.length + 1).join("-"))}`, "\n");
|
|
759
|
-
}
|
|
760
|
-
}
|
|
761
|
-
function display(str) {
|
|
762
|
-
if (!g_config.silent) {
|
|
763
|
-
warningLog(` ${str}`);
|
|
764
|
-
}
|
|
765
|
-
}
|
|
766
|
-
|
|
767
|
-
// packages/node-opcua-pki/lib/toolbox/with_openssl/index.ts
|
|
768
|
-
var import_node_constants = __toESM(require("constants"));
|
|
769
|
-
|
|
770
|
-
// packages/node-opcua-pki/lib/toolbox/with_openssl/create_certificate_signing_request.ts
|
|
771
|
-
var import_node_assert6 = __toESM(require("assert"));
|
|
772
1182
|
var import_node_fs6 = __toESM(require("fs"));
|
|
773
|
-
var
|
|
774
|
-
|
|
775
|
-
// packages/node-opcua-pki/lib/misc/subject.ts
|
|
776
|
-
var import_node_opcua_crypto = require("node-opcua-crypto");
|
|
777
|
-
|
|
778
|
-
// packages/node-opcua-pki/lib/toolbox/with_openssl/toolbox.ts
|
|
779
|
-
var import_node_assert5 = __toESM(require("assert"));
|
|
780
|
-
var import_node_fs5 = __toESM(require("fs"));
|
|
781
|
-
var import_node_path3 = __toESM(require("path"));
|
|
1183
|
+
var import_node_path5 = __toESM(require("path"));
|
|
782
1184
|
function openssl_require2DigitYearInDate() {
|
|
783
1185
|
if (!g_config.opensslVersion) {
|
|
784
1186
|
throw new Error(
|
|
@@ -789,29 +1191,40 @@ function openssl_require2DigitYearInDate() {
|
|
|
789
1191
|
}
|
|
790
1192
|
g_config.opensslVersion = "";
|
|
791
1193
|
var _counter = 0;
|
|
792
|
-
function stripConditionalBlocks(template) {
|
|
1194
|
+
function stripConditionalBlocks(template, envOverrides) {
|
|
793
1195
|
return template.replace(/\{\{#([A-Z_][A-Z0-9_]*)\}\}([\s\S]*?)\{\{\/\1\}\}\r?\n?/g, (_match, key, content) => {
|
|
794
|
-
const keep = hasEnv(key) && getEnv(key) !== "";
|
|
1196
|
+
const keep = envOverrides && Object.prototype.hasOwnProperty.call(envOverrides, key) ? envOverrides[key] !== "" : hasEnv(key) && getEnv(key) !== "";
|
|
795
1197
|
return keep ? content : "";
|
|
796
1198
|
});
|
|
797
1199
|
}
|
|
798
|
-
function generateStaticConfig(configPath, options) {
|
|
1200
|
+
function generateStaticConfig(configPath, options, envOverrides) {
|
|
799
1201
|
const prePath = options?.cwd || "";
|
|
800
|
-
const originalFilename = !
|
|
801
|
-
let staticConfig =
|
|
802
|
-
staticConfig = stripConditionalBlocks(staticConfig);
|
|
1202
|
+
const originalFilename = !import_node_path5.default.isAbsolute(configPath) ? import_node_path5.default.join(prePath, configPath) : configPath;
|
|
1203
|
+
let staticConfig = import_node_fs6.default.readFileSync(originalFilename, { encoding: "utf8" });
|
|
1204
|
+
staticConfig = stripConditionalBlocks(staticConfig, envOverrides);
|
|
803
1205
|
for (const envVar of getEnvironmentVarNames()) {
|
|
804
|
-
|
|
1206
|
+
if (envOverrides && Object.prototype.hasOwnProperty.call(envOverrides, envVar.key)) {
|
|
1207
|
+
continue;
|
|
1208
|
+
}
|
|
1209
|
+
staticConfig = staticConfig.replace(new RegExp(envVar.pattern, "gi"), () => getEnv(envVar.key));
|
|
1210
|
+
}
|
|
1211
|
+
if (envOverrides) {
|
|
1212
|
+
for (const [key, value] of Object.entries(envOverrides)) {
|
|
1213
|
+
staticConfig = staticConfig.replace(new RegExp(`\\$ENV\\:\\:${key}`, "gi"), () => value);
|
|
1214
|
+
}
|
|
805
1215
|
}
|
|
806
1216
|
const staticConfigPath = `${configPath}.${process.pid}-${_counter++}.tmp`;
|
|
807
|
-
const temporaryConfigPath = !
|
|
808
|
-
|
|
1217
|
+
const temporaryConfigPath = !import_node_path5.default.isAbsolute(configPath) ? import_node_path5.default.join(prePath, staticConfigPath) : staticConfigPath;
|
|
1218
|
+
import_node_fs6.default.writeFileSync(temporaryConfigPath, staticConfig);
|
|
809
1219
|
if (options?.cwd) {
|
|
810
|
-
return
|
|
1220
|
+
return import_node_path5.default.relative(options.cwd, temporaryConfigPath);
|
|
811
1221
|
} else {
|
|
812
1222
|
return temporaryConfigPath;
|
|
813
1223
|
}
|
|
814
1224
|
}
|
|
1225
|
+
async function cleanupStaticConfig(configFile, options) {
|
|
1226
|
+
await import_node_fs6.default.promises.rm(import_node_path5.default.resolve(options?.cwd ?? "", configFile), { force: true });
|
|
1227
|
+
}
|
|
815
1228
|
function x509Date(date) {
|
|
816
1229
|
date = date || /* @__PURE__ */ new Date();
|
|
817
1230
|
const Y = date.getUTCFullYear();
|
|
@@ -830,48 +1243,348 @@ function x509Date(date) {
|
|
|
830
1243
|
}
|
|
831
1244
|
}
|
|
832
1245
|
|
|
833
|
-
// packages/node-opcua-pki/lib/
|
|
834
|
-
var
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
(
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
[
|
|
852
|
-
"req",
|
|
853
|
-
"-new",
|
|
854
|
-
"-sha256",
|
|
855
|
-
"-batch",
|
|
856
|
-
"-text",
|
|
857
|
-
"-config",
|
|
858
|
-
n3(configFile),
|
|
859
|
-
"-key",
|
|
860
|
-
n3(params.privateKey),
|
|
861
|
-
...subject ? ["-subj", subject] : [],
|
|
862
|
-
"-out",
|
|
863
|
-
n3(certificateSigningRequestFilename)
|
|
864
|
-
],
|
|
865
|
-
options
|
|
866
|
-
);
|
|
1246
|
+
// packages/node-opcua-pki/lib/ca/backends/openssl_ca_backend.ts
|
|
1247
|
+
var n2 = makePath;
|
|
1248
|
+
function caAltName(ca) {
|
|
1249
|
+
return `URI:urn:${ca.subject.commonName || "NodeOPCUA-CA"}`;
|
|
1250
|
+
}
|
|
1251
|
+
function caConfigEnvOverrides(ca, altName = caAltName(ca)) {
|
|
1252
|
+
const aiaLegs = [];
|
|
1253
|
+
if (ca.ocspResponderUrl) {
|
|
1254
|
+
aiaLegs.push(`OCSP;URI:${ca.ocspResponderUrl}`);
|
|
1255
|
+
}
|
|
1256
|
+
if (ca.caIssuersUrl) {
|
|
1257
|
+
aiaLegs.push(`caIssuers;URI:${ca.caIssuersUrl}`);
|
|
1258
|
+
}
|
|
1259
|
+
return {
|
|
1260
|
+
ALTNAME: altName,
|
|
1261
|
+
CDP_URL: ca.crlDistributionUrl ?? "",
|
|
1262
|
+
AIA_VALUE: aiaLegs.join(",")
|
|
1263
|
+
};
|
|
867
1264
|
}
|
|
1265
|
+
var OpenSslCaBackend = class {
|
|
1266
|
+
/** This backend is the `openssl` executable, so it has to be there. */
|
|
1267
|
+
/**
|
|
1268
|
+
* `-passin env:` argv and env for an openssl call that has to load this
|
|
1269
|
+
* CA's key. Built here rather than on the CA: the passphrase is the
|
|
1270
|
+
* CA's business, but expressing it as an openssl argument is this
|
|
1271
|
+
* backend's, and the core has no reason to know the flag exists.
|
|
1272
|
+
*/
|
|
1273
|
+
async #passin(ca) {
|
|
1274
|
+
return passinArg(await ca._privateKeyPassphrase());
|
|
1275
|
+
}
|
|
1276
|
+
/** The openssl CLI loads its key from a file, so it cannot call out to an HSM. */
|
|
1277
|
+
supportsExternalSigner = false;
|
|
1278
|
+
async preflight() {
|
|
1279
|
+
await ensure_openssl_installed();
|
|
1280
|
+
}
|
|
1281
|
+
/**
|
|
1282
|
+
* Render `conf/caconfig.cnf` once with explicit overrides, run `fn`
|
|
1283
|
+
* with the rendered path, and always remove the temp file. Structural
|
|
1284
|
+
* guarantee that (a) every render carries the three required env
|
|
1285
|
+
* values and (b) no rendered `<name>.<pid>-<n>.tmp` file is ever left
|
|
1286
|
+
* behind — each was previously a per-method discipline.
|
|
1287
|
+
*/
|
|
1288
|
+
async #withConfig(ca, altName, fn) {
|
|
1289
|
+
const caRootDir = import_node_path6.default.resolve(ca.rootDir);
|
|
1290
|
+
const options = { cwd: caRootDir };
|
|
1291
|
+
const configFile = generateStaticConfig("conf/caconfig.cnf", options, caConfigEnvOverrides(ca, altName));
|
|
1292
|
+
try {
|
|
1293
|
+
return await fn(configFile, options);
|
|
1294
|
+
} finally {
|
|
1295
|
+
await cleanupStaticConfig(configFile, options);
|
|
1296
|
+
}
|
|
1297
|
+
}
|
|
1298
|
+
async #generateCaCsrWith(ca, configFile, options, privateKeyFile, csrFile) {
|
|
1299
|
+
const passin = await this.#passin(ca);
|
|
1300
|
+
displayTitle("Generate a certificate request for the CA key");
|
|
1301
|
+
await execute_openssl(
|
|
1302
|
+
[
|
|
1303
|
+
"req",
|
|
1304
|
+
"-new",
|
|
1305
|
+
"-sha256",
|
|
1306
|
+
"-extensions",
|
|
1307
|
+
"v3_ca_req",
|
|
1308
|
+
"-config",
|
|
1309
|
+
n2(configFile),
|
|
1310
|
+
"-key",
|
|
1311
|
+
n2(privateKeyFile),
|
|
1312
|
+
"-out",
|
|
1313
|
+
n2(csrFile),
|
|
1314
|
+
"-subj",
|
|
1315
|
+
ca.subject.toString(),
|
|
1316
|
+
...passin.args
|
|
1317
|
+
],
|
|
1318
|
+
{ ...options, env: passin.env }
|
|
1319
|
+
);
|
|
1320
|
+
}
|
|
1321
|
+
/**
|
|
1322
|
+
* `openssl ca -gencrl` signs the CRL with the CA key it finds through the
|
|
1323
|
+
* config file (`private_key = $dir/private/cakey.pem`), so it needs the
|
|
1324
|
+
* CA passphrase like every other `openssl ca` invocation.
|
|
1325
|
+
*/
|
|
1326
|
+
async #regenerateCrlWith(ca, configFile, options) {
|
|
1327
|
+
const passin = await this.#passin(ca);
|
|
1328
|
+
displaySubtitle("regenerate CRL (Certificate Revocation List)");
|
|
1329
|
+
await execute_openssl(["ca", "-gencrl", "-config", n2(configFile), "-out", "crl/revocation_list.crl", ...passin.args], {
|
|
1330
|
+
...options,
|
|
1331
|
+
env: passin.env
|
|
1332
|
+
});
|
|
1333
|
+
await execute_openssl(
|
|
1334
|
+
["crl", "-in", "crl/revocation_list.crl", "-out", "crl/revocation_list.der", "-outform", "der"],
|
|
1335
|
+
options
|
|
1336
|
+
);
|
|
1337
|
+
displaySubtitle("Display (Certificate Revocation List)");
|
|
1338
|
+
await execute_openssl(["crl", "-in", n2(ca.revocationList), "-text", "-noout"], options);
|
|
1339
|
+
}
|
|
1340
|
+
async generateCaCsr(ca, _caRootDir, privateKeyFile, csrFile) {
|
|
1341
|
+
await this.#withConfig(
|
|
1342
|
+
ca,
|
|
1343
|
+
caAltName(ca),
|
|
1344
|
+
(configFile, options) => this.#generateCaCsrWith(ca, configFile, options, privateKeyFile, csrFile)
|
|
1345
|
+
);
|
|
1346
|
+
}
|
|
1347
|
+
async bootstrap(ca) {
|
|
1348
|
+
const caRootDir = import_node_path6.default.resolve(ca.rootDir);
|
|
1349
|
+
const privateKeyFilename = import_node_path6.default.join(caRootDir, "private/cakey.pem");
|
|
1350
|
+
const csrFilename = import_node_path6.default.join(caRootDir, "private/cakey.csr");
|
|
1351
|
+
await this.#withConfig(ca, caAltName(ca), async (configFile, options) => {
|
|
1352
|
+
await this.#generateCaCsrWith(ca, configFile, options, privateKeyFilename, csrFilename);
|
|
1353
|
+
const issuerCA = ca._issuerCA;
|
|
1354
|
+
if (issuerCA) {
|
|
1355
|
+
displayTitle("Generate CA Certificate (signed by issuer CA)");
|
|
1356
|
+
const issuerCert = import_node_path6.default.resolve(issuerCA.caCertificate);
|
|
1357
|
+
const issuerKey = import_node_path6.default.resolve(issuerCA.rootDir, "private/cakey.pem");
|
|
1358
|
+
const issuerSerial = import_node_path6.default.resolve(issuerCA.rootDir, "serial");
|
|
1359
|
+
const issuerPassin = await this.#passin(issuerCA);
|
|
1360
|
+
const signWithIssuer = async () => {
|
|
1361
|
+
await execute_openssl(
|
|
1362
|
+
[
|
|
1363
|
+
"x509",
|
|
1364
|
+
"-sha256",
|
|
1365
|
+
"-req",
|
|
1366
|
+
"-days",
|
|
1367
|
+
"3650",
|
|
1368
|
+
"-extensions",
|
|
1369
|
+
"v3_ca",
|
|
1370
|
+
"-extfile",
|
|
1371
|
+
n2(configFile),
|
|
1372
|
+
"-in",
|
|
1373
|
+
"private/cakey.csr",
|
|
1374
|
+
"-CA",
|
|
1375
|
+
n2(issuerCert),
|
|
1376
|
+
"-CAkey",
|
|
1377
|
+
n2(issuerKey),
|
|
1378
|
+
"-CAserial",
|
|
1379
|
+
n2(issuerSerial),
|
|
1380
|
+
"-out",
|
|
1381
|
+
"public/cacert.pem",
|
|
1382
|
+
...issuerPassin.args
|
|
1383
|
+
],
|
|
1384
|
+
{ ...options, env: issuerPassin.env }
|
|
1385
|
+
);
|
|
1386
|
+
};
|
|
1387
|
+
if (import_node_path6.default.resolve(issuerCA.rootDir) === caRootDir) {
|
|
1388
|
+
await signWithIssuer();
|
|
1389
|
+
} else {
|
|
1390
|
+
await issuerCA._withCaDirectoryLock(signWithIssuer);
|
|
1391
|
+
}
|
|
1392
|
+
} else {
|
|
1393
|
+
displayTitle("Generate CA Certificate (self-signed)");
|
|
1394
|
+
const passin = await this.#passin(ca);
|
|
1395
|
+
await execute_openssl(
|
|
1396
|
+
[
|
|
1397
|
+
"x509",
|
|
1398
|
+
"-sha256",
|
|
1399
|
+
"-req",
|
|
1400
|
+
"-days",
|
|
1401
|
+
"3650",
|
|
1402
|
+
"-extensions",
|
|
1403
|
+
"v3_ca",
|
|
1404
|
+
"-extfile",
|
|
1405
|
+
n2(configFile),
|
|
1406
|
+
"-in",
|
|
1407
|
+
"private/cakey.csr",
|
|
1408
|
+
"-signkey",
|
|
1409
|
+
n2(privateKeyFilename),
|
|
1410
|
+
"-out",
|
|
1411
|
+
"public/cacert.pem",
|
|
1412
|
+
...passin.args
|
|
1413
|
+
],
|
|
1414
|
+
{ ...options, env: passin.env }
|
|
1415
|
+
);
|
|
1416
|
+
}
|
|
1417
|
+
displaySubtitle("generate initial CRL (Certificate Revocation List)");
|
|
1418
|
+
await this.#regenerateCrlWith(ca, configFile, options);
|
|
1419
|
+
});
|
|
1420
|
+
displayTitle("Create Certificate Authority (CA) ---> DONE");
|
|
1421
|
+
}
|
|
1422
|
+
async regenerateCrl(ca) {
|
|
1423
|
+
await this.#withConfig(ca, caAltName(ca), (configFile, options) => this.#regenerateCrlWith(ca, configFile, options));
|
|
1424
|
+
}
|
|
1425
|
+
async signSubordinateCsr(ca, csrFile, certFile, validityDays) {
|
|
1426
|
+
await this.#withConfig(ca, caAltName(ca), async (configFile, options) => {
|
|
1427
|
+
const caRootDir = options.cwd;
|
|
1428
|
+
const passin = await this.#passin(ca);
|
|
1429
|
+
await execute_openssl(
|
|
1430
|
+
[
|
|
1431
|
+
"x509",
|
|
1432
|
+
"-sha256",
|
|
1433
|
+
"-req",
|
|
1434
|
+
"-days",
|
|
1435
|
+
String(validityDays),
|
|
1436
|
+
"-extensions",
|
|
1437
|
+
"v3_ca",
|
|
1438
|
+
"-extfile",
|
|
1439
|
+
n2(configFile),
|
|
1440
|
+
"-in",
|
|
1441
|
+
n2(csrFile),
|
|
1442
|
+
"-CA",
|
|
1443
|
+
n2(ca.caCertificate),
|
|
1444
|
+
"-CAkey",
|
|
1445
|
+
n2(import_node_path6.default.join(caRootDir, "private/cakey.pem")),
|
|
1446
|
+
"-CAserial",
|
|
1447
|
+
n2(import_node_path6.default.join(caRootDir, "serial")),
|
|
1448
|
+
"-out",
|
|
1449
|
+
n2(certFile),
|
|
1450
|
+
...passin.args
|
|
1451
|
+
],
|
|
1452
|
+
{ ...options, env: passin.env }
|
|
1453
|
+
);
|
|
1454
|
+
});
|
|
1455
|
+
}
|
|
1456
|
+
async signEndEntityCsr(ca, certificate, csr, params1, sanOverride) {
|
|
1457
|
+
await this.#withConfig(ca, buildSubjectAltNameString(sanOverride), async (configFile, options) => {
|
|
1458
|
+
displaySubtitle("- then we ask the authority to sign the certificate signing request");
|
|
1459
|
+
const passin = await this.#passin(ca);
|
|
1460
|
+
await execute_openssl(
|
|
1461
|
+
[
|
|
1462
|
+
"ca",
|
|
1463
|
+
"-config",
|
|
1464
|
+
configFile,
|
|
1465
|
+
"-startdate",
|
|
1466
|
+
x509Date(params1.startDate),
|
|
1467
|
+
"-enddate",
|
|
1468
|
+
x509Date(params1.endDate),
|
|
1469
|
+
"-batch",
|
|
1470
|
+
"-out",
|
|
1471
|
+
n2(certificate),
|
|
1472
|
+
"-in",
|
|
1473
|
+
n2(csr),
|
|
1474
|
+
...passin.args
|
|
1475
|
+
],
|
|
1476
|
+
{ ...options, env: passin.env }
|
|
1477
|
+
);
|
|
1478
|
+
displaySubtitle("- dump the certificate for a check");
|
|
1479
|
+
await execute_openssl(["x509", "-in", n2(certificate), "-dates", "-fingerprint", "-purpose", "-noout"], options);
|
|
1480
|
+
});
|
|
1481
|
+
}
|
|
1482
|
+
async revoke(ca, certificate, reason) {
|
|
1483
|
+
setEnv("RANDFILE", import_node_path6.default.join(ca.rootDir, "random.rnd"));
|
|
1484
|
+
await this.#withConfig(ca, caAltName(ca), async (configFile, options) => {
|
|
1485
|
+
displaySubtitle("Revoke certificate");
|
|
1486
|
+
const passin = await this.#passin(ca);
|
|
1487
|
+
await execute_openssl_no_failure(
|
|
1488
|
+
["ca", "-verbose", "-config", n2(configFile), "-revoke", certificate, "-crl_reason", reason, ...passin.args],
|
|
1489
|
+
{ ...options, env: passin.env }
|
|
1490
|
+
);
|
|
1491
|
+
await this.#regenerateCrlWith(ca, configFile, options);
|
|
1492
|
+
displaySubtitle("Verify that certificate is revoked");
|
|
1493
|
+
await execute_openssl_no_failure(
|
|
1494
|
+
[
|
|
1495
|
+
"verify",
|
|
1496
|
+
"-verbose",
|
|
1497
|
+
"-CRLfile",
|
|
1498
|
+
n2(ca.revocationList),
|
|
1499
|
+
"-CAfile",
|
|
1500
|
+
n2(ca.caCertificate),
|
|
1501
|
+
"-crl_check",
|
|
1502
|
+
n2(certificate)
|
|
1503
|
+
],
|
|
1504
|
+
options
|
|
1505
|
+
);
|
|
1506
|
+
displaySubtitle("Produce CRL in DER form ");
|
|
1507
|
+
await execute_openssl(
|
|
1508
|
+
["crl", "-in", n2(ca.revocationList), "-out", "crl/revocation_list.der", "-outform", "der"],
|
|
1509
|
+
options
|
|
1510
|
+
);
|
|
1511
|
+
displaySubtitle("Produce CRL in PEM form ");
|
|
1512
|
+
await execute_openssl(
|
|
1513
|
+
["crl", "-in", n2(ca.revocationList), "-out", "crl/revocation_list.pem", "-outform", "pem", "-text"],
|
|
1514
|
+
options
|
|
1515
|
+
);
|
|
1516
|
+
});
|
|
1517
|
+
}
|
|
1518
|
+
async createSelfSignedCertificate(ca, certificateFile, privateKeyFile, params) {
|
|
1519
|
+
const envOverrides = caConfigEnvOverrides(ca, buildSubjectAltNameString(params));
|
|
1520
|
+
const configFile = generateStaticConfig(ca.configFile, { cwd: ca.rootDir }, envOverrides);
|
|
1521
|
+
const options = {
|
|
1522
|
+
cwd: ca.rootDir,
|
|
1523
|
+
openssl_conf: makePath(configFile)
|
|
1524
|
+
};
|
|
1525
|
+
try {
|
|
1526
|
+
const subject = params.subject ? new import_node_opcua_crypto4.Subject(params.subject).toString() : "";
|
|
1527
|
+
const subjectOptions = subject && subject.length > 1 ? ["-subj", subject] : [];
|
|
1528
|
+
const csrFile = `${certificateFile}_csr`;
|
|
1529
|
+
const passin = await this.#passin(ca);
|
|
1530
|
+
displaySubtitle("- the certificate signing request");
|
|
1531
|
+
await execute_openssl(
|
|
1532
|
+
[
|
|
1533
|
+
"req",
|
|
1534
|
+
"-new",
|
|
1535
|
+
"-sha256",
|
|
1536
|
+
...subjectOptions,
|
|
1537
|
+
"-batch",
|
|
1538
|
+
"-key",
|
|
1539
|
+
n2(privateKeyFile),
|
|
1540
|
+
"-out",
|
|
1541
|
+
n2(csrFile),
|
|
1542
|
+
...passin.args
|
|
1543
|
+
],
|
|
1544
|
+
{ ...options, env: passin.env }
|
|
1545
|
+
);
|
|
1546
|
+
displaySubtitle("- creating the self-signed certificate");
|
|
1547
|
+
await execute_openssl(
|
|
1548
|
+
[
|
|
1549
|
+
"ca",
|
|
1550
|
+
"-selfsign",
|
|
1551
|
+
"-keyfile",
|
|
1552
|
+
n2(privateKeyFile),
|
|
1553
|
+
"-startdate",
|
|
1554
|
+
x509Date(params.startDate),
|
|
1555
|
+
"-enddate",
|
|
1556
|
+
x509Date(params.endDate),
|
|
1557
|
+
"-batch",
|
|
1558
|
+
"-out",
|
|
1559
|
+
n2(certificateFile),
|
|
1560
|
+
"-in",
|
|
1561
|
+
n2(csrFile),
|
|
1562
|
+
...passin.args
|
|
1563
|
+
],
|
|
1564
|
+
{ ...options, env: passin.env }
|
|
1565
|
+
);
|
|
1566
|
+
displaySubtitle("- dump the certificate for a check");
|
|
1567
|
+
await execute_openssl(["x509", "-in", n2(certificateFile), "-dates", "-fingerprint", "-purpose", "-noout"], {});
|
|
1568
|
+
displaySubtitle("- verify self-signed certificate");
|
|
1569
|
+
await execute_openssl_no_failure(["verify", "-verbose", "-CAfile", n2(certificateFile), n2(certificateFile)], options);
|
|
1570
|
+
await import_node_fs7.default.promises.unlink(csrFile);
|
|
1571
|
+
} finally {
|
|
1572
|
+
await cleanupStaticConfig(configFile, { cwd: ca.rootDir });
|
|
1573
|
+
}
|
|
1574
|
+
}
|
|
1575
|
+
};
|
|
868
1576
|
|
|
869
|
-
// packages/node-opcua-pki/lib/
|
|
870
|
-
var
|
|
871
|
-
var
|
|
1577
|
+
// packages/node-opcua-pki/lib/ca/core/certificate_authority_core.ts
|
|
1578
|
+
var import_node_assert5 = __toESM(require("assert"));
|
|
1579
|
+
var import_node_fs8 = __toESM(require("fs"));
|
|
1580
|
+
var import_node_os3 = __toESM(require("os"));
|
|
1581
|
+
var import_node_path7 = __toESM(require("path"));
|
|
1582
|
+
var import_global_mutex = require("@ster5/global-mutex");
|
|
1583
|
+
var import_chalk5 = __toESM(require("chalk"));
|
|
1584
|
+
var import_node_opcua_crypto5 = require("node-opcua-crypto");
|
|
872
1585
|
|
|
873
1586
|
// packages/node-opcua-pki/lib/ca/templates/ca_config_template.cnf.ts
|
|
874
|
-
var
|
|
1587
|
+
var config = `#.........DO NOT MODIFY BY HAND .........................
|
|
875
1588
|
[ ca ]
|
|
876
1589
|
default_ca = CA_default
|
|
877
1590
|
[ CA_default ]
|
|
@@ -896,7 +1609,7 @@ policy = policy_match
|
|
|
896
1609
|
# default_enddate = YYMMDDHHMMSSZ
|
|
897
1610
|
crl_dir = $dir/crl
|
|
898
1611
|
crl_extensions = crl_ext
|
|
899
|
-
crl = $dir/revocation_list.crl # the Revocation list
|
|
1612
|
+
crl = $dir/crl/revocation_list.crl # the Revocation list
|
|
900
1613
|
crlnumber = $dir/crlnumber # CRL number file
|
|
901
1614
|
default_crl_days = 30
|
|
902
1615
|
default_crl_hours = 24
|
|
@@ -1002,197 +1715,28 @@ subjectAltName = $ENV::ALTNAME
|
|
|
1002
1715
|
#issuerAltName = issuer:copy
|
|
1003
1716
|
authorityKeyIdentifier = keyid:always,issuer:always
|
|
1004
1717
|
#authorityInfoAccess = @issuer_info`;
|
|
1005
|
-
var ca_config_template_cnf_default =
|
|
1718
|
+
var ca_config_template_cnf_default = config;
|
|
1006
1719
|
|
|
1007
|
-
// packages/node-opcua-pki/lib/ca/
|
|
1720
|
+
// packages/node-opcua-pki/lib/ca/core/certificate_authority_core.ts
|
|
1008
1721
|
var defaultSubject = "/C=FR/ST=IDF/L=Paris/O=Local NODE-OPCUA Certificate Authority/CN=NodeOPCUA-CA";
|
|
1009
1722
|
var configurationFileTemplate = ca_config_template_cnf_default;
|
|
1010
|
-
var configurationFileSimpleTemplate = simple_config_template_cnf_default;
|
|
1011
1723
|
function escapeOpensslConfDoubleQuoted(value) {
|
|
1012
1724
|
return value.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
|
|
1013
1725
|
}
|
|
1014
1726
|
function renderCaConfig(caRootDir) {
|
|
1015
1727
|
return configurationFileTemplate.replace(/%%ROOT_FOLDER%%/, escapeOpensslConfDoubleQuoted(makePath(caRootDir)));
|
|
1016
1728
|
}
|
|
1017
|
-
var
|
|
1729
|
+
var config2 = {
|
|
1018
1730
|
certificateDir: "INVALID",
|
|
1019
1731
|
forceCA: false,
|
|
1020
1732
|
pkiDir: "INVALID"
|
|
1021
1733
|
};
|
|
1022
|
-
var
|
|
1734
|
+
var CA_LOCK_MAX_WAIT_MS = 5 * 6e4;
|
|
1735
|
+
var CA_LOCK_RETRY = { minTimeout: 20, maxTimeout: 250 };
|
|
1023
1736
|
function octetStringToIpAddress(a) {
|
|
1024
1737
|
return parseInt(a.substring(0, 2), 16).toString() + "." + parseInt(a.substring(2, 4), 16).toString() + "." + parseInt(a.substring(4, 6), 16).toString() + "." + parseInt(a.substring(6, 8), 16).toString();
|
|
1025
1738
|
}
|
|
1026
|
-
(0,
|
|
1027
|
-
async function construct_CertificateAuthority(certificateAuthority) {
|
|
1028
|
-
const subject = certificateAuthority.subject;
|
|
1029
|
-
const caRootDir = import_node_path5.default.resolve(certificateAuthority.rootDir);
|
|
1030
|
-
async function make_folders() {
|
|
1031
|
-
mkdirRecursiveSync(caRootDir);
|
|
1032
|
-
ensurePrivateDirectory(import_node_path5.default.join(caRootDir, "private"));
|
|
1033
|
-
mkdirRecursiveSync(import_node_path5.default.join(caRootDir, "public"));
|
|
1034
|
-
mkdirRecursiveSync(import_node_path5.default.join(caRootDir, "certs"));
|
|
1035
|
-
mkdirRecursiveSync(import_node_path5.default.join(caRootDir, "crl"));
|
|
1036
|
-
mkdirRecursiveSync(import_node_path5.default.join(caRootDir, "conf"));
|
|
1037
|
-
}
|
|
1038
|
-
await make_folders();
|
|
1039
|
-
async function construct_default_files() {
|
|
1040
|
-
const serial = import_node_path5.default.join(caRootDir, "serial");
|
|
1041
|
-
if (!import_node_fs7.default.existsSync(serial)) {
|
|
1042
|
-
await import_node_fs7.default.promises.writeFile(serial, "1000");
|
|
1043
|
-
}
|
|
1044
|
-
const crlNumber = import_node_path5.default.join(caRootDir, "crlnumber");
|
|
1045
|
-
if (!import_node_fs7.default.existsSync(crlNumber)) {
|
|
1046
|
-
await import_node_fs7.default.promises.writeFile(crlNumber, "1000");
|
|
1047
|
-
}
|
|
1048
|
-
const indexFile = import_node_path5.default.join(caRootDir, "index.txt");
|
|
1049
|
-
if (!import_node_fs7.default.existsSync(indexFile)) {
|
|
1050
|
-
await import_node_fs7.default.promises.writeFile(indexFile, "");
|
|
1051
|
-
}
|
|
1052
|
-
}
|
|
1053
|
-
await construct_default_files();
|
|
1054
|
-
const caKeyExists = import_node_fs7.default.existsSync(import_node_path5.default.join(caRootDir, "private/cakey.pem"));
|
|
1055
|
-
const caCertExists = import_node_fs7.default.existsSync(import_node_path5.default.join(caRootDir, "public/cacert.pem"));
|
|
1056
|
-
if (caKeyExists && caCertExists && !config3.forceCA) {
|
|
1057
|
-
restrictPrivateFilePermissions(import_node_path5.default.join(caRootDir, "private/cakey.pem"), 384);
|
|
1058
|
-
await certificateAuthority._ensurePrivateKeyProtection();
|
|
1059
|
-
debugLog("CA private key and certificate already exist ... skipping");
|
|
1060
|
-
return;
|
|
1061
|
-
}
|
|
1062
|
-
if (caKeyExists && !caCertExists) {
|
|
1063
|
-
debugLog("CA private key exists but cacert.pem is missing \u2014 rebuilding CA");
|
|
1064
|
-
import_node_fs7.default.unlinkSync(import_node_path5.default.join(caRootDir, "private/cakey.pem"));
|
|
1065
|
-
const staleCsr = import_node_path5.default.join(caRootDir, "private/cakey.csr");
|
|
1066
|
-
if (import_node_fs7.default.existsSync(staleCsr)) {
|
|
1067
|
-
import_node_fs7.default.unlinkSync(staleCsr);
|
|
1068
|
-
}
|
|
1069
|
-
}
|
|
1070
|
-
displayTitle("Create Certificate Authority (CA)");
|
|
1071
|
-
const indexFileAttr = import_node_path5.default.join(caRootDir, "index.txt.attr");
|
|
1072
|
-
if (!import_node_fs7.default.existsSync(indexFileAttr)) {
|
|
1073
|
-
await import_node_fs7.default.promises.writeFile(indexFileAttr, "unique_subject = no");
|
|
1074
|
-
}
|
|
1075
|
-
const caConfigFile = certificateAuthority.configFile;
|
|
1076
|
-
if (1) {
|
|
1077
|
-
await import_node_fs7.default.promises.writeFile(caConfigFile, renderCaConfig(caRootDir));
|
|
1078
|
-
}
|
|
1079
|
-
const subjectOpt = ["-subj", subject.toString()];
|
|
1080
|
-
const caCommonName = subject.commonName || "NodeOPCUA-CA";
|
|
1081
|
-
setEnv("ALTNAME", `URI:urn:${caCommonName}`);
|
|
1082
|
-
certificateAuthority._wireRevocationEnvVars();
|
|
1083
|
-
const options = { cwd: caRootDir };
|
|
1084
|
-
const configFile = generateStaticConfig("conf/caconfig.cnf", options);
|
|
1085
|
-
const configOption = ["-config", n4(configFile)];
|
|
1086
|
-
const keySize = certificateAuthority.keySize;
|
|
1087
|
-
const privateKeyFilename = import_node_path5.default.join(caRootDir, "private/cakey.pem");
|
|
1088
|
-
const csrFilename = import_node_path5.default.join(caRootDir, "private/cakey.csr");
|
|
1089
|
-
displayTitle(`Generate the CA private Key - ${keySize}`);
|
|
1090
|
-
const passin = await certificateAuthority._opensslPassin();
|
|
1091
|
-
await (0, import_node_opcua_crypto2.generatePrivateKeyFile)(privateKeyFilename, keySize, { passphrase: await certificateAuthority._privateKeyPassphrase() });
|
|
1092
|
-
restrictPrivateFilePermissions(privateKeyFilename, 384);
|
|
1093
|
-
displayTitle("Generate a certificate request for the CA key");
|
|
1094
|
-
await execute_openssl(
|
|
1095
|
-
[
|
|
1096
|
-
"req",
|
|
1097
|
-
"-new",
|
|
1098
|
-
"-sha256",
|
|
1099
|
-
"-text",
|
|
1100
|
-
"-extensions",
|
|
1101
|
-
"v3_ca_req",
|
|
1102
|
-
...configOption,
|
|
1103
|
-
"-key",
|
|
1104
|
-
n4(privateKeyFilename),
|
|
1105
|
-
"-out",
|
|
1106
|
-
n4(csrFilename),
|
|
1107
|
-
...subjectOpt,
|
|
1108
|
-
...passin.args
|
|
1109
|
-
],
|
|
1110
|
-
{ ...options, env: passin.env }
|
|
1111
|
-
);
|
|
1112
|
-
const issuerCA = certificateAuthority._issuerCA;
|
|
1113
|
-
if (issuerCA) {
|
|
1114
|
-
displayTitle("Generate CA Certificate (signed by issuer CA)");
|
|
1115
|
-
const issuerCert = import_node_path5.default.resolve(issuerCA.caCertificate);
|
|
1116
|
-
const issuerKey = import_node_path5.default.resolve(issuerCA.rootDir, "private/cakey.pem");
|
|
1117
|
-
const issuerSerial = import_node_path5.default.resolve(issuerCA.rootDir, "serial");
|
|
1118
|
-
const issuerPassin = await issuerCA._opensslPassin();
|
|
1119
|
-
await execute_openssl(
|
|
1120
|
-
[
|
|
1121
|
-
"x509",
|
|
1122
|
-
"-sha256",
|
|
1123
|
-
"-req",
|
|
1124
|
-
"-days",
|
|
1125
|
-
"3650",
|
|
1126
|
-
"-text",
|
|
1127
|
-
"-extensions",
|
|
1128
|
-
"v3_ca",
|
|
1129
|
-
"-extfile",
|
|
1130
|
-
n4(configFile),
|
|
1131
|
-
"-in",
|
|
1132
|
-
"private/cakey.csr",
|
|
1133
|
-
"-CA",
|
|
1134
|
-
n4(issuerCert),
|
|
1135
|
-
"-CAkey",
|
|
1136
|
-
n4(issuerKey),
|
|
1137
|
-
"-CAserial",
|
|
1138
|
-
n4(issuerSerial),
|
|
1139
|
-
"-out",
|
|
1140
|
-
"public/cacert.pem",
|
|
1141
|
-
...issuerPassin.args
|
|
1142
|
-
],
|
|
1143
|
-
{ ...options, env: issuerPassin.env }
|
|
1144
|
-
);
|
|
1145
|
-
} else {
|
|
1146
|
-
displayTitle("Generate CA Certificate (self-signed)");
|
|
1147
|
-
await execute_openssl(
|
|
1148
|
-
[
|
|
1149
|
-
"x509",
|
|
1150
|
-
"-sha256",
|
|
1151
|
-
"-req",
|
|
1152
|
-
"-days",
|
|
1153
|
-
"3650",
|
|
1154
|
-
"-text",
|
|
1155
|
-
"-extensions",
|
|
1156
|
-
"v3_ca",
|
|
1157
|
-
"-extfile",
|
|
1158
|
-
n4(configFile),
|
|
1159
|
-
"-in",
|
|
1160
|
-
"private/cakey.csr",
|
|
1161
|
-
"-signkey",
|
|
1162
|
-
n4(privateKeyFilename),
|
|
1163
|
-
"-out",
|
|
1164
|
-
"public/cacert.pem",
|
|
1165
|
-
...passin.args
|
|
1166
|
-
],
|
|
1167
|
-
{ ...options, env: passin.env }
|
|
1168
|
-
);
|
|
1169
|
-
}
|
|
1170
|
-
displaySubtitle("generate initial CRL (Certificate Revocation List)");
|
|
1171
|
-
await regenerateCrl(certificateAuthority.revocationList, configOption, options, passin);
|
|
1172
|
-
displayTitle("Create Certificate Authority (CA) ---> DONE");
|
|
1173
|
-
}
|
|
1174
|
-
async function regenerateCrl(revocationList, configOption, options, passin) {
|
|
1175
|
-
displaySubtitle("regenerate CRL (Certificate Revocation List)");
|
|
1176
|
-
await execute_openssl(["ca", "-gencrl", ...configOption, "-out", "crl/revocation_list.crl", ...passin.args], {
|
|
1177
|
-
...options,
|
|
1178
|
-
env: passin.env
|
|
1179
|
-
});
|
|
1180
|
-
await execute_openssl(["crl", "-in", "crl/revocation_list.crl", "-out", "crl/revocation_list.der", "-outform", "der"], options);
|
|
1181
|
-
displaySubtitle("Display (Certificate Revocation List)");
|
|
1182
|
-
await execute_openssl(["crl", "-in", n4(revocationList), "-text", "-noout"], options);
|
|
1183
|
-
}
|
|
1184
|
-
function parseOpenSSLDate(dateStr) {
|
|
1185
|
-
const raw = dateStr?.split(",")[0] ?? "";
|
|
1186
|
-
if (raw.length < 12) return "";
|
|
1187
|
-
const yy = parseInt(raw.substring(0, 2), 10);
|
|
1188
|
-
const year = yy >= 70 ? 1900 + yy : 2e3 + yy;
|
|
1189
|
-
const month = raw.substring(2, 4);
|
|
1190
|
-
const day = raw.substring(4, 6);
|
|
1191
|
-
const hour = raw.substring(6, 8);
|
|
1192
|
-
const min = raw.substring(8, 10);
|
|
1193
|
-
const sec = raw.substring(10, 12);
|
|
1194
|
-
return `${year}-${month}-${day}T${hour}:${min}:${sec}Z`;
|
|
1195
|
-
}
|
|
1739
|
+
(0, import_node_assert5.default)(octetStringToIpAddress("c07b9179") === "192.123.145.121");
|
|
1196
1740
|
function validateRevocationUrl(url, fieldName) {
|
|
1197
1741
|
if (url === void 0) {
|
|
1198
1742
|
return void 0;
|
|
@@ -1220,7 +1764,7 @@ function validateRevocationUrl(url, fieldName) {
|
|
|
1220
1764
|
}
|
|
1221
1765
|
return url;
|
|
1222
1766
|
}
|
|
1223
|
-
var
|
|
1767
|
+
var CertificateAuthorityCore = class {
|
|
1224
1768
|
/** RSA key size used when generating the CA private key. */
|
|
1225
1769
|
keySize;
|
|
1226
1770
|
/** Root filesystem path of the CA directory structure. */
|
|
@@ -1237,14 +1781,41 @@ var CertificateAuthority = class {
|
|
|
1237
1781
|
/** resolved once (see `privateKeyPassphrase`); `#passphraseResolved` distinguishes "none" from "not yet" */
|
|
1238
1782
|
#resolvedPassphrase;
|
|
1239
1783
|
#passphraseResolved = false;
|
|
1784
|
+
/** Signing backend: `openssl` shells out to the CLI, `native` signs in-process. */
|
|
1785
|
+
#backend;
|
|
1786
|
+
/** External signing key (HSM/KMS), when one was supplied instead of a key file. */
|
|
1787
|
+
#signer;
|
|
1788
|
+
/** Read access to `index.txt` / `certs/<SERIAL>.pem`. */
|
|
1789
|
+
#db;
|
|
1240
1790
|
constructor(options) {
|
|
1241
|
-
(0,
|
|
1242
|
-
(0,
|
|
1791
|
+
(0, import_node_assert5.default)(Object.prototype.hasOwnProperty.call(options, "location"));
|
|
1792
|
+
(0, import_node_assert5.default)(Object.prototype.hasOwnProperty.call(options, "keySize"));
|
|
1243
1793
|
this.location = options.location;
|
|
1244
1794
|
this.keySize = options.keySize || 2048;
|
|
1245
|
-
this.subject = new
|
|
1795
|
+
this.subject = new import_node_opcua_crypto5.Subject(options.subject || defaultSubject);
|
|
1246
1796
|
this._issuerCA = options.issuerCA;
|
|
1247
1797
|
this.#privateKeyPassphrase = options.privateKeyPassphrase;
|
|
1798
|
+
this.#signer = options.signer;
|
|
1799
|
+
if (options.signer) {
|
|
1800
|
+
const algorithm = options.signer.algorithm;
|
|
1801
|
+
if (algorithm.name !== "RSASSA-PKCS1-v1_5" && algorithm.name !== "ECDSA") {
|
|
1802
|
+
throw new Error(
|
|
1803
|
+
`CertificateAuthority: signer algorithm ${algorithm.name} is not supported - use RSASSA-PKCS1-v1_5 or ECDSA.`
|
|
1804
|
+
);
|
|
1805
|
+
}
|
|
1806
|
+
if (algorithm.name === "ECDSA" && !algorithm.namedCurve) {
|
|
1807
|
+
throw new Error(
|
|
1808
|
+
"CertificateAuthority: an ECDSA signer must declare its namedCurve (P-256, P-384 or P-521) - importing the signer's public key needs the curve, and an SPKI import cannot infer it."
|
|
1809
|
+
);
|
|
1810
|
+
}
|
|
1811
|
+
}
|
|
1812
|
+
if (options.signer && !options.backend.supportsExternalSigner) {
|
|
1813
|
+
throw new Error(
|
|
1814
|
+
"CertificateAuthority: this backend cannot sign with an external signer - it loads its key from a file. Use a backend that supports one, such as NativeCaBackend."
|
|
1815
|
+
);
|
|
1816
|
+
}
|
|
1817
|
+
this.#backend = options.backend;
|
|
1818
|
+
this.#db = new CaDatabase(this.location);
|
|
1248
1819
|
if (options.crlDistributionUrl !== void 0) {
|
|
1249
1820
|
this.setCrlDistributionUrl(options.crlDistributionUrl);
|
|
1250
1821
|
}
|
|
@@ -1308,42 +1879,17 @@ var CertificateAuthority = class {
|
|
|
1308
1879
|
setCaIssuersUrl(url) {
|
|
1309
1880
|
this._caIssuersUrl = validateRevocationUrl(url, "caIssuersUrl");
|
|
1310
1881
|
}
|
|
1311
|
-
/**
|
|
1312
|
-
* @internal
|
|
1313
|
-
* Populate the OpenSSL config substitution env vars (`CDP_URL` and
|
|
1314
|
-
* `AIA_VALUE`) from the configured URLs, or unset them so the
|
|
1315
|
-
* matching `{{#KEY}}...{{/KEY}}` blocks in the templates are
|
|
1316
|
-
* stripped. MUST be called before every `generateStaticConfig`
|
|
1317
|
-
* invocation that signs a certificate.
|
|
1318
|
-
*/
|
|
1319
|
-
_wireRevocationEnvVars() {
|
|
1320
|
-
unsetEnv("CDP_URL");
|
|
1321
|
-
unsetEnv("AIA_VALUE");
|
|
1322
|
-
if (this._crlDistributionUrl) {
|
|
1323
|
-
setEnv("CDP_URL", this._crlDistributionUrl);
|
|
1324
|
-
}
|
|
1325
|
-
const aiaLegs = [];
|
|
1326
|
-
if (this._ocspResponderUrl) {
|
|
1327
|
-
aiaLegs.push(`OCSP;URI:${this._ocspResponderUrl}`);
|
|
1328
|
-
}
|
|
1329
|
-
if (this._caIssuersUrl) {
|
|
1330
|
-
aiaLegs.push(`caIssuers;URI:${this._caIssuersUrl}`);
|
|
1331
|
-
}
|
|
1332
|
-
if (aiaLegs.length > 0) {
|
|
1333
|
-
setEnv("AIA_VALUE", aiaLegs.join(","));
|
|
1334
|
-
}
|
|
1335
|
-
}
|
|
1336
1882
|
/** Absolute path to the CA root directory (alias for {@link location}). */
|
|
1337
1883
|
get rootDir() {
|
|
1338
1884
|
return this.location;
|
|
1339
1885
|
}
|
|
1340
1886
|
/** Path to the OpenSSL configuration file (`conf/caconfig.cnf`). */
|
|
1341
1887
|
get configFile() {
|
|
1342
|
-
return
|
|
1888
|
+
return import_node_path7.default.normalize(import_node_path7.default.join(this.rootDir, "./conf/caconfig.cnf"));
|
|
1343
1889
|
}
|
|
1344
1890
|
/** Path to the CA private key (`private/cakey.pem`); may be passphrase-encrypted, see {@link getPrivateKey}. */
|
|
1345
1891
|
get privateKey() {
|
|
1346
|
-
return
|
|
1892
|
+
return import_node_path7.default.join(import_node_path7.default.resolve(this.rootDir), "private/cakey.pem");
|
|
1347
1893
|
}
|
|
1348
1894
|
/**
|
|
1349
1895
|
* The CA private key, decrypted with the configured `privateKeyPassphrase`
|
|
@@ -1351,7 +1897,51 @@ var CertificateAuthority = class {
|
|
|
1351
1897
|
* on an encrypted key with no or the wrong passphrase.
|
|
1352
1898
|
*/
|
|
1353
1899
|
async getPrivateKey() {
|
|
1354
|
-
|
|
1900
|
+
this.#assertKeyIsOnDisk("getPrivateKey");
|
|
1901
|
+
return (0, import_node_opcua_crypto5.readPrivateKey)(this.privateKey, await this._privateKeyPassphrase());
|
|
1902
|
+
}
|
|
1903
|
+
/**
|
|
1904
|
+
* True when this CA signs with an external {@link CaSigner} (HSM/KMS)
|
|
1905
|
+
* rather than with `private/cakey.pem`. There is then no private key
|
|
1906
|
+
* file on disk, and never was one.
|
|
1907
|
+
*/
|
|
1908
|
+
get hasExternalSigner() {
|
|
1909
|
+
return this.#signer !== void 0;
|
|
1910
|
+
}
|
|
1911
|
+
/**
|
|
1912
|
+
* Does `cert` certify the key this CA signs with? With the key on disk
|
|
1913
|
+
* that is a private-key match; with a signer there is no private key to
|
|
1914
|
+
* match, so compare the certified public key against the signer's own -
|
|
1915
|
+
* the same question, asked the only way an HSM allows.
|
|
1916
|
+
*/
|
|
1917
|
+
async #certificateMatchesOurKey(certPem, certDer) {
|
|
1918
|
+
if (this.#signer) {
|
|
1919
|
+
const ours = Buffer.from(await this.#signer.getPublicKey());
|
|
1920
|
+
const certified = Buffer.from(new import_node_opcua_crypto5.x509.X509Certificate(certPem).publicKey.rawData);
|
|
1921
|
+
return ours.equals(certified);
|
|
1922
|
+
}
|
|
1923
|
+
return (0, import_node_opcua_crypto5.certificateMatchesPrivateKey)(certDer, await this.getPrivateKey());
|
|
1924
|
+
}
|
|
1925
|
+
/** Reject the key-file-only operations up front, rather than failing on a missing file later. */
|
|
1926
|
+
#assertKeyIsOnDisk(what) {
|
|
1927
|
+
if (this.#signer) {
|
|
1928
|
+
throw new Error(
|
|
1929
|
+
`CertificateAuthority.${what} is not available on a signer-backed CA: the private key lives in the signer (HSM/KMS) and is never written to disk.`
|
|
1930
|
+
);
|
|
1931
|
+
}
|
|
1932
|
+
}
|
|
1933
|
+
/**
|
|
1934
|
+
* @internal The key every signing operation goes through. An injected
|
|
1935
|
+
* {@link CaSigner} is returned as-is; otherwise the on-disk key is read
|
|
1936
|
+
* (and decrypted) and handed over as a plain `CryptoKey`. Both are
|
|
1937
|
+
* accepted by the `node-opcua-crypto` signing primitives, so callers
|
|
1938
|
+
* never branch on which one they got.
|
|
1939
|
+
*/
|
|
1940
|
+
async _getSigningKey() {
|
|
1941
|
+
if (this.#signer) {
|
|
1942
|
+
return this.#signer;
|
|
1943
|
+
}
|
|
1944
|
+
return (0, import_node_opcua_crypto5.privateKeyToCryptoKey)(await this.getPrivateKey());
|
|
1355
1945
|
}
|
|
1356
1946
|
/**
|
|
1357
1947
|
* Enable, disable, or rotate the passphrase protecting `private/cakey.pem`
|
|
@@ -1360,18 +1950,19 @@ var CertificateAuthority = class {
|
|
|
1360
1950
|
* passphrase to continue using it.
|
|
1361
1951
|
*/
|
|
1362
1952
|
async reencryptPrivateKey(oldPassphrase, newPassphrase) {
|
|
1953
|
+
this.#assertKeyIsOnDisk("reencryptPrivateKey");
|
|
1363
1954
|
const oldPass = await resolvePrivateKeyPassphrase(oldPassphrase);
|
|
1364
1955
|
const newPass = await resolvePrivateKeyPassphrase(newPassphrase);
|
|
1365
|
-
const key = (0,
|
|
1956
|
+
const key = (0, import_node_opcua_crypto5.readPrivateKey)(this.privateKey, oldPass);
|
|
1366
1957
|
await this.#rewritePrivateKeyFile(key, newPass);
|
|
1367
1958
|
}
|
|
1368
1959
|
async #rewritePrivateKeyFile(privateKey, passphrase) {
|
|
1369
1960
|
const tmpFilename = `${this.privateKey}.${process.pid}-${Date.now()}.tmp`;
|
|
1370
1961
|
try {
|
|
1371
|
-
await (0,
|
|
1372
|
-
await
|
|
1962
|
+
await (0, import_node_opcua_crypto5.writePrivateKeyFile)(tmpFilename, privateKey, { passphrase });
|
|
1963
|
+
await import_node_fs8.default.promises.rename(tmpFilename, this.privateKey);
|
|
1373
1964
|
} finally {
|
|
1374
|
-
await
|
|
1965
|
+
await import_node_fs8.default.promises.rm(tmpFilename, { force: true });
|
|
1375
1966
|
}
|
|
1376
1967
|
}
|
|
1377
1968
|
/** @internal resolve the configured passphrase, at most once per instance */
|
|
@@ -1382,10 +1973,6 @@ var CertificateAuthority = class {
|
|
|
1382
1973
|
}
|
|
1383
1974
|
return this.#resolvedPassphrase;
|
|
1384
1975
|
}
|
|
1385
|
-
/** @internal `-passin env:` argv + env for every openssl call that loads this CA's key (always emitted, empty when none) */
|
|
1386
|
-
async _opensslPassin() {
|
|
1387
|
-
return passinArg(await this._privateKeyPassphrase());
|
|
1388
|
-
}
|
|
1389
1976
|
/**
|
|
1390
1977
|
* @internal On an existing key: encrypt it in place if a passphrase is
|
|
1391
1978
|
* configured and it is still plaintext (secure by default: the option
|
|
@@ -1394,16 +1981,58 @@ var CertificateAuthority = class {
|
|
|
1394
1981
|
* first signing operation.
|
|
1395
1982
|
*/
|
|
1396
1983
|
async _ensurePrivateKeyProtection() {
|
|
1397
|
-
if (!
|
|
1984
|
+
if (this.#signer || !import_node_fs8.default.existsSync(this.privateKey)) {
|
|
1398
1985
|
return;
|
|
1399
1986
|
}
|
|
1400
1987
|
if (this.#privateKeyPassphrase !== void 0 && !isEncryptedPrivateKeyFile(this.privateKey)) {
|
|
1401
1988
|
warningLog("CertificateAuthority: private key is plaintext but a passphrase is configured; encrypting it in place");
|
|
1402
|
-
const plaintextKey = (0,
|
|
1989
|
+
const plaintextKey = (0, import_node_opcua_crypto5.readPrivateKey)(this.privateKey);
|
|
1403
1990
|
await this.#rewritePrivateKeyFile(plaintextKey, await this._privateKeyPassphrase());
|
|
1404
1991
|
}
|
|
1405
1992
|
await this.getPrivateKey();
|
|
1406
1993
|
}
|
|
1994
|
+
/**
|
|
1995
|
+
* Acquire a file-based lock on this CA's directory for the duration of
|
|
1996
|
+
* `action` — serializes every operation that mutates the certificate
|
|
1997
|
+
* database (`index.txt`, `serial`, `crlnumber`, `certs/`,
|
|
1998
|
+
* `crl/revocation_list.*`) or the CA's own key/certificate, so
|
|
1999
|
+
* concurrent calls on one `CertificateAuthority` (or two instances
|
|
2000
|
+
* pointed at the same directory, in this or another process) cannot
|
|
2001
|
+
* interleave. Mirrors `CertificateManager.withLock2`.
|
|
2002
|
+
*
|
|
2003
|
+
* Only ever call this from a top-level public operation — nested calls
|
|
2004
|
+
* on the same instance would deadlock, since the underlying file lock
|
|
2005
|
+
* is not reentrant.
|
|
2006
|
+
*
|
|
2007
|
+
* The wait is bounded: a waiter gives up (throws) after
|
|
2008
|
+
* `CA_LOCK_MAX_WAIT_MS`. Without a bound, a holder whose openssl child
|
|
2009
|
+
* hangs would block every CA operation in every process forever and
|
|
2010
|
+
* silently — the lock library's keepalive keeps refreshing the lock
|
|
2011
|
+
* file's mtime as long as the holder process is alive, so stale-lock
|
|
2012
|
+
* recovery never fires for a live-but-stuck holder. A dead holder's
|
|
2013
|
+
* lock goes stale (default 2 minutes) and is taken over well within
|
|
2014
|
+
* this bound.
|
|
2015
|
+
*/
|
|
2016
|
+
async #withCaLock(action) {
|
|
2017
|
+
const lockFileName = import_node_path7.default.join(this.rootDir, ".ca.lock");
|
|
2018
|
+
return (0, import_global_mutex.withLock)(
|
|
2019
|
+
{
|
|
2020
|
+
fileToLock: lockFileName,
|
|
2021
|
+
retries: { forever: true, maxRetryTime: CA_LOCK_MAX_WAIT_MS, ...CA_LOCK_RETRY }
|
|
2022
|
+
},
|
|
2023
|
+
action
|
|
2024
|
+
);
|
|
2025
|
+
}
|
|
2026
|
+
/**
|
|
2027
|
+
* @internal Run `action` under this CA's directory lock. For the one
|
|
2028
|
+
* legitimate cross-instance use: a subordinate CA's bootstrap signs its
|
|
2029
|
+
* certificate with `openssl x509 -CAserial`, which read-increment-writes
|
|
2030
|
+
* THIS issuer's `serial` file, and must therefore hold this issuer's
|
|
2031
|
+
* lock (ordering is always subordinate -> issuer, so no cycle).
|
|
2032
|
+
*/
|
|
2033
|
+
async _withCaDirectoryLock(action) {
|
|
2034
|
+
return this.#withCaLock(action);
|
|
2035
|
+
}
|
|
1407
2036
|
/** Path to the CA certificate in PEM format (`public/cacert.pem`). */
|
|
1408
2037
|
get caCertificate() {
|
|
1409
2038
|
return makePath(this.rootDir, "./public/cacert.pem");
|
|
@@ -1451,8 +2080,8 @@ var CertificateAuthority = class {
|
|
|
1451
2080
|
* (call {@link initialize} first).
|
|
1452
2081
|
*/
|
|
1453
2082
|
getCACertificateDER() {
|
|
1454
|
-
const pem = (0,
|
|
1455
|
-
return (0,
|
|
2083
|
+
const pem = (0, import_node_opcua_crypto5.readCertificatePEM)(this.caCertificate);
|
|
2084
|
+
return (0, import_node_opcua_crypto5.convertPEMtoDER)(pem);
|
|
1456
2085
|
}
|
|
1457
2086
|
/**
|
|
1458
2087
|
* Return the CA certificate as a PEM-encoded string.
|
|
@@ -1461,7 +2090,7 @@ var CertificateAuthority = class {
|
|
|
1461
2090
|
* (call {@link initialize} first).
|
|
1462
2091
|
*/
|
|
1463
2092
|
getCACertificatePEM() {
|
|
1464
|
-
const raw = (0,
|
|
2093
|
+
const raw = (0, import_node_opcua_crypto5.readCertificatePEM)(this.caCertificate);
|
|
1465
2094
|
const beginMarker = "-----BEGIN CERTIFICATE-----";
|
|
1466
2095
|
const idx = raw.indexOf(beginMarker);
|
|
1467
2096
|
if (idx > 0) {
|
|
@@ -1477,10 +2106,10 @@ var CertificateAuthority = class {
|
|
|
1477
2106
|
*/
|
|
1478
2107
|
getCRLDER() {
|
|
1479
2108
|
const crlPath = this.revocationListDER;
|
|
1480
|
-
if (!
|
|
2109
|
+
if (!import_node_fs8.default.existsSync(crlPath)) {
|
|
1481
2110
|
return Buffer.alloc(0);
|
|
1482
2111
|
}
|
|
1483
|
-
return
|
|
2112
|
+
return import_node_fs8.default.readFileSync(crlPath);
|
|
1484
2113
|
}
|
|
1485
2114
|
/**
|
|
1486
2115
|
* Return the current Certificate Revocation List as a
|
|
@@ -1490,10 +2119,10 @@ var CertificateAuthority = class {
|
|
|
1490
2119
|
*/
|
|
1491
2120
|
getCRLPEM() {
|
|
1492
2121
|
const crlPath = this.revocationList;
|
|
1493
|
-
if (!
|
|
2122
|
+
if (!import_node_fs8.default.existsSync(crlPath)) {
|
|
1494
2123
|
return "";
|
|
1495
2124
|
}
|
|
1496
|
-
const raw =
|
|
2125
|
+
const raw = import_node_fs8.default.readFileSync(crlPath, "utf-8");
|
|
1497
2126
|
const beginMarker = "-----BEGIN X509 CRL-----";
|
|
1498
2127
|
const idx = raw.indexOf(beginMarker);
|
|
1499
2128
|
if (idx > 0) {
|
|
@@ -1512,14 +2141,14 @@ var CertificateAuthority = class {
|
|
|
1512
2141
|
* expiry date, and (for revoked certs) the revocation date.
|
|
1513
2142
|
*/
|
|
1514
2143
|
getIssuedCertificates() {
|
|
1515
|
-
return this.
|
|
2144
|
+
return this.#db.readIndex();
|
|
1516
2145
|
}
|
|
1517
2146
|
/**
|
|
1518
2147
|
* Return the total number of certificates recorded in
|
|
1519
2148
|
* `index.txt`.
|
|
1520
2149
|
*/
|
|
1521
2150
|
getIssuedCertificateCount() {
|
|
1522
|
-
return this.
|
|
2151
|
+
return this.#db.readIndex().length;
|
|
1523
2152
|
}
|
|
1524
2153
|
/**
|
|
1525
2154
|
* Return the status of a certificate by its serial number.
|
|
@@ -1529,9 +2158,7 @@ var CertificateAuthority = class {
|
|
|
1529
2158
|
* `undefined` if not found
|
|
1530
2159
|
*/
|
|
1531
2160
|
getCertificateStatus(serial) {
|
|
1532
|
-
|
|
1533
|
-
const record = this._parseIndexTxt().find((r) => r.serial.toUpperCase() === upper);
|
|
1534
|
-
return record?.status;
|
|
2161
|
+
return this.#db.findBySerial(serial)?.status;
|
|
1535
2162
|
}
|
|
1536
2163
|
/**
|
|
1537
2164
|
* Read a specific issued certificate by serial number and
|
|
@@ -1540,86 +2167,17 @@ var CertificateAuthority = class {
|
|
|
1540
2167
|
* OpenSSL stores signed certificates in the `certs/`
|
|
1541
2168
|
* directory using the naming convention `<SERIAL>.pem`.
|
|
1542
2169
|
*
|
|
1543
|
-
* @param serial - hex-encoded serial number (e.g. `"1000"`)
|
|
1544
|
-
* @returns the DER buffer, or `undefined` if not found
|
|
1545
|
-
*/
|
|
1546
|
-
getCertificateBySerial(serial) {
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
return
|
|
1554
|
-
}
|
|
1555
|
-
/**
|
|
1556
|
-
* Path to the OpenSSL certificate database file.
|
|
1557
|
-
*/
|
|
1558
|
-
get indexFile() {
|
|
1559
|
-
return import_node_path5.default.join(this.rootDir, "index.txt");
|
|
1560
|
-
}
|
|
1561
|
-
/**
|
|
1562
|
-
* Parse the OpenSSL `index.txt` certificate database.
|
|
1563
|
-
*
|
|
1564
|
-
* Each line has tab-separated fields:
|
|
1565
|
-
* ```
|
|
1566
|
-
* status expiry [revocationDate] serial unknown subject
|
|
1567
|
-
* ```
|
|
1568
|
-
*
|
|
1569
|
-
* - status: `V` (valid), `R` (revoked), `E` (expired)
|
|
1570
|
-
* - expiry: `YYMMDDHHmmssZ`
|
|
1571
|
-
* - revocationDate: present only for revoked certs
|
|
1572
|
-
* - serial: hex string
|
|
1573
|
-
* - unknown: always `"unknown"`
|
|
1574
|
-
* - subject: X.500 slash-delimited string
|
|
1575
|
-
*/
|
|
1576
|
-
_parseIndexTxt() {
|
|
1577
|
-
const indexPath = this.indexFile;
|
|
1578
|
-
if (!import_node_fs7.default.existsSync(indexPath)) {
|
|
1579
|
-
return [];
|
|
1580
|
-
}
|
|
1581
|
-
const content = import_node_fs7.default.readFileSync(indexPath, "utf-8");
|
|
1582
|
-
const lines = content.split("\n").filter((l) => l.trim().length > 0);
|
|
1583
|
-
const records = [];
|
|
1584
|
-
for (const line of lines) {
|
|
1585
|
-
const fields = line.split(" ");
|
|
1586
|
-
if (fields.length < 4) continue;
|
|
1587
|
-
const statusChar = fields[0];
|
|
1588
|
-
const expiryStr = fields[1];
|
|
1589
|
-
let serial;
|
|
1590
|
-
let subject;
|
|
1591
|
-
let revocationDate;
|
|
1592
|
-
if (statusChar === "R") {
|
|
1593
|
-
revocationDate = fields[2];
|
|
1594
|
-
serial = fields[3];
|
|
1595
|
-
subject = fields.length >= 6 ? fields[5] : "";
|
|
1596
|
-
} else {
|
|
1597
|
-
serial = fields[3];
|
|
1598
|
-
subject = fields.length >= 6 ? fields[5] : "";
|
|
1599
|
-
}
|
|
1600
|
-
let status;
|
|
1601
|
-
switch (statusChar) {
|
|
1602
|
-
case "V":
|
|
1603
|
-
status = "valid";
|
|
1604
|
-
break;
|
|
1605
|
-
case "R":
|
|
1606
|
-
status = "revoked";
|
|
1607
|
-
break;
|
|
1608
|
-
case "E":
|
|
1609
|
-
status = "expired";
|
|
1610
|
-
break;
|
|
1611
|
-
default:
|
|
1612
|
-
continue;
|
|
1613
|
-
}
|
|
1614
|
-
records.push({
|
|
1615
|
-
serial,
|
|
1616
|
-
status,
|
|
1617
|
-
subject,
|
|
1618
|
-
expiryDate: parseOpenSSLDate(expiryStr),
|
|
1619
|
-
revocationDate: revocationDate ? parseOpenSSLDate(revocationDate) : void 0
|
|
1620
|
-
});
|
|
1621
|
-
}
|
|
1622
|
-
return records;
|
|
2170
|
+
* @param serial - hex-encoded serial number (e.g. `"1000"`)
|
|
2171
|
+
* @returns the DER buffer, or `undefined` if not found
|
|
2172
|
+
*/
|
|
2173
|
+
getCertificateBySerial(serial) {
|
|
2174
|
+
return this.#db.getCertificateBySerial(serial);
|
|
2175
|
+
}
|
|
2176
|
+
/**
|
|
2177
|
+
* Path to the OpenSSL certificate database file.
|
|
2178
|
+
*/
|
|
2179
|
+
get indexFile() {
|
|
2180
|
+
return this.#db.indexFile;
|
|
1623
2181
|
}
|
|
1624
2182
|
// ---------------------------------------------------------------
|
|
1625
2183
|
// Buffer-based CA operations (US-058)
|
|
@@ -1641,12 +2199,12 @@ var CertificateAuthority = class {
|
|
|
1641
2199
|
* @returns the signed certificate as a DER-encoded buffer
|
|
1642
2200
|
*/
|
|
1643
2201
|
async signCertificateRequestFromDER(csrDer, options) {
|
|
1644
|
-
const tmpDir = await
|
|
2202
|
+
const tmpDir = await import_node_fs8.default.promises.mkdtemp(import_node_path7.default.join(import_node_os3.default.tmpdir(), "pki-sign-"));
|
|
1645
2203
|
try {
|
|
1646
|
-
const csrFile =
|
|
1647
|
-
const certFile =
|
|
1648
|
-
const csrPem = (0,
|
|
1649
|
-
await
|
|
2204
|
+
const csrFile = import_node_path7.default.join(tmpDir, "request.csr");
|
|
2205
|
+
const certFile = import_node_path7.default.join(tmpDir, "certificate.pem");
|
|
2206
|
+
const csrPem = (0, import_node_opcua_crypto5.toPem)(csrDer, "CERTIFICATE REQUEST");
|
|
2207
|
+
await import_node_fs8.default.promises.writeFile(csrFile, csrPem, "utf-8");
|
|
1650
2208
|
const signingParams = {};
|
|
1651
2209
|
if (options?.validityMs !== void 0) signingParams.validityMs = options.validityMs;
|
|
1652
2210
|
else signingParams.validity = options?.validity ?? 365;
|
|
@@ -1656,10 +2214,10 @@ var CertificateAuthority = class {
|
|
|
1656
2214
|
if (options?.applicationUri) signingParams.applicationUri = options.applicationUri;
|
|
1657
2215
|
if (options?.subject) signingParams.subject = options.subject;
|
|
1658
2216
|
await this.signCertificateRequest(certFile, csrFile, signingParams);
|
|
1659
|
-
const certPem = (0,
|
|
1660
|
-
return (0,
|
|
2217
|
+
const certPem = (0, import_node_opcua_crypto5.readCertificatePEM)(certFile);
|
|
2218
|
+
return (0, import_node_opcua_crypto5.convertPEMtoDER)(certPem);
|
|
1661
2219
|
} finally {
|
|
1662
|
-
await
|
|
2220
|
+
await import_node_fs8.default.promises.rm(tmpDir, {
|
|
1663
2221
|
recursive: true,
|
|
1664
2222
|
force: true
|
|
1665
2223
|
});
|
|
@@ -1709,27 +2267,35 @@ var CertificateAuthority = class {
|
|
|
1709
2267
|
* @returns `{ certificateDer, privateKey }` — certificate as DER,
|
|
1710
2268
|
* private key as a branded `PrivateKey` buffer
|
|
1711
2269
|
*/
|
|
2270
|
+
/**
|
|
2271
|
+
* An ephemeral key and a CSR for it, written into `tmpDir`. Both
|
|
2272
|
+
* `generateKeyPairAndSign*` methods need exactly this, and neither
|
|
2273
|
+
* needs a subprocess for it: the key comes from node's crypto and the
|
|
2274
|
+
* request is built and self-signed in process, so no `openssl.cnf` has
|
|
2275
|
+
* to be rendered either.
|
|
2276
|
+
*/
|
|
2277
|
+
async #createEphemeralKeyAndCsr(tmpDir, keySize, options) {
|
|
2278
|
+
const privateKeyFile = import_node_path7.default.join(tmpDir, "private_key.pem");
|
|
2279
|
+
await (0, import_node_opcua_crypto5.generatePrivateKeyFile)(privateKeyFile, keySize);
|
|
2280
|
+
const { csr } = await (0, import_node_opcua_crypto5.createCertificateSigningRequest)({
|
|
2281
|
+
privateKey: await (0, import_node_opcua_crypto5.privateKeyToCryptoKey)((0, import_node_opcua_crypto5.readPrivateKey)(privateKeyFile)),
|
|
2282
|
+
subject: options.subject ? new import_node_opcua_crypto5.Subject(options.subject).toString() : void 0,
|
|
2283
|
+
applicationUri: options.applicationUri,
|
|
2284
|
+
dns: options.dns ?? [],
|
|
2285
|
+
ip: options.ip ?? [],
|
|
2286
|
+
purpose: import_node_opcua_crypto5.CertificatePurpose.ForApplication
|
|
2287
|
+
});
|
|
2288
|
+
const csrFile = import_node_path7.default.join(tmpDir, "request.csr");
|
|
2289
|
+
await import_node_fs8.default.promises.writeFile(csrFile, csr);
|
|
2290
|
+
return { privateKeyFile, csrFile };
|
|
2291
|
+
}
|
|
1712
2292
|
async generateKeyPairAndSignDER(options) {
|
|
1713
2293
|
const keySize = options.keySize ?? 2048;
|
|
1714
2294
|
const startDate = options.startDate ?? /* @__PURE__ */ new Date();
|
|
1715
|
-
const tmpDir = await
|
|
2295
|
+
const tmpDir = await import_node_fs8.default.promises.mkdtemp(import_node_path7.default.join(import_node_os3.default.tmpdir(), "pki-keygen-"));
|
|
1716
2296
|
try {
|
|
1717
|
-
const privateKeyFile =
|
|
1718
|
-
|
|
1719
|
-
const configFile = import_node_path5.default.join(tmpDir, "openssl.cnf");
|
|
1720
|
-
await import_node_fs7.default.promises.writeFile(configFile, configurationFileSimpleTemplate, "utf-8");
|
|
1721
|
-
const csrFile = import_node_path5.default.join(tmpDir, "request.csr");
|
|
1722
|
-
await createCertificateSigningRequestWithOpenSSL(csrFile, {
|
|
1723
|
-
rootDir: tmpDir,
|
|
1724
|
-
configFile,
|
|
1725
|
-
privateKey: privateKeyFile,
|
|
1726
|
-
applicationUri: options.applicationUri,
|
|
1727
|
-
subject: options.subject,
|
|
1728
|
-
dns: options.dns ?? [],
|
|
1729
|
-
ip: options.ip ?? [],
|
|
1730
|
-
purpose: import_node_opcua_crypto2.CertificatePurpose.ForApplication
|
|
1731
|
-
});
|
|
1732
|
-
const certFile = import_node_path5.default.join(tmpDir, "certificate.pem");
|
|
2297
|
+
const { privateKeyFile, csrFile } = await this.#createEphemeralKeyAndCsr(tmpDir, keySize, options);
|
|
2298
|
+
const certFile = import_node_path7.default.join(tmpDir, "certificate.pem");
|
|
1733
2299
|
const signingParams = {
|
|
1734
2300
|
applicationUri: options.applicationUri,
|
|
1735
2301
|
dns: options.dns,
|
|
@@ -1739,12 +2305,12 @@ var CertificateAuthority = class {
|
|
|
1739
2305
|
if (options.validityMs !== void 0) signingParams.validityMs = options.validityMs;
|
|
1740
2306
|
else signingParams.validity = options.validity ?? 365;
|
|
1741
2307
|
await this.signCertificateRequest(certFile, csrFile, signingParams);
|
|
1742
|
-
const certPem = (0,
|
|
1743
|
-
const certificateDer = (0,
|
|
1744
|
-
const privateKey = (0,
|
|
2308
|
+
const certPem = (0, import_node_opcua_crypto5.readCertificatePEM)(certFile);
|
|
2309
|
+
const certificateDer = (0, import_node_opcua_crypto5.convertPEMtoDER)(certPem);
|
|
2310
|
+
const privateKey = (0, import_node_opcua_crypto5.readPrivateKey)(privateKeyFile);
|
|
1745
2311
|
return { certificateDer, privateKey };
|
|
1746
2312
|
} finally {
|
|
1747
|
-
await
|
|
2313
|
+
await import_node_fs8.default.promises.rm(tmpDir, {
|
|
1748
2314
|
recursive: true,
|
|
1749
2315
|
force: true
|
|
1750
2316
|
});
|
|
@@ -1765,24 +2331,10 @@ var CertificateAuthority = class {
|
|
|
1765
2331
|
const keySize = options.keySize ?? 2048;
|
|
1766
2332
|
const startDate = options.startDate ?? /* @__PURE__ */ new Date();
|
|
1767
2333
|
const passphrase = options.passphrase ?? "";
|
|
1768
|
-
const tmpDir = await
|
|
2334
|
+
const tmpDir = await import_node_fs8.default.promises.mkdtemp(import_node_path7.default.join(import_node_os3.default.tmpdir(), "pki-keygen-pfx-"));
|
|
1769
2335
|
try {
|
|
1770
|
-
const privateKeyFile =
|
|
1771
|
-
|
|
1772
|
-
const configFile = import_node_path5.default.join(tmpDir, "openssl.cnf");
|
|
1773
|
-
await import_node_fs7.default.promises.writeFile(configFile, configurationFileSimpleTemplate, "utf-8");
|
|
1774
|
-
const csrFile = import_node_path5.default.join(tmpDir, "request.csr");
|
|
1775
|
-
await createCertificateSigningRequestWithOpenSSL(csrFile, {
|
|
1776
|
-
rootDir: tmpDir,
|
|
1777
|
-
configFile,
|
|
1778
|
-
privateKey: privateKeyFile,
|
|
1779
|
-
applicationUri: options.applicationUri,
|
|
1780
|
-
subject: options.subject,
|
|
1781
|
-
dns: options.dns ?? [],
|
|
1782
|
-
ip: options.ip ?? [],
|
|
1783
|
-
purpose: import_node_opcua_crypto2.CertificatePurpose.ForApplication
|
|
1784
|
-
});
|
|
1785
|
-
const certFile = import_node_path5.default.join(tmpDir, "certificate.pem");
|
|
2336
|
+
const { privateKeyFile, csrFile } = await this.#createEphemeralKeyAndCsr(tmpDir, keySize, options);
|
|
2337
|
+
const certFile = import_node_path7.default.join(tmpDir, "certificate.pem");
|
|
1786
2338
|
const signingParams = {
|
|
1787
2339
|
applicationUri: options.applicationUri,
|
|
1788
2340
|
dns: options.dns,
|
|
@@ -1792,17 +2344,20 @@ var CertificateAuthority = class {
|
|
|
1792
2344
|
if (options.validityMs !== void 0) signingParams.validityMs = options.validityMs;
|
|
1793
2345
|
else signingParams.validity = options.validity ?? 365;
|
|
1794
2346
|
await this.signCertificateRequest(certFile, csrFile, signingParams);
|
|
1795
|
-
const
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
2347
|
+
const blocks = (await import_node_fs8.default.promises.readFile(certFile, "utf-8")).match(
|
|
2348
|
+
/-----BEGIN CERTIFICATE-----[\s\S]*?-----END CERTIFICATE-----/g
|
|
2349
|
+
);
|
|
2350
|
+
if (!blocks || blocks.length === 0) {
|
|
2351
|
+
throw new Error(`generateKeyPairAndSignPFX: no certificate was produced in ${certFile}`);
|
|
2352
|
+
}
|
|
2353
|
+
return await (0, import_node_opcua_crypto5.createPfx)({
|
|
2354
|
+
certificate: (0, import_node_opcua_crypto5.convertPEMtoDER)(blocks[0]),
|
|
2355
|
+
certificateChain: blocks.slice(1).map(import_node_opcua_crypto5.convertPEMtoDER),
|
|
2356
|
+
privateKey: (0, import_node_opcua_crypto5.readPrivateKey)(privateKeyFile),
|
|
2357
|
+
password: passphrase
|
|
1802
2358
|
});
|
|
1803
|
-
return await import_node_fs7.default.promises.readFile(pfxFile);
|
|
1804
2359
|
} finally {
|
|
1805
|
-
await
|
|
2360
|
+
await import_node_fs8.default.promises.rm(tmpDir, {
|
|
1806
2361
|
recursive: true,
|
|
1807
2362
|
force: true
|
|
1808
2363
|
});
|
|
@@ -1821,10 +2376,10 @@ var CertificateAuthority = class {
|
|
|
1821
2376
|
* @throws if the certificate's serial is not found in the CA
|
|
1822
2377
|
*/
|
|
1823
2378
|
async revokeCertificateDER(certDer, reason) {
|
|
1824
|
-
const info = (0,
|
|
2379
|
+
const info = (0, import_node_opcua_crypto5.exploreCertificate)(certDer);
|
|
1825
2380
|
const serial = info.tbsCertificate.serialNumber.replace(/:/g, "").toUpperCase();
|
|
1826
|
-
const storedCertFile =
|
|
1827
|
-
if (!
|
|
2381
|
+
const storedCertFile = import_node_path7.default.join(this.rootDir, "certs", `${serial}.pem`);
|
|
2382
|
+
if (!import_node_fs8.default.existsSync(storedCertFile)) {
|
|
1828
2383
|
throw new Error(`Cannot revoke: no stored certificate found for serial ${serial} at ${storedCertFile}`);
|
|
1829
2384
|
}
|
|
1830
2385
|
await this.revokeCertificate(storedCertFile, {
|
|
@@ -1837,7 +2392,69 @@ var CertificateAuthority = class {
|
|
|
1837
2392
|
* already exist.
|
|
1838
2393
|
*/
|
|
1839
2394
|
async initialize() {
|
|
1840
|
-
|
|
2395
|
+
mkdirRecursiveSync(import_node_path7.default.resolve(this.rootDir));
|
|
2396
|
+
await this.#withCaLock(() => this.#bootstrap());
|
|
2397
|
+
}
|
|
2398
|
+
/**
|
|
2399
|
+
* @internal Shared (backend-agnostic) part of `initialize()`: directory
|
|
2400
|
+
* layout, default database files, the "already initialized" / "partial
|
|
2401
|
+
* init" checks, and the openssl config file — then delegates CSR
|
|
2402
|
+
* generation, CA-certificate signing, and the initial CRL to
|
|
2403
|
+
* {@link CaBackend.bootstrap}. Must be called under {@link #withCaLock}.
|
|
2404
|
+
*/
|
|
2405
|
+
async #bootstrap() {
|
|
2406
|
+
const caRootDir = import_node_path7.default.resolve(this.rootDir);
|
|
2407
|
+
mkdirRecursiveSync(caRootDir);
|
|
2408
|
+
ensurePrivateDirectory(import_node_path7.default.join(caRootDir, "private"));
|
|
2409
|
+
mkdirRecursiveSync(import_node_path7.default.join(caRootDir, "public"));
|
|
2410
|
+
mkdirRecursiveSync(import_node_path7.default.join(caRootDir, "certs"));
|
|
2411
|
+
mkdirRecursiveSync(import_node_path7.default.join(caRootDir, "crl"));
|
|
2412
|
+
mkdirRecursiveSync(import_node_path7.default.join(caRootDir, "conf"));
|
|
2413
|
+
const serial = import_node_path7.default.join(caRootDir, "serial");
|
|
2414
|
+
if (!import_node_fs8.default.existsSync(serial)) {
|
|
2415
|
+
await import_node_fs8.default.promises.writeFile(serial, "1000");
|
|
2416
|
+
}
|
|
2417
|
+
const crlNumber = import_node_path7.default.join(caRootDir, "crlnumber");
|
|
2418
|
+
if (!import_node_fs8.default.existsSync(crlNumber)) {
|
|
2419
|
+
await import_node_fs8.default.promises.writeFile(crlNumber, "1000");
|
|
2420
|
+
}
|
|
2421
|
+
const indexFile = import_node_path7.default.join(caRootDir, "index.txt");
|
|
2422
|
+
if (!import_node_fs8.default.existsSync(indexFile)) {
|
|
2423
|
+
await import_node_fs8.default.promises.writeFile(indexFile, "");
|
|
2424
|
+
}
|
|
2425
|
+
const signerBacked = this.hasExternalSigner;
|
|
2426
|
+
const caKeyExists = signerBacked || import_node_fs8.default.existsSync(import_node_path7.default.join(caRootDir, "private/cakey.pem"));
|
|
2427
|
+
const caCertExists = import_node_fs8.default.existsSync(import_node_path7.default.join(caRootDir, "public/cacert.pem"));
|
|
2428
|
+
if (caKeyExists && caCertExists && !config2.forceCA) {
|
|
2429
|
+
if (!signerBacked) {
|
|
2430
|
+
restrictPrivateFilePermissions(import_node_path7.default.join(caRootDir, "private/cakey.pem"), 384);
|
|
2431
|
+
await this._ensurePrivateKeyProtection();
|
|
2432
|
+
}
|
|
2433
|
+
debugLog("CA private key and certificate already exist ... skipping");
|
|
2434
|
+
return;
|
|
2435
|
+
}
|
|
2436
|
+
if (!signerBacked && caKeyExists && !caCertExists) {
|
|
2437
|
+
debugLog("CA private key exists but cacert.pem is missing \u2014 rebuilding CA");
|
|
2438
|
+
import_node_fs8.default.unlinkSync(import_node_path7.default.join(caRootDir, "private/cakey.pem"));
|
|
2439
|
+
const staleCsr = import_node_path7.default.join(caRootDir, "private/cakey.csr");
|
|
2440
|
+
if (import_node_fs8.default.existsSync(staleCsr)) {
|
|
2441
|
+
import_node_fs8.default.unlinkSync(staleCsr);
|
|
2442
|
+
}
|
|
2443
|
+
}
|
|
2444
|
+
displayTitle("Create Certificate Authority (CA)");
|
|
2445
|
+
const indexFileAttr = import_node_path7.default.join(caRootDir, "index.txt.attr");
|
|
2446
|
+
if (!import_node_fs8.default.existsSync(indexFileAttr)) {
|
|
2447
|
+
await import_node_fs8.default.promises.writeFile(indexFileAttr, "unique_subject = no");
|
|
2448
|
+
}
|
|
2449
|
+
const caConfigFile = this.configFile;
|
|
2450
|
+
await import_node_fs8.default.promises.writeFile(caConfigFile, renderCaConfig(caRootDir));
|
|
2451
|
+
if (!signerBacked) {
|
|
2452
|
+
const privateKeyFilename = import_node_path7.default.join(caRootDir, "private/cakey.pem");
|
|
2453
|
+
displayTitle(`Generate the CA private Key - ${this.keySize}`);
|
|
2454
|
+
await (0, import_node_opcua_crypto5.generatePrivateKeyFile)(privateKeyFilename, this.keySize, { passphrase: await this._privateKeyPassphrase() });
|
|
2455
|
+
restrictPrivateFilePermissions(privateKeyFilename, 384);
|
|
2456
|
+
}
|
|
2457
|
+
await this.#backend.bootstrap(this);
|
|
1841
2458
|
}
|
|
1842
2459
|
/**
|
|
1843
2460
|
* Initialize the CA directory structure and generate the
|
|
@@ -1859,22 +2476,26 @@ var CertificateAuthority = class {
|
|
|
1859
2476
|
* @returns an {@link InitializeCSRResult} describing the CA state
|
|
1860
2477
|
*/
|
|
1861
2478
|
async initializeCSR() {
|
|
1862
|
-
const caRootDir =
|
|
2479
|
+
const caRootDir = import_node_path7.default.resolve(this.rootDir);
|
|
1863
2480
|
mkdirRecursiveSync(caRootDir);
|
|
2481
|
+
return this.#withCaLock(() => this.#initializeCSRLocked(caRootDir));
|
|
2482
|
+
}
|
|
2483
|
+
async #initializeCSRLocked(caRootDir) {
|
|
1864
2484
|
for (const dir of ["public", "certs", "crl", "conf"]) {
|
|
1865
|
-
mkdirRecursiveSync(
|
|
2485
|
+
mkdirRecursiveSync(import_node_path7.default.join(caRootDir, dir));
|
|
1866
2486
|
}
|
|
1867
|
-
ensurePrivateDirectory(
|
|
2487
|
+
ensurePrivateDirectory(import_node_path7.default.join(caRootDir, "private"));
|
|
1868
2488
|
const caCertFile = this.caCertificate;
|
|
1869
|
-
const privateKeyFile =
|
|
1870
|
-
const csrFile =
|
|
1871
|
-
|
|
2489
|
+
const privateKeyFile = import_node_path7.default.join(caRootDir, "private/cakey.pem");
|
|
2490
|
+
const csrFile = import_node_path7.default.join(caRootDir, "private/cakey.csr");
|
|
2491
|
+
const keyAvailable = this.hasExternalSigner || import_node_fs8.default.existsSync(privateKeyFile);
|
|
2492
|
+
if (!this.hasExternalSigner && import_node_fs8.default.existsSync(privateKeyFile)) {
|
|
1872
2493
|
restrictPrivateFilePermissions(privateKeyFile, 384);
|
|
1873
2494
|
await this._ensurePrivateKeyProtection();
|
|
1874
2495
|
}
|
|
1875
|
-
if (
|
|
1876
|
-
const certDer = (0,
|
|
1877
|
-
const certInfo = (0,
|
|
2496
|
+
if (import_node_fs8.default.existsSync(caCertFile)) {
|
|
2497
|
+
const certDer = (0, import_node_opcua_crypto5.convertPEMtoDER)((0, import_node_opcua_crypto5.readCertificatePEM)(caCertFile));
|
|
2498
|
+
const certInfo = (0, import_node_opcua_crypto5.exploreCertificate)(certDer);
|
|
1878
2499
|
const notAfter = certInfo.tbsCertificate.validity.notAfter;
|
|
1879
2500
|
if (notAfter.getTime() < Date.now()) {
|
|
1880
2501
|
debugLog("CA certificate has expired \u2014 generating renewal CSR");
|
|
@@ -1884,30 +2505,30 @@ var CertificateAuthority = class {
|
|
|
1884
2505
|
debugLog("CA certificate already exists and is valid \u2014 ready");
|
|
1885
2506
|
return { status: "ready" };
|
|
1886
2507
|
}
|
|
1887
|
-
if (
|
|
2508
|
+
if (keyAvailable && import_node_fs8.default.existsSync(csrFile)) {
|
|
1888
2509
|
debugLog("CA key + CSR already exist \u2014 pending external signing");
|
|
1889
2510
|
return { status: "pending", csrPath: csrFile };
|
|
1890
2511
|
}
|
|
1891
|
-
const serial =
|
|
1892
|
-
if (!
|
|
1893
|
-
await
|
|
2512
|
+
const serial = import_node_path7.default.join(caRootDir, "serial");
|
|
2513
|
+
if (!import_node_fs8.default.existsSync(serial)) {
|
|
2514
|
+
await import_node_fs8.default.promises.writeFile(serial, "1000");
|
|
1894
2515
|
}
|
|
1895
|
-
const crlNumber =
|
|
1896
|
-
if (!
|
|
1897
|
-
await
|
|
2516
|
+
const crlNumber = import_node_path7.default.join(caRootDir, "crlnumber");
|
|
2517
|
+
if (!import_node_fs8.default.existsSync(crlNumber)) {
|
|
2518
|
+
await import_node_fs8.default.promises.writeFile(crlNumber, "1000");
|
|
1898
2519
|
}
|
|
1899
|
-
const indexFile =
|
|
1900
|
-
if (!
|
|
1901
|
-
await
|
|
2520
|
+
const indexFile = import_node_path7.default.join(caRootDir, "index.txt");
|
|
2521
|
+
if (!import_node_fs8.default.existsSync(indexFile)) {
|
|
2522
|
+
await import_node_fs8.default.promises.writeFile(indexFile, "");
|
|
1902
2523
|
}
|
|
1903
|
-
const indexFileAttr =
|
|
1904
|
-
if (!
|
|
1905
|
-
await
|
|
2524
|
+
const indexFileAttr = import_node_path7.default.join(caRootDir, "index.txt.attr");
|
|
2525
|
+
if (!import_node_fs8.default.existsSync(indexFileAttr)) {
|
|
2526
|
+
await import_node_fs8.default.promises.writeFile(indexFileAttr, "unique_subject = no");
|
|
1906
2527
|
}
|
|
1907
2528
|
const caConfigFile = this.configFile;
|
|
1908
|
-
await
|
|
1909
|
-
if (!
|
|
1910
|
-
await (0,
|
|
2529
|
+
await import_node_fs8.default.promises.writeFile(caConfigFile, renderCaConfig(caRootDir));
|
|
2530
|
+
if (!keyAvailable) {
|
|
2531
|
+
await (0, import_node_opcua_crypto5.generatePrivateKeyFile)(privateKeyFile, this.keySize, { passphrase: await this._privateKeyPassphrase() });
|
|
1911
2532
|
restrictPrivateFilePermissions(privateKeyFile, 384);
|
|
1912
2533
|
}
|
|
1913
2534
|
await this._generateCSR(caRootDir, privateKeyFile, csrFile);
|
|
@@ -1927,53 +2548,34 @@ var CertificateAuthority = class {
|
|
|
1927
2548
|
* renewal is needed, `"ready"` if the cert is still valid
|
|
1928
2549
|
*/
|
|
1929
2550
|
async renewCSR(thresholdDays = 30) {
|
|
1930
|
-
const caRootDir =
|
|
2551
|
+
const caRootDir = import_node_path7.default.resolve(this.rootDir);
|
|
1931
2552
|
const caCertFile = this.caCertificate;
|
|
1932
|
-
const privateKeyFile =
|
|
1933
|
-
const csrFile =
|
|
1934
|
-
if (!
|
|
2553
|
+
const privateKeyFile = import_node_path7.default.join(caRootDir, "private/cakey.pem");
|
|
2554
|
+
const csrFile = import_node_path7.default.join(caRootDir, "private/cakey.csr");
|
|
2555
|
+
if (!import_node_fs8.default.existsSync(caCertFile)) {
|
|
1935
2556
|
return this.initializeCSR();
|
|
1936
2557
|
}
|
|
1937
|
-
const certDer = (0,
|
|
1938
|
-
const certInfo = (0,
|
|
2558
|
+
const certDer = (0, import_node_opcua_crypto5.convertPEMtoDER)((0, import_node_opcua_crypto5.readCertificatePEM)(caCertFile));
|
|
2559
|
+
const certInfo = (0, import_node_opcua_crypto5.exploreCertificate)(certDer);
|
|
1939
2560
|
const notAfter = certInfo.tbsCertificate.validity.notAfter;
|
|
1940
2561
|
const thresholdMs = thresholdDays * 24 * 60 * 60 * 1e3;
|
|
1941
2562
|
if (notAfter.getTime() - Date.now() < thresholdMs) {
|
|
1942
2563
|
debugLog(`CA certificate expires within ${thresholdDays} days \u2014 generating renewal CSR`);
|
|
1943
|
-
await this._generateCSR(caRootDir, privateKeyFile, csrFile);
|
|
2564
|
+
await this.#withCaLock(() => this._generateCSR(caRootDir, privateKeyFile, csrFile));
|
|
1944
2565
|
return { status: "expired", csrPath: csrFile, expiryDate: notAfter };
|
|
1945
2566
|
}
|
|
1946
2567
|
return { status: "ready" };
|
|
1947
2568
|
}
|
|
1948
2569
|
/**
|
|
1949
2570
|
* Generate a CSR using the existing private key.
|
|
2571
|
+
* Must be called under {@link #withCaLock} — the lock is taken by the
|
|
2572
|
+
* public callers (`initializeCSR`, `renewCSR`), not here, so that
|
|
2573
|
+
* `initializeCSR` can hold one lock across key generation AND CSR
|
|
2574
|
+
* generation without the non-reentrant file lock deadlocking.
|
|
1950
2575
|
* @internal
|
|
1951
2576
|
*/
|
|
1952
2577
|
async _generateCSR(caRootDir, privateKeyFile, csrFile) {
|
|
1953
|
-
|
|
1954
|
-
const options = { cwd: caRootDir };
|
|
1955
|
-
const configFile = generateStaticConfig("conf/caconfig.cnf", options);
|
|
1956
|
-
const passin = await this._opensslPassin();
|
|
1957
|
-
await execute_openssl(
|
|
1958
|
-
[
|
|
1959
|
-
"req",
|
|
1960
|
-
"-new",
|
|
1961
|
-
"-sha256",
|
|
1962
|
-
"-text",
|
|
1963
|
-
"-extensions",
|
|
1964
|
-
"v3_ca_req",
|
|
1965
|
-
"-config",
|
|
1966
|
-
n4(configFile),
|
|
1967
|
-
"-key",
|
|
1968
|
-
n4(privateKeyFile),
|
|
1969
|
-
"-out",
|
|
1970
|
-
n4(csrFile),
|
|
1971
|
-
"-subj",
|
|
1972
|
-
this.subject.toString(),
|
|
1973
|
-
...passin.args
|
|
1974
|
-
],
|
|
1975
|
-
{ ...options, env: passin.env }
|
|
1976
|
-
);
|
|
2578
|
+
await this.#backend.generateCaCsr(this, caRootDir, privateKeyFile, csrFile);
|
|
1977
2579
|
}
|
|
1978
2580
|
/**
|
|
1979
2581
|
* Install an externally-signed CA certificate and generate
|
|
@@ -1992,43 +2594,41 @@ var CertificateAuthority = class {
|
|
|
1992
2594
|
* `status: "success"` or `status: "error"` and a `reason`
|
|
1993
2595
|
*/
|
|
1994
2596
|
async installCACertificate(signedCertFile) {
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
}
|
|
2014
|
-
|
|
2015
|
-
await import_node_fs7.default.promises.writeFile(caCertFile, `${pemBlocks[0]}
|
|
2597
|
+
return this.#withCaLock(async () => {
|
|
2598
|
+
const caCertFile = this.caCertificate;
|
|
2599
|
+
const fullPem = await import_node_fs8.default.promises.readFile(signedCertFile, "utf8");
|
|
2600
|
+
const pemBlocks = fullPem.match(/-----BEGIN CERTIFICATE-----[\s\S]*?-----END CERTIFICATE-----/g);
|
|
2601
|
+
if (!pemBlocks || pemBlocks.length === 0) {
|
|
2602
|
+
return {
|
|
2603
|
+
status: "error",
|
|
2604
|
+
reason: "no_certificate_found",
|
|
2605
|
+
message: "The provided file does not contain any PEM-encoded certificate."
|
|
2606
|
+
};
|
|
2607
|
+
}
|
|
2608
|
+
const certDer = (0, import_node_opcua_crypto5.convertPEMtoDER)(pemBlocks[0]);
|
|
2609
|
+
if (!await this.#certificateMatchesOurKey(pemBlocks[0], certDer)) {
|
|
2610
|
+
return {
|
|
2611
|
+
status: "error",
|
|
2612
|
+
reason: "certificate_key_mismatch",
|
|
2613
|
+
message: "The provided certificate does not match the CA private key. Ensure the certificate was signed from the CSR generated by initializeCSR()."
|
|
2614
|
+
};
|
|
2615
|
+
}
|
|
2616
|
+
await import_node_fs8.default.promises.writeFile(caCertFile, `${pemBlocks[0]}
|
|
2016
2617
|
`);
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
|
|
2618
|
+
const issuerChainFile = this.issuerCertificateChain;
|
|
2619
|
+
if (pemBlocks.length > 1) {
|
|
2620
|
+
const issuerPem = `${pemBlocks.slice(1).join("\n")}
|
|
2020
2621
|
`;
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2622
|
+
await import_node_fs8.default.promises.writeFile(issuerChainFile, issuerPem);
|
|
2623
|
+
debugLog(`Stored ${pemBlocks.length - 1} issuer certificate(s) in issuer_chain.pem`);
|
|
2624
|
+
} else {
|
|
2625
|
+
if (import_node_fs8.default.existsSync(issuerChainFile)) {
|
|
2626
|
+
await import_node_fs8.default.promises.unlink(issuerChainFile);
|
|
2627
|
+
}
|
|
2026
2628
|
}
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
await regenerateCrl(this.revocationList, ["-config", n4(configFile)], options, await this._opensslPassin());
|
|
2031
|
-
return { status: "success" };
|
|
2629
|
+
await this.#backend.regenerateCrl(this);
|
|
2630
|
+
return { status: "success" };
|
|
2631
|
+
});
|
|
2032
2632
|
}
|
|
2033
2633
|
/**
|
|
2034
2634
|
* Sign a CSR with CA extensions (`v3_ca`), producing a
|
|
@@ -2044,39 +2644,11 @@ var CertificateAuthority = class {
|
|
|
2044
2644
|
* @param params - signing parameters
|
|
2045
2645
|
*/
|
|
2046
2646
|
async signCACertificateRequest(certFile, csrFile, params) {
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
const passin = await this._opensslPassin();
|
|
2053
|
-
await execute_openssl(
|
|
2054
|
-
[
|
|
2055
|
-
"x509",
|
|
2056
|
-
"-sha256",
|
|
2057
|
-
"-req",
|
|
2058
|
-
"-days",
|
|
2059
|
-
String(validity),
|
|
2060
|
-
"-text",
|
|
2061
|
-
"-extensions",
|
|
2062
|
-
"v3_ca",
|
|
2063
|
-
"-extfile",
|
|
2064
|
-
n4(configFile),
|
|
2065
|
-
"-in",
|
|
2066
|
-
n4(csrFile),
|
|
2067
|
-
"-CA",
|
|
2068
|
-
n4(this.caCertificate),
|
|
2069
|
-
"-CAkey",
|
|
2070
|
-
n4(import_node_path5.default.join(caRootDir, "private/cakey.pem")),
|
|
2071
|
-
"-CAserial",
|
|
2072
|
-
n4(import_node_path5.default.join(caRootDir, "serial")),
|
|
2073
|
-
"-out",
|
|
2074
|
-
n4(certFile),
|
|
2075
|
-
...passin.args
|
|
2076
|
-
],
|
|
2077
|
-
{ ...options, env: passin.env }
|
|
2078
|
-
);
|
|
2079
|
-
await this.constructCertificateChain(certFile);
|
|
2647
|
+
await this.#withCaLock(async () => {
|
|
2648
|
+
const validity = params.validity ?? 3650;
|
|
2649
|
+
await this.#backend.signSubordinateCsr(this, csrFile, certFile, validity);
|
|
2650
|
+
await this.constructCertificateChain(certFile);
|
|
2651
|
+
});
|
|
2080
2652
|
}
|
|
2081
2653
|
/**
|
|
2082
2654
|
* Rebuild the combined CA certificate + CRL file.
|
|
@@ -2087,13 +2659,13 @@ var CertificateAuthority = class {
|
|
|
2087
2659
|
*/
|
|
2088
2660
|
async constructCACertificateWithCRL() {
|
|
2089
2661
|
const cacertWithCRL = this.caCertificateWithCrl;
|
|
2090
|
-
if (
|
|
2091
|
-
await
|
|
2662
|
+
if (import_node_fs8.default.existsSync(this.revocationList)) {
|
|
2663
|
+
await import_node_fs8.default.promises.writeFile(
|
|
2092
2664
|
cacertWithCRL,
|
|
2093
|
-
|
|
2665
|
+
import_node_fs8.default.readFileSync(this.caCertificate, "utf8") + import_node_fs8.default.readFileSync(this.revocationList, "utf8")
|
|
2094
2666
|
);
|
|
2095
2667
|
} else {
|
|
2096
|
-
await
|
|
2668
|
+
await import_node_fs8.default.promises.writeFile(cacertWithCRL, import_node_fs8.default.readFileSync(this.caCertificate));
|
|
2097
2669
|
}
|
|
2098
2670
|
}
|
|
2099
2671
|
/**
|
|
@@ -2103,15 +2675,15 @@ var CertificateAuthority = class {
|
|
|
2103
2675
|
* @param certificate - path to the certificate file to extend
|
|
2104
2676
|
*/
|
|
2105
2677
|
async constructCertificateChain(certificate) {
|
|
2106
|
-
(0,
|
|
2107
|
-
(0,
|
|
2678
|
+
(0, import_node_assert5.default)(import_node_fs8.default.existsSync(certificate));
|
|
2679
|
+
(0, import_node_assert5.default)(import_node_fs8.default.existsSync(this.caCertificate));
|
|
2108
2680
|
debugLog(import_chalk5.default.yellow(" certificate file :"), import_chalk5.default.cyan(certificate));
|
|
2109
|
-
let chain = await
|
|
2110
|
-
chain += await
|
|
2111
|
-
if (
|
|
2112
|
-
chain += await
|
|
2681
|
+
let chain = await import_node_fs8.default.promises.readFile(certificate, "utf8");
|
|
2682
|
+
chain += await import_node_fs8.default.promises.readFile(this.caCertificate, "utf8");
|
|
2683
|
+
if (import_node_fs8.default.existsSync(this.issuerCertificateChain)) {
|
|
2684
|
+
chain += await import_node_fs8.default.promises.readFile(this.issuerCertificateChain, "utf8");
|
|
2113
2685
|
}
|
|
2114
|
-
await
|
|
2686
|
+
await import_node_fs8.default.promises.writeFile(certificate, chain);
|
|
2115
2687
|
}
|
|
2116
2688
|
/**
|
|
2117
2689
|
* Create a self-signed certificate using OpenSSL.
|
|
@@ -2121,52 +2693,30 @@ var CertificateAuthority = class {
|
|
|
2121
2693
|
* @param params - certificate parameters (subject, validity, SANs)
|
|
2122
2694
|
*/
|
|
2123
2695
|
async createSelfSignedCertificate(certificateFile, privateKey, params) {
|
|
2124
|
-
(0,
|
|
2125
|
-
(0,
|
|
2696
|
+
(0, import_node_assert5.default)(typeof privateKey === "string");
|
|
2697
|
+
(0, import_node_assert5.default)(import_node_fs8.default.existsSync(privateKey));
|
|
2126
2698
|
if (!certificateFileExist(certificateFile)) {
|
|
2127
2699
|
return;
|
|
2128
2700
|
}
|
|
2129
2701
|
adjustDate(params);
|
|
2130
2702
|
adjustApplicationUri(params);
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
(
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
)
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
[
|
|
2149
|
-
"ca",
|
|
2150
|
-
"-selfsign",
|
|
2151
|
-
"-keyfile",
|
|
2152
|
-
n4(privateKey),
|
|
2153
|
-
"-startdate",
|
|
2154
|
-
x509Date(params.startDate),
|
|
2155
|
-
"-enddate",
|
|
2156
|
-
x509Date(params.endDate),
|
|
2157
|
-
"-batch",
|
|
2158
|
-
"-out",
|
|
2159
|
-
n4(certificateFile),
|
|
2160
|
-
"-in",
|
|
2161
|
-
n4(csrFile)
|
|
2162
|
-
],
|
|
2163
|
-
options
|
|
2164
|
-
);
|
|
2165
|
-
displaySubtitle("- dump the certificate for a check");
|
|
2166
|
-
await execute_openssl(["x509", "-in", n4(certificateFile), "-dates", "-fingerprint", "-purpose", "-noout"], {});
|
|
2167
|
-
displaySubtitle("- verify self-signed certificate");
|
|
2168
|
-
await execute_openssl_no_failure(["verify", "-verbose", "-CAfile", n4(certificateFile), n4(certificateFile)], options);
|
|
2169
|
-
await import_node_fs7.default.promises.unlink(csrFile);
|
|
2703
|
+
params.dns = params.dns || [];
|
|
2704
|
+
params.ip = params.ip || [];
|
|
2705
|
+
await this.#withCaLock(async () => {
|
|
2706
|
+
await this.#backend.createSelfSignedCertificate(this, certificateFile, privateKey, params);
|
|
2707
|
+
});
|
|
2708
|
+
}
|
|
2709
|
+
/**
|
|
2710
|
+
* Regenerate `crl/revocation_list.{crl,der}` from the current database
|
|
2711
|
+
* state, without changing any certificate's status. Normally
|
|
2712
|
+
* unnecessary — `revokeCertificate` already regenerates the CRL as
|
|
2713
|
+
* part of revoking — but useful to force a refresh (e.g. after
|
|
2714
|
+
* switching a CA's `backend` between `"openssl"` and `"native"`).
|
|
2715
|
+
*/
|
|
2716
|
+
async regenerateCrl() {
|
|
2717
|
+
await this.#withCaLock(async () => {
|
|
2718
|
+
await this.#backend.regenerateCrl(this);
|
|
2719
|
+
});
|
|
2170
2720
|
}
|
|
2171
2721
|
/**
|
|
2172
2722
|
* Revoke a certificate and regenerate the CRL.
|
|
@@ -2187,49 +2737,12 @@ var CertificateAuthority = class {
|
|
|
2187
2737
|
"certificateHold",
|
|
2188
2738
|
"removeFromCRL"
|
|
2189
2739
|
];
|
|
2190
|
-
const configFile = generateStaticConfig("conf/caconfig.cnf", { cwd: this.rootDir });
|
|
2191
|
-
const options = {
|
|
2192
|
-
cwd: this.rootDir,
|
|
2193
|
-
openssl_conf: makePath(configFile)
|
|
2194
|
-
};
|
|
2195
|
-
setEnv("ALTNAME", "");
|
|
2196
|
-
const randomFile = import_node_path5.default.join(this.rootDir, "random.rnd");
|
|
2197
|
-
setEnv("RANDFILE", randomFile);
|
|
2198
|
-
const configOption = ["-config", n4(configFile)];
|
|
2199
2740
|
const reason = params.reason || "keyCompromise";
|
|
2200
|
-
(0,
|
|
2741
|
+
(0, import_node_assert5.default)(crlReasons.indexOf(reason) >= 0);
|
|
2201
2742
|
displayTitle(`Revoking certificate ${certificate}`);
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
["ca", "-verbose", ...configOption, "-revoke", certificate, "-crl_reason", reason, ...passin.args],
|
|
2206
|
-
{ ...options, env: passin.env }
|
|
2207
|
-
);
|
|
2208
|
-
await regenerateCrl(this.revocationList, configOption, options, passin);
|
|
2209
|
-
displaySubtitle("Verify that certificate is revoked");
|
|
2210
|
-
await execute_openssl_no_failure(
|
|
2211
|
-
[
|
|
2212
|
-
"verify",
|
|
2213
|
-
"-verbose",
|
|
2214
|
-
"-CRLfile",
|
|
2215
|
-
n4(this.revocationList),
|
|
2216
|
-
"-CAfile",
|
|
2217
|
-
n4(this.caCertificate),
|
|
2218
|
-
"-crl_check",
|
|
2219
|
-
n4(certificate)
|
|
2220
|
-
],
|
|
2221
|
-
options
|
|
2222
|
-
);
|
|
2223
|
-
displaySubtitle("Produce CRL in DER form ");
|
|
2224
|
-
await execute_openssl(
|
|
2225
|
-
["crl", "-in", n4(this.revocationList), "-out", "crl/revocation_list.der", "-outform", "der"],
|
|
2226
|
-
options
|
|
2227
|
-
);
|
|
2228
|
-
displaySubtitle("Produce CRL in PEM form ");
|
|
2229
|
-
await execute_openssl(
|
|
2230
|
-
["crl", "-in", n4(this.revocationList), "-out", "crl/revocation_list.pem", "-outform", "pem", "-text"],
|
|
2231
|
-
options
|
|
2232
|
-
);
|
|
2743
|
+
await this.#withCaLock(async () => {
|
|
2744
|
+
await this.#backend.revoke(this, certificate, reason);
|
|
2745
|
+
});
|
|
2233
2746
|
}
|
|
2234
2747
|
/**
|
|
2235
2748
|
* Sign a Certificate Signing Request (CSR) with this CA.
|
|
@@ -2244,17 +2757,15 @@ var CertificateAuthority = class {
|
|
|
2244
2757
|
* @returns the path to the signed certificate
|
|
2245
2758
|
*/
|
|
2246
2759
|
async signCertificateRequest(certificate, certificateSigningRequestFilename, params1) {
|
|
2247
|
-
await
|
|
2248
|
-
(0,
|
|
2760
|
+
await this.#backend.preflight();
|
|
2761
|
+
(0, import_node_assert5.default)(import_node_fs8.default.existsSync(certificateSigningRequestFilename));
|
|
2249
2762
|
if (!certificateFileExist(certificate)) {
|
|
2250
2763
|
return "";
|
|
2251
2764
|
}
|
|
2252
2765
|
adjustDate(params1);
|
|
2253
2766
|
adjustApplicationUri(params1);
|
|
2254
|
-
|
|
2255
|
-
const
|
|
2256
|
-
const csr = await (0, import_node_opcua_crypto2.readCertificateSigningRequest)(certificateSigningRequestFilename);
|
|
2257
|
-
const csrInfo = (0, import_node_opcua_crypto2.exploreCertificateSigningRequest)(csr);
|
|
2767
|
+
const csr = await (0, import_node_opcua_crypto5.readCertificateSigningRequest)(certificateSigningRequestFilename);
|
|
2768
|
+
const csrInfo = (0, import_node_opcua_crypto5.exploreCertificateSigningRequest)(csr);
|
|
2258
2769
|
const applicationUri = csrInfo.extensionRequest.subjectAltName.uniformResourceIdentifier ? csrInfo.extensionRequest.subjectAltName.uniformResourceIdentifier[0] : void 0;
|
|
2259
2770
|
if (typeof applicationUri !== "string") {
|
|
2260
2771
|
throw new Error("Cannot find applicationUri in CSR");
|
|
@@ -2262,91 +2773,129 @@ var CertificateAuthority = class {
|
|
|
2262
2773
|
const dns = csrInfo.extensionRequest.subjectAltName.dNSName || [];
|
|
2263
2774
|
let ip = csrInfo.extensionRequest.subjectAltName.iPAddress || [];
|
|
2264
2775
|
ip = ip.map(octetStringToIpAddress);
|
|
2265
|
-
const
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
|
|
2274
|
-
|
|
2275
|
-
|
|
2276
|
-
[
|
|
2277
|
-
"ca",
|
|
2278
|
-
"-config",
|
|
2279
|
-
configFile,
|
|
2280
|
-
"-startdate",
|
|
2281
|
-
x509Date(params1.startDate),
|
|
2282
|
-
"-enddate",
|
|
2283
|
-
x509Date(params1.endDate),
|
|
2284
|
-
"-batch",
|
|
2285
|
-
"-out",
|
|
2286
|
-
n4(certificate),
|
|
2287
|
-
"-in",
|
|
2288
|
-
n4(certificateSigningRequestFilename),
|
|
2289
|
-
...passin.args
|
|
2290
|
-
],
|
|
2291
|
-
{ ...options, env: passin.env }
|
|
2292
|
-
);
|
|
2293
|
-
displaySubtitle("- dump the certificate for a check");
|
|
2294
|
-
await execute_openssl(["x509", "-in", n4(certificate), "-dates", "-fingerprint", "-purpose", "-noout"], options);
|
|
2295
|
-
displaySubtitle("- construct CA certificate with CRL");
|
|
2296
|
-
await this.constructCACertificateWithCRL();
|
|
2297
|
-
displaySubtitle("- construct certificate chain");
|
|
2298
|
-
await this.constructCertificateChain(certificate);
|
|
2299
|
-
displaySubtitle("- verify certificate against the root CA");
|
|
2300
|
-
await this.verifyCertificate(certificate);
|
|
2301
|
-
return certificate;
|
|
2776
|
+
const sanOverride = { applicationUri, dns, ip };
|
|
2777
|
+
return this.#withCaLock(async () => {
|
|
2778
|
+
await this.#backend.signEndEntityCsr(this, certificate, certificateSigningRequestFilename, params1, sanOverride);
|
|
2779
|
+
displaySubtitle("- construct CA certificate with CRL");
|
|
2780
|
+
await this.constructCACertificateWithCRL();
|
|
2781
|
+
displaySubtitle("- construct certificate chain");
|
|
2782
|
+
await this.constructCertificateChain(certificate);
|
|
2783
|
+
displaySubtitle("- verify certificate against the root CA");
|
|
2784
|
+
await this.verifyCertificate(certificate);
|
|
2785
|
+
return certificate;
|
|
2786
|
+
});
|
|
2302
2787
|
}
|
|
2303
2788
|
/**
|
|
2304
|
-
*
|
|
2789
|
+
* Check that `certificate` really was signed by this CA, and throw if it
|
|
2790
|
+
* was not.
|
|
2305
2791
|
*
|
|
2306
|
-
*
|
|
2792
|
+
* This used to do nothing: `openssl verify` crashes on Windows, so the
|
|
2793
|
+
* check was left as a placeholder. It no longer needs a subprocess -
|
|
2794
|
+
* `verifyCertificateSignature` does it in pure JS, the same way
|
|
2795
|
+
* {@link CertificateManager} already validates a chain - so the check
|
|
2796
|
+
* that `signCertificateRequest` always claimed to perform now actually
|
|
2797
|
+
* happens on every issuance, whichever backend did the signing.
|
|
2798
|
+
*
|
|
2799
|
+
* Only the leading certificate is examined: the file may be a chain,
|
|
2800
|
+
* and the rest of it is this CA's own certificate and its issuers.
|
|
2801
|
+
*
|
|
2802
|
+
* @param certificate - path to the certificate (or chain) to verify
|
|
2307
2803
|
*/
|
|
2308
2804
|
async verifyCertificate(certificate) {
|
|
2309
|
-
const
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
|
|
2314
|
-
|
|
2315
|
-
|
|
2316
|
-
|
|
2805
|
+
const pem = await import_node_fs8.default.promises.readFile(certificate, "utf-8");
|
|
2806
|
+
const blocks = pem.match(/-----BEGIN CERTIFICATE-----[\s\S]*?-----END CERTIFICATE-----/g);
|
|
2807
|
+
if (!blocks || blocks.length === 0) {
|
|
2808
|
+
throw new Error(`verifyCertificate: ${certificate} contains no PEM-encoded certificate`);
|
|
2809
|
+
}
|
|
2810
|
+
const caCertificateDer = (0, import_node_opcua_crypto5.convertPEMtoDER)((0, import_node_opcua_crypto5.readCertificatePEM)(this.caCertificate));
|
|
2811
|
+
if (!(0, import_node_opcua_crypto5.verifyCertificateSignature)((0, import_node_opcua_crypto5.convertPEMtoDER)(blocks[0]), caCertificateDer)) {
|
|
2812
|
+
throw new Error(
|
|
2813
|
+
`verifyCertificate: ${certificate} was not signed by this certificate authority (${this.caCertificate})`
|
|
2317
2814
|
);
|
|
2318
2815
|
}
|
|
2319
2816
|
}
|
|
2320
2817
|
};
|
|
2321
2818
|
|
|
2819
|
+
// packages/node-opcua-pki/lib/ca/certificate_authority.ts
|
|
2820
|
+
var CertificateAuthority = class extends CertificateAuthorityCore {
|
|
2821
|
+
constructor(options) {
|
|
2822
|
+
if (options.signer && options.backend === "openssl") {
|
|
2823
|
+
throw new Error(
|
|
2824
|
+
"CertificateAuthority: backend 'openssl' cannot be combined with 'signer' - the openssl CLI loads its key from a file and cannot call an external signer. Drop the backend option (a signer implies 'native') or drop the signer."
|
|
2825
|
+
);
|
|
2826
|
+
}
|
|
2827
|
+
const backend = options.signer || options.backend === "native" ? new NativeCaBackend() : new OpenSslCaBackend();
|
|
2828
|
+
super({ ...options, backend });
|
|
2829
|
+
}
|
|
2830
|
+
/**
|
|
2831
|
+
* @internal `-passin env:` argv + env for an openssl call that loads
|
|
2832
|
+
* this CA's key (always emitted, empty when none).
|
|
2833
|
+
*
|
|
2834
|
+
* The openssl backend builds its own now; this remains so that external
|
|
2835
|
+
* code calling it keeps working, and lives here rather than on the core
|
|
2836
|
+
* because the flag means nothing to a backend that spawns nothing.
|
|
2837
|
+
*/
|
|
2838
|
+
async _opensslPassin() {
|
|
2839
|
+
return passinArg(await this._privateKeyPassphrase());
|
|
2840
|
+
}
|
|
2841
|
+
/**
|
|
2842
|
+
* @internal
|
|
2843
|
+
* Legacy shim: publish the `CDP_URL` / `AIA_VALUE` config substitution
|
|
2844
|
+
* values to the shared env registry, or unset them so the matching
|
|
2845
|
+
* `{{#KEY}}...{{/KEY}}` blocks are stripped. Nothing in this package
|
|
2846
|
+
* reads the registry any more - every openssl config render receives
|
|
2847
|
+
* these values explicitly, from the same {@link caConfigEnvOverrides}
|
|
2848
|
+
* builder this delegates to, so the two cannot drift - kept only for
|
|
2849
|
+
* external code that renders openssl config templates against the
|
|
2850
|
+
* registry directly.
|
|
2851
|
+
*
|
|
2852
|
+
* It lives on this class rather than the core because it is meaningful
|
|
2853
|
+
* only to the openssl backend.
|
|
2854
|
+
*/
|
|
2855
|
+
_wireRevocationEnvVars() {
|
|
2856
|
+
const overrides = caConfigEnvOverrides(this);
|
|
2857
|
+
if (overrides.CDP_URL) {
|
|
2858
|
+
setEnv("CDP_URL", overrides.CDP_URL);
|
|
2859
|
+
} else {
|
|
2860
|
+
unsetEnv("CDP_URL");
|
|
2861
|
+
}
|
|
2862
|
+
if (overrides.AIA_VALUE) {
|
|
2863
|
+
setEnv("AIA_VALUE", overrides.AIA_VALUE);
|
|
2864
|
+
} else {
|
|
2865
|
+
unsetEnv("AIA_VALUE");
|
|
2866
|
+
}
|
|
2867
|
+
}
|
|
2868
|
+
};
|
|
2869
|
+
|
|
2322
2870
|
// packages/node-opcua-pki/lib/pki/certificate_manager.ts
|
|
2323
2871
|
var import_node_events = require("events");
|
|
2324
|
-
var
|
|
2325
|
-
var
|
|
2326
|
-
var
|
|
2872
|
+
var import_node_fs11 = __toESM(require("fs"));
|
|
2873
|
+
var import_node_path8 = __toESM(require("path"));
|
|
2874
|
+
var import_global_mutex2 = require("@ster5/global-mutex");
|
|
2327
2875
|
var import_chalk6 = __toESM(require("chalk"));
|
|
2328
2876
|
var import_chokidar = __toESM(require("chokidar"));
|
|
2329
|
-
var
|
|
2877
|
+
var import_node_opcua_crypto8 = require("node-opcua-crypto");
|
|
2330
2878
|
|
|
2331
2879
|
// packages/node-opcua-pki/lib/toolbox/without_openssl/create_certificate_signing_request.ts
|
|
2332
|
-
var
|
|
2333
|
-
var
|
|
2334
|
-
var
|
|
2880
|
+
var import_node_assert6 = __toESM(require("assert"));
|
|
2881
|
+
var import_node_fs9 = __toESM(require("fs"));
|
|
2882
|
+
var import_node_opcua_crypto6 = require("node-opcua-crypto");
|
|
2335
2883
|
async function createCertificateSigningRequestAsync(certificateSigningRequestFilename, params) {
|
|
2336
|
-
(0,
|
|
2337
|
-
(0,
|
|
2338
|
-
(0,
|
|
2339
|
-
(0,
|
|
2884
|
+
(0, import_node_assert6.default)(params);
|
|
2885
|
+
(0, import_node_assert6.default)(params.rootDir);
|
|
2886
|
+
(0, import_node_assert6.default)(params.configFile);
|
|
2887
|
+
(0, import_node_assert6.default)(params.privateKey);
|
|
2340
2888
|
if (typeof params.privateKey === "string") {
|
|
2341
|
-
(0,
|
|
2889
|
+
(0, import_node_assert6.default)(import_node_fs9.default.existsSync(params.privateKey), `Private key must exist${params.privateKey}`);
|
|
2342
2890
|
}
|
|
2343
|
-
(0,
|
|
2344
|
-
(0,
|
|
2345
|
-
const subject = params.subject ? new
|
|
2891
|
+
(0, import_node_assert6.default)(import_node_fs9.default.existsSync(params.rootDir), "RootDir key must exist");
|
|
2892
|
+
(0, import_node_assert6.default)(typeof certificateSigningRequestFilename === "string");
|
|
2893
|
+
const subject = params.subject ? new import_node_opcua_crypto6.Subject(params.subject).toString() : void 0;
|
|
2346
2894
|
displaySubtitle("- Creating a Certificate Signing Request with subtile");
|
|
2347
|
-
const
|
|
2348
|
-
|
|
2349
|
-
|
|
2895
|
+
const privateKey = isOpaqueSigner(params.privateKey) ? params.privateKey : await (0, import_node_opcua_crypto6.pemToPrivateKey)(
|
|
2896
|
+
typeof params.privateKey === "string" ? await import_node_fs9.default.promises.readFile(params.privateKey, "utf-8") : (0, import_node_opcua_crypto6.coercePrivateKeyPem)(params.privateKey)
|
|
2897
|
+
);
|
|
2898
|
+
const { csr } = await (0, import_node_opcua_crypto6.createCertificateSigningRequest)({
|
|
2350
2899
|
privateKey,
|
|
2351
2900
|
dns: params.dns,
|
|
2352
2901
|
ip: params.ip,
|
|
@@ -2354,37 +2903,40 @@ async function createCertificateSigningRequestAsync(certificateSigningRequestFil
|
|
|
2354
2903
|
applicationUri: params.applicationUri,
|
|
2355
2904
|
purpose: params.purpose
|
|
2356
2905
|
});
|
|
2357
|
-
await
|
|
2358
|
-
display(
|
|
2906
|
+
await import_node_fs9.default.promises.writeFile(certificateSigningRequestFilename, csr, "utf-8");
|
|
2907
|
+
display(
|
|
2908
|
+
`- privateKey ${typeof params.privateKey === "string" ? params.privateKey : isOpaqueSigner(params.privateKey) ? "<opaque-signer>" : "<in-memory>"}`
|
|
2909
|
+
);
|
|
2359
2910
|
display(`- certificateSigningRequestFilename ${certificateSigningRequestFilename}`);
|
|
2360
2911
|
}
|
|
2361
2912
|
|
|
2362
2913
|
// packages/node-opcua-pki/lib/toolbox/without_openssl/create_self_signed_certificate.ts
|
|
2363
|
-
var
|
|
2364
|
-
var
|
|
2365
|
-
var
|
|
2914
|
+
var import_node_assert7 = __toESM(require("assert"));
|
|
2915
|
+
var import_node_fs10 = __toESM(require("fs"));
|
|
2916
|
+
var import_node_opcua_crypto7 = require("node-opcua-crypto");
|
|
2366
2917
|
async function createSelfSignedCertificateAsync(certificate, params) {
|
|
2367
|
-
params.purpose = params.purpose ||
|
|
2368
|
-
(0,
|
|
2369
|
-
(0,
|
|
2370
|
-
(0,
|
|
2918
|
+
params.purpose = params.purpose || import_node_opcua_crypto7.CertificatePurpose.ForApplication;
|
|
2919
|
+
(0, import_node_assert7.default)(params.purpose, "Please provide a Certificate Purpose");
|
|
2920
|
+
(0, import_node_assert7.default)(import_node_fs10.default.existsSync(params.configFile));
|
|
2921
|
+
(0, import_node_assert7.default)(import_node_fs10.default.existsSync(params.rootDir));
|
|
2371
2922
|
if (typeof params.privateKey === "string") {
|
|
2372
|
-
(0,
|
|
2923
|
+
(0, import_node_assert7.default)(import_node_fs10.default.existsSync(params.privateKey));
|
|
2373
2924
|
}
|
|
2374
2925
|
if (!params.subject) {
|
|
2375
2926
|
throw Error("Missing subject");
|
|
2376
2927
|
}
|
|
2377
|
-
(0,
|
|
2378
|
-
(0,
|
|
2928
|
+
(0, import_node_assert7.default)(typeof params.applicationUri === "string");
|
|
2929
|
+
(0, import_node_assert7.default)(Array.isArray(params.dns));
|
|
2379
2930
|
adjustDate(params);
|
|
2380
|
-
(0,
|
|
2381
|
-
let subject = new
|
|
2931
|
+
(0, import_node_assert7.default)(Object.prototype.hasOwnProperty.call(params, "validity"));
|
|
2932
|
+
let subject = new import_node_opcua_crypto7.Subject(params.subject);
|
|
2382
2933
|
subject = subject.toString();
|
|
2383
2934
|
const purpose = params.purpose;
|
|
2384
2935
|
displayTitle("Generate a certificate request");
|
|
2385
|
-
const
|
|
2386
|
-
|
|
2387
|
-
|
|
2936
|
+
const privateKey = isOpaqueSigner(params.privateKey) ? params.privateKey : await (0, import_node_opcua_crypto7.pemToPrivateKey)(
|
|
2937
|
+
typeof params.privateKey === "string" ? await import_node_fs10.default.promises.readFile(params.privateKey, "utf-8") : (0, import_node_opcua_crypto7.coercePrivateKeyPem)(params.privateKey)
|
|
2938
|
+
);
|
|
2939
|
+
const { cert } = await (0, import_node_opcua_crypto7.createSelfSignedCertificate)({
|
|
2388
2940
|
privateKey,
|
|
2389
2941
|
notBefore: params.startDate,
|
|
2390
2942
|
notAfter: params.endDate,
|
|
@@ -2395,18 +2947,22 @@ async function createSelfSignedCertificateAsync(certificate, params) {
|
|
|
2395
2947
|
applicationUri: params.applicationUri,
|
|
2396
2948
|
purpose
|
|
2397
2949
|
});
|
|
2398
|
-
await
|
|
2950
|
+
await import_node_fs10.default.promises.writeFile(certificate, cert, "utf-8");
|
|
2399
2951
|
}
|
|
2400
2952
|
async function createSelfSignedCertificate(certificate, params) {
|
|
2401
2953
|
await createSelfSignedCertificateAsync(certificate, params);
|
|
2402
2954
|
}
|
|
2403
2955
|
|
|
2956
|
+
// packages/node-opcua-pki/lib/pki/templates/simple_config_template.cnf.ts
|
|
2957
|
+
var config3 = '##################################################################################################\n## SIMPLE OPENSSL CONFIG FILE FOR SELF-SIGNED CERTIFICATE GENERATION\n################################################################################################################\n\ndistinguished_name = req_distinguished_name\ndefault_md = sha1\n\ndefault_md = sha256 # The default digest algorithm\n\n[ v3_ca ]\nsubjectKeyIdentifier = hash\nauthorityKeyIdentifier = keyid:always,issuer:always\n\n# authorityKeyIdentifier = keyid\nbasicConstraints = CA:TRUE\nkeyUsage = critical, cRLSign, keyCertSign\nnsComment = "Self-signed Certificate for CA generated by Node-OPCUA Certificate utility"\n#nsCertType = sslCA, emailCA\n#subjectAltName = email:copy\n#issuerAltName = issuer:copy\n#obj = DER:02:03\n# crlDistributionPoints = @crl_info\n# [ crl_info ]\n# URI.0 = http://localhost:8900/crl.pem\nsubjectAltName = $ENV::ALTNAME\n\n[ req ]\ndays = 390\nreq_extensions = v3_req\nx509_extensions = v3_ca\n\n[v3_req]\nbasicConstraints = CA:false\nkeyUsage = critical, nonRepudiation, digitalSignature, keyEncipherment, dataEncipherment\nsubjectAltName = $ENV::ALTNAME\n\n[ v3_ca_signed]\nsubjectKeyIdentifier = hash\nauthorityKeyIdentifier = keyid,issuer\nbasicConstraints = critical, CA:FALSE\nkeyUsage = nonRepudiation, digitalSignature, keyEncipherment, dataEncipherment\nextendedKeyUsage = clientAuth,serverAuth \nnsComment = "certificate generated by Node-OPCUA Certificate utility and signed by a CA"\nsubjectAltName = $ENV::ALTNAME\n[ v3_selfsigned]\nsubjectKeyIdentifier = hash\nauthorityKeyIdentifier = keyid,issuer\nbasicConstraints = critical, CA:FALSE\nkeyUsage = nonRepudiation, digitalSignature, keyEncipherment, dataEncipherment\nextendedKeyUsage = clientAuth,serverAuth \nnsComment = "Self-signed certificate generated by Node-OPCUA Certificate utility"\nsubjectAltName = $ENV::ALTNAME\n[ req_distinguished_name ]\ncountryName = Country Name (2 letter code)\ncountryName_default = FR\ncountryName_min = 2\ncountryName_max = 2\n# stateOrProvinceName = State or Province Name (full name)\n# stateOrProvinceName_default = Ile de France\n# localityName = Locality Name (city, district)\n# localityName_default = Paris\norganizationName = Organization Name (company)\norganizationName_default = NodeOPCUA\n# organizationalUnitName = Organizational Unit Name (department, division)\n# organizationalUnitName_default = R&D\ncommonName = Common Name (hostname, FQDN, IP, or your name)\ncommonName_max = 256\ncommonName_default = NodeOPCUA\n# emailAddress = Email Address\n# emailAddress_max = 40\n# emailAddress_default = node-opcua (at) node-opcua (dot) com\nsubjectAltName = $ENV::ALTNAME';
|
|
2958
|
+
var simple_config_template_cnf_default = config3;
|
|
2959
|
+
|
|
2404
2960
|
// packages/node-opcua-pki/lib/pki/certificate_manager.ts
|
|
2405
|
-
var
|
|
2406
|
-
var fsWriteFile =
|
|
2961
|
+
var configurationFileSimpleTemplate = simple_config_template_cnf_default;
|
|
2962
|
+
var fsWriteFile = import_node_fs11.default.promises.writeFile;
|
|
2407
2963
|
function getOrComputeInfo(entry) {
|
|
2408
2964
|
if (!entry.info) {
|
|
2409
|
-
entry.info = (0,
|
|
2965
|
+
entry.info = (0, import_node_opcua_crypto8.exploreCertificate)(entry.certificate);
|
|
2410
2966
|
}
|
|
2411
2967
|
return entry.info;
|
|
2412
2968
|
}
|
|
@@ -2433,14 +2989,14 @@ function coerceCertificateChain(certificate) {
|
|
|
2433
2989
|
if (Array.isArray(certificate)) {
|
|
2434
2990
|
if (certificate.length === 0) return [];
|
|
2435
2991
|
return certificate.reduce((acc, cert) => {
|
|
2436
|
-
return acc.concat((0,
|
|
2992
|
+
return acc.concat((0, import_node_opcua_crypto8.split_der)(cert));
|
|
2437
2993
|
}, []);
|
|
2438
2994
|
}
|
|
2439
|
-
return (0,
|
|
2995
|
+
return (0, import_node_opcua_crypto8.split_der)(certificate);
|
|
2440
2996
|
}
|
|
2441
2997
|
function makeFingerprint(certificate) {
|
|
2442
2998
|
const chain = coerceCertificateChain(certificate);
|
|
2443
|
-
return (0,
|
|
2999
|
+
return (0, import_node_opcua_crypto8.makeSHA1Thumbprint)(chain[0]).toString("hex");
|
|
2444
3000
|
}
|
|
2445
3001
|
function short(stringToShorten) {
|
|
2446
3002
|
return stringToShorten.substring(0, 10);
|
|
@@ -2450,7 +3006,7 @@ function buildIdealCertificateName(certificate) {
|
|
|
2450
3006
|
const chain = coerceCertificateChain(certificate);
|
|
2451
3007
|
const fingerprint = makeFingerprint(chain);
|
|
2452
3008
|
try {
|
|
2453
|
-
const commonName = (0,
|
|
3009
|
+
const commonName = (0, import_node_opcua_crypto8.exploreCertificate)(chain[0]).tbsCertificate.subject.commonName || "";
|
|
2454
3010
|
const sanitizedCommonName = commonName.replace(forbiddenChars, "_");
|
|
2455
3011
|
return `${sanitizedCommonName}[${fingerprint}]`;
|
|
2456
3012
|
} catch (_err) {
|
|
@@ -2467,7 +3023,7 @@ function isSelfSigned2(info) {
|
|
|
2467
3023
|
return info.tbsCertificate.extensions?.subjectKeyIdentifier === info.tbsCertificate.extensions?.authorityKeyIdentifier?.keyIdentifier;
|
|
2468
3024
|
}
|
|
2469
3025
|
function isSelfSigned3(certificate) {
|
|
2470
|
-
const info = (0,
|
|
3026
|
+
const info = (0, import_node_opcua_crypto8.exploreCertificate)(certificate);
|
|
2471
3027
|
return isSelfSigned2(info);
|
|
2472
3028
|
}
|
|
2473
3029
|
function _isIssuerInfo(info) {
|
|
@@ -2483,7 +3039,7 @@ function _isIssuerInfo(info) {
|
|
|
2483
3039
|
}
|
|
2484
3040
|
function isIssuer(certificate) {
|
|
2485
3041
|
try {
|
|
2486
|
-
const info = (0,
|
|
3042
|
+
const info = (0, import_node_opcua_crypto8.exploreCertificate)(certificate);
|
|
2487
3043
|
return _isIssuerInfo(info);
|
|
2488
3044
|
} catch (_err) {
|
|
2489
3045
|
return false;
|
|
@@ -2491,7 +3047,7 @@ function isIssuer(certificate) {
|
|
|
2491
3047
|
}
|
|
2492
3048
|
function isIntermediateIssuer(certificate) {
|
|
2493
3049
|
try {
|
|
2494
|
-
const info = (0,
|
|
3050
|
+
const info = (0, import_node_opcua_crypto8.exploreCertificate)(certificate);
|
|
2495
3051
|
if (!_isIssuerInfo(info)) {
|
|
2496
3052
|
return false;
|
|
2497
3053
|
}
|
|
@@ -2502,7 +3058,7 @@ function isIntermediateIssuer(certificate) {
|
|
|
2502
3058
|
}
|
|
2503
3059
|
function isRootIssuer(certificate) {
|
|
2504
3060
|
try {
|
|
2505
|
-
const info = (0,
|
|
3061
|
+
const info = (0, import_node_opcua_crypto8.exploreCertificate)(certificate);
|
|
2506
3062
|
if (!_isIssuerInfo(info)) {
|
|
2507
3063
|
return false;
|
|
2508
3064
|
}
|
|
@@ -2517,7 +3073,7 @@ function findIssuerCertificateInChain(certificate, chain) {
|
|
|
2517
3073
|
if (!firstCertificate) {
|
|
2518
3074
|
return null;
|
|
2519
3075
|
}
|
|
2520
|
-
const certInfo = (0,
|
|
3076
|
+
const certInfo = (0, import_node_opcua_crypto8.exploreCertificate)(firstCertificate);
|
|
2521
3077
|
if (isSelfSigned2(certInfo)) {
|
|
2522
3078
|
return firstCertificate;
|
|
2523
3079
|
}
|
|
@@ -2528,7 +3084,7 @@ function findIssuerCertificateInChain(certificate, chain) {
|
|
|
2528
3084
|
}
|
|
2529
3085
|
const coercedChain = coerceCertificateChain(chain);
|
|
2530
3086
|
const potentialIssuers = coercedChain.filter((c) => {
|
|
2531
|
-
const info = (0,
|
|
3087
|
+
const info = (0, import_node_opcua_crypto8.exploreCertificate)(c);
|
|
2532
3088
|
return info.tbsCertificate.extensions && info.tbsCertificate.extensions.subjectKeyIdentifier === wantedIssuerKey;
|
|
2533
3089
|
});
|
|
2534
3090
|
if (potentialIssuers.length === 1) {
|
|
@@ -2563,7 +3119,31 @@ var CertificateManager = class _CertificateManager extends import_node_events.Ev
|
|
|
2563
3119
|
// even if the consumer forgets to call dispose().
|
|
2564
3120
|
static #activeInstances = /* @__PURE__ */ new Set();
|
|
2565
3121
|
static #cleanupInstalled = false;
|
|
2566
|
-
static #
|
|
3122
|
+
static #exitHandler;
|
|
3123
|
+
/**
|
|
3124
|
+
* Install a best-effort `exit` hook that closes any watcher
|
|
3125
|
+
* still open when the process terminates.
|
|
3126
|
+
*
|
|
3127
|
+
* **This library never terminates the host process.** No
|
|
3128
|
+
* SIGINT/SIGTERM handler is installed: deciding how (and
|
|
3129
|
+
* whether) to shut down on a signal is the application's
|
|
3130
|
+
* responsibility, and a listener registered here would both
|
|
3131
|
+
* pre-empt the application's own graceful shutdown and
|
|
3132
|
+
* silently suppress Node's default signal behaviour.
|
|
3133
|
+
*
|
|
3134
|
+
* Nothing here is load-bearing for process exit. The native
|
|
3135
|
+
* `fs.watch` handles are `unref()`'d when the watchers are
|
|
3136
|
+
* created (see `#readCertificates`), so an undisposed
|
|
3137
|
+
* CertificateManager never keeps the event loop alive. This
|
|
3138
|
+
* hook is only tidiness on the way out.
|
|
3139
|
+
*
|
|
3140
|
+
* `exit` rather than `beforeExit`: `beforeExit` fires when
|
|
3141
|
+
* the loop merely drains and the loop can subsequently be
|
|
3142
|
+
* resurrected, which would leave a still-in-use instance
|
|
3143
|
+
* marked Disposed. `exit` is terminal, synchronous-only and
|
|
3144
|
+
* cannot alter the exit code.
|
|
3145
|
+
*/
|
|
3146
|
+
static #installExitCleanup() {
|
|
2567
3147
|
if (_CertificateManager.#cleanupInstalled) return;
|
|
2568
3148
|
_CertificateManager.#cleanupInstalled = true;
|
|
2569
3149
|
const closeDanglingWatchers = () => {
|
|
@@ -2579,13 +3159,22 @@ var CertificateManager = class _CertificateManager extends import_node_events.Ev
|
|
|
2579
3159
|
}
|
|
2580
3160
|
_CertificateManager.#activeInstances.clear();
|
|
2581
3161
|
};
|
|
2582
|
-
|
|
2583
|
-
|
|
2584
|
-
|
|
2585
|
-
|
|
2586
|
-
|
|
2587
|
-
|
|
3162
|
+
_CertificateManager.#exitHandler = closeDanglingWatchers;
|
|
3163
|
+
process.on("exit", closeDanglingWatchers);
|
|
3164
|
+
}
|
|
3165
|
+
/**
|
|
3166
|
+
* Remove the `exit` hook once the last instance is disposed,
|
|
3167
|
+
* so a library that is initialized and disposed repeatedly
|
|
3168
|
+
* does not accumulate process listeners. A later
|
|
3169
|
+
* `initialize()` re-arms it.
|
|
3170
|
+
*/
|
|
3171
|
+
static #uninstallExitCleanupIfIdle() {
|
|
3172
|
+
if (_CertificateManager.#activeInstances.size > 0) return;
|
|
3173
|
+
if (_CertificateManager.#exitHandler) {
|
|
3174
|
+
process.removeListener("exit", _CertificateManager.#exitHandler);
|
|
3175
|
+
_CertificateManager.#exitHandler = void 0;
|
|
2588
3176
|
}
|
|
3177
|
+
_CertificateManager.#cleanupInstalled = false;
|
|
2589
3178
|
}
|
|
2590
3179
|
/**
|
|
2591
3180
|
* Dispose **all** active CertificateManager instances,
|
|
@@ -2651,6 +3240,9 @@ var CertificateManager = class _CertificateManager extends import_node_events.Ev
|
|
|
2651
3240
|
#disableFileWatchers;
|
|
2652
3241
|
#privateKeyPassphrase;
|
|
2653
3242
|
#privateKeyProvider;
|
|
3243
|
+
#keyOperations;
|
|
3244
|
+
/** The stable lazy wrap handed out by {@link getKeyOperations} for non-opaque configurations. */
|
|
3245
|
+
#lazyLocalKeyOperations;
|
|
2654
3246
|
/**
|
|
2655
3247
|
* The on-disk key, decrypted once and kept for the instance's lifetime,
|
|
2656
3248
|
* so the passphrase (or its resolver function) is consulted at most
|
|
@@ -2694,14 +3286,25 @@ var CertificateManager = class _CertificateManager extends import_node_events.Ev
|
|
|
2694
3286
|
this.#disableFileWatchers = options.disableFileWatchers ?? process.env.OPCUA_PKI_DISABLE_FILE_WATCHERS === "true";
|
|
2695
3287
|
this.#privateKeyPassphrase = options.privateKeyPassphrase;
|
|
2696
3288
|
this.#privateKeyProvider = options.privateKeyProvider;
|
|
3289
|
+
this.#keyOperations = options.keyOperations;
|
|
3290
|
+
if (this.#keyOperations && this.#privateKeyProvider) {
|
|
3291
|
+
throw new Error(
|
|
3292
|
+
"CertificateManager: 'keyOperations' and 'privateKeyProvider' are mutually exclusive: one hides the key, the other sources its material"
|
|
3293
|
+
);
|
|
3294
|
+
}
|
|
3295
|
+
if (this.#keyOperations && this.#privateKeyPassphrase) {
|
|
3296
|
+
throw new Error(
|
|
3297
|
+
"CertificateManager: 'privateKeyPassphrase' is meaningless with 'keyOperations': there is no key material for a passphrase to protect"
|
|
3298
|
+
);
|
|
3299
|
+
}
|
|
2697
3300
|
mkdirRecursiveSync(options.location);
|
|
2698
|
-
if (!
|
|
3301
|
+
if (!import_node_fs11.default.existsSync(this.#location)) {
|
|
2699
3302
|
throw new Error(`CertificateManager cannot access location ${this.#location}`);
|
|
2700
3303
|
}
|
|
2701
3304
|
}
|
|
2702
3305
|
/** Path to the OpenSSL configuration file. */
|
|
2703
3306
|
get configFile() {
|
|
2704
|
-
return
|
|
3307
|
+
return import_node_path8.default.join(this.rootDir, "own/openssl.cnf");
|
|
2705
3308
|
}
|
|
2706
3309
|
/** Root directory of the PKI store. */
|
|
2707
3310
|
get rootDir() {
|
|
@@ -2717,7 +3320,7 @@ var CertificateManager = class _CertificateManager extends import_node_events.Ev
|
|
|
2717
3320
|
* be no meaningful file in that case).
|
|
2718
3321
|
*/
|
|
2719
3322
|
get privateKey() {
|
|
2720
|
-
return
|
|
3323
|
+
return import_node_path8.default.join(this.rootDir, "own/private/private_key.pem");
|
|
2721
3324
|
}
|
|
2722
3325
|
/**
|
|
2723
3326
|
* Resolve the private key: from `privateKeyProvider` if configured,
|
|
@@ -2735,6 +3338,11 @@ var CertificateManager = class _CertificateManager extends import_node_events.Ev
|
|
|
2735
3338
|
* authority on what the current key is.
|
|
2736
3339
|
*/
|
|
2737
3340
|
async getPrivateKey() {
|
|
3341
|
+
if (this.#keyOperations) {
|
|
3342
|
+
throw new import_node_opcua_crypto8.PrivateKeyUnavailableError(
|
|
3343
|
+
"CertificateManager.getPrivateKey is not available when keyOperations is configured: the private key is held by the key-operations provider (HSM/KMS) and cannot be read \u2014 use getKeyOperations() instead"
|
|
3344
|
+
);
|
|
3345
|
+
}
|
|
2738
3346
|
if (this.#privateKeyProvider) {
|
|
2739
3347
|
return await this.#privateKeyProvider.getPrivateKey();
|
|
2740
3348
|
}
|
|
@@ -2744,7 +3352,7 @@ var CertificateManager = class _CertificateManager extends import_node_events.Ev
|
|
|
2744
3352
|
if (!this.#privateKeyPromise) {
|
|
2745
3353
|
this.#privateKeyPromise = (async () => {
|
|
2746
3354
|
const passphrase = await resolvePrivateKeyPassphrase(this.#privateKeyPassphrase);
|
|
2747
|
-
return (0,
|
|
3355
|
+
return (0, import_node_opcua_crypto8.readPrivateKey)(this.privateKey, passphrase);
|
|
2748
3356
|
})().then(
|
|
2749
3357
|
(key) => {
|
|
2750
3358
|
this.#cachedPrivateKey = key;
|
|
@@ -2760,6 +3368,75 @@ var CertificateManager = class _CertificateManager extends import_node_events.Ev
|
|
|
2760
3368
|
}
|
|
2761
3369
|
/** In-flight first read of the on-disk key, so concurrent callers share one passphrase resolution. */
|
|
2762
3370
|
#privateKeyPromise;
|
|
3371
|
+
/**
|
|
3372
|
+
* True when this manager's key is opaque — configured through
|
|
3373
|
+
* `keyOperations`, held by an HSM/KMS, never obtainable as material.
|
|
3374
|
+
* When true, {@link getPrivateKey} throws `PrivateKeyUnavailableError`
|
|
3375
|
+
* and {@link getKeyOperations} is the only way to use the key.
|
|
3376
|
+
*/
|
|
3377
|
+
isPrivateKeyOpaque() {
|
|
3378
|
+
return !!this.#keyOperations;
|
|
3379
|
+
}
|
|
3380
|
+
/**
|
|
3381
|
+
* The key as an opaque {@link IKeyOperations} — the recommended way to
|
|
3382
|
+
* *use* the private key regardless of where it lives.
|
|
3383
|
+
*
|
|
3384
|
+
* Returns the configured `keyOperations` object when the key is opaque.
|
|
3385
|
+
* Otherwise returns a stable lazy wrap over {@link getPrivateKey}: its
|
|
3386
|
+
* methods resolve the key on first use (disk read, passphrase,
|
|
3387
|
+
* `privateKeyProvider` — all async), so the wrap offers no synchronous
|
|
3388
|
+
* fast path; callers that need one resolve the key themselves and build
|
|
3389
|
+
* a `LocalKeyOperations` over it. The wrap follows key rotation: a
|
|
3390
|
+
* `privateKeyProvider` that starts returning a different key gets a
|
|
3391
|
+
* fresh underlying `LocalKeyOperations`.
|
|
3392
|
+
*/
|
|
3393
|
+
getKeyOperations() {
|
|
3394
|
+
if (this.#keyOperations) {
|
|
3395
|
+
return this.#keyOperations;
|
|
3396
|
+
}
|
|
3397
|
+
if (!this.#lazyLocalKeyOperations) {
|
|
3398
|
+
let cached;
|
|
3399
|
+
const resolve = async () => {
|
|
3400
|
+
const key = await this.getPrivateKey();
|
|
3401
|
+
if (!cached || cached.key !== key) {
|
|
3402
|
+
cached = { key, ops: (0, import_node_opcua_crypto8.keyOperationsFromPrivateKey)(key) };
|
|
3403
|
+
}
|
|
3404
|
+
return cached.ops;
|
|
3405
|
+
};
|
|
3406
|
+
this.#lazyLocalKeyOperations = {
|
|
3407
|
+
sign: async (data, params) => (await resolve()).sign(data, params),
|
|
3408
|
+
decryptBlock: async (block, params) => (await resolve()).decryptBlock(block, params),
|
|
3409
|
+
getKeyMetadata: async () => (await resolve()).getKeyMetadata(),
|
|
3410
|
+
getPublicKey: async () => (await resolve()).getPublicKey()
|
|
3411
|
+
};
|
|
3412
|
+
}
|
|
3413
|
+
return this.#lazyLocalKeyOperations;
|
|
3414
|
+
}
|
|
3415
|
+
/**
|
|
3416
|
+
* Fail closed at `initialize()` time, not on the first certificate
|
|
3417
|
+
* operation, if the key cannot actually be used: wrong/missing
|
|
3418
|
+
* passphrase on an encrypted key, a broken `privateKeyProvider`, or an
|
|
3419
|
+
* unreachable `keyOperations` provider (probed via `getKeyMetadata`).
|
|
3420
|
+
*/
|
|
3421
|
+
async #probePrivateKey() {
|
|
3422
|
+
if (this.#keyOperations) {
|
|
3423
|
+
await this.#keyOperations.getKeyMetadata();
|
|
3424
|
+
return;
|
|
3425
|
+
}
|
|
3426
|
+
await this.getPrivateKey();
|
|
3427
|
+
}
|
|
3428
|
+
/**
|
|
3429
|
+
* The key to hand to a certificate-issuance primitive: the raw
|
|
3430
|
+
* {@link PrivateKey} for a local configuration, or a {@link CaSigner}
|
|
3431
|
+
* adapted from `keyOperations` when the key is opaque — which requires
|
|
3432
|
+
* the provider to implement `getPublicKey` (the adapter says so if not).
|
|
3433
|
+
*/
|
|
3434
|
+
async #resolveSigningKey() {
|
|
3435
|
+
if (this.#keyOperations) {
|
|
3436
|
+
return (0, import_node_opcua_crypto8.caSignerFromKeyOperations)(this.#keyOperations);
|
|
3437
|
+
}
|
|
3438
|
+
return await this.getPrivateKey();
|
|
3439
|
+
}
|
|
2763
3440
|
/**
|
|
2764
3441
|
* Enable, disable, or rotate the passphrase protecting the on-disk
|
|
2765
3442
|
* private key: decrypt with `oldPassphrase` (omit if the key is
|
|
@@ -2781,13 +3458,18 @@ var CertificateManager = class _CertificateManager extends import_node_events.Ev
|
|
|
2781
3458
|
* disk file for this method to rewrite).
|
|
2782
3459
|
*/
|
|
2783
3460
|
async reencryptPrivateKey(oldPassphrase, newPassphrase) {
|
|
3461
|
+
if (this.#keyOperations) {
|
|
3462
|
+
throw new import_node_opcua_crypto8.PrivateKeyUnavailableError(
|
|
3463
|
+
"reencryptPrivateKey: not supported when keyOperations is configured \u2014 there is no key material to rewrite"
|
|
3464
|
+
);
|
|
3465
|
+
}
|
|
2784
3466
|
if (this.#privateKeyProvider) {
|
|
2785
3467
|
throw new Error("reencryptPrivateKey: not supported when a privateKeyProvider is configured");
|
|
2786
3468
|
}
|
|
2787
3469
|
const oldPass = await resolvePrivateKeyPassphrase(oldPassphrase);
|
|
2788
3470
|
const newPass = await resolvePrivateKeyPassphrase(newPassphrase);
|
|
2789
3471
|
await this.withLock2(async () => {
|
|
2790
|
-
const privateKey = (0,
|
|
3472
|
+
const privateKey = (0, import_node_opcua_crypto8.readPrivateKey)(this.privateKey, oldPass);
|
|
2791
3473
|
await this.#rewritePrivateKeyFile(privateKey, newPass);
|
|
2792
3474
|
});
|
|
2793
3475
|
this.#cachedPrivateKey = void 0;
|
|
@@ -2803,15 +3485,15 @@ var CertificateManager = class _CertificateManager extends import_node_events.Ev
|
|
|
2803
3485
|
async #rewritePrivateKeyFile(privateKey, passphrase) {
|
|
2804
3486
|
const tmpFilename = `${this.privateKey}.${process.pid}-${Date.now()}.tmp`;
|
|
2805
3487
|
try {
|
|
2806
|
-
await (0,
|
|
2807
|
-
await
|
|
3488
|
+
await (0, import_node_opcua_crypto8.writePrivateKeyFile)(tmpFilename, privateKey, { passphrase });
|
|
3489
|
+
await import_node_fs11.default.promises.rename(tmpFilename, this.privateKey);
|
|
2808
3490
|
} finally {
|
|
2809
|
-
await
|
|
3491
|
+
await import_node_fs11.default.promises.rm(tmpFilename, { force: true });
|
|
2810
3492
|
}
|
|
2811
3493
|
}
|
|
2812
3494
|
/** Path to the OpenSSL random seed file. */
|
|
2813
3495
|
get randomFile() {
|
|
2814
|
-
return
|
|
3496
|
+
return import_node_path8.default.join(this.rootDir, "./random.rnd");
|
|
2815
3497
|
}
|
|
2816
3498
|
/**
|
|
2817
3499
|
* Move a certificate to the rejected store.
|
|
@@ -2848,30 +3530,30 @@ var CertificateManager = class _CertificateManager extends import_node_events.Ev
|
|
|
2848
3530
|
}
|
|
2849
3531
|
/** Path to the rejected certificates folder. */
|
|
2850
3532
|
get rejectedFolder() {
|
|
2851
|
-
return
|
|
3533
|
+
return import_node_path8.default.join(this.rootDir, "rejected");
|
|
2852
3534
|
}
|
|
2853
3535
|
/** Path to the trusted certificates folder. */
|
|
2854
3536
|
get trustedFolder() {
|
|
2855
|
-
return
|
|
3537
|
+
return import_node_path8.default.join(this.rootDir, "trusted/certs");
|
|
2856
3538
|
}
|
|
2857
3539
|
/** Path to the trusted CRL folder. */
|
|
2858
3540
|
get crlFolder() {
|
|
2859
|
-
return
|
|
3541
|
+
return import_node_path8.default.join(this.rootDir, "trusted/crl");
|
|
2860
3542
|
}
|
|
2861
3543
|
/** Path to the issuer (CA) certificates folder. */
|
|
2862
3544
|
get issuersCertFolder() {
|
|
2863
|
-
return
|
|
3545
|
+
return import_node_path8.default.join(this.rootDir, "issuers/certs");
|
|
2864
3546
|
}
|
|
2865
3547
|
/** Path to the issuer CRL folder. */
|
|
2866
3548
|
get issuersCrlFolder() {
|
|
2867
|
-
return
|
|
3549
|
+
return import_node_path8.default.join(this.rootDir, "issuers/crl");
|
|
2868
3550
|
}
|
|
2869
3551
|
/** Path to the own certificate folder. */
|
|
2870
3552
|
get ownCertFolder() {
|
|
2871
|
-
return
|
|
3553
|
+
return import_node_path8.default.join(this.rootDir, "own/certs");
|
|
2872
3554
|
}
|
|
2873
3555
|
get ownPrivateFolder() {
|
|
2874
|
-
return
|
|
3556
|
+
return import_node_path8.default.join(this.rootDir, "own/private");
|
|
2875
3557
|
}
|
|
2876
3558
|
/**
|
|
2877
3559
|
* Check if a certificate is in the trusted store.
|
|
@@ -2902,13 +3584,13 @@ var CertificateManager = class _CertificateManager extends import_node_events.Ev
|
|
|
2902
3584
|
return "Good";
|
|
2903
3585
|
}
|
|
2904
3586
|
try {
|
|
2905
|
-
(0,
|
|
3587
|
+
(0, import_node_opcua_crypto8.exploreCertificateInfo)(chain[0]);
|
|
2906
3588
|
} catch (_err) {
|
|
2907
3589
|
return "BadCertificateInvalid";
|
|
2908
3590
|
}
|
|
2909
|
-
const filename =
|
|
3591
|
+
const filename = import_node_path8.default.join(this.rejectedFolder, `${buildIdealCertificateName(leafCertificate)}.pem`);
|
|
2910
3592
|
debugLog("certificate has never been seen before and is now rejected (untrusted) ", filename);
|
|
2911
|
-
await fsWriteFile(filename, (0,
|
|
3593
|
+
await fsWriteFile(filename, (0, import_node_opcua_crypto8.toPem)(chain, "CERTIFICATE"));
|
|
2912
3594
|
this.#thumbs.rejected.set(fingerprint, { certificate: leafCertificate, filename });
|
|
2913
3595
|
}
|
|
2914
3596
|
return "BadCertificateUntrusted";
|
|
@@ -2922,7 +3604,7 @@ var CertificateManager = class _CertificateManager extends import_node_events.Ev
|
|
|
2922
3604
|
}
|
|
2923
3605
|
const chain = coerceCertificateChain(certificateOrChain);
|
|
2924
3606
|
debugLog("NB CERTIFICATE IN CHAIN = ", chain.length);
|
|
2925
|
-
const info = (0,
|
|
3607
|
+
const info = (0, import_node_opcua_crypto8.exploreCertificate)(chain[0]);
|
|
2926
3608
|
let hasValidIssuer = false;
|
|
2927
3609
|
let hasTrustedIssuer = false;
|
|
2928
3610
|
const hasIssuerKey = info.tbsCertificate.extensions?.authorityKeyIdentifier?.keyIdentifier;
|
|
@@ -2969,7 +3651,7 @@ var CertificateManager = class _CertificateManager extends import_node_events.Ev
|
|
|
2969
3651
|
if (issuerStatus !== "Good" /* Good */ && issuerStatus !== "BadCertificateUntrusted" /* BadCertificateUntrusted */) {
|
|
2970
3652
|
return "BadSecurityChecksFailed" /* BadSecurityChecksFailed */;
|
|
2971
3653
|
}
|
|
2972
|
-
const isCertificateSignatureOK = (0,
|
|
3654
|
+
const isCertificateSignatureOK = (0, import_node_opcua_crypto8.verifyCertificateSignature)(chain[0], issuerCertificate);
|
|
2973
3655
|
if (!isCertificateSignatureOK) {
|
|
2974
3656
|
debugLog(" the certificate was not signed by the issuer as it claim to be ! Danger");
|
|
2975
3657
|
return "BadSecurityChecksFailed" /* BadSecurityChecksFailed */;
|
|
@@ -2995,7 +3677,7 @@ var CertificateManager = class _CertificateManager extends import_node_events.Ev
|
|
|
2995
3677
|
return "BadSecurityChecksFailed" /* BadSecurityChecksFailed */;
|
|
2996
3678
|
}
|
|
2997
3679
|
} else {
|
|
2998
|
-
const isCertificateSignatureOK = (0,
|
|
3680
|
+
const isCertificateSignatureOK = (0, import_node_opcua_crypto8.verifyCertificateSignature)(chain[0], chain[0]);
|
|
2999
3681
|
if (!isCertificateSignatureOK) {
|
|
3000
3682
|
debugLog("Self-signed Certificate signature is not valid");
|
|
3001
3683
|
return "BadSecurityChecksFailed" /* BadSecurityChecksFailed */;
|
|
@@ -3010,23 +3692,19 @@ var CertificateManager = class _CertificateManager extends import_node_events.Ev
|
|
|
3010
3692
|
return "BadCertificateUntrusted" /* BadCertificateUntrusted */;
|
|
3011
3693
|
}
|
|
3012
3694
|
}
|
|
3013
|
-
const
|
|
3014
|
-
debugLog("chain[1] info=", _c2);
|
|
3015
|
-
const certificateInfo = (0, import_node_opcua_crypto5.exploreCertificateInfo)(chain[0]);
|
|
3695
|
+
const { validity } = (0, import_node_opcua_crypto8.exploreCertificate)(chain[0]).tbsCertificate;
|
|
3016
3696
|
const now = /* @__PURE__ */ new Date();
|
|
3017
3697
|
let isTimeInvalid = false;
|
|
3018
|
-
if (
|
|
3698
|
+
if (validity.notBefore.getTime() > now.getTime()) {
|
|
3019
3699
|
debugLog(
|
|
3020
|
-
`${import_chalk6.default.red("certificate is invalid : certificate is not active yet !")} not before date =${
|
|
3700
|
+
`${import_chalk6.default.red("certificate is invalid : certificate is not active yet !")} not before date =${validity.notBefore}`
|
|
3021
3701
|
);
|
|
3022
3702
|
if (!options.acceptPendingCertificate) {
|
|
3023
3703
|
isTimeInvalid = true;
|
|
3024
3704
|
}
|
|
3025
3705
|
}
|
|
3026
|
-
if (
|
|
3027
|
-
debugLog(
|
|
3028
|
-
`${import_chalk6.default.red("certificate is invalid : certificate has expired !")} not after date =${certificateInfo.notAfter}`
|
|
3029
|
-
);
|
|
3706
|
+
if (validity.notAfter.getTime() <= now.getTime()) {
|
|
3707
|
+
debugLog(`${import_chalk6.default.red("certificate is invalid : certificate has expired !")} not after date =${validity.notAfter}`);
|
|
3030
3708
|
if (!options.acceptOutdatedCertificate) {
|
|
3031
3709
|
isTimeInvalid = true;
|
|
3032
3710
|
}
|
|
@@ -3065,7 +3743,7 @@ var CertificateManager = class _CertificateManager extends import_node_events.Ev
|
|
|
3065
3743
|
const chain = coerceCertificateChain(certificate);
|
|
3066
3744
|
for (const element of chain) {
|
|
3067
3745
|
try {
|
|
3068
|
-
(0,
|
|
3746
|
+
(0, import_node_opcua_crypto8.exploreCertificate)(element);
|
|
3069
3747
|
} catch (_err) {
|
|
3070
3748
|
return "BadCertificateInvalid" /* BadCertificateInvalid */;
|
|
3071
3749
|
}
|
|
@@ -3118,55 +3796,55 @@ var CertificateManager = class _CertificateManager extends import_node_events.Ev
|
|
|
3118
3796
|
this.#initializingPromise = void 0;
|
|
3119
3797
|
this.state = 2 /* Initialized */;
|
|
3120
3798
|
_CertificateManager.#activeInstances.add(this);
|
|
3121
|
-
_CertificateManager.#
|
|
3799
|
+
_CertificateManager.#installExitCleanup();
|
|
3122
3800
|
}
|
|
3123
3801
|
async #initialize() {
|
|
3124
3802
|
const pkiDir = this.#location;
|
|
3125
3803
|
mkdirRecursiveSync(pkiDir);
|
|
3126
|
-
mkdirRecursiveSync(
|
|
3127
|
-
mkdirRecursiveSync(
|
|
3128
|
-
ensurePrivateDirectory(
|
|
3129
|
-
mkdirRecursiveSync(
|
|
3130
|
-
mkdirRecursiveSync(
|
|
3131
|
-
mkdirRecursiveSync(
|
|
3132
|
-
mkdirRecursiveSync(
|
|
3133
|
-
mkdirRecursiveSync(
|
|
3134
|
-
mkdirRecursiveSync(
|
|
3135
|
-
mkdirRecursiveSync(
|
|
3136
|
-
const ownsDiskKey = !this.#privateKeyProvider;
|
|
3137
|
-
const needsKeyGeneration = ownsDiskKey && !
|
|
3804
|
+
mkdirRecursiveSync(import_node_path8.default.join(pkiDir, "own"));
|
|
3805
|
+
mkdirRecursiveSync(import_node_path8.default.join(pkiDir, "own/certs"));
|
|
3806
|
+
ensurePrivateDirectory(import_node_path8.default.join(pkiDir, "own/private"));
|
|
3807
|
+
mkdirRecursiveSync(import_node_path8.default.join(pkiDir, "rejected"));
|
|
3808
|
+
mkdirRecursiveSync(import_node_path8.default.join(pkiDir, "trusted"));
|
|
3809
|
+
mkdirRecursiveSync(import_node_path8.default.join(pkiDir, "trusted/certs"));
|
|
3810
|
+
mkdirRecursiveSync(import_node_path8.default.join(pkiDir, "trusted/crl"));
|
|
3811
|
+
mkdirRecursiveSync(import_node_path8.default.join(pkiDir, "issuers"));
|
|
3812
|
+
mkdirRecursiveSync(import_node_path8.default.join(pkiDir, "issuers/certs"));
|
|
3813
|
+
mkdirRecursiveSync(import_node_path8.default.join(pkiDir, "issuers/crl"));
|
|
3814
|
+
const ownsDiskKey = !this.#privateKeyProvider && !this.#keyOperations;
|
|
3815
|
+
const needsKeyGeneration = ownsDiskKey && !import_node_fs11.default.existsSync(this.privateKey);
|
|
3138
3816
|
const needsKeyEncryption = ownsDiskKey && !needsKeyGeneration && this.#privateKeyPassphrase !== void 0 && !isEncryptedPrivateKeyFile(this.privateKey);
|
|
3139
|
-
if (!
|
|
3817
|
+
if (!import_node_fs11.default.existsSync(this.configFile) || needsKeyGeneration || needsKeyEncryption) {
|
|
3140
3818
|
return await this.withLock2(async () => {
|
|
3141
3819
|
if (this.state === 3 /* Disposing */ || this.state === 4 /* Disposed */) {
|
|
3142
3820
|
return;
|
|
3143
3821
|
}
|
|
3144
|
-
if (!
|
|
3145
|
-
|
|
3822
|
+
if (!import_node_fs11.default.existsSync(this.configFile)) {
|
|
3823
|
+
import_node_fs11.default.writeFileSync(this.configFile, configurationFileSimpleTemplate);
|
|
3146
3824
|
}
|
|
3147
|
-
if (ownsDiskKey && !
|
|
3825
|
+
if (ownsDiskKey && !import_node_fs11.default.existsSync(this.privateKey)) {
|
|
3148
3826
|
debugLog("generating private key ...");
|
|
3149
3827
|
const passphrase = await resolvePrivateKeyPassphrase(this.#privateKeyPassphrase);
|
|
3150
|
-
await (0,
|
|
3151
|
-
this.#cachedPrivateKey = (0,
|
|
3828
|
+
await (0, import_node_opcua_crypto8.generatePrivateKeyFile)(this.privateKey, this.keySize, { passphrase });
|
|
3829
|
+
this.#cachedPrivateKey = (0, import_node_opcua_crypto8.readPrivateKey)(this.privateKey, passphrase);
|
|
3152
3830
|
} else if (ownsDiskKey && this.#privateKeyPassphrase !== void 0 && !isEncryptedPrivateKeyFile(this.privateKey)) {
|
|
3153
3831
|
warningLog("initialize: private key is plaintext but a passphrase is configured; encrypting it in place");
|
|
3154
3832
|
const passphrase = await resolvePrivateKeyPassphrase(this.#privateKeyPassphrase);
|
|
3155
|
-
const plaintextKey = (0,
|
|
3833
|
+
const plaintextKey = (0, import_node_opcua_crypto8.readPrivateKey)(this.privateKey);
|
|
3156
3834
|
await this.#rewritePrivateKeyFile(plaintextKey, passphrase);
|
|
3157
3835
|
this.#cachedPrivateKey = plaintextKey;
|
|
3158
3836
|
}
|
|
3159
3837
|
if (ownsDiskKey) {
|
|
3160
3838
|
restrictPrivateFilePermissions(this.privateKey, 384);
|
|
3161
3839
|
}
|
|
3162
|
-
await this
|
|
3840
|
+
await this.#probePrivateKey();
|
|
3163
3841
|
await this.#readCertificates();
|
|
3164
3842
|
});
|
|
3165
3843
|
} else {
|
|
3166
3844
|
if (ownsDiskKey) {
|
|
3167
3845
|
restrictPrivateFilePermissions(this.privateKey, 384);
|
|
3168
3846
|
}
|
|
3169
|
-
await this
|
|
3847
|
+
await this.#probePrivateKey();
|
|
3170
3848
|
await this.#readCertificates();
|
|
3171
3849
|
}
|
|
3172
3850
|
}
|
|
@@ -3190,7 +3868,7 @@ var CertificateManager = class _CertificateManager extends import_node_events.Ev
|
|
|
3190
3868
|
}
|
|
3191
3869
|
try {
|
|
3192
3870
|
this.state = 3 /* Disposing */;
|
|
3193
|
-
await (0,
|
|
3871
|
+
await (0, import_global_mutex2.drainPendingLocks)();
|
|
3194
3872
|
for (const unreff of this.#pendingUnrefs) {
|
|
3195
3873
|
unreff();
|
|
3196
3874
|
}
|
|
@@ -3205,6 +3883,7 @@ var CertificateManager = class _CertificateManager extends import_node_events.Ev
|
|
|
3205
3883
|
this.#cachedPrivateKey = void 0;
|
|
3206
3884
|
this.#privateKeyPromise = void 0;
|
|
3207
3885
|
_CertificateManager.#activeInstances.delete(this);
|
|
3886
|
+
_CertificateManager.#uninstallExitCleanupIfIdle();
|
|
3208
3887
|
}
|
|
3209
3888
|
}
|
|
3210
3889
|
/**
|
|
@@ -3232,8 +3911,8 @@ var CertificateManager = class _CertificateManager extends import_node_events.Ev
|
|
|
3232
3911
|
await this.#readCertificates();
|
|
3233
3912
|
}
|
|
3234
3913
|
async withLock2(action) {
|
|
3235
|
-
const lockFileName =
|
|
3236
|
-
return (0,
|
|
3914
|
+
const lockFileName = import_node_path8.default.join(this.rootDir, "mutex");
|
|
3915
|
+
return (0, import_global_mutex2.withLock)({ fileToLock: lockFileName }, async () => {
|
|
3237
3916
|
return await action();
|
|
3238
3917
|
});
|
|
3239
3918
|
}
|
|
@@ -3250,16 +3929,16 @@ var CertificateManager = class _CertificateManager extends import_node_events.Ev
|
|
|
3250
3929
|
if (typeof params.applicationUri !== "string") {
|
|
3251
3930
|
throw new Error("createSelfSignedCertificate: expecting applicationUri to be a string");
|
|
3252
3931
|
}
|
|
3253
|
-
if (!this.#privateKeyProvider && !
|
|
3932
|
+
if (!this.#privateKeyProvider && !this.#keyOperations && !import_node_fs11.default.existsSync(this.privateKey)) {
|
|
3254
3933
|
throw new Error(`Cannot find private key ${this.privateKey}`);
|
|
3255
3934
|
}
|
|
3256
|
-
let certificateFilename =
|
|
3935
|
+
let certificateFilename = import_node_path8.default.join(this.rootDir, "own/certs/self_signed_certificate.pem");
|
|
3257
3936
|
certificateFilename = params.outputFile || certificateFilename;
|
|
3258
3937
|
const _params = {
|
|
3259
3938
|
...params,
|
|
3260
3939
|
rootDir: this.rootDir,
|
|
3261
3940
|
configFile: this.configFile,
|
|
3262
|
-
privateKey: await this
|
|
3941
|
+
privateKey: await this.#resolveSigningKey(),
|
|
3263
3942
|
subject: params.subject || "CN=FIXME"
|
|
3264
3943
|
};
|
|
3265
3944
|
await this.withLock2(async () => {
|
|
@@ -3285,14 +3964,14 @@ var CertificateManager = class _CertificateManager extends import_node_events.Ev
|
|
|
3285
3964
|
}
|
|
3286
3965
|
const _params = {
|
|
3287
3966
|
...params,
|
|
3288
|
-
rootDir:
|
|
3289
|
-
configFile:
|
|
3290
|
-
privateKey: await this
|
|
3967
|
+
rootDir: import_node_path8.default.resolve(this.rootDir),
|
|
3968
|
+
configFile: import_node_path8.default.resolve(this.configFile),
|
|
3969
|
+
privateKey: await this.#resolveSigningKey()
|
|
3291
3970
|
};
|
|
3292
3971
|
return await this.withLock2(async () => {
|
|
3293
3972
|
const now = /* @__PURE__ */ new Date();
|
|
3294
3973
|
const today = `${now.toISOString().slice(0, 10)}_${now.getTime()}`;
|
|
3295
|
-
const certificateSigningRequestFilename =
|
|
3974
|
+
const certificateSigningRequestFilename = import_node_path8.default.join(this.rootDir, "own/certs", `certificate_${today}.csr`);
|
|
3296
3975
|
await createCertificateSigningRequestAsync(certificateSigningRequestFilename, _params);
|
|
3297
3976
|
return certificateSigningRequestFilename;
|
|
3298
3977
|
});
|
|
@@ -3312,13 +3991,13 @@ var CertificateManager = class _CertificateManager extends import_node_events.Ev
|
|
|
3312
3991
|
return status;
|
|
3313
3992
|
}
|
|
3314
3993
|
}
|
|
3315
|
-
const pemCertificate = (0,
|
|
3994
|
+
const pemCertificate = (0, import_node_opcua_crypto8.toPem)(certificate, "CERTIFICATE");
|
|
3316
3995
|
const fingerprint = makeFingerprint(certificate);
|
|
3317
3996
|
if (this.#thumbs.issuers.certs.has(fingerprint)) {
|
|
3318
3997
|
return "Good" /* Good */;
|
|
3319
3998
|
}
|
|
3320
|
-
const filename =
|
|
3321
|
-
await
|
|
3999
|
+
const filename = import_node_path8.default.join(this.issuersCertFolder, `issuer_${buildIdealCertificateName(certificate)}.pem`);
|
|
4000
|
+
await import_node_fs11.default.promises.writeFile(filename, pemCertificate, "ascii");
|
|
3322
4001
|
this.#thumbs.issuers.certs.set(fingerprint, { certificate, filename });
|
|
3323
4002
|
if (addInTrustList) {
|
|
3324
4003
|
await this.trustCertificate(certificate);
|
|
@@ -3352,15 +4031,15 @@ var CertificateManager = class _CertificateManager extends import_node_events.Ev
|
|
|
3352
4031
|
try {
|
|
3353
4032
|
const index = target === "trusted" ? this.#thumbs.crl : this.#thumbs.issuersCrl;
|
|
3354
4033
|
const folder = target === "trusted" ? this.crlFolder : this.issuersCrlFolder;
|
|
3355
|
-
const crlInfo = (0,
|
|
4034
|
+
const crlInfo = (0, import_node_opcua_crypto8.exploreCertificateRevocationList)(crl);
|
|
3356
4035
|
const key = crlInfo.tbsCertList.issuerFingerprint;
|
|
3357
4036
|
if (!index.has(key)) {
|
|
3358
4037
|
index.set(key, { crls: [], serialNumbers: {} });
|
|
3359
4038
|
}
|
|
3360
|
-
const pemCertificate = (0,
|
|
4039
|
+
const pemCertificate = (0, import_node_opcua_crypto8.toPem)(crl, "X509 CRL");
|
|
3361
4040
|
const sanitizedKey = key.replace(/:/g, "");
|
|
3362
|
-
const filename =
|
|
3363
|
-
await
|
|
4041
|
+
const filename = import_node_path8.default.join(folder, `crl_[${sanitizedKey}].pem`);
|
|
4042
|
+
await import_node_fs11.default.promises.writeFile(filename, pemCertificate, "ascii");
|
|
3364
4043
|
await this.#onCrlFileAdded(index, filename);
|
|
3365
4044
|
await this.#waitAndCheckCRLProcessingStatus();
|
|
3366
4045
|
return "Good" /* Good */;
|
|
@@ -3379,11 +4058,11 @@ var CertificateManager = class _CertificateManager extends import_node_events.Ev
|
|
|
3379
4058
|
async clearRevocationLists(target) {
|
|
3380
4059
|
const clearFolder = async (folder, index) => {
|
|
3381
4060
|
try {
|
|
3382
|
-
const files = await
|
|
4061
|
+
const files = await import_node_fs11.default.promises.readdir(folder);
|
|
3383
4062
|
for (const file of files) {
|
|
3384
|
-
const ext =
|
|
4063
|
+
const ext = import_node_path8.default.extname(file).toLowerCase();
|
|
3385
4064
|
if (ext === ".crl" || ext === ".pem" || ext === ".der") {
|
|
3386
|
-
await
|
|
4065
|
+
await import_node_fs11.default.promises.unlink(import_node_path8.default.join(folder, file));
|
|
3387
4066
|
}
|
|
3388
4067
|
}
|
|
3389
4068
|
} catch (err) {
|
|
@@ -3425,7 +4104,7 @@ var CertificateManager = class _CertificateManager extends import_node_events.Ev
|
|
|
3425
4104
|
return null;
|
|
3426
4105
|
}
|
|
3427
4106
|
try {
|
|
3428
|
-
await
|
|
4107
|
+
await import_node_fs11.default.promises.unlink(entry.filename);
|
|
3429
4108
|
} catch (err) {
|
|
3430
4109
|
if (err.code !== "ENOENT") {
|
|
3431
4110
|
throw err;
|
|
@@ -3449,7 +4128,7 @@ var CertificateManager = class _CertificateManager extends import_node_events.Ev
|
|
|
3449
4128
|
return null;
|
|
3450
4129
|
}
|
|
3451
4130
|
try {
|
|
3452
|
-
await
|
|
4131
|
+
await import_node_fs11.default.promises.unlink(entry.filename);
|
|
3453
4132
|
} catch (err) {
|
|
3454
4133
|
if (err.code !== "ENOENT") {
|
|
3455
4134
|
throw err;
|
|
@@ -3465,14 +4144,14 @@ var CertificateManager = class _CertificateManager extends import_node_events.Ev
|
|
|
3465
4144
|
* @param target - "issuers", "trusted", or "all" (default "all")
|
|
3466
4145
|
*/
|
|
3467
4146
|
async removeRevocationListsForIssuer(issuerCertificate, target = "all") {
|
|
3468
|
-
const issuerInfo = (0,
|
|
4147
|
+
const issuerInfo = (0, import_node_opcua_crypto8.exploreCertificate)(issuerCertificate);
|
|
3469
4148
|
const issuerFingerprint = issuerInfo.tbsCertificate.subjectFingerPrint;
|
|
3470
4149
|
const processIndex = async (index) => {
|
|
3471
4150
|
const crlData = index.get(issuerFingerprint);
|
|
3472
4151
|
if (!crlData) return;
|
|
3473
4152
|
for (const crlEntry of crlData.crls) {
|
|
3474
4153
|
try {
|
|
3475
|
-
await
|
|
4154
|
+
await import_node_fs11.default.promises.unlink(crlEntry.filename);
|
|
3476
4155
|
} catch (err) {
|
|
3477
4156
|
if (err.code !== "ENOENT") {
|
|
3478
4157
|
throw err;
|
|
@@ -3539,7 +4218,7 @@ var CertificateManager = class _CertificateManager extends import_node_events.Ev
|
|
|
3539
4218
|
const opts = this.#addCertValidation;
|
|
3540
4219
|
let leafInfo;
|
|
3541
4220
|
try {
|
|
3542
|
-
leafInfo = (0,
|
|
4221
|
+
leafInfo = (0, import_node_opcua_crypto8.exploreCertificate)(leafCertificate);
|
|
3543
4222
|
} catch (_err) {
|
|
3544
4223
|
return "BadCertificateInvalid" /* BadCertificateInvalid */;
|
|
3545
4224
|
}
|
|
@@ -3553,23 +4232,18 @@ var CertificateManager = class _CertificateManager extends import_node_events.Ev
|
|
|
3553
4232
|
return "BadSecurityChecksFailed" /* BadSecurityChecksFailed */;
|
|
3554
4233
|
}
|
|
3555
4234
|
if (!opts.acceptExpiredCertificate) {
|
|
3556
|
-
|
|
3557
|
-
try {
|
|
3558
|
-
certDetails = (0, import_node_opcua_crypto5.exploreCertificateInfo)(currentCert);
|
|
3559
|
-
} catch (_err) {
|
|
3560
|
-
return "BadCertificateInvalid" /* BadCertificateInvalid */;
|
|
3561
|
-
}
|
|
4235
|
+
const { validity } = currentInfo.tbsCertificate;
|
|
3562
4236
|
const now = /* @__PURE__ */ new Date();
|
|
3563
|
-
if (
|
|
4237
|
+
if (validity.notBefore.getTime() > now.getTime()) {
|
|
3564
4238
|
return "BadCertificateTimeInvalid" /* BadCertificateTimeInvalid */;
|
|
3565
4239
|
}
|
|
3566
|
-
if (
|
|
4240
|
+
if (validity.notAfter.getTime() <= now.getTime()) {
|
|
3567
4241
|
return depth === 1 ? "BadCertificateTimeInvalid" /* BadCertificateTimeInvalid */ : "BadCertificateIssuerTimeInvalid" /* BadCertificateIssuerTimeInvalid */;
|
|
3568
4242
|
}
|
|
3569
4243
|
}
|
|
3570
4244
|
if (isSelfSigned2(currentInfo)) {
|
|
3571
4245
|
try {
|
|
3572
|
-
if (!(0,
|
|
4246
|
+
if (!(0, import_node_opcua_crypto8.verifyCertificateSignature)(currentCert, currentCert)) {
|
|
3573
4247
|
return "BadCertificateInvalid" /* BadCertificateInvalid */;
|
|
3574
4248
|
}
|
|
3575
4249
|
} catch (_err) {
|
|
@@ -3585,7 +4259,7 @@ var CertificateManager = class _CertificateManager extends import_node_events.Ev
|
|
|
3585
4259
|
}
|
|
3586
4260
|
}
|
|
3587
4261
|
try {
|
|
3588
|
-
if (!(0,
|
|
4262
|
+
if (!(0, import_node_opcua_crypto8.verifyCertificateSignature)(currentCert, issuerCert)) {
|
|
3589
4263
|
return "BadCertificateInvalid" /* BadCertificateInvalid */;
|
|
3590
4264
|
}
|
|
3591
4265
|
} catch (_err) {
|
|
@@ -3607,7 +4281,7 @@ var CertificateManager = class _CertificateManager extends import_node_events.Ev
|
|
|
3607
4281
|
}
|
|
3608
4282
|
currentCert = issuerCert;
|
|
3609
4283
|
try {
|
|
3610
|
-
currentInfo = (0,
|
|
4284
|
+
currentInfo = (0, import_node_opcua_crypto8.exploreCertificate)(currentCert);
|
|
3611
4285
|
} catch (_err) {
|
|
3612
4286
|
return "BadCertificateInvalid" /* BadCertificateInvalid */;
|
|
3613
4287
|
}
|
|
@@ -3632,7 +4306,7 @@ var CertificateManager = class _CertificateManager extends import_node_events.Ev
|
|
|
3632
4306
|
for (const entry of this.#thumbs.trusted.values()) {
|
|
3633
4307
|
if (!entry.certificate) continue;
|
|
3634
4308
|
try {
|
|
3635
|
-
if ((0,
|
|
4309
|
+
if ((0, import_node_opcua_crypto8.verifyCertificateSignature)(entry.certificate, issuerCertificate)) {
|
|
3636
4310
|
return true;
|
|
3637
4311
|
}
|
|
3638
4312
|
} catch (_err) {
|
|
@@ -3656,7 +4330,7 @@ var CertificateManager = class _CertificateManager extends import_node_events.Ev
|
|
|
3656
4330
|
*/
|
|
3657
4331
|
async findIssuerCertificate(certificate) {
|
|
3658
4332
|
const firstCertificate = coerceCertificateChain(certificate)[0];
|
|
3659
|
-
const certInfo = (0,
|
|
4333
|
+
const certInfo = (0, import_node_opcua_crypto8.exploreCertificate)(firstCertificate);
|
|
3660
4334
|
if (isSelfSigned2(certInfo)) {
|
|
3661
4335
|
return firstCertificate;
|
|
3662
4336
|
}
|
|
@@ -3714,7 +4388,7 @@ var CertificateManager = class _CertificateManager extends import_node_events.Ev
|
|
|
3714
4388
|
let depth = 0;
|
|
3715
4389
|
while (depth < maxDepth) {
|
|
3716
4390
|
const lastCert = result[result.length - 1];
|
|
3717
|
-
const lastInfo = (0,
|
|
4391
|
+
const lastInfo = (0, import_node_opcua_crypto8.exploreCertificate)(lastCert);
|
|
3718
4392
|
if (isSelfSigned2(lastInfo)) {
|
|
3719
4393
|
const wasExtended = result.length > chain.length;
|
|
3720
4394
|
return {
|
|
@@ -3741,7 +4415,7 @@ var CertificateManager = class _CertificateManager extends import_node_events.Ev
|
|
|
3741
4415
|
return {
|
|
3742
4416
|
chain: result,
|
|
3743
4417
|
status: "AlreadyComplete" /* AlreadyComplete */,
|
|
3744
|
-
message: `Chain ends at root "${(0,
|
|
4418
|
+
message: `Chain ends at root "${(0, import_node_opcua_crypto8.exploreCertificate)(issuerCert).tbsCertificate.subject.commonName}" (already present in chain).`
|
|
3745
4419
|
};
|
|
3746
4420
|
}
|
|
3747
4421
|
result.push(issuerCert);
|
|
@@ -3780,9 +4454,9 @@ var CertificateManager = class _CertificateManager extends import_node_events.Ev
|
|
|
3780
4454
|
const fingerprint = makeFingerprint(certificate);
|
|
3781
4455
|
let status = await this.#checkRejectedOrTrusted(certificate);
|
|
3782
4456
|
if (status === "unknown") {
|
|
3783
|
-
const pem = (0,
|
|
3784
|
-
const filename =
|
|
3785
|
-
await
|
|
4457
|
+
const pem = (0, import_node_opcua_crypto8.toPem)(chain, "CERTIFICATE");
|
|
4458
|
+
const filename = import_node_path8.default.join(this.rejectedFolder, `${buildIdealCertificateName(certificate)}.pem`);
|
|
4459
|
+
await import_node_fs11.default.promises.writeFile(filename, pem);
|
|
3786
4460
|
this.#thumbs.rejected.set(fingerprint, { certificate, filename });
|
|
3787
4461
|
status = "rejected";
|
|
3788
4462
|
}
|
|
@@ -3798,10 +4472,10 @@ var CertificateManager = class _CertificateManager extends import_node_events.Ev
|
|
|
3798
4472
|
throw new Error(`#moveCertificate: certificate ${fingerprint.substring(0, 10)} not found in ${status} index`);
|
|
3799
4473
|
}
|
|
3800
4474
|
const destFolder = newStatus === "trusted" ? this.trustedFolder : this.rejectedFolder;
|
|
3801
|
-
const certificateDest =
|
|
4475
|
+
const certificateDest = import_node_path8.default.join(destFolder, import_node_path8.default.basename(srcEntry.filename));
|
|
3802
4476
|
debugLog("#moveCertificate", fingerprint.substring(0, 10), "old name", srcEntry.filename);
|
|
3803
4477
|
debugLog("#moveCertificate", fingerprint.substring(0, 10), "new name", certificateDest);
|
|
3804
|
-
await
|
|
4478
|
+
await import_node_fs11.default.promises.rename(srcEntry.filename, certificateDest);
|
|
3805
4479
|
indexSrc.delete(fingerprint);
|
|
3806
4480
|
const indexDest = newStatus === "trusted" ? this.#thumbs.trusted : this.#thumbs.rejected;
|
|
3807
4481
|
indexDest.set(fingerprint, { certificate, filename: certificateDest });
|
|
@@ -3809,7 +4483,7 @@ var CertificateManager = class _CertificateManager extends import_node_events.Ev
|
|
|
3809
4483
|
});
|
|
3810
4484
|
}
|
|
3811
4485
|
#findAssociatedCRLs(issuerCertificate) {
|
|
3812
|
-
const issuerCertificateInfo = (0,
|
|
4486
|
+
const issuerCertificateInfo = (0, import_node_opcua_crypto8.exploreCertificate)(issuerCertificate);
|
|
3813
4487
|
const key = issuerCertificateInfo.tbsCertificate.subjectFingerPrint;
|
|
3814
4488
|
return this.#thumbs.issuersCrl.get(key) ?? this.#thumbs.crl.get(key) ?? null;
|
|
3815
4489
|
}
|
|
@@ -3848,7 +4522,7 @@ var CertificateManager = class _CertificateManager extends import_node_events.Ev
|
|
|
3848
4522
|
if (!crls) {
|
|
3849
4523
|
return "BadCertificateRevocationUnknown" /* BadCertificateRevocationUnknown */;
|
|
3850
4524
|
}
|
|
3851
|
-
const certInfo = (0,
|
|
4525
|
+
const certInfo = (0, import_node_opcua_crypto8.exploreCertificate)(firstCertificate);
|
|
3852
4526
|
const serialNumber = certInfo.tbsCertificate.serialNumber || certInfo.tbsCertificate.extensions?.authorityKeyIdentifier?.serial || "";
|
|
3853
4527
|
const key = certInfo.tbsCertificate.extensions?.authorityKeyIdentifier?.authorityCertIssuerFingerPrint || "<unknown>";
|
|
3854
4528
|
const crl2 = this.#thumbs.crl.get(key) ?? null;
|
|
@@ -3872,8 +4546,8 @@ var CertificateManager = class _CertificateManager extends import_node_events.Ev
|
|
|
3872
4546
|
const nextCRL = this.#queue.shift();
|
|
3873
4547
|
if (!nextCRL) return;
|
|
3874
4548
|
const { index, filename } = nextCRL;
|
|
3875
|
-
const crl = await (0,
|
|
3876
|
-
const crlInfo = (0,
|
|
4549
|
+
const crl = await (0, import_node_opcua_crypto8.readCertificateRevocationList)(filename);
|
|
4550
|
+
const crlInfo = (0, import_node_opcua_crypto8.exploreCertificateRevocationList)(crl);
|
|
3877
4551
|
debugLog(import_chalk6.default.cyan("add CRL in folder "), filename);
|
|
3878
4552
|
const fingerprint = crlInfo.tbsCertList.issuerFingerprint;
|
|
3879
4553
|
if (!index.has(fingerprint)) {
|
|
@@ -3913,14 +4587,15 @@ var CertificateManager = class _CertificateManager extends import_node_events.Ev
|
|
|
3913
4587
|
const chokidarOptions = {
|
|
3914
4588
|
usePolling,
|
|
3915
4589
|
...usePolling ? { interval: pollingInterval } : {},
|
|
4590
|
+
depth: 0,
|
|
3916
4591
|
persistent: false
|
|
3917
4592
|
};
|
|
3918
4593
|
const allCapturedHandles = [];
|
|
3919
|
-
const origWatch =
|
|
4594
|
+
const origWatch = import_node_fs11.default.watch;
|
|
3920
4595
|
let watcherReadyCount = 0;
|
|
3921
4596
|
const totalWatchers = 5;
|
|
3922
|
-
|
|
3923
|
-
const handle = origWatch.apply(
|
|
4597
|
+
import_node_fs11.default.watch = ((...args) => {
|
|
4598
|
+
const handle = origWatch.apply(import_node_fs11.default, args);
|
|
3924
4599
|
handle.setMaxListeners(handle.getMaxListeners() + 1);
|
|
3925
4600
|
handle.on("error", () => {
|
|
3926
4601
|
});
|
|
@@ -3936,7 +4611,7 @@ var CertificateManager = class _CertificateManager extends import_node_events.Ev
|
|
|
3936
4611
|
}
|
|
3937
4612
|
watcherReadyCount++;
|
|
3938
4613
|
if (watcherReadyCount >= totalWatchers) {
|
|
3939
|
-
|
|
4614
|
+
import_node_fs11.default.watch = origWatch;
|
|
3940
4615
|
}
|
|
3941
4616
|
};
|
|
3942
4617
|
return { w, capturedHandles: allCapturedHandles.slice(startIdx), unreffAll };
|
|
@@ -3949,7 +4624,7 @@ var CertificateManager = class _CertificateManager extends import_node_events.Ev
|
|
|
3949
4624
|
this.#scanCrlFolder(this.issuersCrlFolder, this.#thumbs.issuersCrl)
|
|
3950
4625
|
]);
|
|
3951
4626
|
if (this.#disableFileWatchers) {
|
|
3952
|
-
|
|
4627
|
+
import_node_fs11.default.watch = origWatch;
|
|
3953
4628
|
} else {
|
|
3954
4629
|
this.#startWatcher(this.trustedFolder, this.#thumbs.trusted, createUnreffedWatcher, "trusted");
|
|
3955
4630
|
this.#startWatcher(this.issuersCertFolder, this.#thumbs.issuers.certs, createUnreffedWatcher, "issuersCerts");
|
|
@@ -3964,19 +4639,19 @@ var CertificateManager = class _CertificateManager extends import_node_events.Ev
|
|
|
3964
4639
|
* file reads, preventing main-loop stalls with large folders.
|
|
3965
4640
|
*/
|
|
3966
4641
|
async #scanCertFolder(folder, index) {
|
|
3967
|
-
if (!
|
|
3968
|
-
const files = await
|
|
4642
|
+
if (!import_node_fs11.default.existsSync(folder)) return;
|
|
4643
|
+
const files = await import_node_fs11.default.promises.readdir(folder);
|
|
3969
4644
|
for (const file of files) {
|
|
3970
|
-
const filename =
|
|
4645
|
+
const filename = import_node_path8.default.join(folder, file);
|
|
3971
4646
|
try {
|
|
3972
|
-
const stat = await
|
|
4647
|
+
const stat = await import_node_fs11.default.promises.stat(filename);
|
|
3973
4648
|
if (!stat.isFile()) continue;
|
|
3974
|
-
const certs = await (0,
|
|
4649
|
+
const certs = await (0, import_node_opcua_crypto8.readCertificateChainAsync)(filename);
|
|
3975
4650
|
if (certs.length === 0) continue;
|
|
3976
4651
|
const certificate = certs[0];
|
|
3977
4652
|
if (certs.length > 1) {
|
|
3978
4653
|
try {
|
|
3979
|
-
await
|
|
4654
|
+
await import_node_fs11.default.promises.writeFile(filename, (0, import_node_opcua_crypto8.toPem)(certs, "CERTIFICATE"), "ascii");
|
|
3980
4655
|
} catch (writeErr) {
|
|
3981
4656
|
debugLog(`scanCertFolder: could not rewrite legacy PEM ${filename} (read-only fs?)`, writeErr);
|
|
3982
4657
|
}
|
|
@@ -3990,7 +4665,7 @@ var CertificateManager = class _CertificateManager extends import_node_events.Ev
|
|
|
3990
4665
|
}
|
|
3991
4666
|
}
|
|
3992
4667
|
}
|
|
3993
|
-
const info = (0,
|
|
4668
|
+
const info = (0, import_node_opcua_crypto8.exploreCertificate)(certificate);
|
|
3994
4669
|
const fingerprint = makeFingerprint(certificate);
|
|
3995
4670
|
index.set(fingerprint, { certificate, filename, info });
|
|
3996
4671
|
this.#filenameToHash.set(filename, fingerprint);
|
|
@@ -4003,12 +4678,12 @@ var CertificateManager = class _CertificateManager extends import_node_events.Ev
|
|
|
4003
4678
|
* Scan a CRL folder and populate the in-memory CRL index.
|
|
4004
4679
|
*/
|
|
4005
4680
|
async #scanCrlFolder(folder, index) {
|
|
4006
|
-
if (!
|
|
4007
|
-
const files = await
|
|
4681
|
+
if (!import_node_fs11.default.existsSync(folder)) return;
|
|
4682
|
+
const files = await import_node_fs11.default.promises.readdir(folder);
|
|
4008
4683
|
for (const file of files) {
|
|
4009
|
-
const filename =
|
|
4684
|
+
const filename = import_node_path8.default.join(folder, file);
|
|
4010
4685
|
try {
|
|
4011
|
-
const stat = await
|
|
4686
|
+
const stat = await import_node_fs11.default.promises.stat(filename);
|
|
4012
4687
|
if (!stat.isFile()) continue;
|
|
4013
4688
|
this.#onCrlFileAdded(index, filename);
|
|
4014
4689
|
} catch (err) {
|
|
@@ -4076,8 +4751,8 @@ var CertificateManager = class _CertificateManager extends import_node_events.Ev
|
|
|
4076
4751
|
w.on("add", (filename) => {
|
|
4077
4752
|
debugLog(import_chalk6.default.cyan(`add in folder ${folder}`), filename);
|
|
4078
4753
|
try {
|
|
4079
|
-
const certificate = (0,
|
|
4080
|
-
const info = (0,
|
|
4754
|
+
const certificate = (0, import_node_opcua_crypto8.readCertificateChain)(filename)[0];
|
|
4755
|
+
const info = (0, import_node_opcua_crypto8.exploreCertificate)(certificate);
|
|
4081
4756
|
const fingerprint = makeFingerprint(certificate);
|
|
4082
4757
|
const isNew = !index.has(fingerprint);
|
|
4083
4758
|
index.set(fingerprint, { certificate, filename, info });
|
|
@@ -4099,13 +4774,13 @@ var CertificateManager = class _CertificateManager extends import_node_events.Ev
|
|
|
4099
4774
|
w.on("change", (changedPath) => {
|
|
4100
4775
|
debugLog(import_chalk6.default.cyan(`change in folder ${folder}`), changedPath);
|
|
4101
4776
|
try {
|
|
4102
|
-
const certificate = (0,
|
|
4777
|
+
const certificate = (0, import_node_opcua_crypto8.readCertificateChain)(changedPath)[0];
|
|
4103
4778
|
const newFingerprint = makeFingerprint(certificate);
|
|
4104
4779
|
const oldHash = this.#filenameToHash.get(changedPath);
|
|
4105
4780
|
if (oldHash && oldHash !== newFingerprint) {
|
|
4106
4781
|
index.delete(oldHash);
|
|
4107
4782
|
}
|
|
4108
|
-
index.set(newFingerprint, { certificate, filename: changedPath, info: (0,
|
|
4783
|
+
index.set(newFingerprint, { certificate, filename: changedPath, info: (0, import_node_opcua_crypto8.exploreCertificate)(certificate) });
|
|
4109
4784
|
this.#filenameToHash.set(changedPath, newFingerprint);
|
|
4110
4785
|
this.emit("certificateChange", { store, certificate, fingerprint: newFingerprint, filename: changedPath });
|
|
4111
4786
|
} catch (err) {
|
|
@@ -4133,13 +4808,141 @@ var CertificateManager = class _CertificateManager extends import_node_events.Ev
|
|
|
4133
4808
|
});
|
|
4134
4809
|
}
|
|
4135
4810
|
};
|
|
4811
|
+
|
|
4812
|
+
// packages/node-opcua-pki/lib/pki/toolbox_pfx.ts
|
|
4813
|
+
var import_node_assert8 = __toESM(require("assert"));
|
|
4814
|
+
var import_node_fs12 = __toESM(require("fs"));
|
|
4815
|
+
var import_node_opcua_crypto9 = require("node-opcua-crypto");
|
|
4816
|
+
async function readPfxFile(options) {
|
|
4817
|
+
const { pfxFile, passphrase = "" } = options;
|
|
4818
|
+
(0, import_node_assert8.default)(import_node_fs12.default.existsSync(pfxFile), `PFX file does not exist: ${pfxFile}`);
|
|
4819
|
+
try {
|
|
4820
|
+
return await (0, import_node_opcua_crypto9.parsePfx)(await import_node_fs12.default.promises.readFile(pfxFile), passphrase);
|
|
4821
|
+
} catch (err) {
|
|
4822
|
+
if (err instanceof import_node_opcua_crypto9.Pkcs12UnsupportedAlgorithmError) {
|
|
4823
|
+
const explained = new Error(
|
|
4824
|
+
[
|
|
4825
|
+
`${pfxFile} uses an encryption algorithm that is not supported (${err.message}).`,
|
|
4826
|
+
"Bundles written by openssl 1.x default to 40-bit RC2, which openssl 3 itself",
|
|
4827
|
+
"rejects unless given -legacy. Convert the file once with:",
|
|
4828
|
+
" openssl pkcs12 -legacy -in old.pfx -nodes -out tmp.pem",
|
|
4829
|
+
" openssl pkcs12 -export -in tmp.pem -out new.pfx"
|
|
4830
|
+
].join("\n")
|
|
4831
|
+
);
|
|
4832
|
+
explained.cause = err;
|
|
4833
|
+
throw explained;
|
|
4834
|
+
}
|
|
4835
|
+
throw err;
|
|
4836
|
+
}
|
|
4837
|
+
}
|
|
4838
|
+
async function createPFX(options) {
|
|
4839
|
+
const { certificateFile, privateKeyFile, privateKeyPassphrase, outputFile, passphrase = "", caCertificateFiles } = options;
|
|
4840
|
+
(0, import_node_assert8.default)(import_node_fs12.default.existsSync(certificateFile), `Certificate file does not exist: ${certificateFile}`);
|
|
4841
|
+
(0, import_node_assert8.default)(import_node_fs12.default.existsSync(privateKeyFile), `Private key file does not exist: ${privateKeyFile}`);
|
|
4842
|
+
const [certificate, ...bundledIssuers] = (0, import_node_opcua_crypto9.readCertificateChain)(certificateFile);
|
|
4843
|
+
if (!certificate) {
|
|
4844
|
+
throw new Error(`Certificate file holds no certificate: ${certificateFile}`);
|
|
4845
|
+
}
|
|
4846
|
+
const certificateChain = [...bundledIssuers];
|
|
4847
|
+
for (const caFile of caCertificateFiles ?? []) {
|
|
4848
|
+
(0, import_node_assert8.default)(import_node_fs12.default.existsSync(caFile), `CA certificate file does not exist: ${caFile}`);
|
|
4849
|
+
certificateChain.push(...(0, import_node_opcua_crypto9.readCertificateChain)(caFile));
|
|
4850
|
+
}
|
|
4851
|
+
const privateKey = (0, import_node_opcua_crypto9.readPrivateKey)(privateKeyFile, privateKeyPassphrase);
|
|
4852
|
+
if (!(0, import_node_opcua_crypto9.certificateMatchesPrivateKey)(certificate, privateKey)) {
|
|
4853
|
+
throw new Error(`The private key ${privateKeyFile} does not match the certificate ${certificateFile}`);
|
|
4854
|
+
}
|
|
4855
|
+
const pfx = await (0, import_node_opcua_crypto9.createPfx)({
|
|
4856
|
+
certificate,
|
|
4857
|
+
certificateChain,
|
|
4858
|
+
privateKey,
|
|
4859
|
+
password: passphrase,
|
|
4860
|
+
friendlyName: options.friendlyName
|
|
4861
|
+
});
|
|
4862
|
+
await import_node_fs12.default.promises.writeFile(outputFile, pfx);
|
|
4863
|
+
}
|
|
4864
|
+
async function extractCertificateFromPFX(options) {
|
|
4865
|
+
const { certificate } = await readPfxFile(options);
|
|
4866
|
+
return (0, import_node_opcua_crypto9.toPem)(certificate, "CERTIFICATE");
|
|
4867
|
+
}
|
|
4868
|
+
async function extractPrivateKeyFromPFX(options) {
|
|
4869
|
+
const { privateKey } = await readPfxFile(options);
|
|
4870
|
+
return (0, import_node_opcua_crypto9.coercePrivateKeyPem)(privateKey);
|
|
4871
|
+
}
|
|
4872
|
+
async function extractCACertificatesFromPFX(options) {
|
|
4873
|
+
const { certificateChain } = await readPfxFile(options);
|
|
4874
|
+
return certificateChain.length === 0 ? "" : (0, import_node_opcua_crypto9.certificatesToPem)(certificateChain);
|
|
4875
|
+
}
|
|
4876
|
+
async function extractAllFromPFX(options) {
|
|
4877
|
+
const { certificate, certificateChain, privateKey, friendlyName } = await readPfxFile(options);
|
|
4878
|
+
return {
|
|
4879
|
+
certificate: (0, import_node_opcua_crypto9.toPem)(certificate, "CERTIFICATE"),
|
|
4880
|
+
privateKey: (0, import_node_opcua_crypto9.coercePrivateKeyPem)(privateKey),
|
|
4881
|
+
caCertificates: certificateChain.length === 0 ? "" : (0, import_node_opcua_crypto9.certificatesToPem)(certificateChain),
|
|
4882
|
+
friendlyName
|
|
4883
|
+
};
|
|
4884
|
+
}
|
|
4885
|
+
async function convertPFXtoPEM(pfxFile, pemFile, passphrase = "") {
|
|
4886
|
+
const { certificate, certificateChain, privateKey } = await readPfxFile({ pfxFile, passphrase });
|
|
4887
|
+
const parts = [(0, import_node_opcua_crypto9.coercePrivateKeyPem)(privateKey), (0, import_node_opcua_crypto9.toPem)(certificate, "CERTIFICATE")];
|
|
4888
|
+
if (certificateChain.length > 0) {
|
|
4889
|
+
parts.push((0, import_node_opcua_crypto9.certificatesToPem)(certificateChain));
|
|
4890
|
+
}
|
|
4891
|
+
await import_node_fs12.default.promises.writeFile(pemFile, `${parts.map((part) => part.trimEnd()).join("\n")}
|
|
4892
|
+
`, "utf-8");
|
|
4893
|
+
}
|
|
4894
|
+
function formatThumbprint(certificate) {
|
|
4895
|
+
return ((0, import_node_opcua_crypto9.makeSHA1Thumbprint)(certificate).toString("hex").toUpperCase().match(/../g) ?? []).join(":");
|
|
4896
|
+
}
|
|
4897
|
+
function describePublicKey(certificate) {
|
|
4898
|
+
const algorithm = certificate.publicKey.algorithm;
|
|
4899
|
+
if (algorithm.modulusLength) {
|
|
4900
|
+
return `${algorithm.name} ${algorithm.modulusLength} bits`;
|
|
4901
|
+
}
|
|
4902
|
+
if (algorithm.namedCurve) {
|
|
4903
|
+
return `${algorithm.name} ${algorithm.namedCurve}`;
|
|
4904
|
+
}
|
|
4905
|
+
return algorithm.name;
|
|
4906
|
+
}
|
|
4907
|
+
function describeCertificate(der, indent) {
|
|
4908
|
+
const certificate = new import_node_opcua_crypto9.x509.X509Certificate(der);
|
|
4909
|
+
return [
|
|
4910
|
+
`${indent}subject : ${certificate.subject}`,
|
|
4911
|
+
`${indent}issuer : ${certificate.issuer}`,
|
|
4912
|
+
`${indent}serial number : ${certificate.serialNumber}`,
|
|
4913
|
+
`${indent}not before : ${certificate.notBefore.toISOString()}`,
|
|
4914
|
+
`${indent}not after : ${certificate.notAfter.toISOString()}`,
|
|
4915
|
+
`${indent}public key : ${describePublicKey(certificate)}`,
|
|
4916
|
+
`${indent}signature : ${certificate.signatureAlgorithm.name}`,
|
|
4917
|
+
`${indent}SHA-1 thumbprint : ${formatThumbprint(der)}`
|
|
4918
|
+
];
|
|
4919
|
+
}
|
|
4920
|
+
async function dumpPFX(pfxFile, passphrase = "") {
|
|
4921
|
+
const { certificate, certificateChain, privateKey, friendlyName } = await readPfxFile({ pfxFile, passphrase });
|
|
4922
|
+
const lines = [`PFX bundle : ${pfxFile}`];
|
|
4923
|
+
if (friendlyName) {
|
|
4924
|
+
lines.push(`friendly name : ${friendlyName}`);
|
|
4925
|
+
}
|
|
4926
|
+
const keyVerdict = (0, import_node_opcua_crypto9.certificateMatchesPrivateKey)(certificate, privateKey) ? "matches this certificate" : "DOES NOT match this certificate";
|
|
4927
|
+
lines.push("", "certificate", ...describeCertificate(certificate, " "), ` private key : ${keyVerdict}`);
|
|
4928
|
+
lines.push("", `issuer chain : ${certificateChain.length} certificate(s)`);
|
|
4929
|
+
for (const [index, issuer] of certificateChain.entries()) {
|
|
4930
|
+
lines.push(` [${index}]`, ...describeCertificate(issuer, " "));
|
|
4931
|
+
}
|
|
4932
|
+
return `${lines.join("\n")}
|
|
4933
|
+
`;
|
|
4934
|
+
}
|
|
4136
4935
|
// Annotate the CommonJS export names for ESM import in node:
|
|
4137
4936
|
0 && (module.exports = {
|
|
4138
4937
|
CertificateAuthority,
|
|
4938
|
+
CertificateAuthorityCore,
|
|
4139
4939
|
CertificateManager,
|
|
4140
4940
|
CertificateManagerState,
|
|
4141
4941
|
ChainCompletionStatus,
|
|
4942
|
+
NativeCaBackend,
|
|
4943
|
+
OpenSslCaBackend,
|
|
4142
4944
|
PrivateKeyPassphraseRequiredError,
|
|
4945
|
+
PrivateKeyUnavailableError,
|
|
4143
4946
|
Subject,
|
|
4144
4947
|
VerificationStatus,
|
|
4145
4948
|
adjustApplicationUri,
|
|
@@ -4156,6 +4959,7 @@ var CertificateManager = class _CertificateManager extends import_node_events.Ev
|
|
|
4156
4959
|
install_prerequisite,
|
|
4157
4960
|
isIntermediateIssuer,
|
|
4158
4961
|
isIssuer,
|
|
4962
|
+
isOpaqueSigner,
|
|
4159
4963
|
isRootIssuer,
|
|
4160
4964
|
makeFingerprint,
|
|
4161
4965
|
quote,
|