node-opcua-pki 6.13.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
@@ -2683,21 +2698,21 @@ nsComment = "CA Generated by Node-OPCUA Certificate utility usin
2683
2698
  subjectKeyIdentifier = hash
2684
2699
  basicConstraints = CA:TRUE
2685
2700
  keyUsage = critical, cRLSign, keyCertSign
2701
+ subjectAltName = $ENV::ALTNAME
2686
2702
  nsComment = "CA CSR generated by Node-OPCUA Certificate utility using openssl"
2687
2703
  [ v3_ca ]
2688
2704
  subjectKeyIdentifier = hash
2689
2705
  authorityKeyIdentifier = keyid:always,issuer:always
2690
2706
  basicConstraints = CA:TRUE
2691
2707
  keyUsage = critical, cRLSign, keyCertSign
2708
+ subjectAltName = $ENV::ALTNAME
2692
2709
  nsComment = "CA Certificate generated by Node-OPCUA Certificate utility using openssl"
2693
2710
  #nsCertType = sslCA, emailCA
2694
- #subjectAltName = email:copy
2695
2711
  #issuerAltName = issuer:copy
2696
2712
  #obj = DER:02:03
2697
- crlDistributionPoints = @crl_info
2698
- [ crl_info ]
2699
- URI.0 = http://localhost:8900/crl.pem
2700
- [ v3_selfsigned]
2713
+ {{#CDP_URL}}crlDistributionPoints = URI:$ENV::CDP_URL
2714
+ {{/CDP_URL}}{{#AIA_VALUE}}authorityInfoAccess = $ENV::AIA_VALUE
2715
+ {{/AIA_VALUE}}[ v3_selfsigned]
2701
2716
  basicConstraints = critical, CA:FALSE
2702
2717
  keyUsage = nonRepudiation, digitalSignature, keyEncipherment, dataEncipherment, keyAgreement
2703
2718
  extendedKeyUsage = critical,serverAuth ,clientAuth
@@ -2787,7 +2802,9 @@ async function construct_CertificateAuthority(certificateAuthority) {
2787
2802
  await fs10.promises.writeFile(caConfigFile, data);
2788
2803
  }
2789
2804
  const subjectOpt = ` -subj "${subject.toString()}" `;
2790
- processAltNames({});
2805
+ const caCommonName = subject.commonName || "NodeOPCUA-CA";
2806
+ setEnv("ALTNAME", `URI:urn:${caCommonName}`);
2807
+ certificateAuthority._wireRevocationEnvVars();
2791
2808
  const options = { cwd: caRootDir };
2792
2809
  const configFile = generateStaticConfig("conf/caconfig.cnf", options);
2793
2810
  const configOption = ` -config ${q4(n5(configFile))}`;
@@ -2841,6 +2858,33 @@ function parseOpenSSLDate(dateStr) {
2841
2858
  const sec = raw.substring(10, 12);
2842
2859
  return `${year}-${month}-${day}T${hour}:${min}:${sec}Z`;
2843
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
+ }
2844
2888
  var defaultSubject, configurationFileTemplate, configurationFileSimpleTemplate2, config3, n5, q4, CertificateAuthority;
2845
2889
  var init_certificate_authority = __esm({
2846
2890
  "packages/node-opcua-pki/lib/ca/certificate_authority.ts"() {
@@ -2871,6 +2915,10 @@ var init_certificate_authority = __esm({
2871
2915
  subject;
2872
2916
  /** @internal Parent CA (undefined for root CAs). */
2873
2917
  _issuerCA;
2918
+ /** @internal Configured CDP / AIA URLs (US-202). */
2919
+ _crlDistributionUrl;
2920
+ _ocspResponderUrl;
2921
+ _caIssuersUrl;
2874
2922
  constructor(options) {
2875
2923
  assert10(Object.prototype.hasOwnProperty.call(options, "location"));
2876
2924
  assert10(Object.prototype.hasOwnProperty.call(options, "keySize"));
@@ -2878,6 +2926,93 @@ var init_certificate_authority = __esm({
2878
2926
  this.keySize = options.keySize || 2048;
2879
2927
  this.subject = new Subject4(options.subject || defaultSubject);
2880
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
+ }
2881
3016
  }
2882
3017
  /** Absolute path to the CA root directory (alias for {@link location}). */
2883
3018
  get rootDir() {
@@ -3480,6 +3615,7 @@ var init_certificate_authority = __esm({
3480
3615
  async signCACertificateRequest(certFile, csrFile, params) {
3481
3616
  const caRootDir = path6.resolve(this.rootDir);
3482
3617
  const options = { cwd: caRootDir };
3618
+ this._wireRevocationEnvVars();
3483
3619
  const configFile = generateStaticConfig("conf/caconfig.cnf", options);
3484
3620
  const validity = params.validity ?? 3650;
3485
3621
  await execute_openssl(
@@ -3646,6 +3782,7 @@ var init_certificate_authority = __esm({
3646
3782
  ip
3647
3783
  };
3648
3784
  processAltNames(params);
3785
+ this._wireRevocationEnvVars();
3649
3786
  const configFile = generateStaticConfig("conf/caconfig.cnf", options);
3650
3787
  displaySubtitle("- then we ask the authority to sign the certificate signing request");
3651
3788
  const configOption = ` -config ${configFile}`;