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.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
@@ -866,10 +881,9 @@ nsComment = "CA Certificate generated by Node-OPCUA Certificate
866
881
  #nsCertType = sslCA, emailCA
867
882
  #issuerAltName = issuer:copy
868
883
  #obj = DER:02:03
869
- crlDistributionPoints = @crl_info
870
- [ crl_info ]
871
- URI.0 = http://localhost:8900/crl.pem
872
- [ v3_selfsigned]
884
+ {{#CDP_URL}}crlDistributionPoints = URI:$ENV::CDP_URL
885
+ {{/CDP_URL}}{{#AIA_VALUE}}authorityInfoAccess = $ENV::AIA_VALUE
886
+ {{/AIA_VALUE}}[ v3_selfsigned]
873
887
  basicConstraints = critical, CA:FALSE
874
888
  keyUsage = nonRepudiation, digitalSignature, keyEncipherment, dataEncipherment, keyAgreement
875
889
  extendedKeyUsage = critical,serverAuth ,clientAuth
@@ -952,6 +966,7 @@ async function construct_CertificateAuthority(certificateAuthority) {
952
966
  const subjectOpt = ` -subj "${subject.toString()}" `;
953
967
  const caCommonName = subject.commonName || "NodeOPCUA-CA";
954
968
  setEnv("ALTNAME", `URI:urn:${caCommonName}`);
969
+ certificateAuthority._wireRevocationEnvVars();
955
970
  const options = { cwd: caRootDir };
956
971
  const configFile = generateStaticConfig("conf/caconfig.cnf", options);
957
972
  const configOption = ` -config ${q3(n4(configFile))}`;
@@ -1005,6 +1020,33 @@ function parseOpenSSLDate(dateStr) {
1005
1020
  const sec = raw.substring(10, 12);
1006
1021
  return `${year}-${month}-${day}T${hour}:${min}:${sec}Z`;
1007
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
+ }
1008
1050
  var CertificateAuthority = class {
1009
1051
  /** RSA key size used when generating the CA private key. */
1010
1052
  keySize;
@@ -1014,6 +1056,10 @@ var CertificateAuthority = class {
1014
1056
  subject;
1015
1057
  /** @internal Parent CA (undefined for root CAs). */
1016
1058
  _issuerCA;
1059
+ /** @internal Configured CDP / AIA URLs (US-202). */
1060
+ _crlDistributionUrl;
1061
+ _ocspResponderUrl;
1062
+ _caIssuersUrl;
1017
1063
  constructor(options) {
1018
1064
  (0, import_node_assert7.default)(Object.prototype.hasOwnProperty.call(options, "location"));
1019
1065
  (0, import_node_assert7.default)(Object.prototype.hasOwnProperty.call(options, "keySize"));
@@ -1021,6 +1067,93 @@ var CertificateAuthority = class {
1021
1067
  this.keySize = options.keySize || 2048;
1022
1068
  this.subject = new import_node_opcua_crypto2.Subject(options.subject || defaultSubject);
1023
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
+ }
1024
1157
  }
1025
1158
  /** Absolute path to the CA root directory (alias for {@link location}). */
1026
1159
  get rootDir() {
@@ -1623,6 +1756,7 @@ var CertificateAuthority = class {
1623
1756
  async signCACertificateRequest(certFile, csrFile, params) {
1624
1757
  const caRootDir = import_node_path5.default.resolve(this.rootDir);
1625
1758
  const options = { cwd: caRootDir };
1759
+ this._wireRevocationEnvVars();
1626
1760
  const configFile = generateStaticConfig("conf/caconfig.cnf", options);
1627
1761
  const validity = params.validity ?? 3650;
1628
1762
  await execute_openssl(
@@ -1789,6 +1923,7 @@ var CertificateAuthority = class {
1789
1923
  ip
1790
1924
  };
1791
1925
  processAltNames(params);
1926
+ this._wireRevocationEnvVars();
1792
1927
  const configFile = generateStaticConfig("conf/caconfig.cnf", options);
1793
1928
  displaySubtitle("- then we ask the authority to sign the certificate signing request");
1794
1929
  const configOption = ` -config ${configFile}`;