node-opcua-pki 6.12.0 → 6.12.2

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
@@ -700,7 +700,7 @@ async function createCertificateSigningRequestWithOpenSSL(certificateSigningRequ
700
700
  }
701
701
 
702
702
  // packages/node-opcua-pki/lib/pki/templates/simple_config_template.cnf.ts
703
- var config = '##################################################################################################\n## SIMPLE OPENSSL CONFIG FILE FOR SELF-SIGNED CERTIFICATE GENERATION\n################################################################################################################\n\ndistinguished_name = req_distinguished_name\ndefault_md = sha1\n\ndefault_md = sha256 # The default digest algorithm\n\n[ v3_ca ]\nsubjectKeyIdentifier = hash\nauthorityKeyIdentifier = keyid:always,issuer:always\n\n# authorityKeyIdentifier = keyid\nbasicConstraints = CA:TRUE\nkeyUsage = critical, cRLSign, keyCertSign\nnsComment = "Self-signed Certificate for CA generated by Node-OPCUA Certificate utility"\n#nsCertType = sslCA, emailCA\n#subjectAltName = email:copy\n#issuerAltName = issuer:copy\n#obj = DER:02:03\n# crlDistributionPoints = @crl_info\n# [ crl_info ]\n# URI.0 = http://localhost:8900/crl.pem\nsubjectAltName = $ENV::ALTNAME\n\n[ req ]\ndays = 390\nreq_extensions = v3_req\nx509_extensions = v3_ca\n\n[v3_req]\nbasicConstraints = CA:false\nkeyUsage = critical, cRLSign, keyCertSign\nsubjectAltName = $ENV::ALTNAME\n\n[ v3_ca_signed]\nsubjectKeyIdentifier = hash\nauthorityKeyIdentifier = keyid,issuer\nbasicConstraints = critical, CA:FALSE\nkeyUsage = nonRepudiation, digitalSignature, keyEncipherment, dataEncipherment, keyCertSign\nextendedKeyUsage = clientAuth,serverAuth \nnsComment = "certificate generated by Node-OPCUA Certificate utility and signed by a CA"\nsubjectAltName = $ENV::ALTNAME\n[ v3_selfsigned]\nsubjectKeyIdentifier = hash\nauthorityKeyIdentifier = keyid,issuer\nbasicConstraints = critical, CA:FALSE\nkeyUsage = nonRepudiation, digitalSignature, keyEncipherment, dataEncipherment, keyCertSign\nextendedKeyUsage = clientAuth,serverAuth \nnsComment = "Self-signed certificate generated by Node-OPCUA Certificate utility"\nsubjectAltName = $ENV::ALTNAME\n[ req_distinguished_name ]\ncountryName = Country Name (2 letter code)\ncountryName_default = FR\ncountryName_min = 2\ncountryName_max = 2\n# stateOrProvinceName = State or Province Name (full name)\n# stateOrProvinceName_default = Ile de France\n# localityName = Locality Name (city, district)\n# localityName_default = Paris\norganizationName = Organization Name (company)\norganizationName_default = NodeOPCUA\n# organizationalUnitName = Organizational Unit Name (department, division)\n# organizationalUnitName_default = R&D\ncommonName = Common Name (hostname, FQDN, IP, or your name)\ncommonName_max = 256\ncommonName_default = NodeOPCUA\n# emailAddress = Email Address\n# emailAddress_max = 40\n# emailAddress_default = node-opcua (at) node-opcua (dot) com\nsubjectAltName = $ENV::ALTNAME';
703
+ var config = '##################################################################################################\n## SIMPLE OPENSSL CONFIG FILE FOR SELF-SIGNED CERTIFICATE GENERATION\n################################################################################################################\n\ndistinguished_name = req_distinguished_name\ndefault_md = sha1\n\ndefault_md = sha256 # The default digest algorithm\n\n[ v3_ca ]\nsubjectKeyIdentifier = hash\nauthorityKeyIdentifier = keyid:always,issuer:always\n\n# authorityKeyIdentifier = keyid\nbasicConstraints = CA:TRUE\nkeyUsage = critical, cRLSign, keyCertSign\nnsComment = "Self-signed Certificate for CA generated by Node-OPCUA Certificate utility"\n#nsCertType = sslCA, emailCA\n#subjectAltName = email:copy\n#issuerAltName = issuer:copy\n#obj = DER:02:03\n# crlDistributionPoints = @crl_info\n# [ crl_info ]\n# URI.0 = http://localhost:8900/crl.pem\nsubjectAltName = $ENV::ALTNAME\n\n[ req ]\ndays = 390\nreq_extensions = v3_req\nx509_extensions = v3_ca\n\n[v3_req]\nbasicConstraints = CA:false\nkeyUsage = critical, nonRepudiation, digitalSignature, keyEncipherment, dataEncipherment\nsubjectAltName = $ENV::ALTNAME\n\n[ v3_ca_signed]\nsubjectKeyIdentifier = hash\nauthorityKeyIdentifier = keyid,issuer\nbasicConstraints = critical, CA:FALSE\nkeyUsage = nonRepudiation, digitalSignature, keyEncipherment, dataEncipherment\nextendedKeyUsage = clientAuth,serverAuth \nnsComment = "certificate generated by Node-OPCUA Certificate utility and signed by a CA"\nsubjectAltName = $ENV::ALTNAME\n[ v3_selfsigned]\nsubjectKeyIdentifier = hash\nauthorityKeyIdentifier = keyid,issuer\nbasicConstraints = critical, CA:FALSE\nkeyUsage = nonRepudiation, digitalSignature, keyEncipherment, dataEncipherment\nextendedKeyUsage = clientAuth,serverAuth \nnsComment = "Self-signed certificate generated by Node-OPCUA Certificate utility"\nsubjectAltName = $ENV::ALTNAME\n[ req_distinguished_name ]\ncountryName = Country Name (2 letter code)\ncountryName_default = FR\ncountryName_min = 2\ncountryName_max = 2\n# stateOrProvinceName = State or Province Name (full name)\n# stateOrProvinceName_default = Ile de France\n# localityName = Locality Name (city, district)\n# localityName_default = Paris\norganizationName = Organization Name (company)\norganizationName_default = NodeOPCUA\n# organizationalUnitName = Organizational Unit Name (department, division)\n# organizationalUnitName_default = R&D\ncommonName = Common Name (hostname, FQDN, IP, or your name)\ncommonName_max = 256\ncommonName_default = NodeOPCUA\n# emailAddress = Email Address\n# emailAddress_max = 40\n# emailAddress_default = node-opcua (at) node-opcua (dot) com\nsubjectAltName = $ENV::ALTNAME';
704
704
  var simple_config_template_cnf_default = config;
