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();