node-opcua-pki 6.19.0 → 6.20.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 +668 -227
- package/dist/bin/pki.mjs.map +1 -1
- package/dist/index.d.mts +201 -21
- package/dist/index.d.ts +201 -21
- package/dist/index.js +663 -214
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +669 -216
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -3
- package/readme.md +4 -0
package/dist/index.js
CHANGED
|
@@ -34,6 +34,7 @@ __export(lib_exports, {
|
|
|
34
34
|
CertificateManager: () => CertificateManager,
|
|
35
35
|
CertificateManagerState: () => CertificateManagerState,
|
|
36
36
|
ChainCompletionStatus: () => ChainCompletionStatus,
|
|
37
|
+
PrivateKeyPassphraseRequiredError: () => import_node_opcua_crypto6.PrivateKeyPassphraseRequiredError,
|
|
37
38
|
Subject: () => import_node_opcua_crypto.Subject,
|
|
38
39
|
VerificationStatus: () => VerificationStatus,
|
|
39
40
|
adjustApplicationUri: () => adjustApplicationUri,
|
|
@@ -52,9 +53,11 @@ __export(lib_exports, {
|
|
|
52
53
|
isIssuer: () => isIssuer,
|
|
53
54
|
isRootIssuer: () => isRootIssuer,
|
|
54
55
|
makeFingerprint: () => makeFingerprint,
|
|
55
|
-
quote: () => quote
|
|
56
|
+
quote: () => quote,
|
|
57
|
+
resolvePrivateKeyPassphrase: () => resolvePrivateKeyPassphrase
|
|
56
58
|
});
|
|
57
59
|
module.exports = __toCommonJS(lib_exports);
|
|
60
|
+
var import_node_opcua_crypto6 = require("node-opcua-crypto");
|
|
58
61
|
|
|
59
62
|
// packages/node-opcua-pki/lib/ca/certificate_authority.ts
|
|
60
63
|
var import_node_assert7 = __toESM(require("assert"));
|
|
@@ -65,41 +68,11 @@ var import_chalk5 = __toESM(require("chalk"));
|
|
|
65
68
|
var import_node_opcua_crypto2 = require("node-opcua-crypto");
|
|
66
69
|
|
|
67
70
|
// packages/node-opcua-pki/lib/pki/toolbox_pfx.ts
|
|
68
|
-
var
|
|
71
|
+
var import_node_assert3 = __toESM(require("assert"));
|
|
69
72
|
var import_node_fs4 = __toESM(require("fs"));
|
|
70
73
|
|
|
71
|
-
// packages/node-opcua-pki/lib/toolbox/common.ts
|
|
72
|
-
var import_node_assert = __toESM(require("assert"));
|
|
73
|
-
function quote(str) {
|
|
74
|
-
return `"${str || ""}"`;
|
|
75
|
-
}
|
|
76
|
-
function adjustDate(params) {
|
|
77
|
-
(0, import_node_assert.default)(params instanceof Object);
|
|
78
|
-
params.startDate = params.startDate || /* @__PURE__ */ new Date();
|
|
79
|
-
(0, import_node_assert.default)(params.startDate instanceof Date);
|
|
80
|
-
if (params.validityMs !== void 0) {
|
|
81
|
-
if (params.validityMs <= 0) {
|
|
82
|
-
throw new RangeError(`validityMs must be > 0 (got ${params.validityMs})`);
|
|
83
|
-
}
|
|
84
|
-
params.endDate = new Date(params.startDate.getTime() + params.validityMs);
|
|
85
|
-
params.validity = Math.ceil(params.validityMs / 864e5);
|
|
86
|
-
} else {
|
|
87
|
-
params.validity = params.validity || 365;
|
|
88
|
-
params.endDate = new Date(params.startDate.getTime());
|
|
89
|
-
params.endDate.setDate(params.startDate.getDate() + params.validity);
|
|
90
|
-
}
|
|
91
|
-
(0, import_node_assert.default)(params.endDate instanceof Date);
|
|
92
|
-
(0, import_node_assert.default)(params.startDate instanceof Date);
|
|
93
|
-
}
|
|
94
|
-
function adjustApplicationUri(params) {
|
|
95
|
-
const applicationUri = params.applicationUri || "";
|
|
96
|
-
if (applicationUri.length > 200) {
|
|
97
|
-
throw new Error(`Openssl doesn't support urn with length greater than 200${applicationUri}`);
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
|
|
101
74
|
// packages/node-opcua-pki/lib/toolbox/common2.ts
|
|
102
|
-
var
|
|
75
|
+
var import_node_assert = __toESM(require("assert"));
|
|
103
76
|
var import_node_fs = __toESM(require("fs"));
|
|
104
77
|
var import_node_path = __toESM(require("path"));
|
|
105
78
|
var import_chalk = __toESM(require("chalk"));
|
|
@@ -140,12 +113,31 @@ function mkdirRecursiveSync(folder) {
|
|
|
140
113
|
import_node_fs.default.mkdirSync(folder, { recursive: true });
|
|
141
114
|
}
|
|
142
115
|
}
|
|
116
|
+
function restrictPrivateFilePermissions(target, mode) {
|
|
117
|
+
if (process.platform === "win32") {
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
try {
|
|
121
|
+
import_node_fs.default.chmodSync(target, mode);
|
|
122
|
+
} catch (err) {
|
|
123
|
+
warningLog(import_chalk.default.yellow(" could not restrict permissions on "), target, err.message);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
function ensurePrivateDirectory(dir) {
|
|
127
|
+
if (!import_node_fs.default.existsSync(dir)) {
|
|
128
|
+
import_node_fs.default.mkdirSync(dir, { recursive: true, mode: 448 });
|
|
129
|
+
}
|
|
130
|
+
restrictPrivateFilePermissions(dir, 448);
|
|
131
|
+
}
|
|
132
|
+
function isEncryptedPrivateKeyFile(filename) {
|
|
133
|
+
return import_node_fs.default.readFileSync(filename, "utf-8").includes("-----BEGIN ENCRYPTED PRIVATE KEY-----");
|
|
134
|
+
}
|
|
143
135
|
function makePath(folderName, filename) {
|
|
144
136
|
let s;
|
|
145
137
|
if (filename) {
|
|
146
138
|
s = import_node_path.default.join(import_node_path.default.normalize(folderName), filename);
|
|
147
139
|
} else {
|
|
148
|
-
(0,
|
|
140
|
+
(0, import_node_assert.default)(folderName);
|
|
149
141
|
s = folderName;
|
|
150
142
|
}
|
|
151
143
|
s = s.replace(/\\/g, "/");
|
|
@@ -153,7 +145,7 @@ function makePath(folderName, filename) {
|
|
|
153
145
|
}
|
|
154
146
|
|
|
155
147
|
// packages/node-opcua-pki/lib/toolbox/with_openssl/execute_openssl.ts
|
|
156
|
-
var
|
|
148
|
+
var import_node_assert2 = __toESM(require("assert"));
|
|
157
149
|
var import_node_child_process2 = __toESM(require("child_process"));
|
|
158
150
|
var import_node_fs3 = __toESM(require("fs"));
|
|
159
151
|
var import_node_os2 = __toESM(require("os"));
|
|
@@ -161,6 +153,50 @@ var import_byline2 = __toESM(require("byline"));
|
|
|
161
153
|
var import_chalk3 = __toESM(require("chalk"));
|
|
162
154
|
|
|
163
155
|
// packages/node-opcua-pki/lib/toolbox/with_openssl/_env.ts
|
|
156
|
+
var SAFE_ENV_PASSTHROUGH = /* @__PURE__ */ new Set([
|
|
157
|
+
// POSIX/Windows shell and process essentials
|
|
158
|
+
"path",
|
|
159
|
+
"home",
|
|
160
|
+
"userprofile",
|
|
161
|
+
"temp",
|
|
162
|
+
"tmp",
|
|
163
|
+
"tmpdir",
|
|
164
|
+
"systemroot",
|
|
165
|
+
"windir",
|
|
166
|
+
"comspec",
|
|
167
|
+
"pathext",
|
|
168
|
+
"appdata",
|
|
169
|
+
"localappdata",
|
|
170
|
+
// dynamic loader: a custom-built or relocated openssl (e.g. under /opt,
|
|
171
|
+
// or Homebrew on macOS) may need these to find its own libcrypto/libssl
|
|
172
|
+
"ld_library_path",
|
|
173
|
+
"dyld_library_path",
|
|
174
|
+
"dyld_fallback_library_path",
|
|
175
|
+
// locale, so openssl's textual output stays parseable
|
|
176
|
+
"lang",
|
|
177
|
+
"lc_all",
|
|
178
|
+
"lc_ctype",
|
|
179
|
+
// openssl configuration this app, or the host OS/user, may rely on
|
|
180
|
+
"openssl_conf",
|
|
181
|
+
"randfile",
|
|
182
|
+
"openssl_modules",
|
|
183
|
+
"openssl_engines",
|
|
184
|
+
"ssl_cert_file",
|
|
185
|
+
"ssl_cert_dir"
|
|
186
|
+
]);
|
|
187
|
+
function buildChildEnv(extra) {
|
|
188
|
+
const env = {};
|
|
189
|
+
for (const key of Object.keys(process.env)) {
|
|
190
|
+
if (SAFE_ENV_PASSTHROUGH.has(key.toLowerCase())) {
|
|
191
|
+
env[key] = process.env[key];
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
return { ...env, ...extra };
|
|
195
|
+
}
|
|
196
|
+
function redactEnvForLog(options) {
|
|
197
|
+
const { env, ...rest } = options;
|
|
198
|
+
return env ? { ...rest, env: Object.keys(env) } : rest;
|
|
199
|
+
}
|
|
164
200
|
var exportedEnvVars = {};
|
|
165
201
|
function setEnv(varName, value) {
|
|
166
202
|
if (!g_config.silent) {
|
|
@@ -215,24 +251,17 @@ var import_byline = __toESM(require("byline"));
|
|
|
215
251
|
var import_chalk2 = __toESM(require("chalk"));
|
|
216
252
|
var import_yauzl = __toESM(require("yauzl"));
|
|
217
253
|
var doDebug2 = process.env.NODEOPCUAPKIDEBUG || false;
|
|
218
|
-
async function execute(
|
|
254
|
+
async function execute(file, args, cwd) {
|
|
219
255
|
let output = "";
|
|
220
|
-
const options = {
|
|
221
|
-
cwd,
|
|
222
|
-
windowsHide: true
|
|
223
|
-
};
|
|
224
256
|
return await new Promise((resolve, reject) => {
|
|
225
|
-
const child = import_node_child_process.default.
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
(
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
);
|
|
257
|
+
const child = import_node_child_process.default.spawn(file, args, {
|
|
258
|
+
cwd,
|
|
259
|
+
windowsHide: true,
|
|
260
|
+
env: buildChildEnv(),
|
|
261
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
262
|
+
});
|
|
263
|
+
child.on("error", (err) => reject(err));
|
|
264
|
+
child.on("close", (code) => resolve({ exitCode: code ?? 1, output }));
|
|
236
265
|
const stream1 = (0, import_byline.default)(child.stdout);
|
|
237
266
|
stream1.on("data", (line) => {
|
|
238
267
|
output += `${line}
|
|
@@ -244,16 +273,13 @@ async function execute(cmd, cwd) {
|
|
|
244
273
|
});
|
|
245
274
|
});
|
|
246
275
|
}
|
|
247
|
-
function quote2(str) {
|
|
248
|
-
return `"${str.replace(/\\/g, "/")}"`;
|
|
249
|
-
}
|
|
250
276
|
function is_expected_openssl_version(strVersion) {
|
|
251
277
|
return !!strVersion.match(/OpenSSL \d/);
|
|
252
278
|
}
|
|
253
279
|
async function getopensslExecPath() {
|
|
254
280
|
let result1;
|
|
255
281
|
try {
|
|
256
|
-
result1 = await execute("which openssl");
|
|
282
|
+
result1 = await execute("which", ["openssl"]);
|
|
257
283
|
} catch (err) {
|
|
258
284
|
warningLog("warning: ", err.message);
|
|
259
285
|
throw new Error("Cannot find openssl");
|
|
@@ -270,11 +296,10 @@ async function getopensslExecPath() {
|
|
|
270
296
|
}
|
|
271
297
|
async function check_system_openssl_version() {
|
|
272
298
|
const opensslExecPath = await getopensslExecPath();
|
|
273
|
-
const q_opensslExecPath = quote2(opensslExecPath);
|
|
274
299
|
if (doDebug2) {
|
|
275
300
|
warningLog(` OpenSSL found in : ${import_chalk2.default.yellow(opensslExecPath)}`);
|
|
276
301
|
}
|
|
277
|
-
const result = await execute(
|
|
302
|
+
const result = await execute(opensslExecPath, ["version"]);
|
|
278
303
|
const exitCode = result?.exitCode;
|
|
279
304
|
const output = result?.output;
|
|
280
305
|
const version = output.trim();
|
|
@@ -314,9 +339,8 @@ async function install_and_check_win32_openssl_version() {
|
|
|
314
339
|
version: `cannot find file ${opensslExecPath2}`
|
|
315
340
|
};
|
|
316
341
|
} else {
|
|
317
|
-
const q_openssl_exe_path = quote2(opensslExecPath2);
|
|
318
342
|
const cwd = ".";
|
|
319
|
-
const { exitCode, output } = await execute(
|
|
343
|
+
const { exitCode, output } = await execute(opensslExecPath2, ["version"], cwd);
|
|
320
344
|
const version = output.trim();
|
|
321
345
|
if (doDebug2) {
|
|
322
346
|
warningLog(" Version = ", version);
|
|
@@ -329,7 +353,7 @@ async function install_and_check_win32_openssl_version() {
|
|
|
329
353
|
}
|
|
330
354
|
async function find_system_openssl_win32() {
|
|
331
355
|
try {
|
|
332
|
-
const result = await execute("where openssl");
|
|
356
|
+
const result = await execute("where", ["openssl"]);
|
|
333
357
|
if (result.exitCode !== 0) {
|
|
334
358
|
return void 0;
|
|
335
359
|
}
|
|
@@ -337,8 +361,7 @@ async function install_and_check_win32_openssl_version() {
|
|
|
337
361
|
if (!opensslPath2 || !import_node_fs2.default.existsSync(opensslPath2)) {
|
|
338
362
|
return void 0;
|
|
339
363
|
}
|
|
340
|
-
const
|
|
341
|
-
const versionResult = await execute(`${q4} version`);
|
|
364
|
+
const versionResult = await execute(opensslPath2, ["version"]);
|
|
342
365
|
const version = versionResult.output.trim();
|
|
343
366
|
if (versionResult.exitCode === 0 && is_expected_openssl_version(version)) {
|
|
344
367
|
warningLog(
|
|
@@ -501,36 +524,55 @@ async function get_openssl_exec_path() {
|
|
|
501
524
|
// packages/node-opcua-pki/lib/toolbox/with_openssl/execute_openssl.ts
|
|
502
525
|
var opensslPath;
|
|
503
526
|
var n = makePath;
|
|
504
|
-
|
|
527
|
+
var PASSIN_ENV_VAR = "NODE_OPCUA_PKI_OPENSSL_PASSIN";
|
|
528
|
+
var PASSOUT_ENV_VAR = "NODE_OPCUA_PKI_OPENSSL_PASSOUT";
|
|
529
|
+
function passinArg(passphrase = "") {
|
|
530
|
+
return { args: ["-passin", `env:${PASSIN_ENV_VAR}`], env: { [PASSIN_ENV_VAR]: passphrase } };
|
|
531
|
+
}
|
|
532
|
+
function passoutArg(passphrase = "") {
|
|
533
|
+
return { args: ["-passout", `env:${PASSOUT_ENV_VAR}`], env: { [PASSOUT_ENV_VAR]: passphrase } };
|
|
534
|
+
}
|
|
535
|
+
function renderForDisplay(file, args) {
|
|
536
|
+
return [file, ...args].map((a) => a === "" || /[\s"'`$\\]/.test(a) ? JSON.stringify(a) : a).join(" ");
|
|
537
|
+
}
|
|
538
|
+
async function execute2(file, args, options) {
|
|
505
539
|
const from = new Error();
|
|
506
540
|
options.cwd = options.cwd || process.cwd();
|
|
507
541
|
if (!g_config.silent) {
|
|
508
542
|
warningLog(import_chalk3.default.cyan(" CWD "), options.cwd);
|
|
509
543
|
}
|
|
510
544
|
const outputs = [];
|
|
545
|
+
const errorOutputs = [];
|
|
546
|
+
const display2 = renderForDisplay(file, args);
|
|
511
547
|
return await new Promise((resolve, reject) => {
|
|
512
|
-
const child = import_node_child_process2.default.
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
548
|
+
const child = import_node_child_process2.default.spawn(file, [...args], {
|
|
549
|
+
cwd: options.cwd,
|
|
550
|
+
windowsHide: true,
|
|
551
|
+
env: buildChildEnv(options.env),
|
|
552
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
553
|
+
});
|
|
554
|
+
const fail = (message) => {
|
|
555
|
+
if (!options.hideErrorMessage) {
|
|
556
|
+
const fence = "###########################################";
|
|
557
|
+
console.error(import_chalk3.default.bgWhiteBright.redBright(`${fence} OPENSSL ERROR ${fence}`));
|
|
558
|
+
console.error(import_chalk3.default.bgWhiteBright.redBright(`CWD = ${options.cwd}`));
|
|
559
|
+
console.error(import_chalk3.default.bgWhiteBright.redBright(message));
|
|
560
|
+
console.error(import_chalk3.default.bgWhiteBright.redBright(`${fence} OPENSSL ERROR ${fence}`));
|
|
561
|
+
console.error(from.stack);
|
|
562
|
+
}
|
|
563
|
+
reject(new Error(message));
|
|
564
|
+
};
|
|
565
|
+
child.on("error", (err) => fail(`Command failed: ${display2}
|
|
566
|
+
${err.message}`));
|
|
567
|
+
child.on("close", (code, signal) => {
|
|
568
|
+
if (code === 0) {
|
|
531
569
|
resolve(outputs.join(""));
|
|
570
|
+
return;
|
|
532
571
|
}
|
|
533
|
-
|
|
572
|
+
const why = signal ? `signal ${signal}` : `exit code ${code}`;
|
|
573
|
+
fail(`Command failed: ${display2}
|
|
574
|
+
${errorOutputs.join("")}(${why})`);
|
|
575
|
+
});
|
|
534
576
|
if (child.stdout) {
|
|
535
577
|
const stream2 = (0, import_byline2.default)(child.stdout);
|
|
536
578
|
stream2.on("data", (line) => {
|
|
@@ -547,17 +589,17 @@ async function execute2(cmd, options) {
|
|
|
547
589
|
});
|
|
548
590
|
}
|
|
549
591
|
}
|
|
550
|
-
if (
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
if (displayError) {
|
|
556
|
-
process.stdout.write(`${import_chalk3.default.white(" stderr ") + import_chalk3.default.red(line)}
|
|
592
|
+
if (child.stderr) {
|
|
593
|
+
const stream1 = (0, import_byline2.default)(child.stderr);
|
|
594
|
+
stream1.on("data", (line) => {
|
|
595
|
+
line = line.toString();
|
|
596
|
+
errorOutputs.push(`${line}
|
|
557
597
|
`);
|
|
558
|
-
|
|
559
|
-
}
|
|
560
|
-
|
|
598
|
+
if (!g_config.silent && displayError) {
|
|
599
|
+
process.stdout.write(`${import_chalk3.default.white(" stderr ") + import_chalk3.default.red(line)}
|
|
600
|
+
`);
|
|
601
|
+
}
|
|
602
|
+
});
|
|
561
603
|
}
|
|
562
604
|
});
|
|
563
605
|
}
|
|
@@ -567,18 +609,18 @@ async function find_openssl() {
|
|
|
567
609
|
async function ensure_openssl_installed() {
|
|
568
610
|
if (!opensslPath) {
|
|
569
611
|
opensslPath = await find_openssl();
|
|
570
|
-
const outputs = await execute_openssl("version", { cwd: "." });
|
|
612
|
+
const outputs = await execute_openssl(["version"], { cwd: "." });
|
|
571
613
|
g_config.opensslVersion = outputs.trim();
|
|
572
614
|
if (doDebug) {
|
|
573
615
|
warningLog("OpenSSL version : ", g_config.opensslVersion);
|
|
574
616
|
}
|
|
575
617
|
}
|
|
576
618
|
}
|
|
577
|
-
async function execute_openssl_no_failure(
|
|
619
|
+
async function execute_openssl_no_failure(args, options) {
|
|
578
620
|
options = options || {};
|
|
579
621
|
options.hideErrorMessage = true;
|
|
580
622
|
try {
|
|
581
|
-
return await execute_openssl(
|
|
623
|
+
return await execute_openssl(args, options);
|
|
582
624
|
} catch (err) {
|
|
583
625
|
debugLog(" (ignored error = ERROR : )", err.message);
|
|
584
626
|
}
|
|
@@ -586,62 +628,64 @@ async function execute_openssl_no_failure(cmd, options) {
|
|
|
586
628
|
function getTempFolder() {
|
|
587
629
|
return import_node_os2.default.tmpdir();
|
|
588
630
|
}
|
|
589
|
-
async function execute_openssl(
|
|
590
|
-
debugLog("execute_openssl",
|
|
631
|
+
async function execute_openssl(args, options) {
|
|
632
|
+
debugLog("execute_openssl", args, redactEnvForLog(options));
|
|
591
633
|
const empty_config_file = n(getTempFolder(), "empty_config.cnf");
|
|
592
634
|
if (!import_node_fs3.default.existsSync(empty_config_file)) {
|
|
593
635
|
await import_node_fs3.default.promises.writeFile(empty_config_file, "# empty config file");
|
|
594
636
|
}
|
|
595
637
|
options = options || {};
|
|
596
638
|
options.openssl_conf = options.openssl_conf || empty_config_file;
|
|
597
|
-
(0,
|
|
639
|
+
(0, import_node_assert2.default)(options.openssl_conf);
|
|
598
640
|
setEnv("OPENSSL_CONF", options.openssl_conf);
|
|
599
641
|
if (!g_config.silent) {
|
|
600
642
|
warningLog(import_chalk3.default.cyan(" OPENSSL_CONF"), process.env.OPENSSL_CONF);
|
|
601
643
|
warningLog(import_chalk3.default.cyan(" RANDFILE "), process.env.RANDFILE);
|
|
602
|
-
warningLog(import_chalk3.default.cyan(" CMD
|
|
644
|
+
warningLog(import_chalk3.default.cyan(" CMD "), import_chalk3.default.cyanBright(renderForDisplay("openssl", args)));
|
|
603
645
|
}
|
|
604
646
|
await ensure_openssl_installed();
|
|
605
|
-
return await execute2(
|
|
647
|
+
return await execute2(opensslPath, args, options);
|
|
606
648
|
}
|
|
607
649
|
|
|
608
650
|
// packages/node-opcua-pki/lib/pki/toolbox_pfx.ts
|
|
609
|
-
var q = quote;
|
|
610
651
|
var n2 = makePath;
|
|
611
652
|
async function createPFX(options) {
|
|
612
|
-
const { certificateFile, privateKeyFile, outputFile, passphrase = "", caCertificateFiles } = options;
|
|
613
|
-
(0,
|
|
614
|
-
(0,
|
|
615
|
-
|
|
616
|
-
cmd += ` -in ${q(n2(certificateFile))}`;
|
|
617
|
-
cmd += ` -inkey ${q(n2(privateKeyFile))}`;
|
|
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)];
|
|
618
657
|
if (caCertificateFiles) {
|
|
619
658
|
for (const caFile of caCertificateFiles) {
|
|
620
|
-
(0,
|
|
621
|
-
|
|
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));
|
|
622
661
|
}
|
|
623
662
|
}
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
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 } });
|
|
627
667
|
}
|
|
628
668
|
async function extractCertificateFromPFX(options) {
|
|
629
669
|
const { pfxFile, passphrase = "" } = options;
|
|
630
|
-
(0,
|
|
631
|
-
const
|
|
632
|
-
return await execute_openssl(
|
|
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
|
+
});
|
|
633
675
|
}
|
|
634
676
|
async function extractPrivateKeyFromPFX(options) {
|
|
635
677
|
const { pfxFile, passphrase = "" } = options;
|
|
636
|
-
(0,
|
|
637
|
-
const
|
|
638
|
-
return await execute_openssl(
|
|
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 });
|
|
639
681
|
}
|
|
640
682
|
async function extractCACertificatesFromPFX(options) {
|
|
641
683
|
const { pfxFile, passphrase = "" } = options;
|
|
642
|
-
(0,
|
|
643
|
-
const
|
|
644
|
-
return await execute_openssl(
|
|
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
|
+
});
|
|
645
689
|
}
|
|
646
690
|
async function extractAllFromPFX(options) {
|
|
647
691
|
const [certificate, privateKey, caCertificates] = await Promise.all([
|
|
@@ -652,14 +696,50 @@ async function extractAllFromPFX(options) {
|
|
|
652
696
|
return { certificate, privateKey, caCertificates };
|
|
653
697
|
}
|
|
654
698
|
async function convertPFXtoPEM(pfxFile, pemFile, passphrase = "") {
|
|
655
|
-
(0,
|
|
656
|
-
const
|
|
657
|
-
await execute_openssl(
|
|
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 });
|
|
658
702
|
}
|
|
659
703
|
async function dumpPFX(pfxFile, passphrase = "") {
|
|
660
|
-
(0,
|
|
661
|
-
const
|
|
662
|
-
return await execute_openssl(
|
|
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
|
|
710
|
+
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
|
+
}
|
|
663
743
|
}
|
|
664
744
|
|
|
665
745
|
// packages/node-opcua-pki/lib/toolbox/display.ts
|
|
@@ -751,7 +831,6 @@ function x509Date(date) {
|
|
|
751
831
|
}
|
|
752
832
|
|
|
753
833
|
// packages/node-opcua-pki/lib/toolbox/with_openssl/create_certificate_signing_request.ts
|
|
754
|
-
var q2 = quote;
|
|
755
834
|
var n3 = makePath;
|
|
756
835
|
async function createCertificateSigningRequestWithOpenSSL(certificateSigningRequestFilename, params) {
|
|
757
836
|
(0, import_node_assert6.default)(params);
|
|
@@ -766,12 +845,23 @@ async function createCertificateSigningRequestWithOpenSSL(certificateSigningRequ
|
|
|
766
845
|
processAltNames(params);
|
|
767
846
|
const configFile = generateStaticConfig(params.configFile, { cwd: params.rootDir });
|
|
768
847
|
const options = { cwd: params.rootDir, openssl_conf: import_node_path4.default.relative(params.rootDir, configFile) };
|
|
769
|
-
const configOption = ` -config ${q2(n3(configFile))}`;
|
|
770
848
|
const subject = params.subject ? new import_node_opcua_crypto.Subject(params.subject).toString() : void 0;
|
|
771
|
-
const subjectOptions = subject ? ` -subj "${subject}"` : "";
|
|
772
849
|
displaySubtitle("- Creating a Certificate Signing Request with openssl");
|
|
773
850
|
await execute_openssl(
|
|
774
|
-
|
|
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
|
+
],
|
|
775
865
|
options
|
|
776
866
|
);
|
|
777
867
|
}
|
|
@@ -785,7 +875,7 @@ var config2 = `#.........DO NOT MODIFY BY HAND .........................
|
|
|
785
875
|
[ ca ]
|
|
786
876
|
default_ca = CA_default
|
|
787
877
|
[ CA_default ]
|
|
788
|
-
dir = %%ROOT_FOLDER%%
|
|
878
|
+
dir = "%%ROOT_FOLDER%%" # the main CA folder (quoted: see renderCaConfig)
|
|
789
879
|
certs = $dir/certs # where to store certificates
|
|
790
880
|
new_certs_dir = $dir/certs #
|
|
791
881
|
database = $dir/index.txt # the certificate database
|
|
@@ -918,13 +1008,18 @@ var ca_config_template_cnf_default = config2;
|
|
|
918
1008
|
var defaultSubject = "/C=FR/ST=IDF/L=Paris/O=Local NODE-OPCUA Certificate Authority/CN=NodeOPCUA-CA";
|
|
919
1009
|
var configurationFileTemplate = ca_config_template_cnf_default;
|
|
920
1010
|
var configurationFileSimpleTemplate = simple_config_template_cnf_default;
|
|
1011
|
+
function escapeOpensslConfDoubleQuoted(value) {
|
|
1012
|
+
return value.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
|
|
1013
|
+
}
|
|
1014
|
+
function renderCaConfig(caRootDir) {
|
|
1015
|
+
return configurationFileTemplate.replace(/%%ROOT_FOLDER%%/, escapeOpensslConfDoubleQuoted(makePath(caRootDir)));
|
|
1016
|
+
}
|
|
921
1017
|
var config3 = {
|
|
922
1018
|
certificateDir: "INVALID",
|
|
923
1019
|
forceCA: false,
|
|
924
1020
|
pkiDir: "INVALID"
|
|
925
1021
|
};
|
|
926
1022
|
var n4 = makePath;
|
|
927
|
-
var q3 = quote;
|
|
928
1023
|
function octetStringToIpAddress(a) {
|
|
929
1024
|
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();
|
|
930
1025
|
}
|
|
@@ -934,7 +1029,7 @@ async function construct_CertificateAuthority(certificateAuthority) {
|
|
|
934
1029
|
const caRootDir = import_node_path5.default.resolve(certificateAuthority.rootDir);
|
|
935
1030
|
async function make_folders() {
|
|
936
1031
|
mkdirRecursiveSync(caRootDir);
|
|
937
|
-
|
|
1032
|
+
ensurePrivateDirectory(import_node_path5.default.join(caRootDir, "private"));
|
|
938
1033
|
mkdirRecursiveSync(import_node_path5.default.join(caRootDir, "public"));
|
|
939
1034
|
mkdirRecursiveSync(import_node_path5.default.join(caRootDir, "certs"));
|
|
940
1035
|
mkdirRecursiveSync(import_node_path5.default.join(caRootDir, "crl"));
|
|
@@ -959,6 +1054,8 @@ async function construct_CertificateAuthority(certificateAuthority) {
|
|
|
959
1054
|
const caKeyExists = import_node_fs7.default.existsSync(import_node_path5.default.join(caRootDir, "private/cakey.pem"));
|
|
960
1055
|
const caCertExists = import_node_fs7.default.existsSync(import_node_path5.default.join(caRootDir, "public/cacert.pem"));
|
|
961
1056
|
if (caKeyExists && caCertExists && !config3.forceCA) {
|
|
1057
|
+
restrictPrivateFilePermissions(import_node_path5.default.join(caRootDir, "private/cakey.pem"), 384);
|
|
1058
|
+
await certificateAuthority._ensurePrivateKeyProtection();
|
|
962
1059
|
debugLog("CA private key and certificate already exist ... skipping");
|
|
963
1060
|
return;
|
|
964
1061
|
}
|
|
@@ -977,26 +1074,40 @@ async function construct_CertificateAuthority(certificateAuthority) {
|
|
|
977
1074
|
}
|
|
978
1075
|
const caConfigFile = certificateAuthority.configFile;
|
|
979
1076
|
if (1) {
|
|
980
|
-
|
|
981
|
-
data = makePath(data.replace(/%%ROOT_FOLDER%%/, caRootDir));
|
|
982
|
-
await import_node_fs7.default.promises.writeFile(caConfigFile, data);
|
|
1077
|
+
await import_node_fs7.default.promises.writeFile(caConfigFile, renderCaConfig(caRootDir));
|
|
983
1078
|
}
|
|
984
|
-
const subjectOpt =
|
|
1079
|
+
const subjectOpt = ["-subj", subject.toString()];
|
|
985
1080
|
const caCommonName = subject.commonName || "NodeOPCUA-CA";
|
|
986
1081
|
setEnv("ALTNAME", `URI:urn:${caCommonName}`);
|
|
987
1082
|
certificateAuthority._wireRevocationEnvVars();
|
|
988
1083
|
const options = { cwd: caRootDir };
|
|
989
1084
|
const configFile = generateStaticConfig("conf/caconfig.cnf", options);
|
|
990
|
-
const configOption =
|
|
1085
|
+
const configOption = ["-config", n4(configFile)];
|
|
991
1086
|
const keySize = certificateAuthority.keySize;
|
|
992
1087
|
const privateKeyFilename = import_node_path5.default.join(caRootDir, "private/cakey.pem");
|
|
993
1088
|
const csrFilename = import_node_path5.default.join(caRootDir, "private/cakey.csr");
|
|
994
1089
|
displayTitle(`Generate the CA private Key - ${keySize}`);
|
|
995
|
-
await
|
|
1090
|
+
const passin = await certificateAuthority._opensslPassin();
|
|
1091
|
+
await (0, import_node_opcua_crypto2.generatePrivateKeyFile)(privateKeyFilename, keySize, { passphrase: await certificateAuthority._privateKeyPassphrase() });
|
|
1092
|
+
restrictPrivateFilePermissions(privateKeyFilename, 384);
|
|
996
1093
|
displayTitle("Generate a certificate request for the CA key");
|
|
997
1094
|
await execute_openssl(
|
|
998
|
-
|
|
999
|
-
|
|
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 }
|
|
1000
1111
|
);
|
|
1001
1112
|
const issuerCA = certificateAuthority._issuerCA;
|
|
1002
1113
|
if (issuerCA) {
|
|
@@ -1004,27 +1115,71 @@ async function construct_CertificateAuthority(certificateAuthority) {
|
|
|
1004
1115
|
const issuerCert = import_node_path5.default.resolve(issuerCA.caCertificate);
|
|
1005
1116
|
const issuerKey = import_node_path5.default.resolve(issuerCA.rootDir, "private/cakey.pem");
|
|
1006
1117
|
const issuerSerial = import_node_path5.default.resolve(issuerCA.rootDir, "serial");
|
|
1118
|
+
const issuerPassin = await issuerCA._opensslPassin();
|
|
1007
1119
|
await execute_openssl(
|
|
1008
|
-
|
|
1009
|
-
|
|
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 }
|
|
1010
1144
|
);
|
|
1011
1145
|
} else {
|
|
1012
1146
|
displayTitle("Generate CA Certificate (self-signed)");
|
|
1013
1147
|
await execute_openssl(
|
|
1014
|
-
|
|
1015
|
-
|
|
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 }
|
|
1016
1168
|
);
|
|
1017
1169
|
}
|
|
1018
1170
|
displaySubtitle("generate initial CRL (Certificate Revocation List)");
|
|
1019
|
-
await regenerateCrl(certificateAuthority.revocationList, configOption, options);
|
|
1171
|
+
await regenerateCrl(certificateAuthority.revocationList, configOption, options, passin);
|
|
1020
1172
|
displayTitle("Create Certificate Authority (CA) ---> DONE");
|
|
1021
1173
|
}
|
|
1022
|
-
async function regenerateCrl(revocationList, configOption, options) {
|
|
1174
|
+
async function regenerateCrl(revocationList, configOption, options, passin) {
|
|
1023
1175
|
displaySubtitle("regenerate CRL (Certificate Revocation List)");
|
|
1024
|
-
await execute_openssl(
|
|
1025
|
-
|
|
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);
|
|
1026
1181
|
displaySubtitle("Display (Certificate Revocation List)");
|
|
1027
|
-
await execute_openssl(
|
|
1182
|
+
await execute_openssl(["crl", "-in", n4(revocationList), "-text", "-noout"], options);
|
|
1028
1183
|
}
|
|
1029
1184
|
function parseOpenSSLDate(dateStr) {
|
|
1030
1185
|
const raw = dateStr?.split(",")[0] ?? "";
|
|
@@ -1078,6 +1233,10 @@ var CertificateAuthority = class {
|
|
|
1078
1233
|
_crlDistributionUrl;
|
|
1079
1234
|
_ocspResponderUrl;
|
|
1080
1235
|
_caIssuersUrl;
|
|
1236
|
+
#privateKeyPassphrase;
|
|
1237
|
+
/** resolved once (see `privateKeyPassphrase`); `#passphraseResolved` distinguishes "none" from "not yet" */
|
|
1238
|
+
#resolvedPassphrase;
|
|
1239
|
+
#passphraseResolved = false;
|
|
1081
1240
|
constructor(options) {
|
|
1082
1241
|
(0, import_node_assert7.default)(Object.prototype.hasOwnProperty.call(options, "location"));
|
|
1083
1242
|
(0, import_node_assert7.default)(Object.prototype.hasOwnProperty.call(options, "keySize"));
|
|
@@ -1085,6 +1244,7 @@ var CertificateAuthority = class {
|
|
|
1085
1244
|
this.keySize = options.keySize || 2048;
|
|
1086
1245
|
this.subject = new import_node_opcua_crypto2.Subject(options.subject || defaultSubject);
|
|
1087
1246
|
this._issuerCA = options.issuerCA;
|
|
1247
|
+
this.#privateKeyPassphrase = options.privateKeyPassphrase;
|
|
1088
1248
|
if (options.crlDistributionUrl !== void 0) {
|
|
1089
1249
|
this.setCrlDistributionUrl(options.crlDistributionUrl);
|
|
1090
1250
|
}
|
|
@@ -1181,6 +1341,69 @@ var CertificateAuthority = class {
|
|
|
1181
1341
|
get configFile() {
|
|
1182
1342
|
return import_node_path5.default.normalize(import_node_path5.default.join(this.rootDir, "./conf/caconfig.cnf"));
|
|
1183
1343
|
}
|
|
1344
|
+
/** Path to the CA private key (`private/cakey.pem`); may be passphrase-encrypted, see {@link getPrivateKey}. */
|
|
1345
|
+
get privateKey() {
|
|
1346
|
+
return import_node_path5.default.join(import_node_path5.default.resolve(this.rootDir), "private/cakey.pem");
|
|
1347
|
+
}
|
|
1348
|
+
/**
|
|
1349
|
+
* The CA private key, decrypted with the configured `privateKeyPassphrase`
|
|
1350
|
+
* if it is encrypted. Fails closed (`PrivateKeyPassphraseRequiredError`)
|
|
1351
|
+
* on an encrypted key with no or the wrong passphrase.
|
|
1352
|
+
*/
|
|
1353
|
+
async getPrivateKey() {
|
|
1354
|
+
return (0, import_node_opcua_crypto2.readPrivateKey)(this.privateKey, await this._privateKeyPassphrase());
|
|
1355
|
+
}
|
|
1356
|
+
/**
|
|
1357
|
+
* Enable, disable, or rotate the passphrase protecting `private/cakey.pem`
|
|
1358
|
+
* (temp file + atomic rename, temp file removed on failure). Only
|
|
1359
|
+
* rewrites the file: construct a new `CertificateAuthority` with the new
|
|
1360
|
+
* passphrase to continue using it.
|
|
1361
|
+
*/
|
|
1362
|
+
async reencryptPrivateKey(oldPassphrase, newPassphrase) {
|
|
1363
|
+
const oldPass = await resolvePrivateKeyPassphrase(oldPassphrase);
|
|
1364
|
+
const newPass = await resolvePrivateKeyPassphrase(newPassphrase);
|
|
1365
|
+
const key = (0, import_node_opcua_crypto2.readPrivateKey)(this.privateKey, oldPass);
|
|
1366
|
+
await this.#rewritePrivateKeyFile(key, newPass);
|
|
1367
|
+
}
|
|
1368
|
+
async #rewritePrivateKeyFile(privateKey, passphrase) {
|
|
1369
|
+
const tmpFilename = `${this.privateKey}.${process.pid}-${Date.now()}.tmp`;
|
|
1370
|
+
try {
|
|
1371
|
+
await (0, import_node_opcua_crypto2.writePrivateKeyFile)(tmpFilename, privateKey, { passphrase });
|
|
1372
|
+
await import_node_fs7.default.promises.rename(tmpFilename, this.privateKey);
|
|
1373
|
+
} finally {
|
|
1374
|
+
await import_node_fs7.default.promises.rm(tmpFilename, { force: true });
|
|
1375
|
+
}
|
|
1376
|
+
}
|
|
1377
|
+
/** @internal resolve the configured passphrase, at most once per instance */
|
|
1378
|
+
async _privateKeyPassphrase() {
|
|
1379
|
+
if (!this.#passphraseResolved) {
|
|
1380
|
+
this.#resolvedPassphrase = await resolvePrivateKeyPassphrase(this.#privateKeyPassphrase);
|
|
1381
|
+
this.#passphraseResolved = true;
|
|
1382
|
+
}
|
|
1383
|
+
return this.#resolvedPassphrase;
|
|
1384
|
+
}
|
|
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
|
+
/**
|
|
1390
|
+
* @internal On an existing key: encrypt it in place if a passphrase is
|
|
1391
|
+
* configured and it is still plaintext (secure by default: the option
|
|
1392
|
+
* means "protect this key", not "ignore me"), then read it back so a
|
|
1393
|
+
* wrong or missing passphrase fails initialize() closed rather than the
|
|
1394
|
+
* first signing operation.
|
|
1395
|
+
*/
|
|
1396
|
+
async _ensurePrivateKeyProtection() {
|
|
1397
|
+
if (!import_node_fs7.default.existsSync(this.privateKey)) {
|
|
1398
|
+
return;
|
|
1399
|
+
}
|
|
1400
|
+
if (this.#privateKeyPassphrase !== void 0 && !isEncryptedPrivateKeyFile(this.privateKey)) {
|
|
1401
|
+
warningLog("CertificateAuthority: private key is plaintext but a passphrase is configured; encrypting it in place");
|
|
1402
|
+
const plaintextKey = (0, import_node_opcua_crypto2.readPrivateKey)(this.privateKey);
|
|
1403
|
+
await this.#rewritePrivateKeyFile(plaintextKey, await this._privateKeyPassphrase());
|
|
1404
|
+
}
|
|
1405
|
+
await this.getPrivateKey();
|
|
1406
|
+
}
|
|
1184
1407
|
/** Path to the CA certificate in PEM format (`public/cacert.pem`). */
|
|
1185
1408
|
get caCertificate() {
|
|
1186
1409
|
return makePath(this.rootDir, "./public/cacert.pem");
|
|
@@ -1638,12 +1861,17 @@ var CertificateAuthority = class {
|
|
|
1638
1861
|
async initializeCSR() {
|
|
1639
1862
|
const caRootDir = import_node_path5.default.resolve(this.rootDir);
|
|
1640
1863
|
mkdirRecursiveSync(caRootDir);
|
|
1641
|
-
for (const dir of ["
|
|
1864
|
+
for (const dir of ["public", "certs", "crl", "conf"]) {
|
|
1642
1865
|
mkdirRecursiveSync(import_node_path5.default.join(caRootDir, dir));
|
|
1643
1866
|
}
|
|
1867
|
+
ensurePrivateDirectory(import_node_path5.default.join(caRootDir, "private"));
|
|
1644
1868
|
const caCertFile = this.caCertificate;
|
|
1645
1869
|
const privateKeyFile = import_node_path5.default.join(caRootDir, "private/cakey.pem");
|
|
1646
1870
|
const csrFile = import_node_path5.default.join(caRootDir, "private/cakey.csr");
|
|
1871
|
+
if (import_node_fs7.default.existsSync(privateKeyFile)) {
|
|
1872
|
+
restrictPrivateFilePermissions(privateKeyFile, 384);
|
|
1873
|
+
await this._ensurePrivateKeyProtection();
|
|
1874
|
+
}
|
|
1647
1875
|
if (import_node_fs7.default.existsSync(caCertFile)) {
|
|
1648
1876
|
const certDer = (0, import_node_opcua_crypto2.convertPEMtoDER)((0, import_node_opcua_crypto2.readCertificatePEM)(caCertFile));
|
|
1649
1877
|
const certInfo = (0, import_node_opcua_crypto2.exploreCertificate)(certDer);
|
|
@@ -1677,11 +1905,10 @@ var CertificateAuthority = class {
|
|
|
1677
1905
|
await import_node_fs7.default.promises.writeFile(indexFileAttr, "unique_subject = no");
|
|
1678
1906
|
}
|
|
1679
1907
|
const caConfigFile = this.configFile;
|
|
1680
|
-
|
|
1681
|
-
data = makePath(data.replace(/%%ROOT_FOLDER%%/, caRootDir));
|
|
1682
|
-
await import_node_fs7.default.promises.writeFile(caConfigFile, data);
|
|
1908
|
+
await import_node_fs7.default.promises.writeFile(caConfigFile, renderCaConfig(caRootDir));
|
|
1683
1909
|
if (!import_node_fs7.default.existsSync(privateKeyFile)) {
|
|
1684
|
-
await (0, import_node_opcua_crypto2.generatePrivateKeyFile)(privateKeyFile, this.keySize);
|
|
1910
|
+
await (0, import_node_opcua_crypto2.generatePrivateKeyFile)(privateKeyFile, this.keySize, { passphrase: await this._privateKeyPassphrase() });
|
|
1911
|
+
restrictPrivateFilePermissions(privateKeyFile, 384);
|
|
1685
1912
|
}
|
|
1686
1913
|
await this._generateCSR(caRootDir, privateKeyFile, csrFile);
|
|
1687
1914
|
return { status: "created", csrPath: csrFile };
|
|
@@ -1723,14 +1950,29 @@ var CertificateAuthority = class {
|
|
|
1723
1950
|
* @internal
|
|
1724
1951
|
*/
|
|
1725
1952
|
async _generateCSR(caRootDir, privateKeyFile, csrFile) {
|
|
1726
|
-
const subjectOpt = ` -subj "${this.subject.toString()}" `;
|
|
1727
1953
|
processAltNames({});
|
|
1728
1954
|
const options = { cwd: caRootDir };
|
|
1729
1955
|
const configFile = generateStaticConfig("conf/caconfig.cnf", options);
|
|
1730
|
-
const
|
|
1956
|
+
const passin = await this._opensslPassin();
|
|
1731
1957
|
await execute_openssl(
|
|
1732
|
-
|
|
1733
|
-
|
|
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 }
|
|
1734
1976
|
);
|
|
1735
1977
|
}
|
|
1736
1978
|
/**
|
|
@@ -1752,7 +1994,6 @@ var CertificateAuthority = class {
|
|
|
1752
1994
|
async installCACertificate(signedCertFile) {
|
|
1753
1995
|
const caRootDir = import_node_path5.default.resolve(this.rootDir);
|
|
1754
1996
|
const caCertFile = this.caCertificate;
|
|
1755
|
-
const privateKeyFile = import_node_path5.default.join(caRootDir, "private/cakey.pem");
|
|
1756
1997
|
const fullPem = await import_node_fs7.default.promises.readFile(signedCertFile, "utf8");
|
|
1757
1998
|
const pemBlocks = fullPem.match(/-----BEGIN CERTIFICATE-----[\s\S]*?-----END CERTIFICATE-----/g);
|
|
1758
1999
|
if (!pemBlocks || pemBlocks.length === 0) {
|
|
@@ -1763,7 +2004,7 @@ var CertificateAuthority = class {
|
|
|
1763
2004
|
};
|
|
1764
2005
|
}
|
|
1765
2006
|
const certDer = (0, import_node_opcua_crypto2.convertPEMtoDER)(pemBlocks[0]);
|
|
1766
|
-
const privateKey =
|
|
2007
|
+
const privateKey = await this.getPrivateKey();
|
|
1767
2008
|
if (!(0, import_node_opcua_crypto2.certificateMatchesPrivateKey)(certDer, privateKey)) {
|
|
1768
2009
|
return {
|
|
1769
2010
|
status: "error",
|
|
@@ -1786,8 +2027,7 @@ var CertificateAuthority = class {
|
|
|
1786
2027
|
}
|
|
1787
2028
|
const options = { cwd: caRootDir };
|
|
1788
2029
|
const configFile = generateStaticConfig("conf/caconfig.cnf", options);
|
|
1789
|
-
|
|
1790
|
-
await regenerateCrl(this.revocationList, configOption, options);
|
|
2030
|
+
await regenerateCrl(this.revocationList, ["-config", n4(configFile)], options, await this._opensslPassin());
|
|
1791
2031
|
return { status: "success" };
|
|
1792
2032
|
}
|
|
1793
2033
|
/**
|
|
@@ -1809,9 +2049,32 @@ var CertificateAuthority = class {
|
|
|
1809
2049
|
this._wireRevocationEnvVars();
|
|
1810
2050
|
const configFile = generateStaticConfig("conf/caconfig.cnf", options);
|
|
1811
2051
|
const validity = params.validity ?? 3650;
|
|
2052
|
+
const passin = await this._opensslPassin();
|
|
1812
2053
|
await execute_openssl(
|
|
1813
|
-
|
|
1814
|
-
|
|
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 }
|
|
1815
2078
|
);
|
|
1816
2079
|
await this.constructCertificateChain(certFile);
|
|
1817
2080
|
}
|
|
@@ -1873,23 +2136,36 @@ var CertificateAuthority = class {
|
|
|
1873
2136
|
cwd: this.rootDir,
|
|
1874
2137
|
openssl_conf: makePath(configFile)
|
|
1875
2138
|
};
|
|
1876
|
-
const configOption = "";
|
|
1877
2139
|
const subject = params.subject ? new import_node_opcua_crypto2.Subject(params.subject).toString() : "";
|
|
1878
|
-
const subjectOptions = subject && subject.length > 1 ?
|
|
2140
|
+
const subjectOptions = subject && subject.length > 1 ? ["-subj", subject] : [];
|
|
1879
2141
|
displaySubtitle("- the certificate signing request");
|
|
1880
2142
|
await execute_openssl(
|
|
1881
|
-
"req
|
|
2143
|
+
["req", "-new", "-sha256", "-text", ...subjectOptions, "-batch", "-key", n4(privateKey), "-out", n4(csrFile)],
|
|
1882
2144
|
options
|
|
1883
2145
|
);
|
|
1884
2146
|
displaySubtitle("- creating the self-signed certificate");
|
|
1885
2147
|
await execute_openssl(
|
|
1886
|
-
|
|
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
|
+
],
|
|
1887
2163
|
options
|
|
1888
2164
|
);
|
|
1889
2165
|
displaySubtitle("- dump the certificate for a check");
|
|
1890
|
-
await execute_openssl(
|
|
2166
|
+
await execute_openssl(["x509", "-in", n4(certificateFile), "-dates", "-fingerprint", "-purpose", "-noout"], {});
|
|
1891
2167
|
displaySubtitle("- verify self-signed certificate");
|
|
1892
|
-
await execute_openssl_no_failure(
|
|
2168
|
+
await execute_openssl_no_failure(["verify", "-verbose", "-CAfile", n4(certificateFile), n4(certificateFile)], options);
|
|
1893
2169
|
await import_node_fs7.default.promises.unlink(csrFile);
|
|
1894
2170
|
}
|
|
1895
2171
|
/**
|
|
@@ -1919,22 +2195,41 @@ var CertificateAuthority = class {
|
|
|
1919
2195
|
setEnv("ALTNAME", "");
|
|
1920
2196
|
const randomFile = import_node_path5.default.join(this.rootDir, "random.rnd");
|
|
1921
2197
|
setEnv("RANDFILE", randomFile);
|
|
1922
|
-
const configOption =
|
|
2198
|
+
const configOption = ["-config", n4(configFile)];
|
|
1923
2199
|
const reason = params.reason || "keyCompromise";
|
|
1924
2200
|
(0, import_node_assert7.default)(crlReasons.indexOf(reason) >= 0);
|
|
1925
2201
|
displayTitle(`Revoking certificate ${certificate}`);
|
|
1926
2202
|
displaySubtitle("Revoke certificate");
|
|
1927
|
-
|
|
1928
|
-
await
|
|
2203
|
+
const passin = await this._opensslPassin();
|
|
2204
|
+
await execute_openssl_no_failure(
|
|
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);
|
|
1929
2209
|
displaySubtitle("Verify that certificate is revoked");
|
|
1930
2210
|
await execute_openssl_no_failure(
|
|
1931
|
-
|
|
2211
|
+
[
|
|
2212
|
+
"verify",
|
|
2213
|
+
"-verbose",
|
|
2214
|
+
"-CRLfile",
|
|
2215
|
+
n4(this.revocationList),
|
|
2216
|
+
"-CAfile",
|
|
2217
|
+
n4(this.caCertificate),
|
|
2218
|
+
"-crl_check",
|
|
2219
|
+
n4(certificate)
|
|
2220
|
+
],
|
|
1932
2221
|
options
|
|
1933
2222
|
);
|
|
1934
2223
|
displaySubtitle("Produce CRL in DER form ");
|
|
1935
|
-
await execute_openssl(
|
|
2224
|
+
await execute_openssl(
|
|
2225
|
+
["crl", "-in", n4(this.revocationList), "-out", "crl/revocation_list.der", "-outform", "der"],
|
|
2226
|
+
options
|
|
2227
|
+
);
|
|
1936
2228
|
displaySubtitle("Produce CRL in PEM form ");
|
|
1937
|
-
await execute_openssl(
|
|
2229
|
+
await execute_openssl(
|
|
2230
|
+
["crl", "-in", n4(this.revocationList), "-out", "crl/revocation_list.pem", "-outform", "pem", "-text"],
|
|
2231
|
+
options
|
|
2232
|
+
);
|
|
1938
2233
|
}
|
|
1939
2234
|
/**
|
|
1940
2235
|
* Sign a Certificate Signing Request (CSR) with this CA.
|
|
@@ -1976,13 +2271,27 @@ var CertificateAuthority = class {
|
|
|
1976
2271
|
this._wireRevocationEnvVars();
|
|
1977
2272
|
const configFile = generateStaticConfig("conf/caconfig.cnf", options);
|
|
1978
2273
|
displaySubtitle("- then we ask the authority to sign the certificate signing request");
|
|
1979
|
-
const
|
|
2274
|
+
const passin = await this._opensslPassin();
|
|
1980
2275
|
await execute_openssl(
|
|
1981
|
-
|
|
1982
|
-
|
|
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 }
|
|
1983
2292
|
);
|
|
1984
2293
|
displaySubtitle("- dump the certificate for a check");
|
|
1985
|
-
await execute_openssl(
|
|
2294
|
+
await execute_openssl(["x509", "-in", n4(certificate), "-dates", "-fingerprint", "-purpose", "-noout"], options);
|
|
1986
2295
|
displaySubtitle("- construct CA certificate with CRL");
|
|
1987
2296
|
await this.constructCACertificateWithCRL();
|
|
1988
2297
|
displaySubtitle("- construct certificate chain");
|
|
@@ -2002,10 +2311,8 @@ var CertificateAuthority = class {
|
|
|
2002
2311
|
const options = { cwd: this.rootDir };
|
|
2003
2312
|
const configFile = generateStaticConfig("conf/caconfig.cnf", options);
|
|
2004
2313
|
setEnv("OPENSSL_CONF", makePath(configFile));
|
|
2005
|
-
const _configOption = ` -config ${configFile}`;
|
|
2006
|
-
_configOption;
|
|
2007
2314
|
await execute_openssl_no_failure(
|
|
2008
|
-
|
|
2315
|
+
["verify", "-verbose", "-CAfile", n4(this.caCertificateWithCrl), n4(certificate)],
|
|
2009
2316
|
options
|
|
2010
2317
|
);
|
|
2011
2318
|
}
|
|
@@ -2030,13 +2337,14 @@ async function createCertificateSigningRequestAsync(certificateSigningRequestFil
|
|
|
2030
2337
|
(0, import_node_assert8.default)(params.rootDir);
|
|
2031
2338
|
(0, import_node_assert8.default)(params.configFile);
|
|
2032
2339
|
(0, import_node_assert8.default)(params.privateKey);
|
|
2033
|
-
|
|
2034
|
-
|
|
2340
|
+
if (typeof params.privateKey === "string") {
|
|
2341
|
+
(0, import_node_assert8.default)(import_node_fs8.default.existsSync(params.privateKey), `Private key must exist${params.privateKey}`);
|
|
2342
|
+
}
|
|
2035
2343
|
(0, import_node_assert8.default)(import_node_fs8.default.existsSync(params.rootDir), "RootDir key must exist");
|
|
2036
2344
|
(0, import_node_assert8.default)(typeof certificateSigningRequestFilename === "string");
|
|
2037
2345
|
const subject = params.subject ? new import_node_opcua_crypto3.Subject(params.subject).toString() : void 0;
|
|
2038
2346
|
displaySubtitle("- Creating a Certificate Signing Request with subtile");
|
|
2039
|
-
const privateKeyPem = await import_node_fs8.default.promises.readFile(params.privateKey, "utf-8");
|
|
2347
|
+
const privateKeyPem = typeof params.privateKey === "string" ? await import_node_fs8.default.promises.readFile(params.privateKey, "utf-8") : (0, import_node_opcua_crypto3.coercePrivateKeyPem)(params.privateKey);
|
|
2040
2348
|
const privateKey = await (0, import_node_opcua_crypto3.pemToPrivateKey)(privateKeyPem);
|
|
2041
2349
|
const { csr } = await (0, import_node_opcua_crypto3.createCertificateSigningRequest)({
|
|
2042
2350
|
privateKey,
|
|
@@ -2047,7 +2355,7 @@ async function createCertificateSigningRequestAsync(certificateSigningRequestFil
|
|
|
2047
2355
|
purpose: params.purpose
|
|
2048
2356
|
});
|
|
2049
2357
|
await import_node_fs8.default.promises.writeFile(certificateSigningRequestFilename, csr, "utf-8");
|
|
2050
|
-
display(`- privateKey ${params.privateKey}`);
|
|
2358
|
+
display(`- privateKey ${typeof params.privateKey === "string" ? params.privateKey : "<in-memory>"}`);
|
|
2051
2359
|
display(`- certificateSigningRequestFilename ${certificateSigningRequestFilename}`);
|
|
2052
2360
|
}
|
|
2053
2361
|
|
|
@@ -2060,7 +2368,9 @@ async function createSelfSignedCertificateAsync(certificate, params) {
|
|
|
2060
2368
|
(0, import_node_assert9.default)(params.purpose, "Please provide a Certificate Purpose");
|
|
2061
2369
|
(0, import_node_assert9.default)(import_node_fs9.default.existsSync(params.configFile));
|
|
2062
2370
|
(0, import_node_assert9.default)(import_node_fs9.default.existsSync(params.rootDir));
|
|
2063
|
-
(
|
|
2371
|
+
if (typeof params.privateKey === "string") {
|
|
2372
|
+
(0, import_node_assert9.default)(import_node_fs9.default.existsSync(params.privateKey));
|
|
2373
|
+
}
|
|
2064
2374
|
if (!params.subject) {
|
|
2065
2375
|
throw Error("Missing subject");
|
|
2066
2376
|
}
|
|
@@ -2072,7 +2382,7 @@ async function createSelfSignedCertificateAsync(certificate, params) {
|
|
|
2072
2382
|
subject = subject.toString();
|
|
2073
2383
|
const purpose = params.purpose;
|
|
2074
2384
|
displayTitle("Generate a certificate request");
|
|
2075
|
-
const privateKeyPem = await import_node_fs9.default.promises.readFile(params.privateKey, "utf-8");
|
|
2385
|
+
const privateKeyPem = typeof params.privateKey === "string" ? await import_node_fs9.default.promises.readFile(params.privateKey, "utf-8") : (0, import_node_opcua_crypto4.coercePrivateKeyPem)(params.privateKey);
|
|
2076
2386
|
const privateKey = await (0, import_node_opcua_crypto4.pemToPrivateKey)(privateKeyPem);
|
|
2077
2387
|
const { cert } = await (0, import_node_opcua_crypto4.createSelfSignedCertificate)({
|
|
2078
2388
|
privateKey,
|
|
@@ -2339,6 +2649,15 @@ var CertificateManager = class _CertificateManager extends import_node_events.Ev
|
|
|
2339
2649
|
#initializingPromise;
|
|
2340
2650
|
#addCertValidation;
|
|
2341
2651
|
#disableFileWatchers;
|
|
2652
|
+
#privateKeyPassphrase;
|
|
2653
|
+
#privateKeyProvider;
|
|
2654
|
+
/**
|
|
2655
|
+
* The on-disk key, decrypted once and kept for the instance's lifetime,
|
|
2656
|
+
* so the passphrase (or its resolver function) is consulted at most
|
|
2657
|
+
* once. Cleared by `dispose()`. Not used when a provider is configured:
|
|
2658
|
+
* the provider is the authority on the current key.
|
|
2659
|
+
*/
|
|
2660
|
+
#cachedPrivateKey;
|
|
2342
2661
|
#thumbs = {
|
|
2343
2662
|
rejected: /* @__PURE__ */ new Map(),
|
|
2344
2663
|
trusted: /* @__PURE__ */ new Map(),
|
|
@@ -2373,6 +2692,8 @@ var CertificateManager = class _CertificateManager extends import_node_events.Ev
|
|
|
2373
2692
|
maxChainLength: v.maxChainLength ?? 5
|
|
2374
2693
|
};
|
|
2375
2694
|
this.#disableFileWatchers = options.disableFileWatchers ?? process.env.OPCUA_PKI_DISABLE_FILE_WATCHERS === "true";
|
|
2695
|
+
this.#privateKeyPassphrase = options.privateKeyPassphrase;
|
|
2696
|
+
this.#privateKeyProvider = options.privateKeyProvider;
|
|
2376
2697
|
mkdirRecursiveSync(options.location);
|
|
2377
2698
|
if (!import_node_fs10.default.existsSync(this.#location)) {
|
|
2378
2699
|
throw new Error(`CertificateManager cannot access location ${this.#location}`);
|
|
@@ -2386,10 +2707,108 @@ var CertificateManager = class _CertificateManager extends import_node_events.Ev
|
|
|
2386
2707
|
get rootDir() {
|
|
2387
2708
|
return this.#location;
|
|
2388
2709
|
}
|
|
2389
|
-
/**
|
|
2710
|
+
/**
|
|
2711
|
+
* Path to the private key file (`own/private/private_key.pem`).
|
|
2712
|
+
*
|
|
2713
|
+
* Kept for backward compatibility with code that reads the key
|
|
2714
|
+
* directly from disk. When a passphrase or a `privateKeyProvider` is
|
|
2715
|
+
* configured, prefer {@link getPrivateKey} instead — this getter still
|
|
2716
|
+
* returns the on-disk path even if a provider is configured (there may
|
|
2717
|
+
* be no meaningful file in that case).
|
|
2718
|
+
*/
|
|
2390
2719
|
get privateKey() {
|
|
2391
2720
|
return import_node_path6.default.join(this.rootDir, "own/private/private_key.pem");
|
|
2392
2721
|
}
|
|
2722
|
+
/**
|
|
2723
|
+
* Resolve the private key: from `privateKeyProvider` if configured,
|
|
2724
|
+
* otherwise from disk (decrypting with `privateKeyPassphrase` if the
|
|
2725
|
+
* key is encrypted). Fails closed — throws
|
|
2726
|
+
* `PrivateKeyPassphraseRequiredError` — if the on-disk key is encrypted
|
|
2727
|
+
* and no passphrase is configured, or if the wrong passphrase is
|
|
2728
|
+
* configured.
|
|
2729
|
+
*
|
|
2730
|
+
* The on-disk key is read and decrypted once and then cached for the
|
|
2731
|
+
* lifetime of this instance, so a `privateKeyPassphrase` function is
|
|
2732
|
+
* called at most once (concurrent first calls share the same read). A
|
|
2733
|
+
* failed read is not cached, so a caller can fix the passphrase and
|
|
2734
|
+
* retry. A `privateKeyProvider` is consulted on every call: it is the
|
|
2735
|
+
* authority on what the current key is.
|
|
2736
|
+
*/
|
|
2737
|
+
async getPrivateKey() {
|
|
2738
|
+
if (this.#privateKeyProvider) {
|
|
2739
|
+
return await this.#privateKeyProvider.getPrivateKey();
|
|
2740
|
+
}
|
|
2741
|
+
if (this.#cachedPrivateKey) {
|
|
2742
|
+
return this.#cachedPrivateKey;
|
|
2743
|
+
}
|
|
2744
|
+
if (!this.#privateKeyPromise) {
|
|
2745
|
+
this.#privateKeyPromise = (async () => {
|
|
2746
|
+
const passphrase = await resolvePrivateKeyPassphrase(this.#privateKeyPassphrase);
|
|
2747
|
+
return (0, import_node_opcua_crypto5.readPrivateKey)(this.privateKey, passphrase);
|
|
2748
|
+
})().then(
|
|
2749
|
+
(key) => {
|
|
2750
|
+
this.#cachedPrivateKey = key;
|
|
2751
|
+
return key;
|
|
2752
|
+
},
|
|
2753
|
+
(err) => {
|
|
2754
|
+
this.#privateKeyPromise = void 0;
|
|
2755
|
+
throw err;
|
|
2756
|
+
}
|
|
2757
|
+
);
|
|
2758
|
+
}
|
|
2759
|
+
return await this.#privateKeyPromise;
|
|
2760
|
+
}
|
|
2761
|
+
/** In-flight first read of the on-disk key, so concurrent callers share one passphrase resolution. */
|
|
2762
|
+
#privateKeyPromise;
|
|
2763
|
+
/**
|
|
2764
|
+
* Enable, disable, or rotate the passphrase protecting the on-disk
|
|
2765
|
+
* private key: decrypt with `oldPassphrase` (omit if the key is
|
|
2766
|
+
* currently unencrypted), then write back encrypted with
|
|
2767
|
+
* `newPassphrase` (omit to leave it unencrypted). The write goes to a
|
|
2768
|
+
* temporary file in the same directory and is atomically renamed into
|
|
2769
|
+
* place, so a crash mid-rotation cannot leave a partially-written key;
|
|
2770
|
+
* the temporary file is removed if anything fails, so a rotation *to*
|
|
2771
|
+
* plaintext can never leave a stray cleartext copy behind. Runs under
|
|
2772
|
+
* the same lock as `initialize()`.
|
|
2773
|
+
*
|
|
2774
|
+
* This only rewrites the on-disk file — it does not update this
|
|
2775
|
+
* instance's own `privateKeyPassphrase` (set at construction), and it
|
|
2776
|
+
* drops this instance's cached key so that disk stays the source of
|
|
2777
|
+
* truth. Construct a new `CertificateManager` with the new passphrase to
|
|
2778
|
+
* continue using it afterward.
|
|
2779
|
+
*
|
|
2780
|
+
* Not supported when a `privateKeyProvider` is configured (there is no
|
|
2781
|
+
* disk file for this method to rewrite).
|
|
2782
|
+
*/
|
|
2783
|
+
async reencryptPrivateKey(oldPassphrase, newPassphrase) {
|
|
2784
|
+
if (this.#privateKeyProvider) {
|
|
2785
|
+
throw new Error("reencryptPrivateKey: not supported when a privateKeyProvider is configured");
|
|
2786
|
+
}
|
|
2787
|
+
const oldPass = await resolvePrivateKeyPassphrase(oldPassphrase);
|
|
2788
|
+
const newPass = await resolvePrivateKeyPassphrase(newPassphrase);
|
|
2789
|
+
await this.withLock2(async () => {
|
|
2790
|
+
const privateKey = (0, import_node_opcua_crypto5.readPrivateKey)(this.privateKey, oldPass);
|
|
2791
|
+
await this.#rewritePrivateKeyFile(privateKey, newPass);
|
|
2792
|
+
});
|
|
2793
|
+
this.#cachedPrivateKey = void 0;
|
|
2794
|
+
this.#privateKeyPromise = void 0;
|
|
2795
|
+
}
|
|
2796
|
+
/**
|
|
2797
|
+
* Atomically replace the on-disk private key with `privateKey`, written
|
|
2798
|
+
* as PKCS#8 (encrypted with `passphrase` if given). Temp file next to the
|
|
2799
|
+
* target, `0600`, renamed into place; the temp file is unlinked on any
|
|
2800
|
+
* failure so no partial or cleartext copy can be left behind.
|
|
2801
|
+
* Caller must hold the lock.
|
|
2802
|
+
*/
|
|
2803
|
+
async #rewritePrivateKeyFile(privateKey, passphrase) {
|
|
2804
|
+
const tmpFilename = `${this.privateKey}.${process.pid}-${Date.now()}.tmp`;
|
|
2805
|
+
try {
|
|
2806
|
+
await (0, import_node_opcua_crypto5.writePrivateKeyFile)(tmpFilename, privateKey, { passphrase });
|
|
2807
|
+
await import_node_fs10.default.promises.rename(tmpFilename, this.privateKey);
|
|
2808
|
+
} finally {
|
|
2809
|
+
await import_node_fs10.default.promises.rm(tmpFilename, { force: true });
|
|
2810
|
+
}
|
|
2811
|
+
}
|
|
2393
2812
|
/** Path to the OpenSSL random seed file. */
|
|
2394
2813
|
get randomFile() {
|
|
2395
2814
|
return import_node_path6.default.join(this.rootDir, "./random.rnd");
|
|
@@ -2689,19 +3108,24 @@ var CertificateManager = class _CertificateManager extends import_node_events.Ev
|
|
|
2689
3108
|
}
|
|
2690
3109
|
this.state = 1 /* Initializing */;
|
|
2691
3110
|
this.#initializingPromise = this.#initialize();
|
|
2692
|
-
|
|
3111
|
+
try {
|
|
3112
|
+
await this.#initializingPromise;
|
|
3113
|
+
} catch (err) {
|
|
3114
|
+
this.#initializingPromise = void 0;
|
|
3115
|
+
this.state = 0 /* Uninitialized */;
|
|
3116
|
+
throw err;
|
|
3117
|
+
}
|
|
2693
3118
|
this.#initializingPromise = void 0;
|
|
2694
3119
|
this.state = 2 /* Initialized */;
|
|
2695
3120
|
_CertificateManager.#activeInstances.add(this);
|
|
2696
3121
|
_CertificateManager.#installProcessCleanup();
|
|
2697
3122
|
}
|
|
2698
3123
|
async #initialize() {
|
|
2699
|
-
this.state = 1 /* Initializing */;
|
|
2700
3124
|
const pkiDir = this.#location;
|
|
2701
3125
|
mkdirRecursiveSync(pkiDir);
|
|
2702
3126
|
mkdirRecursiveSync(import_node_path6.default.join(pkiDir, "own"));
|
|
2703
3127
|
mkdirRecursiveSync(import_node_path6.default.join(pkiDir, "own/certs"));
|
|
2704
|
-
|
|
3128
|
+
ensurePrivateDirectory(import_node_path6.default.join(pkiDir, "own/private"));
|
|
2705
3129
|
mkdirRecursiveSync(import_node_path6.default.join(pkiDir, "rejected"));
|
|
2706
3130
|
mkdirRecursiveSync(import_node_path6.default.join(pkiDir, "trusted"));
|
|
2707
3131
|
mkdirRecursiveSync(import_node_path6.default.join(pkiDir, "trusted/certs"));
|
|
@@ -2709,7 +3133,10 @@ var CertificateManager = class _CertificateManager extends import_node_events.Ev
|
|
|
2709
3133
|
mkdirRecursiveSync(import_node_path6.default.join(pkiDir, "issuers"));
|
|
2710
3134
|
mkdirRecursiveSync(import_node_path6.default.join(pkiDir, "issuers/certs"));
|
|
2711
3135
|
mkdirRecursiveSync(import_node_path6.default.join(pkiDir, "issuers/crl"));
|
|
2712
|
-
|
|
3136
|
+
const ownsDiskKey = !this.#privateKeyProvider;
|
|
3137
|
+
const needsKeyGeneration = ownsDiskKey && !import_node_fs10.default.existsSync(this.privateKey);
|
|
3138
|
+
const needsKeyEncryption = ownsDiskKey && !needsKeyGeneration && this.#privateKeyPassphrase !== void 0 && !isEncryptedPrivateKeyFile(this.privateKey);
|
|
3139
|
+
if (!import_node_fs10.default.existsSync(this.configFile) || needsKeyGeneration || needsKeyEncryption) {
|
|
2713
3140
|
return await this.withLock2(async () => {
|
|
2714
3141
|
if (this.state === 3 /* Disposing */ || this.state === 4 /* Disposed */) {
|
|
2715
3142
|
return;
|
|
@@ -2717,15 +3144,29 @@ var CertificateManager = class _CertificateManager extends import_node_events.Ev
|
|
|
2717
3144
|
if (!import_node_fs10.default.existsSync(this.configFile)) {
|
|
2718
3145
|
import_node_fs10.default.writeFileSync(this.configFile, configurationFileSimpleTemplate2);
|
|
2719
3146
|
}
|
|
2720
|
-
if (!import_node_fs10.default.existsSync(this.privateKey)) {
|
|
3147
|
+
if (ownsDiskKey && !import_node_fs10.default.existsSync(this.privateKey)) {
|
|
2721
3148
|
debugLog("generating private key ...");
|
|
2722
|
-
await (
|
|
2723
|
-
await
|
|
2724
|
-
|
|
2725
|
-
|
|
3149
|
+
const passphrase = await resolvePrivateKeyPassphrase(this.#privateKeyPassphrase);
|
|
3150
|
+
await (0, import_node_opcua_crypto5.generatePrivateKeyFile)(this.privateKey, this.keySize, { passphrase });
|
|
3151
|
+
this.#cachedPrivateKey = (0, import_node_opcua_crypto5.readPrivateKey)(this.privateKey, passphrase);
|
|
3152
|
+
} else if (ownsDiskKey && this.#privateKeyPassphrase !== void 0 && !isEncryptedPrivateKeyFile(this.privateKey)) {
|
|
3153
|
+
warningLog("initialize: private key is plaintext but a passphrase is configured; encrypting it in place");
|
|
3154
|
+
const passphrase = await resolvePrivateKeyPassphrase(this.#privateKeyPassphrase);
|
|
3155
|
+
const plaintextKey = (0, import_node_opcua_crypto5.readPrivateKey)(this.privateKey);
|
|
3156
|
+
await this.#rewritePrivateKeyFile(plaintextKey, passphrase);
|
|
3157
|
+
this.#cachedPrivateKey = plaintextKey;
|
|
3158
|
+
}
|
|
3159
|
+
if (ownsDiskKey) {
|
|
3160
|
+
restrictPrivateFilePermissions(this.privateKey, 384);
|
|
2726
3161
|
}
|
|
3162
|
+
await this.getPrivateKey();
|
|
3163
|
+
await this.#readCertificates();
|
|
2727
3164
|
});
|
|
2728
3165
|
} else {
|
|
3166
|
+
if (ownsDiskKey) {
|
|
3167
|
+
restrictPrivateFilePermissions(this.privateKey, 384);
|
|
3168
|
+
}
|
|
3169
|
+
await this.getPrivateKey();
|
|
2729
3170
|
await this.#readCertificates();
|
|
2730
3171
|
}
|
|
2731
3172
|
}
|
|
@@ -2761,6 +3202,8 @@ var CertificateManager = class _CertificateManager extends import_node_events.Ev
|
|
|
2761
3202
|
this.#watchers.splice(0);
|
|
2762
3203
|
} finally {
|
|
2763
3204
|
this.state = 4 /* Disposed */;
|
|
3205
|
+
this.#cachedPrivateKey = void 0;
|
|
3206
|
+
this.#privateKeyPromise = void 0;
|
|
2764
3207
|
_CertificateManager.#activeInstances.delete(this);
|
|
2765
3208
|
}
|
|
2766
3209
|
}
|
|
@@ -2807,16 +3250,18 @@ var CertificateManager = class _CertificateManager extends import_node_events.Ev
|
|
|
2807
3250
|
if (typeof params.applicationUri !== "string") {
|
|
2808
3251
|
throw new Error("createSelfSignedCertificate: expecting applicationUri to be a string");
|
|
2809
3252
|
}
|
|
2810
|
-
if (!import_node_fs10.default.existsSync(this.privateKey)) {
|
|
3253
|
+
if (!this.#privateKeyProvider && !import_node_fs10.default.existsSync(this.privateKey)) {
|
|
2811
3254
|
throw new Error(`Cannot find private key ${this.privateKey}`);
|
|
2812
3255
|
}
|
|
2813
3256
|
let certificateFilename = import_node_path6.default.join(this.rootDir, "own/certs/self_signed_certificate.pem");
|
|
2814
3257
|
certificateFilename = params.outputFile || certificateFilename;
|
|
2815
|
-
const _params =
|
|
2816
|
-
|
|
2817
|
-
|
|
2818
|
-
|
|
2819
|
-
|
|
3258
|
+
const _params = {
|
|
3259
|
+
...params,
|
|
3260
|
+
rootDir: this.rootDir,
|
|
3261
|
+
configFile: this.configFile,
|
|
3262
|
+
privateKey: await this.getPrivateKey(),
|
|
3263
|
+
subject: params.subject || "CN=FIXME"
|
|
3264
|
+
};
|
|
2820
3265
|
await this.withLock2(async () => {
|
|
2821
3266
|
await createSelfSignedCertificate(certificateFilename, _params);
|
|
2822
3267
|
});
|
|
@@ -2835,13 +3280,15 @@ var CertificateManager = class _CertificateManager extends import_node_events.Ev
|
|
|
2835
3280
|
if (!params) {
|
|
2836
3281
|
throw new Error("params is required");
|
|
2837
3282
|
}
|
|
2838
|
-
|
|
2839
|
-
if (Object.prototype.hasOwnProperty.call(_params, "rootDir")) {
|
|
3283
|
+
if (Object.prototype.hasOwnProperty.call(params, "rootDir")) {
|
|
2840
3284
|
throw new Error("rootDir should not be specified ");
|
|
2841
3285
|
}
|
|
2842
|
-
_params
|
|
2843
|
-
|
|
2844
|
-
|
|
3286
|
+
const _params = {
|
|
3287
|
+
...params,
|
|
3288
|
+
rootDir: import_node_path6.default.resolve(this.rootDir),
|
|
3289
|
+
configFile: import_node_path6.default.resolve(this.configFile),
|
|
3290
|
+
privateKey: await this.getPrivateKey()
|
|
3291
|
+
};
|
|
2845
3292
|
return await this.withLock2(async () => {
|
|
2846
3293
|
const now = /* @__PURE__ */ new Date();
|
|
2847
3294
|
const today = `${now.toISOString().slice(0, 10)}_${now.getTime()}`;
|
|
@@ -3692,6 +4139,7 @@ var CertificateManager = class _CertificateManager extends import_node_events.Ev
|
|
|
3692
4139
|
CertificateManager,
|
|
3693
4140
|
CertificateManagerState,
|
|
3694
4141
|
ChainCompletionStatus,
|
|
4142
|
+
PrivateKeyPassphraseRequiredError,
|
|
3695
4143
|
Subject,
|
|
3696
4144
|
VerificationStatus,
|
|
3697
4145
|
adjustApplicationUri,
|
|
@@ -3710,6 +4158,7 @@ var CertificateManager = class _CertificateManager extends import_node_events.Ev
|
|
|
3710
4158
|
isIssuer,
|
|
3711
4159
|
isRootIssuer,
|
|
3712
4160
|
makeFingerprint,
|
|
3713
|
-
quote
|
|
4161
|
+
quote,
|
|
4162
|
+
resolvePrivateKeyPassphrase
|
|
3714
4163
|
});
|
|
3715
4164
|
//# sourceMappingURL=index.js.map
|