705
705
 
706
706
  // packages/node-opcua-pki/lib/ca/templates/ca_config_template.cnf.ts
@@ -795,7 +795,7 @@ nsComment = ''OpenSSL Generated Certificate''
795
795
  #nsRenewalUrl =
796
796
  #nsCaPolicyUrl =
797
797
  #nsSslServerName =
798
- keyUsage = critical, digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment, keyAgreement, keyCertSign
798
+ keyUsage = critical, digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment, keyAgreement
799
799
  extendedKeyUsage = critical,serverAuth ,clientAuth
800
800
 
801
801
  [ v3_req ]
@@ -1903,8 +1903,17 @@ var VerificationStatus = /* @__PURE__ */ ((VerificationStatus2) => {
1903
1903
  VerificationStatus2["Good"] = "Good";
1904
1904
  return VerificationStatus2;
1905
1905
  })(VerificationStatus || {});
1906
+ function coerceCertificateChain(certificate) {
1907
+ if (Array.isArray(certificate)) {
1908
+ if (certificate.length === 0) return [];
1909
+ return certificate.reduce((acc, cert) => {
1910
+ return acc.concat(split_der(cert));
1911
+ }, []);
1912
+ }
1913
+ return split_der(certificate);
1914
+ }
1906
1915
  function makeFingerprint(certificate) {
1907
- const chain = Array.isArray(certificate) ? certificate : split_der(certificate);
1916
+ const chain = coerceCertificateChain(certificate);
1908
1917
  return makeSHA1Thumbprint(chain[0]).toString("hex");
1909
1918
  }
