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.d.mts CHANGED
@@ -771,6 +771,7 @@ declare enum VerificationStatus {
771
771
  /** Validation OK. */
772
772
  Good = "Good"
773
773
  }
774
+ declare function coerceCertificateChain(certificate: Certificate | Certificate[]): Certificate[];
774
775
  declare function makeFingerprint(certificate: Certificate | Certificate[] | CertificateRevocationList): string;
775
776
  /**
776
777
  * Check if the provided certificate acts as an issuer (CA)
@@ -918,15 +919,15 @@ declare class CertificateManager extends EventEmitter {
918
919
  /**
919
920
  * Move a certificate to the rejected store.
920
921
  * If the certificate was previously trusted, it will be removed from the trusted folder.
921
- * @param certificate - the DER-encoded certificate
922
+ * @param certificateOrChain - the DER-encoded certificate or certificate chain
922
923
  */
923
- rejectCertificate(certificate: Certificate): Promise<void>;
924
+ rejectCertificate(certificateOrChain: Certificate | Certificate[]): Promise<void>;
924
925
  /**
925
926
  * Move a certificate to the trusted store.
926
927
  * If the certificate was previously rejected, it will be removed from the rejected folder.
927
- * @param certificate - the DER-encoded certificate
928
+ * @param certificateOrChain - the DER-encoded certificate or certificate chain
928
929
  */
929
- trustCertificate(certificate: Certificate): Promise<void>;
930
+ trustCertificate(certificateOrChain: Certificate | Certificate[]): Promise<void>;
930
931
  /**
931
932
  * Check whether the trusted certificate store is empty.
932
933
  *
@@ -961,7 +962,7 @@ declare class CertificateManager extends EventEmitter {
961
962
  * @returns `"Good"` if trusted, `"BadCertificateUntrusted"` if rejected/unknown,
962
963
  * or `"BadCertificateInvalid"` if the certificate cannot be parsed.
963
964
  */
964
- isCertificateTrusted(certificate: Certificate): Promise<"Good" | "BadCertificateUntrusted" | "BadCertificateInvalid">;
965
+ isCertificateTrusted(certificateOrCertificateChain: Certificate | Certificate[]): Promise<"Good" | "BadCertificateUntrusted" | "BadCertificateInvalid">;
965
966
  /**
966
967
  * Internal verification hook called by {@link verifyCertificate}.
967
968
  *
@@ -1304,4 +1305,4 @@ declare function dumpPFX(pfxFile: Filename, passphrase?: string): Promise<string
1304
1305
  */
1305
1306
  declare function install_prerequisite(): Promise<string>;
1306
1307
 
1307
- export { type AddCertificateValidationOptions, CertificateAuthority, type CertificateAuthorityOptions, CertificateManager, type CertificateManagerEvents, type CertificateManagerOptions, CertificateManagerState, type CertificateStatus, type CertificateStore, type CreateCertificateSigningRequestOptions, type CreateCertificateSigningRequestWithConfigOptions, type CreatePFXOptions, type CreateSelfSignCertificateParam, type CreateSelfSignCertificateParam1, type CreateSelfSignCertificateWithConfigParam, type CrlStore, type ExtractPFXOptions, type ExtractPFXResult, type Filename, type GenerateKeyPairAndSignOptions, type GenerateKeyPairAndSignPFXOptions, type InitializeCSRResult, type InstallCACertificateResult, type KeyLength, type KeySize, type Params, type ProcessAltNamesParam, type SignCertificateOptions, type StartDateEndDateParam, type Thumbprint, VerificationStatus, type VerifyCertificateOptions, adjustApplicationUri, adjustDate, convertPFXtoPEM, createPFX, dumpPFX, extractAllFromPFX, extractCACertificatesFromPFX, extractCertificateFromPFX, extractPrivateKeyFromPFX, findIssuerCertificateInChain, install_prerequisite, isIntermediateIssuer, isIssuer, isRootIssuer, makeFingerprint, quote };
1308
+ export { type AddCertificateValidationOptions, CertificateAuthority, type CertificateAuthorityOptions, CertificateManager, type CertificateManagerEvents, type CertificateManagerOptions, CertificateManagerState, type CertificateStatus, type CertificateStore, type CreateCertificateSigningRequestOptions, type CreateCertificateSigningRequestWithConfigOptions, type CreatePFXOptions, type CreateSelfSignCertificateParam, type CreateSelfSignCertificateParam1, type CreateSelfSignCertificateWithConfigParam, type CrlStore, type ExtractPFXOptions, type ExtractPFXResult, type Filename, type GenerateKeyPairAndSignOptions, type GenerateKeyPairAndSignPFXOptions, type InitializeCSRResult, type InstallCACertificateResult, type KeyLength, type KeySize, type Params, type ProcessAltNamesParam, type SignCertificateOptions, type StartDateEndDateParam, type Thumbprint, VerificationStatus, type VerifyCertificateOptions, adjustApplicationUri, adjustDate, coerceCertificateChain, convertPFXtoPEM, createPFX, dumpPFX, extractAllFromPFX, extractCACertificatesFromPFX, extractCertificateFromPFX, extractPrivateKeyFromPFX, findIssuerCertificateInChain, install_prerequisite, isIntermediateIssuer, isIssuer, isRootIssuer, makeFingerprint, quote };
package/dist/index.d.ts CHANGED
@@ -771,6 +771,7 @@ declare enum VerificationStatus {
771
771
  /** Validation OK. */
772
772
  Good = "Good"
773
773
  }
