Added support for PKCS#8 encrypted private keys.

Ylian Saint-Hilaire committed Aug 20, 2021 at 12:03 UTC e47e050149baf0caa8de027709e07ef5a30c862a
3 files changed +36 -11
certoperations.js
+33 -10
@@ -743,6 +743,29 @@ module.exports.CertificateOperations = function (parent) {
743 return true;
744 }
745
746 + // Decrypt private key if needed
747 + obj.decryptPrivateKey = function (key) {
748 + if (typeof key != 'string') return key;
749 + var i = key.indexOf('-----BEGIN ENCRYPTED PRIVATE KEY-----');
750 + var j = key.indexOf('-----END ENCRYPTED PRIVATE KEY-----');
751 + if ((i >= 0) && (j > i)) {
752 + var passwords = parent.config.settings.certificateprivatekeypassword;
753 + if (parent.config.settings.certificateprivatekeypassword == null) { passwords = []; }
754 + else if (typeof parent.config.settings.certificateprivatekeypassword == 'string') { passwords = [parent.config.settings.certificateprivatekeypassword ]; }
755 + var privateKey = null;
756 + for (var k in passwords) { if (privateKey == null) { try { privateKey = obj.pki.decryptRsaPrivateKey(key, passwords[k]); } catch (ex) { } } }
757 + if (privateKey == null) {
758 + console.log("Private certificate key is encrypted, but no correct password was found.");
759 + console.log("Add the password to the \"certificatePrivateKeyPassword\" value in the Settings section of the config.json.");
760 + console.log("Example: \"certificatePrivateKeyPassword\": [ \"MyPassword\" ]");
761 + process.exit();
762 + return null;
763 + }
764 + return obj.pki.privateKeyToPem(privateKey);
765 + }
766 + return key;
767 + }
768 +
769 // Returns the web server TLS certificate and private key, if not present, create demonstration ones.
770 obj.GetMeshServerCertificate = function (args, config, func) {
771 var i = 0;
@@ -764,7 +787,7 @@ module.exports.CertificateOperations = function (parent) {
787 // If the root certificate already exist, load it
788 if (obj.fileExists('root-cert-public.crt') && obj.fileExists('root-cert-private.key')) {
789 var rootCertificate = obj.fileLoad('root-cert-public.crt', 'utf8');
767 - var rootPrivateKey = obj.fileLoad('root-cert-private.key', 'utf8');
790 + var rootPrivateKey = obj.decryptPrivateKey(obj.fileLoad('root-cert-private.key', 'utf8'));
791 r.root = { cert: rootCertificate, key: rootPrivateKey };
792 rcount++;
793
@@ -786,7 +809,7 @@ module.exports.CertificateOperations = function (parent) {
809
810 // If web certificate exist, load it as default. This is useful for agent-only port. Load both certificate and private key
811 if (obj.fileExists('webserver-cert-public.crt') && obj.fileExists('webserver-cert-private.key')) {
789 - r.webdefault = { cert: obj.fileLoad('webserver-cert-public.crt', 'utf8'), key: obj.fileLoad('webserver-cert-private.key', 'utf8') };
812 + r.webdefault = { cert: obj.fileLoad('webserver-cert-public.crt', 'utf8'), key: obj.decryptPrivateKey(obj.fileLoad('webserver-cert-private.key', 'utf8')) };
813 if (obj.checkCertificate(r.webdefault.cert, r.webdefault.key) == false) { delete r.webdefault; }
814 }
815
@@ -798,27 +821,27 @@ module.exports.CertificateOperations = function (parent) {
821 }
822 } else {
823 // If the web certificate already exist, load it. Load both certificate and private key
801 - if (obj.fileExists('webserver-cert-public.crt') && obj.fileExists('webserver-cert-private.key')) {
802 - r.web = { cert: obj.fileLoad('webserver-cert-public.crt', 'utf8'), key: obj.fileLoad('webserver-cert-private.key', 'utf8') };
824 + if (obj.fileExists('webserver-cert-public.crt') && obj.decryptPrivateKey(obj.fileExists('webserver-cert-private.key'))) {
825 + r.web = { cert: obj.fileLoad('webserver-cert-public.crt', 'utf8'), key: obj.decryptPrivateKey(obj.fileLoad('webserver-cert-private.key', 'utf8')) };
826 if (obj.checkCertificate(r.web.cert, r.web.key) == false) { delete r.web; } else { rcount++; }
827 }
828 }
829
830 // If the mps certificate already exist, load it
831 if (obj.fileExists('mpsserver-cert-public.crt') && obj.fileExists('mpsserver-cert-private.key')) {
809 - r.mps = { cert: obj.fileLoad('mpsserver-cert-public.crt', 'utf8'), key: obj.fileLoad('mpsserver-cert-private.key', 'utf8') };
832 + r.mps = { cert: obj.fileLoad('mpsserver-cert-public.crt', 'utf8'), key: obj.decryptPrivateKey(obj.fileLoad('mpsserver-cert-private.key', 'utf8')) };
833 if (obj.checkCertificate(r.mps.cert, r.mps.key) == false) { delete r.mps; } else { rcount++; }
834 }
835
836 // If the agent certificate already exist, load it
837 if (obj.fileExists("agentserver-cert-public.crt") && obj.fileExists("agentserver-cert-private.key")) {
815 - r.agent = { cert: obj.fileLoad("agentserver-cert-public.crt", 'utf8'), key: obj.fileLoad("agentserver-cert-private.key", 'utf8') };
838 + r.agent = { cert: obj.fileLoad("agentserver-cert-public.crt", 'utf8'), key: obj.decryptPrivateKey(obj.fileLoad("agentserver-cert-private.key", 'utf8')) };
839 if (obj.checkCertificate(r.agent.cert, r.agent.key) == false) { delete r.agent; } else { rcount++; }
840 }
841
842 // If the swarm server certificate exist, load it (This is an optional certificate)
843 if (obj.fileExists('swarmserver-cert-public.crt') && obj.fileExists('swarmserver-cert-private.key')) {
821 - r.swarmserver = { cert: obj.fileLoad('swarmserver-cert-public.crt', 'utf8'), key: obj.fileLoad('swarmserver-cert-private.key', 'utf8') };
844 + r.swarmserver = { cert: obj.fileLoad('swarmserver-cert-public.crt', 'utf8'), key: obj.decryptPrivateKey(obj.fileLoad('swarmserver-cert-private.key', 'utf8')) };
845 if (obj.checkCertificate(r.swarmserver.cert, r.swarmserver.key) == false) { delete r.swarmserver; }
846 }
847
@@ -894,7 +917,7 @@ module.exports.CertificateOperations = function (parent) {
917 dnsname = config.domains[i].dns;
918 // Check if this domain matches a parent wildcard cert, if so, use the parent cert.
919 if (obj.compareCertificateNames(r.CommonNames, dnsname) == true) {
897 - r.dns[i] = { cert: obj.fileLoad('webserver-cert-public.crt', 'utf8'), key: obj.fileLoad('webserver-cert-private.key', 'utf8') };
920 + r.dns[i] = { cert: obj.fileLoad('webserver-cert-public.crt', 'utf8'), key: obj.decryptPrivateKey(obj.fileLoad('webserver-cert-private.key', 'utf8')) };
921 } else {
922 if (args.tlsoffload) {
923 // If the web certificate already exist, load it. Load just the certificate since we are in TLS offload situation
@@ -907,7 +930,7 @@ module.exports.CertificateOperations = function (parent) {
930 } else {
931 // If the web certificate already exist, load it. Load both certificate and private key
932 if (obj.fileExists('webserver-' + i + '-cert-public.crt') && obj.fileExists('webserver-' + i + '-cert-private.key')) {
910 - r.dns[i] = { cert: obj.fileLoad('webserver-' + i + '-cert-public.crt', 'utf8'), key: obj.fileLoad('webserver-' + i + '-cert-private.key', 'utf8') };
933 + r.dns[i] = { cert: obj.fileLoad('webserver-' + i + '-cert-public.crt', 'utf8'), key: obj.decryptPrivateKey(obj.fileLoad('webserver-' + i + '-cert-private.key', 'utf8')) };
934 config.domains[i].certs = r.dns[i];
935 // If CA certificates are present, load them
936 caindex = 1;
@@ -1062,7 +1085,7 @@ module.exports.CertificateOperations = function (parent) {
1085 dnsname = config.domains[i].dns;
1086 // Check if this domain matches a parent wildcard cert, if so, use the parent cert.
1087 if (obj.compareCertificateNames(r.CommonNames, dnsname) == true) {
1065 - r.dns[i] = { cert: obj.fileLoad('webserver-cert-public.crt', 'utf8'), key: obj.fileLoad('webserver-cert-private.key', 'utf8') };
1088 + r.dns[i] = { cert: obj.fileLoad('webserver-cert-public.crt', 'utf8'), key: obj.decryptPrivateKey(obj.fileLoad('webserver-cert-private.key', 'utf8')) };
1089 } else {
1090 if (!args.tlsoffload) {
1091 // If the web certificate does not exist, create it
meshcentral-config-schema.json
+2 -1
@@ -58,8 +58,9 @@
58 "WANonly": { "type": "boolean", "default": false, "description": "When enabled, only MeshCentral WAN features are enabled and agents will connect to the server using a well known DNS name." },
59 "LANonly": { "type": "boolean", "default": false, "description": "When enabled, only MeshCentral LAN features are enabled and agents will find the server using multicast LAN packets." },
60 "maintenanceMode": { "type": "boolean", "default": false, "description": "When enabled the server is in maintenance mode, only administrators can login. Use the maintenance command in server console to change." },
61 + "certificatePrivateKeyPassword": { "type": "array", "default": null, "description": "List of passwords used to decrypt PKCK#8 .key files that are in the meshcentral-data folder." },
62 "sessionTime": { "type": "integer", "default": 60, "description": "Duration of a session cookie in minutes. Changing this affects how often the session needs to be automatically refreshed." },
62 - "sessionKey": { "type": "string" },
63 + "sessionKey": { "type": "string", "default": null, "description": "Password used to encrypt the MeshCentral web session cookies. If null, a random one is generated each time the server starts." },
64 "sessionSameSite": { "type": "string" },
65 "dbEncryptKey": { "type": "string" },
66 "dbRecordsEncryptKey": { "type": "string", "default": null },
sample-config-advanced.json
+1
@@ -10,6 +10,7 @@
10 "_LANonly": true,
11 "_sessionKey": "MyReallySecretPassword1",
12 "_sessionSameSite": "strict",
13 + "_certificatePrivateKeyPassword": [ "password1", "password2" ],
14 "_dbEncryptKey": "MyReallySecretPassword2",
15 "_dbRecordsEncryptKey": "MyReallySecretPassword",
16 "_dbRecordsDecryptKey": "MyReallySecretPassword",