Added web cert loading, useful for TLS offload.

Ylian Saint-Hilaire committed Oct 31, 2018 at 16:03 UTC 41b86b53970dbdbea0d66d8e098e0459372749e0
6 files changed +80 -10
certoperations.js
+14
@@ -20,11 +20,25 @@ module.exports.CertificateOperations = function () {
20 obj.fs = require("fs");
21 obj.forge = require("node-forge");
22 obj.crypto = require("crypto");
23 + obj.tls = require('tls');
24 obj.pki = obj.forge.pki;
25 obj.dirExists = function (filePath) { try { return obj.fs.statSync(filePath).isDirectory(); } catch (err) { return false; } };
26 obj.getFilesizeInBytes = function (filename) { try { return obj.fs.statSync(filename).size; } catch (err) { return -1; } };
27 obj.fileExists = function (filePath) { try { return obj.fs.statSync(filePath).isFile(); } catch (err) { return false; } };
28
29 + // Return the certificate of the remote HTTPS server
30 + obj.loadCertificate = function (url, tag, func) {
31 + var u = require('url').parse(url);
32 + if (u.protocol == 'https:') {
33 + var tlssocket = obj.tls.connect((u.port ? u.port : 443), u.hostname, { rejectUnauthorized: false }, function () { this.xxcert = this.getPeerCertificate(); this.end(); });
34 + tlssocket.xxurl = url;
35 + tlssocket.xxfunc = func;
36 + tlssocket.xxtag = tag;
37 + tlssocket.on('end', function () { this.xxfunc(this.xxurl, this.xxcert, this.xxtag); });
38 + tlssocket.on('error', function () { this.xxfunc(this.xxurl, null, this.xxtag); });
39 + } else { func(url, null, tag); }
40 + };
41 +
42 // Return the SHA386 hash of the certificate public key
43 obj.getPublicKeyHash = function (cert) {
44 var publickey = obj.pki.certificateFromPem(cert).publicKey;
meshagent.js
+2 -2
@@ -198,10 +198,10 @@ module.exports.CreateMeshAgent = function (parent, db, ws, req, args, domain) {
198 obj.receivedCommands += 1; // Agent can't send the same command twice on the same connection ever. Block DOS attack path.
199
200 // Check that the server hash matches our own web certificate hash (SHA386)
201 - if (getWebCertHash(obj.domain) != msg.substring(2, 50)) { console.log('Agent connected with bad web certificate hash, holding connection (' + obj.remoteaddr + ').'); return; }
201 + if (getWebCertHash(obj.domain) != msg.substring(2, 50)) { console.log('Agent connected with bad web certificate hash (' + (new Buffer(getWebCertHash(obj.domain), 'binary').toString('hex').substring(0, 10)) + ' != ' + (new Buffer(msg.substring(2, 50), 'binary').toString('hex').substring(0, 10)) + '), holding connection (' + obj.remoteaddr + ').'); return; }
202
203 // Use our server private key to sign the ServerHash + AgentNonce + ServerNonce
204 - obj.agentnonce = msg.substring(50);
204 + obj.agentnonce = msg.substring(50, 98);
205
206 // Check if we got the agent auth confirmation
207 if ((obj.receivedCommands & 8) == 0) {
meshcentral.js
+36 -3
@@ -398,12 +398,45 @@ function CreateMeshCentralServer(config, args) {
398 });
399 };
400
401 - // Start the server with the given certificates
401 + // Start the server with the given certificates, but check if we have web certificates to load
402 obj.StartEx3 = function (certs) {
403 - var i;
403 + var i, webCertLoadCount = 0;
404 obj.certificates = certs;
405 obj.certificateOperations.acceleratorStart(certs); // Set the state of the accelerators
406
407 + // Load any domain web certificates
408 + for (i in obj.config.domains) {
409 + if (obj.config.domains[i].certurl != null) {
410 + // Load web certs
411 + webCertLoadCount++;
412 + obj.certificateOperations.loadCertificate(obj.config.domains[i].certurl, obj.config.domains[i], function (url, cert, xdomain) {
413 + if (cert != null) {
414 + try {
415 + // Decode a RSA certificate and hash the public key
416 + var forgeCert = obj.certificateOperations.forge.pki.certificateFromAsn1(obj.certificateOperations.forge.asn1.fromDer(cert.raw.toString('binary')));
417 + var hash = obj.certificateOperations.forge.pki.getPublicKeyFingerprint(forgeCert.publicKey, { md: obj.certificateOperations.forge.md.sha384.create(), encoding: 'hex' });
418 + xdomain.certhash = hash;
419 + } catch (ex) {
420 + // This may be a ECDSA certificate, hash the entire cert
421 + xdomain.certhash = obj.crypto.createHash('sha384').update(cert.raw).digest('hex');
422 + }
423 + } else {
424 + console.log('Failed to load web certificate at: ' + url);
425 + }
426 + webCertLoadCount--;
427 + if (webCertLoadCount == 0) { obj.StartEx4(); } // Done loading all certificates
428 + });
429 + }
430 + }
431 +
432 + // No certificate to load, start the server
433 + if (webCertLoadCount == 0) { obj.StartEx4(); }
434 + }
435 +
436 + // Start the server with the given certificates
437 + obj.StartEx4 = function () {
438 + var i;
439 +
440 // If the certificate is un-configured, force LAN-only mode
441 if (obj.certificates.CommonName == 'un-configured') { console.log('Server name not configured, running in LAN-only mode.'); obj.args.lanonly = true; }
442
@@ -435,7 +468,7 @@ function CreateMeshCentralServer(config, args) {
468 if ((obj.args.sessiontime != null) && ((typeof obj.args.sessiontime != 'number') || (obj.args.sessiontime < 1))) { delete obj.args.sessiontime; }
469 if (!obj.args.sessionkey) { obj.args.sessionkey = buf.toString('hex').toUpperCase(); }
470
438 - // Start eh web server and if needed, the redirection web server.
471 + // Start the web server and if needed, the redirection web server.
472 obj.webserver = require('./webserver.js').CreateWebServer(obj, obj.db, obj.args, obj.certificates);
473 if (obj.redirserver != null) { obj.redirserver.hookMainWebServer(obj.certificates); }
474
package.json
+1 -1
@@ -1,6 +1,6 @@
1 {
2 "name": "meshcentral",
3 - "version": "0.2.2-n",
3 + "version": "0.2.2-o",
4 "keywords": [
5 "Remote Management",
6 "Intel AMT",
sample-config.json
+4 -2
@@ -25,7 +25,8 @@
25 "userQuota": 1048576,
26 "meshQuota": 248576,
27 "newAccounts": 1,
28 - "footer": "<a href='https://twitter.com/mytwitter'>Twitter</a>"
28 + "footer": "<a href='https://twitter.com/mytwitter'>Twitter</a>",
29 + "_certUrl": "https://192.168.2.106:443/"
30 },
31 "customer1": {
32 "dns": "customer1.myserver.com",
@@ -33,7 +34,8 @@
34 "title2": "TestServer",
35 "newAccounts": 1,
36 "auth": "sspi",
36 - "footer": "Test"
37 + "footer": "Test",
38 + "_certUrl": "https://192.168.2.106:443/"
39 },
40 "info": {
41 "share": "C:\\ExtraWebSite"
webserver.js
+23 -2
@@ -104,18 +104,39 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
104 // Perform hash on web certificate and agent certificate
105 obj.webCertificateHash = parent.certificateOperations.forge.pki.getPublicKeyFingerprint(parent.certificateOperations.forge.pki.certificateFromPem(obj.certificates.web.cert).publicKey, { md: parent.certificateOperations.forge.md.sha384.create(), encoding: 'binary' });
106 obj.webCertificateHashs = { '': obj.webCertificateHash };
107 - for (var i in obj.parent.config.domains) { if (obj.parent.config.domains[i].dns != null) { obj.webCertificateHashs[i] = parent.certificateOperations.forge.pki.getPublicKeyFingerprint(parent.certificateOperations.forge.pki.certificateFromPem(obj.parent.config.domains[i].certs.cert).publicKey, { md: parent.certificateOperations.forge.md.sha384.create(), encoding: 'binary' }); } }
107 obj.webCertificateHashBase64 = new Buffer(parent.certificateOperations.forge.pki.getPublicKeyFingerprint(parent.certificateOperations.forge.pki.certificateFromPem(obj.certificates.web.cert).publicKey, { md: parent.certificateOperations.forge.md.sha384.create(), encoding: 'binary' }), 'binary').toString('base64').replace(/\+/g, '@').replace(/\//g, '$');
108 obj.agentCertificateHashHex = parent.certificateOperations.forge.pki.getPublicKeyFingerprint(parent.certificateOperations.forge.pki.certificateFromPem(obj.certificates.agent.cert).publicKey, { md: parent.certificateOperations.forge.md.sha384.create(), encoding: 'hex' });
109 obj.agentCertificateHashBase64 = new Buffer(parent.certificateOperations.forge.pki.getPublicKeyFingerprint(parent.certificateOperations.forge.pki.certificateFromPem(obj.certificates.agent.cert).publicKey, { md: parent.certificateOperations.forge.md.sha384.create(), encoding: 'binary' }), 'binary').toString('base64').replace(/\+/g, '@').replace(/\//g, '$');
110 obj.agentCertificateAsn1 = parent.certificateOperations.forge.asn1.toDer(parent.certificateOperations.forge.pki.certificateToAsn1(parent.certificateOperations.forge.pki.certificateFromPem(parent.certificates.agent.cert))).getBytes();
111 +
112 + // Compute the hash of all of the web certificates for each domain
113 + for (var i in obj.parent.config.domains) {
114 + if (obj.parent.config.domains[i].certhash != null) {
115 + // If the web certificate hash is provided, use it.
116 + obj.webCertificateHashs[i] = new Buffer(obj.parent.config.domains[i].certhash, 'hex').toString('binary');
117 + } else if ((obj.parent.config.domains[i].dns != null) && (obj.parent.config.domains[i].certs != null)) {
118 + // If the domain has a different DNS name, use a different certificate hash.
119 + try {
120 + // Decode a RSA certificate and hash the public key
121 + obj.webCertificateHashs[i] = parent.certificateOperations.forge.pki.getPublicKeyFingerprint(parent.certificateOperations.forge.pki.certificateFromPem(obj.parent.config.domains[i].certs.cert).publicKey, { md: parent.certificateOperations.forge.md.sha384.create(), encoding: 'binary' });
122 + } catch (ex) {
123 + // This may be a ECDSA certificate, hash the entire cert
124 + var x1 = obj.parent.config.domains[i].certs.cert.indexOf('-----BEGIN CERTIFICATE-----'), x2 = obj.parent.config.domains[i].certs.cert.indexOf('-----END CERTIFICATE-----');
125 + if ((x1 >= 0) && (x2 > x1)) {
126 + obj.webCertificateHashs[i] = obj.crypto.createHash('sha384').update(new Buffer(obj.parent.config.domains[i].certs.cert.substring(x1 + 27, x2), 'base64')).digest('binary');
127 + } else { console.log('ERROR: Unable to decode certificate for domain "' + i + '".'); }
128 + }
129 + }
130 + }
131 +
132 + // If we are running the legacy swarm server, compute the hash for that certificate
133 if (parent.certificates.swarmserver != null) {
134 obj.swarmCertificateAsn1 = parent.certificateOperations.forge.asn1.toDer(parent.certificateOperations.forge.pki.certificateToAsn1(parent.certificateOperations.forge.pki.certificateFromPem(parent.certificates.swarmserver.cert))).getBytes();
135 obj.swarmCertificateHash384 = parent.certificateOperations.forge.pki.getPublicKeyFingerprint(parent.certificateOperations.forge.pki.certificateFromPem(obj.certificates.swarmserver.cert).publicKey, { md: parent.certificateOperations.forge.md.sha384.create(), encoding: 'binary' });
136 obj.swarmCertificateHash256 = parent.certificateOperations.forge.pki.getPublicKeyFingerprint(parent.certificateOperations.forge.pki.certificateFromPem(obj.certificates.swarmserver.cert).publicKey, { md: parent.certificateOperations.forge.md.sha256.create(), encoding: 'binary' });
137 }
138
118 - // Main lists
139 + // Main lists
140 obj.wsagents = {};
141 obj.wssessions = {}; // UserId --> Array Of Sessions
142 obj.wssessions2 = {}; // "UserId + SessionRnd" --> Session (Note that the SessionId is the UserId + / + SessionRnd)