node-opcua-pki 6.22.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 CHANGED
@@ -430,17 +430,30 @@ function makeFingerprint(certificate) {
430
430
  function short(stringToShorten) {
431
431
  return stringToShorten.substring(0, 10);
432
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
+ }
433
437
  function buildIdealCertificateName(certificate) {
434
438
  const chain = coerceCertificateChain(certificate);
435
439
  const fingerprint2 = makeFingerprint(chain);
436
440
  try {
437
441
  const commonName = exploreCertificate(chain[0]).tbsCertificate.subject.commonName || "";
438
- const sanitizedCommonName = commonName.replace(forbiddenChars, "_");
442
+ const sanitizedCommonName = toFilenameLabel(commonName);
439
443
  return `${sanitizedCommonName}[${fingerprint2}]`;
440
444
  } catch (_err) {
441
445
  return `invalid_certificate_[${fingerprint2}]`;
442
446
  }
443
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
+ }
444
457
  function findMatchingIssuerKey(entries, wantedIssuerKey) {
445
458
  return entries.filter((entry) => {
446
459
  const info = getOrComputeInfo(entry);
@@ -502,7 +515,7 @@ function findIssuerCertificateInChain(certificate, chain) {
502
515
  }
503
516
  return null;
504
517
  }
505
- var configurationFileSimpleTemplate, fsWriteFile, forbiddenChars, ChainCompletionStatus, CertificateManager;
518
+ var configurationFileSimpleTemplate, fsWriteFile, disallowedInLabel, ChainCompletionStatus, CertificateManager;
506
519
  var init_certificate_manager = __esm({
507
520
  "packages/node-opcua-pki/lib/pki/certificate_manager.ts"() {
508
521
  "use strict";
@@ -514,7 +527,7 @@ var init_certificate_manager = __esm({
514
527
  init_simple_config_template_cnf();
515
528
  configurationFileSimpleTemplate = simple_config_template_cnf_default;
516
529
  fsWriteFile = fs4.promises.writeFile;
517
- forbiddenChars = /[\x00-\x1F<>:"/\\|?*]/g;
530
+ disallowedInLabel = /[^A-Za-z0-9._ -]/g;
518
531
  ChainCompletionStatus = /* @__PURE__ */ ((ChainCompletionStatus2) => {
519
532
  ChainCompletionStatus2["AlreadyComplete"] = "AlreadyComplete";
520
533
  ChainCompletionStatus2["ChainCompleted"] = "ChainCompleted";
@@ -999,7 +1012,7 @@ var init_certificate_manager = __esm({
999
1012
  } catch (_err) {
1000
1013
  return "BadCertificateInvalid";
1001
1014
  }
1002
- const filename = path2.join(this.rejectedFolder, `${buildIdealCertificateName(leafCertificate)}.pem`);
1015
+ const filename = safeStoreJoin(this.rejectedFolder, `${buildIdealCertificateName(leafCertificate)}.pem`);
1003
1016
  debugLog("certificate has never been seen before and is now rejected (untrusted) ", filename);
1004
1017
  await fsWriteFile(filename, toPem(chain, "CERTIFICATE"));
1005
1018
  this.#thumbs.rejected.set(fingerprint2, { certificate: leafCertificate, filename });
@@ -1407,7 +1420,7 @@ var init_certificate_manager = __esm({
1407
1420
  if (this.#thumbs.issuers.certs.has(fingerprint2)) {
1408
1421
  return "Good" /* Good */;
1409
1422
  }
1410
- const filename = path2.join(this.issuersCertFolder, `issuer_${buildIdealCertificateName(certificate)}.pem`);
1423
+ const filename = safeStoreJoin(this.issuersCertFolder, `issuer_${buildIdealCertificateName(certificate)}.pem`);
1411
1424
  await fs4.promises.writeFile(filename, pemCertificate, "ascii");
1412
1425
  this.#thumbs.issuers.certs.set(fingerprint2, { certificate, filename });
1413
1426
  if (addInTrustList) {
@@ -1866,7 +1879,7 @@ var init_certificate_manager = __esm({
1866
1879
  let status = await this.#checkRejectedOrTrusted(certificate);
1867
1880
  if (status === "unknown") {
1868
1881
  const pem = toPem(chain, "CERTIFICATE");
1869
- const filename = path2.join(this.rejectedFolder, `${buildIdealCertificateName(certificate)}.pem`);
1882
+ const filename = safeStoreJoin(this.rejectedFolder, `${buildIdealCertificateName(certificate)}.pem`);
1870
1883
  await fs4.promises.writeFile(filename, pem);
1871
1884
  this.#thumbs.rejected.set(fingerprint2, { certificate, filename });
1872
1885
  status = "rejected";
@@ -1883,7 +1896,7 @@ var init_certificate_manager = __esm({
1883
1896
  throw new Error(`#moveCertificate: certificate ${fingerprint2.substring(0, 10)} not found in ${status} index`);
1884
1897
  }
1885
1898
  const destFolder = newStatus === "trusted" ? this.trustedFolder : this.rejectedFolder;
1886
- const certificateDest = path2.join(destFolder, path2.basename(srcEntry.filename));
1899
+ const certificateDest = safeStoreJoin(destFolder, path2.basename(srcEntry.filename));
1887
1900
  debugLog("#moveCertificate", fingerprint2.substring(0, 10), "old name", srcEntry.filename);
1888
1901
  debugLog("#moveCertificate", fingerprint2.substring(0, 10), "new name", certificateDest);
1889
1902
  await fs4.promises.rename(srcEntry.filename, certificateDest);
@@ -2640,8 +2653,32 @@ function passinArg(passphrase = "") {
2640
2653
  function renderForDisplay(file, args) {
2641
2654
  return [file, ...args].map((a) => a === "" || /[\s"'`$\\]/.test(a) ? JSON.stringify(a) : a).join(" ");
2642
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
+ }
2643
2679
  async function execute2(file, args, options) {
2644
2680
  const from = new Error();
2681
+ checkArgs(file, args);
2645
2682
  options.cwd = options.cwd || process.cwd();
2646
2683
  if (!g_config.silent) {
2647
2684
  warningLog(chalk5.cyan(" CWD "), options.cwd);