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/index.d.mts CHANGED
@@ -161,6 +161,28 @@ interface CertificateAuthorityOptions {
161
161
  * The parent CA must be initialized before this CA.
162
162
  */
163
163
  issuerCA?: CertificateAuthority;
164
+ /**
165
+ * Public URL (http/https) where the CRL produced by this CA is
166
+ * reachable. When set, every issued certificate carries an
167
+ * X.509v3 `crlDistributionPoints` extension pointing at this URL.
168
+ *
169
+ * Leave undefined to omit the extension entirely (opt-in — see
170
+ * US-202). Validated synchronously at construction / setter call.
171
+ */
172
+ crlDistributionUrl?: string;
173
+ /**
174
+ * Public URL of the OCSP responder. When set, every issued cert
175
+ * carries an `authorityInfoAccess` extension with an `OCSP` leg
176
+ * pointing at this URL. Leave undefined to omit (US-202).
177
+ */
178
+ ocspResponderUrl?: string;
179
+ /**
180
+ * Public URL where the issuer's certificate can be fetched.
181
+ * When set, the `authorityInfoAccess` extension on every issued
182
+ * cert carries a `caIssuers` leg pointing at this URL (chain
183
+ * repair). Leave undefined to omit (US-202).
184
+ */
185
+ caIssuersUrl?: string;
164
186
  }
165
187
  /**
166
188
  * An OpenSSL-based Certificate Authority (CA) that can create,
@@ -270,7 +292,61 @@ declare class CertificateAuthority {
270
292
  readonly subject: Subject;
271
293
  /** @internal Parent CA (undefined for root CAs). */
272
294
  readonly _issuerCA?: CertificateAuthority;
295
+ /** @internal Configured CDP / AIA URLs (US-202). */
296
+ private _crlDistributionUrl?;
297
+ private _ocspResponderUrl?;
298
+ private _caIssuersUrl?;
273
299
  constructor(options: CertificateAuthorityOptions);
300
+ /**
301
+ * Public URL where the CRL produced by this CA is reachable, or
302
+ * `undefined` if no CDP extension should be emitted on issued certs.
303
+ */
304
+ get crlDistributionUrl(): string | undefined;
305
+ /**
306
+ * Public URL of the OCSP responder, or `undefined` if no AIA OCSP
307
+ * leg should be emitted on issued certs.
308
+ */
309
+ get ocspResponderUrl(): string | undefined;
310
+ /**
311
+ * Public URL where the issuer's certificate can be fetched, or
312
+ * `undefined` if no AIA caIssuers leg should be emitted.
313
+ */
314
+ get caIssuersUrl(): string | undefined;
315
+ /**
316
+ * Configure the URL embedded as `crlDistributionPoints` in every
317
+ * subsequently-issued certificate. Pass `undefined` to disable
318
+ * the extension entirely. Validated synchronously — throws on
319
+ * empty string, non-http(s) protocol, missing path. Warns (does
320
+ * not throw) when the URL points at loopback.
321
+ *
322
+ * @see US-202
323
+ */
324
+ setCrlDistributionUrl(url: string | undefined): void;
325
+ /**
326
+ * Configure the OCSP responder URL embedded as the `OCSP` leg of
327
+ * the `authorityInfoAccess` extension on every subsequently-issued
328
+ * certificate. Pass `undefined` to disable.
329
+ *
330
+ * @see US-202
331
+ */
332
+ setOcspResponderUrl(url: string | undefined): void;
333
+ /**
334
+ * Configure the caIssuers URL embedded as the `caIssuers` leg of
335
+ * the `authorityInfoAccess` extension on every subsequently-issued
336
+ * certificate. Pass `undefined` to disable.
337
+ *
338
+ * @see US-202
339
+ */
340
+ setCaIssuersUrl(url: string | undefined): void;
341
+ /**
342
+ * @internal
343
+ * Populate the OpenSSL config substitution env vars (`CDP_URL` and
344
+ * `AIA_VALUE`) from the configured URLs, or unset them so the
345
+ * matching `{{#KEY}}...{{/KEY}}` blocks in the templates are
346
+ * stripped. MUST be called before every `generateStaticConfig`
347
+ * invocation that signs a certificate.
348
+ */
349
+ _wireRevocationEnvVars(): void;
274
350
  /** Absolute path to the CA root directory (alias for {@link location}). */
275
351
  get rootDir(): string;
276
352
  /** Path to the OpenSSL configuration file (`conf/caconfig.cnf`). */
