Started work on support for loading ECDSA certificates as HTTPS cert.
Ylian Saint-Hilaire committed
Aug 20, 2023 at 23:29 UTC
b1d2d1aea96c5de48be210e89961347cb6b8b72b
2 files changed
+113
-28
certoperations.js
+112
-27
@@ -634,9 +634,17 @@ module.exports.CertificateOperations = function (parent) {
634
};
635
636
// Return the SHA384 hash of the certificate public key
637
- obj.getPublicKeyHashBinary = function (cert) {
638
- var publickey = obj.pki.certificateFromPem(cert).publicKey;
639
- return obj.pki.getPublicKeyFingerprint(publickey, { encoding: 'binary', md: obj.forge.md.sha384.create() });
637
+ obj.getPublicKeyHashBinary = function (pem) {
638
+ const { X509Certificate } = require('crypto');
639
+ if (X509Certificate == null) {
640
+ // This version of NodeJS (<v15.6.0) does not support X509 certs, use Node-Forge instead which only supports RSA certs.
641
+ var publickey = obj.pki.certificateFromPem(pem).publicKey;
642
+ return obj.pki.getPublicKeyFingerprint(publickey, { encoding: 'binary', md: obj.forge.md.sha384.create() });
643
+ } else {
644
+ // This version of NodeJS supports x509 certificates
645
+ // TODO: THIS IS NOT CORRECT, this is SHA254 of the entire cert.
646
+ return Buffer.from(new X509Certificate(pem).fingerprint256.split(':').join(''), 'hex');
647
+ }
648
};
649
650
// Return the SHA384 hash of the certificate, return binary
@@ -741,7 +749,7 @@ module.exports.CertificateOperations = function (parent) {
749
}
750
751
// Return true if the name is found in the certificates names, we support wildcard certificates
744
- obj.compareCertificateNames = function(certNames, name) {
752
+ obj.compareCertificateNames = function (certNames, name) {
753
if (certNames == null) return false;
754
name = name.toLowerCase();
755
var xcertNames = [];
@@ -759,12 +767,102 @@ module.exports.CertificateOperations = function (parent) {
767
768
// Return true if the certificate is valid
769
obj.checkCertificate = function (pem, key) {
762
- var cert = null;
763
- try { cert = obj.pki.certificateFromPem(pem); } catch (ex) { return false; } // Unable to decode certificate
764
- if (cert.serialNumber == '') return false; // Empty serial number is not allowed.
770
+ const { X509Certificate } = require('crypto');
771
+ if (X509Certificate == null) {
772
+ // This version of NodeJS (<v15.6.0) does not support X509 certs, use Node-Forge instead which only supports RSA certs.
773
+ var cert = null;
774
+ try { cert = obj.pki.certificateFromPem(pem); } catch (ex) { return false; } // Unable to decode certificate
775
+ if (cert.serialNumber == '') return false; // Empty serial number is not allowed.
776
+ } else {
777
+ // This version of NodeJS supports x509 certificates
778
+ try {
779
+ const cert = new X509Certificate(pem);
780
+ if ((cert.serialNumber == '') || (cert.serialNumber == null)) return false; // Empty serial number is not allowed.
781
+ } catch (ex) { return false; } // Unable to decode certificate
782
+ }
783
return true;
784
}
785
786
+ // Get the Common Name from a certificate
787
+ obj.getCertificateCommonName = function (pem, field) {
788
+ if (field == null) { field = 'CN'; }
789
+ const { X509Certificate } = require('crypto');
790
+ if (X509Certificate == null) {
791
+ // This version of NodeJS (<v15.6.0) does not support X509 certs, use Node-Forge instead which only supports RSA certs.
792
+ var cert = obj.pki.certificateFromPem(pem);
793
+ if (cert.subject.getField(field) != null) return cert.subject.getField(field).value;
794
+ } else {
795
+ // This version of NodeJS supports x509 certificates
796
+ const subjects = new X509Certificate(pem).subject.split('\n');
797
+ for (var i in subjects) { if (subjects[i].startsWith(field + '=')) { return subjects[i].substring(field.length + 1); } }
798
+ }
799
+ return null;
800
+ }
801
+
802
+ // Get the Issuer Common Name from a certificate
803
+ obj.getCertificateIssuerCommonName = function (pem, field) {
804
+ if (field == null) { field = 'CN'; }
805
+ const { X509Certificate } = require('crypto');
806
+ if (X509Certificate == null) {
807
+ // This version of NodeJS (<v15.6.0) does not support X509 certs, use Node-Forge instead which only supports RSA certs.
808
+ var cert = obj.pki.certificateFromPem(pem);
809
+ if (cert.issuer.getField(field) != null) return cert.issuer.getField(field).value;
810
+ } else {
811
+ // This version of NodeJS supports x509 certificates
812
+ const subjects = new X509Certificate(pem).issuer.split('\n');
813
+ for (var i in subjects) { if (subjects[i].startsWith(field + '=')) { return subjects[i].substring(field.length + 1); } }
814
+ }
815
+ return null;
816
+ }
817
+
818
+ // Get the Common Name and alternate names from a certificate
819
+ obj.getCertificateAltNames = function (pem) {
820
+ const altNamesResults = [];
821
+ const { X509Certificate } = require('crypto');
822
+ if (X509Certificate == null) {
823
+ // This version of NodeJS (<v15.6.0) does not support X509 certs, use Node-Forge instead which only supports RSA certs.
824
+ var cert = obj.pki.certificateFromPem(pem);
825
+ if (cert.subject.getField('CN') != null) { altNamesResults.push(cert.subject.getField('CN').value); }
826
+ var altNames = cert.getExtension('subjectAltName');
827
+ if (altNames) {
828
+ for (i = 0; i < altNames.altNames.length; i++) {
829
+ if ((altNames.altNames[i] != null) && (altNames.altNames[i].type === 2) && (typeof altNames.altNames[i].value === 'string')) {
830
+ var acn = altNames.altNames[i].value.toLowerCase();
831
+ if (altNamesResults.indexOf(acn) == -1) { altNamesResults.push(acn); }
832
+ }
833
+ }
834
+ }
835
+ } else {
836
+ // This version of NodeJS supports x509 certificates
837
+ const cert = new X509Certificate(pem);
838
+ const subjects = cert.subject.split('\n');
839
+ for (var i in subjects) { if (subjects[i].startsWith('CN=')) { altNamesResults.push(subjects[i].substring(3)); } }
840
+ var subjectAltNames = cert.subjectAltName;
841
+ if (subjectAltNames != null) {
842
+ subjectAltNames = subjectAltNames.split(', ');
843
+ for (var i = 0; i < subjectAltNames.length; i++) {
844
+ if (subjectAltNames[i].startsWith('DNS:') && altNamesResults.indexOf(subjectAltNames[i].substring(4)) == -1) {
845
+ altNamesResults.push(subjectAltNames[i].substring(4));
846
+ }
847
+ }
848
+ }
849
+ }
850
+ return altNamesResults;
851
+ }
852
+
853
+ // Get the expiration time from a certificate
854
+ obj.getCertificateExpire = function (pem) {
855
+ const altNamesResults = [];
856
+ const { X509Certificate } = require('crypto');
857
+ if (X509Certificate == null) {
858
+ // This version of NodeJS (<v15.6.0) does not support X509 certs, use Node-Forge instead which only supports RSA certs.
859
+ return Date.parse(parent.certificateOperations.forge.pki.certificateFromPem(parent.certificates.web.cert).validity.notAfter);
860
+ } else {
861
+ // This version of NodeJS supports x509 certificates
862
+ return Date.parse(new X509Certificate(pem).validTo);
863
+ }
864
+ }
865
+
866
// Decrypt private key if needed
867
obj.decryptPrivateKey = function (key) {
868
if (typeof key != 'string') return key;
@@ -921,22 +1019,11 @@ module.exports.CertificateOperations = function (parent) {
1019
1020
if (rcount === rcountmax) {
1021
// Fetch the certificates names for the main certificate
924
- r.AmtMpsName = obj.pki.certificateFromPem(r.mps.cert).subject.getField('CN').value;
925
- var webCertificate = obj.pki.certificateFromPem(r.web.cert);
926
- if (webCertificate.issuer.getField('CN') != null) { r.WebIssuer = webCertificate.issuer.getField('CN').value; } else { r.WebIssuer = null; }
927
- r.CommonName = webCertificate.subject.getField('CN').value;
928
- r.CommonNames = [ r.CommonName ];
929
- var altNames = webCertificate.getExtension('subjectAltName');
930
- if (altNames) {
931
- for (i = 0; i < altNames.altNames.length; i++) {
932
- if ((altNames.altNames[i] != null) && (altNames.altNames[i].type === 2) && (typeof altNames.altNames[i].value === 'string')) {
933
- var acn = altNames.altNames[i].value.toLowerCase();
934
- if (r.CommonNames.indexOf(acn) == -1) { r.CommonNames.push(acn); }
935
- }
936
- }
937
- }
938
- var rootCertificate = obj.pki.certificateFromPem(r.root.cert);
939
- r.RootName = rootCertificate.subject.getField('CN').value;
1022
+ r.AmtMpsName = obj.getCertificateCommonName(r.mps.cert);
1023
+ r.WebIssuer = obj.getCertificateIssuerCommonName(r.web.cert);
1024
+ r.CommonName = obj.getCertificateCommonName(r.web.cert);
1025
+ r.CommonNames = obj.getCertificateAltNames(r.web.cert);
1026
+ r.RootName = obj.getCertificateCommonName(r.root.cert);
1027
1028
// If the "cert" name is not set, try to use the certificate CN instead (ok if the certificate is not wildcard).
1029
if (commonName == 'un-configured') {
@@ -989,10 +1076,8 @@ module.exports.CertificateOperations = function (parent) {
1076
// If we have all the certificates we need, stop here.
1077
if (rcount === rcountmax) {
1078
if ((certargs == null) && (mpscertargs == null)) { if (func != undefined) { func(r); } return r; } // If no certificate arguments are given, keep the certificate
992
- var xcountry, xcountryField = webCertificate.subject.getField('C');
993
- if (xcountryField != null) { xcountry = xcountryField.value; }
994
- var xorganization, xorganizationField = webCertificate.subject.getField('O');
995
- if (xorganizationField != null) { xorganization = xorganizationField.value; }
1079
+ const xcountry = obj.getCertificateCommonName(r.web.cert, 'C');
1080
+ const xorganization = obj.getCertificateCommonName(r.web.cert, 'O');
1081
if (certargs == null) { commonName = r.CommonName; country = xcountry; organization = xorganization; }
1082
1083
// Check if we have correct certificates.
webserver.js
+1
-1
@@ -151,7 +151,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates, doneF
151
obj.webCertificateHashBase64 = Buffer.from(obj.webCertificateHash, 'binary').toString('base64').replace(/\+/g, '@').replace(/\//g, '$');
152
obj.webCertificateFullHash = parent.certificateOperations.getCertHashBinary(obj.certificates.web.cert);
153
obj.webCertificateFullHashs = { '': obj.webCertificateFullHash };
154
- obj.webCertificateExpire = { '': Date.parse(parent.certificateOperations.forge.pki.certificateFromPem(parent.certificates.web.cert).validity.notAfter) };
154
+ obj.webCertificateExpire = { '': parent.certificateOperations.getCertificateExpire(parent.certificates.web.cert) };
155
obj.agentCertificateHashHex = parent.certificateOperations.getPublicKeyHash(obj.certificates.agent.cert);
156
obj.agentCertificateHashBase64 = Buffer.from(obj.agentCertificateHashHex, 'hex').toString('base64').replace(/\+/g, '@').replace(/\//g, '$');
157
obj.agentCertificateAsn1 = parent.certificateOperations.forge.asn1.toDer(parent.certificateOperations.forge.pki.certificateToAsn1(parent.certificateOperations.forge.pki.certificateFromPem(parent.certificates.agent.cert))).getBytes();