node-opcua-pki 6.19.0 → 6.20.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin/pki.mjs +668 -227
- package/dist/bin/pki.mjs.map +1 -1
- package/dist/index.d.mts +201 -21
- package/dist/index.d.ts +201 -21
- package/dist/index.js +663 -214
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +669 -216
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -3
- package/readme.md +4 -0
package/dist/bin/pki.mjs
CHANGED
|
@@ -91,6 +91,45 @@ var init_hostname = __esm({
|
|
|
91
91
|
}
|
|
92
92
|
});
|
|
93
93
|
|
|
94
|
+
// packages/node-opcua-pki/lib/toolbox/common.ts
|
|
95
|
+
import assert2 from "assert";
|
|
96
|
+
async function resolvePrivateKeyPassphrase(passphrase) {
|
|
97
|
+
if (passphrase === void 0) {
|
|
98
|
+
return void 0;
|
|
99
|
+
}
|
|
100
|
+
return typeof passphrase === "function" ? await passphrase() : passphrase;
|
|
101
|
+
}
|
|
102
|
+
function adjustDate(params) {
|
|
103
|
+
assert2(params instanceof Object);
|
|
104
|
+
params.startDate = params.startDate || /* @__PURE__ */ new Date();
|
|
105
|
+
assert2(params.startDate instanceof Date);
|
|
106
|
+
if (params.validityMs !== void 0) {
|
|
107
|
+
if (params.validityMs <= 0) {
|
|
108
|
+
throw new RangeError(`validityMs must be > 0 (got ${params.validityMs})`);
|
|
109
|
+
}
|
|
110
|
+
params.endDate = new Date(params.startDate.getTime() + params.validityMs);
|
|
111
|
+
params.validity = Math.ceil(params.validityMs / 864e5);
|
|
112
|
+
} else {
|
|
113
|
+
params.validity = params.validity || 365;
|
|
114
|
+
params.endDate = new Date(params.startDate.getTime());
|
|
115
|
+
params.endDate.setDate(params.startDate.getDate() + params.validity);
|
|
116
|
+
}
|
|
117
|
+
assert2(params.endDate instanceof Date);
|
|
118
|
+
assert2(params.startDate instanceof Date);
|
|
119
|
+
}
|
|
120
|
+
function adjustApplicationUri(params) {
|
|
121
|
+
const applicationUri = params.applicationUri || "";
|
|
122
|
+
if (applicationUri.length > 200) {
|
|
123
|
+
throw new Error(`Openssl doesn't support urn with length greater than 200${applicationUri}`);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
var init_common = __esm({
|
|
127
|
+
"packages/node-opcua-pki/lib/toolbox/common.ts"() {
|
|
128
|
+
"use strict";
|
|
129
|
+
init_esm_shims();
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
|
|
94
133
|
// packages/node-opcua-pki/lib/toolbox/config.ts
|
|
95
134
|
var g_config;
|
|
96
135
|
var init_config = __esm({
|
|
@@ -126,7 +165,7 @@ var init_debug = __esm({
|
|
|
126
165
|
});
|
|
127
166
|
|
|
128
167
|
// packages/node-opcua-pki/lib/toolbox/common2.ts
|
|
129
|
-
import
|
|
168
|
+
import assert3 from "assert";
|
|
130
169
|
import fs from "fs";
|
|
131
170
|
import path from "path";
|
|
132
171
|
import chalk from "chalk";
|
|
@@ -145,12 +184,31 @@ function mkdirRecursiveSync(folder) {
|
|
|
145
184
|
fs.mkdirSync(folder, { recursive: true });
|
|
146
185
|
}
|
|
147
186
|
}
|
|
187
|
+
function restrictPrivateFilePermissions(target, mode) {
|
|
188
|
+
if (process.platform === "win32") {
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
try {
|
|
192
|
+
fs.chmodSync(target, mode);
|
|
193
|
+
} catch (err) {
|
|
194
|
+
warningLog(chalk.yellow(" could not restrict permissions on "), target, err.message);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
function ensurePrivateDirectory(dir) {
|
|
198
|
+
if (!fs.existsSync(dir)) {
|
|
199
|
+
fs.mkdirSync(dir, { recursive: true, mode: 448 });
|
|
200
|
+
}
|
|
201
|
+
restrictPrivateFilePermissions(dir, 448);
|
|
202
|
+
}
|
|
203
|
+
function isEncryptedPrivateKeyFile(filename) {
|
|
204
|
+
return fs.readFileSync(filename, "utf-8").includes("-----BEGIN ENCRYPTED PRIVATE KEY-----");
|
|
205
|
+
}
|
|
148
206
|
function makePath(folderName, filename) {
|
|
149
207
|
let s;
|
|
150
208
|
if (filename) {
|
|
151
209
|
s = path.join(path.normalize(folderName), filename);
|
|
152
210
|
} else {
|
|
153
|
-
|
|
211
|
+
assert3(folderName);
|
|
154
212
|
s = folderName;
|
|
155
213
|
}
|
|
156
214
|
s = s.replace(/\\/g, "/");
|
|
@@ -203,21 +261,22 @@ var init_display = __esm({
|
|
|
203
261
|
});
|
|
204
262
|
|
|
205
263
|
// packages/node-opcua-pki/lib/toolbox/without_openssl/create_certificate_signing_request.ts
|
|
206
|
-
import
|
|
264
|
+
import assert4 from "assert";
|
|
207
265
|
import fs2 from "fs";
|
|
208
|
-
import { createCertificateSigningRequest, pemToPrivateKey, Subject } from "node-opcua-crypto";
|
|
266
|
+
import { coercePrivateKeyPem, createCertificateSigningRequest, pemToPrivateKey, Subject } from "node-opcua-crypto";
|
|
209
267
|
async function createCertificateSigningRequestAsync(certificateSigningRequestFilename, params) {
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
268
|
+
assert4(params);
|
|
269
|
+
assert4(params.rootDir);
|
|
270
|
+
assert4(params.configFile);
|
|
271
|
+
assert4(params.privateKey);
|
|
272
|
+
if (typeof params.privateKey === "string") {
|
|
273
|
+
assert4(fs2.existsSync(params.privateKey), `Private key must exist${params.privateKey}`);
|
|
274
|
+
}
|
|
275
|
+
assert4(fs2.existsSync(params.rootDir), "RootDir key must exist");
|
|
276
|
+
assert4(typeof certificateSigningRequestFilename === "string");
|
|
218
277
|
const subject = params.subject ? new Subject(params.subject).toString() : void 0;
|
|
219
278
|
displaySubtitle("- Creating a Certificate Signing Request with subtile");
|
|
220
|
-
const privateKeyPem = await fs2.promises.readFile(params.privateKey, "utf-8");
|
|
279
|
+
const privateKeyPem = typeof params.privateKey === "string" ? await fs2.promises.readFile(params.privateKey, "utf-8") : coercePrivateKeyPem(params.privateKey);
|
|
221
280
|
const privateKey = await pemToPrivateKey(privateKeyPem);
|
|
222
281
|
const { csr } = await createCertificateSigningRequest({
|
|
223
282
|
privateKey,
|
|
@@ -228,7 +287,7 @@ async function createCertificateSigningRequestAsync(certificateSigningRequestFil
|
|
|
228
287
|
purpose: params.purpose
|
|
229
288
|
});
|
|
230
289
|
await fs2.promises.writeFile(certificateSigningRequestFilename, csr, "utf-8");
|
|
231
|
-
display(`- privateKey ${params.privateKey}`);
|
|
290
|
+
display(`- privateKey ${typeof params.privateKey === "string" ? params.privateKey : "<in-memory>"}`);
|
|
232
291
|
display(`- certificateSigningRequestFilename ${certificateSigningRequestFilename}`);
|
|
233
292
|
}
|
|
234
293
|
var init_create_certificate_signing_request = __esm({
|
|
@@ -239,47 +298,12 @@ var init_create_certificate_signing_request = __esm({
|
|
|
239
298
|
}
|
|
240
299
|
});
|
|
241
300
|
|
|
242
|
-
// packages/node-opcua-pki/lib/toolbox/common.ts
|
|
243
|
-
import assert4 from "assert";
|
|
244
|
-
function quote(str) {
|
|
245
|
-
return `"${str || ""}"`;
|
|
246
|
-
}
|
|
247
|
-
function adjustDate(params) {
|
|
248
|
-
assert4(params instanceof Object);
|
|
249
|
-
params.startDate = params.startDate || /* @__PURE__ */ new Date();
|
|
250
|
-
assert4(params.startDate instanceof Date);
|
|
251
|
-
if (params.validityMs !== void 0) {
|
|
252
|
-
if (params.validityMs <= 0) {
|
|
253
|
-
throw new RangeError(`validityMs must be > 0 (got ${params.validityMs})`);
|
|
254
|
-
}
|
|
255
|
-
params.endDate = new Date(params.startDate.getTime() + params.validityMs);
|
|
256
|
-
params.validity = Math.ceil(params.validityMs / 864e5);
|
|
257
|
-
} else {
|
|
258
|
-
params.validity = params.validity || 365;
|
|
259
|
-
params.endDate = new Date(params.startDate.getTime());
|
|
260
|
-
params.endDate.setDate(params.startDate.getDate() + params.validity);
|
|
261
|
-
}
|
|
262
|
-
assert4(params.endDate instanceof Date);
|
|
263
|
-
assert4(params.startDate instanceof Date);
|
|
264
|
-
}
|
|
265
|
-
function adjustApplicationUri(params) {
|
|
266
|
-
const applicationUri = params.applicationUri || "";
|
|
267
|
-
if (applicationUri.length > 200) {
|
|
268
|
-
throw new Error(`Openssl doesn't support urn with length greater than 200${applicationUri}`);
|
|
269
|
-
}
|
|
270
|
-
}
|
|
271
|
-
var init_common = __esm({
|
|
272
|
-
"packages/node-opcua-pki/lib/toolbox/common.ts"() {
|
|
273
|
-
"use strict";
|
|
274
|
-
init_esm_shims();
|
|
275
|
-
}
|
|
276
|
-
});
|
|
277
|
-
|
|
278
301
|
// packages/node-opcua-pki/lib/toolbox/without_openssl/create_self_signed_certificate.ts
|
|
279
302
|
import assert5 from "assert";
|
|
280
303
|
import fs3 from "fs";
|
|
281
304
|
import {
|
|
282
305
|
CertificatePurpose,
|
|
306
|
+
coercePrivateKeyPem as coercePrivateKeyPem2,
|
|
283
307
|
createSelfSignedCertificate as createSelfSignedCertificate1,
|
|
284
308
|
pemToPrivateKey as pemToPrivateKey2,
|
|
285
309
|
Subject as Subject2
|
|
@@ -289,7 +313,9 @@ async function createSelfSignedCertificateAsync(certificate, params) {
|
|
|
289
313
|
assert5(params.purpose, "Please provide a Certificate Purpose");
|
|
290
314
|
assert5(fs3.existsSync(params.configFile));
|
|
291
315
|
assert5(fs3.existsSync(params.rootDir));
|
|
292
|
-
|
|
316
|
+
if (typeof params.privateKey === "string") {
|
|
317
|
+
assert5(fs3.existsSync(params.privateKey));
|
|
318
|
+
}
|
|
293
319
|
if (!params.subject) {
|
|
294
320
|
throw Error("Missing subject");
|
|
295
321
|
}
|
|
@@ -301,7 +327,7 @@ async function createSelfSignedCertificateAsync(certificate, params) {
|
|
|
301
327
|
subject = subject.toString();
|
|
302
328
|
const purpose = params.purpose;
|
|
303
329
|
displayTitle("Generate a certificate request");
|
|
304
|
-
const privateKeyPem = await fs3.promises.readFile(params.privateKey, "utf-8");
|
|
330
|
+
const privateKeyPem = typeof params.privateKey === "string" ? await fs3.promises.readFile(params.privateKey, "utf-8") : coercePrivateKeyPem2(params.privateKey);
|
|
305
331
|
const privateKey = await pemToPrivateKey2(privateKeyPem);
|
|
306
332
|
const { cert } = await createSelfSignedCertificate1({
|
|
307
333
|
privateKey,
|
|
@@ -365,9 +391,11 @@ import {
|
|
|
365
391
|
readCertificateChain,
|
|
366
392
|
readCertificateChainAsync,
|
|
367
393
|
readCertificateRevocationList,
|
|
394
|
+
readPrivateKey,
|
|
368
395
|
split_der,
|
|
369
396
|
toPem,
|
|
370
|
-
verifyCertificateSignature
|
|
397
|
+
verifyCertificateSignature,
|
|
398
|
+
writePrivateKeyFile
|
|
371
399
|
} from "node-opcua-crypto";
|
|
372
400
|
function getOrComputeInfo(entry) {
|
|
373
401
|
if (!entry.info) {
|
|
@@ -468,6 +496,7 @@ var init_certificate_manager = __esm({
|
|
|
468
496
|
"packages/node-opcua-pki/lib/pki/certificate_manager.ts"() {
|
|
469
497
|
"use strict";
|
|
470
498
|
init_esm_shims();
|
|
499
|
+
init_common();
|
|
471
500
|
init_common2();
|
|
472
501
|
init_debug();
|
|
473
502
|
init_without_openssl();
|
|
@@ -576,6 +605,15 @@ var init_certificate_manager = __esm({
|
|
|
576
605
|
#initializingPromise;
|
|
577
606
|
#addCertValidation;
|
|
578
607
|
#disableFileWatchers;
|
|
608
|
+
#privateKeyPassphrase;
|
|
609
|
+
#privateKeyProvider;
|
|
610
|
+
/**
|
|
611
|
+
* The on-disk key, decrypted once and kept for the instance's lifetime,
|
|
612
|
+
* so the passphrase (or its resolver function) is consulted at most
|
|
613
|
+
* once. Cleared by `dispose()`. Not used when a provider is configured:
|
|
614
|
+
* the provider is the authority on the current key.
|
|
615
|
+
*/
|
|
616
|
+
#cachedPrivateKey;
|
|
579
617
|
#thumbs = {
|
|
580
618
|
rejected: /* @__PURE__ */ new Map(),
|
|
581
619
|
trusted: /* @__PURE__ */ new Map(),
|
|
@@ -610,6 +648,8 @@ var init_certificate_manager = __esm({
|
|
|
610
648
|
maxChainLength: v.maxChainLength ?? 5
|
|
611
649
|
};
|
|
612
650
|
this.#disableFileWatchers = options.disableFileWatchers ?? process.env.OPCUA_PKI_DISABLE_FILE_WATCHERS === "true";
|
|
651
|
+
this.#privateKeyPassphrase = options.privateKeyPassphrase;
|
|
652
|
+
this.#privateKeyProvider = options.privateKeyProvider;
|
|
613
653
|
mkdirRecursiveSync(options.location);
|
|
614
654
|
if (!fs4.existsSync(this.#location)) {
|
|
615
655
|
throw new Error(`CertificateManager cannot access location ${this.#location}`);
|
|
@@ -623,10 +663,108 @@ var init_certificate_manager = __esm({
|
|
|
623
663
|
get rootDir() {
|
|
624
664
|
return this.#location;
|
|
625
665
|
}
|
|
626
|
-
/**
|
|
666
|
+
/**
|
|
667
|
+
* Path to the private key file (`own/private/private_key.pem`).
|
|
668
|
+
*
|
|
669
|
+
* Kept for backward compatibility with code that reads the key
|
|
670
|
+
* directly from disk. When a passphrase or a `privateKeyProvider` is
|
|
671
|
+
* configured, prefer {@link getPrivateKey} instead — this getter still
|
|
672
|
+
* returns the on-disk path even if a provider is configured (there may
|
|
673
|
+
* be no meaningful file in that case).
|
|
674
|
+
*/
|
|
627
675
|
get privateKey() {
|
|
628
676
|
return path2.join(this.rootDir, "own/private/private_key.pem");
|
|
629
677
|
}
|
|
678
|
+
/**
|
|
679
|
+
* Resolve the private key: from `privateKeyProvider` if configured,
|
|
680
|
+
* otherwise from disk (decrypting with `privateKeyPassphrase` if the
|
|
681
|
+
* key is encrypted). Fails closed — throws
|
|
682
|
+
* `PrivateKeyPassphraseRequiredError` — if the on-disk key is encrypted
|
|
683
|
+
* and no passphrase is configured, or if the wrong passphrase is
|
|
684
|
+
* configured.
|
|
685
|
+
*
|
|
686
|
+
* The on-disk key is read and decrypted once and then cached for the
|
|
687
|
+
* lifetime of this instance, so a `privateKeyPassphrase` function is
|
|
688
|
+
* called at most once (concurrent first calls share the same read). A
|
|
689
|
+
* failed read is not cached, so a caller can fix the passphrase and
|
|
690
|
+
* retry. A `privateKeyProvider` is consulted on every call: it is the
|
|
691
|
+
* authority on what the current key is.
|
|
692
|
+
*/
|
|
693
|
+
async getPrivateKey() {
|
|
694
|
+
if (this.#privateKeyProvider) {
|
|
695
|
+
return await this.#privateKeyProvider.getPrivateKey();
|
|
696
|
+
}
|
|
697
|
+
if (this.#cachedPrivateKey) {
|
|
698
|
+
return this.#cachedPrivateKey;
|
|
699
|
+
}
|
|
700
|
+
if (!this.#privateKeyPromise) {
|
|
701
|
+
this.#privateKeyPromise = (async () => {
|
|
702
|
+
const passphrase = await resolvePrivateKeyPassphrase(this.#privateKeyPassphrase);
|
|
703
|
+
return readPrivateKey(this.privateKey, passphrase);
|
|
704
|
+
})().then(
|
|
705
|
+
(key) => {
|
|
706
|
+
this.#cachedPrivateKey = key;
|
|
707
|
+
return key;
|
|
708
|
+
},
|
|
709
|
+
(err) => {
|
|
710
|
+
this.#privateKeyPromise = void 0;
|
|
711
|
+
throw err;
|
|
712
|
+
}
|
|
713
|
+
);
|
|
714
|
+
}
|
|
715
|
+
return await this.#privateKeyPromise;
|
|
716
|
+
}
|
|
717
|
+
/** In-flight first read of the on-disk key, so concurrent callers share one passphrase resolution. */
|
|
718
|
+
#privateKeyPromise;
|
|
719
|
+
/**
|
|
720
|
+
* Enable, disable, or rotate the passphrase protecting the on-disk
|
|
721
|
+
* private key: decrypt with `oldPassphrase` (omit if the key is
|
|
722
|
+
* currently unencrypted), then write back encrypted with
|
|
723
|
+
* `newPassphrase` (omit to leave it unencrypted). The write goes to a
|
|
724
|
+
* temporary file in the same directory and is atomically renamed into
|
|
725
|
+
* place, so a crash mid-rotation cannot leave a partially-written key;
|
|
726
|
+
* the temporary file is removed if anything fails, so a rotation *to*
|
|
727
|
+
* plaintext can never leave a stray cleartext copy behind. Runs under
|
|
728
|
+
* the same lock as `initialize()`.
|
|
729
|
+
*
|
|
730
|
+
* This only rewrites the on-disk file — it does not update this
|
|
731
|
+
* instance's own `privateKeyPassphrase` (set at construction), and it
|
|
732
|
+
* drops this instance's cached key so that disk stays the source of
|
|
733
|
+
* truth. Construct a new `CertificateManager` with the new passphrase to
|
|
734
|
+
* continue using it afterward.
|
|
735
|
+
*
|
|
736
|
+
* Not supported when a `privateKeyProvider` is configured (there is no
|
|
737
|
+
* disk file for this method to rewrite).
|
|
738
|
+
*/
|
|
739
|
+
async reencryptPrivateKey(oldPassphrase, newPassphrase) {
|
|
740
|
+
if (this.#privateKeyProvider) {
|
|
741
|
+
throw new Error("reencryptPrivateKey: not supported when a privateKeyProvider is configured");
|
|
742
|
+
}
|
|
743
|
+
const oldPass = await resolvePrivateKeyPassphrase(oldPassphrase);
|
|
744
|
+
const newPass = await resolvePrivateKeyPassphrase(newPassphrase);
|
|
745
|
+
await this.withLock2(async () => {
|
|
746
|
+
const privateKey = readPrivateKey(this.privateKey, oldPass);
|
|
747
|
+
await this.#rewritePrivateKeyFile(privateKey, newPass);
|
|
748
|
+
});
|
|
749
|
+
this.#cachedPrivateKey = void 0;
|
|
750
|
+
this.#privateKeyPromise = void 0;
|
|
751
|
+
}
|
|
752
|
+
/**
|
|
753
|
+
* Atomically replace the on-disk private key with `privateKey`, written
|
|
754
|
+
* as PKCS#8 (encrypted with `passphrase` if given). Temp file next to the
|
|
755
|
+
* target, `0600`, renamed into place; the temp file is unlinked on any
|
|
756
|
+
* failure so no partial or cleartext copy can be left behind.
|
|
757
|
+
* Caller must hold the lock.
|
|
758
|
+
*/
|
|
759
|
+
async #rewritePrivateKeyFile(privateKey, passphrase) {
|
|
760
|
+
const tmpFilename = `${this.privateKey}.${process.pid}-${Date.now()}.tmp`;
|
|
761
|
+
try {
|
|
762
|
+
await writePrivateKeyFile(tmpFilename, privateKey, { passphrase });
|
|
763
|
+
await fs4.promises.rename(tmpFilename, this.privateKey);
|
|
764
|
+
} finally {
|
|
765
|
+
await fs4.promises.rm(tmpFilename, { force: true });
|
|
766
|
+
}
|
|
767
|
+
}
|
|
630
768
|
/** Path to the OpenSSL random seed file. */
|
|
631
769
|
get randomFile() {
|
|
632
770
|
return path2.join(this.rootDir, "./random.rnd");
|
|
@@ -926,19 +1064,24 @@ var init_certificate_manager = __esm({
|
|
|
926
1064
|
}
|
|
927
1065
|
this.state = 1 /* Initializing */;
|
|
928
1066
|
this.#initializingPromise = this.#initialize();
|
|
929
|
-
|
|
1067
|
+
try {
|
|
1068
|
+
await this.#initializingPromise;
|
|
1069
|
+
} catch (err) {
|
|
1070
|
+
this.#initializingPromise = void 0;
|
|
1071
|
+
this.state = 0 /* Uninitialized */;
|
|
1072
|
+
throw err;
|
|
1073
|
+
}
|
|
930
1074
|
this.#initializingPromise = void 0;
|
|
931
1075
|
this.state = 2 /* Initialized */;
|
|
932
1076
|
_CertificateManager.#activeInstances.add(this);
|
|
933
1077
|
_CertificateManager.#installProcessCleanup();
|
|
934
1078
|
}
|
|
935
1079
|
async #initialize() {
|
|
936
|
-
this.state = 1 /* Initializing */;
|
|
937
1080
|
const pkiDir = this.#location;
|
|
938
1081
|
mkdirRecursiveSync(pkiDir);
|
|
939
1082
|
mkdirRecursiveSync(path2.join(pkiDir, "own"));
|
|
940
1083
|
mkdirRecursiveSync(path2.join(pkiDir, "own/certs"));
|
|
941
|
-
|
|
1084
|
+
ensurePrivateDirectory(path2.join(pkiDir, "own/private"));
|
|
942
1085
|
mkdirRecursiveSync(path2.join(pkiDir, "rejected"));
|
|
943
1086
|
mkdirRecursiveSync(path2.join(pkiDir, "trusted"));
|
|
944
1087
|
mkdirRecursiveSync(path2.join(pkiDir, "trusted/certs"));
|
|
@@ -946,7 +1089,10 @@ var init_certificate_manager = __esm({
|
|
|
946
1089
|
mkdirRecursiveSync(path2.join(pkiDir, "issuers"));
|
|
947
1090
|
mkdirRecursiveSync(path2.join(pkiDir, "issuers/certs"));
|
|
948
1091
|
mkdirRecursiveSync(path2.join(pkiDir, "issuers/crl"));
|
|
949
|
-
|
|
1092
|
+
const ownsDiskKey = !this.#privateKeyProvider;
|
|
1093
|
+
const needsKeyGeneration = ownsDiskKey && !fs4.existsSync(this.privateKey);
|
|
1094
|
+
const needsKeyEncryption = ownsDiskKey && !needsKeyGeneration && this.#privateKeyPassphrase !== void 0 && !isEncryptedPrivateKeyFile(this.privateKey);
|
|
1095
|
+
if (!fs4.existsSync(this.configFile) || needsKeyGeneration || needsKeyEncryption) {
|
|
950
1096
|
return await this.withLock2(async () => {
|
|
951
1097
|
if (this.state === 3 /* Disposing */ || this.state === 4 /* Disposed */) {
|
|
952
1098
|
return;
|
|
@@ -954,15 +1100,29 @@ var init_certificate_manager = __esm({
|
|
|
954
1100
|
if (!fs4.existsSync(this.configFile)) {
|
|
955
1101
|
fs4.writeFileSync(this.configFile, configurationFileSimpleTemplate);
|
|
956
1102
|
}
|
|
957
|
-
if (!fs4.existsSync(this.privateKey)) {
|
|
1103
|
+
if (ownsDiskKey && !fs4.existsSync(this.privateKey)) {
|
|
958
1104
|
debugLog("generating private key ...");
|
|
959
|
-
await
|
|
960
|
-
await this
|
|
961
|
-
|
|
962
|
-
|
|
1105
|
+
const passphrase = await resolvePrivateKeyPassphrase(this.#privateKeyPassphrase);
|
|
1106
|
+
await generatePrivateKeyFile(this.privateKey, this.keySize, { passphrase });
|
|
1107
|
+
this.#cachedPrivateKey = readPrivateKey(this.privateKey, passphrase);
|
|
1108
|
+
} else if (ownsDiskKey && this.#privateKeyPassphrase !== void 0 && !isEncryptedPrivateKeyFile(this.privateKey)) {
|
|
1109
|
+
warningLog("initialize: private key is plaintext but a passphrase is configured; encrypting it in place");
|
|
1110
|
+
const passphrase = await resolvePrivateKeyPassphrase(this.#privateKeyPassphrase);
|
|
1111
|
+
const plaintextKey = readPrivateKey(this.privateKey);
|
|
1112
|
+
await this.#rewritePrivateKeyFile(plaintextKey, passphrase);
|
|
1113
|
+
this.#cachedPrivateKey = plaintextKey;
|
|
1114
|
+
}
|
|
1115
|
+
if (ownsDiskKey) {
|
|
1116
|
+
restrictPrivateFilePermissions(this.privateKey, 384);
|
|
963
1117
|
}
|
|
1118
|
+
await this.getPrivateKey();
|
|
1119
|
+
await this.#readCertificates();
|
|
964
1120
|
});
|
|
965
1121
|
} else {
|
|
1122
|
+
if (ownsDiskKey) {
|
|
1123
|
+
restrictPrivateFilePermissions(this.privateKey, 384);
|
|
1124
|
+
}
|
|
1125
|
+
await this.getPrivateKey();
|
|
966
1126
|
await this.#readCertificates();
|
|
967
1127
|
}
|
|
968
1128
|
}
|
|
@@ -998,6 +1158,8 @@ var init_certificate_manager = __esm({
|
|
|
998
1158
|
this.#watchers.splice(0);
|
|
999
1159
|
} finally {
|
|
1000
1160
|
this.state = 4 /* Disposed */;
|
|
1161
|
+
this.#cachedPrivateKey = void 0;
|
|
1162
|
+
this.#privateKeyPromise = void 0;
|
|
1001
1163
|
_CertificateManager.#activeInstances.delete(this);
|
|
1002
1164
|
}
|
|
1003
1165
|
}
|
|
@@ -1044,16 +1206,18 @@ var init_certificate_manager = __esm({
|
|
|
1044
1206
|
if (typeof params.applicationUri !== "string") {
|
|
1045
1207
|
throw new Error("createSelfSignedCertificate: expecting applicationUri to be a string");
|
|
1046
1208
|
}
|
|
1047
|
-
if (!fs4.existsSync(this.privateKey)) {
|
|
1209
|
+
if (!this.#privateKeyProvider && !fs4.existsSync(this.privateKey)) {
|
|
1048
1210
|
throw new Error(`Cannot find private key ${this.privateKey}`);
|
|
1049
1211
|
}
|
|
1050
1212
|
let certificateFilename = path2.join(this.rootDir, "own/certs/self_signed_certificate.pem");
|
|
1051
1213
|
certificateFilename = params.outputFile || certificateFilename;
|
|
1052
|
-
const _params =
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1214
|
+
const _params = {
|
|
1215
|
+
...params,
|
|
1216
|
+
rootDir: this.rootDir,
|
|
1217
|
+
configFile: this.configFile,
|
|
1218
|
+
privateKey: await this.getPrivateKey(),
|
|
1219
|
+
subject: params.subject || "CN=FIXME"
|
|
1220
|
+
};
|
|
1057
1221
|
await this.withLock2(async () => {
|
|
1058
1222
|
await createSelfSignedCertificate(certificateFilename, _params);
|
|
1059
1223
|
});
|
|
@@ -1072,13 +1236,15 @@ var init_certificate_manager = __esm({
|
|
|
1072
1236
|
if (!params) {
|
|
1073
1237
|
throw new Error("params is required");
|
|
1074
1238
|
}
|
|
1075
|
-
|
|
1076
|
-
if (Object.prototype.hasOwnProperty.call(_params, "rootDir")) {
|
|
1239
|
+
if (Object.prototype.hasOwnProperty.call(params, "rootDir")) {
|
|
1077
1240
|
throw new Error("rootDir should not be specified ");
|
|
1078
1241
|
}
|
|
1079
|
-
_params
|
|
1080
|
-
|
|
1081
|
-
|
|
1242
|
+
const _params = {
|
|
1243
|
+
...params,
|
|
1244
|
+
rootDir: path2.resolve(this.rootDir),
|
|
1245
|
+
configFile: path2.resolve(this.configFile),
|
|
1246
|
+
privateKey: await this.getPrivateKey()
|
|
1247
|
+
};
|
|
1082
1248
|
return await this.withLock2(async () => {
|
|
1083
1249
|
const now = /* @__PURE__ */ new Date();
|
|
1084
1250
|
const today2 = `${now.toISOString().slice(0, 10)}_${now.getTime()}`;
|
|
@@ -1940,6 +2106,19 @@ var init_toolbox = __esm({
|
|
|
1940
2106
|
});
|
|
1941
2107
|
|
|
1942
2108
|
// packages/node-opcua-pki/lib/toolbox/with_openssl/_env.ts
|
|
2109
|
+
function buildChildEnv(extra) {
|
|
2110
|
+
const env = {};
|
|
2111
|
+
for (const key of Object.keys(process.env)) {
|
|
2112
|
+
if (SAFE_ENV_PASSTHROUGH.has(key.toLowerCase())) {
|
|
2113
|
+
env[key] = process.env[key];
|
|
2114
|
+
}
|
|
2115
|
+
}
|
|
2116
|
+
return { ...env, ...extra };
|
|
2117
|
+
}
|
|
2118
|
+
function redactEnvForLog(options) {
|
|
2119
|
+
const { env, ...rest } = options;
|
|
2120
|
+
return env ? { ...rest, env: Object.keys(env) } : rest;
|
|
2121
|
+
}
|
|
1943
2122
|
function setEnv(varName, value) {
|
|
1944
2123
|
if (!g_config.silent) {
|
|
1945
2124
|
warningLog(` set ${varName}=${value}`);
|
|
@@ -1982,13 +2161,44 @@ function processAltNames(params) {
|
|
|
1982
2161
|
const subjectAltNameString = subjectAltName.join(", ");
|
|
1983
2162
|
setEnv("ALTNAME", subjectAltNameString);
|
|
1984
2163
|
}
|
|
1985
|
-
var exportedEnvVars;
|
|
2164
|
+
var SAFE_ENV_PASSTHROUGH, exportedEnvVars;
|
|
1986
2165
|
var init_env = __esm({
|
|
1987
2166
|
"packages/node-opcua-pki/lib/toolbox/with_openssl/_env.ts"() {
|
|
1988
2167
|
"use strict";
|
|
1989
2168
|
init_esm_shims();
|
|
1990
2169
|
init_config();
|
|
1991
2170
|
init_debug();
|
|
2171
|
+
SAFE_ENV_PASSTHROUGH = /* @__PURE__ */ new Set([
|
|
2172
|
+
// POSIX/Windows shell and process essentials
|
|
2173
|
+
"path",
|
|
2174
|
+
"home",
|
|
2175
|
+
"userprofile",
|
|
2176
|
+
"temp",
|
|
2177
|
+
"tmp",
|
|
2178
|
+
"tmpdir",
|
|
2179
|
+
"systemroot",
|
|
2180
|
+
"windir",
|
|
2181
|
+
"comspec",
|
|
2182
|
+
"pathext",
|
|
2183
|
+
"appdata",
|
|
2184
|
+
"localappdata",
|
|
2185
|
+
// dynamic loader: a custom-built or relocated openssl (e.g. under /opt,
|
|
2186
|
+
// or Homebrew on macOS) may need these to find its own libcrypto/libssl
|
|
2187
|
+
"ld_library_path",
|
|
2188
|
+
"dyld_library_path",
|
|
2189
|
+
"dyld_fallback_library_path",
|
|
2190
|
+
// locale, so openssl's textual output stays parseable
|
|
2191
|
+
"lang",
|
|
2192
|
+
"lc_all",
|
|
2193
|
+
"lc_ctype",
|
|
2194
|
+
// openssl configuration this app, or the host OS/user, may rely on
|
|
2195
|
+
"openssl_conf",
|
|
2196
|
+
"randfile",
|
|
2197
|
+
"openssl_modules",
|
|
2198
|
+
"openssl_engines",
|
|
2199
|
+
"ssl_cert_file",
|
|
2200
|
+
"ssl_cert_dir"
|
|
2201
|
+
]);
|
|
1992
2202
|
exportedEnvVars = {};
|
|
1993
2203
|
}
|
|
1994
2204
|
});
|
|
@@ -2011,24 +2221,17 @@ import { pipeline } from "stream/promises";
|
|
|
2011
2221
|
import byline from "byline";
|
|
2012
2222
|
import chalk4 from "chalk";
|
|
2013
2223
|
import yauzl from "yauzl";
|
|
2014
|
-
async function execute(
|
|
2224
|
+
async function execute(file, args, cwd) {
|
|
2015
2225
|
let output = "";
|
|
2016
|
-
const options = {
|
|
2017
|
-
cwd,
|
|
2018
|
-
windowsHide: true
|
|
2019
|
-
};
|
|
2020
2226
|
return await new Promise((resolve, reject) => {
|
|
2021
|
-
const child = child_process.
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
(
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
}
|
|
2030
|
-
}
|
|
2031
|
-
);
|
|
2227
|
+
const child = child_process.spawn(file, args, {
|
|
2228
|
+
cwd,
|
|
2229
|
+
windowsHide: true,
|
|
2230
|
+
env: buildChildEnv(),
|
|
2231
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
2232
|
+
});
|
|
2233
|
+
child.on("error", (err) => reject(err));
|
|
2234
|
+
child.on("close", (code) => resolve({ exitCode: code ?? 1, output }));
|
|
2032
2235
|
const stream1 = byline(child.stdout);
|
|
2033
2236
|
stream1.on("data", (line) => {
|
|
2034
2237
|
output += `${line}
|
|
@@ -2040,16 +2243,13 @@ async function execute(cmd, cwd) {
|
|
|
2040
2243
|
});
|
|
2041
2244
|
});
|
|
2042
2245
|
}
|
|
2043
|
-
function quote2(str) {
|
|
2044
|
-
return `"${str.replace(/\\/g, "/")}"`;
|
|
2045
|
-
}
|
|
2046
2246
|
function is_expected_openssl_version(strVersion) {
|
|
2047
2247
|
return !!strVersion.match(/OpenSSL \d/);
|
|
2048
2248
|
}
|
|
2049
2249
|
async function getopensslExecPath() {
|
|
2050
2250
|
let result1;
|
|
2051
2251
|
try {
|
|
2052
|
-
result1 = await execute("which openssl");
|
|
2252
|
+
result1 = await execute("which", ["openssl"]);
|
|
2053
2253
|
} catch (err) {
|
|
2054
2254
|
warningLog("warning: ", err.message);
|
|
2055
2255
|
throw new Error("Cannot find openssl");
|
|
@@ -2066,11 +2266,10 @@ async function getopensslExecPath() {
|
|
|
2066
2266
|
}
|
|
2067
2267
|
async function check_system_openssl_version() {
|
|
2068
2268
|
const opensslExecPath = await getopensslExecPath();
|
|
2069
|
-
const q_opensslExecPath = quote2(opensslExecPath);
|
|
2070
2269
|
if (doDebug2) {
|
|
2071
2270
|
warningLog(` OpenSSL found in : ${chalk4.yellow(opensslExecPath)}`);
|
|
2072
2271
|
}
|
|
2073
|
-
const result = await execute(
|
|
2272
|
+
const result = await execute(opensslExecPath, ["version"]);
|
|
2074
2273
|
const exitCode = result?.exitCode;
|
|
2075
2274
|
const output = result?.output;
|
|
2076
2275
|
const version = output.trim();
|
|
@@ -2110,9 +2309,8 @@ async function install_and_check_win32_openssl_version() {
|
|
|
2110
2309
|
version: `cannot find file ${opensslExecPath2}`
|
|
2111
2310
|
};
|
|
2112
2311
|
} else {
|
|
2113
|
-
const q_openssl_exe_path = quote2(opensslExecPath2);
|
|
2114
2312
|
const cwd = ".";
|
|
2115
|
-
const { exitCode, output } = await execute(
|
|
2313
|
+
const { exitCode, output } = await execute(opensslExecPath2, ["version"], cwd);
|
|
2116
2314
|
const version = output.trim();
|
|
2117
2315
|
if (doDebug2) {
|
|
2118
2316
|
warningLog(" Version = ", version);
|
|
@@ -2125,7 +2323,7 @@ async function install_and_check_win32_openssl_version() {
|
|
|
2125
2323
|
}
|
|
2126
2324
|
async function find_system_openssl_win32() {
|
|
2127
2325
|
try {
|
|
2128
|
-
const result = await execute("where openssl");
|
|
2326
|
+
const result = await execute("where", ["openssl"]);
|
|
2129
2327
|
if (result.exitCode !== 0) {
|
|
2130
2328
|
return void 0;
|
|
2131
2329
|
}
|
|
@@ -2133,8 +2331,7 @@ async function install_and_check_win32_openssl_version() {
|
|
|
2133
2331
|
if (!opensslPath2 || !fs5.existsSync(opensslPath2)) {
|
|
2134
2332
|
return void 0;
|
|
2135
2333
|
}
|
|
2136
|
-
const
|
|
2137
|
-
const versionResult = await execute(`${q5} version`);
|
|
2334
|
+
const versionResult = await execute(opensslPath2, ["version"]);
|
|
2138
2335
|
const version = versionResult.output.trim();
|
|
2139
2336
|
if (versionResult.exitCode === 0 && is_expected_openssl_version(version)) {
|
|
2140
2337
|
warningLog(
|
|
@@ -2299,6 +2496,7 @@ var init_install_prerequisite = __esm({
|
|
|
2299
2496
|
"use strict";
|
|
2300
2497
|
init_esm_shims();
|
|
2301
2498
|
init_debug();
|
|
2499
|
+
init_env();
|
|
2302
2500
|
doDebug2 = process.env.NODEOPCUAPKIDEBUG || false;
|
|
2303
2501
|
}
|
|
2304
2502
|
});
|
|
@@ -2310,36 +2508,53 @@ import fs6 from "fs";
|
|
|
2310
2508
|
import os3 from "os";
|
|
2311
2509
|
import byline2 from "byline";
|
|
2312
2510
|
import chalk5 from "chalk";
|
|
2313
|
-
|
|
2511
|
+
function passinArg(passphrase = "") {
|
|
2512
|
+
return { args: ["-passin", `env:${PASSIN_ENV_VAR}`], env: { [PASSIN_ENV_VAR]: passphrase } };
|
|
2513
|
+
}
|
|
2514
|
+
function passoutArg(passphrase = "") {
|
|
2515
|
+
return { args: ["-passout", `env:${PASSOUT_ENV_VAR}`], env: { [PASSOUT_ENV_VAR]: passphrase } };
|
|
2516
|
+
}
|
|
2517
|
+
function renderForDisplay(file, args) {
|
|
2518
|
+
return [file, ...args].map((a) => a === "" || /[\s"'`$\\]/.test(a) ? JSON.stringify(a) : a).join(" ");
|
|
2519
|
+
}
|
|
2520
|
+
async function execute2(file, args, options) {
|
|
2314
2521
|
const from = new Error();
|
|
2315
2522
|
options.cwd = options.cwd || process.cwd();
|
|
2316
2523
|
if (!g_config.silent) {
|
|
2317
2524
|
warningLog(chalk5.cyan(" CWD "), options.cwd);
|
|
2318
2525
|
}
|
|
2319
2526
|
const outputs = [];
|
|
2527
|
+
const errorOutputs = [];
|
|
2528
|
+
const display2 = renderForDisplay(file, args);
|
|
2320
2529
|
return await new Promise((resolve, reject) => {
|
|
2321
|
-
const child = child_process2.
|
|
2322
|
-
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
|
|
2331
|
-
|
|
2332
|
-
|
|
2333
|
-
|
|
2334
|
-
|
|
2335
|
-
|
|
2336
|
-
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
|
|
2530
|
+
const child = child_process2.spawn(file, [...args], {
|
|
2531
|
+
cwd: options.cwd,
|
|
2532
|
+
windowsHide: true,
|
|
2533
|
+
env: buildChildEnv(options.env),
|
|
2534
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
2535
|
+
});
|
|
2536
|
+
const fail = (message) => {
|
|
2537
|
+
if (!options.hideErrorMessage) {
|
|
2538
|
+
const fence = "###########################################";
|
|
2539
|
+
console.error(chalk5.bgWhiteBright.redBright(`${fence} OPENSSL ERROR ${fence}`));
|
|
2540
|
+
console.error(chalk5.bgWhiteBright.redBright(`CWD = ${options.cwd}`));
|
|
2541
|
+
console.error(chalk5.bgWhiteBright.redBright(message));
|
|
2542
|
+
console.error(chalk5.bgWhiteBright.redBright(`${fence} OPENSSL ERROR ${fence}`));
|
|
2543
|
+
console.error(from.stack);
|
|
2544
|
+
}
|
|
2545
|
+
reject(new Error(message));
|
|
2546
|
+
};
|
|
2547
|
+
child.on("error", (err) => fail(`Command failed: ${display2}
|
|
2548
|
+
${err.message}`));
|
|
2549
|
+
child.on("close", (code, signal) => {
|
|
2550
|
+
if (code === 0) {
|
|
2340
2551
|
resolve(outputs.join(""));
|
|
2552
|
+
return;
|
|
2341
2553
|
}
|
|
2342
|
-
|
|
2554
|
+
const why = signal ? `signal ${signal}` : `exit code ${code}`;
|
|
2555
|
+
fail(`Command failed: ${display2}
|
|
2556
|
+
${errorOutputs.join("")}(${why})`);
|
|
2557
|
+
});
|
|
2343
2558
|
if (child.stdout) {
|
|
2344
2559
|
const stream2 = byline2(child.stdout);
|
|
2345
2560
|
stream2.on("data", (line) => {
|
|
@@ -2356,17 +2571,17 @@ async function execute2(cmd, options) {
|
|
|
2356
2571
|
});
|
|
2357
2572
|
}
|
|
2358
2573
|
}
|
|
2359
|
-
if (
|
|
2360
|
-
|
|
2361
|
-
|
|
2362
|
-
|
|
2363
|
-
|
|
2364
|
-
if (displayError) {
|
|
2365
|
-
process.stdout.write(`${chalk5.white(" stderr ") + chalk5.red(line)}
|
|
2574
|
+
if (child.stderr) {
|
|
2575
|
+
const stream1 = byline2(child.stderr);
|
|
2576
|
+
stream1.on("data", (line) => {
|
|
2577
|
+
line = line.toString();
|
|
2578
|
+
errorOutputs.push(`${line}
|
|
2366
2579
|
`);
|
|
2367
|
-
|
|
2368
|
-
}
|
|
2369
|
-
|
|
2580
|
+
if (!g_config.silent && displayError) {
|
|
2581
|
+
process.stdout.write(`${chalk5.white(" stderr ") + chalk5.red(line)}
|
|
2582
|
+
`);
|
|
2583
|
+
}
|
|
2584
|
+
});
|
|
2370
2585
|
}
|
|
2371
2586
|
});
|
|
2372
2587
|
}
|
|
@@ -2376,18 +2591,18 @@ async function find_openssl() {
|
|
|
2376
2591
|
async function ensure_openssl_installed() {
|
|
2377
2592
|
if (!opensslPath) {
|
|
2378
2593
|
opensslPath = await find_openssl();
|
|
2379
|
-
const outputs = await execute_openssl("version", { cwd: "." });
|
|
2594
|
+
const outputs = await execute_openssl(["version"], { cwd: "." });
|
|
2380
2595
|
g_config.opensslVersion = outputs.trim();
|
|
2381
2596
|
if (doDebug) {
|
|
2382
2597
|
warningLog("OpenSSL version : ", g_config.opensslVersion);
|
|
2383
2598
|
}
|
|
2384
2599
|
}
|
|
2385
2600
|
}
|
|
2386
|
-
async function execute_openssl_no_failure(
|
|
2601
|
+
async function execute_openssl_no_failure(args, options) {
|
|
2387
2602
|
options = options || {};
|
|
2388
2603
|
options.hideErrorMessage = true;
|
|
2389
2604
|
try {
|
|
2390
|
-
return await execute_openssl(
|
|
2605
|
+
return await execute_openssl(args, options);
|
|
2391
2606
|
} catch (err) {
|
|
2392
2607
|
debugLog(" (ignored error = ERROR : )", err.message);
|
|
2393
2608
|
}
|
|
@@ -2395,8 +2610,8 @@ async function execute_openssl_no_failure(cmd, options) {
|
|
|
2395
2610
|
function getTempFolder() {
|
|
2396
2611
|
return os3.tmpdir();
|
|
2397
2612
|
}
|
|
2398
|
-
async function execute_openssl(
|
|
2399
|
-
debugLog("execute_openssl",
|
|
2613
|
+
async function execute_openssl(args, options) {
|
|
2614
|
+
debugLog("execute_openssl", args, redactEnvForLog(options));
|
|
2400
2615
|
const empty_config_file = n(getTempFolder(), "empty_config.cnf");
|
|
2401
2616
|
if (!fs6.existsSync(empty_config_file)) {
|
|
2402
2617
|
await fs6.promises.writeFile(empty_config_file, "# empty config file");
|
|
@@ -2408,23 +2623,24 @@ async function execute_openssl(cmd, options) {
|
|
|
2408
2623
|
if (!g_config.silent) {
|
|
2409
2624
|
warningLog(chalk5.cyan(" OPENSSL_CONF"), process.env.OPENSSL_CONF);
|
|
2410
2625
|
warningLog(chalk5.cyan(" RANDFILE "), process.env.RANDFILE);
|
|
2411
|
-
warningLog(chalk5.cyan(" CMD
|
|
2626
|
+
warningLog(chalk5.cyan(" CMD "), chalk5.cyanBright(renderForDisplay("openssl", args)));
|
|
2412
2627
|
}
|
|
2413
2628
|
await ensure_openssl_installed();
|
|
2414
|
-
return await execute2(
|
|
2629
|
+
return await execute2(opensslPath, args, options);
|
|
2415
2630
|
}
|
|
2416
|
-
var opensslPath, n;
|
|
2631
|
+
var opensslPath, n, PASSIN_ENV_VAR, PASSOUT_ENV_VAR;
|
|
2417
2632
|
var init_execute_openssl = __esm({
|
|
2418
2633
|
"packages/node-opcua-pki/lib/toolbox/with_openssl/execute_openssl.ts"() {
|
|
2419
2634
|
"use strict";
|
|
2420
2635
|
init_esm_shims();
|
|
2421
|
-
init_common();
|
|
2422
2636
|
init_common2();
|
|
2423
2637
|
init_config();
|
|
2424
2638
|
init_debug();
|
|
2425
2639
|
init_env();
|
|
2426
2640
|
init_install_prerequisite();
|
|
2427
2641
|
n = makePath;
|
|
2642
|
+
PASSIN_ENV_VAR = "NODE_OPCUA_PKI_OPENSSL_PASSIN";
|
|
2643
|
+
PASSOUT_ENV_VAR = "NODE_OPCUA_PKI_OPENSSL_PASSOUT";
|
|
2428
2644
|
}
|
|
2429
2645
|
});
|
|
2430
2646
|
|
|
@@ -2463,9 +2679,12 @@ function generateStaticConfig(configPath, options) {
|
|
|
2463
2679
|
return temporaryConfigPath;
|
|
2464
2680
|
}
|
|
2465
2681
|
}
|
|
2466
|
-
async function getPublicKeyFromPrivateKey(privateKeyFilename, publicKeyFilename) {
|
|
2682
|
+
async function getPublicKeyFromPrivateKey(privateKeyFilename, publicKeyFilename, passphrase) {
|
|
2467
2683
|
assert7(fs7.existsSync(privateKeyFilename));
|
|
2468
|
-
|
|
2684
|
+
const passin = passinArg(passphrase);
|
|
2685
|
+
await execute_openssl(["rsa", "-pubout", "-in", n2(privateKeyFilename), "-out", n2(publicKeyFilename), ...passin.args], {
|
|
2686
|
+
env: passin.env
|
|
2687
|
+
});
|
|
2469
2688
|
}
|
|
2470
2689
|
function x509Date(date) {
|
|
2471
2690
|
date = date || /* @__PURE__ */ new Date();
|
|
@@ -2486,30 +2705,28 @@ function x509Date(date) {
|
|
|
2486
2705
|
}
|
|
2487
2706
|
async function dumpCertificate(certificate) {
|
|
2488
2707
|
assert7(fs7.existsSync(certificate));
|
|
2489
|
-
return await execute_openssl(
|
|
2708
|
+
return await execute_openssl(["x509", "-in", n2(certificate), "-text", "-noout"], {});
|
|
2490
2709
|
}
|
|
2491
2710
|
async function toDer(certificatePem) {
|
|
2492
2711
|
assert7(fs7.existsSync(certificatePem));
|
|
2493
2712
|
const certificateDer = certificatePem.replace(".pem", ".der");
|
|
2494
|
-
return await execute_openssl(
|
|
2713
|
+
return await execute_openssl(["x509", "-outform", "der", "-in", certificatePem, "-out", certificateDer], {});
|
|
2495
2714
|
}
|
|
2496
2715
|
async function fingerprint(certificatePem) {
|
|
2497
2716
|
assert7(fs7.existsSync(certificatePem));
|
|
2498
|
-
return await execute_openssl(
|
|
2717
|
+
return await execute_openssl(["x509", "-fingerprint", "-noout", "-in", certificatePem], {});
|
|
2499
2718
|
}
|
|
2500
|
-
var _counter,
|
|
2719
|
+
var _counter, n2;
|
|
2501
2720
|
var init_toolbox2 = __esm({
|
|
2502
2721
|
"packages/node-opcua-pki/lib/toolbox/with_openssl/toolbox.ts"() {
|
|
2503
2722
|
"use strict";
|
|
2504
2723
|
init_esm_shims();
|
|
2505
|
-
init_common();
|
|
2506
2724
|
init_common2();
|
|
2507
2725
|
init_config();
|
|
2508
2726
|
init_env();
|
|
2509
2727
|
init_execute_openssl();
|
|
2510
2728
|
g_config.opensslVersion = "";
|
|
2511
2729
|
_counter = 0;
|
|
2512
|
-
q = quote;
|
|
2513
2730
|
n2 = makePath;
|
|
2514
2731
|
}
|
|
2515
2732
|
});
|
|
@@ -2531,28 +2748,37 @@ async function createCertificateSigningRequestWithOpenSSL(certificateSigningRequ
|
|
|
2531
2748
|
processAltNames(params);
|
|
2532
2749
|
const configFile = generateStaticConfig(params.configFile, { cwd: params.rootDir });
|
|
2533
2750
|
const options = { cwd: params.rootDir, openssl_conf: path5.relative(params.rootDir, configFile) };
|
|
2534
|
-
const configOption = ` -config ${q2(n3(configFile))}`;
|
|
2535
2751
|
const subject = params.subject ? new Subject3(params.subject).toString() : void 0;
|
|
2536
|
-
const subjectOptions = subject ? ` -subj "${subject}"` : "";
|
|
2537
2752
|
displaySubtitle("- Creating a Certificate Signing Request with openssl");
|
|
2538
2753
|
await execute_openssl(
|
|
2539
|
-
|
|
2754
|
+
[
|
|
2755
|
+
"req",
|
|
2756
|
+
"-new",
|
|
2757
|
+
"-sha256",
|
|
2758
|
+
"-batch",
|
|
2759
|
+
"-text",
|
|
2760
|
+
"-config",
|
|
2761
|
+
n3(configFile),
|
|
2762
|
+
"-key",
|
|
2763
|
+
n3(params.privateKey),
|
|
2764
|
+
...subject ? ["-subj", subject] : [],
|
|
2765
|
+
"-out",
|
|
2766
|
+
n3(certificateSigningRequestFilename)
|
|
2767
|
+
],
|
|
2540
2768
|
options
|
|
2541
2769
|
);
|
|
2542
2770
|
}
|
|
2543
|
-
var
|
|
2771
|
+
var n3;
|
|
2544
2772
|
var init_create_certificate_signing_request2 = __esm({
|
|
2545
2773
|
"packages/node-opcua-pki/lib/toolbox/with_openssl/create_certificate_signing_request.ts"() {
|
|
2546
2774
|
"use strict";
|
|
2547
2775
|
init_esm_shims();
|
|
2548
2776
|
init_subject();
|
|
2549
|
-
init_common();
|
|
2550
2777
|
init_common2();
|
|
2551
2778
|
init_display();
|
|
2552
2779
|
init_env();
|
|
2553
2780
|
init_execute_openssl();
|
|
2554
2781
|
init_toolbox2();
|
|
2555
|
-
q2 = quote;
|
|
2556
2782
|
n3 = makePath;
|
|
2557
2783
|
}
|
|
2558
2784
|
});
|
|
@@ -2575,31 +2801,28 @@ var init_with_openssl = __esm({
|
|
|
2575
2801
|
import assert9 from "assert";
|
|
2576
2802
|
import fs9 from "fs";
|
|
2577
2803
|
async function createPFX(options) {
|
|
2578
|
-
const { certificateFile, privateKeyFile, outputFile, passphrase = "", caCertificateFiles } = options;
|
|
2804
|
+
const { certificateFile, privateKeyFile, privateKeyPassphrase, outputFile, passphrase = "", caCertificateFiles } = options;
|
|
2579
2805
|
assert9(fs9.existsSync(certificateFile), `Certificate file does not exist: ${certificateFile}`);
|
|
2580
2806
|
assert9(fs9.existsSync(privateKeyFile), `Private key file does not exist: ${privateKeyFile}`);
|
|
2581
|
-
|
|
2582
|
-
cmd += ` -in ${q3(n4(certificateFile))}`;
|
|
2583
|
-
cmd += ` -inkey ${q3(n4(privateKeyFile))}`;
|
|
2807
|
+
const args = ["pkcs12", "-export", "-in", n4(certificateFile), "-inkey", n4(privateKeyFile)];
|
|
2584
2808
|
if (caCertificateFiles) {
|
|
2585
2809
|
for (const caFile of caCertificateFiles) {
|
|
2586
2810
|
assert9(fs9.existsSync(caFile), `CA certificate file does not exist: ${caFile}`);
|
|
2587
|
-
|
|
2811
|
+
args.push("-certfile", n4(caFile));
|
|
2588
2812
|
}
|
|
2589
2813
|
}
|
|
2590
|
-
|
|
2591
|
-
|
|
2592
|
-
|
|
2814
|
+
const passin = passinArg(privateKeyPassphrase);
|
|
2815
|
+
const passout = passoutArg(passphrase);
|
|
2816
|
+
args.push("-out", n4(outputFile), ...passin.args, ...passout.args);
|
|
2817
|
+
await execute_openssl(args, { env: { ...passin.env, ...passout.env } });
|
|
2593
2818
|
}
|
|
2594
|
-
var
|
|
2819
|
+
var n4;
|
|
2595
2820
|
var init_toolbox_pfx = __esm({
|
|
2596
2821
|
"packages/node-opcua-pki/lib/pki/toolbox_pfx.ts"() {
|
|
2597
2822
|
"use strict";
|
|
2598
2823
|
init_esm_shims();
|
|
2599
|
-
init_common();
|
|
2600
2824
|
init_common2();
|
|
2601
2825
|
init_execute_openssl();
|
|
2602
|
-
q3 = quote;
|
|
2603
2826
|
n4 = makePath;
|
|
2604
2827
|
}
|
|
2605
2828
|
});
|
|
@@ -2614,7 +2837,7 @@ var init_ca_config_template_cnf = __esm({
|
|
|
2614
2837
|
[ ca ]
|
|
2615
2838
|
default_ca = CA_default
|
|
2616
2839
|
[ CA_default ]
|
|
2617
|
-
dir = %%ROOT_FOLDER%%
|
|
2840
|
+
dir = "%%ROOT_FOLDER%%" # the main CA folder (quoted: see renderCaConfig)
|
|
2618
2841
|
certs = $dir/certs # where to store certificates
|
|
2619
2842
|
new_certs_dir = $dir/certs #
|
|
2620
2843
|
database = $dir/index.txt # the certificate database
|
|
@@ -2760,10 +2983,17 @@ import {
|
|
|
2760
2983
|
generatePrivateKeyFile as generatePrivateKeyFile2,
|
|
2761
2984
|
readCertificatePEM,
|
|
2762
2985
|
readCertificateSigningRequest,
|
|
2763
|
-
readPrivateKey,
|
|
2986
|
+
readPrivateKey as readPrivateKey2,
|
|
2764
2987
|
Subject as Subject4,
|
|
2765
|
-
toPem as toPem2
|
|
2988
|
+
toPem as toPem2,
|
|
2989
|
+
writePrivateKeyFile as writePrivateKeyFile2
|
|
2766
2990
|
} from "node-opcua-crypto";
|
|
2991
|
+
function escapeOpensslConfDoubleQuoted(value) {
|
|
2992
|
+
return value.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
|
|
2993
|
+
}
|
|
2994
|
+
function renderCaConfig(caRootDir) {
|
|
2995
|
+
return configurationFileTemplate.replace(/%%ROOT_FOLDER%%/, escapeOpensslConfDoubleQuoted(makePath(caRootDir)));
|
|
2996
|
+
}
|
|
2767
2997
|
function octetStringToIpAddress(a) {
|
|
2768
2998
|
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();
|
|
2769
2999
|
}
|
|
@@ -2772,7 +3002,7 @@ async function construct_CertificateAuthority(certificateAuthority) {
|
|
|
2772
3002
|
const caRootDir = path6.resolve(certificateAuthority.rootDir);
|
|
2773
3003
|
async function make_folders() {
|
|
2774
3004
|
mkdirRecursiveSync(caRootDir);
|
|
2775
|
-
|
|
3005
|
+
ensurePrivateDirectory(path6.join(caRootDir, "private"));
|
|
2776
3006
|
mkdirRecursiveSync(path6.join(caRootDir, "public"));
|
|
2777
3007
|
mkdirRecursiveSync(path6.join(caRootDir, "certs"));
|
|
2778
3008
|
mkdirRecursiveSync(path6.join(caRootDir, "crl"));
|
|
@@ -2797,6 +3027,8 @@ async function construct_CertificateAuthority(certificateAuthority) {
|
|
|
2797
3027
|
const caKeyExists = fs10.existsSync(path6.join(caRootDir, "private/cakey.pem"));
|
|
2798
3028
|
const caCertExists = fs10.existsSync(path6.join(caRootDir, "public/cacert.pem"));
|
|
2799
3029
|
if (caKeyExists && caCertExists && !config3.forceCA) {
|
|
3030
|
+
restrictPrivateFilePermissions(path6.join(caRootDir, "private/cakey.pem"), 384);
|
|
3031
|
+
await certificateAuthority._ensurePrivateKeyProtection();
|
|
2800
3032
|
debugLog("CA private key and certificate already exist ... skipping");
|
|
2801
3033
|
return;
|
|
2802
3034
|
}
|
|
@@ -2815,26 +3047,40 @@ async function construct_CertificateAuthority(certificateAuthority) {
|
|
|
2815
3047
|
}
|
|
2816
3048
|
const caConfigFile = certificateAuthority.configFile;
|
|
2817
3049
|
if (1) {
|
|
2818
|
-
|
|
2819
|
-
data = makePath(data.replace(/%%ROOT_FOLDER%%/, caRootDir));
|
|
2820
|
-
await fs10.promises.writeFile(caConfigFile, data);
|
|
3050
|
+
await fs10.promises.writeFile(caConfigFile, renderCaConfig(caRootDir));
|
|
2821
3051
|
}
|
|
2822
|
-
const subjectOpt =
|
|
3052
|
+
const subjectOpt = ["-subj", subject.toString()];
|
|
2823
3053
|
const caCommonName = subject.commonName || "NodeOPCUA-CA";
|
|
2824
3054
|
setEnv("ALTNAME", `URI:urn:${caCommonName}`);
|
|
2825
3055
|
certificateAuthority._wireRevocationEnvVars();
|
|
2826
3056
|
const options = { cwd: caRootDir };
|
|
2827
3057
|
const configFile = generateStaticConfig("conf/caconfig.cnf", options);
|
|
2828
|
-
const configOption =
|
|
3058
|
+
const configOption = ["-config", n5(configFile)];
|
|
2829
3059
|
const keySize = certificateAuthority.keySize;
|
|
2830
3060
|
const privateKeyFilename = path6.join(caRootDir, "private/cakey.pem");
|
|
2831
3061
|
const csrFilename = path6.join(caRootDir, "private/cakey.csr");
|
|
2832
3062
|
displayTitle(`Generate the CA private Key - ${keySize}`);
|
|
2833
|
-
await
|
|
3063
|
+
const passin = await certificateAuthority._opensslPassin();
|
|
3064
|
+
await generatePrivateKeyFile2(privateKeyFilename, keySize, { passphrase: await certificateAuthority._privateKeyPassphrase() });
|
|
3065
|
+
restrictPrivateFilePermissions(privateKeyFilename, 384);
|
|
2834
3066
|
displayTitle("Generate a certificate request for the CA key");
|
|
2835
3067
|
await execute_openssl(
|
|
2836
|
-
|
|
2837
|
-
|
|
3068
|
+
[
|
|
3069
|
+
"req",
|
|
3070
|
+
"-new",
|
|
3071
|
+
"-sha256",
|
|
3072
|
+
"-text",
|
|
3073
|
+
"-extensions",
|
|
3074
|
+
"v3_ca_req",
|
|
3075
|
+
...configOption,
|
|
3076
|
+
"-key",
|
|
3077
|
+
n5(privateKeyFilename),
|
|
3078
|
+
"-out",
|
|
3079
|
+
n5(csrFilename),
|
|
3080
|
+
...subjectOpt,
|
|
3081
|
+
...passin.args
|
|
3082
|
+
],
|
|
3083
|
+
{ ...options, env: passin.env }
|
|
2838
3084
|
);
|
|
2839
3085
|
const issuerCA = certificateAuthority._issuerCA;
|
|
2840
3086
|
if (issuerCA) {
|
|
@@ -2842,27 +3088,71 @@ async function construct_CertificateAuthority(certificateAuthority) {
|
|
|
2842
3088
|
const issuerCert = path6.resolve(issuerCA.caCertificate);
|
|
2843
3089
|
const issuerKey = path6.resolve(issuerCA.rootDir, "private/cakey.pem");
|
|
2844
3090
|
const issuerSerial = path6.resolve(issuerCA.rootDir, "serial");
|
|
3091
|
+
const issuerPassin = await issuerCA._opensslPassin();
|
|
2845
3092
|
await execute_openssl(
|
|
2846
|
-
|
|
2847
|
-
|
|
3093
|
+
[
|
|
3094
|
+
"x509",
|
|
3095
|
+
"-sha256",
|
|
3096
|
+
"-req",
|
|
3097
|
+
"-days",
|
|
3098
|
+
"3650",
|
|
3099
|
+
"-text",
|
|
3100
|
+
"-extensions",
|
|
3101
|
+
"v3_ca",
|
|
3102
|
+
"-extfile",
|
|
3103
|
+
n5(configFile),
|
|
3104
|
+
"-in",
|
|
3105
|
+
"private/cakey.csr",
|
|
3106
|
+
"-CA",
|
|
3107
|
+
n5(issuerCert),
|
|
3108
|
+
"-CAkey",
|
|
3109
|
+
n5(issuerKey),
|
|
3110
|
+
"-CAserial",
|
|
3111
|
+
n5(issuerSerial),
|
|
3112
|
+
"-out",
|
|
3113
|
+
"public/cacert.pem",
|
|
3114
|
+
...issuerPassin.args
|
|
3115
|
+
],
|
|
3116
|
+
{ ...options, env: issuerPassin.env }
|
|
2848
3117
|
);
|
|
2849
3118
|
} else {
|
|
2850
3119
|
displayTitle("Generate CA Certificate (self-signed)");
|
|
2851
3120
|
await execute_openssl(
|
|
2852
|
-
|
|
2853
|
-
|
|
3121
|
+
[
|
|
3122
|
+
"x509",
|
|
3123
|
+
"-sha256",
|
|
3124
|
+
"-req",
|
|
3125
|
+
"-days",
|
|
3126
|
+
"3650",
|
|
3127
|
+
"-text",
|
|
3128
|
+
"-extensions",
|
|
3129
|
+
"v3_ca",
|
|
3130
|
+
"-extfile",
|
|
3131
|
+
n5(configFile),
|
|
3132
|
+
"-in",
|
|
3133
|
+
"private/cakey.csr",
|
|
3134
|
+
"-signkey",
|
|
3135
|
+
n5(privateKeyFilename),
|
|
3136
|
+
"-out",
|
|
3137
|
+
"public/cacert.pem",
|
|
3138
|
+
...passin.args
|
|
3139
|
+
],
|
|
3140
|
+
{ ...options, env: passin.env }
|
|
2854
3141
|
);
|
|
2855
3142
|
}
|
|
2856
3143
|
displaySubtitle("generate initial CRL (Certificate Revocation List)");
|
|
2857
|
-
await regenerateCrl(certificateAuthority.revocationList, configOption, options);
|
|
3144
|
+
await regenerateCrl(certificateAuthority.revocationList, configOption, options, passin);
|
|
2858
3145
|
displayTitle("Create Certificate Authority (CA) ---> DONE");
|
|
2859
3146
|
}
|
|
2860
|
-
async function regenerateCrl(revocationList, configOption, options) {
|
|
3147
|
+
async function regenerateCrl(revocationList, configOption, options, passin) {
|
|
2861
3148
|
displaySubtitle("regenerate CRL (Certificate Revocation List)");
|
|
2862
|
-
await execute_openssl(
|
|
2863
|
-
|
|
3149
|
+
await execute_openssl(["ca", "-gencrl", ...configOption, "-out", "crl/revocation_list.crl", ...passin.args], {
|
|
3150
|
+
...options,
|
|
3151
|
+
env: passin.env
|
|
3152
|
+
});
|
|
3153
|
+
await execute_openssl(["crl", "-in", "crl/revocation_list.crl", "-out", "crl/revocation_list.der", "-outform", "der"], options);
|
|
2864
3154
|
displaySubtitle("Display (Certificate Revocation List)");
|
|
2865
|
-
await execute_openssl(
|
|
3155
|
+
await execute_openssl(["crl", "-in", n5(revocationList), "-text", "-noout"], options);
|
|
2866
3156
|
}
|
|
2867
3157
|
function parseOpenSSLDate(dateStr) {
|
|
2868
3158
|
const raw = dateStr?.split(",")[0] ?? "";
|
|
@@ -2903,7 +3193,7 @@ function validateRevocationUrl(url, fieldName) {
|
|
|
2903
3193
|
}
|
|
2904
3194
|
return url;
|
|
2905
3195
|
}
|
|
2906
|
-
var defaultSubject, configurationFileTemplate, configurationFileSimpleTemplate2, config3, n5,
|
|
3196
|
+
var defaultSubject, configurationFileTemplate, configurationFileSimpleTemplate2, config3, n5, CertificateAuthority;
|
|
2907
3197
|
var init_certificate_authority = __esm({
|
|
2908
3198
|
"packages/node-opcua-pki/lib/ca/certificate_authority.ts"() {
|
|
2909
3199
|
"use strict";
|
|
@@ -2922,7 +3212,6 @@ var init_certificate_authority = __esm({
|
|
|
2922
3212
|
pkiDir: "INVALID"
|
|
2923
3213
|
};
|
|
2924
3214
|
n5 = makePath;
|
|
2925
|
-
q4 = quote;
|
|
2926
3215
|
assert10(octetStringToIpAddress("c07b9179") === "192.123.145.121");
|
|
2927
3216
|
CertificateAuthority = class {
|
|
2928
3217
|
/** RSA key size used when generating the CA private key. */
|
|
@@ -2937,6 +3226,10 @@ var init_certificate_authority = __esm({
|
|
|
2937
3226
|
_crlDistributionUrl;
|
|
2938
3227
|
_ocspResponderUrl;
|
|
2939
3228
|
_caIssuersUrl;
|
|
3229
|
+
#privateKeyPassphrase;
|
|
3230
|
+
/** resolved once (see `privateKeyPassphrase`); `#passphraseResolved` distinguishes "none" from "not yet" */
|
|
3231
|
+
#resolvedPassphrase;
|
|
3232
|
+
#passphraseResolved = false;
|
|
2940
3233
|
constructor(options) {
|
|
2941
3234
|
assert10(Object.prototype.hasOwnProperty.call(options, "location"));
|
|
2942
3235
|
assert10(Object.prototype.hasOwnProperty.call(options, "keySize"));
|
|
@@ -2944,6 +3237,7 @@ var init_certificate_authority = __esm({
|
|
|
2944
3237
|
this.keySize = options.keySize || 2048;
|
|
2945
3238
|
this.subject = new Subject4(options.subject || defaultSubject);
|
|
2946
3239
|
this._issuerCA = options.issuerCA;
|
|
3240
|
+
this.#privateKeyPassphrase = options.privateKeyPassphrase;
|
|
2947
3241
|
if (options.crlDistributionUrl !== void 0) {
|
|
2948
3242
|
this.setCrlDistributionUrl(options.crlDistributionUrl);
|
|
2949
3243
|
}
|
|
@@ -3040,6 +3334,69 @@ var init_certificate_authority = __esm({
|
|
|
3040
3334
|
get configFile() {
|
|
3041
3335
|
return path6.normalize(path6.join(this.rootDir, "./conf/caconfig.cnf"));
|
|
3042
3336
|
}
|
|
3337
|
+
/** Path to the CA private key (`private/cakey.pem`); may be passphrase-encrypted, see {@link getPrivateKey}. */
|
|
3338
|
+
get privateKey() {
|
|
3339
|
+
return path6.join(path6.resolve(this.rootDir), "private/cakey.pem");
|
|
3340
|
+
}
|
|
3341
|
+
/**
|
|
3342
|
+
* The CA private key, decrypted with the configured `privateKeyPassphrase`
|
|
3343
|
+
* if it is encrypted. Fails closed (`PrivateKeyPassphraseRequiredError`)
|
|
3344
|
+
* on an encrypted key with no or the wrong passphrase.
|
|
3345
|
+
*/
|
|
3346
|
+
async getPrivateKey() {
|
|
3347
|
+
return readPrivateKey2(this.privateKey, await this._privateKeyPassphrase());
|
|
3348
|
+
}
|
|
3349
|
+
/**
|
|
3350
|
+
* Enable, disable, or rotate the passphrase protecting `private/cakey.pem`
|
|
3351
|
+
* (temp file + atomic rename, temp file removed on failure). Only
|
|
3352
|
+
* rewrites the file: construct a new `CertificateAuthority` with the new
|
|
3353
|
+
* passphrase to continue using it.
|
|
3354
|
+
*/
|
|
3355
|
+
async reencryptPrivateKey(oldPassphrase, newPassphrase) {
|
|
3356
|
+
const oldPass = await resolvePrivateKeyPassphrase(oldPassphrase);
|
|
3357
|
+
const newPass = await resolvePrivateKeyPassphrase(newPassphrase);
|
|
3358
|
+
const key = readPrivateKey2(this.privateKey, oldPass);
|
|
3359
|
+
await this.#rewritePrivateKeyFile(key, newPass);
|
|
3360
|
+
}
|
|
3361
|
+
async #rewritePrivateKeyFile(privateKey, passphrase) {
|
|
3362
|
+
const tmpFilename = `${this.privateKey}.${process.pid}-${Date.now()}.tmp`;
|
|
3363
|
+
try {
|
|
3364
|
+
await writePrivateKeyFile2(tmpFilename, privateKey, { passphrase });
|
|
3365
|
+
await fs10.promises.rename(tmpFilename, this.privateKey);
|
|
3366
|
+
} finally {
|
|
3367
|
+
await fs10.promises.rm(tmpFilename, { force: true });
|
|
3368
|
+
}
|
|
3369
|
+
}
|
|
3370
|
+
/** @internal resolve the configured passphrase, at most once per instance */
|
|
3371
|
+
async _privateKeyPassphrase() {
|
|
3372
|
+
if (!this.#passphraseResolved) {
|
|
3373
|
+
this.#resolvedPassphrase = await resolvePrivateKeyPassphrase(this.#privateKeyPassphrase);
|
|
3374
|
+
this.#passphraseResolved = true;
|
|
3375
|
+
}
|
|
3376
|
+
return this.#resolvedPassphrase;
|
|
3377
|
+
}
|
|
3378
|
+
/** @internal `-passin env:` argv + env for every openssl call that loads this CA's key (always emitted, empty when none) */
|
|
3379
|
+
async _opensslPassin() {
|
|
3380
|
+
return passinArg(await this._privateKeyPassphrase());
|
|
3381
|
+
}
|
|
3382
|
+
/**
|
|
3383
|
+
* @internal On an existing key: encrypt it in place if a passphrase is
|
|
3384
|
+
* configured and it is still plaintext (secure by default: the option
|
|
3385
|
+
* means "protect this key", not "ignore me"), then read it back so a
|
|
3386
|
+
* wrong or missing passphrase fails initialize() closed rather than the
|
|
3387
|
+
* first signing operation.
|
|
3388
|
+
*/
|
|
3389
|
+
async _ensurePrivateKeyProtection() {
|
|
3390
|
+
if (!fs10.existsSync(this.privateKey)) {
|
|
3391
|
+
return;
|
|
3392
|
+
}
|
|
3393
|
+
if (this.#privateKeyPassphrase !== void 0 && !isEncryptedPrivateKeyFile(this.privateKey)) {
|
|
3394
|
+
warningLog("CertificateAuthority: private key is plaintext but a passphrase is configured; encrypting it in place");
|
|
3395
|
+
const plaintextKey = readPrivateKey2(this.privateKey);
|
|
3396
|
+
await this.#rewritePrivateKeyFile(plaintextKey, await this._privateKeyPassphrase());
|
|
3397
|
+
}
|
|
3398
|
+
await this.getPrivateKey();
|
|
3399
|
+
}
|
|
3043
3400
|
/** Path to the CA certificate in PEM format (`public/cacert.pem`). */
|
|
3044
3401
|
get caCertificate() {
|
|
3045
3402
|
return makePath(this.rootDir, "./public/cacert.pem");
|
|
@@ -3377,7 +3734,7 @@ var init_certificate_authority = __esm({
|
|
|
3377
3734
|
await this.signCertificateRequest(certFile, csrFile, signingParams);
|
|
3378
3735
|
const certPem = readCertificatePEM(certFile);
|
|
3379
3736
|
const certificateDer = convertPEMtoDER(certPem);
|
|
3380
|
-
const privateKey =
|
|
3737
|
+
const privateKey = readPrivateKey2(privateKeyFile);
|
|
3381
3738
|
return { certificateDer, privateKey };
|
|
3382
3739
|
} finally {
|
|
3383
3740
|
await fs10.promises.rm(tmpDir, {
|
|
@@ -3497,12 +3854,17 @@ var init_certificate_authority = __esm({
|
|
|
3497
3854
|
async initializeCSR() {
|
|
3498
3855
|
const caRootDir = path6.resolve(this.rootDir);
|
|
3499
3856
|
mkdirRecursiveSync(caRootDir);
|
|
3500
|
-
for (const dir of ["
|
|
3857
|
+
for (const dir of ["public", "certs", "crl", "conf"]) {
|
|
3501
3858
|
mkdirRecursiveSync(path6.join(caRootDir, dir));
|
|
3502
3859
|
}
|
|
3860
|
+
ensurePrivateDirectory(path6.join(caRootDir, "private"));
|
|
3503
3861
|
const caCertFile = this.caCertificate;
|
|
3504
3862
|
const privateKeyFile = path6.join(caRootDir, "private/cakey.pem");
|
|
3505
3863
|
const csrFile = path6.join(caRootDir, "private/cakey.csr");
|
|
3864
|
+
if (fs10.existsSync(privateKeyFile)) {
|
|
3865
|
+
restrictPrivateFilePermissions(privateKeyFile, 384);
|
|
3866
|
+
await this._ensurePrivateKeyProtection();
|
|
3867
|
+
}
|
|
3506
3868
|
if (fs10.existsSync(caCertFile)) {
|
|
3507
3869
|
const certDer = convertPEMtoDER(readCertificatePEM(caCertFile));
|
|
3508
3870
|
const certInfo = exploreCertificate2(certDer);
|
|
@@ -3536,11 +3898,10 @@ var init_certificate_authority = __esm({
|
|
|
3536
3898
|
await fs10.promises.writeFile(indexFileAttr, "unique_subject = no");
|
|
3537
3899
|
}
|
|
3538
3900
|
const caConfigFile = this.configFile;
|
|
3539
|
-
|
|
3540
|
-
data = makePath(data.replace(/%%ROOT_FOLDER%%/, caRootDir));
|
|
3541
|
-
await fs10.promises.writeFile(caConfigFile, data);
|
|
3901
|
+
await fs10.promises.writeFile(caConfigFile, renderCaConfig(caRootDir));
|
|
3542
3902
|
if (!fs10.existsSync(privateKeyFile)) {
|
|
3543
|
-
await generatePrivateKeyFile2(privateKeyFile, this.keySize);
|
|
3903
|
+
await generatePrivateKeyFile2(privateKeyFile, this.keySize, { passphrase: await this._privateKeyPassphrase() });
|
|
3904
|
+
restrictPrivateFilePermissions(privateKeyFile, 384);
|
|
3544
3905
|
}
|
|
3545
3906
|
await this._generateCSR(caRootDir, privateKeyFile, csrFile);
|
|
3546
3907
|
return { status: "created", csrPath: csrFile };
|
|
@@ -3582,14 +3943,29 @@ var init_certificate_authority = __esm({
|
|
|
3582
3943
|
* @internal
|
|
3583
3944
|
*/
|
|
3584
3945
|
async _generateCSR(caRootDir, privateKeyFile, csrFile) {
|
|
3585
|
-
const subjectOpt = ` -subj "${this.subject.toString()}" `;
|
|
3586
3946
|
processAltNames({});
|
|
3587
3947
|
const options = { cwd: caRootDir };
|
|
3588
3948
|
const configFile = generateStaticConfig("conf/caconfig.cnf", options);
|
|
3589
|
-
const
|
|
3949
|
+
const passin = await this._opensslPassin();
|
|
3590
3950
|
await execute_openssl(
|
|
3591
|
-
|
|
3592
|
-
|
|
3951
|
+
[
|
|
3952
|
+
"req",
|
|
3953
|
+
"-new",
|
|
3954
|
+
"-sha256",
|
|
3955
|
+
"-text",
|
|
3956
|
+
"-extensions",
|
|
3957
|
+
"v3_ca_req",
|
|
3958
|
+
"-config",
|
|
3959
|
+
n5(configFile),
|
|
3960
|
+
"-key",
|
|
3961
|
+
n5(privateKeyFile),
|
|
3962
|
+
"-out",
|
|
3963
|
+
n5(csrFile),
|
|
3964
|
+
"-subj",
|
|
3965
|
+
this.subject.toString(),
|
|
3966
|
+
...passin.args
|
|
3967
|
+
],
|
|
3968
|
+
{ ...options, env: passin.env }
|
|
3593
3969
|
);
|
|
3594
3970
|
}
|
|
3595
3971
|
/**
|
|
@@ -3611,7 +3987,6 @@ var init_certificate_authority = __esm({
|
|
|
3611
3987
|
async installCACertificate(signedCertFile) {
|
|
3612
3988
|
const caRootDir = path6.resolve(this.rootDir);
|
|
3613
3989
|
const caCertFile = this.caCertificate;
|
|
3614
|
-
const privateKeyFile = path6.join(caRootDir, "private/cakey.pem");
|
|
3615
3990
|
const fullPem = await fs10.promises.readFile(signedCertFile, "utf8");
|
|
3616
3991
|
const pemBlocks = fullPem.match(/-----BEGIN CERTIFICATE-----[\s\S]*?-----END CERTIFICATE-----/g);
|
|
3617
3992
|
if (!pemBlocks || pemBlocks.length === 0) {
|
|
@@ -3622,7 +3997,7 @@ var init_certificate_authority = __esm({
|
|
|
3622
3997
|
};
|
|
3623
3998
|
}
|
|
3624
3999
|
const certDer = convertPEMtoDER(pemBlocks[0]);
|
|
3625
|
-
const privateKey =
|
|
4000
|
+
const privateKey = await this.getPrivateKey();
|
|
3626
4001
|
if (!certificateMatchesPrivateKey(certDer, privateKey)) {
|
|
3627
4002
|
return {
|
|
3628
4003
|
status: "error",
|
|
@@ -3645,8 +4020,7 @@ var init_certificate_authority = __esm({
|
|
|
3645
4020
|
}
|
|
3646
4021
|
const options = { cwd: caRootDir };
|
|
3647
4022
|
const configFile = generateStaticConfig("conf/caconfig.cnf", options);
|
|
3648
|
-
|
|
3649
|
-
await regenerateCrl(this.revocationList, configOption, options);
|
|
4023
|
+
await regenerateCrl(this.revocationList, ["-config", n5(configFile)], options, await this._opensslPassin());
|
|
3650
4024
|
return { status: "success" };
|
|
3651
4025
|
}
|
|
3652
4026
|
/**
|
|
@@ -3668,9 +4042,32 @@ var init_certificate_authority = __esm({
|
|
|
3668
4042
|
this._wireRevocationEnvVars();
|
|
3669
4043
|
const configFile = generateStaticConfig("conf/caconfig.cnf", options);
|
|
3670
4044
|
const validity = params.validity ?? 3650;
|
|
4045
|
+
const passin = await this._opensslPassin();
|
|
3671
4046
|
await execute_openssl(
|
|
3672
|
-
|
|
3673
|
-
|
|
4047
|
+
[
|
|
4048
|
+
"x509",
|
|
4049
|
+
"-sha256",
|
|
4050
|
+
"-req",
|
|
4051
|
+
"-days",
|
|
4052
|
+
String(validity),
|
|
4053
|
+
"-text",
|
|
4054
|
+
"-extensions",
|
|
4055
|
+
"v3_ca",
|
|
4056
|
+
"-extfile",
|
|
4057
|
+
n5(configFile),
|
|
4058
|
+
"-in",
|
|
4059
|
+
n5(csrFile),
|
|
4060
|
+
"-CA",
|
|
4061
|
+
n5(this.caCertificate),
|
|
4062
|
+
"-CAkey",
|
|
4063
|
+
n5(path6.join(caRootDir, "private/cakey.pem")),
|
|
4064
|
+
"-CAserial",
|
|
4065
|
+
n5(path6.join(caRootDir, "serial")),
|
|
4066
|
+
"-out",
|
|
4067
|
+
n5(certFile),
|
|
4068
|
+
...passin.args
|
|
4069
|
+
],
|
|
4070
|
+
{ ...options, env: passin.env }
|
|
3674
4071
|
);
|
|
3675
4072
|
await this.constructCertificateChain(certFile);
|
|
3676
4073
|
}
|
|
@@ -3732,23 +4129,36 @@ var init_certificate_authority = __esm({
|
|
|
3732
4129
|
cwd: this.rootDir,
|
|
3733
4130
|
openssl_conf: makePath(configFile)
|
|
3734
4131
|
};
|
|
3735
|
-
const configOption = "";
|
|
3736
4132
|
const subject = params.subject ? new Subject4(params.subject).toString() : "";
|
|
3737
|
-
const subjectOptions = subject && subject.length > 1 ?
|
|
4133
|
+
const subjectOptions = subject && subject.length > 1 ? ["-subj", subject] : [];
|
|
3738
4134
|
displaySubtitle("- the certificate signing request");
|
|
3739
4135
|
await execute_openssl(
|
|
3740
|
-
"req
|
|
4136
|
+
["req", "-new", "-sha256", "-text", ...subjectOptions, "-batch", "-key", n5(privateKey), "-out", n5(csrFile)],
|
|
3741
4137
|
options
|
|
3742
4138
|
);
|
|
3743
4139
|
displaySubtitle("- creating the self-signed certificate");
|
|
3744
4140
|
await execute_openssl(
|
|
3745
|
-
|
|
4141
|
+
[
|
|
4142
|
+
"ca",
|
|
4143
|
+
"-selfsign",
|
|
4144
|
+
"-keyfile",
|
|
4145
|
+
n5(privateKey),
|
|
4146
|
+
"-startdate",
|
|
4147
|
+
x509Date(params.startDate),
|
|
4148
|
+
"-enddate",
|
|
4149
|
+
x509Date(params.endDate),
|
|
4150
|
+
"-batch",
|
|
4151
|
+
"-out",
|
|
4152
|
+
n5(certificateFile),
|
|
4153
|
+
"-in",
|
|
4154
|
+
n5(csrFile)
|
|
4155
|
+
],
|
|
3746
4156
|
options
|
|
3747
4157
|
);
|
|
3748
4158
|
displaySubtitle("- dump the certificate for a check");
|
|
3749
|
-
await execute_openssl(
|
|
4159
|
+
await execute_openssl(["x509", "-in", n5(certificateFile), "-dates", "-fingerprint", "-purpose", "-noout"], {});
|
|
3750
4160
|
displaySubtitle("- verify self-signed certificate");
|
|
3751
|
-
await execute_openssl_no_failure(
|
|
4161
|
+
await execute_openssl_no_failure(["verify", "-verbose", "-CAfile", n5(certificateFile), n5(certificateFile)], options);
|
|
3752
4162
|
await fs10.promises.unlink(csrFile);
|
|
3753
4163
|
}
|
|
3754
4164
|
/**
|
|
@@ -3778,22 +4188,41 @@ var init_certificate_authority = __esm({
|
|
|
3778
4188
|
setEnv("ALTNAME", "");
|
|
3779
4189
|
const randomFile = path6.join(this.rootDir, "random.rnd");
|
|
3780
4190
|
setEnv("RANDFILE", randomFile);
|
|
3781
|
-
const configOption =
|
|
4191
|
+
const configOption = ["-config", n5(configFile)];
|
|
3782
4192
|
const reason = params.reason || "keyCompromise";
|
|
3783
4193
|
assert10(crlReasons.indexOf(reason) >= 0);
|
|
3784
4194
|
displayTitle(`Revoking certificate ${certificate}`);
|
|
3785
4195
|
displaySubtitle("Revoke certificate");
|
|
3786
|
-
|
|
3787
|
-
await
|
|
4196
|
+
const passin = await this._opensslPassin();
|
|
4197
|
+
await execute_openssl_no_failure(
|
|
4198
|
+
["ca", "-verbose", ...configOption, "-revoke", certificate, "-crl_reason", reason, ...passin.args],
|
|
4199
|
+
{ ...options, env: passin.env }
|
|
4200
|
+
);
|
|
4201
|
+
await regenerateCrl(this.revocationList, configOption, options, passin);
|
|
3788
4202
|
displaySubtitle("Verify that certificate is revoked");
|
|
3789
4203
|
await execute_openssl_no_failure(
|
|
3790
|
-
|
|
4204
|
+
[
|
|
4205
|
+
"verify",
|
|
4206
|
+
"-verbose",
|
|
4207
|
+
"-CRLfile",
|
|
4208
|
+
n5(this.revocationList),
|
|
4209
|
+
"-CAfile",
|
|
4210
|
+
n5(this.caCertificate),
|
|
4211
|
+
"-crl_check",
|
|
4212
|
+
n5(certificate)
|
|
4213
|
+
],
|
|
3791
4214
|
options
|
|
3792
4215
|
);
|
|
3793
4216
|
displaySubtitle("Produce CRL in DER form ");
|
|
3794
|
-
await execute_openssl(
|
|
4217
|
+
await execute_openssl(
|
|
4218
|
+
["crl", "-in", n5(this.revocationList), "-out", "crl/revocation_list.der", "-outform", "der"],
|
|
4219
|
+
options
|
|
4220
|
+
);
|
|
3795
4221
|
displaySubtitle("Produce CRL in PEM form ");
|
|
3796
|
-
await execute_openssl(
|
|
4222
|
+
await execute_openssl(
|
|
4223
|
+
["crl", "-in", n5(this.revocationList), "-out", "crl/revocation_list.pem", "-outform", "pem", "-text"],
|
|
4224
|
+
options
|
|
4225
|
+
);
|
|
3797
4226
|
}
|
|
3798
4227
|
/**
|
|
3799
4228
|
* Sign a Certificate Signing Request (CSR) with this CA.
|
|
@@ -3835,13 +4264,27 @@ var init_certificate_authority = __esm({
|
|
|
3835
4264
|
this._wireRevocationEnvVars();
|
|
3836
4265
|
const configFile = generateStaticConfig("conf/caconfig.cnf", options);
|
|
3837
4266
|
displaySubtitle("- then we ask the authority to sign the certificate signing request");
|
|
3838
|
-
const
|
|
4267
|
+
const passin = await this._opensslPassin();
|
|
3839
4268
|
await execute_openssl(
|
|
3840
|
-
|
|
3841
|
-
|
|
4269
|
+
[
|
|
4270
|
+
"ca",
|
|
4271
|
+
"-config",
|
|
4272
|
+
configFile,
|
|
4273
|
+
"-startdate",
|
|
4274
|
+
x509Date(params1.startDate),
|
|
4275
|
+
"-enddate",
|
|
4276
|
+
x509Date(params1.endDate),
|
|
4277
|
+
"-batch",
|
|
4278
|
+
"-out",
|
|
4279
|
+
n5(certificate),
|
|
4280
|
+
"-in",
|
|
4281
|
+
n5(certificateSigningRequestFilename),
|
|
4282
|
+
...passin.args
|
|
4283
|
+
],
|
|
4284
|
+
{ ...options, env: passin.env }
|
|
3842
4285
|
);
|
|
3843
4286
|
displaySubtitle("- dump the certificate for a check");
|
|
3844
|
-
await execute_openssl(
|
|
4287
|
+
await execute_openssl(["x509", "-in", n5(certificate), "-dates", "-fingerprint", "-purpose", "-noout"], options);
|
|
3845
4288
|
displaySubtitle("- construct CA certificate with CRL");
|
|
3846
4289
|
await this.constructCACertificateWithCRL();
|
|
3847
4290
|
displaySubtitle("- construct certificate chain");
|
|
@@ -3861,10 +4304,8 @@ var init_certificate_authority = __esm({
|
|
|
3861
4304
|
const options = { cwd: this.rootDir };
|
|
3862
4305
|
const configFile = generateStaticConfig("conf/caconfig.cnf", options);
|
|
3863
4306
|
setEnv("OPENSSL_CONF", makePath(configFile));
|
|
3864
|
-
const _configOption = ` -config ${configFile}`;
|
|
3865
|
-
_configOption;
|
|
3866
4307
|
await execute_openssl_no_failure(
|
|
3867
|
-
|
|
4308
|
+
["verify", "-verbose", "-CAfile", n5(this.caCertificateWithCrl), n5(certificate)],
|
|
3868
4309
|
options
|
|
3869
4310
|
);
|
|
3870
4311
|
}
|
|
@@ -4131,7 +4572,7 @@ async function createDefaultCertificate(base_name, prefix, key_length, applicati
|
|
|
4131
4572
|
365
|
|
4132
4573
|
);
|
|
4133
4574
|
warningLog(" certificate to revoke => ", certificate);
|
|
4134
|
-
revoke_certificate(certificate_revoked);
|
|
4575
|
+
await revoke_certificate(certificate_revoked);
|
|
4135
4576
|
}
|
|
4136
4577
|
}
|
|
4137
4578
|
}
|
|
@@ -4149,7 +4590,7 @@ async function create_default_certificates(dev) {
|
|
|
4149
4590
|
let clientURN;
|
|
4150
4591
|
let serverURN;
|
|
4151
4592
|
let discoveryServerURN;
|
|
4152
|
-
wrap(async () => {
|
|
4593
|
+
await wrap(async () => {
|
|
4153
4594
|
await extractFullyQualifiedDomainName();
|
|
4154
4595
|
const hostname = os5.hostname();
|
|
4155
4596
|
const fqdn2 = getFullyQualifiedDomainName();
|