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/bin/pki.mjs +92 -49
- package/dist/bin/pki.mjs.map +1 -1
- package/dist/index.d.mts +7 -6
- package/dist/index.d.ts +7 -6
- package/dist/index.js +94 -49
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +93 -49
- package/dist/index.mjs.map +1 -1
- package/package.json +4 -4
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
|
});
|
|
@@ -367,17 +367,27 @@ function getOrComputeInfo(entry) {
|
|
|
367
367
|
}
|
|
368
368
|
return entry.info;
|
|
369
369
|
}
|
|
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
|
+
}, []);
|
|
376
|
+
}
|
|
377
|
+
return split_der(certificate);
|
|
378
|
+
}
|
|
370
379
|
function makeFingerprint(certificate) {
|
|
371
|
-
const chain =
|
|
380
|
+
const chain = coerceCertificateChain(certificate);
|
|
372
381
|
return makeSHA1Thumbprint(chain[0]).toString("hex");
|
|
373
382
|
}
|
|
374
383
|
function short(stringToShorten) {
|
|
375
384
|
return stringToShorten.substring(0, 10);
|
|
376
385
|
}
|
|
377
386
|
function buildIdealCertificateName(certificate) {
|
|
378
|
-
const
|
|
387
|
+
const chain = coerceCertificateChain(certificate);
|
|
388
|
+
const fingerprint2 = makeFingerprint(chain);
|
|
379
389
|
try {
|
|
380
|
-
const commonName = exploreCertificate(
|
|
390
|
+
const commonName = exploreCertificate(chain[0]).tbsCertificate.subject.commonName || "";
|
|
381
391
|
const sanitizedCommonName = commonName.replace(forbiddenChars, "_");
|
|
382
392
|
return `${sanitizedCommonName}[${fingerprint2}]`;
|
|
383
393
|
} catch (_err) {
|
|
@@ -417,7 +427,8 @@ function isIssuer(certificate) {
|
|
|
417
427
|
}
|
|
418
428
|
}
|
|
419
429
|
function findIssuerCertificateInChain(certificate, chain) {
|
|
420
|
-
const
|
|
430
|
+
const coercedCertificate = coerceCertificateChain(certificate);
|
|
431
|
+
const firstCertificate = coercedCertificate[0];
|
|
421
432
|
if (!firstCertificate) {
|
|
422
433
|
return null;
|
|
423
434
|
}
|
|
@@ -430,7 +441,8 @@ function findIssuerCertificateInChain(certificate, chain) {
|
|
|
430
441
|
debugLog("Certificate has no extension 3");
|
|
431
442
|
return null;
|
|
432
443
|
}
|
|
433
|
-
const
|
|
444
|
+
const coercedChain = coerceCertificateChain(chain);
|
|
445
|
+
const potentialIssuers = coercedChain.filter((c) => {
|
|
434
446
|
const info = exploreCertificate(c);
|
|
435
447
|
return info.tbsCertificate.extensions && info.tbsCertificate.extensions.subjectKeyIdentifier === wantedIssuerKey;
|
|
436
448
|
});
|
|
@@ -604,18 +616,18 @@ var init_certificate_manager = __esm({
|
|
|
604
616
|
/**
|
|
605
617
|
* Move a certificate to the rejected store.
|
|
606
618
|
* If the certificate was previously trusted, it will be removed from the trusted folder.
|
|
607
|
-
* @param
|
|
619
|
+
* @param certificateOrChain - the DER-encoded certificate or certificate chain
|
|
608
620
|
*/
|
|
609
|
-
async rejectCertificate(
|
|
610
|
-
await this.#moveCertificate(
|
|
621
|
+
async rejectCertificate(certificateOrChain) {
|
|
622
|
+
await this.#moveCertificate(certificateOrChain, "rejected");
|
|
611
623
|
}
|
|
612
624
|
/**
|
|
613
625
|
* Move a certificate to the trusted store.
|
|
614
626
|
* If the certificate was previously rejected, it will be removed from the rejected folder.
|
|
615
|
-
* @param
|
|
627
|
+
* @param certificateOrChain - the DER-encoded certificate or certificate chain
|
|
616
628
|
*/
|
|
617
|
-
async trustCertificate(
|
|
618
|
-
await this.#moveCertificate(
|
|
629
|
+
async trustCertificate(certificateOrChain) {
|
|
630
|
+
await this.#moveCertificate(certificateOrChain, "trusted");
|
|
619
631
|
}
|
|
620
632
|
/**
|
|
621
633
|
* Check whether the trusted certificate store is empty.
|
|
@@ -669,37 +681,46 @@ var init_certificate_manager = __esm({
|
|
|
669
681
|
* @returns `"Good"` if trusted, `"BadCertificateUntrusted"` if rejected/unknown,
|
|
670
682
|
* or `"BadCertificateInvalid"` if the certificate cannot be parsed.
|
|
671
683
|
*/
|
|
672
|
-
async isCertificateTrusted(
|
|
673
|
-
let fingerprint2;
|
|
684
|
+
async isCertificateTrusted(certificateOrCertificateChain) {
|
|
674
685
|
try {
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
if (this.#thumbs.trusted.has(fingerprint2)) {
|
|
680
|
-
return "Good";
|
|
681
|
-
}
|
|
682
|
-
if (!this.#thumbs.rejected.has(fingerprint2)) {
|
|
683
|
-
if (!this.untrustUnknownCertificate) {
|
|
684
|
-
return "Good";
|
|
686
|
+
const chain = coerceCertificateChain(certificateOrCertificateChain);
|
|
687
|
+
const leafCertificate = chain[0];
|
|
688
|
+
if (chain.length < 1) {
|
|
689
|
+
return "BadCertificateInvalid";
|
|
685
690
|
}
|
|
691
|
+
let fingerprint2;
|
|
686
692
|
try {
|
|
687
|
-
|
|
693
|
+
fingerprint2 = makeFingerprint(chain[0]);
|
|
688
694
|
} catch (_err) {
|
|
689
695
|
return "BadCertificateInvalid";
|
|
690
696
|
}
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
this.#thumbs.rejected.
|
|
697
|
+
if (this.#thumbs.trusted.has(fingerprint2)) {
|
|
698
|
+
return "Good";
|
|
699
|
+
}
|
|
700
|
+
if (!this.#thumbs.rejected.has(fingerprint2)) {
|
|
701
|
+
if (!this.untrustUnknownCertificate) {
|
|
702
|
+
return "Good";
|
|
703
|
+
}
|
|
704
|
+
try {
|
|
705
|
+
exploreCertificateInfo(chain[0]);
|
|
706
|
+
} catch (_err) {
|
|
707
|
+
return "BadCertificateInvalid";
|
|
708
|
+
}
|
|
709
|
+
const filename = path2.join(this.rejectedFolder, `${buildIdealCertificateName(leafCertificate)}.pem`);
|
|
710
|
+
debugLog("certificate has never been seen before and is now rejected (untrusted) ", filename);
|
|
711
|
+
await fsWriteFile(filename, toPem(chain, "CERTIFICATE"));
|
|
712
|
+
this.#thumbs.rejected.set(fingerprint2, { certificate: leafCertificate, filename });
|
|
713
|
+
}
|
|
714
|
+
return "BadCertificateUntrusted";
|
|
715
|
+
} catch (_err) {
|
|
716
|
+
return "BadCertificateInvalid";
|
|
695
717
|
}
|
|
696
|
-
return "BadCertificateUntrusted";
|
|
697
718
|
}
|
|
698
719
|
async #innerVerifyCertificateAsync(certificateOrChain, _isIssuer, level, options) {
|
|
699
720
|
if (level >= 5) {
|
|
700
721
|
return "BadSecurityChecksFailed" /* BadSecurityChecksFailed */;
|
|
701
722
|
}
|
|
702
|
-
const chain =
|
|
723
|
+
const chain = coerceCertificateChain(certificateOrChain);
|
|
703
724
|
debugLog("NB CERTIFICATE IN CHAIN = ", chain.length);
|
|
704
725
|
const info = exploreCertificate(chain[0]);
|
|
705
726
|
let hasValidIssuer = false;
|
|
@@ -754,7 +775,7 @@ var init_certificate_manager = __esm({
|
|
|
754
775
|
return "BadSecurityChecksFailed" /* BadSecurityChecksFailed */;
|
|
755
776
|
}
|
|
756
777
|
hasValidIssuer = true;
|
|
757
|
-
let revokedStatus = await this.isCertificateRevoked(
|
|
778
|
+
let revokedStatus = await this.isCertificateRevoked(chain, issuerCertificate);
|
|
758
779
|
if (revokedStatus === "BadCertificateRevocationUnknown" /* BadCertificateRevocationUnknown */) {
|
|
759
780
|
if (options?.ignoreMissingRevocationList) {
|
|
760
781
|
revokedStatus = "Good" /* Good */;
|
|
@@ -779,11 +800,11 @@ var init_certificate_manager = __esm({
|
|
|
779
800
|
debugLog("Self-signed Certificate signature is not valid");
|
|
780
801
|
return "BadSecurityChecksFailed" /* BadSecurityChecksFailed */;
|
|
781
802
|
}
|
|
782
|
-
const revokedStatus = await this.isCertificateRevoked(
|
|
803
|
+
const revokedStatus = await this.isCertificateRevoked(chain);
|
|
783
804
|
debugLog("revokedStatus of self signed certificate:", revokedStatus);
|
|
784
805
|
}
|
|
785
806
|
}
|
|
786
|
-
const status = await this.#checkRejectedOrTrusted(
|
|
807
|
+
const status = await this.#checkRejectedOrTrusted(chain[0]);
|
|
787
808
|
if (status === "rejected") {
|
|
788
809
|
if (!(options.acceptCertificateWithValidIssuerChain && hasValidIssuer && hasTrustedIssuer)) {
|
|
789
810
|
return "BadCertificateUntrusted" /* BadCertificateUntrusted */;
|
|
@@ -841,17 +862,15 @@ var init_certificate_manager = __esm({
|
|
|
841
862
|
* @returns the verification status code
|
|
842
863
|
*/
|
|
843
864
|
async verifyCertificateAsync(certificate, options) {
|
|
844
|
-
|
|
865
|
+
const chain = coerceCertificateChain(certificate);
|
|
866
|
+
for (const element of chain) {
|
|
845
867
|
try {
|
|
846
|
-
|
|
847
|
-
for (const element of derElements) {
|
|
848
|
-
exploreCertificateInfo(element);
|
|
849
|
-
}
|
|
868
|
+
exploreCertificateInfo(element);
|
|
850
869
|
} catch (_err) {
|
|
851
870
|
return "BadCertificateInvalid" /* BadCertificateInvalid */;
|
|
852
871
|
}
|
|
853
872
|
}
|
|
854
|
-
const status1 = await this.#innerVerifyCertificateAsync(
|
|
873
|
+
const status1 = await this.#innerVerifyCertificateAsync(chain, false, 0, options);
|
|
855
874
|
return status1;
|
|
856
875
|
}
|
|
857
876
|
/**
|
|
@@ -1281,7 +1300,7 @@ var init_certificate_manager = __esm({
|
|
|
1281
1300
|
async #addTrustedCertificateFromChainImpl(certificateChain) {
|
|
1282
1301
|
let certificates;
|
|
1283
1302
|
try {
|
|
1284
|
-
certificates =
|
|
1303
|
+
certificates = coerceCertificateChain(certificateChain);
|
|
1285
1304
|
} catch (_err) {
|
|
1286
1305
|
return "BadCertificateInvalid" /* BadCertificateInvalid */;
|
|
1287
1306
|
}
|
|
@@ -1365,7 +1384,7 @@ var init_certificate_manager = __esm({
|
|
|
1365
1384
|
return "BadCertificateInvalid" /* BadCertificateInvalid */;
|
|
1366
1385
|
}
|
|
1367
1386
|
}
|
|
1368
|
-
await this.trustCertificate(
|
|
1387
|
+
await this.trustCertificate(certificates);
|
|
1369
1388
|
return "Good" /* Good */;
|
|
1370
1389
|
}
|
|
1371
1390
|
/**
|
|
@@ -1408,7 +1427,7 @@ var init_certificate_manager = __esm({
|
|
|
1408
1427
|
*
|
|
1409
1428
|
*/
|
|
1410
1429
|
async findIssuerCertificate(certificate) {
|
|
1411
|
-
const firstCertificate =
|
|
1430
|
+
const firstCertificate = coerceCertificateChain(certificate)[0];
|
|
1412
1431
|
const certInfo = exploreCertificate(firstCertificate);
|
|
1413
1432
|
if (isSelfSigned2(certInfo)) {
|
|
1414
1433
|
return firstCertificate;
|
|
@@ -1445,7 +1464,7 @@ var init_certificate_manager = __esm({
|
|
|
1445
1464
|
* @private
|
|
1446
1465
|
*/
|
|
1447
1466
|
async #checkRejectedOrTrusted(certificate) {
|
|
1448
|
-
const firstCertificate =
|
|
1467
|
+
const firstCertificate = coerceCertificateChain(certificate)[0];
|
|
1449
1468
|
const fingerprint2 = makeFingerprint(firstCertificate);
|
|
1450
1469
|
debugLog("#checkRejectedOrTrusted fingerprint ", short(fingerprint2));
|
|
1451
1470
|
await this.#readCertificates();
|
|
@@ -1457,12 +1476,14 @@ var init_certificate_manager = __esm({
|
|
|
1457
1476
|
}
|
|
1458
1477
|
return "unknown";
|
|
1459
1478
|
}
|
|
1460
|
-
async #moveCertificate(
|
|
1479
|
+
async #moveCertificate(certificateOrChain, newStatus) {
|
|
1461
1480
|
await this.withLock2(async () => {
|
|
1481
|
+
const chain = coerceCertificateChain(certificateOrChain);
|
|
1482
|
+
const certificate = chain[0];
|
|
1462
1483
|
const fingerprint2 = makeFingerprint(certificate);
|
|
1463
1484
|
let status = await this.#checkRejectedOrTrusted(certificate);
|
|
1464
1485
|
if (status === "unknown") {
|
|
1465
|
-
const pem = toPem(
|
|
1486
|
+
const pem = toPem(chain, "CERTIFICATE");
|
|
1466
1487
|
const filename = path2.join(this.rejectedFolder, `${buildIdealCertificateName(certificate)}.pem`);
|
|
1467
1488
|
await fs4.promises.writeFile(filename, pem);
|
|
1468
1489
|
this.#thumbs.rejected.set(fingerprint2, { certificate, filename });
|
|
@@ -1512,13 +1533,17 @@ var init_certificate_manager = __esm({
|
|
|
1512
1533
|
* found.
|
|
1513
1534
|
*/
|
|
1514
1535
|
async isCertificateRevoked(certificate, issuerCertificate) {
|
|
1515
|
-
const
|
|
1536
|
+
const chain = coerceCertificateChain(certificate);
|
|
1537
|
+
const firstCertificate = chain[0];
|
|
1516
1538
|
if (isSelfSigned3(firstCertificate)) {
|
|
1517
1539
|
return "Good" /* Good */;
|
|
1518
1540
|
}
|
|
1519
1541
|
if (!issuerCertificate) {
|
|
1520
1542
|
issuerCertificate = await this.findIssuerCertificate(firstCertificate);
|
|
1521
1543
|
}
|
|
1544
|
+
if (!issuerCertificate) {
|
|
1545
|
+
issuerCertificate = findIssuerCertificateInChain(firstCertificate, chain);
|
|
1546
|
+
}
|
|
1522
1547
|
if (!issuerCertificate) {
|
|
1523
1548
|
return "BadCertificateChainIncomplete" /* BadCertificateChainIncomplete */;
|
|
1524
1549
|
}
|
|
@@ -1645,7 +1670,25 @@ var init_certificate_manager = __esm({
|
|
|
1645
1670
|
try {
|
|
1646
1671
|
const stat = await fs4.promises.stat(filename);
|
|
1647
1672
|
if (!stat.isFile()) continue;
|
|
1648
|
-
const
|
|
1673
|
+
const certs = await readCertificateChainAsync(filename);
|
|
1674
|
+
if (certs.length === 0) continue;
|
|
1675
|
+
const certificate = certs[0];
|
|
1676
|
+
if (certs.length > 1) {
|
|
1677
|
+
try {
|
|
1678
|
+
await fs4.promises.writeFile(filename, toPem(certs, "CERTIFICATE"), "ascii");
|
|
1679
|
+
} catch (writeErr) {
|
|
1680
|
+
debugLog(`scanCertFolder: could not rewrite legacy PEM ${filename} (read-only fs?)`, writeErr);
|
|
1681
|
+
}
|
|
1682
|
+
for (let i = 1; i < certs.length; i++) {
|
|
1683
|
+
if (isIssuer(certs[i])) {
|
|
1684
|
+
try {
|
|
1685
|
+
await this.addIssuer(certs[i]);
|
|
1686
|
+
} catch (issuerErr) {
|
|
1687
|
+
debugLog(`scanCertFolder: could not auto-register issuer from ${filename}`, issuerErr);
|
|
1688
|
+
}
|
|
1689
|
+
}
|
|
1690
|
+
}
|
|
1691
|
+
}
|
|
1649
1692
|
const info = exploreCertificate(certificate);
|
|
1650
1693
|
const fingerprint2 = makeFingerprint(certificate);
|
|
1651
1694
|
index.set(fingerprint2, { certificate, filename, info });
|
|
@@ -2544,7 +2587,7 @@ nsComment = ''OpenSSL Generated Certificate''
|
|
|
2544
2587
|
#nsRenewalUrl =
|
|
2545
2588
|
#nsCaPolicyUrl =
|
|
2546
2589
|
#nsSslServerName =
|
|
2547
|
-
keyUsage = critical, digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment, keyAgreement
|
|
2590
|
+
keyUsage = critical, digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment, keyAgreement
|
|
2548
2591
|
extendedKeyUsage = critical,serverAuth ,clientAuth
|
|
2549
2592
|
|
|
2550
2593
|
[ v3_req ]
|