node-opcua-pki 6.22.1 → 7.0.1

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 (138) hide show
  1. package/bin/install_prerequisite.ts +1 -1
  2. package/bin/pki.ts +1 -1
  3. package/bin/pki_config.example.js +0 -1
  4. package/dist/bin/install_prerequisite.d.ts +2 -0
  5. package/dist/bin/install_prerequisite.js +6 -0
  6. package/dist/bin/install_prerequisite.js.map +1 -0
  7. package/dist/bin/pki.d.ts +2 -0
  8. package/dist/bin/pki.js +4 -0
  9. package/dist/bin/pki.js.map +1 -0
  10. package/dist/lib/ca/backends/native_ca_backend.d.ts +49 -0
  11. package/dist/lib/ca/backends/native_ca_backend.js +365 -0
  12. package/dist/lib/ca/backends/native_ca_backend.js.map +1 -0
  13. package/dist/lib/ca/backends/openssl_ca_backend.d.ts +47 -0
  14. package/dist/lib/ca/backends/openssl_ca_backend.js +398 -0
  15. package/dist/lib/ca/backends/openssl_ca_backend.js.map +1 -0
  16. package/dist/lib/ca/certificate_authority.d.ts +62 -0
  17. package/dist/lib/ca/certificate_authority.js +91 -0
  18. package/dist/lib/ca/certificate_authority.js.map +1 -0
  19. package/dist/lib/ca/core/ca_backend.d.ts +89 -0
  20. package/dist/lib/ca/core/ca_backend.js +24 -0
  21. package/dist/lib/ca/core/ca_backend.js.map +1 -0
  22. package/dist/lib/ca/core/ca_database.d.ts +91 -0
  23. package/dist/lib/ca/core/ca_database.js +267 -0
  24. package/dist/lib/ca/core/ca_database.js.map +1 -0
  25. package/dist/{core-BwqSJPHc.d.mts → lib/ca/core/certificate_authority_core.d.ts} +45 -394
  26. package/dist/lib/ca/core/certificate_authority_core.js +1300 -0
  27. package/dist/lib/ca/core/certificate_authority_core.js.map +1 -0
  28. package/dist/lib/ca/core/index.d.ts +17 -0
  29. package/dist/lib/ca/core/index.js +25 -0
  30. package/dist/lib/ca/core/index.js.map +1 -0
  31. package/dist/lib/ca/crypto_create_CA.d.ts +1 -0
  32. package/dist/lib/ca/crypto_create_CA.js +821 -0
  33. package/dist/lib/ca/crypto_create_CA.js.map +1 -0
  34. package/dist/lib/ca/index.d.ts +1 -0
  35. package/dist/lib/ca/index.js +2 -0
  36. package/dist/lib/ca/index.js.map +1 -0
  37. package/dist/lib/ca/templates/ca_config_template.cnf.d.ts +2 -0
  38. package/dist/lib/ca/templates/ca_config_template.cnf.js +170 -0
  39. package/dist/lib/ca/templates/ca_config_template.cnf.js.map +1 -0
  40. package/dist/lib/core.d.ts +33 -0
  41. package/dist/lib/core.js +56 -0
  42. package/dist/lib/core.js.map +1 -0
  43. package/dist/lib/index.d.ts +11 -0
  44. package/dist/lib/index.js +37 -0
  45. package/dist/lib/index.js.map +1 -0
  46. package/dist/lib/misc/applicationurn.d.ts +1 -0
  47. package/dist/lib/misc/applicationurn.js +40 -0
  48. package/dist/lib/misc/applicationurn.js.map +1 -0
  49. package/dist/lib/misc/hostname.d.ts +8 -0
  50. package/dist/lib/misc/hostname.js +82 -0
  51. package/dist/lib/misc/hostname.js.map +1 -0
  52. package/dist/lib/misc/subject.d.ts +2 -0
  53. package/dist/lib/misc/subject.js +2 -0
  54. package/dist/lib/misc/subject.js.map +1 -0
  55. package/dist/{index.d.ts → lib/pki/certificate_manager.d.ts} +24 -248
  56. package/dist/lib/pki/certificate_manager.js +2302 -0
  57. package/dist/lib/pki/certificate_manager.js.map +1 -0
  58. package/dist/lib/pki/templates/simple_config_template.cnf.d.ts +2 -0
  59. package/dist/lib/pki/templates/simple_config_template.cnf.js +74 -0
  60. package/dist/lib/pki/templates/simple_config_template.cnf.js.map +1 -0
  61. package/dist/lib/pki/toolbox_pfx.d.ts +127 -0
  62. package/dist/lib/pki/toolbox_pfx.js +248 -0
  63. package/dist/lib/pki/toolbox_pfx.js.map +1 -0
  64. package/dist/lib/toolbox/common.d.ts +150 -0
  65. package/dist/lib/toolbox/common.js +82 -0
  66. package/dist/lib/toolbox/common.js.map +1 -0
  67. package/dist/lib/toolbox/common2.d.ts +25 -0
  68. package/dist/lib/toolbox/common2.js +97 -0
  69. package/dist/lib/toolbox/common2.js.map +1 -0
  70. package/dist/lib/toolbox/config.d.ts +5 -0
  71. package/dist/lib/toolbox/config.js +28 -0
  72. package/dist/lib/toolbox/config.js.map +1 -0
  73. package/dist/lib/toolbox/debug.d.ts +5 -0
  74. package/dist/lib/toolbox/debug.js +35 -0
  75. package/dist/lib/toolbox/debug.js.map +1 -0
  76. package/dist/lib/toolbox/display.d.ts +4 -0
  77. package/dist/lib/toolbox/display.js +56 -0
  78. package/dist/lib/toolbox/display.js.map +1 -0
  79. package/dist/lib/toolbox/index.d.ts +5 -0
  80. package/dist/lib/toolbox/index.js +28 -0
  81. package/dist/lib/toolbox/index.js.map +1 -0
  82. package/dist/lib/toolbox/with_openssl/_create_random_file.d.ts +4 -0
  83. package/dist/lib/toolbox/with_openssl/_create_random_file.js +50 -0
  84. package/dist/lib/toolbox/with_openssl/_create_random_file.js.map +1 -0
  85. package/dist/lib/toolbox/with_openssl/_env.d.ts +58 -0
  86. package/dist/lib/toolbox/with_openssl/_env.js +126 -0
  87. package/dist/lib/toolbox/with_openssl/_env.js.map +1 -0
  88. package/dist/lib/toolbox/with_openssl/create_certificate_signing_request.d.ts +5 -0
  89. package/dist/lib/toolbox/with_openssl/create_certificate_signing_request.js +72 -0
  90. package/dist/lib/toolbox/with_openssl/create_certificate_signing_request.js.map +1 -0
  91. package/dist/lib/toolbox/with_openssl/create_private_key.d.ts +5 -0
  92. package/dist/lib/toolbox/with_openssl/create_private_key.js +92 -0
  93. package/dist/lib/toolbox/with_openssl/create_private_key.js.map +1 -0
  94. package/dist/lib/toolbox/with_openssl/create_self_signed_certificate.d.ts +5 -0
  95. package/dist/lib/toolbox/with_openssl/create_self_signed_certificate.js +135 -0
  96. package/dist/lib/toolbox/with_openssl/create_self_signed_certificate.js.map +1 -0
  97. package/dist/lib/toolbox/with_openssl/execute_openssl.d.ts +67 -0
  98. package/dist/lib/toolbox/with_openssl/execute_openssl.js +246 -0
  99. package/dist/lib/toolbox/with_openssl/execute_openssl.js.map +1 -0
  100. package/dist/lib/toolbox/with_openssl/index.d.ts +5 -0
  101. package/dist/lib/toolbox/with_openssl/index.js +30 -0
  102. package/dist/lib/toolbox/with_openssl/index.js.map +1 -0
  103. package/dist/lib/toolbox/with_openssl/install_prerequisite.d.ts +7 -0
  104. package/dist/lib/toolbox/with_openssl/install_prerequisite.js +358 -0
  105. package/dist/lib/toolbox/with_openssl/install_prerequisite.js.map +1 -0
  106. package/dist/lib/toolbox/with_openssl/toolbox.d.ts +53 -0
  107. package/dist/lib/toolbox/with_openssl/toolbox.js +190 -0
  108. package/dist/lib/toolbox/with_openssl/toolbox.js.map +1 -0
  109. package/dist/lib/toolbox/without_openssl/create_certificate_signing_request.d.ts +5 -0
  110. package/dist/lib/toolbox/without_openssl/create_certificate_signing_request.js +69 -0
  111. package/dist/lib/toolbox/without_openssl/create_certificate_signing_request.js.map +1 -0
  112. package/dist/lib/toolbox/without_openssl/create_self_signed_certificate.d.ts +3 -0
  113. package/dist/lib/toolbox/without_openssl/create_self_signed_certificate.js +78 -0
  114. package/dist/lib/toolbox/without_openssl/create_self_signed_certificate.js.map +1 -0
  115. package/dist/lib/toolbox/without_openssl/index.d.ts +2 -0
  116. package/dist/lib/toolbox/without_openssl/index.js +25 -0
  117. package/dist/lib/toolbox/without_openssl/index.js.map +1 -0
  118. package/package.json +12 -31
  119. package/dist/bin/install_prerequisite.mjs +0 -18
  120. package/dist/bin/install_prerequisite.mjs.map +0 -1
  121. package/dist/bin/pki.mjs +0 -5932
  122. package/dist/bin/pki.mjs.map +0 -1
  123. package/dist/chunk-EQ4DGI4M.mjs +0 -1917
  124. package/dist/chunk-EQ4DGI4M.mjs.map +0 -1
  125. package/dist/chunk-GCHH54PS.mjs +0 -30
  126. package/dist/chunk-GCHH54PS.mjs.map +0 -1
  127. package/dist/core-BwqSJPHc.d.ts +0 -1041
  128. package/dist/core.d.mts +0 -2
  129. package/dist/core.d.ts +0 -2
  130. package/dist/core.js +0 -1902
  131. package/dist/core.js.map +0 -1
  132. package/dist/core.mjs +0 -21
  133. package/dist/core.mjs.map +0 -1
  134. package/dist/index.d.mts +0 -1003
  135. package/dist/index.js +0 -5009
  136. package/dist/index.js.map +0 -1
  137. package/dist/index.mjs +0 -3145
  138. package/dist/index.mjs.map +0 -1
