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 +216 -75
- package/dist/bin/pki.mjs.map +1 -1
- package/dist/index.d.mts +99 -17
- package/dist/index.d.ts +99 -17
- package/dist/index.js +248 -74
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +245 -77
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -3
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,
|
|
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
|
-
|
|
358
|
-
|
|
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 =
|
|
366
|
+
entry.info = exploreCertificate(entry.certificate);
|
|
368
367
|
}
|
|
369
368
|
return entry.info;
|
|
370
369
|
}
|
|
371
|
-
function
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
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
|
-
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
429
|
+
const coercedCertificate = coerceCertificateChain(certificate);
|
|
430
|
+
const firstCertificate = coercedCertificate[0];
|
|
431
|
+
if (!firstCertificate) {
|
|
419
432
|
return null;
|
|
420
433
|
}
|
|
421
|
-
const certInfo =
|
|
434
|
+
const certInfo = exploreCertificate(firstCertificate);
|
|
422
435
|
if (isSelfSigned2(certInfo)) {
|
|
423
|
-
return
|
|
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
|
|
431
|
-
|
|
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,
|
|
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
|
-
|
|
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(
|
|
709
|
+
async #innerVerifyCertificateAsync(certificateOrChain, _isIssuer, level, options) {
|
|
685
710
|
if (level >= 5) {
|
|
686
711
|
return "BadSecurityChecksFailed" /* BadSecurityChecksFailed */;
|
|
687
712
|
}
|
|
688
|
-
const chain =
|
|
713
|
+
const chain = coerceCertificateChain(certificateOrChain);
|
|
689
714
|
debugLog("NB CERTIFICATE IN CHAIN = ", chain.length);
|
|
690
|
-
const info =
|
|
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
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
|
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 =
|
|
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
|
-
*
|
|
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
|
-
*
|
|
1212
|
-
*
|
|
1213
|
-
*
|
|
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
|
-
*
|
|
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
|
-
|
|
1284
|
+
return await this.#addTrustedCertificateFromChainImpl(certificateChain);
|
|
1227
1285
|
} catch (_err) {
|
|
1286
|
+
warningLog("addTrustedCertificateFromChain: unexpected error", _err);
|
|
1228
1287
|
return "BadCertificateInvalid" /* BadCertificateInvalid */;
|
|
1229
1288
|
}
|
|
1230
|
-
|
|
1231
|
-
|
|
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
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
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
|
|
1420
|
+
const firstCertificate = coerceCertificateChain(certificate)[0];
|
|
1421
|
+
const certInfo = exploreCertificate(firstCertificate);
|
|
1287
1422
|
if (isSelfSigned2(certInfo)) {
|
|
1288
|
-
return
|
|
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
|
|
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 =
|
|
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
|
-
|
|
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(
|
|
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 =
|
|
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
|
|
1521
|
-
const info =
|
|
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 =
|
|
1608
|
-
const info =
|
|
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 =
|
|
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:
|
|
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
|
|
2560
|
+
keyUsage = critical, digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment, keyAgreement
|
|
2420
2561
|
extendedKeyUsage = critical,serverAuth ,clientAuth
|
|
2421
2562
|
|
|
2422
2563
|
[ v3_req ]
|