774
+ declare function coerceCertificateChain(certificate: Certificate | Certificate[]): Certificate[];
774
775
  declare function makeFingerprint(certificate: Certificate | Certificate[] | CertificateRevocationList): string;
775
776
  /**
776
777
  * Check if the provided certificate acts as an issuer (CA)
@@ -918,15 +919,15 @@ declare class CertificateManager extends EventEmitter {
918
919
  /**
919
920
  * Move a certificate to the rejected store.
920
921
  * If the certificate was previously trusted, it will be removed from the trusted folder.
921
- * @param certificate - the DER-encoded certificate
922
+ * @param certificateOrChain - the DER-encoded certificate or certificate chain
922
923
  */
923
- rejectCertificate(certificate: Certificate): Promise<void>;
924
+ rejectCertificate(certificateOrChain: Certificate | Certificate[]): Promise<void>;
924
925
  /**
925
926
  * Move a certificate to the trusted store.
926
927
  * If the certificate was previously rejected, it will be removed from the rejected folder.
927
- * @param certificate - the DER-encoded certificate
928
+ * @param certificateOrChain - the DER-encoded certificate or certificate chain
928
929
  */
929
- trustCertificate(certificate: Certificate): Promise<void>;
930
+ trustCertificate(certificateOrChain: Certificate | Certificate[]): Promise<void>;
930
931
  /**
931
932
  * Check whether the trusted certificate store is empty.
932
933
  *
@@ -961,7 +962,7 @@ declare class CertificateManager extends EventEmitter {
961
962
  * @returns `"Good"` if trusted, `"BadCertificateUntrusted"` if rejected/unknown,
962
963
  * or `"BadCertificateInvalid"` if the certificate cannot be parsed.
963
964
  */
964
- isCertificateTrusted(certificate: Certificate): Promise<"Good" | "BadCertificateUntrusted" | "BadCertificateInvalid">;
965
+ isCertificateTrusted(certificateOrCertificateChain: Certificate | Certificate[]): Promise<"Good" | "BadCertificateUntrusted" | "BadCertificateInvalid">;
965
966
  /**
966
967
  * Internal verification hook called by {@link verifyCertificate}.
967
968
  *
@@ -1304,4 +1305,4 @@ declare function dumpPFX(pfxFile: Filename, passphrase?: string): Promise<string
1304
1305
  */
1305
1306
  declare function install_prerequisite(): Promise<string>;
1306
1307
 
