node-opcua-pki 6.14.0 → 6.15.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/index.mjs CHANGED
@@ -120,9 +120,15 @@ function setEnv(varName, value) {
120
120
  process.env[varName] = value;
121
121
  }
122
122
  }
123
+ function hasEnv(varName) {
124
+ return Object.prototype.hasOwnProperty.call(exportedEnvVars, varName);
125
+ }
123
126
  function getEnv(varName) {
124
127
  return exportedEnvVars[varName];
125
128
  }
129
+ function unsetEnv(varName) {
130
+ delete exportedEnvVars[varName];
131
+ }
126
132
  function getEnvironmentVarNames() {
127
133
  return Object.keys(exportedEnvVars).map((varName) => {
128
134
  return { key: varName, pattern: `\\$ENV\\:\\:${varName}` };
@@ -639,10 +645,17 @@ function openssl_require2DigitYearInDate() {
639
645
  }
640
646
  g_config.opensslVersion = "";
641
647
  var _counter = 0;
648
+ function stripConditionalBlocks(template) {
649
+ return template.replace(/\{\{#([A-Z_][A-Z0-9_]*)\}\}([\s\S]*?)\{\{\/\1\}\}\r?\n?/g, (_match, key, content) => {
650
+ const keep = hasEnv(key) && getEnv(key) !== "";
651
+ return keep ? content : "";
652
+ });
653
+ }
642
654
  function generateStaticConfig(configPath, options) {
643
655
  const prePath = options?.cwd || "";
644
656
  const originalFilename = !path3.isAbsolute(configPath) ? path3.join(prePath, configPath) : configPath;
645
657
  let staticConfig = fs5.readFileSync(originalFilename, { encoding: "utf8" });
658
+ staticConfig = stripConditionalBlocks(staticConfig);
646
659
  for (const envVar of getEnvironmentVarNames()) {
647
660
  staticConfig = staticConfig.replace(new RegExp(envVar.pattern, "gi"), getEnv(envVar.key));
648
661
  }
@@ -797,7 +810,9 @@ nsComment = ''OpenSSL Generated Certificate''
797
810
  #nsSslServerName =
798
811
  keyUsage = critical, digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment, keyAgreement
799
812
  extendedKeyUsage = critical,serverAuth ,clientAuth
800
-
813
+ {{#CDP_URL}}crlDistributionPoints = URI:$ENV::CDP_URL
814
+ {{/CDP_URL}}{{#AIA_VALUE}}authorityInfoAccess = $ENV::AIA_VALUE
815
+ {{/AIA_VALUE}}
801
816
  [ v3_req ]
802
817
  basicConstraints = critical, CA:FALSE
803
818
  keyUsage = nonRepudiation, digitalSignature, keyEncipherment, dataEncipherment, keyAgreement
@@ -820,10 +835,9 @@ nsComment = "CA Certificate generated by Node-OPCUA Certificate
820
835
  #nsCertType = sslCA, emailCA
821
836
  #issuerAltName = issuer:copy
822
837
  #obj = DER:02:03
823
- crlDistributionPoints = @crl_info
824
- [ crl_info ]
825
- URI.0 = http://localhost:8900/crl.pem
826
- [ v3_selfsigned]
838
+ {{#CDP_URL}}crlDistributionPoints = URI:$ENV::CDP_URL
839
+ {{/CDP_URL}}{{#AIA_VALUE}}authorityInfoAccess = $ENV::AIA_VALUE
840
+ {{/AIA_VALUE}}[ v3_selfsigned]
827
841
  basicConstraints = critical, CA:FALSE
828
842
  keyUsage = nonRepudiation, digitalSignature, keyEncipherment, dataEncipherment, keyAgreement
829
843
  extendedKeyUsage = critical,serverAuth ,clientAuth
@@ -906,6 +920,7 @@ async function construct_CertificateAuthority(certificateAuthority) {
906
920
  const subjectOpt = ` -subj "${subject.toString()}" `;
907
921
  const caCommonName = subject.commonName || "NodeOPCUA-CA";
908
922
  setEnv("ALTNAME", `URI:urn:${caCommonName}`);
923
+ certificateAuthority._wireRevocationEnvVars();
909
924
  const options = { cwd: caRootDir };
910
925
  const configFile = generateStaticConfig("conf/caconfig.cnf", options);
911
926
  const configOption = ` -config ${q3(n4(configFile))}`;
@@ -959,6 +974,33 @@ function parseOpenSSLDate(dateStr) {
959
974
  const sec = raw.substring(10, 12);
960
975
  return `${year}-${month}-${day}T${hour}:${min}:${sec}Z`;
961
976
  }
977
+ function validateRevocationUrl(url2, fieldName) {
978
+ if (url2 === void 0) {
979
+ return void 0;
980
+ }
981
+ if (url2 === "") {
982
+ throw new Error(`${fieldName} must not be empty \u2014 pass undefined to disable the extension`);
983
+ }
984
+ let parsed;
985
+ try {
986
+ parsed = new URL(url2);
987
+ } catch {
988
+ throw new Error(`${fieldName} is not a valid URL: ${url2}`);
989
+ }
990
+ if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
991
+ throw new Error(`${fieldName} must use http: or https: (got ${parsed.protocol} in ${url2})`);
992
+ }
993
+ if (!parsed.pathname || parsed.pathname === "/") {
994
+ throw new Error(`${fieldName} must include a path component (got ${url2})`);
995
+ }
996
+ const isLoopback = parsed.hostname === "localhost" || parsed.hostname === "::1" || parsed.hostname.startsWith("127.");
997
+ if (isLoopback) {
998
+ console.warn(
999
+ `[node-opcua-pki] ${fieldName} points at loopback (${url2}) \u2014 certificates issued with this URL will be unreachable from any other host.`
1000
+ );
1001
+ }
1002
+ return url2;
1003
+ }
962
1004
  var CertificateAuthority = class {
963
1005
  /** RSA key size used when generating the CA private key. */
964
1006
  keySize;
@@ -968,6 +1010,10 @@ var CertificateAuthority = class {
968
1010
  subject;
969
1011
  /** @internal Parent CA (undefined for root CAs). */
970
1012
  _issuerCA;
1013
+ /** @internal Configured CDP / AIA URLs (US-202). */
1014
+ _crlDistributionUrl;
1015
+ _ocspResponderUrl;
1016
+ _caIssuersUrl;
971
1017
  constructor(options) {
972
1018
  assert7(Object.prototype.hasOwnProperty.call(options, "location"));
973
1019
  assert7(Object.prototype.hasOwnProperty.call(options, "keySize"));
@@ -975,6 +1021,93 @@ var CertificateAuthority = class {
975
1021
  this.keySize = options.keySize || 2048;
976
1022
  this.subject = new Subject2(options.subject || defaultSubject);
977
1023
  this._issuerCA = options.issuerCA;
1024
+ if (options.crlDistributionUrl !== void 0) {
1025
+ this.setCrlDistributionUrl(options.crlDistributionUrl);
1026
+ }
1027
+ if (options.ocspResponderUrl !== void 0) {
1028
+ this.setOcspResponderUrl(options.ocspResponderUrl);
1029
+ }
1030
+ if (options.caIssuersUrl !== void 0) {
1031
+ this.setCaIssuersUrl(options.caIssuersUrl);
1032
+ }
1033
+ }
1034
+ /**
1035
+ * Public URL where the CRL produced by this CA is reachable, or
1036
+ * `undefined` if no CDP extension should be emitted on issued certs.
1037
+ */
1038
+ get crlDistributionUrl() {
1039
+ return this._crlDistributionUrl;
1040
+ }
1041
+ /**
1042
+ * Public URL of the OCSP responder, or `undefined` if no AIA OCSP
1043
+ * leg should be emitted on issued certs.
1044
+ */
1045
+ get ocspResponderUrl() {
1046
+ return this._ocspResponderUrl;
1047
+ }
1048
+ /**
1049
+ * Public URL where the issuer's certificate can be fetched, or
1050
+ * `undefined` if no AIA caIssuers leg should be emitted.
1051
+ */
1052
+ get caIssuersUrl() {
1053
+ return this._caIssuersUrl;
1054
+ }
1055
+ /**
1056
+ * Configure the URL embedded as `crlDistributionPoints` in every
1057
+ * subsequently-issued certificate. Pass `undefined` to disable
1058
+ * the extension entirely. Validated synchronously — throws on
1059
+ * empty string, non-http(s) protocol, missing path. Warns (does
1060
+ * not throw) when the URL points at loopback.
1061
+ *
1062
+ * @see US-202
1063
+ */
1064
+ setCrlDistributionUrl(url2) {
1065
+ this._crlDistributionUrl = validateRevocationUrl(url2, "crlDistributionUrl");
1066
+ }
1067
+ /**
1068
+ * Configure the OCSP responder URL embedded as the `OCSP` leg of
1069
+ * the `authorityInfoAccess` extension on every subsequently-issued
1070
+ * certificate. Pass `undefined` to disable.
1071
+ *
1072
+ * @see US-202
1073
+ */
1074
+ setOcspResponderUrl(url2) {
1075
+ this._ocspResponderUrl = validateRevocationUrl(url2, "ocspResponderUrl");
1076
+ }
1077
+ /**
1078
+ * Configure the caIssuers URL embedded as the `caIssuers` leg of
1079
+ * the `authorityInfoAccess` extension on every subsequently-issued
1080
+ * certificate. Pass `undefined` to disable.
1081
+ *
1082
+ * @see US-202
1083
+ */
1084
+ setCaIssuersUrl(url2) {
1085
+ this._caIssuersUrl = validateRevocationUrl(url2, "caIssuersUrl");
1086
+ }
1087
+ /**
1088
+ * @internal
1089
+ * Populate the OpenSSL config substitution env vars (`CDP_URL` and
1090
+ * `AIA_VALUE`) from the configured URLs, or unset them so the
1091
+ * matching `{{#KEY}}...{{/KEY}}` blocks in the templates are
1092
+ * stripped. MUST be called before every `generateStaticConfig`
1093
+ * invocation that signs a certificate.
1094
+ */
1095
+ _wireRevocationEnvVars() {
1096
+ unsetEnv("CDP_URL");
1097
+ unsetEnv("AIA_VALUE");
1098
+ if (this._crlDistributionUrl) {
1099
+ setEnv("CDP_URL", this._crlDistributionUrl);
1100
+ }
1101
+ const aiaLegs = [];
1102
+ if (this._ocspResponderUrl) {
1103
+ aiaLegs.push(`OCSP;URI:${this._ocspResponderUrl}`);
1104
+ }
1105
+ if (this._caIssuersUrl) {
1106
+ aiaLegs.push(`caIssuers;URI:${this._caIssuersUrl}`);
1107
+ }
1108
+ if (aiaLegs.length > 0) {
1109
+ setEnv("AIA_VALUE", aiaLegs.join(","));
1110
+ }
978
1111
  }
979
1112
  /** Absolute path to the CA root directory (alias for {@link location}). */
980
1113
  get rootDir() {
@@ -1577,6 +1710,7 @@ var CertificateAuthority = class {
1577
1710
  async signCACertificateRequest(certFile, csrFile, params) {
1578
1711
  const caRootDir = path5.resolve(this.rootDir);
1579
1712
  const options = { cwd: caRootDir };
1713
+ this._wireRevocationEnvVars();
1580
1714
  const configFile = generateStaticConfig("conf/caconfig.cnf", options);
1581
1715
  const validity = params.validity ?? 3650;
1582
1716
  await execute_openssl(
@@ -1743,6 +1877,7 @@ var CertificateAuthority = class {
1743
1877
  ip
1744
1878
  };
1745
1879
  processAltNames(params);
1880
+ this._wireRevocationEnvVars();
1746
1881
  const configFile = generateStaticConfig("conf/caconfig.cnf", options);
1747
1882
  displaySubtitle("- then we ask the authority to sign the certificate signing request");
1748
1883
  const configOption = ` -config ${configFile}`;