package/dist/index.d.ts CHANGED
@@ -161,6 +161,28 @@ interface CertificateAuthorityOptions {
161
161
  * The parent CA must be initialized before this CA.
162
162
  */
163
163
  issuerCA?: CertificateAuthority;
164
+ /**
165
+ * Public URL (http/https) where the CRL produced by this CA is
166
+ * reachable. When set, every issued certificate carries an
167
+ * X.509v3 `crlDistributionPoints` extension pointing at this URL.
168
+ *
169
+ * Leave undefined to omit the extension entirely (opt-in — see
170
+ * US-202). Validated synchronously at construction / setter call.
171
+ */
172
+ crlDistributionUrl?: string;
173
+ /**
174
+ * Public URL of the OCSP responder. When set, every issued cert
175
+ * carries an `authorityInfoAccess` extension with an `OCSP` leg
176
+ * pointing at this URL. Leave undefined to omit (US-202).
177
+ */
178
+ ocspResponderUrl?: string;
179
+ /**
180
+ * Public URL where the issuer's certificate can be fetched.
181
+ * When set, the `authorityInfoAccess` extension on every issued
182
+ * cert carries a `caIssuers` leg pointing at this URL (chain
183
+ * repair). Leave undefined to omit (US-202).
184
+ */
185
+ caIssuersUrl?: string;
164
186
  }
165
187
  /**
166
188
  * An OpenSSL-based Certificate Authority (CA) that can create,
@@ -270,7 +292,61 @@ declare class CertificateAuthority {
270
292
  readonly subject: Subject;
271
293
  /** @internal Parent CA (undefined for root CAs). */
272
294
  readonly _issuerCA?: CertificateAuthority;
295
+ /** @internal Configured CDP / AIA URLs (US-202). */
296
+ private _crlDistributionUrl?;
297
+ private _ocspResponderUrl?;
298
+ private _caIssuersUrl?;
273
299
  constructor(options: CertificateAuthorityOptions);
300
+ /**
301
+ * Public URL where the CRL produced by this CA is reachable, or
302
+ * `undefined` if no CDP extension should be emitted on issued certs.
303
+ */
304
+ get crlDistributionUrl(): string | undefined;
305
+ /**
306
+ * Public URL of the OCSP responder, or `undefined` if no AIA OCSP
307
+ * leg should be emitted on issued certs.
308
+ */
309
+ get ocspResponderUrl(): string | undefined;
310
+ /**
311
+ * Public URL where the issuer's certificate can be fetched, or
312
+ * `undefined` if no AIA caIssuers leg should be emitted.
313
+ */
314
+ get caIssuersUrl(): string | undefined;
315
+ /**
316
+ * Configure the URL embedded as `crlDistributionPoints` in every
317
+ * subsequently-issued certificate. Pass `undefined` to disable
318
+ * the extension entirely. Validated synchronously — throws on
319
+ * empty string, non-http(s) protocol, missing path. Warns (does
320
+ * not throw) when the URL points at loopback.
321
+ *
322
+ * @see US-202
323
+ */
324
+ setCrlDistributionUrl(url: string | undefined): void;
325
+ /**
326
+ * Configure the OCSP responder URL embedded as the `OCSP` leg of
327
+ * the `authorityInfoAccess` extension on every subsequently-issued
328
+ * certificate. Pass `undefined` to disable.
329
+ *
330
+ * @see US-202
331
+ */
332
+ setOcspResponderUrl(url: string | undefined): void;
333
+ /**
334
+ * Configure the caIssuers URL embedded as the `caIssuers` leg of
335
+ * the `authorityInfoAccess` extension on every subsequently-issued
336
+ * certificate. Pass `undefined` to disable.
337
+ *
338
+ * @see US-202
339
+ */
340
+ setCaIssuersUrl(url: string | undefined): void;
341
+ /**
342
+ * @internal
343
+ * Populate the OpenSSL config substitution env vars (`CDP_URL` and
344
+ * `AIA_VALUE`) from the configured URLs, or unset them so the
345
+ * matching `{{#KEY}}...{{/KEY}}` blocks in the templates are
346
+ * stripped. MUST be called before every `generateStaticConfig`
347
+ * invocation that signs a certificate.
348
+ */
349
+ _wireRevocationEnvVars(): void;
274
350
  /** Absolute path to the CA root directory (alias for {@link location}). */
275
351
  get rootDir(): string;
276
352
  /** Path to the OpenSSL configuration file (`conf/caconfig.cnf`). */
