node-opcua-pki 6.22.1 → 7.0.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.
Files changed (137) hide show
  1. package/bin/install_prerequisite.ts +1 -1
  2. package/bin/pki.ts +1 -1
  3. package/dist/bin/install_prerequisite.d.ts +2 -0
  4. package/dist/bin/install_prerequisite.js +6 -0
  5. package/dist/bin/install_prerequisite.js.map +1 -0
  6. package/dist/bin/pki.d.ts +2 -0
  7. package/dist/bin/pki.js +4 -0
  8. package/dist/bin/pki.js.map +1 -0
  9. package/dist/lib/ca/backends/native_ca_backend.d.ts +49 -0
  10. package/dist/lib/ca/backends/native_ca_backend.js +365 -0
  11. package/dist/lib/ca/backends/native_ca_backend.js.map +1 -0
  12. package/dist/lib/ca/backends/openssl_ca_backend.d.ts +47 -0
  13. package/dist/lib/ca/backends/openssl_ca_backend.js +398 -0
  14. package/dist/lib/ca/backends/openssl_ca_backend.js.map +1 -0
  15. package/dist/lib/ca/certificate_authority.d.ts +62 -0
  16. package/dist/lib/ca/certificate_authority.js +91 -0
  17. package/dist/lib/ca/certificate_authority.js.map +1 -0
  18. package/dist/lib/ca/core/ca_backend.d.ts +89 -0
  19. package/dist/lib/ca/core/ca_backend.js +24 -0
  20. package/dist/lib/ca/core/ca_backend.js.map +1 -0
  21. package/dist/lib/ca/core/ca_database.d.ts +91 -0
  22. package/dist/lib/ca/core/ca_database.js +267 -0
  23. package/dist/lib/ca/core/ca_database.js.map +1 -0
  24. package/dist/{core-BwqSJPHc.d.mts → lib/ca/core/certificate_authority_core.d.ts} +45 -394
  25. package/dist/lib/ca/core/certificate_authority_core.js +1300 -0
  26. package/dist/lib/ca/core/certificate_authority_core.js.map +1 -0
  27. package/dist/lib/ca/core/index.d.ts +17 -0
  28. package/dist/lib/ca/core/index.js +25 -0
  29. package/dist/lib/ca/core/index.js.map +1 -0
  30. package/dist/lib/ca/crypto_create_CA.d.ts +1 -0
  31. package/dist/lib/ca/crypto_create_CA.js +821 -0
  32. package/dist/lib/ca/crypto_create_CA.js.map +1 -0
  33. package/dist/lib/ca/index.d.ts +1 -0
  34. package/dist/lib/ca/index.js +2 -0
  35. package/dist/lib/ca/index.js.map +1 -0
  36. package/dist/lib/ca/templates/ca_config_template.cnf.d.ts +2 -0
  37. package/dist/lib/ca/templates/ca_config_template.cnf.js +170 -0
  38. package/dist/lib/ca/templates/ca_config_template.cnf.js.map +1 -0
  39. package/dist/lib/core.d.ts +33 -0
  40. package/dist/lib/core.js +56 -0
  41. package/dist/lib/core.js.map +1 -0
  42. package/dist/lib/index.d.ts +11 -0
  43. package/dist/lib/index.js +37 -0
  44. package/dist/lib/index.js.map +1 -0
  45. package/dist/lib/misc/applicationurn.d.ts +1 -0
  46. package/dist/lib/misc/applicationurn.js +40 -0
  47. package/dist/lib/misc/applicationurn.js.map +1 -0
  48. package/dist/lib/misc/hostname.d.ts +8 -0
  49. package/dist/lib/misc/hostname.js +82 -0
  50. package/dist/lib/misc/hostname.js.map +1 -0
  51. package/dist/lib/misc/subject.d.ts +2 -0
  52. package/dist/lib/misc/subject.js +2 -0
  53. package/dist/lib/misc/subject.js.map +1 -0
  54. package/dist/{index.d.ts → lib/pki/certificate_manager.d.ts} +24 -248
  55. package/dist/lib/pki/certificate_manager.js +2302 -0
  56. package/dist/lib/pki/certificate_manager.js.map +1 -0
  57. package/dist/lib/pki/templates/simple_config_template.cnf.d.ts +2 -0
  58. package/dist/lib/pki/templates/simple_config_template.cnf.js +74 -0
  59. package/dist/lib/pki/templates/simple_config_template.cnf.js.map +1 -0
  60. package/dist/lib/pki/toolbox_pfx.d.ts +127 -0
  61. package/dist/lib/pki/toolbox_pfx.js +248 -0
  62. package/dist/lib/pki/toolbox_pfx.js.map +1 -0
  63. package/dist/lib/toolbox/common.d.ts +150 -0
  64. package/dist/lib/toolbox/common.js +82 -0
  65. package/dist/lib/toolbox/common.js.map +1 -0
  66. package/dist/lib/toolbox/common2.d.ts +25 -0
  67. package/dist/lib/toolbox/common2.js +97 -0
  68. package/dist/lib/toolbox/common2.js.map +1 -0
  69. package/dist/lib/toolbox/config.d.ts +5 -0
  70. package/dist/lib/toolbox/config.js +28 -0
  71. package/dist/lib/toolbox/config.js.map +1 -0
  72. package/dist/lib/toolbox/debug.d.ts +5 -0
  73. package/dist/lib/toolbox/debug.js +35 -0
  74. package/dist/lib/toolbox/debug.js.map +1 -0
  75. package/dist/lib/toolbox/display.d.ts +4 -0
  76. package/dist/lib/toolbox/display.js +56 -0
  77. package/dist/lib/toolbox/display.js.map +1 -0
  78. package/dist/lib/toolbox/index.d.ts +5 -0
  79. package/dist/lib/toolbox/index.js +28 -0
  80. package/dist/lib/toolbox/index.js.map +1 -0
  81. package/dist/lib/toolbox/with_openssl/_create_random_file.d.ts +4 -0
  82. package/dist/lib/toolbox/with_openssl/_create_random_file.js +50 -0
  83. package/dist/lib/toolbox/with_openssl/_create_random_file.js.map +1 -0
  84. package/dist/lib/toolbox/with_openssl/_env.d.ts +58 -0
  85. package/dist/lib/toolbox/with_openssl/_env.js +126 -0
  86. package/dist/lib/toolbox/with_openssl/_env.js.map +1 -0
  87. package/dist/lib/toolbox/with_openssl/create_certificate_signing_request.d.ts +5 -0
  88. package/dist/lib/toolbox/with_openssl/create_certificate_signing_request.js +72 -0
  89. package/dist/lib/toolbox/with_openssl/create_certificate_signing_request.js.map +1 -0
  90. package/dist/lib/toolbox/with_openssl/create_private_key.d.ts +5 -0
  91. package/dist/lib/toolbox/with_openssl/create_private_key.js +92 -0
  92. package/dist/lib/toolbox/with_openssl/create_private_key.js.map +1 -0
  93. package/dist/lib/toolbox/with_openssl/create_self_signed_certificate.d.ts +5 -0
  94. package/dist/lib/toolbox/with_openssl/create_self_signed_certificate.js +135 -0
  95. package/dist/lib/toolbox/with_openssl/create_self_signed_certificate.js.map +1 -0
  96. package/dist/lib/toolbox/with_openssl/execute_openssl.d.ts +67 -0
  97. package/dist/lib/toolbox/with_openssl/execute_openssl.js +246 -0
  98. package/dist/lib/toolbox/with_openssl/execute_openssl.js.map +1 -0
  99. package/dist/lib/toolbox/with_openssl/index.d.ts +5 -0
  100. package/dist/lib/toolbox/with_openssl/index.js +30 -0
  101. package/dist/lib/toolbox/with_openssl/index.js.map +1 -0
  102. package/dist/lib/toolbox/with_openssl/install_prerequisite.d.ts +7 -0
  103. package/dist/lib/toolbox/with_openssl/install_prerequisite.js +358 -0
  104. package/dist/lib/toolbox/with_openssl/install_prerequisite.js.map +1 -0
  105. package/dist/lib/toolbox/with_openssl/toolbox.d.ts +53 -0
  106. package/dist/lib/toolbox/with_openssl/toolbox.js +190 -0
  107. package/dist/lib/toolbox/with_openssl/toolbox.js.map +1 -0
  108. package/dist/lib/toolbox/without_openssl/create_certificate_signing_request.d.ts +5 -0
  109. package/dist/lib/toolbox/without_openssl/create_certificate_signing_request.js +69 -0
  110. package/dist/lib/toolbox/without_openssl/create_certificate_signing_request.js.map +1 -0
  111. package/dist/lib/toolbox/without_openssl/create_self_signed_certificate.d.ts +3 -0
  112. package/dist/lib/toolbox/without_openssl/create_self_signed_certificate.js +78 -0
  113. package/dist/lib/toolbox/without_openssl/create_self_signed_certificate.js.map +1 -0
  114. package/dist/lib/toolbox/without_openssl/index.d.ts +2 -0
  115. package/dist/lib/toolbox/without_openssl/index.js +25 -0
  116. package/dist/lib/toolbox/without_openssl/index.js.map +1 -0
  117. package/package.json +11 -30
  118. package/dist/bin/install_prerequisite.mjs +0 -18
  119. package/dist/bin/install_prerequisite.mjs.map +0 -1
  120. package/dist/bin/pki.mjs +0 -5932
  121. package/dist/bin/pki.mjs.map +0 -1
  122. package/dist/chunk-EQ4DGI4M.mjs +0 -1917
  123. package/dist/chunk-EQ4DGI4M.mjs.map +0 -1
  124. package/dist/chunk-GCHH54PS.mjs +0 -30
  125. package/dist/chunk-GCHH54PS.mjs.map +0 -1
  126. package/dist/core-BwqSJPHc.d.ts +0 -1041
  127. package/dist/core.d.mts +0 -2
  128. package/dist/core.d.ts +0 -2
  129. package/dist/core.js +0 -1902
  130. package/dist/core.js.map +0 -1
  131. package/dist/core.mjs +0 -21
  132. package/dist/core.mjs.map +0 -1
  133. package/dist/index.d.mts +0 -1003
  134. package/dist/index.js +0 -5009
  135. package/dist/index.js.map +0 -1
  136. package/dist/index.mjs +0 -3145
  137. package/dist/index.mjs.map +0 -1
