node-opcua-pki 6.21.0 → 6.22.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.
- package/dist/bin/pki.mjs +159 -18
- package/dist/bin/pki.mjs.map +1 -1
- package/dist/{chunk-2HTNH73K.mjs → chunk-EQ4DGI4M.mjs} +5 -1
- package/dist/chunk-EQ4DGI4M.mjs.map +1 -0
- package/dist/{core-C6faZClF.d.mts → core-BwqSJPHc.d.mts} +19 -10
- package/dist/{core-C6faZClF.d.ts → core-BwqSJPHc.d.ts} +19 -10
- package/dist/core.d.mts +1 -1
- package/dist/core.d.ts +1 -1
- package/dist/core.js.map +1 -1
- package/dist/core.mjs +1 -1
- package/dist/index.d.mts +51 -5
- package/dist/index.d.ts +51 -5
- package/dist/index.js +164 -19
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +165 -20
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -3
- package/dist/chunk-2HTNH73K.mjs.map +0 -1
package/dist/bin/pki.mjs
CHANGED
|
@@ -93,6 +93,9 @@ var init_hostname = __esm({
|
|
|
93
93
|
|
|
94
94
|
// packages/node-opcua-pki/lib/toolbox/common.ts
|
|
95
95
|
import assert2 from "assert";
|
|
96
|
+
function isOpaqueSigner(privateKey) {
|
|
97
|
+
return typeof privateKey !== "string" && typeof privateKey.sign === "function";
|
|
98
|
+
}
|
|
96
99
|
async function resolvePrivateKeyPassphrase(passphrase) {
|
|
97
100
|
if (passphrase === void 0) {
|
|
98
101
|
return void 0;
|
|
@@ -276,8 +279,9 @@ async function createCertificateSigningRequestAsync(certificateSigningRequestFil
|
|
|
276
279
|
assert4(typeof certificateSigningRequestFilename === "string");
|
|
277
280
|
const subject = params.subject ? new Subject(params.subject).toString() : void 0;
|
|
278
281
|
displaySubtitle("- Creating a Certificate Signing Request with subtile");
|
|
279
|
-
const
|
|
280
|
-
|
|
282
|
+
const privateKey = isOpaqueSigner(params.privateKey) ? params.privateKey : await pemToPrivateKey(
|
|
283
|
+
typeof params.privateKey === "string" ? await fs2.promises.readFile(params.privateKey, "utf-8") : coercePrivateKeyPem(params.privateKey)
|
|
284
|
+
);
|
|
281
285
|
const { csr } = await createCertificateSigningRequest({
|
|
282
286
|
privateKey,
|
|
283
287
|
dns: params.dns,
|
|
@@ -287,13 +291,16 @@ async function createCertificateSigningRequestAsync(certificateSigningRequestFil
|
|
|
287
291
|
purpose: params.purpose
|
|
288
292
|
});
|
|
289
293
|
await fs2.promises.writeFile(certificateSigningRequestFilename, csr, "utf-8");
|
|
290
|
-
display(
|
|
294
|
+
display(
|
|
295
|
+
`- privateKey ${typeof params.privateKey === "string" ? params.privateKey : isOpaqueSigner(params.privateKey) ? "<opaque-signer>" : "<in-memory>"}`
|
|
296
|
+
);
|
|
291
297
|
display(`- certificateSigningRequestFilename ${certificateSigningRequestFilename}`);
|
|
292
298
|
}
|
|
293
299
|
var init_create_certificate_signing_request = __esm({
|
|
294
300
|
"packages/node-opcua-pki/lib/toolbox/without_openssl/create_certificate_signing_request.ts"() {
|
|
295
301
|
"use strict";
|
|
296
302
|
init_esm_shims();
|
|
303
|
+
init_common();
|
|
297
304
|
init_display();
|
|
298
305
|
}
|
|
299
306
|
});
|
|
@@ -327,8 +334,9 @@ async function createSelfSignedCertificateAsync(certificate, params) {
|
|
|
327
334
|
subject = subject.toString();
|
|
328
335
|
const purpose = params.purpose;
|
|
329
336
|
displayTitle("Generate a certificate request");
|
|
330
|
-
const
|
|
331
|
-
|
|
337
|
+
const privateKey = isOpaqueSigner(params.privateKey) ? params.privateKey : await pemToPrivateKey2(
|
|
338
|
+
typeof params.privateKey === "string" ? await fs3.promises.readFile(params.privateKey, "utf-8") : coercePrivateKeyPem2(params.privateKey)
|
|
339
|
+
);
|
|
332
340
|
const { cert } = await createSelfSignedCertificate1({
|
|
333
341
|
privateKey,
|
|
334
342
|
notBefore: params.startDate,
|
|
@@ -383,11 +391,14 @@ import { drainPendingLocks, withLock } from "@ster5/global-mutex";
|
|
|
383
391
|
import chalk3 from "chalk";
|
|
384
392
|
import chokidar from "chokidar";
|
|
385
393
|
import {
|
|
394
|
+
caSignerFromKeyOperations,
|
|
386
395
|
exploreCertificate,
|
|
387
396
|
exploreCertificateInfo,
|
|
388
397
|
exploreCertificateRevocationList,
|
|
389
398
|
generatePrivateKeyFile,
|
|
399
|
+
keyOperationsFromPrivateKey,
|
|
390
400
|
makeSHA1Thumbprint,
|
|
401
|
+
PrivateKeyUnavailableError,
|
|
391
402
|
readCertificateChain,
|
|
392
403
|
readCertificateChainAsync,
|
|
393
404
|
readCertificateRevocationList,
|
|
@@ -419,17 +430,30 @@ function makeFingerprint(certificate) {
|
|
|
419
430
|
function short(stringToShorten) {
|
|
420
431
|
return stringToShorten.substring(0, 10);
|
|
421
432
|
}
|
|
433
|
+
function toFilenameLabel(commonName) {
|
|
434
|
+
const label = (commonName || "").normalize("NFKC").replace(disallowedInLabel, "_").replace(/[^\x20-\x7E]/g, "").slice(0, 64).trim();
|
|
435
|
+
return label || "certificate";
|
|
436
|
+
}
|
|
422
437
|
function buildIdealCertificateName(certificate) {
|
|
423
438
|
const chain = coerceCertificateChain(certificate);
|
|
424
439
|
const fingerprint2 = makeFingerprint(chain);
|
|
425
440
|
try {
|
|
426
441
|
const commonName = exploreCertificate(chain[0]).tbsCertificate.subject.commonName || "";
|
|
427
|
-
const sanitizedCommonName = commonName
|
|
442
|
+
const sanitizedCommonName = toFilenameLabel(commonName);
|
|
428
443
|
return `${sanitizedCommonName}[${fingerprint2}]`;
|
|
429
444
|
} catch (_err) {
|
|
430
445
|
return `invalid_certificate_[${fingerprint2}]`;
|
|
431
446
|
}
|
|
432
447
|
}
|
|
448
|
+
function safeStoreJoin(folder, filename) {
|
|
449
|
+
const full = path2.resolve(folder, filename);
|
|
450
|
+
const base = path2.resolve(folder);
|
|
451
|
+
if (full !== base && !full.startsWith(base + path2.sep)) {
|
|
452
|
+
warningLog("refusing to write certificate outside its store folder:", filename);
|
|
453
|
+
throw new Error(`certificate filename escapes its store folder: ${filename}`);
|
|
454
|
+
}
|
|
455
|
+
return full;
|
|
456
|
+
}
|
|
433
457
|
function findMatchingIssuerKey(entries, wantedIssuerKey) {
|
|
434
458
|
return entries.filter((entry) => {
|
|
435
459
|
const info = getOrComputeInfo(entry);
|
|
@@ -491,7 +515,7 @@ function findIssuerCertificateInChain(certificate, chain) {
|
|
|
491
515
|
}
|
|
492
516
|
return null;
|
|
493
517
|
}
|
|
494
|
-
var configurationFileSimpleTemplate, fsWriteFile,
|
|
518
|
+
var configurationFileSimpleTemplate, fsWriteFile, disallowedInLabel, ChainCompletionStatus, CertificateManager;
|
|
495
519
|
var init_certificate_manager = __esm({
|
|
496
520
|
"packages/node-opcua-pki/lib/pki/certificate_manager.ts"() {
|
|
497
521
|
"use strict";
|
|
@@ -503,7 +527,7 @@ var init_certificate_manager = __esm({
|
|
|
503
527
|
init_simple_config_template_cnf();
|
|
504
528
|
configurationFileSimpleTemplate = simple_config_template_cnf_default;
|
|
505
529
|
fsWriteFile = fs4.promises.writeFile;
|
|
506
|
-
|
|
530
|
+
disallowedInLabel = /[^A-Za-z0-9._ -]/g;
|
|
507
531
|
ChainCompletionStatus = /* @__PURE__ */ ((ChainCompletionStatus2) => {
|
|
508
532
|
ChainCompletionStatus2["AlreadyComplete"] = "AlreadyComplete";
|
|
509
533
|
ChainCompletionStatus2["ChainCompleted"] = "ChainCompleted";
|
|
@@ -640,6 +664,9 @@ var init_certificate_manager = __esm({
|
|
|
640
664
|
#disableFileWatchers;
|
|
641
665
|
#privateKeyPassphrase;
|
|
642
666
|
#privateKeyProvider;
|
|
667
|
+
#keyOperations;
|
|
668
|
+
/** The stable lazy wrap handed out by {@link getKeyOperations} for non-opaque configurations. */
|
|
669
|
+
#lazyLocalKeyOperations;
|
|
643
670
|
/**
|
|
644
671
|
* The on-disk key, decrypted once and kept for the instance's lifetime,
|
|
645
672
|
* so the passphrase (or its resolver function) is consulted at most
|
|
@@ -683,6 +710,17 @@ var init_certificate_manager = __esm({
|
|
|
683
710
|
this.#disableFileWatchers = options.disableFileWatchers ?? process.env.OPCUA_PKI_DISABLE_FILE_WATCHERS === "true";
|
|
684
711
|
this.#privateKeyPassphrase = options.privateKeyPassphrase;
|
|
685
712
|
this.#privateKeyProvider = options.privateKeyProvider;
|
|
713
|
+
this.#keyOperations = options.keyOperations;
|
|
714
|
+
if (this.#keyOperations && this.#privateKeyProvider) {
|
|
715
|
+
throw new Error(
|
|
716
|
+
"CertificateManager: 'keyOperations' and 'privateKeyProvider' are mutually exclusive: one hides the key, the other sources its material"
|
|
717
|
+
);
|
|
718
|
+
}
|
|
719
|
+
if (this.#keyOperations && this.#privateKeyPassphrase) {
|
|
720
|
+
throw new Error(
|
|
721
|
+
"CertificateManager: 'privateKeyPassphrase' is meaningless with 'keyOperations': there is no key material for a passphrase to protect"
|
|
722
|
+
);
|
|
723
|
+
}
|
|
686
724
|
mkdirRecursiveSync(options.location);
|
|
687
725
|
if (!fs4.existsSync(this.#location)) {
|
|
688
726
|
throw new Error(`CertificateManager cannot access location ${this.#location}`);
|
|
@@ -724,6 +762,11 @@ var init_certificate_manager = __esm({
|
|
|
724
762
|
* authority on what the current key is.
|
|
725
763
|
*/
|
|
726
764
|
async getPrivateKey() {
|
|
765
|
+
if (this.#keyOperations) {
|
|
766
|
+
throw new PrivateKeyUnavailableError(
|
|
767
|
+
"CertificateManager.getPrivateKey is not available when keyOperations is configured: the private key is held by the key-operations provider (HSM/KMS) and cannot be read \u2014 use getKeyOperations() instead"
|
|
768
|
+
);
|
|
769
|
+
}
|
|
727
770
|
if (this.#privateKeyProvider) {
|
|
728
771
|
return await this.#privateKeyProvider.getPrivateKey();
|
|
729
772
|
}
|
|
@@ -749,6 +792,75 @@ var init_certificate_manager = __esm({
|
|
|
749
792
|
}
|
|
750
793
|
/** In-flight first read of the on-disk key, so concurrent callers share one passphrase resolution. */
|
|
751
794
|
#privateKeyPromise;
|
|
795
|
+
/**
|
|
796
|
+
* True when this manager's key is opaque — configured through
|
|
797
|
+
* `keyOperations`, held by an HSM/KMS, never obtainable as material.
|
|
798
|
+
* When true, {@link getPrivateKey} throws `PrivateKeyUnavailableError`
|
|
799
|
+
* and {@link getKeyOperations} is the only way to use the key.
|
|
800
|
+
*/
|
|
801
|
+
isPrivateKeyOpaque() {
|
|
802
|
+
return !!this.#keyOperations;
|
|
803
|
+
}
|
|
804
|
+
/**
|
|
805
|
+
* The key as an opaque {@link IKeyOperations} — the recommended way to
|
|
806
|
+
* *use* the private key regardless of where it lives.
|
|
807
|
+
*
|
|
808
|
+
* Returns the configured `keyOperations` object when the key is opaque.
|
|
809
|
+
* Otherwise returns a stable lazy wrap over {@link getPrivateKey}: its
|
|
810
|
+
* methods resolve the key on first use (disk read, passphrase,
|
|
811
|
+
* `privateKeyProvider` — all async), so the wrap offers no synchronous
|
|
812
|
+
* fast path; callers that need one resolve the key themselves and build
|
|
813
|
+
* a `LocalKeyOperations` over it. The wrap follows key rotation: a
|
|
814
|
+
* `privateKeyProvider` that starts returning a different key gets a
|
|
815
|
+
* fresh underlying `LocalKeyOperations`.
|
|
816
|
+
*/
|
|
817
|
+
getKeyOperations() {
|
|
818
|
+
if (this.#keyOperations) {
|
|
819
|
+
return this.#keyOperations;
|
|
820
|
+
}
|
|
821
|
+
if (!this.#lazyLocalKeyOperations) {
|
|
822
|
+
let cached;
|
|
823
|
+
const resolve = async () => {
|
|
824
|
+
const key = await this.getPrivateKey();
|
|
825
|
+
if (!cached || cached.key !== key) {
|
|
826
|
+
cached = { key, ops: keyOperationsFromPrivateKey(key) };
|
|
827
|
+
}
|
|
828
|
+
return cached.ops;
|
|
829
|
+
};
|
|
830
|
+
this.#lazyLocalKeyOperations = {
|
|
831
|
+
sign: async (data, params) => (await resolve()).sign(data, params),
|
|
832
|
+
decryptBlock: async (block, params) => (await resolve()).decryptBlock(block, params),
|
|
833
|
+
getKeyMetadata: async () => (await resolve()).getKeyMetadata(),
|
|
834
|
+
getPublicKey: async () => (await resolve()).getPublicKey()
|
|
835
|
+
};
|
|
836
|
+
}
|
|
837
|
+
return this.#lazyLocalKeyOperations;
|
|
838
|
+
}
|
|
839
|
+
/**
|
|
840
|
+
* Fail closed at `initialize()` time, not on the first certificate
|
|
841
|
+
* operation, if the key cannot actually be used: wrong/missing
|
|
842
|
+
* passphrase on an encrypted key, a broken `privateKeyProvider`, or an
|
|
843
|
+
* unreachable `keyOperations` provider (probed via `getKeyMetadata`).
|
|
844
|
+
*/
|
|
845
|
+
async #probePrivateKey() {
|
|
846
|
+
if (this.#keyOperations) {
|
|
847
|
+
await this.#keyOperations.getKeyMetadata();
|
|
848
|
+
return;
|
|
849
|
+
}
|
|
850
|
+
await this.getPrivateKey();
|
|
851
|
+
}
|
|
852
|
+
/**
|
|
853
|
+
* The key to hand to a certificate-issuance primitive: the raw
|
|
854
|
+
* {@link PrivateKey} for a local configuration, or a {@link CaSigner}
|
|
855
|
+
* adapted from `keyOperations` when the key is opaque — which requires
|
|
856
|
+
* the provider to implement `getPublicKey` (the adapter says so if not).
|
|
857
|
+
*/
|
|
858
|
+
async #resolveSigningKey() {
|
|
859
|
+
if (this.#keyOperations) {
|
|
860
|
+
return caSignerFromKeyOperations(this.#keyOperations);
|
|
861
|
+
}
|
|
862
|
+
return await this.getPrivateKey();
|
|
863
|
+
}
|
|
752
864
|
/**
|
|
753
865
|
* Enable, disable, or rotate the passphrase protecting the on-disk
|
|
754
866
|
* private key: decrypt with `oldPassphrase` (omit if the key is
|
|
@@ -770,6 +882,11 @@ var init_certificate_manager = __esm({
|
|
|
770
882
|
* disk file for this method to rewrite).
|
|
771
883
|
*/
|
|
772
884
|
async reencryptPrivateKey(oldPassphrase, newPassphrase) {
|
|
885
|
+
if (this.#keyOperations) {
|
|
886
|
+
throw new PrivateKeyUnavailableError(
|
|
887
|
+
"reencryptPrivateKey: not supported when keyOperations is configured \u2014 there is no key material to rewrite"
|
|
888
|
+
);
|
|
889
|
+
}
|
|
773
890
|
if (this.#privateKeyProvider) {
|
|
774
891
|
throw new Error("reencryptPrivateKey: not supported when a privateKeyProvider is configured");
|
|
775
892
|
}
|
|
@@ -895,7 +1012,7 @@ var init_certificate_manager = __esm({
|
|
|
895
1012
|
} catch (_err) {
|
|
896
1013
|
return "BadCertificateInvalid";
|
|
897
1014
|
}
|
|
898
|
-
const filename =
|
|
1015
|
+
const filename = safeStoreJoin(this.rejectedFolder, `${buildIdealCertificateName(leafCertificate)}.pem`);
|
|
899
1016
|
debugLog("certificate has never been seen before and is now rejected (untrusted) ", filename);
|
|
900
1017
|
await fsWriteFile(filename, toPem(chain, "CERTIFICATE"));
|
|
901
1018
|
this.#thumbs.rejected.set(fingerprint2, { certificate: leafCertificate, filename });
|
|
@@ -1118,7 +1235,7 @@ var init_certificate_manager = __esm({
|
|
|
1118
1235
|
mkdirRecursiveSync(path2.join(pkiDir, "issuers"));
|
|
1119
1236
|
mkdirRecursiveSync(path2.join(pkiDir, "issuers/certs"));
|
|
1120
1237
|
mkdirRecursiveSync(path2.join(pkiDir, "issuers/crl"));
|
|
1121
|
-
const ownsDiskKey = !this.#privateKeyProvider;
|
|
1238
|
+
const ownsDiskKey = !this.#privateKeyProvider && !this.#keyOperations;
|
|
1122
1239
|
const needsKeyGeneration = ownsDiskKey && !fs4.existsSync(this.privateKey);
|
|
1123
1240
|
const needsKeyEncryption = ownsDiskKey && !needsKeyGeneration && this.#privateKeyPassphrase !== void 0 && !isEncryptedPrivateKeyFile(this.privateKey);
|
|
1124
1241
|
if (!fs4.existsSync(this.configFile) || needsKeyGeneration || needsKeyEncryption) {
|
|
@@ -1144,14 +1261,14 @@ var init_certificate_manager = __esm({
|
|
|
1144
1261
|
if (ownsDiskKey) {
|
|
1145
1262
|
restrictPrivateFilePermissions(this.privateKey, 384);
|
|
1146
1263
|
}
|
|
1147
|
-
await this
|
|
1264
|
+
await this.#probePrivateKey();
|
|
1148
1265
|
await this.#readCertificates();
|
|
1149
1266
|
});
|
|
1150
1267
|
} else {
|
|
1151
1268
|
if (ownsDiskKey) {
|
|
1152
1269
|
restrictPrivateFilePermissions(this.privateKey, 384);
|
|
1153
1270
|
}
|
|
1154
|
-
await this
|
|
1271
|
+
await this.#probePrivateKey();
|
|
1155
1272
|
await this.#readCertificates();
|
|
1156
1273
|
}
|
|
1157
1274
|
}
|
|
@@ -1236,7 +1353,7 @@ var init_certificate_manager = __esm({
|
|
|
1236
1353
|
if (typeof params.applicationUri !== "string") {
|
|
1237
1354
|
throw new Error("createSelfSignedCertificate: expecting applicationUri to be a string");
|
|
1238
1355
|
}
|
|
1239
|
-
if (!this.#privateKeyProvider && !fs4.existsSync(this.privateKey)) {
|
|
1356
|
+
if (!this.#privateKeyProvider && !this.#keyOperations && !fs4.existsSync(this.privateKey)) {
|
|
1240
1357
|
throw new Error(`Cannot find private key ${this.privateKey}`);
|
|
1241
1358
|
}
|
|
1242
1359
|
let certificateFilename = path2.join(this.rootDir, "own/certs/self_signed_certificate.pem");
|
|
@@ -1245,7 +1362,7 @@ var init_certificate_manager = __esm({
|
|
|
1245
1362
|
...params,
|
|
1246
1363
|
rootDir: this.rootDir,
|
|
1247
1364
|
configFile: this.configFile,
|
|
1248
|
-
privateKey: await this
|
|
1365
|
+
privateKey: await this.#resolveSigningKey(),
|
|
1249
1366
|
subject: params.subject || "CN=FIXME"
|
|
1250
1367
|
};
|
|
1251
1368
|
await this.withLock2(async () => {
|
|
@@ -1273,7 +1390,7 @@ var init_certificate_manager = __esm({
|
|
|
1273
1390
|
...params,
|
|
1274
1391
|
rootDir: path2.resolve(this.rootDir),
|
|
1275
1392
|
configFile: path2.resolve(this.configFile),
|
|
1276
|
-
privateKey: await this
|
|
1393
|
+
privateKey: await this.#resolveSigningKey()
|
|
1277
1394
|
};
|
|
1278
1395
|
return await this.withLock2(async () => {
|
|
1279
1396
|
const now = /* @__PURE__ */ new Date();
|
|
@@ -1303,7 +1420,7 @@ var init_certificate_manager = __esm({
|
|
|
1303
1420
|
if (this.#thumbs.issuers.certs.has(fingerprint2)) {
|
|
1304
1421
|
return "Good" /* Good */;
|
|
1305
1422
|
}
|
|
1306
|
-
const filename =
|
|
1423
|
+
const filename = safeStoreJoin(this.issuersCertFolder, `issuer_${buildIdealCertificateName(certificate)}.pem`);
|
|
1307
1424
|
await fs4.promises.writeFile(filename, pemCertificate, "ascii");
|
|
1308
1425
|
this.#thumbs.issuers.certs.set(fingerprint2, { certificate, filename });
|
|
1309
1426
|
if (addInTrustList) {
|
|
@@ -1762,7 +1879,7 @@ var init_certificate_manager = __esm({
|
|
|
1762
1879
|
let status = await this.#checkRejectedOrTrusted(certificate);
|
|
1763
1880
|
if (status === "unknown") {
|
|
1764
1881
|
const pem = toPem(chain, "CERTIFICATE");
|
|
1765
|
-
const filename =
|
|
1882
|
+
const filename = safeStoreJoin(this.rejectedFolder, `${buildIdealCertificateName(certificate)}.pem`);
|
|
1766
1883
|
await fs4.promises.writeFile(filename, pem);
|
|
1767
1884
|
this.#thumbs.rejected.set(fingerprint2, { certificate, filename });
|
|
1768
1885
|
status = "rejected";
|
|
@@ -1779,7 +1896,7 @@ var init_certificate_manager = __esm({
|
|
|
1779
1896
|
throw new Error(`#moveCertificate: certificate ${fingerprint2.substring(0, 10)} not found in ${status} index`);
|
|
1780
1897
|
}
|
|
1781
1898
|
const destFolder = newStatus === "trusted" ? this.trustedFolder : this.rejectedFolder;
|
|
1782
|
-
const certificateDest =
|
|
1899
|
+
const certificateDest = safeStoreJoin(destFolder, path2.basename(srcEntry.filename));
|
|
1783
1900
|
debugLog("#moveCertificate", fingerprint2.substring(0, 10), "old name", srcEntry.filename);
|
|
1784
1901
|
debugLog("#moveCertificate", fingerprint2.substring(0, 10), "new name", certificateDest);
|
|
1785
1902
|
await fs4.promises.rename(srcEntry.filename, certificateDest);
|
|
@@ -2536,8 +2653,32 @@ function passinArg(passphrase = "") {
|
|
|
2536
2653
|
function renderForDisplay(file, args) {
|
|
2537
2654
|
return [file, ...args].map((a) => a === "" || /[\s"'`$\\]/.test(a) ? JSON.stringify(a) : a).join(" ");
|
|
2538
2655
|
}
|
|
2656
|
+
function hasUnsafeArgChar(value) {
|
|
2657
|
+
for (const ch of value) {
|
|
2658
|
+
const code = ch.codePointAt(0);
|
|
2659
|
+
if (code < 32 || code === 127) {
|
|
2660
|
+
return true;
|
|
2661
|
+
}
|
|
2662
|
+
if (code < 128) {
|
|
2663
|
+
continue;
|
|
2664
|
+
}
|
|
2665
|
+
if (/[\p{L}\p{N}\p{M}]/u.test(ch)) {
|
|
2666
|
+
continue;
|
|
2667
|
+
}
|
|
2668
|
+
return true;
|
|
2669
|
+
}
|
|
2670
|
+
return false;
|
|
2671
|
+
}
|
|
2672
|
+
function checkArgs(file, args) {
|
|
2673
|
+
for (const value of [file, ...args]) {
|
|
2674
|
+
if (typeof value === "string" && hasUnsafeArgChar(value)) {
|
|
2675
|
+
throw new Error(`execute_openssl: refusing argument with an unexpected character: ${JSON.stringify(value)}`);
|
|
2676
|
+
}
|
|
2677
|
+
}
|
|
2678
|
+
}
|
|
2539
2679
|
async function execute2(file, args, options) {
|
|
2540
2680
|
const from = new Error();
|
|
2681
|
+
checkArgs(file, args);
|
|
2541
2682
|
options.cwd = options.cwd || process.cwd();
|
|
2542
2683
|
if (!g_config.silent) {
|
|
2543
2684
|
warningLog(chalk5.cyan(" CWD "), options.cwd);
|