package/dist/core.js DELETED
@@ -1,1902 +0,0 @@
1
- "use strict";
2
- var __create = Object.create;
3
- var __defProp = Object.defineProperty;
4
- var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
- var __getOwnPropNames = Object.getOwnPropertyNames;
6
- var __getProtoOf = Object.getPrototypeOf;
7
- var __hasOwnProp = Object.prototype.hasOwnProperty;
8
- var __export = (target, all) => {
9
- for (var name in all)
10
- __defProp(target, name, { get: all[name], enumerable: true });
11
- };
12
- var __copyProps = (to, from, except, desc) => {
13
- if (from && typeof from === "object" || typeof from === "function") {
14
- for (let key of __getOwnPropNames(from))
15
- if (!__hasOwnProp.call(to, key) && key !== except)
16
- __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
- }
18
- return to;
19
- };
20
- var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
- // If the importer is in node compatibility mode or this is not an ESM
22
- // file that has been converted to a CommonJS file using a Babel-
23
- // compatible transform (i.e. "__esModule" has not been set), then set
24
- // "default" to the CommonJS "module.exports" for node compatibility.
25
- isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
- mod
27
- ));
28
- var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
-
30
- // packages/node-opcua-pki/lib/core.ts
31
- var core_exports = {};
32
- __export(core_exports, {
33
- CaDatabase: () => CaDatabase,
34
- CertificateAuthorityCore: () => CertificateAuthorityCore,
35
- NativeCaBackend: () => NativeCaBackend,
36
- Subject: () => import_node_opcua_crypto4.Subject,
37
- configurationFileTemplate: () => configurationFileTemplate,
38
- defaultSubject: () => defaultSubject,
39
- escapeOpensslConfDoubleQuoted: () => escapeOpensslConfDoubleQuoted,
40
- renderCaConfig: () => renderCaConfig
41
- });
42
- module.exports = __toCommonJS(core_exports);
43
-
44
- // packages/node-opcua-pki/lib/ca/backends/native_ca_backend.ts
45
- var import_node_fs3 = __toESM(require("fs"));
46
- var import_node_path3 = __toESM(require("path"));
47
- var import_node_opcua_crypto2 = require("node-opcua-crypto");
48
-
49
- // packages/node-opcua-pki/lib/toolbox/common.ts
50
- var import_node_assert = __toESM(require("assert"));
51
- async function resolvePrivateKeyPassphrase(passphrase) {
52
- if (passphrase === void 0) {
53
- return void 0;
54
- }
55
- return typeof passphrase === "function" ? await passphrase() : passphrase;
56
- }
57
- function adjustDate(params) {
58
- (0, import_node_assert.default)(params instanceof Object);
59
- params.startDate = params.startDate || /* @__PURE__ */ new Date();
60
- (0, import_node_assert.default)(params.startDate instanceof Date);
61
- if (params.validityMs !== void 0) {
62
- if (params.validityMs <= 0) {
63
- throw new RangeError(`validityMs must be > 0 (got ${params.validityMs})`);
64
- }
65
- params.endDate = new Date(params.startDate.getTime() + params.validityMs);
66
- params.validity = Math.ceil(params.validityMs / 864e5);
67
- } else {
68
- params.validity = params.validity || 365;
69
- params.endDate = new Date(params.startDate.getTime());
70
- params.endDate.setDate(params.startDate.getDate() + params.validity);
71
- }
72
- (0, import_node_assert.default)(params.endDate instanceof Date);
73
- (0, import_node_assert.default)(params.startDate instanceof Date);
74
- }
75
- function adjustApplicationUri(params) {
76
- const applicationUri = params.applicationUri || "";
77
- if (applicationUri.length > 200) {
78
- throw new Error(`Openssl doesn't support urn with length greater than 200${applicationUri}`);
79
- }
80
- }
81
-
82
- // packages/node-opcua-pki/lib/toolbox/common2.ts
83
- var import_node_assert2 = __toESM(require("assert"));
84
- var import_node_fs = __toESM(require("fs"));
85
- var import_node_path = __toESM(require("path"));
86
- var import_chalk = __toESM(require("chalk"));
87
-
88
- // packages/node-opcua-pki/lib/toolbox/config.ts
89
- var g_config = {
90
- opensslVersion: "unset",
91
- silent: process.env.VERBOSE ? !process.env.VERBOSE : true,
92
- force: false
93
- };
94
-
95
- // packages/node-opcua-pki/lib/toolbox/debug.ts
96
- var doDebug = process.env.NODEOPCUAPKIDEBUG || false;
97
- var displayDebug = !!process.env.NODEOPCUAPKIDEBUG || false;
98
- function debugLog(...args) {
99
- if (displayDebug) {
100
- console.log.apply(null, args);
101
- }
102
- }
103
- function warningLog(...args) {
104
- console.log.apply(null, args);
105
- }
106
-
107
- // packages/node-opcua-pki/lib/toolbox/common2.ts
108
- function certificateFileExist(certificateFile) {
109
- if (import_node_fs.default.existsSync(certificateFile) && !g_config.force) {
110
- warningLog(
111
- import_chalk.default.yellow(" certificate ") + import_chalk.default.cyan(certificateFile) + import_chalk.default.yellow(" already exists => do not overwrite")
112
- );
113
- return false;
114
- }
115
- return true;
116
- }
117
- function mkdirRecursiveSync(folder) {
118
- if (!import_node_fs.default.existsSync(folder)) {
119
- debugLog(import_chalk.default.white(" .. constructing "), folder);
120
- import_node_fs.default.mkdirSync(folder, { recursive: true });
121
- }
122
- }
123
- function restrictPrivateFilePermissions(target, mode) {
124
- if (process.platform === "win32") {
125
- return;
126
- }
127
- try {
128
- import_node_fs.default.chmodSync(target, mode);
129
- } catch (err) {
130
- warningLog(import_chalk.default.yellow(" could not restrict permissions on "), target, err.message);
131
- }
132
- }
133
- function ensurePrivateDirectory(dir) {
134
- if (!import_node_fs.default.existsSync(dir)) {
135
- import_node_fs.default.mkdirSync(dir, { recursive: true, mode: 448 });
136
- }
137
- restrictPrivateFilePermissions(dir, 448);
138
- }
139
- function isEncryptedPrivateKeyFile(filename) {
140
- return import_node_fs.default.readFileSync(filename, "utf-8").includes("-----BEGIN ENCRYPTED PRIVATE KEY-----");
141
- }
142
- function makePath(folderName, filename) {
143
- let s;
144
- if (filename) {
145
- s = import_node_path.default.join(import_node_path.default.normalize(folderName), filename);
146
- } else {
147
- (0, import_node_assert2.default)(folderName);
148
- s = folderName;
149
- }
150
- s = s.replace(/\\/g, "/");
151
- return s;
152
- }
153
-
154
- // packages/node-opcua-pki/lib/toolbox/display.ts
155
- var import_chalk2 = __toESM(require("chalk"));
156
- function displayTitle(str) {
157
- if (!g_config.silent) {
158
- warningLog("");
159
- warningLog(import_chalk2.default.yellowBright(str));
160
- warningLog(import_chalk2.default.yellow(new Array(str.length + 1).join("=")), "\n");
161
- }
162
- }
163
- function displaySubtitle(str) {
164
- if (!g_config.silent) {
165
- warningLog("");
166
- warningLog(` ${import_chalk2.default.yellowBright(str)}`);
167
- warningLog(` ${import_chalk2.default.white(new Array(str.length + 1).join("-"))}`, "\n");
168
- }
169
- }
170
-
171
- // packages/node-opcua-pki/lib/ca/core/ca_database.ts
172
- var import_node_fs2 = __toESM(require("fs"));
173
- var import_node_path2 = __toESM(require("path"));
174
- var import_node_opcua_crypto = require("node-opcua-crypto");
175
- function parseOpenSSLDate(dateStr) {
176
- const raw = dateStr?.split(",")[0] ?? "";
177
- if (raw.length < 12) return "";
178
- const yy = parseInt(raw.substring(0, 2), 10);
179
- const year = yy >= 70 ? 1900 + yy : 2e3 + yy;
180
- const month = raw.substring(2, 4);
181
- const day = raw.substring(4, 6);
182
- const hour = raw.substring(6, 8);
183
- const min = raw.substring(8, 10);
184
- const sec = raw.substring(10, 12);
185
- return `${year}-${month}-${day}T${hour}:${min}:${sec}Z`;
186
- }
187
- function formatOpenSSLDate(date) {
188
- const yy = String(date.getUTCFullYear() % 100).padStart(2, "0");
189
- const mm = String(date.getUTCMonth() + 1).padStart(2, "0");
190
- const dd = String(date.getUTCDate()).padStart(2, "0");
191
- const hh = String(date.getUTCHours()).padStart(2, "0");
192
- const mi = String(date.getUTCMinutes()).padStart(2, "0");
193
- const ss = String(date.getUTCSeconds()).padStart(2, "0");
194
- return `${yy}${mm}${dd}${hh}${mi}${ss}Z`;
195
- }
196
- function parseRevocationReason(dateStr) {
197
- const parts = dateStr?.split(",");
198
- return parts && parts.length > 1 ? parts[1] : void 0;
199
- }
200
- function evenLengthHex(value) {
201
- const hex = value.toString(16).toUpperCase();
202
- return hex.length % 2 === 0 ? hex : `0${hex}`;
203
- }
204
- function escapeIndexField(value) {
205
- return value.replace(/[\x00-\x1f\x7f\\]/g, (c) => `\\x${c.charCodeAt(0).toString(16).toUpperCase().padStart(2, "0")}`);
206
- }
207
- function assertHexSerial(serial) {
208
- if (!/^[0-9A-Fa-f]+$/.test(serial)) {
209
- throw new Error(`Invalid certificate serial number: ${JSON.stringify(serial)}`);
210
- }
211
- return serial.toUpperCase();
212
- }
213
- var CaDatabase = class {
214
- #rootDir;
215
- constructor(rootDir) {
216
- this.#rootDir = rootDir;
217
- }
218
- /** Path to the OpenSSL certificate database file (`index.txt`). */
219
- get indexFile() {
220
- return import_node_path2.default.join(this.#rootDir, "index.txt");
221
- }
222
- /**
223
- * Parse the OpenSSL `index.txt` certificate database.
224
- *
225
- * Each line has tab-separated fields:
226
- * ```
227
- * status expiry [revocationDate] serial unknown subject
228
- * ```
229
- *
230
- * - status: `V` (valid), `R` (revoked), `E` (expired)
231
- * - expiry: `YYMMDDHHmmssZ`
232
- * - revocationDate: present only for revoked certs
233
- * - serial: hex string
234
- * - unknown: always `"unknown"`
235
- * - subject: X.500 slash-delimited string
236
- */
237
- readIndex() {
238
- const indexPath = this.indexFile;
239
- if (!import_node_fs2.default.existsSync(indexPath)) {
240
- return [];
241
- }
242
- const content = import_node_fs2.default.readFileSync(indexPath, "utf-8");
243
- const lines = content.split("\n").filter((l) => l.trim().length > 0);
244
- const records = [];
245
- for (const line of lines) {
246
- const fields = line.split(" ");
247
- if (fields.length < 4) continue;
248
- const statusChar = fields[0];
249
- const expiryStr = fields[1];
250
- let serial;
251
- let subject;
252
- let revocationDate;
253
- if (statusChar === "R") {
254
- revocationDate = fields[2];
255
- serial = fields[3];
256
- subject = fields.length >= 6 ? fields[5] : "";
257
- } else {
258
- serial = fields[3];
259
- subject = fields.length >= 6 ? fields[5] : "";
260
- }
261
- let status;
262
- switch (statusChar) {
263
- case "V":
264
- status = "valid";
265
- break;
266
- case "R":
267
- status = "revoked";
268
- break;
269
- case "E":
270
- status = "expired";
271
- break;
272
- default:
273
- continue;
274
- }
275
- records.push({
276
- serial,
277
- status,
278
- subject,
279
- expiryDate: parseOpenSSLDate(expiryStr),
280
- revocationDate: revocationDate ? parseOpenSSLDate(revocationDate) : void 0,
281
- reason: revocationDate ? parseRevocationReason(revocationDate) : void 0
282
- });
283
- }
284
- return records;
285
- }
286
- /** Look up one record by serial number (case-insensitive). */
287
- findBySerial(serial) {
288
- const upper = serial.toUpperCase();
289
- return this.readIndex().find((r) => r.serial.toUpperCase() === upper);
290
- }
291
- /**
292
- * Read a specific issued certificate by serial number.
293
- *
294
- * OpenSSL stores signed certificates in the `certs/` directory using
295
- * the naming convention `<SERIAL>.pem`.
296
- *
297
- * @param serial - hex-encoded serial number (e.g. `"1000"`)
298
- * @returns the DER buffer, or `undefined` if not found
299
- */
300
- getCertificateBySerial(serial) {
301
- const upper = serial.toUpperCase();
302
- const certFile = import_node_path2.default.join(this.#rootDir, "certs", `${upper}.pem`);
303
- if (!import_node_fs2.default.existsSync(certFile)) {
304
- return void 0;
305
- }
306
- const pem = (0, import_node_opcua_crypto.readCertificatePEM)(certFile);
307
- return (0, import_node_opcua_crypto.convertPEMtoDER)(pem);
308
- }
309
- /**
310
- * Read-increment-write a hex counter file (`serial`/`crlnumber`),
311
- * returning the value to hand out — matching `openssl ca`'s own
312
- * behavior: the file holds the *next* value to assign, and is bumped
313
- * to the following one immediately after being read. A `.old` backup
314
- * of the pre-bump content is kept, as `openssl` itself does.
315
- */
316
- #nextHexCounter(fileName) {
317
- const counterFile = import_node_path2.default.join(this.#rootDir, fileName);
318
- const current = BigInt(`0x${import_node_fs2.default.readFileSync(counterFile, "utf-8").trim()}`);
319
- import_node_fs2.default.copyFileSync(counterFile, `${counterFile}.old`);
320
- import_node_fs2.default.writeFileSync(counterFile, evenLengthHex(current + 1n));
321
- return evenLengthHex(current);
322
- }
323
- /** Hand out the next certificate serial number, bumping the `serial` file. */
324
- nextSerial() {
325
- return this.#nextHexCounter("serial");
326
- }
327
- /** Hand out the next CRL number, bumping the `crlnumber` file. */
328
- nextCrlNumber() {
329
- return this.#nextHexCounter("crlnumber");
330
- }
331
- /** Append a newly-issued certificate's `V` (valid) row to `index.txt`. */
332
- appendIssued(record) {
333
- const serial = assertHexSerial(record.serial);
334
- const subject = escapeIndexField(record.subject);
335
- const line = `V ${formatOpenSSLDate(record.expiryDate)} ${serial} unknown ${subject}
336
- `;
337
- import_node_fs2.default.appendFileSync(this.indexFile, line);
338
- }
339
- /**
340
- * Rewrite a certificate's `index.txt` row from `V` to `R` (revoked).
341
- * Throws if the serial is not found, or is already revoked — the same
342
- * "already revoked" case `openssl ca -revoke` itself rejects.
343
- */
344
- markRevoked(serial, revocationDate, reason) {
345
- const upper = assertHexSerial(serial);
346
- if (!/^[A-Za-z]+$/.test(reason)) {
347
- throw new Error(`Invalid CRL reason: ${JSON.stringify(reason)}`);
348
- }
349
- const lines = import_node_fs2.default.readFileSync(this.indexFile, "utf-8").split("\n").filter((l) => l.trim().length > 0);
350
- let found = false;
351
- const rewritten = lines.map((line) => {
352
- const fields = line.split(" ");
353
- if (fields.length < 4 || fields[3]?.toUpperCase() !== upper) {
354
- return line;
355
- }
356
- if (fields[0] === "R") {
357
- throw new Error(`Certificate ${upper} is already revoked`);
358
- }
359
- found = true;
360
- const subject = fields.length >= 6 ? fields[5] : "";
361
- return `R ${fields[1]} ${formatOpenSSLDate(revocationDate)},${reason} ${upper} unknown ${subject}`;
362
- });
363
- if (!found) {
364
- throw new Error(`Certificate ${upper} not found in the certificate database`);
365
- }
366
- import_node_fs2.default.writeFileSync(this.indexFile, `${rewritten.join("\n")}
367
- `);
368
- }
369
- /** Store a signed certificate's PEM under `certs/<SERIAL>.pem`, as `openssl ca` does. */
370
- storeCertificate(serial, pem) {
371
- const certFile = import_node_path2.default.join(this.#rootDir, "certs", `${assertHexSerial(serial)}.pem`);
372
- import_node_fs2.default.writeFileSync(certFile, pem);
373
- }
374
- };
375
-
376
- // packages/node-opcua-pki/lib/ca/backends/native_ca_backend.ts
377
- var CA_CERT_VALIDITY_DAYS = 3650;
378
- var RSA_SHA256 = { name: "RSASSA-PKCS1-v1_5", hash: { name: "SHA-256" } };
379
- function signingAlgorithmOf(key) {
380
- return (0, import_node_opcua_crypto2.isCaSigner)(key) ? key.algorithm : RSA_SHA256;
381
- }
382
- function daysFromNow(notBefore, days) {
383
- return new Date(notBefore.getTime() + days * 24 * 60 * 60 * 1e3);
384
- }
385
- function caSanUri(ca) {
386
- return `urn:${ca.subject.commonName || "NodeOPCUA-CA"}`;
387
- }
388
- function sanFromCsr(csrPem) {
389
- const request = new import_node_opcua_crypto2.x509.Pkcs10CertificateRequest(csrPem);
390
- const extension = request.getExtension("2.5.29.17");
391
- const dns = [];
392
- const ip = [];
393
- let applicationUri;
394
- for (const name of extension?.names.toJSON() ?? []) {
395
- if (name.type === "dns") {
396
- dns.push(name.value);
397
- } else if (name.type === "ip") {
398
- ip.push(name.value);
399
- } else if (name.type === "url" && applicationUri === void 0) {
400
- applicationUri = name.value;
401
- }
402
- }
403
- return { dns, ip, applicationUri };
404
- }
405
- var REASON_TO_X509_CRL_REASON = {
406
- unspecified: import_node_opcua_crypto2.x509.X509CrlReason.unspecified,
407
- keyCompromise: import_node_opcua_crypto2.x509.X509CrlReason.keyCompromise,
408
- CACompromise: import_node_opcua_crypto2.x509.X509CrlReason.cACompromise,
409
- affiliationChanged: import_node_opcua_crypto2.x509.X509CrlReason.affiliationChanged,
410
- superseded: import_node_opcua_crypto2.x509.X509CrlReason.superseded,
411
- cessationOfOperation: import_node_opcua_crypto2.x509.X509CrlReason.cessationOfOperation,
412
- certificateHold: import_node_opcua_crypto2.x509.X509CrlReason.certificateHold,
413
- removeFromCRL: import_node_opcua_crypto2.x509.X509CrlReason.removeFromCRL
414
- };
415
- function toOpenSslSubjectString(subjectName) {
416
- const parts = [];
417
- for (const rdn of subjectName.toJSON()) {
418
- for (const [type, values] of Object.entries(rdn)) {
419
- for (const value of values) {
420
- parts.push(`${type}=${value}`);
421
- }
422
- }
423
- }
424
- return `/${parts.join("/")}`;
425
- }
426
- var NativeCaBackend = class {
427
- /** Nothing to check: this backend spawns no process and needs no tool on PATH. */
428
- /** Signing goes through `ca._getSigningKey()`, which may be an external signer. */
429
- supportsExternalSigner = true;
430
- async preflight() {
431
- }
432
- /**
433
- * Write a CSR for the CA's own key, the `[v3_ca_req]` equivalent. The
434
- * key comes from the CA rather than from `privateKeyFile`: on a
435
- * signer-backed CA that file does not exist.
436
- */
437
- async generateCaCsr(ca, _caRootDir, _privateKeyFile, csrFile) {
438
- displayTitle("Generate a certificate request for the CA key");
439
- const signingKey = await ca._getSigningKey();
440
- const { csr } = await (0, import_node_opcua_crypto2.createCertificateSigningRequest)({
441
- privateKey: signingKey,
442
- subject: ca.subject.toString(),
443
- applicationUri: caSanUri(ca),
444
- purpose: import_node_opcua_crypto2.CertificatePurpose.ForCertificateAuthority
445
- });
446
- await import_node_fs3.default.promises.writeFile(csrFile, csr);
447
- }
448
- async bootstrap(ca) {
449
- const caRootDir = import_node_path3.default.resolve(ca.rootDir);
450
- const csrFile = import_node_path3.default.join(caRootDir, "private/cakey.csr");
451
- await this.generateCaCsr(ca, caRootDir, import_node_path3.default.join(caRootDir, "private/cakey.pem"), csrFile);
452
- const issuerCA = ca._issuerCA;
453
- if (issuerCA) {
454
- displayTitle("Generate CA Certificate (signed by issuer CA)");
455
- const signWithIssuer = () => this.signSubordinateCsr(issuerCA, csrFile, ca.caCertificate, CA_CERT_VALIDITY_DAYS);
456
- if (import_node_path3.default.resolve(issuerCA.rootDir) === caRootDir) {
457
- await signWithIssuer();
458
- } else {
459
- await issuerCA._withCaDirectoryLock(signWithIssuer);
460
- }
461
- } else {
462
- displayTitle("Generate CA Certificate (self-signed)");
463
- const csrPem = await import_node_fs3.default.promises.readFile(csrFile, "utf-8");
464
- const signingKey = await ca._getSigningKey();
465
- const request = new import_node_opcua_crypto2.x509.Pkcs10CertificateRequest(csrPem);
466
- const notBefore = /* @__PURE__ */ new Date();
467
- const { cert } = await (0, import_node_opcua_crypto2.createCertificateFromCsr)({
468
- csr: csrPem,
469
- // the CSR's own parsed subject, so issuer and subject are
470
- // byte-identical on a self-signed root: re-encoding a string
471
- // form could differ and break chain building
472
- issuerName: request.subjectName,
473
- issuerPublicKey: request.publicKey,
474
- signingKey,
475
- signingAlgorithm: signingAlgorithmOf(signingKey),
476
- notBefore,
477
- notAfter: daysFromNow(notBefore, CA_CERT_VALIDITY_DAYS),
478
- purpose: import_node_opcua_crypto2.CertificatePurpose.ForCertificateAuthority,
479
- applicationUri: caSanUri(ca),
480
- revocation: {
481
- crlDistributionUrl: ca.crlDistributionUrl,
482
- ocspResponderUrl: ca.ocspResponderUrl,
483
- caIssuersUrl: ca.caIssuersUrl
484
- }
485
- });
486
- await import_node_fs3.default.promises.writeFile(ca.caCertificate, cert);
487
- }
488
- displaySubtitle("generate initial CRL (Certificate Revocation List)");
489
- await this.#regenerateCrlLocked(ca, new CaDatabase(ca.rootDir));
490
- displayTitle("Create Certificate Authority (CA) ---> DONE");
491
- }
492
- /** Sign a subordinate CA's request with this CA's key, the `[v3_ca]` equivalent. */
493
- async signSubordinateCsr(ca, csrFile, certFile, validityDays) {
494
- const { signingKey, signingAlgorithm, issuerPublicKey, issuerName } = await this.#issuerContext(ca);
495
- const csrPem = await import_node_fs3.default.promises.readFile(csrFile, "utf-8");
496
- const db = new CaDatabase(ca.rootDir);
497
- const serialNumber = db.nextSerial();
498
- const notBefore = /* @__PURE__ */ new Date();
499
- const notAfter = daysFromNow(notBefore, validityDays);
500
- const { cert } = await (0, import_node_opcua_crypto2.createCertificateFromCsr)({
501
- csr: csrPem,
502
- issuerName,
503
- issuerPublicKey,
504
- signingKey,
505
- signingAlgorithm,
506
- serialNumber,
507
- notBefore,
508
- notAfter,
509
- purpose: import_node_opcua_crypto2.CertificatePurpose.ForCertificateAuthority,
510
- // the subordinate's own SAN, not the issuer's
511
- ...sanFromCsr(csrPem),
512
- revocation: {
513
- crlDistributionUrl: ca.crlDistributionUrl,
514
- ocspResponderUrl: ca.ocspResponderUrl,
515
- caIssuersUrl: ca.caIssuersUrl
516
- }
517
- });
518
- await import_node_fs3.default.promises.writeFile(certFile, cert);
519
- this.#record(db, serialNumber, cert, notAfter);
520
- }
521
- /**
522
- * Self-sign a certificate with a caller-supplied key file and record it
523
- * in this CA's database: the native equivalent of `openssl req -new`
524
- * followed by `openssl ca -selfsign`, which likewise applies the
525
- * end-entity profile and updates `index.txt`.
526
- */
527
- async createSelfSignedCertificate(ca, certificateFile, privateKeyFile, params) {
528
- displaySubtitle("- the certificate signing request");
529
- const key = await (0, import_node_opcua_crypto2.privateKeyToCryptoKey)((0, import_node_opcua_crypto2.readPrivateKey)(privateKeyFile, await ca._privateKeyPassphrase()));
530
- const { csr } = await (0, import_node_opcua_crypto2.createCertificateSigningRequest)({
531
- privateKey: key,
532
- subject: params.subject ? new import_node_opcua_crypto2.Subject(params.subject).toString() : ca.subject.toString(),
533
- dns: params.dns,
534
- ip: params.ip,
535
- applicationUri: params.applicationUri,
536
- purpose: import_node_opcua_crypto2.CertificatePurpose.ForApplication
537
- });
538
- displaySubtitle("- creating the self-signed certificate");
539
- const request = new import_node_opcua_crypto2.x509.Pkcs10CertificateRequest(csr);
540
- const db = new CaDatabase(ca.rootDir);
541
- const serialNumber = db.nextSerial();
542
- const notBefore = params.startDate ?? /* @__PURE__ */ new Date();
543
- const notAfter = params.endDate ?? daysFromNow(notBefore, params.validity ?? 365);
544
- const { cert } = await (0, import_node_opcua_crypto2.createCertificateFromCsr)({
545
- csr,
546
- issuerName: request.subjectName,
547
- issuerPublicKey: request.publicKey,
548
- signingKey: key,
549
- signingAlgorithm: RSA_SHA256,
550
- serialNumber,
551
- notBefore,
552
- notAfter,
553
- purpose: import_node_opcua_crypto2.CertificatePurpose.ForApplication,
554
- ...sanFromCsr(csr)
555
- });
556
- await import_node_fs3.default.promises.writeFile(certificateFile, cert);
557
- this.#record(db, serialNumber, cert, notAfter);
558
- }
559
- /** `certs/<SERIAL>.pem` plus the `V` row, exactly as `openssl ca` writes them. */
560
- #record(db, serialNumber, certPem, notAfter) {
561
- db.storeCertificate(serialNumber, certPem);
562
- db.appendIssued({
563
- serial: serialNumber,
564
- expiryDate: notAfter,
565
- subject: toOpenSslSubjectString(new import_node_opcua_crypto2.x509.X509Certificate(certPem).subjectName)
566
- });
567
- }
568
- /**
569
- * The CA's signing key and issuer identity, resolved fresh on every
570
- * call and never cached across operations. The identity is read back
571
- * from the CA certificate on disk so that the issuer field of anything
572
- * this CA signs is byte-identical to that certificate's subject.
573
- */
574
- async #issuerContext(ca) {
575
- const signingKey = await ca._getSigningKey();
576
- const caCertPem = await import_node_fs3.default.promises.readFile(ca.caCertificate, "utf-8");
577
- const caCert = new import_node_opcua_crypto2.x509.X509Certificate(caCertPem);
578
- return {
579
- signingKey,
580
- signingAlgorithm: signingAlgorithmOf(signingKey),
581
- issuerPublicKey: caCert.publicKey,
582
- issuerName: caCert.subjectName
583
- };
584
- }
585
- async signEndEntityCsr(ca, certificate, csr, params, sanOverride) {
586
- const { signingKey, signingAlgorithm, issuerPublicKey, issuerName } = await this.#issuerContext(ca);
587
- const db = new CaDatabase(ca.rootDir);
588
- const serialNumber = db.nextSerial();
589
- const csrPem = await import_node_fs3.default.promises.readFile(csr, "utf-8");
590
- const notBefore = params.startDate ?? /* @__PURE__ */ new Date();
591
- const notAfter = params.endDate ?? new Date(notBefore.getTime() + (params.validity ?? 365) * 24 * 60 * 60 * 1e3);
592
- const { cert } = await (0, import_node_opcua_crypto2.createCertificateFromCsr)({
593
- csr: csrPem,
594
- issuerName,
595
- issuerPublicKey,
596
- signingKey,
597
- signingAlgorithm,
598
- serialNumber,
599
- notBefore,
600
- notAfter,
601
- purpose: import_node_opcua_crypto2.CertificatePurpose.ForApplication,
602
- dns: sanOverride.dns,
603
- ip: sanOverride.ip,
604
- applicationUri: sanOverride.applicationUri,
605
- revocation: {
606
- crlDistributionUrl: ca.crlDistributionUrl,
607
- ocspResponderUrl: ca.ocspResponderUrl,
608
- caIssuersUrl: ca.caIssuersUrl
609
- }
610
- });
611
- await import_node_fs3.default.promises.writeFile(certificate, cert);
612
- this.#record(db, serialNumber, cert, notAfter);
613
- }
614
- async #regenerateCrlLocked(ca, db) {
615
- const { signingKey, signingAlgorithm, issuerPublicKey, issuerName } = await this.#issuerContext(ca);
616
- const crlNumber = db.nextCrlNumber();
617
- const entries = db.readIndex().filter((r) => r.status === "revoked").map((r) => ({
618
- serialNumber: r.serial,
619
- revocationDate: r.revocationDate ? new Date(r.revocationDate) : /* @__PURE__ */ new Date(),
620
- reason: r.reason ? REASON_TO_X509_CRL_REASON[r.reason] : void 0
621
- }));
622
- const { crl } = await (0, import_node_opcua_crypto2.createCrl)({
623
- issuerName,
624
- issuerPublicKey,
625
- signingKey,
626
- signingAlgorithm,
627
- crlNumber: BigInt(`0x${crlNumber}`),
628
- entries
629
- });
630
- await import_node_fs3.default.promises.writeFile(ca.revocationList, crl);
631
- const der = new import_node_opcua_crypto2.x509.X509Crl(crl).rawData;
632
- await import_node_fs3.default.promises.writeFile(ca.revocationListDER, Buffer.from(der));
633
- }
634
- async regenerateCrl(ca) {
635
- const db = new CaDatabase(ca.rootDir);
636
- await this.#regenerateCrlLocked(ca, db);
637
- }
638
- async revoke(ca, certificate, reason) {
639
- const certPem = await import_node_fs3.default.promises.readFile(certificate, "utf-8");
640
- const cert = new import_node_opcua_crypto2.x509.X509Certificate(certPem);
641
- const db = new CaDatabase(ca.rootDir);
642
- db.markRevoked(cert.serialNumber, /* @__PURE__ */ new Date(), reason);
643
- await this.#regenerateCrlLocked(ca, db);
644
- }
645
- };
646
-
647
- // packages/node-opcua-pki/lib/ca/core/certificate_authority_core.ts
648
- var import_node_assert3 = __toESM(require("assert"));
649
- var import_node_fs4 = __toESM(require("fs"));
650
- var import_node_os = __toESM(require("os"));
651
- var import_node_path4 = __toESM(require("path"));
652
- var import_global_mutex = require("@ster5/global-mutex");
653
- var import_chalk3 = __toESM(require("chalk"));
654
- var import_node_opcua_crypto3 = require("node-opcua-crypto");
655
-
656
- // packages/node-opcua-pki/lib/ca/templates/ca_config_template.cnf.ts
657
- var config = `#.........DO NOT MODIFY BY HAND .........................
658
- [ ca ]
659
- default_ca = CA_default
660
- [ CA_default ]
661
- dir = "%%ROOT_FOLDER%%" # the main CA folder (quoted: see renderCaConfig)
662
- certs = $dir/certs # where to store certificates
663
- new_certs_dir = $dir/certs #
664
- database = $dir/index.txt # the certificate database
665
- serial = $dir/serial # the serial number counter
666
- certificate = $dir/public/cacert.pem # The root CA certificate
667
- private_key = $dir/private/cakey.pem # the CA private key
668
- x509_extensions = usr_cert #
669
- default_days = 3650 # default validity : 10 years
670
-
671
- # default_md = sha1
672
-
673
- default_md = sha256 # The default digest algorithm
674
-
675
- preserve = no
676
- policy = policy_match
677
- # randfile = $dir/random.rnd
678
- # default_startdate = YYMMDDHHMMSSZ
679
- # default_enddate = YYMMDDHHMMSSZ
680
- crl_dir = $dir/crl
681
- crl_extensions = crl_ext
682
- crl = $dir/crl/revocation_list.crl # the Revocation list
683
- crlnumber = $dir/crlnumber # CRL number file
684
- default_crl_days = 30
685
- default_crl_hours = 24
686
- #msie_hack
687
-
688
- [ policy_match ]
689
- countryName = optional
690
- stateOrProvinceName = optional
691
- localityName = optional
692
- organizationName = optional
693
- organizationalUnitName = optional
694
- commonName = optional
695
- emailAddress = optional
696
-
697
- [ req ]
698
- default_bits = 4096 # Size of keys
699
- default_keyfile = key.pem # name of generated keys
700
- distinguished_name = req_distinguished_name
701
- attributes = req_attributes
702
- x509_extensions = v3_ca
703
- #input_password
704
- #output_password
705
- string_mask = nombstr # permitted characters
706
- req_extensions = v3_req
707
-
708
- [ req_distinguished_name ]
709
-
710
- #0 countryName = Country Name (2 letter code)
711
- # countryName_default = FR
712
- # countryName_min = 2
713
- # countryName_max = 2
714
- # stateOrProvinceName = State or Province Name (full name)
715
- # stateOrProvinceName_default = Ile de France
716
- # localityName = Locality Name (city, district)
717
- # localityName_default = Paris
718
- organizationName = Organization Name (company)
719
- organizationName_default = NodeOPCUA
720
- # organizationalUnitName = Organizational Unit Name (department, division)
721
- # organizationalUnitName_default = R&D
722
- commonName = Common Name (hostname, FQDN, IP, or your name)
723
- commonName_max = 256
724
- commonName_default = NodeOPCUA
725
- # emailAddress = Email Address
726
- # emailAddress_max = 40
727
- # emailAddress_default = node-opcua (at) node-opcua (dot) com
728
-
729
- [ req_attributes ]
730
- #challengePassword = A challenge password
731
- #challengePassword_min = 4
732
- #challengePassword_max = 20
733
- #unstructuredName = An optional company name
734
- [ usr_cert ]
735
- basicConstraints = critical, CA:FALSE
736
- subjectKeyIdentifier = hash
737
- authorityKeyIdentifier = keyid,issuer:always
738
- #authorityKeyIdentifier = keyid
739
- subjectAltName = $ENV::ALTNAME
740
- # issuerAltName = issuer:copy
741
- nsComment = ''OpenSSL Generated Certificate''
742
- #nsCertType = client, email, objsign for ''everything including object signing''
743
- #nsCaRevocationUrl = http://www.domain.dom/ca-crl.pem
744
- #nsBaseUrl =
745
- #nsRenewalUrl =
746
- #nsCaPolicyUrl =
747
- #nsSslServerName =
748
- keyUsage = critical, digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment, keyAgreement
749
- extendedKeyUsage = critical,serverAuth ,clientAuth
750
- {{#CDP_URL}}crlDistributionPoints = URI:$ENV::CDP_URL
751
- {{/CDP_URL}}{{#AIA_VALUE}}authorityInfoAccess = $ENV::AIA_VALUE
752
- {{/AIA_VALUE}}
753
- [ v3_req ]
754
- basicConstraints = critical, CA:FALSE
755
- keyUsage = nonRepudiation, digitalSignature, keyEncipherment, dataEncipherment, keyAgreement
756
- extendedKeyUsage = critical,serverAuth ,clientAuth
757
- subjectAltName = $ENV::ALTNAME
758
- nsComment = "CA Generated by Node-OPCUA Certificate utility using openssl"
759
- [ v3_ca_req ]
760
- subjectKeyIdentifier = hash
761
- basicConstraints = CA:TRUE
762
- keyUsage = critical, cRLSign, keyCertSign
763
- subjectAltName = $ENV::ALTNAME
764
- nsComment = "CA CSR generated by Node-OPCUA Certificate utility using openssl"
765
- [ v3_ca ]
766
- subjectKeyIdentifier = hash
767
- authorityKeyIdentifier = keyid:always,issuer:always
768
- basicConstraints = CA:TRUE
769
- keyUsage = critical, cRLSign, keyCertSign
770
- subjectAltName = $ENV::ALTNAME
771
- nsComment = "CA Certificate generated by Node-OPCUA Certificate utility using openssl"
772
- #nsCertType = sslCA, emailCA
773
- #issuerAltName = issuer:copy
774
- #obj = DER:02:03
775
- {{#CDP_URL}}crlDistributionPoints = URI:$ENV::CDP_URL
776
- {{/CDP_URL}}{{#AIA_VALUE}}authorityInfoAccess = $ENV::AIA_VALUE
777
- {{/AIA_VALUE}}[ v3_selfsigned]
778
- basicConstraints = critical, CA:FALSE
779
- keyUsage = nonRepudiation, digitalSignature, keyEncipherment, dataEncipherment, keyAgreement
780
- extendedKeyUsage = critical,serverAuth ,clientAuth
781
- nsComment = "Self-signed certificate, generated by NodeOPCUA"
782
- subjectAltName = $ENV::ALTNAME
783
-
784
- [ crl_ext ]
785
- #issuerAltName = issuer:copy
786
- authorityKeyIdentifier = keyid:always,issuer:always
787
- #authorityInfoAccess = @issuer_info`;
788
- var ca_config_template_cnf_default = config;
789
-
790
- // packages/node-opcua-pki/lib/ca/core/certificate_authority_core.ts
791
- var defaultSubject = "/C=FR/ST=IDF/L=Paris/O=Local NODE-OPCUA Certificate Authority/CN=NodeOPCUA-CA";
792
- var configurationFileTemplate = ca_config_template_cnf_default;
793
- function escapeOpensslConfDoubleQuoted(value) {
794
- return value.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
795
- }
796
- function renderCaConfig(caRootDir) {
797
- return configurationFileTemplate.replace(/%%ROOT_FOLDER%%/, escapeOpensslConfDoubleQuoted(makePath(caRootDir)));
798
- }
799
- var config2 = {
800
- certificateDir: "INVALID",
801
- forceCA: false,
802
- pkiDir: "INVALID"
803
- };
804
- var CA_LOCK_MAX_WAIT_MS = 5 * 6e4;
805
- var CA_LOCK_RETRY = { minTimeout: 20, maxTimeout: 250 };
806
- function octetStringToIpAddress(a) {
807
- return parseInt(a.substring(0, 2), 16).toString() + "." + parseInt(a.substring(2, 4), 16).toString() + "." + parseInt(a.substring(4, 6), 16).toString() + "." + parseInt(a.substring(6, 8), 16).toString();
808
- }
809
- (0, import_node_assert3.default)(octetStringToIpAddress("c07b9179") === "192.123.145.121");
810
- function validateRevocationUrl(url, fieldName) {
811
- if (url === void 0) {
812
- return void 0;
813
- }
814
- if (url === "") {
815
- throw new Error(`${fieldName} must not be empty \u2014 pass undefined to disable the extension`);
816
- }
817
- let parsed;
818
- try {
819
- parsed = new URL(url);
820
- } catch {
821
- throw new Error(`${fieldName} is not a valid URL: ${url}`);
822
- }
823
- if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
824
- throw new Error(`${fieldName} must use http: or https: (got ${parsed.protocol} in ${url})`);
825
- }
826
- if (!parsed.pathname || parsed.pathname === "/") {
827
- throw new Error(`${fieldName} must include a path component (got ${url})`);
828
- }
829
- const isLoopback = parsed.hostname === "localhost" || parsed.hostname === "::1" || parsed.hostname.startsWith("127.");
830
- if (isLoopback) {
831
- console.warn(
832
- `[node-opcua-pki] ${fieldName} points at loopback (${url}) \u2014 certificates issued with this URL will be unreachable from any other host.`
833
- );
834
- }
835
- return url;
836
- }
837
- var CertificateAuthorityCore = class {
838
- /** RSA key size used when generating the CA private key. */
839
- keySize;
840
- /** Root filesystem path of the CA directory structure. */
841
- location;
842
- /** X.500 subject of the CA certificate. */
843
- subject;
844
- /** @internal Parent CA (undefined for root CAs). */
845
- _issuerCA;
846
- /** @internal Configured CDP / AIA URLs (US-202). */
847
- _crlDistributionUrl;
848
- _ocspResponderUrl;
849
- _caIssuersUrl;
850
- #privateKeyPassphrase;
851
- /** resolved once (see `privateKeyPassphrase`); `#passphraseResolved` distinguishes "none" from "not yet" */
852
- #resolvedPassphrase;
853
- #passphraseResolved = false;
854
- /** Signing backend: `openssl` shells out to the CLI, `native` signs in-process. */
855
- #backend;
856
- /** External signing key (HSM/KMS), when one was supplied instead of a key file. */
857
- #signer;
858
- /** Read access to `index.txt` / `certs/<SERIAL>.pem`. */
859
- #db;
860
- constructor(options) {
861
- (0, import_node_assert3.default)(Object.prototype.hasOwnProperty.call(options, "location"));
862
- (0, import_node_assert3.default)(Object.prototype.hasOwnProperty.call(options, "keySize"));
863
- this.location = options.location;
864
- this.keySize = options.keySize || 2048;
865
- this.subject = new import_node_opcua_crypto3.Subject(options.subject || defaultSubject);
866
- this._issuerCA = options.issuerCA;
867
- this.#privateKeyPassphrase = options.privateKeyPassphrase;
868
- this.#signer = options.signer;
869
- if (options.signer) {
870
- const algorithm = options.signer.algorithm;
871
- if (algorithm.name !== "RSASSA-PKCS1-v1_5" && algorithm.name !== "ECDSA") {
872
- throw new Error(
873
- `CertificateAuthority: signer algorithm ${algorithm.name} is not supported - use RSASSA-PKCS1-v1_5 or ECDSA.`
874
- );
875
- }
876
- if (algorithm.name === "ECDSA" && !algorithm.namedCurve) {
877
- throw new Error(
878
- "CertificateAuthority: an ECDSA signer must declare its namedCurve (P-256, P-384 or P-521) - importing the signer's public key needs the curve, and an SPKI import cannot infer it."
879
- );
880
- }
881
- }
882
- if (options.signer && !options.backend.supportsExternalSigner) {
883
- throw new Error(
884
- "CertificateAuthority: this backend cannot sign with an external signer - it loads its key from a file. Use a backend that supports one, such as NativeCaBackend."
885
- );
886
- }
887
- this.#backend = options.backend;
888
- this.#db = new CaDatabase(this.location);
889
- if (options.crlDistributionUrl !== void 0) {
890
- this.setCrlDistributionUrl(options.crlDistributionUrl);
891
- }
892
- if (options.ocspResponderUrl !== void 0) {
893
- this.setOcspResponderUrl(options.ocspResponderUrl);
894
- }
895
- if (options.caIssuersUrl !== void 0) {
896
- this.setCaIssuersUrl(options.caIssuersUrl);
897
- }
898
- }
899
- /**
900
- * Public URL where the CRL produced by this CA is reachable, or
901
- * `undefined` if no CDP extension should be emitted on issued certs.
902
- */
903
- get crlDistributionUrl() {
904
- return this._crlDistributionUrl;
905
- }
906
- /**
907
- * Public URL of the OCSP responder, or `undefined` if no AIA OCSP
908
- * leg should be emitted on issued certs.
909
- */
910
- get ocspResponderUrl() {
911
- return this._ocspResponderUrl;
912
- }
913
- /**
914
- * Public URL where the issuer's certificate can be fetched, or
915
- * `undefined` if no AIA caIssuers leg should be emitted.
916
- */
917
- get caIssuersUrl() {
918
- return this._caIssuersUrl;
919
- }
920
- /**
921
- * Configure the URL embedded as `crlDistributionPoints` in every
922
- * subsequently-issued certificate. Pass `undefined` to disable
923
- * the extension entirely. Validated synchronously — throws on
924
- * empty string, non-http(s) protocol, missing path. Warns (does
925
- * not throw) when the URL points at loopback.
926
- *
927
- * @see US-202
928
- */
929
- setCrlDistributionUrl(url) {
930
- this._crlDistributionUrl = validateRevocationUrl(url, "crlDistributionUrl");
931
- }
932
- /**
933
- * Configure the OCSP responder URL embedded as the `OCSP` leg of
934
- * the `authorityInfoAccess` extension on every subsequently-issued
935
- * certificate. Pass `undefined` to disable.
936
- *
937
- * @see US-202
938
- */
939
- setOcspResponderUrl(url) {
940
- this._ocspResponderUrl = validateRevocationUrl(url, "ocspResponderUrl");
941
- }
942
- /**
943
- * Configure the caIssuers URL embedded as the `caIssuers` leg of
944
- * the `authorityInfoAccess` extension on every subsequently-issued
945
- * certificate. Pass `undefined` to disable.
946
- *
947
- * @see US-202
948
- */
949
- setCaIssuersUrl(url) {
950
- this._caIssuersUrl = validateRevocationUrl(url, "caIssuersUrl");
951
- }
952
- /** Absolute path to the CA root directory (alias for {@link location}). */
953
- get rootDir() {
954
- return this.location;
955
- }
956
- /** Path to the OpenSSL configuration file (`conf/caconfig.cnf`). */
957
- get configFile() {
958
- return import_node_path4.default.normalize(import_node_path4.default.join(this.rootDir, "./conf/caconfig.cnf"));
959
- }
960
- /** Path to the CA private key (`private/cakey.pem`); may be passphrase-encrypted, see {@link getPrivateKey}. */
961
- get privateKey() {
962
- return import_node_path4.default.join(import_node_path4.default.resolve(this.rootDir), "private/cakey.pem");
963
- }
964
- /**
965
- * The CA private key, decrypted with the configured `privateKeyPassphrase`
966
- * if it is encrypted. Fails closed (`PrivateKeyPassphraseRequiredError`)
967
- * on an encrypted key with no or the wrong passphrase.
968
- */
969
- async getPrivateKey() {
970
- this.#assertKeyIsOnDisk("getPrivateKey");
971
- return (0, import_node_opcua_crypto3.readPrivateKey)(this.privateKey, await this._privateKeyPassphrase());
972
- }
973
- /**
974
- * True when this CA signs with an external {@link CaSigner} (HSM/KMS)
975
- * rather than with `private/cakey.pem`. There is then no private key
976
- * file on disk, and never was one.
977
- */
978
- get hasExternalSigner() {
979
- return this.#signer !== void 0;
980
- }
981
- /**
982
- * Does `cert` certify the key this CA signs with? With the key on disk
983
- * that is a private-key match; with a signer there is no private key to
984
- * match, so compare the certified public key against the signer's own -
985
- * the same question, asked the only way an HSM allows.
986
- */
987
- async #certificateMatchesOurKey(certPem, certDer) {
988
- if (this.#signer) {
989
- const ours = Buffer.from(await this.#signer.getPublicKey());
990
- const certified = Buffer.from(new import_node_opcua_crypto3.x509.X509Certificate(certPem).publicKey.rawData);
991
- return ours.equals(certified);
992
- }
993
- return (0, import_node_opcua_crypto3.certificateMatchesPrivateKey)(certDer, await this.getPrivateKey());
994
- }
995
- /** Reject the key-file-only operations up front, rather than failing on a missing file later. */
996
- #assertKeyIsOnDisk(what) {
997
- if (this.#signer) {
998
- throw new Error(
999
- `CertificateAuthority.${what} is not available on a signer-backed CA: the private key lives in the signer (HSM/KMS) and is never written to disk.`
1000
- );
1001
- }
1002
- }
1003
- /**
1004
- * @internal The key every signing operation goes through. An injected
1005
- * {@link CaSigner} is returned as-is; otherwise the on-disk key is read
1006
- * (and decrypted) and handed over as a plain `CryptoKey`. Both are
1007
- * accepted by the `node-opcua-crypto` signing primitives, so callers
1008
- * never branch on which one they got.
1009
- */
1010
- async _getSigningKey() {
1011
- if (this.#signer) {
1012
- return this.#signer;
1013
- }
1014
- return (0, import_node_opcua_crypto3.privateKeyToCryptoKey)(await this.getPrivateKey());
1015
- }
1016
- /**
1017
- * Enable, disable, or rotate the passphrase protecting `private/cakey.pem`
1018
- * (temp file + atomic rename, temp file removed on failure). Only
1019
- * rewrites the file: construct a new `CertificateAuthority` with the new
1020
- * passphrase to continue using it.
1021
- */
1022
- async reencryptPrivateKey(oldPassphrase, newPassphrase) {
1023
- this.#assertKeyIsOnDisk("reencryptPrivateKey");
1024
- const oldPass = await resolvePrivateKeyPassphrase(oldPassphrase);
1025
- const newPass = await resolvePrivateKeyPassphrase(newPassphrase);
1026
- const key = (0, import_node_opcua_crypto3.readPrivateKey)(this.privateKey, oldPass);
1027
- await this.#rewritePrivateKeyFile(key, newPass);
1028
- }
1029
- async #rewritePrivateKeyFile(privateKey, passphrase) {
1030
- const tmpFilename = `${this.privateKey}.${process.pid}-${Date.now()}.tmp`;
1031
- try {
1032
- await (0, import_node_opcua_crypto3.writePrivateKeyFile)(tmpFilename, privateKey, { passphrase });
1033
- await import_node_fs4.default.promises.rename(tmpFilename, this.privateKey);
1034
- } finally {
1035
- await import_node_fs4.default.promises.rm(tmpFilename, { force: true });
1036
- }
1037
- }
1038
- /** @internal resolve the configured passphrase, at most once per instance */
1039
- async _privateKeyPassphrase() {
1040
- if (!this.#passphraseResolved) {
1041
- this.#resolvedPassphrase = await resolvePrivateKeyPassphrase(this.#privateKeyPassphrase);
1042
- this.#passphraseResolved = true;
1043
- }
1044
- return this.#resolvedPassphrase;
1045
- }
1046
- /**
1047
- * @internal On an existing key: encrypt it in place if a passphrase is
1048
- * configured and it is still plaintext (secure by default: the option
1049
- * means "protect this key", not "ignore me"), then read it back so a
1050
- * wrong or missing passphrase fails initialize() closed rather than the
1051
- * first signing operation.
1052
- */
1053
- async _ensurePrivateKeyProtection() {
1054
- if (this.#signer || !import_node_fs4.default.existsSync(this.privateKey)) {
1055
- return;
1056
- }
1057
- if (this.#privateKeyPassphrase !== void 0 && !isEncryptedPrivateKeyFile(this.privateKey)) {
1058
- warningLog("CertificateAuthority: private key is plaintext but a passphrase is configured; encrypting it in place");
1059
- const plaintextKey = (0, import_node_opcua_crypto3.readPrivateKey)(this.privateKey);
1060
- await this.#rewritePrivateKeyFile(plaintextKey, await this._privateKeyPassphrase());
1061
- }
1062
- await this.getPrivateKey();
1063
- }
1064
- /**
1065
- * Acquire a file-based lock on this CA's directory for the duration of
1066
- * `action` — serializes every operation that mutates the certificate
1067
- * database (`index.txt`, `serial`, `crlnumber`, `certs/`,
1068
- * `crl/revocation_list.*`) or the CA's own key/certificate, so
1069
- * concurrent calls on one `CertificateAuthority` (or two instances
1070
- * pointed at the same directory, in this or another process) cannot
1071
- * interleave. Mirrors `CertificateManager.withLock2`.
1072
- *
1073
- * Only ever call this from a top-level public operation — nested calls
1074
- * on the same instance would deadlock, since the underlying file lock
1075
- * is not reentrant.
1076
- *
1077
- * The wait is bounded: a waiter gives up (throws) after
1078
- * `CA_LOCK_MAX_WAIT_MS`. Without a bound, a holder whose openssl child
1079
- * hangs would block every CA operation in every process forever and
1080
- * silently — the lock library's keepalive keeps refreshing the lock
1081
- * file's mtime as long as the holder process is alive, so stale-lock
1082
- * recovery never fires for a live-but-stuck holder. A dead holder's
1083
- * lock goes stale (default 2 minutes) and is taken over well within
1084
- * this bound.
1085
- */
1086
- async #withCaLock(action) {
1087
- const lockFileName = import_node_path4.default.join(this.rootDir, ".ca.lock");
1088
- return (0, import_global_mutex.withLock)(
1089
- {
1090
- fileToLock: lockFileName,
1091
- retries: { forever: true, maxRetryTime: CA_LOCK_MAX_WAIT_MS, ...CA_LOCK_RETRY }
1092
- },
1093
- action
1094
- );
1095
- }
1096
- /**
1097
- * @internal Run `action` under this CA's directory lock. For the one
1098
- * legitimate cross-instance use: a subordinate CA's bootstrap signs its
1099
- * certificate with `openssl x509 -CAserial`, which read-increment-writes
1100
- * THIS issuer's `serial` file, and must therefore hold this issuer's
1101
- * lock (ordering is always subordinate -> issuer, so no cycle).
1102
- */
1103
- async _withCaDirectoryLock(action) {
1104
- return this.#withCaLock(action);
1105
- }
1106
- /** Path to the CA certificate in PEM format (`public/cacert.pem`). */
1107
- get caCertificate() {
1108
- return makePath(this.rootDir, "./public/cacert.pem");
1109
- }
1110
- /**
1111
- * Path to the issuer certificate chain (`public/issuer_chain.pem`).
1112
- *
1113
- * This file is created by {@link installCACertificate} when the
1114
- * provided cert file contains additional issuer certificates
1115
- * (e.g. intermediate + root). It is appended to signed certs
1116
- * by {@link constructCertificateChain} to produce a full chain
1117
- * per OPC UA Part 6 §6.2.6.
1118
- */
1119
- get issuerCertificateChain() {
1120
- return makePath(this.rootDir, "./public/issuer_chain.pem");
1121
- }
1122
- /**
1123
- * Path to the current Certificate Revocation List in DER format.
1124
- * (`crl/revocation_list.der`)
1125
- */
1126
- get revocationListDER() {
1127
- return makePath(this.rootDir, "./crl/revocation_list.der");
1128
- }
1129
- /**
1130
- * Path to the current Certificate Revocation List in PEM format.
1131
- * (`crl/revocation_list.crl`)
1132
- */
1133
- get revocationList() {
1134
- return makePath(this.rootDir, "./crl/revocation_list.crl");
1135
- }
1136
- /**
1137
- * Path to the concatenated CA certificate + CRL file.
1138
- * Used by OpenSSL for CRL-based verification.
1139
- */
1140
- get caCertificateWithCrl() {
1141
- return makePath(this.rootDir, "./public/cacertificate_with_crl.pem");
1142
- }
1143
- // ---------------------------------------------------------------
1144
- // Buffer-based accessors (US-059)
1145
- // ---------------------------------------------------------------
1146
- /**
1147
- * Return the CA certificate as a DER-encoded buffer.
1148
- *
1149
- * @throws if the CA certificate file does not exist
1150
- * (call {@link initialize} first).
1151
- */
1152
- getCACertificateDER() {
1153
- const pem = (0, import_node_opcua_crypto3.readCertificatePEM)(this.caCertificate);
1154
- return (0, import_node_opcua_crypto3.convertPEMtoDER)(pem);
1155
- }
1156
- /**
1157
- * Return the CA certificate as a PEM-encoded string.
1158
- *
1159
- * @throws if the CA certificate file does not exist
1160
- * (call {@link initialize} first).
1161
- */
1162
- getCACertificatePEM() {
1163
- const raw = (0, import_node_opcua_crypto3.readCertificatePEM)(this.caCertificate);
1164
- const beginMarker = "-----BEGIN CERTIFICATE-----";
1165
- const idx = raw.indexOf(beginMarker);
1166
- if (idx > 0) {
1167
- return raw.substring(idx);
1168
- }
1169
- return raw;
1170
- }
1171
- /**
1172
- * Return the current Certificate Revocation List as a
1173
- * DER-encoded buffer.
1174
- *
1175
- * Returns an empty buffer if no CRL has been generated yet.
1176
- */
1177
- getCRLDER() {
1178
- const crlPath = this.revocationListDER;
1179
- if (!import_node_fs4.default.existsSync(crlPath)) {
1180
- return Buffer.alloc(0);
1181
- }
1182
- return import_node_fs4.default.readFileSync(crlPath);
1183
- }
1184
- /**
1185
- * Return the current Certificate Revocation List as a
1186
- * PEM-encoded string.
1187
- *
1188
- * Returns an empty string if no CRL has been generated yet.
1189
- */
1190
- getCRLPEM() {
1191
- const crlPath = this.revocationList;
1192
- if (!import_node_fs4.default.existsSync(crlPath)) {
1193
- return "";
1194
- }
1195
- const raw = import_node_fs4.default.readFileSync(crlPath, "utf-8");
1196
- const beginMarker = "-----BEGIN X509 CRL-----";
1197
- const idx = raw.indexOf(beginMarker);
1198
- if (idx > 0) {
1199
- return raw.substring(idx);
1200
- }
1201
- return raw;
1202
- }
1203
- // ---------------------------------------------------------------
1204
- // Certificate database API (US-057)
1205
- // ---------------------------------------------------------------
1206
- /**
1207
- * Return a list of all issued certificates recorded in the
1208
- * OpenSSL `index.txt` database.
1209
- *
1210
- * Each entry includes the serial number, subject, status,
1211
- * expiry date, and (for revoked certs) the revocation date.
1212
- */
1213
- getIssuedCertificates() {
1214
- return this.#db.readIndex();
1215
- }
1216
- /**
1217
- * Return the total number of certificates recorded in
1218
- * `index.txt`.
1219
- */
1220
- getIssuedCertificateCount() {
1221
- return this.#db.readIndex().length;
1222
- }
1223
- /**
1224
- * Return the status of a certificate by its serial number.
1225
- *
1226
- * @param serial - hex-encoded serial number (e.g. `"1000"`)
1227
- * @returns `"valid"`, `"revoked"`, `"expired"`, or
1228
- * `undefined` if not found
1229
- */
1230
- getCertificateStatus(serial) {
1231
- return this.#db.findBySerial(serial)?.status;
1232
- }
1233
- /**
1234
- * Read a specific issued certificate by serial number and
1235
- * return its content as a DER-encoded buffer.
1236
- *
1237
- * OpenSSL stores signed certificates in the `certs/`
1238
- * directory using the naming convention `<SERIAL>.pem`.
1239
- *
1240
- * @param serial - hex-encoded serial number (e.g. `"1000"`)
1241
- * @returns the DER buffer, or `undefined` if not found
1242
- */
1243
- getCertificateBySerial(serial) {
1244
- return this.#db.getCertificateBySerial(serial);
1245
- }
1246
- /**
1247
- * Path to the OpenSSL certificate database file.
1248
- */
1249
- get indexFile() {
1250
- return this.#db.indexFile;
1251
- }
1252
- // ---------------------------------------------------------------
1253
- // Buffer-based CA operations (US-058)
1254
- // ---------------------------------------------------------------
1255
- /**
1256
- * Sign a DER-encoded Certificate Signing Request and return
1257
- * the signed certificate as a DER buffer.
1258
- *
1259
- * This method handles temp-file creation and cleanup
1260
- * internally so that callers can work with in-memory
1261
- * buffers only.
1262
- *
1263
- * The CA can override fields from the CSR by passing
1264
- * `options.dns`, `options.ip`, `options.applicationUri`,
1265
- * `options.startDate`, or `options.subject`.
1266
- *
1267
- * @param csrDer - the CSR as a DER-encoded buffer
1268
- * @param options - signing options and CA overrides
1269
- * @returns the signed certificate as a DER-encoded buffer
1270
- */
1271
- async signCertificateRequestFromDER(csrDer, options) {
1272
- const tmpDir = await import_node_fs4.default.promises.mkdtemp(import_node_path4.default.join(import_node_os.default.tmpdir(), "pki-sign-"));
1273
- try {
1274
- const csrFile = import_node_path4.default.join(tmpDir, "request.csr");
1275
- const certFile = import_node_path4.default.join(tmpDir, "certificate.pem");
1276
- const csrPem = (0, import_node_opcua_crypto3.toPem)(csrDer, "CERTIFICATE REQUEST");
1277
- await import_node_fs4.default.promises.writeFile(csrFile, csrPem, "utf-8");
1278
- const signingParams = {};
1279
- if (options?.validityMs !== void 0) signingParams.validityMs = options.validityMs;
1280
- else signingParams.validity = options?.validity ?? 365;
1281
- if (options?.startDate) signingParams.startDate = options.startDate;
1282
- if (options?.dns) signingParams.dns = options.dns;
1283
- if (options?.ip) signingParams.ip = options.ip;
1284
- if (options?.applicationUri) signingParams.applicationUri = options.applicationUri;
1285
- if (options?.subject) signingParams.subject = options.subject;
1286
- await this.signCertificateRequest(certFile, csrFile, signingParams);
1287
- const certPem = (0, import_node_opcua_crypto3.readCertificatePEM)(certFile);
1288
- return (0, import_node_opcua_crypto3.convertPEMtoDER)(certPem);
1289
- } finally {
1290
- await import_node_fs4.default.promises.rm(tmpDir, {
1291
- recursive: true,
1292
- force: true
1293
- });
1294
- }
1295
- }
1296
- /**
1297
- * Advertise the validity limits this CA can honor.
1298
- *
1299
- * Consumers (notably the GDS server in [`cert_auth.ts`](https://github.com/sterfive/node-opcua-gds))
1300
- * clamp a requested validity against these bounds before calling
1301
- * {@link signCertificateRequestFromDER}, so a misconfigured
1302
- * `defaultCertValidity` cannot ask the CA for something it cannot
1303
- * produce.
1304
- *
1305
- * Defaults match the OpenSSL-backed implementation:
1306
- * - `minValidityMs = 60_000` (1 minute) — practical floor; the
1307
- * X.509 spec floor is 1 second but very short certs are rarely
1308
- * useful and pathological for any real deployment.
1309
- * - `maxValidityMs = 10 * 365 * 86_400_000` (≈ 10 years) — long
1310
- * enough for root CAs.
1311
- * - `validityGranularityMs = 1_000` (1 second) — RFC 5280 §4.1.2.5
1312
- * floor on `notBefore` / `notAfter`.
1313
- * - `nativeUnit = "second"` — what `x509Date()` actually encodes.
1314
- *
1315
- * @see US-208 — the consumer-side capability story.
1316
- */
1317
- getCapabilities() {
1318
- return {
1319
- minValidityMs: 6e4,
1320
- maxValidityMs: 10 * 365 * 864e5,
1321
- validityGranularityMs: 1e3,
1322
- nativeUnit: "second"
1323
- };
1324
- }
1325
- /**
1326
- * Generate a new RSA key pair, create an internal CSR, sign it
1327
- * with this CA, and return both the certificate and private key
1328
- * as DER-encoded buffers.
1329
- *
1330
- * The private key is **never stored** by the CA — it exists only
1331
- * in a temporary directory that is cleaned up after the operation.
1332
- *
1333
- * This is used by `StartNewKeyPairRequest` (OPC UA Part 12) for
1334
- * constrained devices that cannot generate their own keys.
1335
- *
1336
- * @param options - key generation and certificate parameters
1337
- * @returns `{ certificateDer, privateKey }` — certificate as DER,
1338
- * private key as a branded `PrivateKey` buffer
1339
- */
1340
- /**
1341
- * An ephemeral key and a CSR for it, written into `tmpDir`. Both
1342
- * `generateKeyPairAndSign*` methods need exactly this, and neither
1343
- * needs a subprocess for it: the key comes from node's crypto and the
1344
- * request is built and self-signed in process, so no `openssl.cnf` has
1345
- * to be rendered either.
1346
- */
1347
- async #createEphemeralKeyAndCsr(tmpDir, keySize, options) {
1348
- const privateKeyFile = import_node_path4.default.join(tmpDir, "private_key.pem");
1349
- await (0, import_node_opcua_crypto3.generatePrivateKeyFile)(privateKeyFile, keySize);
1350
- const { csr } = await (0, import_node_opcua_crypto3.createCertificateSigningRequest)({
1351
- privateKey: await (0, import_node_opcua_crypto3.privateKeyToCryptoKey)((0, import_node_opcua_crypto3.readPrivateKey)(privateKeyFile)),
1352
- subject: options.subject ? new import_node_opcua_crypto3.Subject(options.subject).toString() : void 0,
1353
- applicationUri: options.applicationUri,
1354
- dns: options.dns ?? [],
1355
- ip: options.ip ?? [],
1356
- purpose: import_node_opcua_crypto3.CertificatePurpose.ForApplication
1357
- });
1358
- const csrFile = import_node_path4.default.join(tmpDir, "request.csr");
1359
- await import_node_fs4.default.promises.writeFile(csrFile, csr);
1360
- return { privateKeyFile, csrFile };
1361
- }
1362
- async generateKeyPairAndSignDER(options) {
1363
- const keySize = options.keySize ?? 2048;
1364
- const startDate = options.startDate ?? /* @__PURE__ */ new Date();
1365
- const tmpDir = await import_node_fs4.default.promises.mkdtemp(import_node_path4.default.join(import_node_os.default.tmpdir(), "pki-keygen-"));
1366
- try {
1367
- const { privateKeyFile, csrFile } = await this.#createEphemeralKeyAndCsr(tmpDir, keySize, options);
1368
- const certFile = import_node_path4.default.join(tmpDir, "certificate.pem");
1369
- const signingParams = {
1370
- applicationUri: options.applicationUri,
1371
- dns: options.dns,
1372
- ip: options.ip,
1373
- startDate
1374
- };
1375
- if (options.validityMs !== void 0) signingParams.validityMs = options.validityMs;
1376
- else signingParams.validity = options.validity ?? 365;
1377
- await this.signCertificateRequest(certFile, csrFile, signingParams);
1378
- const certPem = (0, import_node_opcua_crypto3.readCertificatePEM)(certFile);
1379
- const certificateDer = (0, import_node_opcua_crypto3.convertPEMtoDER)(certPem);
1380
- const privateKey = (0, import_node_opcua_crypto3.readPrivateKey)(privateKeyFile);
1381
- return { certificateDer, privateKey };
1382
- } finally {
1383
- await import_node_fs4.default.promises.rm(tmpDir, {
1384
- recursive: true,
1385
- force: true
1386
- });
1387
- }
1388
- }
1389
- /**
1390
- * Generate a new RSA key pair, create an internal CSR, sign it
1391
- * with this CA, and return the result as a PKCS#12 (PFX)
1392
- * buffer bundling the certificate, private key, and CA chain.
1393
- *
1394
- * The private key is **never stored** by the CA — it exists only
1395
- * in a temporary directory that is cleaned up after the operation.
1396
- *
1397
- * @param options - key generation, certificate, and PFX options
1398
- * @returns the PFX as a `Buffer`
1399
- */
1400
- async generateKeyPairAndSignPFX(options) {
1401
- const keySize = options.keySize ?? 2048;
1402
- const startDate = options.startDate ?? /* @__PURE__ */ new Date();
1403
- const passphrase = options.passphrase ?? "";
1404
- const tmpDir = await import_node_fs4.default.promises.mkdtemp(import_node_path4.default.join(import_node_os.default.tmpdir(), "pki-keygen-pfx-"));
1405
- try {
1406
- const { privateKeyFile, csrFile } = await this.#createEphemeralKeyAndCsr(tmpDir, keySize, options);
1407
- const certFile = import_node_path4.default.join(tmpDir, "certificate.pem");
1408
- const signingParams = {
1409
- applicationUri: options.applicationUri,
1410
- dns: options.dns,
1411
- ip: options.ip,
1412
- startDate
1413
- };
1414
- if (options.validityMs !== void 0) signingParams.validityMs = options.validityMs;
1415
- else signingParams.validity = options.validity ?? 365;
1416
- await this.signCertificateRequest(certFile, csrFile, signingParams);
1417
- const blocks = (await import_node_fs4.default.promises.readFile(certFile, "utf-8")).match(
1418
- /-----BEGIN CERTIFICATE-----[\s\S]*?-----END CERTIFICATE-----/g
1419
- );
1420
- if (!blocks || blocks.length === 0) {
1421
- throw new Error(`generateKeyPairAndSignPFX: no certificate was produced in ${certFile}`);
1422
- }
1423
- return await (0, import_node_opcua_crypto3.createPfx)({
1424
- certificate: (0, import_node_opcua_crypto3.convertPEMtoDER)(blocks[0]),
1425
- certificateChain: blocks.slice(1).map(import_node_opcua_crypto3.convertPEMtoDER),
1426
- privateKey: (0, import_node_opcua_crypto3.readPrivateKey)(privateKeyFile),
1427
- password: passphrase
1428
- });
1429
- } finally {
1430
- await import_node_fs4.default.promises.rm(tmpDir, {
1431
- recursive: true,
1432
- force: true
1433
- });
1434
- }
1435
- }
1436
- /**
1437
- * Revoke a DER-encoded certificate and regenerate the CRL.
1438
- *
1439
- * Extracts the serial number from the certificate, then
1440
- * uses the stored cert file at `certs/<serial>.pem` for
1441
- * revocation — avoiding temp-file PEM format mismatches.
1442
- *
1443
- * @param certDer - the certificate as a DER-encoded buffer
1444
- * @param reason - CRL reason code
1445
- * (default: `"keyCompromise"`)
1446
- * @throws if the certificate's serial is not found in the CA
1447
- */
1448
- async revokeCertificateDER(certDer, reason) {
1449
- const info = (0, import_node_opcua_crypto3.exploreCertificate)(certDer);
1450
- const serial = info.tbsCertificate.serialNumber.replace(/:/g, "").toUpperCase();
1451
- const storedCertFile = import_node_path4.default.join(this.rootDir, "certs", `${serial}.pem`);
1452
- if (!import_node_fs4.default.existsSync(storedCertFile)) {
1453
- throw new Error(`Cannot revoke: no stored certificate found for serial ${serial} at ${storedCertFile}`);
1454
- }
1455
- await this.revokeCertificate(storedCertFile, {
1456
- reason: reason ?? "keyCompromise"
1457
- });
1458
- }
1459
- /**
1460
- * Initialize the CA directory structure, generate the CA
1461
- * private key and self-signed certificate if they do not
1462
- * already exist.
1463
- */
1464
- async initialize() {
1465
- mkdirRecursiveSync(import_node_path4.default.resolve(this.rootDir));
1466
- await this.#withCaLock(() => this.#bootstrap());
1467
- }
1468
- /**
1469
- * @internal Shared (backend-agnostic) part of `initialize()`: directory
1470
- * layout, default database files, the "already initialized" / "partial
1471
- * init" checks, and the openssl config file — then delegates CSR
1472
- * generation, CA-certificate signing, and the initial CRL to
1473
- * {@link CaBackend.bootstrap}. Must be called under {@link #withCaLock}.
1474
- */
1475
- async #bootstrap() {
1476
- const caRootDir = import_node_path4.default.resolve(this.rootDir);
1477
- mkdirRecursiveSync(caRootDir);
1478
- ensurePrivateDirectory(import_node_path4.default.join(caRootDir, "private"));
1479
- mkdirRecursiveSync(import_node_path4.default.join(caRootDir, "public"));
1480
- mkdirRecursiveSync(import_node_path4.default.join(caRootDir, "certs"));
1481
- mkdirRecursiveSync(import_node_path4.default.join(caRootDir, "crl"));
1482
- mkdirRecursiveSync(import_node_path4.default.join(caRootDir, "conf"));
1483
- const serial = import_node_path4.default.join(caRootDir, "serial");
1484
- if (!import_node_fs4.default.existsSync(serial)) {
1485
- await import_node_fs4.default.promises.writeFile(serial, "1000");
1486
- }
1487
- const crlNumber = import_node_path4.default.join(caRootDir, "crlnumber");
1488
- if (!import_node_fs4.default.existsSync(crlNumber)) {
1489
- await import_node_fs4.default.promises.writeFile(crlNumber, "1000");
1490
- }
1491
- const indexFile = import_node_path4.default.join(caRootDir, "index.txt");
1492
- if (!import_node_fs4.default.existsSync(indexFile)) {
1493
- await import_node_fs4.default.promises.writeFile(indexFile, "");
1494
- }
1495
- const signerBacked = this.hasExternalSigner;
1496
- const caKeyExists = signerBacked || import_node_fs4.default.existsSync(import_node_path4.default.join(caRootDir, "private/cakey.pem"));
1497
- const caCertExists = import_node_fs4.default.existsSync(import_node_path4.default.join(caRootDir, "public/cacert.pem"));
1498
- if (caKeyExists && caCertExists && !config2.forceCA) {
1499
- if (!signerBacked) {
1500
- restrictPrivateFilePermissions(import_node_path4.default.join(caRootDir, "private/cakey.pem"), 384);
1501
- await this._ensurePrivateKeyProtection();
1502
- }
1503
- debugLog("CA private key and certificate already exist ... skipping");
1504
- return;
1505
- }
1506
- if (!signerBacked && caKeyExists && !caCertExists) {
1507
- debugLog("CA private key exists but cacert.pem is missing \u2014 rebuilding CA");
1508
- import_node_fs4.default.unlinkSync(import_node_path4.default.join(caRootDir, "private/cakey.pem"));
1509
- const staleCsr = import_node_path4.default.join(caRootDir, "private/cakey.csr");
1510
- if (import_node_fs4.default.existsSync(staleCsr)) {
1511
- import_node_fs4.default.unlinkSync(staleCsr);
1512
- }
1513
- }
1514
- displayTitle("Create Certificate Authority (CA)");
1515
- const indexFileAttr = import_node_path4.default.join(caRootDir, "index.txt.attr");
1516
- if (!import_node_fs4.default.existsSync(indexFileAttr)) {
1517
- await import_node_fs4.default.promises.writeFile(indexFileAttr, "unique_subject = no");
1518
- }
1519
- const caConfigFile = this.configFile;
1520
- await import_node_fs4.default.promises.writeFile(caConfigFile, renderCaConfig(caRootDir));
1521
- if (!signerBacked) {
1522
- const privateKeyFilename = import_node_path4.default.join(caRootDir, "private/cakey.pem");
1523
- displayTitle(`Generate the CA private Key - ${this.keySize}`);
1524
- await (0, import_node_opcua_crypto3.generatePrivateKeyFile)(privateKeyFilename, this.keySize, { passphrase: await this._privateKeyPassphrase() });
1525
- restrictPrivateFilePermissions(privateKeyFilename, 384);
1526
- }
1527
- await this.#backend.bootstrap(this);
1528
- }
1529
- /**
1530
- * Initialize the CA directory structure and generate the
1531
- * private key + CSR **without signing**.
1532
- *
1533
- * Use this when the CA certificate will be signed by an
1534
- * external (third-party) root CA. After receiving the signed
1535
- * certificate, call {@link installCACertificate} to complete
1536
- * the setup.
1537
- *
1538
- * **Idempotent / restart-safe:**
1539
- * - If the CA certificate exists and is valid → `{ status: "ready" }`
1540
- * - If the CA certificate has expired → `{ status: "expired", csrPath, expiryDate }`
1541
- * (a new CSR is generated, preserving the existing private key)
1542
- * - If key + CSR exist but no cert (restart before install) →
1543
- * `{ status: "pending", csrPath }` without regenerating
1544
- * - Otherwise → generates key + CSR → `{ status: "created", csrPath }`
1545
- *
1546
- * @returns an {@link InitializeCSRResult} describing the CA state
1547
- */
1548
- async initializeCSR() {
1549
- const caRootDir = import_node_path4.default.resolve(this.rootDir);
1550
- mkdirRecursiveSync(caRootDir);
1551
- return this.#withCaLock(() => this.#initializeCSRLocked(caRootDir));
1552
- }
1553
- async #initializeCSRLocked(caRootDir) {
1554
- for (const dir of ["public", "certs", "crl", "conf"]) {
1555
- mkdirRecursiveSync(import_node_path4.default.join(caRootDir, dir));
1556
- }
1557
- ensurePrivateDirectory(import_node_path4.default.join(caRootDir, "private"));
1558
- const caCertFile = this.caCertificate;
1559
- const privateKeyFile = import_node_path4.default.join(caRootDir, "private/cakey.pem");
1560
- const csrFile = import_node_path4.default.join(caRootDir, "private/cakey.csr");
1561
- const keyAvailable = this.hasExternalSigner || import_node_fs4.default.existsSync(privateKeyFile);
1562
- if (!this.hasExternalSigner && import_node_fs4.default.existsSync(privateKeyFile)) {
1563
- restrictPrivateFilePermissions(privateKeyFile, 384);
1564
- await this._ensurePrivateKeyProtection();
1565
- }
1566
- if (import_node_fs4.default.existsSync(caCertFile)) {
1567
- const certDer = (0, import_node_opcua_crypto3.convertPEMtoDER)((0, import_node_opcua_crypto3.readCertificatePEM)(caCertFile));
1568
- const certInfo = (0, import_node_opcua_crypto3.exploreCertificate)(certDer);
1569
- const notAfter = certInfo.tbsCertificate.validity.notAfter;
1570
- if (notAfter.getTime() < Date.now()) {
1571
- debugLog("CA certificate has expired \u2014 generating renewal CSR");
1572
- await this._generateCSR(caRootDir, privateKeyFile, csrFile);
1573
- return { status: "expired", csrPath: csrFile, expiryDate: notAfter };
1574
- }
1575
- debugLog("CA certificate already exists and is valid \u2014 ready");
1576
- return { status: "ready" };
1577
- }
1578
- if (keyAvailable && import_node_fs4.default.existsSync(csrFile)) {
1579
- debugLog("CA key + CSR already exist \u2014 pending external signing");
1580
- return { status: "pending", csrPath: csrFile };
1581
- }
1582
- const serial = import_node_path4.default.join(caRootDir, "serial");
1583
- if (!import_node_fs4.default.existsSync(serial)) {
1584
- await import_node_fs4.default.promises.writeFile(serial, "1000");
1585
- }
1586
- const crlNumber = import_node_path4.default.join(caRootDir, "crlnumber");
1587
- if (!import_node_fs4.default.existsSync(crlNumber)) {
1588
- await import_node_fs4.default.promises.writeFile(crlNumber, "1000");
1589
- }
1590
- const indexFile = import_node_path4.default.join(caRootDir, "index.txt");
1591
- if (!import_node_fs4.default.existsSync(indexFile)) {
1592
- await import_node_fs4.default.promises.writeFile(indexFile, "");
1593
- }
1594
- const indexFileAttr = import_node_path4.default.join(caRootDir, "index.txt.attr");
1595
- if (!import_node_fs4.default.existsSync(indexFileAttr)) {
1596
- await import_node_fs4.default.promises.writeFile(indexFileAttr, "unique_subject = no");
1597
- }
1598
- const caConfigFile = this.configFile;
1599
- await import_node_fs4.default.promises.writeFile(caConfigFile, renderCaConfig(caRootDir));
1600
- if (!keyAvailable) {
1601
- await (0, import_node_opcua_crypto3.generatePrivateKeyFile)(privateKeyFile, this.keySize, { passphrase: await this._privateKeyPassphrase() });
1602
- restrictPrivateFilePermissions(privateKeyFile, 384);
1603
- }
1604
- await this._generateCSR(caRootDir, privateKeyFile, csrFile);
1605
- return { status: "created", csrPath: csrFile };
1606
- }
1607
- /**
1608
- * Check whether the CA certificate needs renewal and, if so,
1609
- * generate a new CSR for re-signing by the external root CA.
1610
- *
1611
- * Use this while the CA is running to detect upcoming expiry
1612
- * **before** it actually expires. The existing private key is
1613
- * preserved so previously issued certs remain valid.
1614
- *
1615
- * @param thresholdDays - number of days before expiry at which
1616
- * to trigger renewal (default: 30)
1617
- * @returns an {@link InitializeCSRResult} — `"expired"` if
1618
- * renewal is needed, `"ready"` if the cert is still valid
1619
- */
1620
- async renewCSR(thresholdDays = 30) {
1621
- const caRootDir = import_node_path4.default.resolve(this.rootDir);
1622
- const caCertFile = this.caCertificate;
1623
- const privateKeyFile = import_node_path4.default.join(caRootDir, "private/cakey.pem");
1624
- const csrFile = import_node_path4.default.join(caRootDir, "private/cakey.csr");
1625
- if (!import_node_fs4.default.existsSync(caCertFile)) {
1626
- return this.initializeCSR();
1627
- }
1628
- const certDer = (0, import_node_opcua_crypto3.convertPEMtoDER)((0, import_node_opcua_crypto3.readCertificatePEM)(caCertFile));
1629
- const certInfo = (0, import_node_opcua_crypto3.exploreCertificate)(certDer);
1630
- const notAfter = certInfo.tbsCertificate.validity.notAfter;
1631
- const thresholdMs = thresholdDays * 24 * 60 * 60 * 1e3;
1632
- if (notAfter.getTime() - Date.now() < thresholdMs) {
1633
- debugLog(`CA certificate expires within ${thresholdDays} days \u2014 generating renewal CSR`);
1634
- await this.#withCaLock(() => this._generateCSR(caRootDir, privateKeyFile, csrFile));
1635
- return { status: "expired", csrPath: csrFile, expiryDate: notAfter };
1636
- }
1637
- return { status: "ready" };
1638
- }
1639
- /**
1640
- * Generate a CSR using the existing private key.
1641
- * Must be called under {@link #withCaLock} — the lock is taken by the
1642
- * public callers (`initializeCSR`, `renewCSR`), not here, so that
1643
- * `initializeCSR` can hold one lock across key generation AND CSR
1644
- * generation without the non-reentrant file lock deadlocking.
1645
- * @internal
1646
- */
1647
- async _generateCSR(caRootDir, privateKeyFile, csrFile) {
1648
- await this.#backend.generateCaCsr(this, caRootDir, privateKeyFile, csrFile);
1649
- }
1650
- /**
1651
- * Install an externally-signed CA certificate and generate
1652
- * the initial CRL.
1653
- *
1654
- * Call this after {@link initializeCSR} once the external
1655
- * root CA has signed the CSR.
1656
- *
1657
- * **Safety checks:**
1658
- * - Verifies that the certificate's public key matches the
1659
- * CA private key before installing.
1660
- *
1661
- * @param signedCertFile - path to the PEM-encoded signed
1662
- * CA certificate (issued by the external root CA)
1663
- * @returns an {@link InstallCACertificateResult} with
1664
- * `status: "success"` or `status: "error"` and a `reason`
1665
- */
1666
- async installCACertificate(signedCertFile) {
1667
- return this.#withCaLock(async () => {
1668
- const caCertFile = this.caCertificate;
1669
- const fullPem = await import_node_fs4.default.promises.readFile(signedCertFile, "utf8");
1670
- const pemBlocks = fullPem.match(/-----BEGIN CERTIFICATE-----[\s\S]*?-----END CERTIFICATE-----/g);
1671
- if (!pemBlocks || pemBlocks.length === 0) {
1672
- return {
1673
- status: "error",
1674
- reason: "no_certificate_found",
1675
- message: "The provided file does not contain any PEM-encoded certificate."
1676
- };
1677
- }
1678
- const certDer = (0, import_node_opcua_crypto3.convertPEMtoDER)(pemBlocks[0]);
1679
- if (!await this.#certificateMatchesOurKey(pemBlocks[0], certDer)) {
1680
- return {
1681
- status: "error",
1682
- reason: "certificate_key_mismatch",
1683
- message: "The provided certificate does not match the CA private key. Ensure the certificate was signed from the CSR generated by initializeCSR()."
1684
- };
1685
- }
1686
- await import_node_fs4.default.promises.writeFile(caCertFile, `${pemBlocks[0]}
1687
- `);
1688
- const issuerChainFile = this.issuerCertificateChain;
1689
- if (pemBlocks.length > 1) {
1690
- const issuerPem = `${pemBlocks.slice(1).join("\n")}
1691
- `;
1692
- await import_node_fs4.default.promises.writeFile(issuerChainFile, issuerPem);
1693
- debugLog(`Stored ${pemBlocks.length - 1} issuer certificate(s) in issuer_chain.pem`);
1694
- } else {
1695
- if (import_node_fs4.default.existsSync(issuerChainFile)) {
1696
- await import_node_fs4.default.promises.unlink(issuerChainFile);
1697
- }
1698
- }
1699
- await this.#backend.regenerateCrl(this);
1700
- return { status: "success" };
1701
- });
1702
- }
1703
- /**
1704
- * Sign a CSR with CA extensions (`v3_ca`), producing a
1705
- * subordinate CA certificate.
1706
- *
1707
- * Unlike {@link signCertificateRequest} which signs with
1708
- * end-entity extensions (SANs, etc.), this method signs
1709
- * with `basicConstraints = CA:TRUE` and `keyUsage =
1710
- * keyCertSign, cRLSign`.
1711
- *
1712
- * @param certFile - output path for the signed CA cert (PEM)
1713
- * @param csrFile - path to the subordinate CA's CSR
1714
- * @param params - signing parameters
1715
- */
1716
- async signCACertificateRequest(certFile, csrFile, params) {
1717
- await this.#withCaLock(async () => {
1718
- const validity = params.validity ?? 3650;
1719
- await this.#backend.signSubordinateCsr(this, csrFile, certFile, validity);
1720
- await this.constructCertificateChain(certFile);
1721
- });
1722
- }
1723
- /**
1724
- * Rebuild the combined CA certificate + CRL file.
1725
- *
1726
- * This concatenates the CA certificate with the current
1727
- * revocation list so that OpenSSL can verify certificates
1728
- * with CRL checking enabled.
1729
- */
1730
- async constructCACertificateWithCRL() {
1731
- const cacertWithCRL = this.caCertificateWithCrl;
1732
- if (import_node_fs4.default.existsSync(this.revocationList)) {
1733
- await import_node_fs4.default.promises.writeFile(
1734
- cacertWithCRL,
1735
- import_node_fs4.default.readFileSync(this.caCertificate, "utf8") + import_node_fs4.default.readFileSync(this.revocationList, "utf8")
1736
- );
1737
- } else {
1738
- await import_node_fs4.default.promises.writeFile(cacertWithCRL, import_node_fs4.default.readFileSync(this.caCertificate));
1739
- }
1740
- }
1741
- /**
1742
- * Append the CA certificate to a signed certificate file,
1743
- * creating a PEM certificate chain.
1744
- *
1745
- * @param certificate - path to the certificate file to extend
1746
- */
1747
- async constructCertificateChain(certificate) {
1748
- (0, import_node_assert3.default)(import_node_fs4.default.existsSync(certificate));
1749
- (0, import_node_assert3.default)(import_node_fs4.default.existsSync(this.caCertificate));
1750
- debugLog(import_chalk3.default.yellow(" certificate file :"), import_chalk3.default.cyan(certificate));
1751
- let chain = await import_node_fs4.default.promises.readFile(certificate, "utf8");
1752
- chain += await import_node_fs4.default.promises.readFile(this.caCertificate, "utf8");
1753
- if (import_node_fs4.default.existsSync(this.issuerCertificateChain)) {
1754
- chain += await import_node_fs4.default.promises.readFile(this.issuerCertificateChain, "utf8");
1755
- }
1756
- await import_node_fs4.default.promises.writeFile(certificate, chain);
1757
- }
1758
- /**
1759
- * Create a self-signed certificate using OpenSSL.
1760
- *
1761
- * @param certificateFile - output path for the signed certificate
1762
- * @param privateKey - path to the private key file
1763
- * @param params - certificate parameters (subject, validity, SANs)
1764
- */
1765
- async createSelfSignedCertificate(certificateFile, privateKey, params) {
1766
- (0, import_node_assert3.default)(typeof privateKey === "string");
1767
- (0, import_node_assert3.default)(import_node_fs4.default.existsSync(privateKey));
1768
- if (!certificateFileExist(certificateFile)) {
1769
- return;
1770
- }
1771
- adjustDate(params);
1772
- adjustApplicationUri(params);
1773
- params.dns = params.dns || [];
1774
- params.ip = params.ip || [];
1775
- await this.#withCaLock(async () => {
1776
- await this.#backend.createSelfSignedCertificate(this, certificateFile, privateKey, params);
1777
- });
1778
- }
1779
- /**
1780
- * Regenerate `crl/revocation_list.{crl,der}` from the current database
1781
- * state, without changing any certificate's status. Normally
1782
- * unnecessary — `revokeCertificate` already regenerates the CRL as
1783
- * part of revoking — but useful to force a refresh (e.g. after
1784
- * switching a CA's `backend` between `"openssl"` and `"native"`).
1785
- */
1786
- async regenerateCrl() {
1787
- await this.#withCaLock(async () => {
1788
- await this.#backend.regenerateCrl(this);
1789
- });
1790
- }
1791
- /**
1792
- * Revoke a certificate and regenerate the CRL.
1793
- *
1794
- * @param certificate - path to the certificate file to revoke
1795
- * @param params - revocation parameters
1796
- * @param params.reason - CRL reason code
1797
- * (default `"keyCompromise"`)
1798
- */
1799
- async revokeCertificate(certificate, params) {
1800
- const crlReasons = [
1801
- "unspecified",
1802
- "keyCompromise",
1803
- "CACompromise",
1804
- "affiliationChanged",
1805
- "superseded",
1806
- "cessationOfOperation",
1807
- "certificateHold",
1808
- "removeFromCRL"
1809
- ];
1810
- const reason = params.reason || "keyCompromise";
1811
- (0, import_node_assert3.default)(crlReasons.indexOf(reason) >= 0);
1812
- displayTitle(`Revoking certificate ${certificate}`);
1813
- await this.#withCaLock(async () => {
1814
- await this.#backend.revoke(this, certificate, reason);
1815
- });
1816
- }
1817
- /**
1818
- * Sign a Certificate Signing Request (CSR) with this CA.
1819
- *
1820
- * The signed certificate is written to `certificate`, and the
1821
- * CA certificate chain plus CRL are appended to form a
1822
- * complete certificate chain.
1823
- *
1824
- * @param certificate - output path for the signed certificate
1825
- * @param certificateSigningRequestFilename - path to the CSR
1826
- * @param params1 - signing parameters (validity, dates, SANs)
1827
- * @returns the path to the signed certificate
1828
- */
1829
- async signCertificateRequest(certificate, certificateSigningRequestFilename, params1) {
1830
- await this.#backend.preflight();
1831
- (0, import_node_assert3.default)(import_node_fs4.default.existsSync(certificateSigningRequestFilename));
1832
- if (!certificateFileExist(certificate)) {
1833
- return "";
1834
- }
1835
- adjustDate(params1);
1836
- adjustApplicationUri(params1);
1837
- const csr = await (0, import_node_opcua_crypto3.readCertificateSigningRequest)(certificateSigningRequestFilename);
1838
- const csrInfo = (0, import_node_opcua_crypto3.exploreCertificateSigningRequest)(csr);
1839
- const applicationUri = csrInfo.extensionRequest.subjectAltName.uniformResourceIdentifier ? csrInfo.extensionRequest.subjectAltName.uniformResourceIdentifier[0] : void 0;
1840
- if (typeof applicationUri !== "string") {
1841
- throw new Error("Cannot find applicationUri in CSR");
1842
- }
1843
- const dns = csrInfo.extensionRequest.subjectAltName.dNSName || [];
1844
- let ip = csrInfo.extensionRequest.subjectAltName.iPAddress || [];
1845
- ip = ip.map(octetStringToIpAddress);
1846
- const sanOverride = { applicationUri, dns, ip };
1847
- return this.#withCaLock(async () => {
1848
- await this.#backend.signEndEntityCsr(this, certificate, certificateSigningRequestFilename, params1, sanOverride);
1849
- displaySubtitle("- construct CA certificate with CRL");
1850
- await this.constructCACertificateWithCRL();
1851
- displaySubtitle("- construct certificate chain");
1852
- await this.constructCertificateChain(certificate);
1853
- displaySubtitle("- verify certificate against the root CA");
1854
- await this.verifyCertificate(certificate);
1855
- return certificate;
1856
- });
1857
- }
1858
- /**
1859
- * Check that `certificate` really was signed by this CA, and throw if it
1860
- * was not.
1861
- *
1862
- * This used to do nothing: `openssl verify` crashes on Windows, so the
1863
- * check was left as a placeholder. It no longer needs a subprocess -
1864
- * `verifyCertificateSignature` does it in pure JS, the same way
1865
- * {@link CertificateManager} already validates a chain - so the check
1866
- * that `signCertificateRequest` always claimed to perform now actually
1867
- * happens on every issuance, whichever backend did the signing.
1868
- *
1869
- * Only the leading certificate is examined: the file may be a chain,
1870
- * and the rest of it is this CA's own certificate and its issuers.
1871
- *
1872
- * @param certificate - path to the certificate (or chain) to verify
1873
- */
1874
- async verifyCertificate(certificate) {
1875
- const pem = await import_node_fs4.default.promises.readFile(certificate, "utf-8");
1876
- const blocks = pem.match(/-----BEGIN CERTIFICATE-----[\s\S]*?-----END CERTIFICATE-----/g);
1877
- if (!blocks || blocks.length === 0) {
1878
- throw new Error(`verifyCertificate: ${certificate} contains no PEM-encoded certificate`);
1879
- }
1880
- const caCertificateDer = (0, import_node_opcua_crypto3.convertPEMtoDER)((0, import_node_opcua_crypto3.readCertificatePEM)(this.caCertificate));
1881
- if (!(0, import_node_opcua_crypto3.verifyCertificateSignature)((0, import_node_opcua_crypto3.convertPEMtoDER)(blocks[0]), caCertificateDer)) {
1882
- throw new Error(
1883
- `verifyCertificate: ${certificate} was not signed by this certificate authority (${this.caCertificate})`
1884
- );
1885
- }
1886
- }
1887
- };
1888
-
1889
- // packages/node-opcua-pki/lib/misc/subject.ts
1890
- var import_node_opcua_crypto4 = require("node-opcua-crypto");
1891
- // Annotate the CommonJS export names for ESM import in node:
1892
- 0 && (module.exports = {
1893
- CaDatabase,
1894
- CertificateAuthorityCore,
1895
- NativeCaBackend,
1896
- Subject,
1897
- configurationFileTemplate,
1898
- defaultSubject,
1899
- escapeOpensslConfDoubleQuoted,
1900
- renderCaConfig
1901
- });
1902
- //# sourceMappingURL=core.js.map