diff --git a/certoperations.js b/certoperations.js index 709cd92a..037f1402 100644 --- a/certoperations.js +++ b/certoperations.js @@ -743,6 +743,29 @@ module.exports.CertificateOperations = function (parent) { return true; } + // Decrypt private key if needed + obj.decryptPrivateKey = function (key) { + if (typeof key != 'string') return key; + var i = key.indexOf('-----BEGIN ENCRYPTED PRIVATE KEY-----'); + var j = key.indexOf('-----END ENCRYPTED PRIVATE KEY-----'); + if ((i >= 0) && (j > i)) { + var passwords = parent.config.settings.certificateprivatekeypassword; + if (parent.config.settings.certificateprivatekeypassword == null) { passwords = []; } + else if (typeof parent.config.settings.certificateprivatekeypassword == 'string') { passwords = [parent.config.settings.certificateprivatekeypassword ]; } + var privateKey = null; + for (var k in passwords) { if (privateKey == null) { try { privateKey = obj.pki.decryptRsaPrivateKey(key, passwords[k]); } catch (ex) { } } } + if (privateKey == null) { + console.log("Private certificate key is encrypted, but no correct password was found."); + console.log("Add the password to the \"certificatePrivateKeyPassword\" value in the Settings section of the config.json."); + console.log("Example: \"certificatePrivateKeyPassword\": [ \"MyPassword\" ]"); + process.exit(); + return null; + } + return obj.pki.privateKeyToPem(privateKey); + } + return key; + } + // Returns the web server TLS certificate and private key, if not present, create demonstration ones. obj.GetMeshServerCertificate = function (args, config, func) { var i = 0; @@ -764,7 +787,7 @@ module.exports.CertificateOperations = function (parent) { // If the root certificate already exist, load it if (obj.fileExists('root-cert-public.crt') && obj.fileExists('root-cert-private.key')) { var rootCertificate = obj.fileLoad('root-cert-public.crt', 'utf8'); - var rootPrivateKey = obj.fileLoad('root-cert-private.key', 'utf8'); + var rootPrivateKey = obj.decryptPrivateKey(obj.fileLoad('root-cert-private.key', 'utf8')); r.root = { cert: rootCertificate, key: rootPrivateKey }; rcount++; @@ -786,7 +809,7 @@ module.exports.CertificateOperations = function (parent) { // If web certificate exist, load it as default. This is useful for agent-only port. Load both certificate and private key if (obj.fileExists('webserver-cert-public.crt') && obj.fileExists('webserver-cert-private.key')) { - r.webdefault = { cert: obj.fileLoad('webserver-cert-public.crt', 'utf8'), key: obj.fileLoad('webserver-cert-private.key', 'utf8') }; + r.webdefault = { cert: obj.fileLoad('webserver-cert-public.crt', 'utf8'), key: obj.decryptPrivateKey(obj.fileLoad('webserver-cert-private.key', 'utf8')) }; if (obj.checkCertificate(r.webdefault.cert, r.webdefault.key) == false) { delete r.webdefault; } } @@ -798,27 +821,27 @@ module.exports.CertificateOperations = function (parent) { } } else { // If the web certificate already exist, load it. Load both certificate and private key - if (obj.fileExists('webserver-cert-public.crt') && obj.fileExists('webserver-cert-private.key')) { - r.web = { cert: obj.fileLoad('webserver-cert-public.crt', 'utf8'), key: obj.fileLoad('webserver-cert-private.key', 'utf8') }; + if (obj.fileExists('webserver-cert-public.crt') && obj.decryptPrivateKey(obj.fileExists('webserver-cert-private.key'))) { + r.web = { cert: obj.fileLoad('webserver-cert-public.crt', 'utf8'), key: obj.decryptPrivateKey(obj.fileLoad('webserver-cert-private.key', 'utf8')) }; if (obj.checkCertificate(r.web.cert, r.web.key) == false) { delete r.web; } else { rcount++; } } } // If the mps certificate already exist, load it if (obj.fileExists('mpsserver-cert-public.crt') && obj.fileExists('mpsserver-cert-private.key')) { - r.mps = { cert: obj.fileLoad('mpsserver-cert-public.crt', 'utf8'), key: obj.fileLoad('mpsserver-cert-private.key', 'utf8') }; + r.mps = { cert: obj.fileLoad('mpsserver-cert-public.crt', 'utf8'), key: obj.decryptPrivateKey(obj.fileLoad('mpsserver-cert-private.key', 'utf8')) }; if (obj.checkCertificate(r.mps.cert, r.mps.key) == false) { delete r.mps; } else { rcount++; } } // If the agent certificate already exist, load it if (obj.fileExists("agentserver-cert-public.crt") && obj.fileExists("agentserver-cert-private.key")) { - r.agent = { cert: obj.fileLoad("agentserver-cert-public.crt", 'utf8'), key: obj.fileLoad("agentserver-cert-private.key", 'utf8') }; + r.agent = { cert: obj.fileLoad("agentserver-cert-public.crt", 'utf8'), key: obj.decryptPrivateKey(obj.fileLoad("agentserver-cert-private.key", 'utf8')) }; if (obj.checkCertificate(r.agent.cert, r.agent.key) == false) { delete r.agent; } else { rcount++; } } // If the swarm server certificate exist, load it (This is an optional certificate) if (obj.fileExists('swarmserver-cert-public.crt') && obj.fileExists('swarmserver-cert-private.key')) { - r.swarmserver = { cert: obj.fileLoad('swarmserver-cert-public.crt', 'utf8'), key: obj.fileLoad('swarmserver-cert-private.key', 'utf8') }; + r.swarmserver = { cert: obj.fileLoad('swarmserver-cert-public.crt', 'utf8'), key: obj.decryptPrivateKey(obj.fileLoad('swarmserver-cert-private.key', 'utf8')) }; if (obj.checkCertificate(r.swarmserver.cert, r.swarmserver.key) == false) { delete r.swarmserver; } } @@ -894,7 +917,7 @@ module.exports.CertificateOperations = function (parent) { dnsname = config.domains[i].dns; // Check if this domain matches a parent wildcard cert, if so, use the parent cert. if (obj.compareCertificateNames(r.CommonNames, dnsname) == true) { - r.dns[i] = { cert: obj.fileLoad('webserver-cert-public.crt', 'utf8'), key: obj.fileLoad('webserver-cert-private.key', 'utf8') }; + r.dns[i] = { cert: obj.fileLoad('webserver-cert-public.crt', 'utf8'), key: obj.decryptPrivateKey(obj.fileLoad('webserver-cert-private.key', 'utf8')) }; } else { if (args.tlsoffload) { // 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) { } else { // If the web certificate already exist, load it. Load both certificate and private key if (obj.fileExists('webserver-' + i + '-cert-public.crt') && obj.fileExists('webserver-' + i + '-cert-private.key')) { - r.dns[i] = { cert: obj.fileLoad('webserver-' + i + '-cert-public.crt', 'utf8'), key: obj.fileLoad('webserver-' + i + '-cert-private.key', 'utf8') }; + r.dns[i] = { cert: obj.fileLoad('webserver-' + i + '-cert-public.crt', 'utf8'), key: obj.decryptPrivateKey(obj.fileLoad('webserver-' + i + '-cert-private.key', 'utf8')) }; config.domains[i].certs = r.dns[i]; // If CA certificates are present, load them caindex = 1; @@ -1062,7 +1085,7 @@ module.exports.CertificateOperations = function (parent) { dnsname = config.domains[i].dns; // Check if this domain matches a parent wildcard cert, if so, use the parent cert. if (obj.compareCertificateNames(r.CommonNames, dnsname) == true) { - r.dns[i] = { cert: obj.fileLoad('webserver-cert-public.crt', 'utf8'), key: obj.fileLoad('webserver-cert-private.key', 'utf8') }; + r.dns[i] = { cert: obj.fileLoad('webserver-cert-public.crt', 'utf8'), key: obj.decryptPrivateKey(obj.fileLoad('webserver-cert-private.key', 'utf8')) }; } else { if (!args.tlsoffload) { // If the web certificate does not exist, create it diff --git a/meshcentral-config-schema.json b/meshcentral-config-schema.json index eb4b7bd9..f781533b 100644 --- a/meshcentral-config-schema.json +++ b/meshcentral-config-schema.json @@ -58,8 +58,9 @@ "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." }, "LANonly": { "type": "boolean", "default": false, "description": "When enabled, only MeshCentral LAN features are enabled and agents will find the server using multicast LAN packets." }, "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." }, + "certificatePrivateKeyPassword": { "type": "array", "default": null, "description": "List of passwords used to decrypt PKCK#8 .key files that are in the meshcentral-data folder." }, "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." }, - "sessionKey": { "type": "string" }, + "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." }, "sessionSameSite": { "type": "string" }, "dbEncryptKey": { "type": "string" }, "dbRecordsEncryptKey": { "type": "string", "default": null }, diff --git a/sample-config-advanced.json b/sample-config-advanced.json index 49e89b86..d4fdd321 100644 --- a/sample-config-advanced.json +++ b/sample-config-advanced.json @@ -10,6 +10,7 @@ "_LANonly": true, "_sessionKey": "MyReallySecretPassword1", "_sessionSameSite": "strict", + "_certificatePrivateKeyPassword": [ "password1", "password2" ], "_dbEncryptKey": "MyReallySecretPassword2", "_dbRecordsEncryptKey": "MyReallySecretPassword", "_dbRecordsDecryptKey": "MyReallySecretPassword",