1910
1919
  function short(stringToShorten) {
@@ -1912,9 +1921,10 @@ function short(stringToShorten) {
1912
1921
  }
1913
1922
  var forbiddenChars = /[\x00-\x1F<>:"/\\|?*]/g;
1914
1923
  function buildIdealCertificateName(certificate) {
1915
- const fingerprint = makeFingerprint(certificate);
1924
+ const chain = coerceCertificateChain(certificate);
1925
+ const fingerprint = makeFingerprint(chain);
1916
1926
  try {
1917
- const commonName = exploreCertificate2(certificate).tbsCertificate.subject.commonName || "";
1927
+ const commonName = exploreCertificate2(chain[0]).tbsCertificate.subject.commonName || "";
1918
1928
  const sanitizedCommonName = commonName.replace(forbiddenChars, "_");
1919
1929
  return `${sanitizedCommonName}[${fingerprint}]`;
1920
1930
  } catch (_err) {
@@ -1976,7 +1986,8 @@ function isRootIssuer(certificate) {
1976
1986
  }
1977
1987
  }
1978
1988
  function findIssuerCertificateInChain(certificate, chain) {
1979
- const firstCertificate = Array.isArray(certificate) ? certificate[0] : certificate;
1989
+ const coercedCertificate = coerceCertificateChain(certificate);
1990
+ const firstCertificate = coercedCertificate[0];
1980
1991
  if (!firstCertificate) {
1981
1992
  return null;
1982
1993
  }
@@ -1989,7 +2000,8 @@ function findIssuerCertificateInChain(certificate, chain) {
1989
2000
  debugLog("Certificate has no extension 3");
1990
2001
  return null;
1991
2002
  }
1992
- const potentialIssuers = chain.filter((c) => {
2003
+ const coercedChain = coerceCertificateChain(chain);
2004
+ const potentialIssuers = coercedChain.filter((c) => {
1993
2005
  const info = exploreCertificate2(c);
1994
2006
  return info.tbsCertificate.extensions && info.tbsCertificate.extensions.subjectKeyIdentifier === wantedIssuerKey;
1995
2007
  });
@@ -2159,18 +2171,18 @@ var CertificateManager = class _CertificateManager extends EventEmitter {
2159
2171
  /**
2160
2172
  * Move a certificate to the rejected store.
2161
2173
  * If the certificate was previously trusted, it will be removed from the trusted folder.
2162
- * @param certificate - the DER-encoded certificate
2174
+ * @param certificateOrChain - the DER-encoded certificate or certificate chain
2163
2175
  */
2164
- async rejectCertificate(certificate) {
2165
- await this.#moveCertificate(certificate, "rejected");
2176
+ async rejectCertificate(certificateOrChain) {
2177
+ await this.#moveCertificate(certificateOrChain, "rejected");
2166
2178
  }
2167
2179
  /**
2168
2180
  * Move a certificate to the trusted store.
2169
2181
  * If the certificate was previously rejected, it will be removed from the rejected folder.
2170
- * @param certificate - the DER-encoded certificate
2182
+ * @param certificateOrChain - the DER-encoded certificate or certificate chain
2171
2183
  */
2172
- async trustCertificate(certificate) {
2173
- await this.#moveCertificate(certificate, "trusted");
2184
+ async trustCertificate(certificateOrChain) {
2185
+ await this.#moveCertificate(certificateOrChain, "trusted");
2174
2186
  }
2175
2187
  /**
2176
2188
  * Check whether the trusted certificate store is empty.
@@ -2224,37 +2236,46 @@ var CertificateManager = class _CertificateManager extends EventEmitter {
2224
2236
  * @returns `"Good"` if trusted, `"BadCertificateUntrusted"` if rejected/unknown,
2225
2237
  * or `"BadCertificateInvalid"` if the certificate cannot be parsed.
2226
2238
  */
2227
- async isCertificateTrusted(certificate) {
2228
- let fingerprint;
2239
+ async isCertificateTrusted(certificateOrCertificateChain) {
2229
2240
  try {
2230
- fingerprint = makeFingerprint(certificate);
2231
- } catch (_err) {
2232
- return "BadCertificateInvalid";
2233
- }
2234
- if (this.#thumbs.trusted.has(fingerprint)) {
2235
- return "Good";
2236
- }
2237
- if (!this.#thumbs.rejected.has(fingerprint)) {
2238
- if (!this.untrustUnknownCertificate) {
2239
- return "Good";
2241
+ const chain = coerceCertificateChain(certificateOrCertificateChain);
2242
+ const leafCertificate = chain[0];
2243
+ if (chain.length < 1) {
2244
+ return "BadCertificateInvalid";
2240
2245
  }
2246
+ let fingerprint;
2241
2247
  try {
2242
- exploreCertificateInfo(certificate);
2248
+ fingerprint = makeFingerprint(chain[0]);
2243
2249
  } catch (_err) {
2244
2250
  return "BadCertificateInvalid";
2245
2251
  }
2246
- const filename = path6.join(this.rejectedFolder, `${buildIdealCertificateName(certificate)}.pem`);
2247
- debugLog("certificate has never been seen before and is now rejected (untrusted) ", filename);
2248
- await fsWriteFile(filename, toPem2(certificate, "CERTIFICATE"));
2249
- this.#thumbs.rejected.set(fingerprint, { certificate, filename });
2252
+ if (this.#thumbs.trusted.has(fingerprint)) {
2253
+ return "Good";
2254
+ }
2255
+ if (!this.#thumbs.rejected.has(fingerprint)) {
2256
+ if (!this.untrustUnknownCertificate) {
2257
+ return "Good";
2258
+ }
2259
+ try {
2260
+ exploreCertificateInfo(chain[0]);
2261
+ } catch (_err) {
2262
+ return "BadCertificateInvalid";
2263
+ }
2264
+ const filename = path6.join(this.rejectedFolder, `${buildIdealCertificateName(leafCertificate)}.pem`);
2265
+ debugLog("certificate has never been seen before and is now rejected (untrusted) ", filename);
2266
+ await fsWriteFile(filename, toPem2(chain, "CERTIFICATE"));
2267
+ this.#thumbs.rejected.set(fingerprint, { certificate: leafCertificate, filename });
2268
+ }
2269
+ return "BadCertificateUntrusted";
2270
+ } catch (_err) {
2271
+ return "BadCertificateInvalid";
2250
2272
  }
2251
- return "BadCertificateUntrusted";
2252
2273
  }
2253
2274
  async #innerVerifyCertificateAsync(certificateOrChain, _isIssuer, level, options) {
2254
2275
  if (level >= 5) {
2255
2276
  return "BadSecurityChecksFailed" /* BadSecurityChecksFailed */;
2256
2277
  }
2257
- const chain = Array.isArray(certificateOrChain) ? certificateOrChain : [certificateOrChain];
2278
+ const chain = coerceCertificateChain(certificateOrChain);
2258
2279
  debugLog("NB CERTIFICATE IN CHAIN = ", chain.length);
2259
2280
  const info = exploreCertificate2(chain[0]);
2260
2281
  let hasValidIssuer = false;
@@ -2309,7 +2330,7 @@ var CertificateManager = class _CertificateManager extends EventEmitter {
2309
2330
  return "BadSecurityChecksFailed" /* BadSecurityChecksFailed */;
2310
2331
  }
2311
2332
  hasValidIssuer = true;
2312
- let revokedStatus = await this.isCertificateRevoked(certificateOrChain);
2333
+ let revokedStatus = await this.isCertificateRevoked(chain, issuerCertificate);
2313
2334
  if (revokedStatus === "BadCertificateRevocationUnknown" /* BadCertificateRevocationUnknown */) {
2314
2335
  if (options?.ignoreMissingRevocationList) {
2315
2336
  revokedStatus = "Good" /* Good */;
@@ -2334,11 +2355,11 @@ var CertificateManager = class _CertificateManager extends EventEmitter {
2334
2355
  debugLog("Self-signed Certificate signature is not valid");
2335
2356
  return "BadSecurityChecksFailed" /* BadSecurityChecksFailed */;
2336
2357
  }
2337
- const revokedStatus = await this.isCertificateRevoked(certificateOrChain);
2358
+ const revokedStatus = await this.isCertificateRevoked(chain);
2338
2359
  debugLog("revokedStatus of self signed certificate:", revokedStatus);
2339
2360
  }
2340
2361
  }
2341
- const status = await this.#checkRejectedOrTrusted(certificateOrChain);
2362
+ const status = await this.#checkRejectedOrTrusted(chain[0]);
2342
2363
  if (status === "rejected") {
2343
2364
  if (!(options.acceptCertificateWithValidIssuerChain && hasValidIssuer && hasTrustedIssuer)) {
2344
2365
  return "BadCertificateUntrusted" /* BadCertificateUntrusted */;
@@ -2396,17 +2417,15 @@ var CertificateManager = class _CertificateManager extends EventEmitter {
2396
2417
  * @returns the verification status code
2397
2418
  */
2398
2419
  async verifyCertificateAsync(certificate, options) {
2399
- if (!Array.isArray(certificate)) {
2420
+ const chain = coerceCertificateChain(certificate);
2421
+ for (const element of chain) {
2400
2422
  try {
2401
- const derElements = split_der(certificate);
2402
- for (const element of derElements) {
2403
- exploreCertificateInfo(element);
2404
- }
2423
+ exploreCertificateInfo(element);
2405
2424
  } catch (_err) {
2406
2425
  return "BadCertificateInvalid" /* BadCertificateInvalid */;
2407
2426
  }
2408
2427
  }
2409
- const status1 = await this.#innerVerifyCertificateAsync(certificate, false, 0, options);
2428
+ const status1 = await this.#innerVerifyCertificateAsync(chain, false, 0, options);
2410
2429
  return status1;
2411
2430
  }
2412
2431
  /**
@@ -2836,7 +2855,7 @@ var CertificateManager = class _CertificateManager extends EventEmitter {
2836
2855
  async #addTrustedCertificateFromChainImpl(certificateChain) {
2837
2856
  let certificates;
2838
2857
  try {
2839
- certificates = Array.isArray(certificateChain) ? certificateChain : split_der(certificateChain);
2858
+ certificates = coerceCertificateChain(certificateChain);
2840
2859
  } catch (_err) {
2841
2860
  return "BadCertificateInvalid" /* BadCertificateInvalid */;
2842
2861
  }
@@ -2920,7 +2939,7 @@ var CertificateManager = class _CertificateManager extends EventEmitter {
2920
2939
  return "BadCertificateInvalid" /* BadCertificateInvalid */;
2921
2940
  }
2922
2941
  }
2923
- await this.trustCertificate(leafCertificate);
2942
+ await this.trustCertificate(certificates);
2924
2943
  return "Good" /* Good */;
2925
2944
  }
2926
2945
  /**
@@ -2963,7 +2982,7 @@ var CertificateManager = class _CertificateManager extends EventEmitter {
2963
2982
  *
2964
2983
  */
2965
2984
  async findIssuerCertificate(certificate) {
2966
- const firstCertificate = Array.isArray(certificate) ? certificate[0] : certificate;
2985
+ const firstCertificate = coerceCertificateChain(certificate)[0];
2967
2986
  const certInfo = exploreCertificate2(firstCertificate);
2968
2987
  if (isSelfSigned2(certInfo)) {
2969
2988
  return firstCertificate;
@@ -3000,7 +3019,7 @@ var CertificateManager = class _CertificateManager extends EventEmitter {
3000
3019
  * @private
3001
3020
  */
3002
3021
  async #checkRejectedOrTrusted(certificate) {
3003
- const firstCertificate = Array.isArray(certificate) ? certificate[0] : certificate;
3022
+ const firstCertificate = coerceCertificateChain(certificate)[0];
3004
3023
  const fingerprint = makeFingerprint(firstCertificate);
3005
3024
  debugLog("#checkRejectedOrTrusted fingerprint ", short(fingerprint));
3006
3025
  await this.#readCertificates();
@@ -3012,12 +3031,14 @@ var CertificateManager = class _CertificateManager extends EventEmitter {
3012
3031
  }
3013
3032
  return "unknown";
3014
3033
  }
3015
- async #moveCertificate(certificate, newStatus) {
3034
+ async #moveCertificate(certificateOrChain, newStatus) {
3016
3035
  await this.withLock2(async () => {
3036
+ const chain = coerceCertificateChain(certificateOrChain);
3037
+ const certificate = chain[0];
3017
3038
  const fingerprint = makeFingerprint(certificate);
3018
3039
  let status = await this.#checkRejectedOrTrusted(certificate);
3019
3040
  if (status === "unknown") {
3020
- const pem = toPem2(certificate, "CERTIFICATE");
3041
+ const pem = toPem2(chain, "CERTIFICATE");
3021
3042
  const filename = path6.join(this.rejectedFolder, `${buildIdealCertificateName(certificate)}.pem`);
3022
3043
  await fs10.promises.writeFile(filename, pem);
3023
3044
  this.#thumbs.rejected.set(fingerprint, { certificate, filename });
@@ -3067,13 +3088,17 @@ var CertificateManager = class _CertificateManager extends EventEmitter {
3067
3088
  * found.
3068
3089
  */
3069
3090
  async isCertificateRevoked(certificate, issuerCertificate) {
3070
- const firstCertificate = Array.isArray(certificate) ? certificate[0] : certificate;
3091
+ const chain = coerceCertificateChain(certificate);
3092
+ const firstCertificate = chain[0];
3071
3093
  if (isSelfSigned3(firstCertificate)) {
3072
3094
  return "Good" /* Good */;
3073
3095
  }
3074
3096
  if (!issuerCertificate) {
3075
3097
  issuerCertificate = await this.findIssuerCertificate(firstCertificate);
3076
3098
  }
3099
+ if (!issuerCertificate) {
3100
+ issuerCertificate = findIssuerCertificateInChain(firstCertificate, chain);
3101
+ }
3077
3102
  if (!issuerCertificate) {
3078
3103
  return "BadCertificateChainIncomplete" /* BadCertificateChainIncomplete */;
3079
3104
  }
@@ -3200,7 +3225,25 @@ var CertificateManager = class _CertificateManager extends EventEmitter {
3200
3225
  try {
3201
3226
  const stat = await fs10.promises.stat(filename);
3202
3227
  if (!stat.isFile()) continue;
3203
- const certificate = (await readCertificateChainAsync(filename))[0];
3228
+ const certs = await readCertificateChainAsync(filename);
3229
+ if (certs.length === 0) continue;
3230
+ const certificate = certs[0];
3231
+ if (certs.length > 1) {
3232
+ try {
3233
+ await fs10.promises.writeFile(filename, toPem2(certs, "CERTIFICATE"), "ascii");
3234
+ } catch (writeErr) {
3235
+ debugLog(`scanCertFolder: could not rewrite legacy PEM ${filename} (read-only fs?)`, writeErr);
3236
+ }
3237
+ for (let i = 1; i < certs.length; i++) {
3238
+ if (isIssuer(certs[i])) {
3239
+ try {
3240
+ await this.addIssuer(certs[i]);
3241
+ } catch (issuerErr) {
3242
+ debugLog(`scanCertFolder: could not auto-register issuer from ${filename}`, issuerErr);
3243
+ }
3244
+ }
3245
+ }
3246
+ }
3204
3247
  const info = exploreCertificate2(certificate);
3205
3248
  const fingerprint = makeFingerprint(certificate);
3206
3249
  index.set(fingerprint, { certificate, filename, info });
@@ -3352,6 +3395,7 @@ export {
3352
3395
  VerificationStatus,
3353
3396
  adjustApplicationUri,
3354
3397
  adjustDate,
3398
+ coerceCertificateChain,
3355
3399
  convertPFXtoPEM,
3356
3400
  createPFX,
3357
3401
  dumpPFX,