1307
- export { type AddCertificateValidationOptions, CertificateAuthority, type CertificateAuthorityOptions, CertificateManager, type CertificateManagerEvents, type CertificateManagerOptions, CertificateManagerState, type CertificateStatus, type CertificateStore, type CreateCertificateSigningRequestOptions, type CreateCertificateSigningRequestWithConfigOptions, type CreatePFXOptions, type CreateSelfSignCertificateParam, type CreateSelfSignCertificateParam1, type CreateSelfSignCertificateWithConfigParam, type CrlStore, type ExtractPFXOptions, type ExtractPFXResult, type Filename, type GenerateKeyPairAndSignOptions, type GenerateKeyPairAndSignPFXOptions, type InitializeCSRResult, type InstallCACertificateResult, type KeyLength, type KeySize, type Params, type ProcessAltNamesParam, type SignCertificateOptions, type StartDateEndDateParam, type Thumbprint, VerificationStatus, type VerifyCertificateOptions, adjustApplicationUri, adjustDate, convertPFXtoPEM, createPFX, dumpPFX, extractAllFromPFX, extractCACertificatesFromPFX, extractCertificateFromPFX, extractPrivateKeyFromPFX, findIssuerCertificateInChain, install_prerequisite, isIntermediateIssuer, isIssuer, isRootIssuer, makeFingerprint, quote };
1308
+ export { type AddCertificateValidationOptions, CertificateAuthority, type CertificateAuthorityOptions, CertificateManager, type CertificateManagerEvents, type CertificateManagerOptions, CertificateManagerState, type CertificateStatus, type CertificateStore, type CreateCertificateSigningRequestOptions, type CreateCertificateSigningRequestWithConfigOptions, type CreatePFXOptions, type CreateSelfSignCertificateParam, type CreateSelfSignCertificateParam1, type CreateSelfSignCertificateWithConfigParam, type CrlStore, type ExtractPFXOptions, type ExtractPFXResult, type Filename, type GenerateKeyPairAndSignOptions, type GenerateKeyPairAndSignPFXOptions, type InitializeCSRResult, type InstallCACertificateResult, type KeyLength, type KeySize, type Params, type ProcessAltNamesParam, type SignCertificateOptions, type StartDateEndDateParam, type Thumbprint, VerificationStatus, type VerifyCertificateOptions, adjustApplicationUri, adjustDate, coerceCertificateChain, convertPFXtoPEM, createPFX, dumpPFX, extractAllFromPFX, extractCACertificatesFromPFX, extractCertificateFromPFX, extractPrivateKeyFromPFX, findIssuerCertificateInChain, install_prerequisite, isIntermediateIssuer, isIssuer, isRootIssuer, makeFingerprint, quote };
package/dist/index.js CHANGED
@@ -37,6 +37,7 @@ __export(lib_exports, {
37
37
  VerificationStatus: () => VerificationStatus,
38
38
  adjustApplicationUri: () => adjustApplicationUri,
39
39
  adjustDate: () => adjustDate,
40
+ coerceCertificateChain: () => coerceCertificateChain,
40
41
  convertPFXtoPEM: () => convertPFXtoPEM,
41
42
  createPFX: () => createPFX,
42
43
  dumpPFX: () => dumpPFX,
@@ -744,7 +745,7 @@ async function createCertificateSigningRequestWithOpenSSL(certificateSigningRequ
744
745
  }
745
746
 
746
747
  // packages/node-opcua-pki/lib/pki/templates/simple_config_template.cnf.ts
747
- 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';
748
+ 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';
748
749
  var simple_config_template_cnf_default = config;
749
750
 
750
751
  // packages/node-opcua-pki/lib/ca/templates/ca_config_template.cnf.ts
@@ -839,7 +840,7 @@ nsComment = ''OpenSSL Generated Certificate''
839
840
  #nsRenewalUrl =
840
841
  #nsCaPolicyUrl =
841
842
  #nsSslServerName =
842
- keyUsage = critical, digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment, keyAgreement, keyCertSign
843
+ keyUsage = critical, digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment, keyAgreement
843
844
  extendedKeyUsage = critical,serverAuth ,clientAuth
844
845
 
845
846
  [ v3_req ]
@@ -1930,8 +1931,17 @@ var VerificationStatus = /* @__PURE__ */ ((VerificationStatus2) => {
1930
1931
  VerificationStatus2["Good"] = "Good";
1931
1932
  return VerificationStatus2;
1932
1933
  })(VerificationStatus || {});
1934
+ function coerceCertificateChain(certificate) {
1935
+ if (Array.isArray(certificate)) {
1936
+ if (certificate.length === 0) return [];
1937
+ return certificate.reduce((acc, cert) => {
1938
+ return acc.concat((0, import_node_opcua_crypto5.split_der)(cert));
1939
+ }, []);
1940
+ }
1941
+ return (0, import_node_opcua_crypto5.split_der)(certificate);
1942
+ }
1933
1943
  function makeFingerprint(certificate) {
1934
- const chain = Array.isArray(certificate) ? certificate : (0, import_node_opcua_crypto5.split_der)(certificate);
1944
+ const chain = coerceCertificateChain(certificate);
1935
1945
  return (0, import_node_opcua_crypto5.makeSHA1Thumbprint)(chain[0]).toString("hex");
1936
1946
  }
1937
1947
  function short(stringToShorten) {
@@ -1939,9 +1949,10 @@ function short(stringToShorten) {
1939
1949
  }
1940
1950
  var forbiddenChars = /[\x00-\x1F<>:"/\\|?*]/g;
1941
1951
  function buildIdealCertificateName(certificate) {
1942
- const fingerprint = makeFingerprint(certificate);
1952
+ const chain = coerceCertificateChain(certificate);
1953
+ const fingerprint = makeFingerprint(chain);
1943
1954
  try {
1944
- const commonName = (0, import_node_opcua_crypto5.exploreCertificate)(certificate).tbsCertificate.subject.commonName || "";
1955
+ const commonName = (0, import_node_opcua_crypto5.exploreCertificate)(chain[0]).tbsCertificate.subject.commonName || "";
1945
1956
  const sanitizedCommonName = commonName.replace(forbiddenChars, "_");
1946
1957
  return `${sanitizedCommonName}[${fingerprint}]`;
1947
1958
  } catch (_err) {
@@ -2003,7 +2014,8 @@ function isRootIssuer(certificate) {
2003
2014
  }
2004
2015
  }
2005
2016
  function findIssuerCertificateInChain(certificate, chain) {
2006
- const firstCertificate = Array.isArray(certificate) ? certificate[0] : certificate;
2017
+ const coercedCertificate = coerceCertificateChain(certificate);
2018
+ const firstCertificate = coercedCertificate[0];
2007
2019
  if (!firstCertificate) {
2008
2020
  return null;
2009
2021
  }
@@ -2016,7 +2028,8 @@ function findIssuerCertificateInChain(certificate, chain) {
2016
2028
  debugLog("Certificate has no extension 3");
2017
2029
  return null;
2018
2030
  }
2019
- const potentialIssuers = chain.filter((c) => {
2031
+ const coercedChain = coerceCertificateChain(chain);
2032
+ const potentialIssuers = coercedChain.filter((c) => {
2020
2033
  const info = (0, import_node_opcua_crypto5.exploreCertificate)(c);
2021
2034
  return info.tbsCertificate.extensions && info.tbsCertificate.extensions.subjectKeyIdentifier === wantedIssuerKey;
2022
2035
  });
@@ -2186,18 +2199,18 @@ var CertificateManager = class _CertificateManager extends import_node_events.Ev
2186
2199
  /**
2187
2200
  * Move a certificate to the rejected store.
2188
2201
  * If the certificate was previously trusted, it will be removed from the trusted folder.
2189
- * @param certificate - the DER-encoded certificate
2202
+ * @param certificateOrChain - the DER-encoded certificate or certificate chain
2190
2203
  */
2191
- async rejectCertificate(certificate) {
2192
- await this.#moveCertificate(certificate, "rejected");
2204
+ async rejectCertificate(certificateOrChain) {
2205
+ await this.#moveCertificate(certificateOrChain, "rejected");
2193
2206
  }
2194
2207
  /**
2195
2208
  * Move a certificate to the trusted store.
2196
2209
  * If the certificate was previously rejected, it will be removed from the rejected folder.
2197
- * @param certificate - the DER-encoded certificate
2210
+ * @param certificateOrChain - the DER-encoded certificate or certificate chain
2198
2211
  */
2199
- async trustCertificate(certificate) {
2200
- await this.#moveCertificate(certificate, "trusted");
2212
+ async trustCertificate(certificateOrChain) {
2213
+ await this.#moveCertificate(certificateOrChain, "trusted");
2201
2214
  }
2202
2215
  /**
2203
2216
  * Check whether the trusted certificate store is empty.
@@ -2251,37 +2264,46 @@ var CertificateManager = class _CertificateManager extends import_node_events.Ev
2251
2264
  * @returns `"Good"` if trusted, `"BadCertificateUntrusted"` if rejected/unknown,
2252
2265
  * or `"BadCertificateInvalid"` if the certificate cannot be parsed.
2253
2266
  */
2254
- async isCertificateTrusted(certificate) {
2255
- let fingerprint;
2267
+ async isCertificateTrusted(certificateOrCertificateChain) {
2256
2268
  try {
2257
- fingerprint = makeFingerprint(certificate);
2258
- } catch (_err) {
2259
- return "BadCertificateInvalid";
2260
- }
2261
- if (this.#thumbs.trusted.has(fingerprint)) {
2262
- return "Good";
2263
- }
2264
- if (!this.#thumbs.rejected.has(fingerprint)) {
2265
- if (!this.untrustUnknownCertificate) {
2266
- return "Good";
2269
+ const chain = coerceCertificateChain(certificateOrCertificateChain);
2270
+ const leafCertificate = chain[0];
2271
+ if (chain.length < 1) {
2272
+ return "BadCertificateInvalid";
2267
2273
  }
2274
+ let fingerprint;
2268
2275
  try {
2269
- (0, import_node_opcua_crypto5.exploreCertificateInfo)(certificate);
2276
+ fingerprint = makeFingerprint(chain[0]);
2270
2277
  } catch (_err) {
2271
2278
  return "BadCertificateInvalid";
2272
2279
  }
2273
- const filename = import_node_path6.default.join(this.rejectedFolder, `${buildIdealCertificateName(certificate)}.pem`);
2274
- debugLog("certificate has never been seen before and is now rejected (untrusted) ", filename);
2275
- await fsWriteFile(filename, (0, import_node_opcua_crypto5.toPem)(certificate, "CERTIFICATE"));
2276
- this.#thumbs.rejected.set(fingerprint, { certificate, filename });
2280
+ if (this.#thumbs.trusted.has(fingerprint)) {
2281
+ return "Good";
2282
+ }
2283
+ if (!this.#thumbs.rejected.has(fingerprint)) {
2284
+ if (!this.untrustUnknownCertificate) {
2285
+ return "Good";
2286
+ }
2287
+ try {
2288
+ (0, import_node_opcua_crypto5.exploreCertificateInfo)(chain[0]);
2289
+ } catch (_err) {
2290
+ return "BadCertificateInvalid";
2291
+ }
2292
+ const filename = import_node_path6.default.join(this.rejectedFolder, `${buildIdealCertificateName(leafCertificate)}.pem`);
2293
+ debugLog("certificate has never been seen before and is now rejected (untrusted) ", filename);
2294
+ await fsWriteFile(filename, (0, import_node_opcua_crypto5.toPem)(chain, "CERTIFICATE"));
2295
+ this.#thumbs.rejected.set(fingerprint, { certificate: leafCertificate, filename });
2296
+ }
2297
+ return "BadCertificateUntrusted";
2298
+ } catch (_err) {
2299
+ return "BadCertificateInvalid";
2277
2300
  }
2278
- return "BadCertificateUntrusted";
2279
2301
  }
2280
2302
  async #innerVerifyCertificateAsync(certificateOrChain, _isIssuer, level, options) {
2281
2303
  if (level >= 5) {
2282
2304
  return "BadSecurityChecksFailed" /* BadSecurityChecksFailed */;
2283
2305
  }
2284
- const chain = Array.isArray(certificateOrChain) ? certificateOrChain : [certificateOrChain];
2306
+ const chain = coerceCertificateChain(certificateOrChain);
2285
2307
  debugLog("NB CERTIFICATE IN CHAIN = ", chain.length);
2286
2308
  const info = (0, import_node_opcua_crypto5.exploreCertificate)(chain[0]);
2287
2309
  let hasValidIssuer = false;
@@ -2336,7 +2358,7 @@ var CertificateManager = class _CertificateManager extends import_node_events.Ev
2336
2358
  return "BadSecurityChecksFailed" /* BadSecurityChecksFailed */;
2337
2359
  }
2338
2360
  hasValidIssuer = true;
2339
- let revokedStatus = await this.isCertificateRevoked(certificateOrChain);
2361
+ let revokedStatus = await this.isCertificateRevoked(chain, issuerCertificate);
2340
2362
  if (revokedStatus === "BadCertificateRevocationUnknown" /* BadCertificateRevocationUnknown */) {
2341
2363
  if (options?.ignoreMissingRevocationList) {
2342
2364
  revokedStatus = "Good" /* Good */;
@@ -2361,11 +2383,11 @@ var CertificateManager = class _CertificateManager extends import_node_events.Ev
2361
2383
  debugLog("Self-signed Certificate signature is not valid");
2362
2384
  return "BadSecurityChecksFailed" /* BadSecurityChecksFailed */;
2363
2385
  }
2364
- const revokedStatus = await this.isCertificateRevoked(certificateOrChain);
2386
+ const revokedStatus = await this.isCertificateRevoked(chain);
2365
2387
  debugLog("revokedStatus of self signed certificate:", revokedStatus);
2366
2388
  }
2367
2389
  }
2368
- const status = await this.#checkRejectedOrTrusted(certificateOrChain);
2390
+ const status = await this.#checkRejectedOrTrusted(chain[0]);
2369
2391
  if (status === "rejected") {
2370
2392
  if (!(options.acceptCertificateWithValidIssuerChain && hasValidIssuer && hasTrustedIssuer)) {
2371
2393
  return "BadCertificateUntrusted" /* BadCertificateUntrusted */;
@@ -2423,17 +2445,15 @@ var CertificateManager = class _CertificateManager extends import_node_events.Ev
2423
2445
  * @returns the verification status code
2424
2446
  */
2425
2447
  async verifyCertificateAsync(certificate, options) {
2426
- if (!Array.isArray(certificate)) {
2448
+ const chain = coerceCertificateChain(certificate);
2449
+ for (const element of chain) {
2427
2450
  try {
2428
- const derElements = (0, import_node_opcua_crypto5.split_der)(certificate);
2429
- for (const element of derElements) {
2430
- (0, import_node_opcua_crypto5.exploreCertificateInfo)(element);
2431
- }
2451
+ (0, import_node_opcua_crypto5.exploreCertificateInfo)(element);
2432
2452
  } catch (_err) {
2433
2453
  return "BadCertificateInvalid" /* BadCertificateInvalid */;
2434
2454
  }
2435
2455
  }
2436
- const status1 = await this.#innerVerifyCertificateAsync(certificate, false, 0, options);
2456
+ const status1 = await this.#innerVerifyCertificateAsync(chain, false, 0, options);
2437
2457
  return status1;
2438
2458
  }
2439
2459
  /**
@@ -2863,7 +2883,7 @@ var CertificateManager = class _CertificateManager extends import_node_events.Ev
2863
2883
  async #addTrustedCertificateFromChainImpl(certificateChain) {
2864
2884
  let certificates;
2865
2885
  try {
2866
- certificates = Array.isArray(certificateChain) ? certificateChain : (0, import_node_opcua_crypto5.split_der)(certificateChain);
2886
+ certificates = coerceCertificateChain(certificateChain);
2867
2887
  } catch (_err) {
2868
2888
  return "BadCertificateInvalid" /* BadCertificateInvalid */;
2869
2889
  }
@@ -2947,7 +2967,7 @@ var CertificateManager = class _CertificateManager extends import_node_events.Ev
2947
2967
  return "BadCertificateInvalid" /* BadCertificateInvalid */;
2948
2968
  }
2949
2969
  }
2950
- await this.trustCertificate(leafCertificate);
2970
+ await this.trustCertificate(certificates);
2951
2971
  return "Good" /* Good */;
2952
2972
  }
2953
2973
  /**
@@ -2990,7 +3010,7 @@ var CertificateManager = class _CertificateManager extends import_node_events.Ev
2990
3010
  *
2991
3011
  */
2992
3012
  async findIssuerCertificate(certificate) {
2993
- const firstCertificate = Array.isArray(certificate) ? certificate[0] : certificate;
3013
+ const firstCertificate = coerceCertificateChain(certificate)[0];
2994
3014
  const certInfo = (0, import_node_opcua_crypto5.exploreCertificate)(firstCertificate);
2995
3015
  if (isSelfSigned2(certInfo)) {
2996
3016
  return firstCertificate;
@@ -3027,7 +3047,7 @@ var CertificateManager = class _CertificateManager extends import_node_events.Ev
3027
3047
  * @private
3028
3048
  */
3029
3049
  async #checkRejectedOrTrusted(certificate) {
3030
- const firstCertificate = Array.isArray(certificate) ? certificate[0] : certificate;
3050
+ const firstCertificate = coerceCertificateChain(certificate)[0];
3031
3051
  const fingerprint = makeFingerprint(firstCertificate);
3032
3052
  debugLog("#checkRejectedOrTrusted fingerprint ", short(fingerprint));
3033
3053
  await this.#readCertificates();
@@ -3039,12 +3059,14 @@ var CertificateManager = class _CertificateManager extends import_node_events.Ev
3039
3059
  }
3040
3060
  return "unknown";
3041
3061
  }
3042
- async #moveCertificate(certificate, newStatus) {
3062
+ async #moveCertificate(certificateOrChain, newStatus) {
3043
3063
  await this.withLock2(async () => {
3064
+ const chain = coerceCertificateChain(certificateOrChain);
3065
+ const certificate = chain[0];
3044
3066
  const fingerprint = makeFingerprint(certificate);
3045
3067
  let status = await this.#checkRejectedOrTrusted(certificate);
3046
3068
  if (status === "unknown") {
3047
- const pem = (0, import_node_opcua_crypto5.toPem)(certificate, "CERTIFICATE");
3069
+ const pem = (0, import_node_opcua_crypto5.toPem)(chain, "CERTIFICATE");
3048
3070
  const filename = import_node_path6.default.join(this.rejectedFolder, `${buildIdealCertificateName(certificate)}.pem`);
3049
3071
  await import_node_fs10.default.promises.writeFile(filename, pem);
3050
3072
  this.#thumbs.rejected.set(fingerprint, { certificate, filename });
@@ -3094,13 +3116,17 @@ var CertificateManager = class _CertificateManager extends import_node_events.Ev
3094
3116
  * found.
3095
3117
  */
3096
3118
  async isCertificateRevoked(certificate, issuerCertificate) {
3097
- const firstCertificate = Array.isArray(certificate) ? certificate[0] : certificate;
3119
+ const chain = coerceCertificateChain(certificate);
3120
+ const firstCertificate = chain[0];
3098
3121
  if (isSelfSigned3(firstCertificate)) {
3099
3122
  return "Good" /* Good */;
3100
3123
  }
3101
3124
  if (!issuerCertificate) {
3102
3125
  issuerCertificate = await this.findIssuerCertificate(firstCertificate);
3103
3126
  }
3127
+ if (!issuerCertificate) {
3128
+ issuerCertificate = findIssuerCertificateInChain(firstCertificate, chain);
3129
+ }
3104
3130
  if (!issuerCertificate) {
3105
3131
  return "BadCertificateChainIncomplete" /* BadCertificateChainIncomplete */;
3106
3132
  }
@@ -3227,7 +3253,25 @@ var CertificateManager = class _CertificateManager extends import_node_events.Ev
3227
3253
  try {
3228
3254
  const stat = await import_node_fs10.default.promises.stat(filename);
3229
3255
  if (!stat.isFile()) continue;
3230
- const certificate = (await (0, import_node_opcua_crypto5.readCertificateChainAsync)(filename))[0];
3256
+ const certs = await (0, import_node_opcua_crypto5.readCertificateChainAsync)(filename);
3257
+ if (certs.length === 0) continue;
3258
+ const certificate = certs[0];
3259
+ if (certs.length > 1) {
3260
+ try {
3261
+ await import_node_fs10.default.promises.writeFile(filename, (0, import_node_opcua_crypto5.toPem)(certs, "CERTIFICATE"), "ascii");
3262
+ } catch (writeErr) {
3263
+ debugLog(`scanCertFolder: could not rewrite legacy PEM ${filename} (read-only fs?)`, writeErr);
3264
+ }
3265
+ for (let i = 1; i < certs.length; i++) {
3266
+ if (isIssuer(certs[i])) {
3267
+ try {
3268
+ await this.addIssuer(certs[i]);
3269
+ } catch (issuerErr) {
3270
+ debugLog(`scanCertFolder: could not auto-register issuer from ${filename}`, issuerErr);
3271
+ }
3272
+ }
3273
+ }
3274
+ }
3231
3275
  const info = (0, import_node_opcua_crypto5.exploreCertificate)(certificate);
3232
3276
  const fingerprint = makeFingerprint(certificate);
3233
3277
  index.set(fingerprint, { certificate, filename, info });
@@ -3380,6 +3424,7 @@ var CertificateManager = class _CertificateManager extends import_node_events.Ev
3380
3424
  VerificationStatus,
3381
3425
  adjustApplicationUri,
3382
3426
  adjustDate,
3427
+ coerceCertificateChain,
3383
3428
  convertPFXtoPEM,
3384
3429
  createPFX,
3385
3430
  dumpPFX,