node-opcua-pki 6.11.1 → 6.12.1

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
@@ -336,7 +336,7 @@ var init_simple_config_template_cnf = __esm({
336
336
  "packages/node-opcua-pki/lib/pki/templates/simple_config_template.cnf.ts"() {
337
337
  "use strict";
338
338
  init_esm_shims();
339
- 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';
339
+ 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';
340
340
  simple_config_template_cnf_default = config;
341
341
  }
342
342
  });
@@ -354,38 +354,30 @@ import {
354
354
  exploreCertificateRevocationList,
355
355
  generatePrivateKeyFile,
356
356
  makeSHA1Thumbprint,
357
- readCertificate,
358
- readCertificateAsync,
357
+ readCertificateChain,
358
+ readCertificateChainAsync,
359
359
  readCertificateRevocationList,
360
360
  split_der,
361
361
  toPem,
362
- verifyCertificateChain,
363
362
  verifyCertificateSignature
364
363
  } from "node-opcua-crypto";
365
364
  function getOrComputeInfo(entry) {
366
365
  if (!entry.info) {
367
- entry.info = exploreCertificateCached(entry.certificate);
366
+ entry.info = exploreCertificate(entry.certificate);
368
367
  }
369
368
  return entry.info;
370
369
  }
371
- function exploreCertificateCached(certificate) {
372
- const key = makeSHA1Thumbprint(certificate).toString("hex");
373
- const cached = _exploreCache.get(key);
374
- if (cached) {
375
- _exploreCache.delete(key);
376
- _exploreCache.set(key, cached);
377
- return cached;
370
+ function coerceCertificateChain(certificate) {
371
+ if (Array.isArray(certificate)) {
372
+ if (certificate.length === 0) return [];
373
+ return certificate.reduce((acc, cert) => {
374
+ return acc.concat(split_der(cert));
375
+ }, []);
378
376
  }
379
- const info = exploreCertificate(certificate);
380
- _exploreCache.set(key, info);
381
- if (_exploreCache.size > EXPLORE_CACHE_MAX) {
382
- const oldest = _exploreCache.keys().next().value;
383
- if (oldest) _exploreCache.delete(oldest);
384
- }
385
- return info;
377
+ return split_der(certificate);
386
378
  }
387
379
  function makeFingerprint(certificate) {
388
- const chain = split_der(certificate);
380
+ const chain = coerceCertificateChain(certificate);
389
381
  return makeSHA1Thumbprint(chain[0]).toString("hex");
390
382
  }
391
383
  function short(stringToShorten) {
@@ -394,7 +386,7 @@ function short(stringToShorten) {
394
386
  function buildIdealCertificateName(certificate) {
395
387
  const fingerprint2 = makeFingerprint(certificate);
396
388
  try {
397
- const commonName = exploreCertificateCached(certificate).tbsCertificate.subject.commonName || "";
389
+ const commonName = exploreCertificate(certificate).tbsCertificate.subject.commonName || "";
398
390
  const sanitizedCommonName = commonName.replace(forbiddenChars, "_");
399
391
  return `${sanitizedCommonName}[${fingerprint2}]`;
400
392
  } catch (_err) {
@@ -411,24 +403,46 @@ function isSelfSigned2(info) {
411
403
  return info.tbsCertificate.extensions?.subjectKeyIdentifier === info.tbsCertificate.extensions?.authorityKeyIdentifier?.keyIdentifier;
412
404
  }
413
405
  function isSelfSigned3(certificate) {
414
- const info = exploreCertificateCached(certificate);
406
+ const info = exploreCertificate(certificate);
415
407
  return isSelfSigned2(info);
416
408
  }
409
+ function _isIssuerInfo(info) {
410
+ const basicConstraints = info.tbsCertificate.extensions?.basicConstraints;
411
+ if (basicConstraints?.cA) {
412
+ return true;
413
+ }
414
+ const keyUsage = info.tbsCertificate.extensions?.keyUsage;
415
+ if (keyUsage?.keyCertSign) {
416
+ return true;
417
+ }
418
+ return false;
419
+ }
420
+ function isIssuer(certificate) {
421
+ try {
422
+ const info = exploreCertificate(certificate);
423
+ return _isIssuerInfo(info);
424
+ } catch (_err) {
425
+ return false;
426
+ }
427
+ }
417
428
  function findIssuerCertificateInChain(certificate, chain) {
418
- if (!certificate) {
429
+ const coercedCertificate = coerceCertificateChain(certificate);
430
+ const firstCertificate = coercedCertificate[0];
431
+ if (!firstCertificate) {
419
432
  return null;
420
433
  }
421
- const certInfo = exploreCertificateCached(certificate);
434
+ const certInfo = exploreCertificate(firstCertificate);
422
435
  if (isSelfSigned2(certInfo)) {
423
- return certificate;
436
+ return firstCertificate;
424
437
  }
425
438
  const wantedIssuerKey = certInfo.tbsCertificate.extensions?.authorityKeyIdentifier?.keyIdentifier;
426
439
  if (!wantedIssuerKey) {
427
440
  debugLog("Certificate has no extension 3");
428
441
  return null;
429
442
  }
430
- const potentialIssuers = chain.filter((c) => {
431
- const info = exploreCertificateCached(c);
443
+ const coercedChain = coerceCertificateChain(chain);
444
+ const potentialIssuers = coercedChain.filter((c) => {
445
+ const info = exploreCertificate(c);
432
446
  return info.tbsCertificate.extensions && info.tbsCertificate.extensions.subjectKeyIdentifier === wantedIssuerKey;
433
447
  });
434
448
  if (potentialIssuers.length === 1) {
@@ -440,7 +454,7 @@ function findIssuerCertificateInChain(certificate, chain) {
440
454
  }
441
455
  return null;
442
456
  }
443
- var configurationFileSimpleTemplate, fsWriteFile, EXPLORE_CACHE_MAX, _exploreCache, forbiddenChars, CertificateManager;
457
+ var configurationFileSimpleTemplate, fsWriteFile, forbiddenChars, CertificateManager;
444
458
  var init_certificate_manager = __esm({
445
459
  "packages/node-opcua-pki/lib/pki/certificate_manager.ts"() {
446
460
  "use strict";
@@ -451,8 +465,6 @@ var init_certificate_manager = __esm({
451
465
  init_simple_config_template_cnf();
452
466
  configurationFileSimpleTemplate = simple_config_template_cnf_default;
453
467
  fsWriteFile = fs4.promises.writeFile;
454
- EXPLORE_CACHE_MAX = 8;
455
- _exploreCache = /* @__PURE__ */ new Map();
456
468
  forbiddenChars = /[\x00-\x1F<>:"/\\|?*]/g;
457
469
  CertificateManager = class _CertificateManager extends EventEmitter {
458
470
  // ── Global instance registry ─────────────────────────────────
@@ -545,6 +557,7 @@ var init_certificate_manager = __esm({
545
557
  #readCertificatesCalled = false;
546
558
  #filenameToHash = /* @__PURE__ */ new Map();
547
559
  #initializingPromise;
560
+ #addCertValidation;
548
561
  #thumbs = {
549
562
  rejected: /* @__PURE__ */ new Map(),
550
563
  trusted: /* @__PURE__ */ new Map(),
@@ -571,6 +584,13 @@ var init_certificate_manager = __esm({
571
584
  }
572
585
  this.#location = makePath(options.location, "");
573
586
  this.keySize = options.keySize;
587
+ const v = options.addCertificateValidationOptions ?? {};
588
+ this.#addCertValidation = {
589
+ acceptExpiredCertificate: v.acceptExpiredCertificate ?? false,
590
+ acceptRevokedCertificate: v.acceptRevokedCertificate ?? false,
591
+ ignoreMissingRevocationList: v.ignoreMissingRevocationList ?? false,
592
+ maxChainLength: v.maxChainLength ?? 5
593
+ };
574
594
  mkdirRecursiveSync(options.location);
575
595
  if (!fs4.existsSync(this.#location)) {
576
596
  throw new Error(`CertificateManager cannot access location ${this.#location}`);
@@ -661,7 +681,12 @@ var init_certificate_manager = __esm({
661
681
  * or `"BadCertificateInvalid"` if the certificate cannot be parsed.
662
682
  */
663
683
  async isCertificateTrusted(certificate) {
664
- const fingerprint2 = makeFingerprint(certificate);
684
+ let fingerprint2;
685
+ try {
686
+ fingerprint2 = makeFingerprint(certificate);
687
+ } catch (_err) {
688
+ return "BadCertificateInvalid";
689
+ }
665
690
  if (this.#thumbs.trusted.has(fingerprint2)) {
666
691
  return "Good";
667
692
  }
@@ -681,13 +706,13 @@ var init_certificate_manager = __esm({
681
706
  }
682
707
  return "BadCertificateUntrusted";
683
708
  }
684
- async #innerVerifyCertificateAsync(certificate, _isIssuer, level, options) {
709
+ async #innerVerifyCertificateAsync(certificateOrChain, _isIssuer, level, options) {
685
710
  if (level >= 5) {
686
711
  return "BadSecurityChecksFailed" /* BadSecurityChecksFailed */;
687
712
  }
688
- const chain = split_der(certificate);
713
+ const chain = coerceCertificateChain(certificateOrChain);
689
714
  debugLog("NB CERTIFICATE IN CHAIN = ", chain.length);
690
- const info = exploreCertificateCached(chain[0]);
715
+ const info = exploreCertificate(chain[0]);
691
716
  let hasValidIssuer = false;
692
717
  let hasTrustedIssuer = false;
693
718
  const hasIssuerKey = info.tbsCertificate.extensions?.authorityKeyIdentifier?.keyIdentifier;
@@ -724,7 +749,7 @@ var init_certificate_manager = __esm({
724
749
  return "BadCertificateIssuerRevocationUnknown" /* BadCertificateIssuerRevocationUnknown */;
725
750
  }
726
751
  if (issuerStatus === "BadCertificateTimeInvalid" /* BadCertificateTimeInvalid */) {
727
- if (!options || !options.acceptOutDatedIssuerCertificate) {
752
+ if (!options?.acceptOutDatedIssuerCertificate) {
728
753
  return "BadCertificateIssuerTimeInvalid" /* BadCertificateIssuerTimeInvalid */;
729
754
  }
730
755
  }
@@ -734,13 +759,13 @@ var init_certificate_manager = __esm({
734
759
  if (issuerStatus !== "Good" /* Good */ && issuerStatus !== "BadCertificateUntrusted" /* BadCertificateUntrusted */) {
735
760
  return "BadSecurityChecksFailed" /* BadSecurityChecksFailed */;
736
761
  }
737
- const isCertificateSignatureOK = verifyCertificateSignature(certificate, issuerCertificate);
762
+ const isCertificateSignatureOK = verifyCertificateSignature(chain[0], issuerCertificate);
738
763
  if (!isCertificateSignatureOK) {
739
764
  debugLog(" the certificate was not signed by the issuer as it claim to be ! Danger");
740
765
  return "BadSecurityChecksFailed" /* BadSecurityChecksFailed */;
741
766
  }
742
767
  hasValidIssuer = true;
743
- let revokedStatus = await this.isCertificateRevoked(certificate);
768
+ let revokedStatus = await this.isCertificateRevoked(chain, issuerCertificate);
744
769
  if (revokedStatus === "BadCertificateRevocationUnknown" /* BadCertificateRevocationUnknown */) {
745
770
  if (options?.ignoreMissingRevocationList) {
746
771
  revokedStatus = "Good" /* Good */;
@@ -760,16 +785,16 @@ var init_certificate_manager = __esm({
760
785
  return "BadSecurityChecksFailed" /* BadSecurityChecksFailed */;
761
786
  }
762
787
  } else {
763
- const isCertificateSignatureOK = verifyCertificateSignature(certificate, certificate);
788
+ const isCertificateSignatureOK = verifyCertificateSignature(chain[0], chain[0]);
764
789
  if (!isCertificateSignatureOK) {
765
790
  debugLog("Self-signed Certificate signature is not valid");
766
791
  return "BadSecurityChecksFailed" /* BadSecurityChecksFailed */;
767
792
  }
768
- const revokedStatus = await this.isCertificateRevoked(certificate);
793
+ const revokedStatus = await this.isCertificateRevoked(chain);
769
794
  debugLog("revokedStatus of self signed certificate:", revokedStatus);
770
795
  }
771
796
  }
772
- const status = await this.#checkRejectedOrTrusted(certificate);
797
+ const status = await this.#checkRejectedOrTrusted(chain[0]);
773
798
  if (status === "rejected") {
774
799
  if (!(options.acceptCertificateWithValidIssuerChain && hasValidIssuer && hasTrustedIssuer)) {
775
800
  return "BadCertificateUntrusted" /* BadCertificateUntrusted */;
@@ -777,7 +802,7 @@ var init_certificate_manager = __esm({
777
802
  }
778
803
  const _c2 = chain[1] ? exploreCertificateInfo(chain[1]) : "non";
779
804
  debugLog("chain[1] info=", _c2);
780
- const certificateInfo = exploreCertificateInfo(certificate);
805
+ const certificateInfo = exploreCertificateInfo(chain[0]);
781
806
  const now = /* @__PURE__ */ new Date();
782
807
  let isTimeInvalid = false;
783
808
  if (certificateInfo.notBefore.getTime() > now.getTime()) {
@@ -827,7 +852,15 @@ var init_certificate_manager = __esm({
827
852
  * @returns the verification status code
828
853
  */
829
854
  async verifyCertificateAsync(certificate, options) {
830
- const status1 = await this.#innerVerifyCertificateAsync(certificate, false, 0, options);
855
+ const chain = coerceCertificateChain(certificate);
856
+ for (const element of chain) {
857
+ try {
858
+ exploreCertificateInfo(element);
859
+ } catch (_err) {
860
+ return "BadCertificateInvalid" /* BadCertificateInvalid */;
861
+ }
862
+ }
863
+ const status1 = await this.#innerVerifyCertificateAsync(chain, false, 0, options);
831
864
  return status1;
832
865
  }
833
866
  /**
@@ -1054,6 +1087,23 @@ var init_certificate_manager = __esm({
1054
1087
  }
1055
1088
  return "Good" /* Good */;
1056
1089
  }
1090
+ /**
1091
+ * Add multiple CA (issuer) certificates to the issuers store.
1092
+ * @param certificates - the DER-encoded CA certificates
1093
+ * @param validate - if `true`, verify each certificate before adding
1094
+ * @param addInTrustList - if `true`, also add each certificate to the trusted store
1095
+ * @returns `VerificationStatus.Good` on success
1096
+ */
1097
+ async addIssuers(certificates, validate = false, addInTrustList = false) {
1098
+ for (const certificate of certificates) {
1099
+ if (!isIssuer(certificate)) {
1100
+ warningLog(`Certificate ${makeFingerprint(certificate)} is not a issuer certificate`);
1101
+ continue;
1102
+ }
1103
+ await this.addIssuer(certificate, validate, addInTrustList);
1104
+ }
1105
+ return "Good" /* Good */;
1106
+ }
1057
1107
  /**
1058
1108
  * Add a CRL to the certificate manager.
1059
1109
  * @param crl - the CRL to add
@@ -1177,7 +1227,7 @@ var init_certificate_manager = __esm({
1177
1227
  * @param target - "issuers", "trusted", or "all" (default "all")
1178
1228
  */
1179
1229
  async removeRevocationListsForIssuer(issuerCertificate, target = "all") {
1180
- const issuerInfo = exploreCertificateCached(issuerCertificate);
1230
+ const issuerInfo = exploreCertificate(issuerCertificate);
1181
1231
  const issuerFingerprint = issuerInfo.tbsCertificate.subjectFingerPrint;
1182
1232
  const processIndex = async (index) => {
1183
1233
  const crlData = index.get(issuerFingerprint);
@@ -1204,41 +1254,125 @@ var init_certificate_manager = __esm({
1204
1254
  * Validate a certificate (optionally with its chain) and add
1205
1255
  * the leaf certificate to the trusted store.
1206
1256
  *
1207
- * The certificate buffer may contain a single certificate or a
1208
- * full chain (leaf + issuer certificates concatenated in DER).
1209
- * Only the leaf certificate is added to the trusted store.
1257
+ * Performs OPC UA Part 4, Table 100 validation:
1210
1258
  *
1211
- * When the chain contains issuer certificates, this method
1212
- * verifies that each issuer is already registered via
1213
- * {@link addIssuer} before trusting the leaf.
1259
+ * 1. **Certificate Structure** parse the DER encoding.
1260
+ * 2. **Build Certificate Chain** walk from the leaf to a
1261
+ * self-signed root CA, using the provided chain and the
1262
+ * issuers store.
1263
+ * 3. **Signature** — verify each certificate's signature
1264
+ * against its issuer.
1265
+ * 4. **Issuer Presence** — every issuer in the chain must
1266
+ * already be registered in the issuers store (per GDS
1267
+ * 7.8.2.6).
1268
+ * 5. **Validity Period** — each certificate must be within
1269
+ * its validity window (overridable via
1270
+ * {@link AddCertificateValidationOptions.acceptExpiredCertificate}).
1271
+ * 6. **Revocation Check** — each certificate is checked
1272
+ * against its issuer's CRL (overridable via
1273
+ * {@link AddCertificateValidationOptions.acceptRevokedCertificate}
1274
+ * and {@link AddCertificateValidationOptions.ignoreMissingRevocationList}).
1214
1275
  *
1215
- * If one of the certificates in the chain is not registered in the issuers store,
1216
- * the leaf certificate will be rejected.
1276
+ * Only the leaf certificate is added to the trusted store.
1217
1277
  *
1218
1278
  * @param certificateChain - DER-encoded certificate or chain
1219
1279
  * @returns `VerificationStatus.Good` on success, or an error
1220
1280
  * status indicating why the certificate was rejected.
1221
1281
  */
1222
1282
  async addTrustedCertificateFromChain(certificateChain) {
1223
- const certificates = split_der(certificateChain);
1224
- const leafCertificate = certificates[0];
1225
1283
  try {
1226
- exploreCertificateCached(leafCertificate);
1284
+ return await this.#addTrustedCertificateFromChainImpl(certificateChain);
1227
1285
  } catch (_err) {
1286
+ warningLog("addTrustedCertificateFromChain: unexpected error", _err);
1228
1287
  return "BadCertificateInvalid" /* BadCertificateInvalid */;
1229
1288
  }
1230
- const result = await verifyCertificateChain([leafCertificate]);
1231
- if (result.status !== "Good") {
1289
+ }
1290
+ async #addTrustedCertificateFromChainImpl(certificateChain) {
1291
+ let certificates;
1292
+ try {
1293
+ certificates = coerceCertificateChain(certificateChain);
1294
+ } catch (_err) {
1232
1295
  return "BadCertificateInvalid" /* BadCertificateInvalid */;
1233
1296
  }
1234
- if (certificates.length > 1) {
1235
- await this.#scanCertFolder(this.issuersCertFolder, this.#thumbs.issuers.certs);
1236
- for (const issuerCert of certificates.slice(1)) {
1237
- const thumbprint = makeFingerprint(issuerCert);
1238
- if (!await this.hasIssuer(thumbprint)) {
1297
+ if (certificates.length === 0) {
1298
+ return "BadCertificateInvalid" /* BadCertificateInvalid */;
1299
+ }
1300
+ const leafCertificate = certificates[0];
1301
+ const opts = this.#addCertValidation;
1302
+ let leafInfo;
1303
+ try {
1304
+ leafInfo = exploreCertificate(leafCertificate);
1305
+ } catch (_err) {
1306
+ return "BadCertificateInvalid" /* BadCertificateInvalid */;
1307
+ }
1308
+ await this.#scanCertFolder(this.issuersCertFolder, this.#thumbs.issuers.certs);
1309
+ let currentCert = leafCertificate;
1310
+ let currentInfo = leafInfo;
1311
+ let depth = 0;
1312
+ while (true) {
1313
+ depth++;
1314
+ if (depth > opts.maxChainLength) {
1315
+ return "BadSecurityChecksFailed" /* BadSecurityChecksFailed */;
1316
+ }
1317
+ if (!opts.acceptExpiredCertificate) {
1318
+ let certDetails;
1319
+ try {
1320
+ certDetails = exploreCertificateInfo(currentCert);
1321
+ } catch (_err) {
1322
+ return "BadCertificateInvalid" /* BadCertificateInvalid */;
1323
+ }
1324
+ const now = /* @__PURE__ */ new Date();
1325
+ if (certDetails.notBefore.getTime() > now.getTime()) {
1326
+ return "BadCertificateTimeInvalid" /* BadCertificateTimeInvalid */;
1327
+ }
1328
+ if (certDetails.notAfter.getTime() <= now.getTime()) {
1329
+ return depth === 1 ? "BadCertificateTimeInvalid" /* BadCertificateTimeInvalid */ : "BadCertificateIssuerTimeInvalid" /* BadCertificateIssuerTimeInvalid */;
1330
+ }
1331
+ }
1332
+ if (isSelfSigned2(currentInfo)) {
1333
+ try {
1334
+ if (!verifyCertificateSignature(currentCert, currentCert)) {
1335
+ return "BadCertificateInvalid" /* BadCertificateInvalid */;
1336
+ }
1337
+ } catch (_err) {
1338
+ return "BadCertificateInvalid" /* BadCertificateInvalid */;
1339
+ }
1340
+ break;
1341
+ }
1342
+ let issuerCert = await this.findIssuerCertificate(currentCert);
1343
+ if (!issuerCert) {
1344
+ issuerCert = findIssuerCertificateInChain(currentCert, certificates);
1345
+ if (!issuerCert || issuerCert === currentCert) {
1239
1346
  return "BadCertificateChainIncomplete" /* BadCertificateChainIncomplete */;
1240
1347
  }
1241
1348
  }
1349
+ try {
1350
+ if (!verifyCertificateSignature(currentCert, issuerCert)) {
1351
+ return "BadCertificateInvalid" /* BadCertificateInvalid */;
1352
+ }
1353
+ } catch (_err) {
1354
+ return "BadCertificateInvalid" /* BadCertificateInvalid */;
1355
+ }
1356
+ const issuerThumbprint = makeFingerprint(issuerCert);
1357
+ if (!await this.hasIssuer(issuerThumbprint)) {
1358
+ return "BadCertificateChainIncomplete" /* BadCertificateChainIncomplete */;
1359
+ }
1360
+ const revokedStatus = await this.isCertificateRevoked(currentCert, issuerCert);
1361
+ if (revokedStatus === "BadCertificateRevoked" /* BadCertificateRevoked */) {
1362
+ if (!opts.acceptRevokedCertificate) {
1363
+ return "BadCertificateRevoked" /* BadCertificateRevoked */;
1364
+ }
1365
+ } else if (revokedStatus === "BadCertificateRevocationUnknown" /* BadCertificateRevocationUnknown */) {
1366
+ if (!opts.ignoreMissingRevocationList) {
1367
+ return "BadCertificateRevocationUnknown" /* BadCertificateRevocationUnknown */;
1368
+ }
1369
+ }
1370
+ currentCert = issuerCert;
1371
+ try {
1372
+ currentInfo = exploreCertificate(currentCert);
1373
+ } catch (_err) {
1374
+ return "BadCertificateInvalid" /* BadCertificateInvalid */;
1375
+ }
1242
1376
  }
1243
1377
  await this.trustCertificate(leafCertificate);
1244
1378
  return "Good" /* Good */;
@@ -1283,9 +1417,10 @@ var init_certificate_manager = __esm({
1283
1417
  *
1284
1418
  */
1285
1419
  async findIssuerCertificate(certificate) {
1286
- const certInfo = exploreCertificateCached(certificate);
1420
+ const firstCertificate = coerceCertificateChain(certificate)[0];
1421
+ const certInfo = exploreCertificate(firstCertificate);
1287
1422
  if (isSelfSigned2(certInfo)) {
1288
- return certificate;
1423
+ return firstCertificate;
1289
1424
  }
1290
1425
  const wantedIssuerKey = certInfo.tbsCertificate.extensions?.authorityKeyIdentifier?.keyIdentifier;
1291
1426
  if (!wantedIssuerKey) {
@@ -1319,7 +1454,8 @@ var init_certificate_manager = __esm({
1319
1454
  * @private
1320
1455
  */
1321
1456
  async #checkRejectedOrTrusted(certificate) {
1322
- const fingerprint2 = makeFingerprint(certificate);
1457
+ const firstCertificate = coerceCertificateChain(certificate)[0];
1458
+ const fingerprint2 = makeFingerprint(firstCertificate);
1323
1459
  debugLog("#checkRejectedOrTrusted fingerprint ", short(fingerprint2));
1324
1460
  await this.#readCertificates();
1325
1461
  if (this.#thumbs.rejected.has(fingerprint2)) {
@@ -1364,7 +1500,7 @@ var init_certificate_manager = __esm({
1364
1500
  });
1365
1501
  }
1366
1502
  #findAssociatedCRLs(issuerCertificate) {
1367
- const issuerCertificateInfo = exploreCertificateCached(issuerCertificate);
1503
+ const issuerCertificateInfo = exploreCertificate(issuerCertificate);
1368
1504
  const key = issuerCertificateInfo.tbsCertificate.subjectFingerPrint;
1369
1505
  return this.#thumbs.issuersCrl.get(key) ?? this.#thumbs.crl.get(key) ?? null;
1370
1506
  }
@@ -1385,11 +1521,16 @@ var init_certificate_manager = __esm({
1385
1521
  * found.
1386
1522
  */
1387
1523
  async isCertificateRevoked(certificate, issuerCertificate) {
1388
- if (isSelfSigned3(certificate)) {
1524
+ const chain = coerceCertificateChain(certificate);
1525
+ const firstCertificate = chain[0];
1526
+ if (isSelfSigned3(firstCertificate)) {
1389
1527
  return "Good" /* Good */;
1390
1528
  }
1391
1529
  if (!issuerCertificate) {
1392
- issuerCertificate = await this.findIssuerCertificate(certificate);
1530
+ issuerCertificate = await this.findIssuerCertificate(firstCertificate);
1531
+ }
1532
+ if (!issuerCertificate) {
1533
+ issuerCertificate = findIssuerCertificateInChain(firstCertificate, chain);
1393
1534
  }
1394
1535
  if (!issuerCertificate) {
1395
1536
  return "BadCertificateChainIncomplete" /* BadCertificateChainIncomplete */;
@@ -1398,7 +1539,7 @@ var init_certificate_manager = __esm({
1398
1539
  if (!crls) {
1399
1540
  return "BadCertificateRevocationUnknown" /* BadCertificateRevocationUnknown */;
1400
1541
  }
1401
- const certInfo = exploreCertificateCached(certificate);
1542
+ const certInfo = exploreCertificate(firstCertificate);
1402
1543
  const serialNumber = certInfo.tbsCertificate.serialNumber || certInfo.tbsCertificate.extensions?.authorityKeyIdentifier?.serial || "";
1403
1544
  const key = certInfo.tbsCertificate.extensions?.authorityKeyIdentifier?.authorityCertIssuerFingerPrint || "<unknown>";
1404
1545
  const crl2 = this.#thumbs.crl.get(key) ?? null;
@@ -1517,8 +1658,8 @@ var init_certificate_manager = __esm({
1517
1658
  try {
1518
1659
  const stat = await fs4.promises.stat(filename);
1519
1660
  if (!stat.isFile()) continue;
1520
- const certificate = await readCertificateAsync(filename);
1521
- const info = exploreCertificateCached(certificate);
1661
+ const certificate = (await readCertificateChainAsync(filename))[0];
1662
+ const info = exploreCertificate(certificate);
1522
1663
  const fingerprint2 = makeFingerprint(certificate);
1523
1664
  index.set(fingerprint2, { certificate, filename, info });
1524
1665
  this.#filenameToHash.set(filename, fingerprint2);
@@ -1604,8 +1745,8 @@ var init_certificate_manager = __esm({
1604
1745
  w.on("add", (filename) => {
1605
1746
  debugLog(chalk3.cyan(`add in folder ${folder}`), filename);
1606
1747
  try {
1607
- const certificate = readCertificate(filename);
1608
- const info = exploreCertificateCached(certificate);
1748
+ const certificate = readCertificateChain(filename)[0];
1749
+ const info = exploreCertificate(certificate);
1609
1750
  const fingerprint2 = makeFingerprint(certificate);
1610
1751
  const isNew = !index.has(fingerprint2);
1611
1752
  index.set(fingerprint2, { certificate, filename, info });
@@ -1627,13 +1768,13 @@ var init_certificate_manager = __esm({
1627
1768
  w.on("change", (changedPath) => {
1628
1769
  debugLog(chalk3.cyan(`change in folder ${folder}`), changedPath);
1629
1770
  try {
1630
- const certificate = readCertificate(changedPath);
1771
+ const certificate = readCertificateChain(changedPath)[0];
1631
1772
  const newFingerprint = makeFingerprint(certificate);
1632
1773
  const oldHash = this.#filenameToHash.get(changedPath);
1633
1774
  if (oldHash && oldHash !== newFingerprint) {
1634
1775
  index.delete(oldHash);
1635
1776
  }
1636
- index.set(newFingerprint, { certificate, filename: changedPath, info: exploreCertificateCached(certificate) });
1777
+ index.set(newFingerprint, { certificate, filename: changedPath, info: exploreCertificate(certificate) });
1637
1778
  this.#filenameToHash.set(changedPath, newFingerprint);
1638
1779
  this.emit("certificateChange", { store, certificate, fingerprint: newFingerprint, filename: changedPath });
1639
1780
  } catch (err) {
@@ -2416,7 +2557,7 @@ nsComment = ''OpenSSL Generated Certificate''
2416
2557
  #nsRenewalUrl =
2417
2558
  #nsCaPolicyUrl =
2418
2559
  #nsSslServerName =
2419
- keyUsage = critical, digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment, keyAgreement, keyCertSign
2560
+ keyUsage = critical, digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment, keyAgreement
2420
2561
  extendedKeyUsage = critical,serverAuth ,clientAuth
2421
2562
 
2422
2563
  [ v3_req ]