package/dist/index.js CHANGED
@@ -166,9 +166,15 @@ function setEnv(varName, value) {
166
166
  process.env[varName] = value;
167
167
  }
168
168
  }
169
+ function hasEnv(varName) {
170
+ return Object.prototype.hasOwnProperty.call(exportedEnvVars, varName);
171
+ }
169
172
  function getEnv(varName) {
170
173
  return exportedEnvVars[varName];
171
174
  }
175
+ function unsetEnv(varName) {
176
+ delete exportedEnvVars[varName];
177
+ }
172
178
  function getEnvironmentVarNames() {
173
179
  return Object.keys(exportedEnvVars).map((varName) => {
174
180
  return { key: varName, pattern: `\\$ENV\\:\\:${varName}` };
@@ -685,10 +691,17 @@ function openssl_require2DigitYearInDate() {
685
691
  }
686
692
  g_config.opensslVersion = "";
687
693
  var _counter = 0;
694
+ function stripConditionalBlocks(template) {
695
+ return template.replace(/\{\{#([A-Z_][A-Z0-9_]*)\}\}([\s\S]*?)\{\{\/\1\}\}\r?\n?/g, (_match, key, content) => {
696
+ const keep = hasEnv(key) && getEnv(key) !== "";
697
+ return keep ? content : "";
698
+ });
699
+ }
688
700
  function generateStaticConfig(configPath, options) {
689
701
  const prePath = options?.cwd || "";
690
702
  const originalFilename = !import_node_path3.default.isAbsolute(configPath) ? import_node_path3.default.join(prePath, configPath) : configPath;
691
703
  let staticConfig = import_node_fs5.default.readFileSync(originalFilename, { encoding: "utf8" });
704
+ staticConfig = stripConditionalBlocks(staticConfig);
692
705
  for (const envVar of getEnvironmentVarNames()) {
693
706
  staticConfig = staticConfig.replace(new RegExp(envVar.pattern, "gi"), getEnv(envVar.key));
694
707
  }
@@ -843,7 +856,9 @@ nsComment = ''OpenSSL Generated Certificate''
843
856
  #nsSslServerName =
844
857
  keyUsage = critical, digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment, keyAgreement
845
858
  extendedKeyUsage = critical,serverAuth ,clientAuth
846
-
859
+ {{#CDP_URL}}crlDistributionPoints = URI:$ENV::CDP_URL
860
+ {{/CDP_URL}}{{#AIA_VALUE}}authorityInfoAccess = $ENV::AIA_VALUE
861
+ {{/AIA_VALUE}}
847
862
  [ v3_req ]
848
863
  basicConstraints = critical, CA:FALSE
849
864
  keyUsage = nonRepudiation, digitalSignature, keyEncipherment, dataEncipherment, keyAgreement
@@ -854,21 +869,21 @@ nsComment = "CA Generated by Node-OPCUA Certificate utility usin
854
869
  subjectKeyIdentifier = hash
855
870
  basicConstraints = CA:TRUE
856
871
  keyUsage = critical, cRLSign, keyCertSign
872
+ subjectAltName = $ENV::ALTNAME
857
873
  nsComment = "CA CSR generated by Node-OPCUA Certificate utility using openssl"
858
874
  [ v3_ca ]
859
875
  subjectKeyIdentifier = hash
860
876
  authorityKeyIdentifier = keyid:always,issuer:always
861
877
  basicConstraints = CA:TRUE
862
878
  keyUsage = critical, cRLSign, keyCertSign
879
+ subjectAltName = $ENV::ALTNAME
863
880
  nsComment = "CA Certificate generated by Node-OPCUA Certificate utility using openssl"
864
881
  #nsCertType = sslCA, emailCA
865
- #subjectAltName = email:copy
866
882
  #issuerAltName = issuer:copy
867
883
  #obj = DER:02:03
868
- crlDistributionPoints = @crl_info
869
- [ crl_info ]
870
- URI.0 = http://localhost:8900/crl.pem
871
- [ v3_selfsigned]
884
+ {{#CDP_URL}}crlDistributionPoints = URI:$ENV::CDP_URL
885
+ {{/CDP_URL}}{{#AIA_VALUE}}authorityInfoAccess = $ENV::AIA_VALUE
886
+ {{/AIA_VALUE}}[ v3_selfsigned]
872
887
  basicConstraints = critical, CA:FALSE
873
888
  keyUsage = nonRepudiation, digitalSignature, keyEncipherment, dataEncipherment, keyAgreement
874
889
  extendedKeyUsage = critical,serverAuth ,clientAuth
@@ -949,7 +964,9 @@ async function construct_CertificateAuthority(certificateAuthority) {
949
964
  await import_node_fs7.default.promises.writeFile(caConfigFile, data);
950
965
  }
951
966
  const subjectOpt = ` -subj "${subject.toString()}" `;
952
- processAltNames({});
967
+ const caCommonName = subject.commonName || "NodeOPCUA-CA";
968
+ setEnv("ALTNAME", `URI:urn:${caCommonName}`);
969
+ certificateAuthority._wireRevocationEnvVars();
953
970
  const options = { cwd: caRootDir };
954
971
  const configFile = generateStaticConfig("conf/caconfig.cnf", options);
955
972
  const configOption = ` -config ${q3(n4(configFile))}`;
@@ -1003,6 +1020,33 @@ function parseOpenSSLDate(dateStr) {
1003
1020
  const sec = raw.substring(10, 12);
1004
1021
  return `${year}-${month}-${day}T${hour}:${min}:${sec}Z`;
1005
1022
  }
1023
+ function validateRevocationUrl(url2, fieldName) {
1024
+ if (url2 === void 0) {
1025
+ return void 0;
1026
+ }
1027
+ if (url2 === "") {
1028
+ throw new Error(`${fieldName} must not be empty \u2014 pass undefined to disable the extension`);
1029
+ }
1030
+ let parsed;
1031
+ try {
1032
+ parsed = new URL(url2);
1033
+ } catch {
1034
+ throw new Error(`${fieldName} is not a valid URL: ${url2}`);
1035
+ }
1036
+ if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
1037
+ throw new Error(`${fieldName} must use http: or https: (got ${parsed.protocol} in ${url2})`);
1038
+ }
1039
+ if (!parsed.pathname || parsed.pathname === "/") {
1040
+ throw new Error(`${fieldName} must include a path component (got ${url2})`);
1041
+ }
1042
+ const isLoopback = parsed.hostname === "localhost" || parsed.hostname === "::1" || parsed.hostname.startsWith("127.");
1043
+ if (isLoopback) {
1044
+ console.warn(
1045
+ `[node-opcua-pki] ${fieldName} points at loopback (${url2}) \u2014 certificates issued with this URL will be unreachable from any other host.`
1046
+ );
1047
+ }
1048
+ return url2;
1049
+ }
1006
1050
  var CertificateAuthority = class {
1007
1051
  /** RSA key size used when generating the CA private key. */
1008
1052
  keySize;
@@ -1012,6 +1056,10 @@ var CertificateAuthority = class {
1012
1056
  subject;
1013
1057
  /** @internal Parent CA (undefined for root CAs). */
1014
1058
  _issuerCA;
1059
+ /** @internal Configured CDP / AIA URLs (US-202). */
1060
+ _crlDistributionUrl;
1061
+ _ocspResponderUrl;
1062
+ _caIssuersUrl;
1015
1063
  constructor(options) {
1016
1064
  (0, import_node_assert7.default)(Object.prototype.hasOwnProperty.call(options, "location"));
1017
1065
  (0, import_node_assert7.default)(Object.prototype.hasOwnProperty.call(options, "keySize"));
@@ -1019,6 +1067,93 @@ var CertificateAuthority = class {
1019
1067
  this.keySize = options.keySize || 2048;
1020
1068
  this.subject = new import_node_opcua_crypto2.Subject(options.subject || defaultSubject);
1021
1069
  this._issuerCA = options.issuerCA;
1070
+ if (options.crlDistributionUrl !== void 0) {
1071
+ this.setCrlDistributionUrl(options.crlDistributionUrl);
1072
+ }
1073
+ if (options.ocspResponderUrl !== void 0) {
1074
+ this.setOcspResponderUrl(options.ocspResponderUrl);
1075
+ }
1076
+ if (options.caIssuersUrl !== void 0) {
1077
+ this.setCaIssuersUrl(options.caIssuersUrl);
1078
+ }
1079
+ }
1080
+ /**
1081
+ * Public URL where the CRL produced by this CA is reachable, or
1082
+ * `undefined` if no CDP extension should be emitted on issued certs.
1083
+ */
1084
+ get crlDistributionUrl() {
1085
+ return this._crlDistributionUrl;
1086
+ }
1087
+ /**
1088
+ * Public URL of the OCSP responder, or `undefined` if no AIA OCSP
1089
+ * leg should be emitted on issued certs.
1090
+ */
1091
+ get ocspResponderUrl() {
1092
+ return this._ocspResponderUrl;
1093
+ }
1094
+ /**
1095
+ * Public URL where the issuer's certificate can be fetched, or
1096
+ * `undefined` if no AIA caIssuers leg should be emitted.
1097
+ */
1098
+ get caIssuersUrl() {
1099
+ return this._caIssuersUrl;
1100
+ }
1101
+ /**
1102
+ * Configure the URL embedded as `crlDistributionPoints` in every
1103
+ * subsequently-issued certificate. Pass `undefined` to disable
1104
+ * the extension entirely. Validated synchronously — throws on
1105
+ * empty string, non-http(s) protocol, missing path. Warns (does
1106
+ * not throw) when the URL points at loopback.
1107
+ *
1108
+ * @see US-202
1109
+ */
1110
+ setCrlDistributionUrl(url2) {
1111
+ this._crlDistributionUrl = validateRevocationUrl(url2, "crlDistributionUrl");
1112
+ }
1113
+ /**
1114
+ * Configure the OCSP responder URL embedded as the `OCSP` leg of
1115
+ * the `authorityInfoAccess` extension on every subsequently-issued
1116
+ * certificate. Pass `undefined` to disable.
1117
+ *
1118
+ * @see US-202
1119
+ */
1120
+ setOcspResponderUrl(url2) {
1121
+ this._ocspResponderUrl = validateRevocationUrl(url2, "ocspResponderUrl");
1122
+ }
1123
+ /**
1124
+ * Configure the caIssuers URL embedded as the `caIssuers` leg of
1125
+ * the `authorityInfoAccess` extension on every subsequently-issued
1126
+ * certificate. Pass `undefined` to disable.
1127
+ *
1128
+ * @see US-202
1129
+ */
1130
+ setCaIssuersUrl(url2) {
1131
+ this._caIssuersUrl = validateRevocationUrl(url2, "caIssuersUrl");
1132
+ }
1133
+ /**
1134
+ * @internal
1135
+ * Populate the OpenSSL config substitution env vars (`CDP_URL` and
1136
+ * `AIA_VALUE`) from the configured URLs, or unset them so the
1137
+ * matching `{{#KEY}}...{{/KEY}}` blocks in the templates are
1138
+ * stripped. MUST be called before every `generateStaticConfig`
1139
+ * invocation that signs a certificate.
1140
+ */
1141
+ _wireRevocationEnvVars() {
1142
+ unsetEnv("CDP_URL");
1143
+ unsetEnv("AIA_VALUE");
1144
+ if (this._crlDistributionUrl) {
1145
+ setEnv("CDP_URL", this._crlDistributionUrl);
1146
+ }
1147
+ const aiaLegs = [];
1148
+ if (this._ocspResponderUrl) {
1149
+ aiaLegs.push(`OCSP;URI:${this._ocspResponderUrl}`);
1150
+ }
1151
+ if (this._caIssuersUrl) {
1152
+ aiaLegs.push(`caIssuers;URI:${this._caIssuersUrl}`);
1153
+ }
1154
+ if (aiaLegs.length > 0) {
1155
+ setEnv("AIA_VALUE", aiaLegs.join(","));
1156
+ }
1022
1157
  }
1023
1158
  /** Absolute path to the CA root directory (alias for {@link location}). */
1024
1159
  get rootDir() {
@@ -1621,6 +1756,7 @@ var CertificateAuthority = class {
1621
1756
  async signCACertificateRequest(certFile, csrFile, params) {
1622
1757
  const caRootDir = import_node_path5.default.resolve(this.rootDir);
1623
1758
  const options = { cwd: caRootDir };
1759
+ this._wireRevocationEnvVars();
1624
1760
  const configFile = generateStaticConfig("conf/caconfig.cnf", options);
1625
1761
  const validity = params.validity ?? 3650;
1626
1762
  await execute_openssl(
@@ -1787,6 +1923,7 @@ var CertificateAuthority = class {
1787
1923
  ip
1788
1924
  };
1789
1925
  processAltNames(params);
1926
+ this._wireRevocationEnvVars();
1790
1927
  const configFile = generateStaticConfig("conf/caconfig.cnf", options);
1791
1928
  displaySubtitle("- then we ask the authority to sign the certificate signing request");
1792
1929
  const configOption = ` -config ${configFile}`;