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",