package/dist/index.mjs DELETED
@@ -1,3145 +0,0 @@
1
- import {
2
- CertificateAuthorityCore,
3
- NativeCaBackend,
4
- Subject,
5
- adjustApplicationUri,
6
- adjustDate,
7
- debugLog,
8
- display,
9
- displayError,
10
- displaySubtitle,
11
- displayTitle,
12
- doDebug,
13
- ensurePrivateDirectory,
14
- g_config,
15
- isEncryptedPrivateKeyFile,
16
- isOpaqueSigner,
17
- makePath,
18
- mkdirRecursiveSync,
19
- quote,
20
- resolvePrivateKeyPassphrase,
21
- restrictPrivateFilePermissions,
22
- warningLog
23
- } from "./chunk-EQ4DGI4M.mjs";
24
-
25
- // packages/node-opcua-pki/lib/index.ts
26
- import {
27
- PrivateKeyPassphraseRequiredError,
28
- PrivateKeyUnavailableError as PrivateKeyUnavailableError2
29
- } from "node-opcua-crypto";
30
-
31
- // packages/node-opcua-pki/lib/ca/backends/openssl_ca_backend.ts
32
- import fs4 from "fs";
33
- import path3 from "path";
34
- import { Subject as Subject2 } from "node-opcua-crypto";
35
-
36
- // packages/node-opcua-pki/lib/toolbox/with_openssl/_env.ts
37
- var SAFE_ENV_PASSTHROUGH = /* @__PURE__ */ new Set([
38
- // POSIX/Windows shell and process essentials
39
- "path",
40
- "home",
41
- "userprofile",
42
- "temp",
43
- "tmp",
44
- "tmpdir",
45
- "systemroot",
46
- "windir",
47
- "comspec",
48
- "pathext",
49
- "appdata",
50
- "localappdata",
51
- // dynamic loader: a custom-built or relocated openssl (e.g. under /opt,
52
- // or Homebrew on macOS) may need these to find its own libcrypto/libssl
53
- "ld_library_path",
54
- "dyld_library_path",
55
- "dyld_fallback_library_path",
56
- // locale, so openssl's textual output stays parseable
57
- "lang",
58
- "lc_all",
59
- "lc_ctype",
60
- // openssl configuration this app, or the host OS/user, may rely on
61
- "openssl_conf",
62
- "randfile",
63
- "openssl_modules",
64
- "openssl_engines",
65
- "ssl_cert_file",
66
- "ssl_cert_dir"
67
- ]);
68
- function buildChildEnv(extra) {
69
- const env = {};
70
- for (const key of Object.keys(process.env)) {
71
- if (SAFE_ENV_PASSTHROUGH.has(key.toLowerCase())) {
72
- env[key] = process.env[key];
73
- }
74
- }
75
- return { ...env, ...extra };
76
- }
77
- function redactEnvForLog(options) {
78
- const { env, ...rest } = options;
79
- return env ? { ...rest, env: Object.keys(env) } : rest;
80
- }
81
- var exportedEnvVars = {};
82
- function setEnv(varName, value) {
83
- if (!g_config.silent) {
84
- warningLog(` set ${varName}=${value}`);
85
- }
86
- exportedEnvVars[varName] = value;
87
- if (["OPENSSL_CONF"].indexOf(varName) >= 0) {
88
- process.env[varName] = value;
89
- }
90
- if (["RANDFILE"].indexOf(varName) >= 0) {
91
- process.env[varName] = value;
92
- }
93
- }
94
- function hasEnv(varName) {
95
- return Object.prototype.hasOwnProperty.call(exportedEnvVars, varName);
96
- }
97
- function getEnv(varName) {
98
- return exportedEnvVars[varName];
99
- }
100
- function unsetEnv(varName) {
101
- delete exportedEnvVars[varName];
102
- }
103
- function getEnvironmentVarNames() {
104
- return Object.keys(exportedEnvVars).map((varName) => {
105
- return { key: varName, pattern: `\\$ENV\\:\\:${varName}` };
106
- });
107
- }
108
- function buildSubjectAltNameString(params) {
109
- return [
110
- `URI:${params.applicationUri}`,
111
- ...(params.dns ?? []).map((d) => `DNS:${d}`),
112
- ...(params.ip ?? []).map((d) => `IP:${d}`)
113
- ].join(", ");
114
- }
115
-
116
- // packages/node-opcua-pki/lib/toolbox/with_openssl/execute_openssl.ts
117
- import assert from "assert";
118
- import child_process2 from "child_process";
119
- import fs2 from "fs";
120
- import os2 from "os";
121
- import byline2 from "byline";
122
- import chalk2 from "chalk";
123
-
124
- // packages/node-opcua-pki/lib/toolbox/with_openssl/install_prerequisite.ts
125
- import child_process from "child_process";
126
- import fs from "fs";
127
- import os from "os";
128
- import path from "path";
129
- import { pipeline } from "stream/promises";
130
- import byline from "byline";
131
- import chalk from "chalk";
132
- import yauzl from "yauzl";
133
- var doDebug2 = process.env.NODEOPCUAPKIDEBUG || false;
134
- async function execute(file, args, cwd) {
135
- let output = "";
136
- return await new Promise((resolve, reject) => {
137
- const child = child_process.spawn(file, args, {
138
- cwd,
139
- windowsHide: true,
140
- env: buildChildEnv(),
141
- stdio: ["ignore", "pipe", "pipe"]
142
- });
143
- child.on("error", (err) => reject(err));
144
- child.on("close", (code) => resolve({ exitCode: code ?? 1, output }));
145
- const stream1 = byline(child.stdout);
146
- stream1.on("data", (line) => {
147
- output += `${line}
148
- `;
149
- if (doDebug2) {
150
- process.stdout.write(` stdout ${chalk.yellow(line)}
151
- `);
152
- }
153
- });
154
- });
155
- }
156
- function is_expected_openssl_version(strVersion) {
157
- return !!strVersion.match(/OpenSSL \d/);
158
- }
159
- async function getopensslExecPath() {
160
- let result1;
161
- try {
162
- result1 = await execute("which", ["openssl"]);
163
- } catch (err) {
164
- warningLog("warning: ", err.message);
165
- throw new Error("Cannot find openssl");
166
- }
167
- const exitCode = result1?.exitCode;
168
- const output = result1?.output;
169
- if (exitCode !== 0) {
170
- warningLog(chalk.yellow(" it seems that ") + chalk.cyan("openssl") + chalk.yellow(" is not installed on your computer "));
171
- warningLog(chalk.yellow("Please install it before running this programs"));
172
- throw new Error("Cannot find openssl");
173
- }
174
- const opensslExecPath = output.replace(/\n\r/g, "").trim();
175
- return opensslExecPath;
176
- }
177
- async function check_system_openssl_version() {
178
- const opensslExecPath = await getopensslExecPath();
179
- if (doDebug2) {
180
- warningLog(` OpenSSL found in : ${chalk.yellow(opensslExecPath)}`);
181
- }
182
- const result = await execute(opensslExecPath, ["version"]);
183
- const exitCode = result?.exitCode;
184
- const output = result?.output;
185
- const version = output.trim();
186
- const versionOK = exitCode === 0 && is_expected_openssl_version(version);
187
- if (!versionOK) {
188
- let message = chalk.whiteBright("Warning !!!!!!!!!!!! ") + "\nyour version of openssl is " + version + ". It doesn't match the expected version";
189
- if (process.platform === "darwin") {
190
- message += chalk.cyan("\nplease refer to :") + chalk.yellow(" https://github.com/node-opcua/node-opcua/wiki/installing-node-opcua-or-node-red-on-MacOS");
191
- }
192
- console.log(message);
193
- }
194
- return output;
195
- }
196
- async function install_and_check_win32_openssl_version() {
197
- const downloadFolder = path.join(os.tmpdir(), ".");
198
- function get_openssl_folder_win32() {
199
- if (process.env.LOCALAPPDATA) {
200
- const userProgramFolder = path.join(process.env.LOCALAPPDATA, "Programs");
201
- if (fs.existsSync(userProgramFolder)) {
202
- return path.join(userProgramFolder, "openssl");
203
- }
204
- }
205
- return path.join(process.cwd(), "openssl");
206
- }
207
- function get_openssl_exec_path_win32() {
208
- const opensslFolder2 = get_openssl_folder_win32();
209
- return path.join(opensslFolder2, "openssl.exe");
210
- }
211
- async function check_openssl_win32() {
212
- const opensslExecPath2 = get_openssl_exec_path_win32();
213
- const exists = fs.existsSync(opensslExecPath2);
214
- if (!exists) {
215
- warningLog("checking presence of ", opensslExecPath2);
216
- warningLog(chalk.red(" cannot find file ") + opensslExecPath2);
217
- return {
218
- opensslOk: false,
219
- version: `cannot find file ${opensslExecPath2}`
220
- };
221
- } else {
222
- const cwd = ".";
223
- const { exitCode, output } = await execute(opensslExecPath2, ["version"], cwd);
224
- const version = output.trim();
225
- if (doDebug2) {
226
- warningLog(" Version = ", version);
227
- }
228
- return {
229
- opensslOk: exitCode === 0 && is_expected_openssl_version(version),
230
- version
231
- };
232
- }
233
- }
234
- async function find_system_openssl_win32() {
235
- try {
236
- const result = await execute("where", ["openssl"]);
237
- if (result.exitCode !== 0) {
238
- return void 0;
239
- }
240
- const opensslPath2 = result.output.split(/\r?\n/)[0].trim();
241
- if (!opensslPath2 || !fs.existsSync(opensslPath2)) {
242
- return void 0;
243
- }
244
- const versionResult = await execute(opensslPath2, ["version"]);
245
- const version = versionResult.output.trim();
246
- if (versionResult.exitCode === 0 && is_expected_openssl_version(version)) {
247
- warningLog(
248
- chalk.green("Using system OpenSSL: ") + chalk.cyan(version) + chalk.green(" at ") + chalk.cyan(opensslPath2)
249
- );
250
- return opensslPath2;
251
- }
252
- warningLog(chalk.yellow("System OpenSSL found but version not accepted: ") + version);
253
- return void 0;
254
- } catch (_err) {
255
- return void 0;
256
- }
257
- }
258
- function win32or64() {
259
- if (process.env.PROCESSOR_ARCHITECTURE === "x86" && process.env.PROCESSOR_ARCHITEW6432) {
260
- return 64;
261
- }
262
- if (process.env.PROCESSOR_ARCHITECTURE === "AMD64") {
263
- return 64;
264
- }
265
- if (process.env.CURRENT_CPU === "x64") {
266
- return 64;
267
- }
268
- return 32;
269
- }
270
- async function download_openssl() {
271
- 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";
272
- const outputFilename = path.join(downloadFolder, path.basename(url));
273
- warningLog(`downloading ${chalk.yellow(url)} to ${outputFilename}`);
274
- if (fs.existsSync(outputFilename)) {
275
- return { downloadedFile: outputFilename };
276
- }
277
- const response = await fetch(url, { redirect: "follow" });
278
- if (!response.ok || !response.body) {
279
- throw new Error(`Failed to download OpenSSL from ${url}: ${response.status} ${response.statusText}`);
280
- }
281
- const contentLength = parseInt(response.headers.get("content-length") || "0", 10);
282
- let downloaded = 0;
283
- let lastPercent = -1;
284
- const fileStream = fs.createWriteStream(outputFilename);
285
- const body = response.body;
286
- body.on("data", (chunk) => {
287
- downloaded += chunk.length;
288
- if (contentLength > 0) {
289
- const percent = Math.floor(downloaded / contentLength * 100);
290
- if (percent !== lastPercent && percent % 10 === 0) {
291
- lastPercent = percent;
292
- warningLog(` download progress: ${percent}%`);
293
- }
294
- }
295
- });
296
- await pipeline(body, fileStream);
297
- const stat = fs.statSync(outputFilename);
298
- if (stat.size === 0) {
299
- fs.unlinkSync(outputFilename);
300
- throw new Error(`Downloaded file is empty: ${outputFilename}`);
301
- }
302
- warningLog(chalk.green("Download complete: ") + `${stat.size} bytes`);
303
- return { downloadedFile: outputFilename };
304
- }
305
- async function unzip_openssl(zipFilename) {
306
- const opensslFolder2 = get_openssl_folder_win32();
307
- const zipFile = await new Promise((resolve, reject) => {
308
- yauzl.open(zipFilename, { lazyEntries: true }, (err, zipfile) => {
309
- if (err) {
310
- reject(err);
311
- } else {
312
- if (!zipfile) {
313
- reject(new Error("zipfile is null"));
314
- } else {
315
- resolve(zipfile);
316
- }
317
- }
318
- });
319
- });
320
- zipFile.readEntry();
321
- await new Promise((resolve, reject) => {
322
- zipFile.on("end", (err) => {
323
- setImmediate(() => {
324
- if (doDebug2) {
325
- warningLog("unzip done");
326
- }
327
- if (err) {
328
- reject(err);
329
- } else {
330
- resolve();
331
- }
332
- });
333
- });
334
- zipFile.on("entry", (entry) => {
335
- zipFile.openReadStream(entry, (err, readStream) => {
336
- if (err) {
337
- return reject(err);
338
- }
339
- const file = path.join(opensslFolder2, entry.fileName);
340
- if (doDebug2) {
341
- warningLog(" unzipping :", file);
342
- }
343
- const writeStream = fs.createWriteStream(file, "binary");
344
- readStream?.pipe(writeStream);
345
- writeStream.on("close", () => {
346
- zipFile.readEntry();
347
- });
348
- });
349
- });
350
- });
351
- }
352
- const systemOpenssl = await find_system_openssl_win32();
353
- if (systemOpenssl) {
354
- return systemOpenssl;
355
- }
356
- const opensslFolder = get_openssl_folder_win32();
357
- const opensslExecPath = get_openssl_exec_path_win32();
358
- if (!fs.existsSync(opensslFolder)) {
359
- if (doDebug2) {
360
- warningLog("creating openssl_folder", opensslFolder);
361
- }
362
- fs.mkdirSync(opensslFolder);
363
- }
364
- const { opensslOk, version: _version } = await check_openssl_win32();
365
- if (!opensslOk) {
366
- warningLog(chalk.yellow("openssl seems to be missing and need to be installed"));
367
- const { downloadedFile } = await download_openssl();
368
- if (doDebug2) {
369
- warningLog("deflating ", chalk.yellow(downloadedFile));
370
- }
371
- await unzip_openssl(downloadedFile);
372
- const opensslExists = !!fs.existsSync(opensslExecPath);
373
- if (doDebug2) {
374
- warningLog("verifying ", opensslExists, opensslExists ? chalk.green("OK ") : chalk.red(" Error"), opensslExecPath);
375
- }
376
- const _opensslExecPath2 = await check_openssl_win32();
377
- return opensslExecPath;
378
- } else {
379
- if (doDebug2) {
380
- warningLog(chalk.green("openssl is already installed and have the expected version."));
381
- }
382
- return opensslExecPath;
383
- }
384
- }
385
- async function install_prerequisite() {
386
- if (process.platform !== "win32") {
387
- return await check_system_openssl_version();
388
- } else {
389
- return await install_and_check_win32_openssl_version();
390
- }
391
- }
392
- async function get_openssl_exec_path() {
393
- if (process.platform === "win32") {
394
- const opensslExecPath = await install_prerequisite();
395
- if (!fs.existsSync(opensslExecPath)) {
396
- throw new Error(`internal error cannot find ${opensslExecPath}`);
397
- }
398
- return opensslExecPath;
399
- } else {
400
- return "openssl";
401
- }
402
- }
403
-
404
- // packages/node-opcua-pki/lib/toolbox/with_openssl/execute_openssl.ts
405
- var opensslPath;
406
- var n = makePath;
407
- var PASSIN_ENV_VAR = "NODE_OPCUA_PKI_OPENSSL_PASSIN";
408
- function passinArg(passphrase = "") {
409
- return { args: ["-passin", `env:${PASSIN_ENV_VAR}`], env: { [PASSIN_ENV_VAR]: passphrase } };
410
- }
411
- function renderForDisplay(file, args) {
412
- return [file, ...args].map((a) => a === "" || /[\s"'`$\\]/.test(a) ? JSON.stringify(a) : a).join(" ");
413
- }
414
- function hasUnsafeArgChar(value) {
415
- for (const ch of value) {
416
- const code = ch.codePointAt(0);
417
- if (code < 32 || code === 127) {
418
- return true;
419
- }
420
- if (code < 128) {
421
- continue;
422
- }
423
- if (/[\p{L}\p{N}\p{M}]/u.test(ch)) {
424
- continue;
425
- }
426
- return true;
427
- }
428
- return false;
429
- }
430
- function checkArgs(file, args) {
431
- for (const value of [file, ...args]) {
432
- if (typeof value === "string" && hasUnsafeArgChar(value)) {
433
- throw new Error(`execute_openssl: refusing argument with an unexpected character: ${JSON.stringify(value)}`);
434
- }
435
- }
436
- }
437
- async function execute2(file, args, options) {
438
- const from = new Error();
439
- checkArgs(file, args);
440
- options.cwd = options.cwd || process.cwd();
441
- if (!g_config.silent) {
442
- warningLog(chalk2.cyan(" CWD "), options.cwd);
443
- }
444
- const outputs = [];
445
- const errorOutputs = [];
446
- const display2 = renderForDisplay(file, args);
447
- return await new Promise((resolve, reject) => {
448
- const child = child_process2.spawn(file, [...args], {
449
- cwd: options.cwd,
450
- windowsHide: true,
451
- env: buildChildEnv(options.env),
452
- stdio: ["ignore", "pipe", "pipe"]
453
- });
454
- const fail = (message) => {
455
- if (!options.hideErrorMessage && !g_config.silent) {
456
- const fence = "###########################################";
457
- console.error(chalk2.bgWhiteBright.redBright(`${fence} OPENSSL ERROR ${fence}`));
458
- console.error(chalk2.bgWhiteBright.redBright(`CWD = ${options.cwd}`));
459
- console.error(chalk2.bgWhiteBright.redBright(message));
460
- console.error(chalk2.bgWhiteBright.redBright(`${fence} OPENSSL ERROR ${fence}`));
461
- console.error(from.stack);
462
- }
463
- reject(new Error(message));
464
- };
465
- child.on("error", (err) => fail(`Command failed: ${display2}
466
- ${err.message}`));
467
- child.on("close", (code, signal) => {
468
- if (code === 0) {
469
- resolve(outputs.join(""));
470
- return;
471
- }
472
- const why = signal ? `signal ${signal}` : `exit code ${code}`;
473
- fail(`Command failed: ${display2}
474
- ${errorOutputs.join("")}(${why})`);
475
- });
476
- if (child.stdout) {
477
- const stream2 = byline2(child.stdout);
478
- stream2.on("data", (line) => {
479
- outputs.push(`${line}
480
- `);
481
- });
482
- if (!g_config.silent) {
483
- stream2.on("data", (line) => {
484
- line = line.toString();
485
- if (doDebug) {
486
- process.stdout.write(`${chalk2.white(" stdout ") + chalk2.whiteBright(line)}
487
- `);
488
- }
489
- });
490
- }
491
- }
492
- if (child.stderr) {
493
- const stream1 = byline2(child.stderr);
494
- stream1.on("data", (line) => {
495
- line = line.toString();
496
- errorOutputs.push(`${line}
497
- `);
498
- if (!g_config.silent && displayError) {
499
- process.stdout.write(`${chalk2.white(" stderr ") + chalk2.red(line)}
500
- `);
501
- }
502
- });
503
- }
504
- });
505
- }
506
- async function find_openssl() {
507
- return await get_openssl_exec_path();
508
- }
509
- async function ensure_openssl_installed() {
510
- if (!opensslPath) {
511
- opensslPath = await find_openssl();
512
- const outputs = await execute_openssl(["version"], { cwd: "." });
513
- g_config.opensslVersion = outputs.trim();
514
- if (doDebug) {
515
- warningLog("OpenSSL version : ", g_config.opensslVersion);
516
- }
517
- }
518
- }
519
- async function execute_openssl_no_failure(args, options) {
520
- options = options || {};
521
- options.hideErrorMessage = true;
522
- try {
523
- return await execute_openssl(args, options);
524
- } catch (err) {
525
- debugLog(" (ignored error = ERROR : )", err.message);
526
- }
527
- }
528
- function getTempFolder() {
529
- return os2.tmpdir();
530
- }
531
- async function execute_openssl(args, options) {
532
- debugLog("execute_openssl", args, redactEnvForLog(options));
533
- const empty_config_file = n(getTempFolder(), "empty_config.cnf");
534
- if (!fs2.existsSync(empty_config_file)) {
535
- await fs2.promises.writeFile(empty_config_file, "# empty config file");
536
- }
537
- options = options || {};
538
- options.openssl_conf = options.openssl_conf || empty_config_file;
539
- assert(options.openssl_conf);
540
- setEnv("OPENSSL_CONF", options.openssl_conf);
541
- if (!g_config.silent) {
542
- warningLog(chalk2.cyan(" OPENSSL_CONF"), process.env.OPENSSL_CONF);
543
- warningLog(chalk2.cyan(" RANDFILE "), process.env.RANDFILE);
544
- warningLog(chalk2.cyan(" CMD "), chalk2.cyanBright(renderForDisplay("openssl", args)));
545
- }
546
- await ensure_openssl_installed();
547
- return await execute2(opensslPath, args, options);
548
- }
549
-
550
- // packages/node-opcua-pki/lib/toolbox/with_openssl/toolbox.ts
551
- import assert2 from "assert";
552
- import fs3 from "fs";
553
- import path2 from "path";
554
- function openssl_require2DigitYearInDate() {
555
- if (!g_config.opensslVersion) {
556
- throw new Error(
557
- "openssl_require2DigitYearInDate : openssl version is not known: please call ensure_openssl_installed()"
558
- );
559
- }
560
- return g_config.opensslVersion.match(/OpenSSL 0\.9/);
561
- }
562
- g_config.opensslVersion = "";
563
- var _counter = 0;
564
- function stripConditionalBlocks(template, envOverrides) {
565
- return template.replace(/\{\{#([A-Z_][A-Z0-9_]*)\}\}([\s\S]*?)\{\{\/\1\}\}\r?\n?/g, (_match, key, content) => {
566
- const keep = envOverrides && Object.prototype.hasOwnProperty.call(envOverrides, key) ? envOverrides[key] !== "" : hasEnv(key) && getEnv(key) !== "";
567
- return keep ? content : "";
568
- });
569
- }
570
- function generateStaticConfig(configPath, options, envOverrides) {
571
- const prePath = options?.cwd || "";
572
- const originalFilename = !path2.isAbsolute(configPath) ? path2.join(prePath, configPath) : configPath;
573
- let staticConfig = fs3.readFileSync(originalFilename, { encoding: "utf8" });
574
- staticConfig = stripConditionalBlocks(staticConfig, envOverrides);
575
- for (const envVar of getEnvironmentVarNames()) {
576
- if (envOverrides && Object.prototype.hasOwnProperty.call(envOverrides, envVar.key)) {
577
- continue;
578
- }
579
- staticConfig = staticConfig.replace(new RegExp(envVar.pattern, "gi"), () => getEnv(envVar.key));
580
- }
581
- if (envOverrides) {
582
- for (const [key, value] of Object.entries(envOverrides)) {
583
- staticConfig = staticConfig.replace(new RegExp(`\\$ENV\\:\\:${key}`, "gi"), () => value);
584
- }
585
- }
586
- const staticConfigPath = `${configPath}.${process.pid}-${_counter++}.tmp`;
587
- const temporaryConfigPath = !path2.isAbsolute(configPath) ? path2.join(prePath, staticConfigPath) : staticConfigPath;
588
- fs3.writeFileSync(temporaryConfigPath, staticConfig);
589
- if (options?.cwd) {
590
- return path2.relative(options.cwd, temporaryConfigPath);
591
- } else {
592
- return temporaryConfigPath;
593
- }
594
- }
595
- async function cleanupStaticConfig(configFile, options) {
596
- await fs3.promises.rm(path2.resolve(options?.cwd ?? "", configFile), { force: true });
597
- }
598
- function x509Date(date) {
599
- date = date || /* @__PURE__ */ new Date();
600
- const Y = date.getUTCFullYear();
601
- const M = date.getUTCMonth() + 1;
602
- const D = date.getUTCDate();
603
- const h = date.getUTCHours();
604
- const m = date.getUTCMinutes();
605
- const s = date.getUTCSeconds();
606
- function w(s2, l) {
607
- return `${s2}`.padStart(l, "0");
608
- }
609
- if (openssl_require2DigitYearInDate()) {
610
- return `${w(Y, 2) + w(M, 2) + w(D, 2) + w(h, 2) + w(m, 2) + w(s, 2)}Z`;
611
- } else {
612
- return `${w(Y, 4) + w(M, 2) + w(D, 2) + w(h, 2) + w(m, 2) + w(s, 2)}Z`;
613
- }
614
- }
615
-
616
- // packages/node-opcua-pki/lib/ca/backends/openssl_ca_backend.ts
617
- var n2 = makePath;
618
- function caAltName(ca) {
619
- return `URI:urn:${ca.subject.commonName || "NodeOPCUA-CA"}`;
620
- }
621
- function caConfigEnvOverrides(ca, altName = caAltName(ca)) {
622
- const aiaLegs = [];
623
- if (ca.ocspResponderUrl) {
624
- aiaLegs.push(`OCSP;URI:${ca.ocspResponderUrl}`);
625
- }
626
- if (ca.caIssuersUrl) {
627
- aiaLegs.push(`caIssuers;URI:${ca.caIssuersUrl}`);
628
- }
629
- return {
630
- ALTNAME: altName,
631
- CDP_URL: ca.crlDistributionUrl ?? "",
632
- AIA_VALUE: aiaLegs.join(",")
633
- };
634
- }
635
- var OpenSslCaBackend = class {
636
- /** This backend is the `openssl` executable, so it has to be there. */
637
- /**
638
- * `-passin env:` argv and env for an openssl call that has to load this
639
- * CA's key. Built here rather than on the CA: the passphrase is the
640
- * CA's business, but expressing it as an openssl argument is this
641
- * backend's, and the core has no reason to know the flag exists.
642
- */
643
- async #passin(ca) {
644
- return passinArg(await ca._privateKeyPassphrase());
645
- }
646
- /** The openssl CLI loads its key from a file, so it cannot call out to an HSM. */
647
- supportsExternalSigner = false;
648
- async preflight() {
649
- await ensure_openssl_installed();
650
- }
651
- /**
652
- * Render `conf/caconfig.cnf` once with explicit overrides, run `fn`
653
- * with the rendered path, and always remove the temp file. Structural
654
- * guarantee that (a) every render carries the three required env
655
- * values and (b) no rendered `<name>.<pid>-<n>.tmp` file is ever left
656
- * behind — each was previously a per-method discipline.
657
- */
658
- async #withConfig(ca, altName, fn) {
659
- const caRootDir = path3.resolve(ca.rootDir);
660
- const options = { cwd: caRootDir };
661
- const configFile = generateStaticConfig("conf/caconfig.cnf", options, caConfigEnvOverrides(ca, altName));
662
- try {
663
- return await fn(configFile, options);
664
- } finally {
665
- await cleanupStaticConfig(configFile, options);
666
- }
667
- }
668
- async #generateCaCsrWith(ca, configFile, options, privateKeyFile, csrFile) {
669
- const passin = await this.#passin(ca);
670
- displayTitle("Generate a certificate request for the CA key");
671
- await execute_openssl(
672
- [
673
- "req",
674
- "-new",
675
- "-sha256",
676
- "-extensions",
677
- "v3_ca_req",
678
- "-config",
679
- n2(configFile),
680
- "-key",
681
- n2(privateKeyFile),
682
- "-out",
683
- n2(csrFile),
684
- "-subj",
685
- ca.subject.toString(),
686
- ...passin.args
687
- ],
688
- { ...options, env: passin.env }
689
- );
690
- }
691
- /**
692
- * `openssl ca -gencrl` signs the CRL with the CA key it finds through the
693
- * config file (`private_key = $dir/private/cakey.pem`), so it needs the
694
- * CA passphrase like every other `openssl ca` invocation.
695
- */
696
- async #regenerateCrlWith(ca, configFile, options) {
697
- const passin = await this.#passin(ca);
698
- displaySubtitle("regenerate CRL (Certificate Revocation List)");
699
- await execute_openssl(["ca", "-gencrl", "-config", n2(configFile), "-out", "crl/revocation_list.crl", ...passin.args], {
700
- ...options,
701
- env: passin.env
702
- });
703
- await execute_openssl(
704
- ["crl", "-in", "crl/revocation_list.crl", "-out", "crl/revocation_list.der", "-outform", "der"],
705
- options
706
- );
707
- displaySubtitle("Display (Certificate Revocation List)");
708
- await execute_openssl(["crl", "-in", n2(ca.revocationList), "-text", "-noout"], options);
709
- }
710
- async generateCaCsr(ca, _caRootDir, privateKeyFile, csrFile) {
711
- await this.#withConfig(
712
- ca,
713
- caAltName(ca),
714
- (configFile, options) => this.#generateCaCsrWith(ca, configFile, options, privateKeyFile, csrFile)
715
- );
716
- }
717
- async bootstrap(ca) {
718
- const caRootDir = path3.resolve(ca.rootDir);
719
- const privateKeyFilename = path3.join(caRootDir, "private/cakey.pem");
720
- const csrFilename = path3.join(caRootDir, "private/cakey.csr");
721
- await this.#withConfig(ca, caAltName(ca), async (configFile, options) => {
722
- await this.#generateCaCsrWith(ca, configFile, options, privateKeyFilename, csrFilename);
723
- const issuerCA = ca._issuerCA;
724
- if (issuerCA) {
725
- displayTitle("Generate CA Certificate (signed by issuer CA)");
726
- const issuerCert = path3.resolve(issuerCA.caCertificate);
727
- const issuerKey = path3.resolve(issuerCA.rootDir, "private/cakey.pem");
728
- const issuerSerial = path3.resolve(issuerCA.rootDir, "serial");
729
- const issuerPassin = await this.#passin(issuerCA);
730
- const signWithIssuer = async () => {
731
- await execute_openssl(
732
- [
733
- "x509",
734
- "-sha256",
735
- "-req",
736
- "-days",
737
- "3650",
738
- "-extensions",
739
- "v3_ca",
740
- "-extfile",
741
- n2(configFile),
742
- "-in",
743
- "private/cakey.csr",
744
- "-CA",
745
- n2(issuerCert),
746
- "-CAkey",
747
- n2(issuerKey),
748
- "-CAserial",
749
- n2(issuerSerial),
750
- "-out",
751
- "public/cacert.pem",
752
- ...issuerPassin.args
753
- ],
754
- { ...options, env: issuerPassin.env }
755
- );
756
- };
757
- if (path3.resolve(issuerCA.rootDir) === caRootDir) {
758
- await signWithIssuer();
759
- } else {
760
- await issuerCA._withCaDirectoryLock(signWithIssuer);
761
- }
762
- } else {
763
- displayTitle("Generate CA Certificate (self-signed)");
764
- const passin = await this.#passin(ca);
765
- await execute_openssl(
766
- [
767
- "x509",
768
- "-sha256",
769
- "-req",
770
- "-days",
771
- "3650",
772
- "-extensions",
773
- "v3_ca",
774
- "-extfile",
775
- n2(configFile),
776
- "-in",
777
- "private/cakey.csr",
778
- "-signkey",
779
- n2(privateKeyFilename),
780
- "-out",
781
- "public/cacert.pem",
782
- ...passin.args
783
- ],
784
- { ...options, env: passin.env }
785
- );
786
- }
787
- displaySubtitle("generate initial CRL (Certificate Revocation List)");
788
- await this.#regenerateCrlWith(ca, configFile, options);
789
- });
790
- displayTitle("Create Certificate Authority (CA) ---> DONE");
791
- }
792
- async regenerateCrl(ca) {
793
- await this.#withConfig(ca, caAltName(ca), (configFile, options) => this.#regenerateCrlWith(ca, configFile, options));
794
- }
795
- async signSubordinateCsr(ca, csrFile, certFile, validityDays) {
796
- await this.#withConfig(ca, caAltName(ca), async (configFile, options) => {
797
- const caRootDir = options.cwd;
798
- const passin = await this.#passin(ca);
799
- await execute_openssl(
800
- [
801
- "x509",
802
- "-sha256",
803
- "-req",
804
- "-days",
805
- String(validityDays),
806
- "-extensions",
807
- "v3_ca",
808
- "-extfile",
809
- n2(configFile),
810
- "-in",
811
- n2(csrFile),
812
- "-CA",
813
- n2(ca.caCertificate),
814
- "-CAkey",
815
- n2(path3.join(caRootDir, "private/cakey.pem")),
816
- "-CAserial",
817
- n2(path3.join(caRootDir, "serial")),
818
- "-out",
819
- n2(certFile),
820
- ...passin.args
821
- ],
822
- { ...options, env: passin.env }
823
- );
824
- });
825
- }
826
- async signEndEntityCsr(ca, certificate, csr, params1, sanOverride) {
827
- await this.#withConfig(ca, buildSubjectAltNameString(sanOverride), async (configFile, options) => {
828
- displaySubtitle("- then we ask the authority to sign the certificate signing request");
829
- const passin = await this.#passin(ca);
830
- await execute_openssl(
831
- [
832
- "ca",
833
- "-config",
834
- configFile,
835
- "-startdate",
836
- x509Date(params1.startDate),
837
- "-enddate",
838
- x509Date(params1.endDate),
839
- "-batch",
840
- "-out",
841
- n2(certificate),
842
- "-in",
843
- n2(csr),
844
- ...passin.args
845
- ],
846
- { ...options, env: passin.env }
847
- );
848
- displaySubtitle("- dump the certificate for a check");
849
- await execute_openssl(["x509", "-in", n2(certificate), "-dates", "-fingerprint", "-purpose", "-noout"], options);
850
- });
851
- }
852
- async revoke(ca, certificate, reason) {
853
- setEnv("RANDFILE", path3.join(ca.rootDir, "random.rnd"));
854
- await this.#withConfig(ca, caAltName(ca), async (configFile, options) => {
855
- displaySubtitle("Revoke certificate");
856
- const passin = await this.#passin(ca);
857
- await execute_openssl_no_failure(
858
- ["ca", "-verbose", "-config", n2(configFile), "-revoke", certificate, "-crl_reason", reason, ...passin.args],
859
- { ...options, env: passin.env }
860
- );
861
- await this.#regenerateCrlWith(ca, configFile, options);
862
- displaySubtitle("Verify that certificate is revoked");
863
- await execute_openssl_no_failure(
864
- [
865
- "verify",
866
- "-verbose",
867
- "-CRLfile",
868
- n2(ca.revocationList),
869
- "-CAfile",
870
- n2(ca.caCertificate),
871
- "-crl_check",
872
- n2(certificate)
873
- ],
874
- options
875
- );
876
- displaySubtitle("Produce CRL in DER form ");
877
- await execute_openssl(
878
- ["crl", "-in", n2(ca.revocationList), "-out", "crl/revocation_list.der", "-outform", "der"],
879
- options
880
- );
881
- displaySubtitle("Produce CRL in PEM form ");
882
- await execute_openssl(
883
- ["crl", "-in", n2(ca.revocationList), "-out", "crl/revocation_list.pem", "-outform", "pem", "-text"],
884
- options
885
- );
886
- });
887
- }
888
- async createSelfSignedCertificate(ca, certificateFile, privateKeyFile, params) {
889
- const envOverrides = caConfigEnvOverrides(ca, buildSubjectAltNameString(params));
890
- const configFile = generateStaticConfig(ca.configFile, { cwd: ca.rootDir }, envOverrides);
891
- const options = {
892
- cwd: ca.rootDir,
893
- openssl_conf: makePath(configFile)
894
- };
895
- try {
896
- const subject = params.subject ? new Subject2(params.subject).toString() : "";
897
- const subjectOptions = subject && subject.length > 1 ? ["-subj", subject] : [];
898
- const csrFile = `${certificateFile}_csr`;
899
- const passin = await this.#passin(ca);
900
- displaySubtitle("- the certificate signing request");
901
- await execute_openssl(
902
- [
903
- "req",
904
- "-new",
905
- "-sha256",
906
- ...subjectOptions,
907
- "-batch",
908
- "-key",
909
- n2(privateKeyFile),
910
- "-out",
911
- n2(csrFile),
912
- ...passin.args
913
- ],
914
- { ...options, env: passin.env }
915
- );
916
- displaySubtitle("- creating the self-signed certificate");
917
- await execute_openssl(
918
- [
919
- "ca",
920
- "-selfsign",
921
- "-keyfile",
922
- n2(privateKeyFile),
923
- "-startdate",
924
- x509Date(params.startDate),
925
- "-enddate",
926
- x509Date(params.endDate),
927
- "-batch",
928
- "-out",
929
- n2(certificateFile),
930
- "-in",
931
- n2(csrFile),
932
- ...passin.args
933
- ],
934
- { ...options, env: passin.env }
935
- );
936
- displaySubtitle("- dump the certificate for a check");
937
- await execute_openssl(["x509", "-in", n2(certificateFile), "-dates", "-fingerprint", "-purpose", "-noout"], {});
938
- displaySubtitle("- verify self-signed certificate");
939
- await execute_openssl_no_failure(["verify", "-verbose", "-CAfile", n2(certificateFile), n2(certificateFile)], options);
940
- await fs4.promises.unlink(csrFile);
941
- } finally {
942
- await cleanupStaticConfig(configFile, { cwd: ca.rootDir });
943
- }
944
- }
945
- };
946
-
947
- // packages/node-opcua-pki/lib/ca/certificate_authority.ts
948
- var CertificateAuthority = class extends CertificateAuthorityCore {
949
- constructor(options) {
950
- if (options.signer && options.backend === "openssl") {
951
- throw new Error(
952
- "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."
953
- );
954
- }
955
- const backend = options.signer || options.backend === "native" ? new NativeCaBackend() : new OpenSslCaBackend();
956
- super({ ...options, backend });
957
- }
958
- /**
959
- * @internal `-passin env:` argv + env for an openssl call that loads
960
- * this CA's key (always emitted, empty when none).
961
- *
962
- * The openssl backend builds its own now; this remains so that external
963
- * code calling it keeps working, and lives here rather than on the core
964
- * because the flag means nothing to a backend that spawns nothing.
965
- */
966
- async _opensslPassin() {
967
- return passinArg(await this._privateKeyPassphrase());
968
- }
969
- /**
970
- * @internal
971
- * Legacy shim: publish the `CDP_URL` / `AIA_VALUE` config substitution
972
- * values to the shared env registry, or unset them so the matching
973
- * `{{#KEY}}...{{/KEY}}` blocks are stripped. Nothing in this package
974
- * reads the registry any more - every openssl config render receives
975
- * these values explicitly, from the same {@link caConfigEnvOverrides}
976
- * builder this delegates to, so the two cannot drift - kept only for
977
- * external code that renders openssl config templates against the
978
- * registry directly.
979
- *
980
- * It lives on this class rather than the core because it is meaningful
981
- * only to the openssl backend.
982
- */
983
- _wireRevocationEnvVars() {
984
- const overrides = caConfigEnvOverrides(this);
985
- if (overrides.CDP_URL) {
986
- setEnv("CDP_URL", overrides.CDP_URL);
987
- } else {
988
- unsetEnv("CDP_URL");
989
- }
990
- if (overrides.AIA_VALUE) {
991
- setEnv("AIA_VALUE", overrides.AIA_VALUE);
992
- } else {
993
- unsetEnv("AIA_VALUE");
994
- }
995
- }
996
- };
997
-
998
- // packages/node-opcua-pki/lib/pki/certificate_manager.ts
999
- import { EventEmitter } from "events";
1000
- import fs7 from "fs";
1001
- import path4 from "path";
1002
- import { drainPendingLocks, withLock } from "@ster5/global-mutex";
1003
- import chalk3 from "chalk";
1004
- import chokidar from "chokidar";
1005
- import {
1006
- caSignerFromKeyOperations,
1007
- exploreCertificate,
1008
- exploreCertificateInfo,
1009
- exploreCertificateRevocationList,
1010
- generatePrivateKeyFile,
1011
- keyOperationsFromPrivateKey,
1012
- makeSHA1Thumbprint,
1013
- PrivateKeyUnavailableError,
1014
- readCertificateChain,
1015
- readCertificateChainAsync,
1016
- readCertificateRevocationList,
1017
- readPrivateKey,
1018
- split_der,
1019
- toPem,
1020
- verifyCertificateSignature,
1021
- writePrivateKeyFile
1022
- } from "node-opcua-crypto";
1023
-
1024
- // packages/node-opcua-pki/lib/toolbox/without_openssl/create_certificate_signing_request.ts
1025
- import assert3 from "assert";
1026
- import fs5 from "fs";
1027
- import { coercePrivateKeyPem, createCertificateSigningRequest, pemToPrivateKey, Subject as Subject3 } from "node-opcua-crypto";
1028
- async function createCertificateSigningRequestAsync(certificateSigningRequestFilename, params) {
1029
- assert3(params);
1030
- assert3(params.rootDir);
1031
- assert3(params.configFile);
1032
- assert3(params.privateKey);
1033
- if (typeof params.privateKey === "string") {
1034
- assert3(fs5.existsSync(params.privateKey), `Private key must exist${params.privateKey}`);
1035
- }
1036
- assert3(fs5.existsSync(params.rootDir), "RootDir key must exist");
1037
- assert3(typeof certificateSigningRequestFilename === "string");
1038
- const subject = params.subject ? new Subject3(params.subject).toString() : void 0;
1039
- displaySubtitle("- Creating a Certificate Signing Request with subtile");
1040
- const privateKey = isOpaqueSigner(params.privateKey) ? params.privateKey : await pemToPrivateKey(
1041
- typeof params.privateKey === "string" ? await fs5.promises.readFile(params.privateKey, "utf-8") : coercePrivateKeyPem(params.privateKey)
1042
- );
1043
- const { csr } = await createCertificateSigningRequest({
1044
- privateKey,
1045
- dns: params.dns,
1046
- ip: params.ip,
1047
- subject,
1048
- applicationUri: params.applicationUri,
1049
- purpose: params.purpose
1050
- });
1051
- await fs5.promises.writeFile(certificateSigningRequestFilename, csr, "utf-8");
1052
- display(
1053
- `- privateKey ${typeof params.privateKey === "string" ? params.privateKey : isOpaqueSigner(params.privateKey) ? "<opaque-signer>" : "<in-memory>"}`
1054
- );
1055
- display(`- certificateSigningRequestFilename ${certificateSigningRequestFilename}`);
1056
- }
1057
-
1058
- // packages/node-opcua-pki/lib/toolbox/without_openssl/create_self_signed_certificate.ts
1059
- import assert4 from "assert";
1060
- import fs6 from "fs";
1061
- import {
1062
- CertificatePurpose,
1063
- coercePrivateKeyPem as coercePrivateKeyPem2,
1064
- createSelfSignedCertificate as createSelfSignedCertificate1,
1065
- pemToPrivateKey as pemToPrivateKey2,
1066
- Subject as Subject4
1067
- } from "node-opcua-crypto";
1068
- async function createSelfSignedCertificateAsync(certificate, params) {
1069
- params.purpose = params.purpose || CertificatePurpose.ForApplication;
1070
- assert4(params.purpose, "Please provide a Certificate Purpose");
1071
- assert4(fs6.existsSync(params.configFile));
1072
- assert4(fs6.existsSync(params.rootDir));
1073
- if (typeof params.privateKey === "string") {
1074
- assert4(fs6.existsSync(params.privateKey));
1075
- }
1076
- if (!params.subject) {
1077
- throw Error("Missing subject");
1078
- }
1079
- assert4(typeof params.applicationUri === "string");
1080
- assert4(Array.isArray(params.dns));
1081
- adjustDate(params);
1082
- assert4(Object.prototype.hasOwnProperty.call(params, "validity"));
1083
- let subject = new Subject4(params.subject);
1084
- subject = subject.toString();
1085
- const purpose = params.purpose;
1086
- displayTitle("Generate a certificate request");
1087
- const privateKey = isOpaqueSigner(params.privateKey) ? params.privateKey : await pemToPrivateKey2(
1088
- typeof params.privateKey === "string" ? await fs6.promises.readFile(params.privateKey, "utf-8") : coercePrivateKeyPem2(params.privateKey)
1089
- );
1090
- const { cert } = await createSelfSignedCertificate1({
1091
- privateKey,
1092
- notBefore: params.startDate,
1093
- notAfter: params.endDate,
1094
- validity: params.validity,
1095
- dns: params.dns,
1096
- ip: params.ip,
1097
- subject,
1098
- applicationUri: params.applicationUri,
1099
- purpose
1100
- });
1101
- await fs6.promises.writeFile(certificate, cert, "utf-8");
1102
- }
1103
- async function createSelfSignedCertificate(certificate, params) {
1104
- await createSelfSignedCertificateAsync(certificate, params);
1105
- }
1106
-
1107
- // packages/node-opcua-pki/lib/pki/templates/simple_config_template.cnf.ts
1108
- var config = '##################################################################################################\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';
1109
- var simple_config_template_cnf_default = config;
1110
-
1111
- // packages/node-opcua-pki/lib/pki/certificate_manager.ts
1112
- var configurationFileSimpleTemplate = simple_config_template_cnf_default;
1113
- var fsWriteFile = fs7.promises.writeFile;
1114
- function getOrComputeInfo(entry) {
1115
- if (!entry.info) {
1116
- entry.info = exploreCertificate(entry.certificate);
1117
- }
1118
- return entry.info;
1119
- }
1120
- var VerificationStatus = /* @__PURE__ */ ((VerificationStatus2) => {
1121
- VerificationStatus2["BadCertificateInvalid"] = "BadCertificateInvalid";
1122
- VerificationStatus2["BadSecurityChecksFailed"] = "BadSecurityChecksFailed";
1123
- VerificationStatus2["BadCertificatePolicyCheckFailed"] = "BadCertificatePolicyCheckFailed";
1124
- VerificationStatus2["BadCertificateTimeInvalid"] = "BadCertificateTimeInvalid";
1125
- VerificationStatus2["BadCertificateIssuerTimeInvalid"] = "BadCertificateIssuerTimeInvalid";
1126
- VerificationStatus2["BadCertificateHostNameInvalid"] = "BadCertificateHostNameInvalid";
1127
- VerificationStatus2["BadCertificateUriInvalid"] = "BadCertificateUriInvalid";
1128
- VerificationStatus2["BadCertificateUseNotAllowed"] = "BadCertificateUseNotAllowed";
1129
- VerificationStatus2["BadCertificateIssuerUseNotAllowed"] = "BadCertificateIssuerUseNotAllowed";
1130
- VerificationStatus2["BadCertificateUntrusted"] = "BadCertificateUntrusted";
1131
- VerificationStatus2["BadCertificateRevocationUnknown"] = "BadCertificateRevocationUnknown";
1132
- VerificationStatus2["BadCertificateIssuerRevocationUnknown"] = "BadCertificateIssuerRevocationUnknown";
1133
- VerificationStatus2["BadCertificateRevoked"] = "BadCertificateRevoked";
1134
- VerificationStatus2["BadCertificateIssuerRevoked"] = "BadCertificateIssuerRevoked";
1135
- VerificationStatus2["BadCertificateChainIncomplete"] = "BadCertificateChainIncomplete";
1136
- VerificationStatus2["Good"] = "Good";
1137
- return VerificationStatus2;
1138
- })(VerificationStatus || {});
1139
- function coerceCertificateChain(certificate) {
1140
- if (Array.isArray(certificate)) {
1141
- if (certificate.length === 0) return [];
1142
- return certificate.reduce((acc, cert) => {
1143
- return acc.concat(split_der(cert));
1144
- }, []);
1145
- }
1146
- return split_der(certificate);
1147
- }
1148
- function makeFingerprint(certificate) {
1149
- const chain = coerceCertificateChain(certificate);
1150
- return makeSHA1Thumbprint(chain[0]).toString("hex");
1151
- }
1152
- function short(stringToShorten) {
1153
- return stringToShorten.substring(0, 10);
1154
- }
1155
- var disallowedInLabel = /[^A-Za-z0-9._ -]/g;
1156
- function toFilenameLabel(commonName) {
1157
- const label = (commonName || "").normalize("NFKC").replace(disallowedInLabel, "_").replace(/[^\x20-\x7E]/g, "").slice(0, 64).trim();
1158
- return label || "certificate";
1159
- }
1160
- function buildIdealCertificateName(certificate) {
1161
- const chain = coerceCertificateChain(certificate);
1162
- const fingerprint = makeFingerprint(chain);
1163
- try {
1164
- const commonName = exploreCertificate(chain[0]).tbsCertificate.subject.commonName || "";
1165
- const sanitizedCommonName = toFilenameLabel(commonName);
1166
- return `${sanitizedCommonName}[${fingerprint}]`;
1167
- } catch (_err) {
1168
- return `invalid_certificate_[${fingerprint}]`;
1169
- }
1170
- }
1171
- function safeStoreJoin(folder, filename) {
1172
- const full = path4.resolve(folder, filename);
1173
- const base = path4.resolve(folder);
1174
- if (full !== base && !full.startsWith(base + path4.sep)) {
1175
- warningLog("refusing to write certificate outside its store folder:", filename);
1176
- throw new Error(`certificate filename escapes its store folder: ${filename}`);
1177
- }
1178
- return full;
1179
- }
1180
- function findMatchingIssuerKey(entries, wantedIssuerKey) {
1181
- return entries.filter((entry) => {
1182
- const info = getOrComputeInfo(entry);
1183
- return info.tbsCertificate.extensions && info.tbsCertificate.extensions.subjectKeyIdentifier === wantedIssuerKey;
1184
- });
1185
- }
1186
- function isSelfSigned2(info) {
1187
- return info.tbsCertificate.extensions?.subjectKeyIdentifier === info.tbsCertificate.extensions?.authorityKeyIdentifier?.keyIdentifier;
1188
- }
1189
- function isSelfSigned3(certificate) {
1190
- const info = exploreCertificate(certificate);
1191
- return isSelfSigned2(info);
1192
- }
1193
- function _isIssuerInfo(info) {
1194
- const basicConstraints = info.tbsCertificate.extensions?.basicConstraints;
1195
- if (basicConstraints?.cA) {
1196
- return true;
1197
- }
1198
- const keyUsage = info.tbsCertificate.extensions?.keyUsage;
1199
- if (keyUsage?.keyCertSign) {
1200
- return true;
1201
- }
1202
- return false;
1203
- }
1204
- function isIssuer(certificate) {
1205
- try {
1206
- const info = exploreCertificate(certificate);
1207
- return _isIssuerInfo(info);
1208
- } catch (_err) {
1209
- return false;
1210
- }
1211
- }
1212
- function isIntermediateIssuer(certificate) {
1213
- try {
1214
- const info = exploreCertificate(certificate);
1215
- if (!_isIssuerInfo(info)) {
1216
- return false;
1217
- }
1218
- return !isSelfSigned2(info);
1219
- } catch (_err) {
1220
- return false;
1221
- }
1222
- }
1223
- function isRootIssuer(certificate) {
1224
- try {
1225
- const info = exploreCertificate(certificate);
1226
- if (!_isIssuerInfo(info)) {
1227
- return false;
1228
- }
1229
- return isSelfSigned2(info);
1230
- } catch (_err) {
1231
- return false;
1232
- }
1233
- }
1234
- function findIssuerCertificateInChain(certificate, chain) {
1235
- const coercedCertificate = coerceCertificateChain(certificate);
1236
- const firstCertificate = coercedCertificate[0];
1237
- if (!firstCertificate) {
1238
- return null;
1239
- }
1240
- const certInfo = exploreCertificate(firstCertificate);
1241
- if (isSelfSigned2(certInfo)) {
1242
- return firstCertificate;
1243
- }
1244
- const wantedIssuerKey = certInfo.tbsCertificate.extensions?.authorityKeyIdentifier?.keyIdentifier;
1245
- if (!wantedIssuerKey) {
1246
- debugLog("Certificate has no extension 3");
1247
- return null;
1248
- }
1249
- const coercedChain = coerceCertificateChain(chain);
1250
- const potentialIssuers = coercedChain.filter((c) => {
1251
- const info = exploreCertificate(c);
1252
- return info.tbsCertificate.extensions && info.tbsCertificate.extensions.subjectKeyIdentifier === wantedIssuerKey;
1253
- });
1254
- if (potentialIssuers.length === 1) {
1255
- return potentialIssuers[0];
1256
- }
1257
- if (potentialIssuers.length > 1) {
1258
- debugLog("findIssuerCertificateInChain: certificate is not self-signed but has several issuers");
1259
- return potentialIssuers[0];
1260
- }
1261
- return null;
1262
- }
1263
- var CertificateManagerState = /* @__PURE__ */ ((CertificateManagerState2) => {
1264
- CertificateManagerState2[CertificateManagerState2["Uninitialized"] = 0] = "Uninitialized";
1265
- CertificateManagerState2[CertificateManagerState2["Initializing"] = 1] = "Initializing";
1266
- CertificateManagerState2[CertificateManagerState2["Initialized"] = 2] = "Initialized";
1267
- CertificateManagerState2[CertificateManagerState2["Disposing"] = 3] = "Disposing";
1268
- CertificateManagerState2[CertificateManagerState2["Disposed"] = 4] = "Disposed";
1269
- return CertificateManagerState2;
1270
- })(CertificateManagerState || {});
1271
- var ChainCompletionStatus = /* @__PURE__ */ ((ChainCompletionStatus2) => {
1272
- ChainCompletionStatus2["AlreadyComplete"] = "AlreadyComplete";
1273
- ChainCompletionStatus2["ChainCompleted"] = "ChainCompleted";
1274
- ChainCompletionStatus2["IssuerNotFound"] = "IssuerNotFound";
1275
- ChainCompletionStatus2["EmptyChain"] = "EmptyChain";
1276
- ChainCompletionStatus2["MaxDepthReached"] = "MaxDepthReached";
1277
- return ChainCompletionStatus2;
1278
- })(ChainCompletionStatus || {});
1279
- var CertificateManager = class _CertificateManager extends EventEmitter {
1280
- // ── Global instance registry ─────────────────────────────────
1281
- // Tracks all initialized CertificateManager instances so their
1282
- // file watchers can be closed automatically on process exit,
1283
- // even if the consumer forgets to call dispose().
1284
- static #activeInstances = /* @__PURE__ */ new Set();
1285
- static #cleanupInstalled = false;
1286
- static #exitHandler;
1287
- /**
1288
- * Install a best-effort `exit` hook that closes any watcher
1289
- * still open when the process terminates.
1290
- *
1291
- * **This library never terminates the host process.** No
1292
- * SIGINT/SIGTERM handler is installed: deciding how (and
1293
- * whether) to shut down on a signal is the application's
1294
- * responsibility, and a listener registered here would both
1295
- * pre-empt the application's own graceful shutdown and
1296
- * silently suppress Node's default signal behaviour.
1297
- *
1298
- * Nothing here is load-bearing for process exit. The native
1299
- * `fs.watch` handles are `unref()`'d when the watchers are
1300
- * created (see `#readCertificates`), so an undisposed
1301
- * CertificateManager never keeps the event loop alive. This
1302
- * hook is only tidiness on the way out.
1303
- *
1304
- * `exit` rather than `beforeExit`: `beforeExit` fires when
1305
- * the loop merely drains and the loop can subsequently be
1306
- * resurrected, which would leave a still-in-use instance
1307
- * marked Disposed. `exit` is terminal, synchronous-only and
1308
- * cannot alter the exit code.
1309
- */
1310
- static #installExitCleanup() {
1311
- if (_CertificateManager.#cleanupInstalled) return;
1312
- _CertificateManager.#cleanupInstalled = true;
1313
- const closeDanglingWatchers = () => {
1314
- for (const cm of _CertificateManager.#activeInstances) {
1315
- for (const w of cm.#watchers) {
1316
- try {
1317
- w.close();
1318
- } catch {
1319
- }
1320
- }
1321
- cm.#watchers.splice(0);
1322
- cm.state = 4 /* Disposed */;
1323
- }
1324
- _CertificateManager.#activeInstances.clear();
1325
- };
1326
- _CertificateManager.#exitHandler = closeDanglingWatchers;
1327
- process.on("exit", closeDanglingWatchers);
1328
- }
1329
- /**
1330
- * Remove the `exit` hook once the last instance is disposed,
1331
- * so a library that is initialized and disposed repeatedly
1332
- * does not accumulate process listeners. A later
1333
- * `initialize()` re-arms it.
1334
- */
1335
- static #uninstallExitCleanupIfIdle() {
1336
- if (_CertificateManager.#activeInstances.size > 0) return;
1337
- if (_CertificateManager.#exitHandler) {
1338
- process.removeListener("exit", _CertificateManager.#exitHandler);
1339
- _CertificateManager.#exitHandler = void 0;
1340
- }
1341
- _CertificateManager.#cleanupInstalled = false;
1342
- }
1343
- /**
1344
- * Dispose **all** active CertificateManager instances,
1345
- * closing their file watchers and freeing resources.
1346
- *
1347
- * This is mainly useful in test tear-down to ensure the
1348
- * Node.js process can exit cleanly.
1349
- */
1350
- static async disposeAll() {
1351
- const instances = [..._CertificateManager.#activeInstances];
1352
- await Promise.all(instances.map((cm) => _CertificateManager.prototype.dispose.call(cm)));
1353
- }
1354
- /**
1355
- * Assert that all CertificateManager instances have been
1356
- * properly disposed. Throws an Error listing the locations
1357
- * of any leaked instances.
1358
- *
1359
- * Intended for use in test `afterAll()` / `afterEach()`
1360
- * hooks to catch missing `dispose()` calls early.
1361
- *
1362
- * @example
1363
- * ```ts
1364
- * after(() => {
1365
- * CertificateManager.checkAllDisposed();
1366
- * });
1367
- * ```
1368
- */
1369
- static checkAllDisposed() {
1370
- if (_CertificateManager.#activeInstances.size === 0) return;
1371
- const locations = [..._CertificateManager.#activeInstances].map((cm) => cm.rootDir);
1372
- throw new Error(
1373
- `${_CertificateManager.#activeInstances.size} CertificateManager instance(s) not disposed:
1374
- - ${locations.join("\n - ")}`
1375
- );
1376
- }
1377
- // ─────────────────────────────────────────────────────────────
1378
- /**
1379
- * When `true` (the default), any certificate that is not
1380
- * already in the trusted or rejected store is automatically
1381
- * written to the rejected folder the first time it is seen.
1382
- */
1383
- untrustUnknownCertificate = true;
1384
- /** Current lifecycle state of this instance. */
1385
- state = 0 /* Uninitialized */;
1386
- /** @deprecated Use {@link folderPollingInterval} instead (typo fix). */
1387
- folderPoolingInterval = 5e3;
1388
- /** Interval in milliseconds for file-system polling (when enabled). */
1389
- get folderPollingInterval() {
1390
- return this.folderPoolingInterval;
1391
- }
1392
- set folderPollingInterval(value) {
1393
- this.folderPoolingInterval = value;
1394
- }
1395
- /** RSA key size used when generating the private key. */
1396
- keySize;
1397
- #location;
1398
- #watchers = [];
1399
- #pendingUnrefs = /* @__PURE__ */ new Set();
1400
- #readCertificatesCalled = false;
1401
- #filenameToHash = /* @__PURE__ */ new Map();
1402
- #initializingPromise;
1403
- #addCertValidation;
1404
- #disableFileWatchers;
1405
- #privateKeyPassphrase;
1406
- #privateKeyProvider;
1407
- #keyOperations;
1408
- /** The stable lazy wrap handed out by {@link getKeyOperations} for non-opaque configurations. */
1409
- #lazyLocalKeyOperations;
1410
- /**
1411
- * The on-disk key, decrypted once and kept for the instance's lifetime,
1412
- * so the passphrase (or its resolver function) is consulted at most
1413
- * once. Cleared by `dispose()`. Not used when a provider is configured:
1414
- * the provider is the authority on the current key.
1415
- */
1416
- #cachedPrivateKey;
1417
- #thumbs = {
1418
- rejected: /* @__PURE__ */ new Map(),
1419
- trusted: /* @__PURE__ */ new Map(),
1420
- issuers: {
1421
- certs: /* @__PURE__ */ new Map()
1422
- },
1423
- crl: /* @__PURE__ */ new Map(),
1424
- issuersCrl: /* @__PURE__ */ new Map()
1425
- };
1426
- /**
1427
- * Create a new CertificateManager.
1428
- *
1429
- * The constructor creates the root directory if it does not
1430
- * exist but does **not** initialise the PKI store — call
1431
- * {@link initialize} before using any other method.
1432
- *
1433
- * @param options - configuration options
1434
- */
1435
- constructor(options) {
1436
- super();
1437
- options.keySize = options.keySize || 2048;
1438
- if (!options.location) {
1439
- throw new Error("CertificateManager: missing 'location' option");
1440
- }
1441
- this.#location = makePath(options.location, "");
1442
- this.keySize = options.keySize;
1443
- const v = options.addCertificateValidationOptions ?? {};
1444
- this.#addCertValidation = {
1445
- acceptExpiredCertificate: v.acceptExpiredCertificate ?? false,
1446
- acceptRevokedCertificate: v.acceptRevokedCertificate ?? false,
1447
- ignoreMissingRevocationList: v.ignoreMissingRevocationList ?? false,
1448
- maxChainLength: v.maxChainLength ?? 5
1449
- };
1450
- this.#disableFileWatchers = options.disableFileWatchers ?? process.env.OPCUA_PKI_DISABLE_FILE_WATCHERS === "true";
1451
- this.#privateKeyPassphrase = options.privateKeyPassphrase;
1452
- this.#privateKeyProvider = options.privateKeyProvider;
1453
- this.#keyOperations = options.keyOperations;
1454
- if (this.#keyOperations && this.#privateKeyProvider) {
1455
- throw new Error(
1456
- "CertificateManager: 'keyOperations' and 'privateKeyProvider' are mutually exclusive: one hides the key, the other sources its material"
1457
- );
1458
- }
1459
- if (this.#keyOperations && this.#privateKeyPassphrase) {
1460
- throw new Error(
1461
- "CertificateManager: 'privateKeyPassphrase' is meaningless with 'keyOperations': there is no key material for a passphrase to protect"
1462
- );
1463
- }
1464
- mkdirRecursiveSync(options.location);
1465
- if (!fs7.existsSync(this.#location)) {
1466
- throw new Error(`CertificateManager cannot access location ${this.#location}`);
1467
- }
1468
- }
1469
- /** Path to the OpenSSL configuration file. */
1470
- get configFile() {
1471
- return path4.join(this.rootDir, "own/openssl.cnf");
1472
- }
1473
- /** Root directory of the PKI store. */
1474
- get rootDir() {
1475
- return this.#location;
1476
- }
1477
- /**
1478
- * Path to the private key file (`own/private/private_key.pem`).
1479
- *
1480
- * Kept for backward compatibility with code that reads the key
1481
- * directly from disk. When a passphrase or a `privateKeyProvider` is
1482
- * configured, prefer {@link getPrivateKey} instead — this getter still
1483
- * returns the on-disk path even if a provider is configured (there may
1484
- * be no meaningful file in that case).
1485
- */
1486
- get privateKey() {
1487
- return path4.join(this.rootDir, "own/private/private_key.pem");
1488
- }
1489
- /**
1490
- * Resolve the private key: from `privateKeyProvider` if configured,
1491
- * otherwise from disk (decrypting with `privateKeyPassphrase` if the
1492
- * key is encrypted). Fails closed — throws
1493
- * `PrivateKeyPassphraseRequiredError` — if the on-disk key is encrypted
1494
- * and no passphrase is configured, or if the wrong passphrase is
1495
- * configured.
1496
- *
1497
- * The on-disk key is read and decrypted once and then cached for the
1498
- * lifetime of this instance, so a `privateKeyPassphrase` function is
1499
- * called at most once (concurrent first calls share the same read). A
1500
- * failed read is not cached, so a caller can fix the passphrase and
1501
- * retry. A `privateKeyProvider` is consulted on every call: it is the
1502
- * authority on what the current key is.
1503
- */
1504
- async getPrivateKey() {
1505
- if (this.#keyOperations) {
1506
- throw new PrivateKeyUnavailableError(
1507
- "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"
1508
- );
1509
- }
1510
- if (this.#privateKeyProvider) {
1511
- return await this.#privateKeyProvider.getPrivateKey();
1512
- }
1513
- if (this.#cachedPrivateKey) {
1514
- return this.#cachedPrivateKey;
1515
- }
1516
- if (!this.#privateKeyPromise) {
1517
- this.#privateKeyPromise = (async () => {
1518
- const passphrase = await resolvePrivateKeyPassphrase(this.#privateKeyPassphrase);
1519
- return readPrivateKey(this.privateKey, passphrase);
1520
- })().then(
1521
- (key) => {
1522
- this.#cachedPrivateKey = key;
1523
- return key;
1524
- },
1525
- (err) => {
1526
- this.#privateKeyPromise = void 0;
1527
- throw err;
1528
- }
1529
- );
1530
- }
1531
- return await this.#privateKeyPromise;
1532
- }
1533
- /** In-flight first read of the on-disk key, so concurrent callers share one passphrase resolution. */
1534
- #privateKeyPromise;
1535
- /**
1536
- * True when this manager's key is opaque — configured through
1537
- * `keyOperations`, held by an HSM/KMS, never obtainable as material.
1538
- * When true, {@link getPrivateKey} throws `PrivateKeyUnavailableError`
1539
- * and {@link getKeyOperations} is the only way to use the key.
1540
- */
1541
- isPrivateKeyOpaque() {
1542
- return !!this.#keyOperations;
1543
- }
1544
- /**
1545
- * The key as an opaque {@link IKeyOperations} — the recommended way to
1546
- * *use* the private key regardless of where it lives.
1547
- *
1548
- * Returns the configured `keyOperations` object when the key is opaque.
1549
- * Otherwise returns a stable lazy wrap over {@link getPrivateKey}: its
1550
- * methods resolve the key on first use (disk read, passphrase,
1551
- * `privateKeyProvider` — all async), so the wrap offers no synchronous
1552
- * fast path; callers that need one resolve the key themselves and build
1553
- * a `LocalKeyOperations` over it. The wrap follows key rotation: a
1554
- * `privateKeyProvider` that starts returning a different key gets a
1555
- * fresh underlying `LocalKeyOperations`.
1556
- */
1557
- getKeyOperations() {
1558
- if (this.#keyOperations) {
1559
- return this.#keyOperations;
1560
- }
1561
- if (!this.#lazyLocalKeyOperations) {
1562
- let cached;
1563
- const resolve = async () => {
1564
- const key = await this.getPrivateKey();
1565
- if (!cached || cached.key !== key) {
1566
- cached = { key, ops: keyOperationsFromPrivateKey(key) };
1567
- }
1568
- return cached.ops;
1569
- };
1570
- this.#lazyLocalKeyOperations = {
1571
- sign: async (data, params) => (await resolve()).sign(data, params),
1572
- decryptBlock: async (block, params) => (await resolve()).decryptBlock(block, params),
1573
- getKeyMetadata: async () => (await resolve()).getKeyMetadata(),
1574
- getPublicKey: async () => (await resolve()).getPublicKey()
1575
- };
1576
- }
1577
- return this.#lazyLocalKeyOperations;
1578
- }
1579
- /**
1580
- * Fail closed at `initialize()` time, not on the first certificate
1581
- * operation, if the key cannot actually be used: wrong/missing
1582
- * passphrase on an encrypted key, a broken `privateKeyProvider`, or an
1583
- * unreachable `keyOperations` provider (probed via `getKeyMetadata`).
1584
- */
1585
- async #probePrivateKey() {
1586
- if (this.#keyOperations) {
1587
- await this.#keyOperations.getKeyMetadata();
1588
- return;
1589
- }
1590
- await this.getPrivateKey();
1591
- }
1592
- /**
1593
- * The key to hand to a certificate-issuance primitive: the raw
1594
- * {@link PrivateKey} for a local configuration, or a {@link CaSigner}
1595
- * adapted from `keyOperations` when the key is opaque — which requires
1596
- * the provider to implement `getPublicKey` (the adapter says so if not).
1597
- */
1598
- async #resolveSigningKey() {
1599
- if (this.#keyOperations) {
1600
- return caSignerFromKeyOperations(this.#keyOperations);
1601
- }
1602
- return await this.getPrivateKey();
1603
- }
1604
- /**
1605
- * Enable, disable, or rotate the passphrase protecting the on-disk
1606
- * private key: decrypt with `oldPassphrase` (omit if the key is
1607
- * currently unencrypted), then write back encrypted with
1608
- * `newPassphrase` (omit to leave it unencrypted). The write goes to a
1609
- * temporary file in the same directory and is atomically renamed into
1610
- * place, so a crash mid-rotation cannot leave a partially-written key;
1611
- * the temporary file is removed if anything fails, so a rotation *to*
1612
- * plaintext can never leave a stray cleartext copy behind. Runs under
1613
- * the same lock as `initialize()`.
1614
- *
1615
- * This only rewrites the on-disk file — it does not update this
1616
- * instance's own `privateKeyPassphrase` (set at construction), and it
1617
- * drops this instance's cached key so that disk stays the source of
1618
- * truth. Construct a new `CertificateManager` with the new passphrase to
1619
- * continue using it afterward.
1620
- *
1621
- * Not supported when a `privateKeyProvider` is configured (there is no
1622
- * disk file for this method to rewrite).
1623
- */
1624
- async reencryptPrivateKey(oldPassphrase, newPassphrase) {
1625
- if (this.#keyOperations) {
1626
- throw new PrivateKeyUnavailableError(
1627
- "reencryptPrivateKey: not supported when keyOperations is configured \u2014 there is no key material to rewrite"
1628
- );
1629
- }
1630
- if (this.#privateKeyProvider) {
1631
- throw new Error("reencryptPrivateKey: not supported when a privateKeyProvider is configured");
1632
- }
1633
- const oldPass = await resolvePrivateKeyPassphrase(oldPassphrase);
1634
- const newPass = await resolvePrivateKeyPassphrase(newPassphrase);
1635
- await this.withLock2(async () => {
1636
- const privateKey = readPrivateKey(this.privateKey, oldPass);
1637
- await this.#rewritePrivateKeyFile(privateKey, newPass);
1638
- });
1639
- this.#cachedPrivateKey = void 0;
1640
- this.#privateKeyPromise = void 0;
1641
- }
1642
- /**
1643
- * Atomically replace the on-disk private key with `privateKey`, written
1644
- * as PKCS#8 (encrypted with `passphrase` if given). Temp file next to the
1645
- * target, `0600`, renamed into place; the temp file is unlinked on any
1646
- * failure so no partial or cleartext copy can be left behind.
1647
- * Caller must hold the lock.
1648
- */
1649
- async #rewritePrivateKeyFile(privateKey, passphrase) {
1650
- const tmpFilename = `${this.privateKey}.${process.pid}-${Date.now()}.tmp`;
1651
- try {
1652
- await writePrivateKeyFile(tmpFilename, privateKey, { passphrase });
1653
- await fs7.promises.rename(tmpFilename, this.privateKey);
1654
- } finally {
1655
- await fs7.promises.rm(tmpFilename, { force: true });
1656
- }
1657
- }
1658
- /** Path to the OpenSSL random seed file. */
1659
- get randomFile() {
1660
- return path4.join(this.rootDir, "./random.rnd");
1661
- }
1662
- /**
1663
- * Move a certificate to the rejected store.
1664
- * If the certificate was previously trusted, it will be removed from the trusted folder.
1665
- * @param certificateOrChain - the DER-encoded certificate or certificate chain
1666
- */
1667
- async rejectCertificate(certificateOrChain) {
1668
- await this.#moveCertificate(certificateOrChain, "rejected");
1669
- }
1670
- /**
1671
- * Move a certificate to the trusted store.
1672
- * If the certificate was previously rejected, it will be removed from the rejected folder.
1673
- * @param certificateOrChain - the DER-encoded certificate or certificate chain
1674
- */
1675
- async trustCertificate(certificateOrChain) {
1676
- await this.#moveCertificate(certificateOrChain, "trusted");
1677
- }
1678
- /**
1679
- * Check whether the trusted certificate store is empty.
1680
- *
1681
- * This inspects the in-memory index, which is kept in
1682
- * sync with the `trusted/certs/` folder by file-system
1683
- * watchers after {@link initialize} has been called.
1684
- */
1685
- isTrustListEmpty() {
1686
- return this.#thumbs.trusted.size === 0;
1687
- }
1688
- /**
1689
- * Return the number of certificates currently in the
1690
- * trusted store.
1691
- */
1692
- getTrustedCertificateCount() {
1693
- return this.#thumbs.trusted.size;
1694
- }
1695
- /** Path to the rejected certificates folder. */
1696
- get rejectedFolder() {
1697
- return path4.join(this.rootDir, "rejected");
1698
- }
1699
- /** Path to the trusted certificates folder. */
1700
- get trustedFolder() {
1701
- return path4.join(this.rootDir, "trusted/certs");
1702
- }
1703
- /** Path to the trusted CRL folder. */
1704
- get crlFolder() {
1705
- return path4.join(this.rootDir, "trusted/crl");
1706
- }
1707
- /** Path to the issuer (CA) certificates folder. */
1708
- get issuersCertFolder() {
1709
- return path4.join(this.rootDir, "issuers/certs");
1710
- }
1711
- /** Path to the issuer CRL folder. */
1712
- get issuersCrlFolder() {
1713
- return path4.join(this.rootDir, "issuers/crl");
1714
- }
1715
- /** Path to the own certificate folder. */
1716
- get ownCertFolder() {
1717
- return path4.join(this.rootDir, "own/certs");
1718
- }
1719
- get ownPrivateFolder() {
1720
- return path4.join(this.rootDir, "own/private");
1721
- }
1722
- /**
1723
- * Check if a certificate is in the trusted store.
1724
- * If the certificate is unknown and `untrustUnknownCertificate` is set,
1725
- * it will be written to the rejected folder.
1726
- * @param certificate - the DER-encoded certificate
1727
- * @returns `"Good"` if trusted, `"BadCertificateUntrusted"` if rejected/unknown,
1728
- * or `"BadCertificateInvalid"` if the certificate cannot be parsed.
1729
- */
1730
- async isCertificateTrusted(certificateOrCertificateChain) {
1731
- try {
1732
- const chain = coerceCertificateChain(certificateOrCertificateChain);
1733
- const leafCertificate = chain[0];
1734
- if (chain.length < 1) {
1735
- return "BadCertificateInvalid";
1736
- }
1737
- let fingerprint;
1738
- try {
1739
- fingerprint = makeFingerprint(chain[0]);
1740
- } catch (_err) {
1741
- return "BadCertificateInvalid";
1742
- }
1743
- if (this.#thumbs.trusted.has(fingerprint)) {
1744
- return "Good";
1745
- }
1746
- if (!this.#thumbs.rejected.has(fingerprint)) {
1747
- if (!this.untrustUnknownCertificate) {
1748
- return "Good";
1749
- }
1750
- try {
1751
- exploreCertificateInfo(chain[0]);
1752
- } catch (_err) {
1753
- return "BadCertificateInvalid";
1754
- }
1755
- const filename = safeStoreJoin(this.rejectedFolder, `${buildIdealCertificateName(leafCertificate)}.pem`);
1756
- debugLog("certificate has never been seen before and is now rejected (untrusted) ", filename);
1757
- await fsWriteFile(filename, toPem(chain, "CERTIFICATE"));
1758
- this.#thumbs.rejected.set(fingerprint, { certificate: leafCertificate, filename });
1759
- }
1760
- return "BadCertificateUntrusted";
1761
- } catch (_err) {
1762
- return "BadCertificateInvalid";
1763
- }
1764
- }
1765
- async #innerVerifyCertificateAsync(certificateOrChain, _isIssuer, level, options) {
1766
- if (level >= 5) {
1767
- return "BadSecurityChecksFailed" /* BadSecurityChecksFailed */;
1768
- }
1769
- const chain = coerceCertificateChain(certificateOrChain);
1770
- debugLog("NB CERTIFICATE IN CHAIN = ", chain.length);
1771
- const info = exploreCertificate(chain[0]);
1772
- let hasValidIssuer = false;
1773
- let hasTrustedIssuer = false;
1774
- const hasIssuerKey = info.tbsCertificate.extensions?.authorityKeyIdentifier?.keyIdentifier;
1775
- debugLog("Certificate as an Issuer Key", hasIssuerKey);
1776
- if (hasIssuerKey) {
1777
- const isSelfSigned = isSelfSigned2(info);
1778
- debugLog("Is the Certificate self-signed ?", isSelfSigned);
1779
- if (!isSelfSigned) {
1780
- debugLog(
1781
- "Is issuer found in the list of know issuers ?",
1782
- "\n subjectKeyIdentifier = ",
1783
- info.tbsCertificate.extensions?.subjectKeyIdentifier,
1784
- "\n authorityKeyIdentifier = ",
1785
- info.tbsCertificate.extensions?.authorityKeyIdentifier?.keyIdentifier
1786
- );
1787
- let issuerCertificate = await this.findIssuerCertificate(chain[0]);
1788
- if (!issuerCertificate) {
1789
- issuerCertificate = findIssuerCertificateInChain(chain[0], chain);
1790
- if (!issuerCertificate) {
1791
- debugLog(
1792
- " the issuer has not been found in the chain itself nor in the issuer.cert list => the chain is incomplete!"
1793
- );
1794
- return "BadCertificateChainIncomplete" /* BadCertificateChainIncomplete */;
1795
- }
1796
- debugLog(" the issuer certificate has been found in the chain itself ! the chain is complete !");
1797
- } else {
1798
- debugLog(" the issuer certificate has been found in the issuer.cert folder !");
1799
- }
1800
- const issuerStatus = await this.#innerVerifyCertificateAsync(issuerCertificate, true, level + 1, options);
1801
- if (issuerStatus === "BadCertificateRevocationUnknown" /* BadCertificateRevocationUnknown */) {
1802
- return "BadCertificateIssuerRevocationUnknown" /* BadCertificateIssuerRevocationUnknown */;
1803
- }
1804
- if (issuerStatus === "BadCertificateIssuerRevocationUnknown" /* BadCertificateIssuerRevocationUnknown */) {
1805
- return "BadCertificateIssuerRevocationUnknown" /* BadCertificateIssuerRevocationUnknown */;
1806
- }
1807
- if (issuerStatus === "BadCertificateTimeInvalid" /* BadCertificateTimeInvalid */) {
1808
- if (!options?.acceptOutDatedIssuerCertificate) {
1809
- return "BadCertificateIssuerTimeInvalid" /* BadCertificateIssuerTimeInvalid */;
1810
- }
1811
- }
1812
- if (issuerStatus === "BadCertificateUntrusted" /* BadCertificateUntrusted */) {
1813
- debugLog("warning issuerStatus = ", issuerStatus.toString(), "the issuer certificate is not trusted");
1814
- }
1815
- if (issuerStatus !== "Good" /* Good */ && issuerStatus !== "BadCertificateUntrusted" /* BadCertificateUntrusted */) {
1816
- return "BadSecurityChecksFailed" /* BadSecurityChecksFailed */;
1817
- }
1818
- const isCertificateSignatureOK = verifyCertificateSignature(chain[0], issuerCertificate);
1819
- if (!isCertificateSignatureOK) {
1820
- debugLog(" the certificate was not signed by the issuer as it claim to be ! Danger");
1821
- return "BadSecurityChecksFailed" /* BadSecurityChecksFailed */;
1822
- }
1823
- hasValidIssuer = true;
1824
- let revokedStatus = await this.isCertificateRevoked(chain, issuerCertificate);
1825
- if (revokedStatus === "BadCertificateRevocationUnknown" /* BadCertificateRevocationUnknown */) {
1826
- if (options?.ignoreMissingRevocationList) {
1827
- revokedStatus = "Good" /* Good */;
1828
- }
1829
- }
1830
- if (revokedStatus !== "Good" /* Good */) {
1831
- debugLog("revokedStatus", revokedStatus);
1832
- return revokedStatus;
1833
- }
1834
- const issuerTrustedStatus = await this.#checkRejectedOrTrusted(issuerCertificate);
1835
- debugLog("issuerTrustedStatus", issuerTrustedStatus);
1836
- if (issuerTrustedStatus === "unknown") {
1837
- hasTrustedIssuer = false;
1838
- } else if (issuerTrustedStatus === "trusted") {
1839
- hasTrustedIssuer = true;
1840
- } else if (issuerTrustedStatus === "rejected") {
1841
- return "BadSecurityChecksFailed" /* BadSecurityChecksFailed */;
1842
- }
1843
- } else {
1844
- const isCertificateSignatureOK = verifyCertificateSignature(chain[0], chain[0]);
1845
- if (!isCertificateSignatureOK) {
1846
- debugLog("Self-signed Certificate signature is not valid");
1847
- return "BadSecurityChecksFailed" /* BadSecurityChecksFailed */;
1848
- }
1849
- const revokedStatus = await this.isCertificateRevoked(chain);
1850
- debugLog("revokedStatus of self signed certificate:", revokedStatus);
1851
- }
1852
- }
1853
- const status = await this.#checkRejectedOrTrusted(chain[0]);
1854
- if (status === "rejected") {
1855
- if (!(options.acceptCertificateWithValidIssuerChain && hasValidIssuer && hasTrustedIssuer)) {
1856
- return "BadCertificateUntrusted" /* BadCertificateUntrusted */;
1857
- }
1858
- }
1859
- const { validity } = exploreCertificate(chain[0]).tbsCertificate;
1860
- const now = /* @__PURE__ */ new Date();
1861
- let isTimeInvalid = false;
1862
- if (validity.notBefore.getTime() > now.getTime()) {
1863
- debugLog(
1864
- `${chalk3.red("certificate is invalid : certificate is not active yet !")} not before date =${validity.notBefore}`
1865
- );
1866
- if (!options.acceptPendingCertificate) {
1867
- isTimeInvalid = true;
1868
- }
1869
- }
1870
- if (validity.notAfter.getTime() <= now.getTime()) {
1871
- debugLog(`${chalk3.red("certificate is invalid : certificate has expired !")} not after date =${validity.notAfter}`);
1872
- if (!options.acceptOutdatedCertificate) {
1873
- isTimeInvalid = true;
1874
- }
1875
- }
1876
- if (status === "trusted") {
1877
- return isTimeInvalid ? "BadCertificateTimeInvalid" /* BadCertificateTimeInvalid */ : "Good" /* Good */;
1878
- }
1879
- if (hasIssuerKey) {
1880
- if (!hasTrustedIssuer) {
1881
- return "BadCertificateUntrusted" /* BadCertificateUntrusted */;
1882
- }
1883
- if (!hasValidIssuer) {
1884
- return "BadCertificateUntrusted" /* BadCertificateUntrusted */;
1885
- }
1886
- if (!options.acceptCertificateWithValidIssuerChain) {
1887
- return "BadCertificateUntrusted" /* BadCertificateUntrusted */;
1888
- }
1889
- return isTimeInvalid ? "BadCertificateTimeInvalid" /* BadCertificateTimeInvalid */ : "Good" /* Good */;
1890
- } else {
1891
- return "BadCertificateUntrusted" /* BadCertificateUntrusted */;
1892
- }
1893
- }
1894
- /**
1895
- * Internal verification hook called by {@link verifyCertificate}.
1896
- *
1897
- * Subclasses can override this to inject additional validation
1898
- * logic (e.g. application-level policy checks) while still
1899
- * delegating to the default chain/CRL/trust verification.
1900
- *
1901
- * @param certificate - the DER-encoded certificate to verify
1902
- * @param options - verification options forwarded from the
1903
- * public API
1904
- * @returns the verification status code
1905
- */
1906
- async verifyCertificateAsync(certificate, options) {
1907
- const chain = coerceCertificateChain(certificate);
1908
- for (const element of chain) {
1909
- try {
1910
- exploreCertificate(element);
1911
- } catch (_err) {
1912
- return "BadCertificateInvalid" /* BadCertificateInvalid */;
1913
- }
1914
- }
1915
- const status1 = await this.#innerVerifyCertificateAsync(chain, false, 0, options);
1916
- return status1;
1917
- }
1918
- /**
1919
- * Verify a certificate against the PKI trust store.
1920
- *
1921
- * This performs a full validation including trust status,
1922
- * issuer chain, CRL revocation checks, and time validity.
1923
- *
1924
- * @param certificate - the DER-encoded certificate to verify
1925
- * @param options - optional flags to relax validation rules
1926
- * @returns the verification status code
1927
- */
1928
- async verifyCertificate(certificate, options) {
1929
- if (!certificate) {
1930
- return "BadSecurityChecksFailed" /* BadSecurityChecksFailed */;
1931
- }
1932
- try {
1933
- const status = await this.verifyCertificateAsync(certificate, options || {});
1934
- return status;
1935
- } catch (error) {
1936
- warningLog(`verifyCertificate error: ${error.message}`);
1937
- return "BadCertificateInvalid" /* BadCertificateInvalid */;
1938
- }
1939
- }
1940
- /**
1941
- * Initialize the PKI directory structure, generate the
1942
- * private key (if missing), and start file-system watchers.
1943
- *
1944
- * This method is idempotent — subsequent calls are no-ops.
1945
- * It must be called before any certificate operations.
1946
- */
1947
- async initialize() {
1948
- if (this.state !== 0 /* Uninitialized */) {
1949
- return;
1950
- }
1951
- this.state = 1 /* Initializing */;
1952
- this.#initializingPromise = this.#initialize();
1953
- try {
1954
- await this.#initializingPromise;
1955
- } catch (err) {
1956
- this.#initializingPromise = void 0;
1957
- this.state = 0 /* Uninitialized */;
1958
- throw err;
1959
- }
1960
- this.#initializingPromise = void 0;
1961
- this.state = 2 /* Initialized */;
1962
- _CertificateManager.#activeInstances.add(this);
1963
- _CertificateManager.#installExitCleanup();
1964
- }
1965
- async #initialize() {
1966
- const pkiDir = this.#location;
1967
- mkdirRecursiveSync(pkiDir);
1968
- mkdirRecursiveSync(path4.join(pkiDir, "own"));
1969
- mkdirRecursiveSync(path4.join(pkiDir, "own/certs"));
1970
- ensurePrivateDirectory(path4.join(pkiDir, "own/private"));
1971
- mkdirRecursiveSync(path4.join(pkiDir, "rejected"));
1972
- mkdirRecursiveSync(path4.join(pkiDir, "trusted"));
1973
- mkdirRecursiveSync(path4.join(pkiDir, "trusted/certs"));
1974
- mkdirRecursiveSync(path4.join(pkiDir, "trusted/crl"));
1975
- mkdirRecursiveSync(path4.join(pkiDir, "issuers"));
1976
- mkdirRecursiveSync(path4.join(pkiDir, "issuers/certs"));
1977
- mkdirRecursiveSync(path4.join(pkiDir, "issuers/crl"));
1978
- const ownsDiskKey = !this.#privateKeyProvider && !this.#keyOperations;
1979
- const needsKeyGeneration = ownsDiskKey && !fs7.existsSync(this.privateKey);
1980
- const needsKeyEncryption = ownsDiskKey && !needsKeyGeneration && this.#privateKeyPassphrase !== void 0 && !isEncryptedPrivateKeyFile(this.privateKey);
1981
- if (!fs7.existsSync(this.configFile) || needsKeyGeneration || needsKeyEncryption) {
1982
- return await this.withLock2(async () => {
1983
- if (this.state === 3 /* Disposing */ || this.state === 4 /* Disposed */) {
1984
- return;
1985
- }
1986
- if (!fs7.existsSync(this.configFile)) {
1987
- fs7.writeFileSync(this.configFile, configurationFileSimpleTemplate);
1988
- }
1989
- if (ownsDiskKey && !fs7.existsSync(this.privateKey)) {
1990
- debugLog("generating private key ...");
1991
- const passphrase = await resolvePrivateKeyPassphrase(this.#privateKeyPassphrase);
1992
- await generatePrivateKeyFile(this.privateKey, this.keySize, { passphrase });
1993
- this.#cachedPrivateKey = readPrivateKey(this.privateKey, passphrase);
1994
- } else if (ownsDiskKey && this.#privateKeyPassphrase !== void 0 && !isEncryptedPrivateKeyFile(this.privateKey)) {
1995
- warningLog("initialize: private key is plaintext but a passphrase is configured; encrypting it in place");
1996
- const passphrase = await resolvePrivateKeyPassphrase(this.#privateKeyPassphrase);
1997
- const plaintextKey = readPrivateKey(this.privateKey);
1998
- await this.#rewritePrivateKeyFile(plaintextKey, passphrase);
1999
- this.#cachedPrivateKey = plaintextKey;
2000
- }
2001
- if (ownsDiskKey) {
2002
- restrictPrivateFilePermissions(this.privateKey, 384);
2003
- }
2004
- await this.#probePrivateKey();
2005
- await this.#readCertificates();
2006
- });
2007
- } else {
2008
- if (ownsDiskKey) {
2009
- restrictPrivateFilePermissions(this.privateKey, 384);
2010
- }
2011
- await this.#probePrivateKey();
2012
- await this.#readCertificates();
2013
- }
2014
- }
2015
- /**
2016
- * Dispose of the CertificateManager, releasing file watchers
2017
- * and other resources. The instance should not be used after
2018
- * calling this method.
2019
- */
2020
- async dispose() {
2021
- if (this.state === 3 /* Disposing */) {
2022
- throw new Error("Already disposing");
2023
- }
2024
- if (this.state === 0 /* Uninitialized */) {
2025
- this.state = 4 /* Disposed */;
2026
- return;
2027
- }
2028
- if (this.state === 1 /* Initializing */) {
2029
- if (this.#initializingPromise) {
2030
- await this.#initializingPromise;
2031
- }
2032
- }
2033
- try {
2034
- this.state = 3 /* Disposing */;
2035
- await drainPendingLocks();
2036
- for (const unreff of this.#pendingUnrefs) {
2037
- unreff();
2038
- }
2039
- this.#pendingUnrefs.clear();
2040
- await Promise.all(this.#watchers.map((w) => w.close()));
2041
- this.#watchers.forEach((w) => {
2042
- w.removeAllListeners();
2043
- });
2044
- this.#watchers.splice(0);
2045
- } finally {
2046
- this.state = 4 /* Disposed */;
2047
- this.#cachedPrivateKey = void 0;
2048
- this.#privateKeyPromise = void 0;
2049
- _CertificateManager.#activeInstances.delete(this);
2050
- _CertificateManager.#uninstallExitCleanupIfIdle();
2051
- }
2052
- }
2053
- /**
2054
- * Force a full re-scan of all PKI folders, rebuilding
2055
- * the in-memory `_thumbs` index from scratch.
2056
- *
2057
- * Call this after external processes have modified the
2058
- * PKI folders (e.g. via `writeTrustList` or CLI tools)
2059
- * to ensure the CertificateManager sees the latest
2060
- * state without waiting for file-system events.
2061
- */
2062
- async reloadCertificates() {
2063
- await Promise.all(this.#watchers.map((w) => w.close()));
2064
- for (const w of this.#watchers) {
2065
- w.removeAllListeners();
2066
- }
2067
- this.#watchers.splice(0);
2068
- this.#thumbs.rejected.clear();
2069
- this.#thumbs.trusted.clear();
2070
- this.#thumbs.issuers.certs.clear();
2071
- this.#thumbs.crl.clear();
2072
- this.#thumbs.issuersCrl.clear();
2073
- this.#filenameToHash.clear();
2074
- this.#readCertificatesCalled = false;
2075
- await this.#readCertificates();
2076
- }
2077
- async withLock2(action) {
2078
- const lockFileName = path4.join(this.rootDir, "mutex");
2079
- return withLock({ fileToLock: lockFileName }, async () => {
2080
- return await action();
2081
- });
2082
- }
2083
- /**
2084
- * Create a self-signed certificate for this PKI's private key.
2085
- *
2086
- * The certificate is written to `params.outputFile` or
2087
- * `own/certs/self_signed_certificate.pem` by default.
2088
- *
2089
- * @param params - certificate parameters (subject, SANs,
2090
- * validity, etc.)
2091
- */
2092
- async createSelfSignedCertificate(params) {
2093
- if (typeof params.applicationUri !== "string") {
2094
- throw new Error("createSelfSignedCertificate: expecting applicationUri to be a string");
2095
- }
2096
- if (!this.#privateKeyProvider && !this.#keyOperations && !fs7.existsSync(this.privateKey)) {
2097
- throw new Error(`Cannot find private key ${this.privateKey}`);
2098
- }
2099
- let certificateFilename = path4.join(this.rootDir, "own/certs/self_signed_certificate.pem");
2100
- certificateFilename = params.outputFile || certificateFilename;
2101
- const _params = {
2102
- ...params,
2103
- rootDir: this.rootDir,
2104
- configFile: this.configFile,
2105
- privateKey: await this.#resolveSigningKey(),
2106
- subject: params.subject || "CN=FIXME"
2107
- };
2108
- await this.withLock2(async () => {
2109
- await createSelfSignedCertificate(certificateFilename, _params);
2110
- });
2111
- }
2112
- /**
2113
- * Create a Certificate Signing Request (CSR) using this
2114
- * PKI's private key and configuration.
2115
- *
2116
- * The CSR file is written to `own/certs/` with a timestamped
2117
- * filename.
2118
- *
2119
- * @param params - CSR parameters (subject, SANs)
2120
- * @returns the filesystem path to the generated CSR file
2121
- */
2122
- async createCertificateRequest(params) {
2123
- if (!params) {
2124
- throw new Error("params is required");
2125
- }
2126
- if (Object.prototype.hasOwnProperty.call(params, "rootDir")) {
2127
- throw new Error("rootDir should not be specified ");
2128
- }
2129
- const _params = {
2130
- ...params,
2131
- rootDir: path4.resolve(this.rootDir),
2132
- configFile: path4.resolve(this.configFile),
2133
- privateKey: await this.#resolveSigningKey()
2134
- };
2135
- return await this.withLock2(async () => {
2136
- const now = /* @__PURE__ */ new Date();
2137
- const today = `${now.toISOString().slice(0, 10)}_${now.getTime()}`;
2138
- const certificateSigningRequestFilename = path4.join(this.rootDir, "own/certs", `certificate_${today}.csr`);
2139
- await createCertificateSigningRequestAsync(certificateSigningRequestFilename, _params);
2140
- return certificateSigningRequestFilename;
2141
- });
2142
- }
2143
- /**
2144
- * Add a CA (issuer) certificate to the issuers store.
2145
- * If the certificate is already present, this is a no-op.
2146
- * @param certificate - the DER-encoded CA certificate
2147
- * @param validate - if `true`, verify the certificate before adding
2148
- * @param addInTrustList - if `true`, also add to the trusted store
2149
- * @returns `VerificationStatus.Good` on success
2150
- */
2151
- async addIssuer(certificate, validate = false, addInTrustList = false) {
2152
- if (validate) {
2153
- const status = await this.verifyCertificate(certificate);
2154
- if (status !== "Good" /* Good */ && status !== "BadCertificateUntrusted" /* BadCertificateUntrusted */) {
2155
- return status;
2156
- }
2157
- }
2158
- const pemCertificate = toPem(certificate, "CERTIFICATE");
2159
- const fingerprint = makeFingerprint(certificate);
2160
- if (this.#thumbs.issuers.certs.has(fingerprint)) {
2161
- return "Good" /* Good */;
2162
- }
2163
- const filename = safeStoreJoin(this.issuersCertFolder, `issuer_${buildIdealCertificateName(certificate)}.pem`);
2164
- await fs7.promises.writeFile(filename, pemCertificate, "ascii");
2165
- this.#thumbs.issuers.certs.set(fingerprint, { certificate, filename });
2166
- if (addInTrustList) {
2167
- await this.trustCertificate(certificate);
2168
- }
2169
- return "Good" /* Good */;
2170
- }
2171
- /**
2172
- * Add multiple CA (issuer) certificates to the issuers store.
2173
- * @param certificates - the DER-encoded CA certificates
2174
- * @param validate - if `true`, verify each certificate before adding
2175
- * @param addInTrustList - if `true`, also add each certificate to the trusted store
2176
- * @returns `VerificationStatus.Good` on success
2177
- */
2178
- async addIssuers(certificates, validate = false, addInTrustList = false) {
2179
- for (const certificate of certificates) {
2180
- if (!isIssuer(certificate)) {
2181
- warningLog(`Certificate ${makeFingerprint(certificate)} is not a issuer certificate`);
2182
- continue;
2183
- }
2184
- await this.addIssuer(certificate, validate, addInTrustList);
2185
- }
2186
- return "Good" /* Good */;
2187
- }
2188
- /**
2189
- * Add a CRL to the certificate manager.
2190
- * @param crl - the CRL to add
2191
- * @param target - "issuers" (default) writes to issuers/crl, "trusted" writes to trusted/crl
2192
- */
2193
- async addRevocationList(crl, target = "issuers") {
2194
- return await this.withLock2(async () => {
2195
- try {
2196
- const index = target === "trusted" ? this.#thumbs.crl : this.#thumbs.issuersCrl;
2197
- const folder = target === "trusted" ? this.crlFolder : this.issuersCrlFolder;
2198
- const crlInfo = exploreCertificateRevocationList(crl);
2199
- const key = crlInfo.tbsCertList.issuerFingerprint;
2200
- if (!index.has(key)) {
2201
- index.set(key, { crls: [], serialNumbers: {} });
2202
- }
2203
- const pemCertificate = toPem(crl, "X509 CRL");
2204
- const sanitizedKey = key.replace(/:/g, "");
2205
- const filename = path4.join(folder, `crl_[${sanitizedKey}].pem`);
2206
- await fs7.promises.writeFile(filename, pemCertificate, "ascii");
2207
- await this.#onCrlFileAdded(index, filename);
2208
- await this.#waitAndCheckCRLProcessingStatus();
2209
- return "Good" /* Good */;
2210
- } catch (err) {
2211
- debugLog(err);
2212
- return "BadSecurityChecksFailed" /* BadSecurityChecksFailed */;
2213
- }
2214
- });
2215
- }
2216
- /**
2217
- * Remove all CRL files from the specified folder(s) and clear the
2218
- * corresponding in-memory index.
2219
- * @param target - "issuers" clears issuers/crl, "trusted" clears
2220
- * trusted/crl, "all" clears both.
2221
- */
2222
- async clearRevocationLists(target) {
2223
- const clearFolder = async (folder, index) => {
2224
- try {
2225
- const files = await fs7.promises.readdir(folder);
2226
- for (const file of files) {
2227
- const ext = path4.extname(file).toLowerCase();
2228
- if (ext === ".crl" || ext === ".pem" || ext === ".der") {
2229
- await fs7.promises.unlink(path4.join(folder, file));
2230
- }
2231
- }
2232
- } catch (err) {
2233
- if (err.code !== "ENOENT") {
2234
- throw err;
2235
- }
2236
- }
2237
- index.clear();
2238
- };
2239
- if (target === "issuers" || target === "all") {
2240
- await clearFolder(this.issuersCrlFolder, this.#thumbs.issuersCrl);
2241
- }
2242
- if (target === "trusted" || target === "all") {
2243
- await clearFolder(this.crlFolder, this.#thumbs.crl);
2244
- }
2245
- }
2246
- /**
2247
- * Check whether an issuer certificate with the given thumbprint
2248
- * is already registered.
2249
- * @param thumbprint - hex-encoded SHA-1 thumbprint (lowercase)
2250
- */
2251
- async hasIssuer(thumbprint) {
2252
- await this.#readCertificates();
2253
- const normalized = thumbprint.toLowerCase();
2254
- return this.#thumbs.issuers.certs.has(normalized);
2255
- }
2256
- /**
2257
- * Remove a trusted certificate identified by its SHA-1 thumbprint.
2258
- * Deletes the file on disk and removes the entry from the
2259
- * in-memory index.
2260
- * @param thumbprint - hex-encoded SHA-1 thumbprint (lowercase)
2261
- * @returns the removed certificate buffer, or `null` if not found
2262
- */
2263
- async removeTrustedCertificate(thumbprint) {
2264
- await this.#readCertificates();
2265
- const normalized = thumbprint.toLowerCase();
2266
- const entry = this.#thumbs.trusted.get(normalized);
2267
- if (!entry) {
2268
- return null;
2269
- }
2270
- try {
2271
- await fs7.promises.unlink(entry.filename);
2272
- } catch (err) {
2273
- if (err.code !== "ENOENT") {
2274
- throw err;
2275
- }
2276
- }
2277
- this.#thumbs.trusted.delete(normalized);
2278
- return entry.certificate;
2279
- }
2280
- /**
2281
- * Remove an issuer certificate identified by its SHA-1 thumbprint.
2282
- * Deletes the file on disk and removes the entry from the
2283
- * in-memory index.
2284
- * @param thumbprint - hex-encoded SHA-1 thumbprint (lowercase)
2285
- * @returns the removed certificate buffer, or `null` if not found
2286
- */
2287
- async removeIssuer(thumbprint) {
2288
- await this.#readCertificates();
2289
- const normalized = thumbprint.toLowerCase();
2290
- const entry = this.#thumbs.issuers.certs.get(normalized);
2291
- if (!entry) {
2292
- return null;
2293
- }
2294
- try {
2295
- await fs7.promises.unlink(entry.filename);
2296
- } catch (err) {
2297
- if (err.code !== "ENOENT") {
2298
- throw err;
2299
- }
2300
- }
2301
- this.#thumbs.issuers.certs.delete(normalized);
2302
- return entry.certificate;
2303
- }
2304
- /**
2305
- * Remove all CRL files that were issued by the given CA certificate
2306
- * from the specified folder (or both).
2307
- * @param issuerCertificate - the CA certificate whose CRLs to remove
2308
- * @param target - "issuers", "trusted", or "all" (default "all")
2309
- */
2310
- async removeRevocationListsForIssuer(issuerCertificate, target = "all") {
2311
- const issuerInfo = exploreCertificate(issuerCertificate);
2312
- const issuerFingerprint = issuerInfo.tbsCertificate.subjectFingerPrint;
2313
- const processIndex = async (index) => {
2314
- const crlData = index.get(issuerFingerprint);
2315
- if (!crlData) return;
2316
- for (const crlEntry of crlData.crls) {
2317
- try {
2318
- await fs7.promises.unlink(crlEntry.filename);
2319
- } catch (err) {
2320
- if (err.code !== "ENOENT") {
2321
- throw err;
2322
- }
2323
- }
2324
- }
2325
- index.delete(issuerFingerprint);
2326
- };
2327
- if (target === "issuers" || target === "all") {
2328
- await processIndex(this.#thumbs.issuersCrl);
2329
- }
2330
- if (target === "trusted" || target === "all") {
2331
- await processIndex(this.#thumbs.crl);
2332
- }
2333
- }
2334
- /**
2335
- * Validate a certificate (optionally with its chain) and add
2336
- * the leaf certificate to the trusted store.
2337
- *
2338
- * Performs OPC UA Part 4, Table 100 validation:
2339
- *
2340
- * 1. **Certificate Structure** — parse the DER encoding.
2341
- * 2. **Build Certificate Chain** — walk from the leaf to a
2342
- * self-signed root CA, using the provided chain and the
2343
- * issuers store.
2344
- * 3. **Signature** — verify each certificate's signature
2345
- * against its issuer.
2346
- * 4. **Issuer Presence** — every issuer in the chain must
2347
- * already be registered in the issuers store (per GDS
2348
- * 7.8.2.6).
2349
- * 5. **Validity Period** — each certificate must be within
2350
- * its validity window (overridable via
2351
- * {@link AddCertificateValidationOptions.acceptExpiredCertificate}).
2352
- * 6. **Revocation Check** — each certificate is checked
2353
- * against its issuer's CRL (overridable via
2354
- * {@link AddCertificateValidationOptions.acceptRevokedCertificate}
2355
- * and {@link AddCertificateValidationOptions.ignoreMissingRevocationList}).
2356
- *
2357
- * Only the leaf certificate is added to the trusted store.
2358
- *
2359
- * @param certificateChain - DER-encoded certificate or chain
2360
- * @returns `VerificationStatus.Good` on success, or an error
2361
- * status indicating why the certificate was rejected.
2362
- */
2363
- async addTrustedCertificateFromChain(certificateChain) {
2364
- try {
2365
- return await this.#addTrustedCertificateFromChainImpl(certificateChain);
2366
- } catch (_err) {
2367
- warningLog("addTrustedCertificateFromChain: unexpected error", _err);
2368
- return "BadCertificateInvalid" /* BadCertificateInvalid */;
2369
- }
2370
- }
2371
- async #addTrustedCertificateFromChainImpl(certificateChain) {
2372
- let certificates;
2373
- try {
2374
- certificates = coerceCertificateChain(certificateChain);
2375
- } catch (_err) {
2376
- return "BadCertificateInvalid" /* BadCertificateInvalid */;
2377
- }
2378
- if (certificates.length === 0) {
2379
- return "BadCertificateInvalid" /* BadCertificateInvalid */;
2380
- }
2381
- const leafCertificate = certificates[0];
2382
- const opts = this.#addCertValidation;
2383
- let leafInfo;
2384
- try {
2385
- leafInfo = exploreCertificate(leafCertificate);
2386
- } catch (_err) {
2387
- return "BadCertificateInvalid" /* BadCertificateInvalid */;
2388
- }
2389
- await this.#scanCertFolder(this.issuersCertFolder, this.#thumbs.issuers.certs);
2390
- let currentCert = leafCertificate;
2391
- let currentInfo = leafInfo;
2392
- let depth = 0;
2393
- while (true) {
2394
- depth++;
2395
- if (depth > opts.maxChainLength) {
2396
- return "BadSecurityChecksFailed" /* BadSecurityChecksFailed */;
2397
- }
2398
- if (!opts.acceptExpiredCertificate) {
2399
- const { validity } = currentInfo.tbsCertificate;
2400
- const now = /* @__PURE__ */ new Date();
2401
- if (validity.notBefore.getTime() > now.getTime()) {
2402
- return "BadCertificateTimeInvalid" /* BadCertificateTimeInvalid */;
2403
- }
2404
- if (validity.notAfter.getTime() <= now.getTime()) {
2405
- return depth === 1 ? "BadCertificateTimeInvalid" /* BadCertificateTimeInvalid */ : "BadCertificateIssuerTimeInvalid" /* BadCertificateIssuerTimeInvalid */;
2406
- }
2407
- }
2408
- if (isSelfSigned2(currentInfo)) {
2409
- try {
2410
- if (!verifyCertificateSignature(currentCert, currentCert)) {
2411
- return "BadCertificateInvalid" /* BadCertificateInvalid */;
2412
- }
2413
- } catch (_err) {
2414
- return "BadCertificateInvalid" /* BadCertificateInvalid */;
2415
- }
2416
- break;
2417
- }
2418
- let issuerCert = await this.findIssuerCertificate(currentCert);
2419
- if (!issuerCert) {
2420
- issuerCert = findIssuerCertificateInChain(currentCert, certificates);
2421
- if (!issuerCert || issuerCert === currentCert) {
2422
- return "BadCertificateChainIncomplete" /* BadCertificateChainIncomplete */;
2423
- }
2424
- }
2425
- try {
2426
- if (!verifyCertificateSignature(currentCert, issuerCert)) {
2427
- return "BadCertificateInvalid" /* BadCertificateInvalid */;
2428
- }
2429
- } catch (_err) {
2430
- return "BadCertificateInvalid" /* BadCertificateInvalid */;
2431
- }
2432
- const issuerThumbprint = makeFingerprint(issuerCert);
2433
- if (!await this.hasIssuer(issuerThumbprint)) {
2434
- return "BadCertificateChainIncomplete" /* BadCertificateChainIncomplete */;
2435
- }
2436
- const revokedStatus = await this.isCertificateRevoked(currentCert, issuerCert);
2437
- if (revokedStatus === "BadCertificateRevoked" /* BadCertificateRevoked */) {
2438
- if (!opts.acceptRevokedCertificate) {
2439
- return "BadCertificateRevoked" /* BadCertificateRevoked */;
2440
- }
2441
- } else if (revokedStatus === "BadCertificateRevocationUnknown" /* BadCertificateRevocationUnknown */) {
2442
- if (!opts.ignoreMissingRevocationList) {
2443
- return "BadCertificateRevocationUnknown" /* BadCertificateRevocationUnknown */;
2444
- }
2445
- }
2446
- currentCert = issuerCert;
2447
- try {
2448
- currentInfo = exploreCertificate(currentCert);
2449
- } catch (_err) {
2450
- return "BadCertificateInvalid" /* BadCertificateInvalid */;
2451
- }
2452
- }
2453
- await this.trustCertificate(certificates);
2454
- return "Good" /* Good */;
2455
- }
2456
- /**
2457
- * Check whether an issuer certificate is still needed by any
2458
- * certificate in the trusted store.
2459
- *
2460
- * This is used before removing an issuer to ensure that
2461
- * doing so would not break the chain of any trusted
2462
- * certificate.
2463
- *
2464
- * @param issuerCertificate - the CA certificate to check
2465
- * @returns `true` if at least one trusted certificate was
2466
- * signed by this issuer.
2467
- */
2468
- async isIssuerInUseByTrustedCertificate(issuerCertificate) {
2469
- await this.#readCertificates();
2470
- for (const entry of this.#thumbs.trusted.values()) {
2471
- if (!entry.certificate) continue;
2472
- try {
2473
- if (verifyCertificateSignature(entry.certificate, issuerCertificate)) {
2474
- return true;
2475
- }
2476
- } catch (_err) {
2477
- }
2478
- }
2479
- return false;
2480
- }
2481
- /**
2482
- * find the issuer certificate among the trusted issuer certificates.
2483
- *
2484
- * The findIssuerCertificate method is an asynchronous method that attempts to find
2485
- * the issuer certificate for a given certificate from the list of issuer certificate declared in the PKI
2486
- *
2487
- * - If the certificate is self-signed, it returns the certificate itself.
2488
- *
2489
- * - If the certificate has no extension 3, it is assumed to be generated by an old system, and a null value is returned.
2490
- *
2491
- * - the method checks both issuer and trusted certificates and returns the appropriate issuercertificate,
2492
- * if found. If multiple matching certificates are found, a warning is logged to the console.
2493
- *
2494
- */
2495
- async findIssuerCertificate(certificate) {
2496
- const firstCertificate = coerceCertificateChain(certificate)[0];
2497
- const certInfo = exploreCertificate(firstCertificate);
2498
- if (isSelfSigned2(certInfo)) {
2499
- return firstCertificate;
2500
- }
2501
- const wantedIssuerKey = certInfo.tbsCertificate.extensions?.authorityKeyIdentifier?.keyIdentifier;
2502
- if (!wantedIssuerKey) {
2503
- debugLog("Certificate has no extension 3");
2504
- return null;
2505
- }
2506
- const issuerCertificates = [...this.#thumbs.issuers.certs.values()];
2507
- const selectedIssuerCertificates = findMatchingIssuerKey(issuerCertificates, wantedIssuerKey);
2508
- if (selectedIssuerCertificates.length > 0) {
2509
- if (selectedIssuerCertificates.length > 1) {
2510
- warningLog("Warning more than one issuer certificate exists with subjectKeyIdentifier ", wantedIssuerKey);
2511
- }
2512
- return selectedIssuerCertificates[0].certificate || null;
2513
- }
2514
- const trustedCertificates = [...this.#thumbs.trusted.values()];
2515
- const selectedTrustedCertificates = findMatchingIssuerKey(trustedCertificates, wantedIssuerKey);
2516
- if (selectedTrustedCertificates.length > 1) {
2517
- warningLog(
2518
- "Warning more than one certificate exists with subjectKeyIdentifier in trusted certificate list ",
2519
- wantedIssuerKey,
2520
- selectedTrustedCertificates.length
2521
- );
2522
- }
2523
- return selectedTrustedCertificates.length > 0 ? selectedTrustedCertificates[0].certificate : null;
2524
- }
2525
- /**
2526
- * Outcome status for {@link CertificateManager.completeCertificateChain}.
2527
- */
2528
- static ChainCompletionStatus = ChainCompletionStatus;
2529
- /**
2530
- * Complete a certificate chain by walking the issuer store.
2531
- *
2532
- * Starting from the last certificate in the provided chain, this method
2533
- * repeatedly calls {@link findIssuerCertificate} to locate the parent
2534
- * certificate until it reaches a self-signed root or can no longer find
2535
- * an issuer.
2536
- *
2537
- * @param chain - the (potentially partial) certificate chain, leaf first
2538
- * @param maxDepth - maximum number of issuers to append (default: 10)
2539
- * @returns a {@link ChainCompletionResult} containing the (possibly completed)
2540
- * chain, a status code, and an optional diagnostic message.
2541
- */
2542
- async completeCertificateChain(chain, maxDepth = 10) {
2543
- if (chain.length === 0) {
2544
- return {
2545
- chain,
2546
- status: "EmptyChain" /* EmptyChain */,
2547
- message: "Input chain is empty \u2014 nothing to complete."
2548
- };
2549
- }
2550
- await this.#scanCertFolder(this.issuersCertFolder, this.#thumbs.issuers.certs);
2551
- const result = [...chain];
2552
- let depth = 0;
2553
- while (depth < maxDepth) {
2554
- const lastCert = result[result.length - 1];
2555
- const lastInfo = exploreCertificate(lastCert);
2556
- if (isSelfSigned2(lastInfo)) {
2557
- const wasExtended = result.length > chain.length;
2558
- return {
2559
- chain: result,
2560
- status: wasExtended ? "ChainCompleted" /* ChainCompleted */ : "AlreadyComplete" /* AlreadyComplete */,
2561
- message: wasExtended ? `Chain completed: ${result.length - chain.length} issuer(s) appended, ending at self-signed root "${lastInfo.tbsCertificate.subject.commonName}".` : `Chain is already complete (self-signed root "${lastInfo.tbsCertificate.subject.commonName}").`
2562
- };
2563
- }
2564
- const issuerCert = await this.findIssuerCertificate(lastCert);
2565
- if (!issuerCert) {
2566
- const cn = lastInfo.tbsCertificate.subject.commonName ?? "?";
2567
- const akid = lastInfo.tbsCertificate.extensions?.authorityKeyIdentifier?.keyIdentifier ?? "?";
2568
- const msg = `Cannot find issuer for "${cn}" (authorityKeyIdentifier: ${akid}). Ensure the CA certificate is present in the issuers/certs folder.`;
2569
- warningLog(`completeCertificateChain: ${msg}`);
2570
- return {
2571
- chain: result,
2572
- status: "IssuerNotFound" /* IssuerNotFound */,
2573
- message: msg
2574
- };
2575
- }
2576
- const issuerFingerprint = makeFingerprint(issuerCert);
2577
- const alreadyInChain = result.some((c) => makeFingerprint(c) === issuerFingerprint);
2578
- if (alreadyInChain) {
2579
- return {
2580
- chain: result,
2581
- status: "AlreadyComplete" /* AlreadyComplete */,
2582
- message: `Chain ends at root "${exploreCertificate(issuerCert).tbsCertificate.subject.commonName}" (already present in chain).`
2583
- };
2584
- }
2585
- result.push(issuerCert);
2586
- depth++;
2587
- }
2588
- return {
2589
- chain: result,
2590
- status: "MaxDepthReached" /* MaxDepthReached */,
2591
- message: `Chain completion stopped after ${maxDepth} iterations \u2014 possible circular chain or very deep hierarchy.`
2592
- };
2593
- }
2594
- /**
2595
- *
2596
- * check if the certificate explicitly appear in the trust list, the reject list or none.
2597
- * In case of being in the reject and trusted list at the same time is consider: rejected.
2598
- * @internal
2599
- * @private
2600
- */
2601
- async #checkRejectedOrTrusted(certificate) {
2602
- const firstCertificate = coerceCertificateChain(certificate)[0];
2603
- const fingerprint = makeFingerprint(firstCertificate);
2604
- debugLog("#checkRejectedOrTrusted fingerprint ", short(fingerprint));
2605
- await this.#readCertificates();
2606
- if (this.#thumbs.rejected.has(fingerprint)) {
2607
- return "rejected";
2608
- }
2609
- if (this.#thumbs.trusted.has(fingerprint)) {
2610
- return "trusted";
2611
- }
2612
- return "unknown";
2613
- }
2614
- async #moveCertificate(certificateOrChain, newStatus) {
2615
- await this.withLock2(async () => {
2616
- const chain = coerceCertificateChain(certificateOrChain);
2617
- const certificate = chain[0];
2618
- const fingerprint = makeFingerprint(certificate);
2619
- let status = await this.#checkRejectedOrTrusted(certificate);
2620
- if (status === "unknown") {
2621
- const pem = toPem(chain, "CERTIFICATE");
2622
- const filename = safeStoreJoin(this.rejectedFolder, `${buildIdealCertificateName(certificate)}.pem`);
2623
- await fs7.promises.writeFile(filename, pem);
2624
- this.#thumbs.rejected.set(fingerprint, { certificate, filename });
2625
- status = "rejected";
2626
- }
2627
- debugLog("#moveCertificate", fingerprint.substring(0, 10), "from", status, "to", newStatus);
2628
- if (status !== "rejected" && status !== "trusted") {
2629
- throw new Error(`#moveCertificate: unexpected status '${status}' for certificate ${fingerprint.substring(0, 10)}`);
2630
- }
2631
- if (status !== newStatus) {
2632
- const indexSrc = status === "rejected" ? this.#thumbs.rejected : this.#thumbs.trusted;
2633
- const srcEntry = indexSrc.get(fingerprint);
2634
- if (!srcEntry) {
2635
- debugLog(" cannot find certificate ", fingerprint.substring(0, 10), " in", status);
2636
- throw new Error(`#moveCertificate: certificate ${fingerprint.substring(0, 10)} not found in ${status} index`);
2637
- }
2638
- const destFolder = newStatus === "trusted" ? this.trustedFolder : this.rejectedFolder;
2639
- const certificateDest = safeStoreJoin(destFolder, path4.basename(srcEntry.filename));
2640
- debugLog("#moveCertificate", fingerprint.substring(0, 10), "old name", srcEntry.filename);
2641
- debugLog("#moveCertificate", fingerprint.substring(0, 10), "new name", certificateDest);
2642
- await fs7.promises.rename(srcEntry.filename, certificateDest);
2643
- indexSrc.delete(fingerprint);
2644
- const indexDest = newStatus === "trusted" ? this.#thumbs.trusted : this.#thumbs.rejected;
2645
- indexDest.set(fingerprint, { certificate, filename: certificateDest });
2646
- }
2647
- });
2648
- }
2649
- #findAssociatedCRLs(issuerCertificate) {
2650
- const issuerCertificateInfo = exploreCertificate(issuerCertificate);
2651
- const key = issuerCertificateInfo.tbsCertificate.subjectFingerPrint;
2652
- return this.#thumbs.issuersCrl.get(key) ?? this.#thumbs.crl.get(key) ?? null;
2653
- }
2654
- /**
2655
- * Check whether a certificate has been revoked by its issuer's CRL.
2656
- *
2657
- * - Self-signed certificates are never considered revoked.
2658
- * - If no `issuerCertificate` is provided, the method attempts
2659
- * to find it via {@link findIssuerCertificate}.
2660
- *
2661
- * @param certificate - the DER-encoded certificate to check
2662
- * @param issuerCertificate - optional issuer certificate; looked
2663
- * up automatically when omitted
2664
- * @returns `Good` if not revoked, `BadCertificateRevoked` if the
2665
- * serial number appears in a CRL,
2666
- * `BadCertificateRevocationUnknown` if no CRL is available,
2667
- * or `BadCertificateChainIncomplete` if the issuer cannot be
2668
- * found.
2669
- */
2670
- async isCertificateRevoked(certificate, issuerCertificate) {
2671
- const chain = coerceCertificateChain(certificate);
2672
- const firstCertificate = chain[0];
2673
- if (isSelfSigned3(firstCertificate)) {
2674
- return "Good" /* Good */;
2675
- }
2676
- if (!issuerCertificate) {
2677
- issuerCertificate = await this.findIssuerCertificate(firstCertificate);
2678
- }
2679
- if (!issuerCertificate) {
2680
- issuerCertificate = findIssuerCertificateInChain(firstCertificate, chain);
2681
- }
2682
- if (!issuerCertificate) {
2683
- return "BadCertificateChainIncomplete" /* BadCertificateChainIncomplete */;
2684
- }
2685
- const crls = this.#findAssociatedCRLs(issuerCertificate);
2686
- if (!crls) {
2687
- return "BadCertificateRevocationUnknown" /* BadCertificateRevocationUnknown */;
2688
- }
2689
- const certInfo = exploreCertificate(firstCertificate);
2690
- const serialNumber = certInfo.tbsCertificate.serialNumber || certInfo.tbsCertificate.extensions?.authorityKeyIdentifier?.serial || "";
2691
- const key = certInfo.tbsCertificate.extensions?.authorityKeyIdentifier?.authorityCertIssuerFingerPrint || "<unknown>";
2692
- const crl2 = this.#thumbs.crl.get(key) ?? null;
2693
- if (crls.serialNumbers[serialNumber] || crl2?.serialNumbers[serialNumber]) {
2694
- return "BadCertificateRevoked" /* BadCertificateRevoked */;
2695
- }
2696
- return "Good" /* Good */;
2697
- }
2698
- #pendingCrlToProcess = 0;
2699
- #onCrlProcessWaiters = [];
2700
- #queue = [];
2701
- #onCrlFileAdded(index, filename) {
2702
- this.#queue.push({ index, filename });
2703
- this.#pendingCrlToProcess += 1;
2704
- if (this.#pendingCrlToProcess === 1) {
2705
- this.#processNextCrl();
2706
- }
2707
- }
2708
- async #processNextCrl() {
2709
- try {
2710
- const nextCRL = this.#queue.shift();
2711
- if (!nextCRL) return;
2712
- const { index, filename } = nextCRL;
2713
- const crl = await readCertificateRevocationList(filename);
2714
- const crlInfo = exploreCertificateRevocationList(crl);
2715
- debugLog(chalk3.cyan("add CRL in folder "), filename);
2716
- const fingerprint = crlInfo.tbsCertList.issuerFingerprint;
2717
- if (!index.has(fingerprint)) {
2718
- index.set(fingerprint, { crls: [], serialNumbers: {} });
2719
- }
2720
- const data = index.get(fingerprint) || { crls: [], serialNumbers: {} };
2721
- data.crls.push({ crlInfo, filename });
2722
- for (const revokedCertificate of crlInfo.tbsCertList.revokedCertificates) {
2723
- const serialNumber = revokedCertificate.userCertificate;
2724
- if (!data.serialNumbers[serialNumber]) {
2725
- data.serialNumbers[serialNumber] = revokedCertificate.revocationDate;
2726
- }
2727
- }
2728
- debugLog(chalk3.cyan("CRL"), fingerprint, "serial numbers = ", Object.keys(data.serialNumbers));
2729
- } catch (err) {
2730
- debugLog("CRL filename error =");
2731
- debugLog(err);
2732
- }
2733
- this.#pendingCrlToProcess -= 1;
2734
- if (this.#pendingCrlToProcess === 0) {
2735
- for (const waiter of this.#onCrlProcessWaiters) {
2736
- waiter();
2737
- }
2738
- this.#onCrlProcessWaiters.length = 0;
2739
- } else {
2740
- this.#processNextCrl();
2741
- }
2742
- }
2743
- async #readCertificates() {
2744
- if (this.#readCertificatesCalled) {
2745
- return;
2746
- }
2747
- this.#readCertificatesCalled = true;
2748
- const usePolling = process.env.OPCUA_PKI_USE_POLLING === "true";
2749
- const envInterval = process.env.OPCUA_PKI_POLLING_INTERVAL ? parseInt(process.env.OPCUA_PKI_POLLING_INTERVAL, 10) : void 0;
2750
- const pollingInterval = Math.min(10 * 60 * 1e3, Math.max(100, envInterval ?? this.folderPollingInterval));
2751
- const chokidarOptions = {
2752
- usePolling,
2753
- ...usePolling ? { interval: pollingInterval } : {},
2754
- depth: 0,
2755
- persistent: false
2756
- };
2757
- const allCapturedHandles = [];
2758
- const origWatch = fs7.watch;
2759
- let watcherReadyCount = 0;
2760
- const totalWatchers = 5;
2761
- fs7.watch = ((...args) => {
2762
- const handle = origWatch.apply(fs7, args);
2763
- handle.setMaxListeners(handle.getMaxListeners() + 1);
2764
- handle.on("error", () => {
2765
- });
2766
- allCapturedHandles.push(handle);
2767
- return handle;
2768
- });
2769
- const createUnreffedWatcher = (folder) => {
2770
- const startIdx = allCapturedHandles.length;
2771
- const w = chokidar.watch(folder, chokidarOptions);
2772
- const unreffAll = () => {
2773
- for (let i = startIdx; i < allCapturedHandles.length; i++) {
2774
- allCapturedHandles[i].unref();
2775
- }
2776
- watcherReadyCount++;
2777
- if (watcherReadyCount >= totalWatchers) {
2778
- fs7.watch = origWatch;
2779
- }
2780
- };
2781
- return { w, capturedHandles: allCapturedHandles.slice(startIdx), unreffAll };
2782
- };
2783
- await Promise.all([
2784
- this.#scanCertFolder(this.trustedFolder, this.#thumbs.trusted),
2785
- this.#scanCertFolder(this.issuersCertFolder, this.#thumbs.issuers.certs),
2786
- this.#scanCertFolder(this.rejectedFolder, this.#thumbs.rejected),
2787
- this.#scanCrlFolder(this.crlFolder, this.#thumbs.crl),
2788
- this.#scanCrlFolder(this.issuersCrlFolder, this.#thumbs.issuersCrl)
2789
- ]);
2790
- if (this.#disableFileWatchers) {
2791
- fs7.watch = origWatch;
2792
- } else {
2793
- this.#startWatcher(this.trustedFolder, this.#thumbs.trusted, createUnreffedWatcher, "trusted");
2794
- this.#startWatcher(this.issuersCertFolder, this.#thumbs.issuers.certs, createUnreffedWatcher, "issuersCerts");
2795
- this.#startWatcher(this.rejectedFolder, this.#thumbs.rejected, createUnreffedWatcher, "rejected");
2796
- this.#startCrlWatcher(this.crlFolder, this.#thumbs.crl, createUnreffedWatcher, "crl");
2797
- this.#startCrlWatcher(this.issuersCrlFolder, this.#thumbs.issuersCrl, createUnreffedWatcher, "issuersCrl");
2798
- }
2799
- }
2800
- /**
2801
- * Scan a certificate folder and populate the in-memory index.
2802
- * Uses async readdir/stat to yield the event loop between
2803
- * file reads, preventing main-loop stalls with large folders.
2804
- */
2805
- async #scanCertFolder(folder, index) {
2806
- if (!fs7.existsSync(folder)) return;
2807
- const files = await fs7.promises.readdir(folder);
2808
- for (const file of files) {
2809
- const filename = path4.join(folder, file);
2810
- try {
2811
- const stat = await fs7.promises.stat(filename);
2812
- if (!stat.isFile()) continue;
2813
- const certs = await readCertificateChainAsync(filename);
2814
- if (certs.length === 0) continue;
2815
- const certificate = certs[0];
2816
- if (certs.length > 1) {
2817
- try {
2818
- await fs7.promises.writeFile(filename, toPem(certs, "CERTIFICATE"), "ascii");
2819
- } catch (writeErr) {
2820
- debugLog(`scanCertFolder: could not rewrite legacy PEM ${filename} (read-only fs?)`, writeErr);
2821
- }
2822
- for (let i = 1; i < certs.length; i++) {
2823
- if (isIssuer(certs[i])) {
2824
- try {
2825
- await this.addIssuer(certs[i]);
2826
- } catch (issuerErr) {
2827
- debugLog(`scanCertFolder: could not auto-register issuer from ${filename}`, issuerErr);
2828
- }
2829
- }
2830
- }
2831
- }
2832
- const info = exploreCertificate(certificate);
2833
- const fingerprint = makeFingerprint(certificate);
2834
- index.set(fingerprint, { certificate, filename, info });
2835
- this.#filenameToHash.set(filename, fingerprint);
2836
- } catch (err) {
2837
- debugLog(`scanCertFolder: skipping ${filename}`, err);
2838
- }
2839
- }
2840
- }
2841
- /**
2842
- * Scan a CRL folder and populate the in-memory CRL index.
2843
- */
2844
- async #scanCrlFolder(folder, index) {
2845
- if (!fs7.existsSync(folder)) return;
2846
- const files = await fs7.promises.readdir(folder);
2847
- for (const file of files) {
2848
- const filename = path4.join(folder, file);
2849
- try {
2850
- const stat = await fs7.promises.stat(filename);
2851
- if (!stat.isFile()) continue;
2852
- this.#onCrlFileAdded(index, filename);
2853
- } catch (err) {
2854
- debugLog(`scanCrlFolder: skipping ${filename}`, err);
2855
- }
2856
- }
2857
- await this.#waitAndCheckCRLProcessingStatus();
2858
- }
2859
- /**
2860
- * Start a chokidar watcher for a CRL folder.
2861
- * Non-blocking — does NOT await "ready".
2862
- */
2863
- #startCrlWatcher(folder, index, createUnreffedWatcher, store) {
2864
- const { w, unreffAll } = createUnreffedWatcher(folder);
2865
- w.on("error", (err) => {
2866
- debugLog(`chokidar CRL watcher error on ${folder}:`, err);
2867
- });
2868
- let ready = false;
2869
- w.on("unlink", (filename) => {
2870
- for (const [key, data] of index.entries()) {
2871
- data.crls = data.crls.filter((c) => c.filename !== filename);
2872
- if (data.crls.length === 0) {
2873
- index.delete(key);
2874
- }
2875
- }
2876
- if (ready) {
2877
- this.emit("crlRemoved", { store, filename });
2878
- }
2879
- });
2880
- w.on("add", (filename) => {
2881
- if (ready) {
2882
- this.#onCrlFileAdded(index, filename);
2883
- this.emit("crlAdded", { store, filename });
2884
- }
2885
- });
2886
- w.on("change", (changedPath) => {
2887
- debugLog("change in folder ", folder, changedPath);
2888
- });
2889
- this.#watchers.push(w);
2890
- this.#pendingUnrefs.add(unreffAll);
2891
- w.on("ready", () => {
2892
- ready = true;
2893
- this.#pendingUnrefs.delete(unreffAll);
2894
- unreffAll();
2895
- });
2896
- }
2897
- /**
2898
- * Start a chokidar watcher for a certificate folder.
2899
- * Non-blocking — does NOT await "ready".
2900
- */
2901
- #startWatcher(folder, index, createUnreffedWatcher, store) {
2902
- const { w, unreffAll } = createUnreffedWatcher(folder);
2903
- w.on("error", (err) => {
2904
- debugLog(`chokidar cert watcher error on ${folder}:`, err);
2905
- });
2906
- let ready = false;
2907
- w.on("unlink", (filename) => {
2908
- debugLog(chalk3.cyan(`unlink in folder ${folder}`), filename);
2909
- const h = this.#filenameToHash.get(filename);
2910
- if (h && index.has(h)) {
2911
- index.delete(h);
2912
- this.emit("certificateRemoved", { store, fingerprint: h, filename });
2913
- }
2914
- });
2915
- w.on("add", (filename) => {
2916
- debugLog(chalk3.cyan(`add in folder ${folder}`), filename);
2917
- try {
2918
- const certificate = readCertificateChain(filename)[0];
2919
- const info = exploreCertificate(certificate);
2920
- const fingerprint = makeFingerprint(certificate);
2921
- const isNew = !index.has(fingerprint);
2922
- index.set(fingerprint, { certificate, filename, info });
2923
- this.#filenameToHash.set(filename, fingerprint);
2924
- debugLog(
2925
- chalk3.magenta("CERT"),
2926
- info.tbsCertificate.subjectFingerPrint,
2927
- info.tbsCertificate.serialNumber,
2928
- info.tbsCertificate.extensions?.authorityKeyIdentifier?.authorityCertIssuerFingerPrint
2929
- );
2930
- if (ready || isNew) {
2931
- this.emit("certificateAdded", { store, certificate, fingerprint, filename });
2932
- }
2933
- } catch (err) {
2934
- debugLog(`Walk files in folder ${folder} with file ${filename}`);
2935
- debugLog(err);
2936
- }
2937
- });
2938
- w.on("change", (changedPath) => {
2939
- debugLog(chalk3.cyan(`change in folder ${folder}`), changedPath);
2940
- try {
2941
- const certificate = readCertificateChain(changedPath)[0];
2942
- const newFingerprint = makeFingerprint(certificate);
2943
- const oldHash = this.#filenameToHash.get(changedPath);
2944
- if (oldHash && oldHash !== newFingerprint) {
2945
- index.delete(oldHash);
2946
- }
2947
- index.set(newFingerprint, { certificate, filename: changedPath, info: exploreCertificate(certificate) });
2948
- this.#filenameToHash.set(changedPath, newFingerprint);
2949
- this.emit("certificateChange", { store, certificate, fingerprint: newFingerprint, filename: changedPath });
2950
- } catch (err) {
2951
- debugLog(`change event: failed to re-read ${changedPath}`, err);
2952
- }
2953
- });
2954
- this.#watchers.push(w);
2955
- this.#pendingUnrefs.add(unreffAll);
2956
- w.on("ready", () => {
2957
- ready = true;
2958
- this.#pendingUnrefs.delete(unreffAll);
2959
- unreffAll();
2960
- debugLog("ready");
2961
- debugLog([...index.keys()].map((k) => k.substring(0, 10)));
2962
- });
2963
- }
2964
- // make sure that all crls have been processed.
2965
- async #waitAndCheckCRLProcessingStatus() {
2966
- return new Promise((resolve, _reject) => {
2967
- if (this.#pendingCrlToProcess === 0) {
2968
- setImmediate(resolve);
2969
- return;
2970
- }
2971
- this.#onCrlProcessWaiters.push(resolve);
2972
- });
2973
- }
2974
- };
2975
-
2976
- // packages/node-opcua-pki/lib/pki/toolbox_pfx.ts
2977
- import assert5 from "assert";
2978
- import fs8 from "fs";
2979
- import {
2980
- certificateMatchesPrivateKey,
2981
- certificatesToPem,
2982
- coercePrivateKeyPem as coercePrivateKeyPem3,
2983
- createPfx,
2984
- makeSHA1Thumbprint as makeSHA1Thumbprint2,
2985
- Pkcs12UnsupportedAlgorithmError,
2986
- parsePfx,
2987
- readCertificateChain as readCertificateChain2,
2988
- readPrivateKey as readPrivateKey2,
2989
- toPem as toPem2,
2990
- x509
2991
- } from "node-opcua-crypto";
2992
- async function readPfxFile(options) {
2993
- const { pfxFile, passphrase = "" } = options;
2994
- assert5(fs8.existsSync(pfxFile), `PFX file does not exist: ${pfxFile}`);
2995
- try {
2996
- return await parsePfx(await fs8.promises.readFile(pfxFile), passphrase);
2997
- } catch (err) {
2998
- if (err instanceof Pkcs12UnsupportedAlgorithmError) {
2999
- const explained = new Error(
3000
- [
3001
- `${pfxFile} uses an encryption algorithm that is not supported (${err.message}).`,
3002
- "Bundles written by openssl 1.x default to 40-bit RC2, which openssl 3 itself",
3003
- "rejects unless given -legacy. Convert the file once with:",
3004
- " openssl pkcs12 -legacy -in old.pfx -nodes -out tmp.pem",
3005
- " openssl pkcs12 -export -in tmp.pem -out new.pfx"
3006
- ].join("\n")
3007
- );
3008
- explained.cause = err;
3009
- throw explained;
3010
- }
3011
- throw err;
3012
- }
3013
- }
3014
- async function createPFX(options) {
3015
- const { certificateFile, privateKeyFile, privateKeyPassphrase, outputFile, passphrase = "", caCertificateFiles } = options;
3016
- assert5(fs8.existsSync(certificateFile), `Certificate file does not exist: ${certificateFile}`);
3017
- assert5(fs8.existsSync(privateKeyFile), `Private key file does not exist: ${privateKeyFile}`);
3018
- const [certificate, ...bundledIssuers] = readCertificateChain2(certificateFile);
3019
- if (!certificate) {
3020
- throw new Error(`Certificate file holds no certificate: ${certificateFile}`);
3021
- }
3022
- const certificateChain = [...bundledIssuers];
3023
- for (const caFile of caCertificateFiles ?? []) {
3024
- assert5(fs8.existsSync(caFile), `CA certificate file does not exist: ${caFile}`);
3025
- certificateChain.push(...readCertificateChain2(caFile));
3026
- }
3027
- const privateKey = readPrivateKey2(privateKeyFile, privateKeyPassphrase);
3028
- if (!certificateMatchesPrivateKey(certificate, privateKey)) {
3029
- throw new Error(`The private key ${privateKeyFile} does not match the certificate ${certificateFile}`);
3030
- }
3031
- const pfx = await createPfx({
3032
- certificate,
3033
- certificateChain,
3034
- privateKey,
3035
- password: passphrase,
3036
- friendlyName: options.friendlyName
3037
- });
3038
- await fs8.promises.writeFile(outputFile, pfx);
3039
- }
3040
- async function extractCertificateFromPFX(options) {
3041
- const { certificate } = await readPfxFile(options);
3042
- return toPem2(certificate, "CERTIFICATE");
3043
- }
3044
- async function extractPrivateKeyFromPFX(options) {
3045
- const { privateKey } = await readPfxFile(options);
3046
- return coercePrivateKeyPem3(privateKey);
3047
- }
3048
- async function extractCACertificatesFromPFX(options) {
3049
- const { certificateChain } = await readPfxFile(options);
3050
- return certificateChain.length === 0 ? "" : certificatesToPem(certificateChain);
3051
- }
3052
- async function extractAllFromPFX(options) {
3053
- const { certificate, certificateChain, privateKey, friendlyName } = await readPfxFile(options);
3054
- return {
3055
- certificate: toPem2(certificate, "CERTIFICATE"),
3056
- privateKey: coercePrivateKeyPem3(privateKey),
3057
- caCertificates: certificateChain.length === 0 ? "" : certificatesToPem(certificateChain),
3058
- friendlyName
3059
- };
3060
- }
3061
- async function convertPFXtoPEM(pfxFile, pemFile, passphrase = "") {
3062
- const { certificate, certificateChain, privateKey } = await readPfxFile({ pfxFile, passphrase });
3063
- const parts = [coercePrivateKeyPem3(privateKey), toPem2(certificate, "CERTIFICATE")];
3064
- if (certificateChain.length > 0) {
3065
- parts.push(certificatesToPem(certificateChain));
3066
- }
3067
- await fs8.promises.writeFile(pemFile, `${parts.map((part) => part.trimEnd()).join("\n")}
3068
- `, "utf-8");
3069
- }
3070
- function formatThumbprint(certificate) {
3071
- return (makeSHA1Thumbprint2(certificate).toString("hex").toUpperCase().match(/../g) ?? []).join(":");
3072
- }
3073
- function describePublicKey(certificate) {
3074
- const algorithm = certificate.publicKey.algorithm;
3075
- if (algorithm.modulusLength) {
3076
- return `${algorithm.name} ${algorithm.modulusLength} bits`;
3077
- }
3078
- if (algorithm.namedCurve) {
3079
- return `${algorithm.name} ${algorithm.namedCurve}`;
3080
- }
3081
- return algorithm.name;
3082
- }
3083
- function describeCertificate(der, indent) {
3084
- const certificate = new x509.X509Certificate(der);
3085
- return [
3086
- `${indent}subject : ${certificate.subject}`,
3087
- `${indent}issuer : ${certificate.issuer}`,
3088
- `${indent}serial number : ${certificate.serialNumber}`,
3089
- `${indent}not before : ${certificate.notBefore.toISOString()}`,
3090
- `${indent}not after : ${certificate.notAfter.toISOString()}`,
3091
- `${indent}public key : ${describePublicKey(certificate)}`,
3092
- `${indent}signature : ${certificate.signatureAlgorithm.name}`,
3093
- `${indent}SHA-1 thumbprint : ${formatThumbprint(der)}`
3094
- ];
3095
- }
3096
- async function dumpPFX(pfxFile, passphrase = "") {
3097
- const { certificate, certificateChain, privateKey, friendlyName } = await readPfxFile({ pfxFile, passphrase });
3098
- const lines = [`PFX bundle : ${pfxFile}`];
3099
- if (friendlyName) {
3100
- lines.push(`friendly name : ${friendlyName}`);
3101
- }
3102
- const keyVerdict = certificateMatchesPrivateKey(certificate, privateKey) ? "matches this certificate" : "DOES NOT match this certificate";
3103
- lines.push("", "certificate", ...describeCertificate(certificate, " "), ` private key : ${keyVerdict}`);
3104
- lines.push("", `issuer chain : ${certificateChain.length} certificate(s)`);
3105
- for (const [index, issuer] of certificateChain.entries()) {
3106
- lines.push(` [${index}]`, ...describeCertificate(issuer, " "));
3107
- }
3108
- return `${lines.join("\n")}
3109
- `;
3110
- }
3111
- export {
3112
- CertificateAuthority,
3113
- CertificateAuthorityCore,
3114
- CertificateManager,
3115
- CertificateManagerState,
3116
- ChainCompletionStatus,
3117
- NativeCaBackend,
3118
- OpenSslCaBackend,
3119
- PrivateKeyPassphraseRequiredError,
3120
- PrivateKeyUnavailableError2 as PrivateKeyUnavailableError,
3121
- Subject,
3122
- VerificationStatus,
3123
- adjustApplicationUri,
3124
- adjustDate,
3125
- coerceCertificateChain,
3126
- convertPFXtoPEM,
3127
- createPFX,
3128
- dumpPFX,
3129
- extractAllFromPFX,
3130
- extractCACertificatesFromPFX,
3131
- extractCertificateFromPFX,
3132
- extractPrivateKeyFromPFX,
3133
- findIssuerCertificateInChain,
3134
- install_prerequisite,
3135
- isIntermediateIssuer,
3136
- isIssuer,
3137
- isOpaqueSigner,
3138
- isRootIssuer,
3139
- makeFingerprint,
3140
- quote,
3141
- resolvePrivateKeyPassphrase,
3142
- safeStoreJoin,
3143
- toFilenameLabel
3144
- };
3145
- //# sourceMappingURL=index.mjs.map