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/bin/pki.mjs CHANGED
@@ -1944,9 +1944,15 @@ function setEnv(varName, value) {
1944
1944
  process.env[varName] = value;
1945
1945
  }
1946
1946
  }
1947
+ function hasEnv(varName) {
1948
+ return Object.prototype.hasOwnProperty.call(exportedEnvVars, varName);
1949
+ }
1947
1950
  function getEnv(varName) {
1948
1951
  return exportedEnvVars[varName];
1949
1952
  }
1953
+ function unsetEnv(varName) {
1954
+ delete exportedEnvVars[varName];
1955
+ }
1950
1956
  function getEnvironmentVarNames() {
1951
1957
  return Object.keys(exportedEnvVars).map((varName) => {
1952
1958
  return { key: varName, pattern: `\\$ENV\\:\\:${varName}` };
@@ -2416,10 +2422,17 @@ function openssl_require2DigitYearInDate() {
2416
2422
  }
2417
2423
  return g_config.opensslVersion.match(/OpenSSL 0\.9/);
2418
2424
  }
2425
+ function stripConditionalBlocks(template) {
2426
+ return template.replace(/\{\{#([A-Z_][A-Z0-9_]*)\}\}([\s\S]*?)\{\{\/\1\}\}\r?\n?/g, (_match, key, content) => {
2427
+ const keep = hasEnv(key) && getEnv(key) !== "";
2428
+ return keep ? content : "";
2429
+ });
2430
+ }
2419
2431
  function generateStaticConfig(configPath, options) {
2420
2432
  const prePath = options?.cwd || "";
2421
2433
  const originalFilename = !path4.isAbsolute(configPath) ? path4.join(prePath, configPath) : configPath;
2422
2434
  let staticConfig = fs7.readFileSync(originalFilename, { encoding: "utf8" });
2435
+ staticConfig = stripConditionalBlocks(staticConfig);
2423
2436
  for (const envVar of getEnvironmentVarNames()) {
2424
2437
  staticConfig = staticConfig.replace(new RegExp(envVar.pattern, "gi"), getEnv(envVar.key));
2425
2438
  }
@@ -2672,7 +2685,9 @@ nsComment = ''OpenSSL Generated Certificate''
2672
2685
  #nsSslServerName =
2673
2686
  keyUsage = critical, digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment, keyAgreement
2674
2687
  extendedKeyUsage = critical,serverAuth ,clientAuth
2675
-
2688
+ {{#CDP_URL}}crlDistributionPoints = URI:$ENV::CDP_URL
2689
+ {{/CDP_URL}}{{#AIA_VALUE}}authorityInfoAccess = $ENV::AIA_VALUE
2690
+ {{/AIA_VALUE}}
2676
2691
  [ v3_req ]
2677
2692
  basicConstraints = critical, CA:FALSE
2678
2693
  keyUsage = nonRepudiation, digitalSignature, keyEncipherment, dataEncipherment, keyAgreement
@@ -2695,10 +2710,9 @@ nsComment = "CA Certificate generated by Node-OPCUA Certificate
2695
2710
  #nsCertType = sslCA, emailCA
2696
2711
  #issuerAltName = issuer:copy
2697
2712
  #obj = DER:02:03
2698
- crlDistributionPoints = @crl_info
2699
- [ crl_info ]
2700
- URI.0 = http://localhost:8900/crl.pem
2701
- [ v3_selfsigned]
2713
+ {{#CDP_URL}}crlDistributionPoints = URI:$ENV::CDP_URL
2714
+ {{/CDP_URL}}{{#AIA_VALUE}}authorityInfoAccess = $ENV::AIA_VALUE
2715
+ {{/AIA_VALUE}}[ v3_selfsigned]
2702
2716
  basicConstraints = critical, CA:FALSE
2703
2717
  keyUsage = nonRepudiation, digitalSignature, keyEncipherment, dataEncipherment, keyAgreement
2704
2718
  extendedKeyUsage = critical,serverAuth ,clientAuth
@@ -2790,6 +2804,7 @@ async function construct_CertificateAuthority(certificateAuthority) {
2790
2804
  const subjectOpt = ` -subj "${subject.toString()}" `;
2791
2805
  const caCommonName = subject.commonName || "NodeOPCUA-CA";
2792
2806
  setEnv("ALTNAME", `URI:urn:${caCommonName}`);
2807
+ certificateAuthority._wireRevocationEnvVars();
2793
2808
  const options = { cwd: caRootDir };
2794
2809
  const configFile = generateStaticConfig("conf/caconfig.cnf", options);
2795
2810
  const configOption = ` -config ${q4(n5(configFile))}`;
@@ -2843,6 +2858,33 @@ function parseOpenSSLDate(dateStr) {
2843
2858
  const sec = raw.substring(10, 12);
2844
2859
  return `${year}-${month}-${day}T${hour}:${min}:${sec}Z`;
2845
2860
  }
2861
+ function validateRevocationUrl(url2, fieldName) {
2862
+ if (url2 === void 0) {
2863
+ return void 0;
2864
+ }
2865
+ if (url2 === "") {
2866
+ throw new Error(`${fieldName} must not be empty \u2014 pass undefined to disable the extension`);
2867
+ }
2868
+ let parsed;
2869
+ try {
2870
+ parsed = new URL(url2);
2871
+ } catch {
2872
+ throw new Error(`${fieldName} is not a valid URL: ${url2}`);
2873
+ }
2874
+ if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
2875
+ throw new Error(`${fieldName} must use http: or https: (got ${parsed.protocol} in ${url2})`);
2876
+ }
2877
+ if (!parsed.pathname || parsed.pathname === "/") {
2878
+ throw new Error(`${fieldName} must include a path component (got ${url2})`);
2879
+ }
2880
+ const isLoopback = parsed.hostname === "localhost" || parsed.hostname === "::1" || parsed.hostname.startsWith("127.");
2881
+ if (isLoopback) {
2882
+ console.warn(
2883
+ `[node-opcua-pki] ${fieldName} points at loopback (${url2}) \u2014 certificates issued with this URL will be unreachable from any other host.`
2884
+ );
2885
+ }
2886
+ return url2;
2887
+ }
2846
2888
  var defaultSubject, configurationFileTemplate, configurationFileSimpleTemplate2, config3, n5, q4, CertificateAuthority;
2847
2889
  var init_certificate_authority = __esm({
2848
2890
  "packages/node-opcua-pki/lib/ca/certificate_authority.ts"() {
@@ -2873,6 +2915,10 @@ var init_certificate_authority = __esm({
2873
2915
  subject;
2874
2916
  /** @internal Parent CA (undefined for root CAs). */
2875
2917
  _issuerCA;
2918
+ /** @internal Configured CDP / AIA URLs (US-202). */
2919
+ _crlDistributionUrl;
2920
+ _ocspResponderUrl;
2921
+ _caIssuersUrl;
2876
2922
  constructor(options) {
2877
2923
  assert10(Object.prototype.hasOwnProperty.call(options, "location"));
2878
2924
  assert10(Object.prototype.hasOwnProperty.call(options, "keySize"));
@@ -2880,6 +2926,93 @@ var init_certificate_authority = __esm({
2880
2926
  this.keySize = options.keySize || 2048;
2881
2927
  this.subject = new Subject4(options.subject || defaultSubject);
2882
2928
  this._issuerCA = options.issuerCA;
2929
+ if (options.crlDistributionUrl !== void 0) {
2930
+ this.setCrlDistributionUrl(options.crlDistributionUrl);
2931
+ }
2932
+ if (options.ocspResponderUrl !== void 0) {
2933
+ this.setOcspResponderUrl(options.ocspResponderUrl);
2934
+ }
2935
+ if (options.caIssuersUrl !== void 0) {
2936
+ this.setCaIssuersUrl(options.caIssuersUrl);
2937
+ }
2938
+ }
2939
+ /**
2940
+ * Public URL where the CRL produced by this CA is reachable, or
2941
+ * `undefined` if no CDP extension should be emitted on issued certs.
2942
+ */
2943
+ get crlDistributionUrl() {
2944
+ return this._crlDistributionUrl;
2945
+ }
2946
+ /**
2947
+ * Public URL of the OCSP responder, or `undefined` if no AIA OCSP
2948
+ * leg should be emitted on issued certs.
2949
+ */
2950
+ get ocspResponderUrl() {
2951
+ return this._ocspResponderUrl;
2952
+ }
2953
+ /**
2954
+ * Public URL where the issuer's certificate can be fetched, or
2955
+ * `undefined` if no AIA caIssuers leg should be emitted.
2956
+ */
2957
+ get caIssuersUrl() {
2958
+ return this._caIssuersUrl;
2959
+ }
2960
+ /**
2961
+ * Configure the URL embedded as `crlDistributionPoints` in every
2962
+ * subsequently-issued certificate. Pass `undefined` to disable
2963
+ * the extension entirely. Validated synchronously — throws on
2964
+ * empty string, non-http(s) protocol, missing path. Warns (does
2965
+ * not throw) when the URL points at loopback.
2966
+ *
2967
+ * @see US-202
2968
+ */
2969
+ setCrlDistributionUrl(url2) {
2970
+ this._crlDistributionUrl = validateRevocationUrl(url2, "crlDistributionUrl");
2971
+ }
2972
+ /**
2973
+ * Configure the OCSP responder URL embedded as the `OCSP` leg of
2974
+ * the `authorityInfoAccess` extension on every subsequently-issued
2975
+ * certificate. Pass `undefined` to disable.
2976
+ *
2977
+ * @see US-202
2978
+ */
2979
+ setOcspResponderUrl(url2) {
2980
+ this._ocspResponderUrl = validateRevocationUrl(url2, "ocspResponderUrl");
2981
+ }
2982
+ /**
2983
+ * Configure the caIssuers URL embedded as the `caIssuers` leg of
2984
+ * the `authorityInfoAccess` extension on every subsequently-issued
2985
+ * certificate. Pass `undefined` to disable.
2986
+ *
2987
+ * @see US-202
2988
+ */
2989
+ setCaIssuersUrl(url2) {
2990
+ this._caIssuersUrl = validateRevocationUrl(url2, "caIssuersUrl");
2991
+ }
2992
+ /**
2993
+ * @internal
2994
+ * Populate the OpenSSL config substitution env vars (`CDP_URL` and
2995
+ * `AIA_VALUE`) from the configured URLs, or unset them so the
2996
+ * matching `{{#KEY}}...{{/KEY}}` blocks in the templates are
2997
+ * stripped. MUST be called before every `generateStaticConfig`
2998
+ * invocation that signs a certificate.
2999
+ */
3000
+ _wireRevocationEnvVars() {
3001
+ unsetEnv("CDP_URL");
3002
+ unsetEnv("AIA_VALUE");
3003
+ if (this._crlDistributionUrl) {
3004
+ setEnv("CDP_URL", this._crlDistributionUrl);
3005
+ }
3006
+ const aiaLegs = [];
3007
+ if (this._ocspResponderUrl) {
3008
+ aiaLegs.push(`OCSP;URI:${this._ocspResponderUrl}`);
3009
+ }
3010
+ if (this._caIssuersUrl) {
3011
+ aiaLegs.push(`caIssuers;URI:${this._caIssuersUrl}`);
3012
+ }
3013
+ if (aiaLegs.length > 0) {
3014
+ setEnv("AIA_VALUE", aiaLegs.join(","));
3015
+ }
2883
3016
  }
2884
3017
  /** Absolute path to the CA root directory (alias for {@link location}). */
2885
3018
  get rootDir() {
@@ -3482,6 +3615,7 @@ var init_certificate_authority = __esm({
3482
3615
  async signCACertificateRequest(certFile, csrFile, params) {
3483
3616
  const caRootDir = path6.resolve(this.rootDir);
3484
3617
  const options = { cwd: caRootDir };
3618
+ this._wireRevocationEnvVars();
3485
3619
  const configFile = generateStaticConfig("conf/caconfig.cnf", options);
3486
3620
  const validity = params.validity ?? 3650;
3487
3621
  await execute_openssl(
@@ -3648,6 +3782,7 @@ var init_certificate_authority = __esm({
3648
3782
  ip
3649
3783
  };
3650
3784
  processAltNames(params);
3785
+ this._wireRevocationEnvVars();
3651
3786
  const configFile = generateStaticConfig("conf/caconfig.cnf", options);
3652
3787
  displaySubtitle("- then we ask the authority to sign the certificate signing request");
3653
3788
  const configOption = ` -config ${configFile}`;