diff --git a/certoperations.js b/certoperations.js index 53fe27df..7b653649 100644 --- a/certoperations.js +++ b/certoperations.js @@ -20,11 +20,25 @@ module.exports.CertificateOperations = function () { obj.fs = require("fs"); obj.forge = require("node-forge"); obj.crypto = require("crypto"); + obj.tls = require('tls'); obj.pki = obj.forge.pki; obj.dirExists = function (filePath) { try { return obj.fs.statSync(filePath).isDirectory(); } catch (err) { return false; } }; obj.getFilesizeInBytes = function (filename) { try { return obj.fs.statSync(filename).size; } catch (err) { return -1; } }; obj.fileExists = function (filePath) { try { return obj.fs.statSync(filePath).isFile(); } catch (err) { return false; } }; + // Return the certificate of the remote HTTPS server + obj.loadCertificate = function (url, tag, func) { + var u = require('url').parse(url); + if (u.protocol == 'https:') { + var tlssocket = obj.tls.connect((u.port ? u.port : 443), u.hostname, { rejectUnauthorized: false }, function () { this.xxcert = this.getPeerCertificate(); this.end(); }); + tlssocket.xxurl = url; + tlssocket.xxfunc = func; + tlssocket.xxtag = tag; + tlssocket.on('end', function () { this.xxfunc(this.xxurl, this.xxcert, this.xxtag); }); + tlssocket.on('error', function () { this.xxfunc(this.xxurl, null, this.xxtag); }); + } else { func(url, null, tag); } + }; + // Return the SHA386 hash of the certificate public key obj.getPublicKeyHash = function (cert) { var publickey = obj.pki.certificateFromPem(cert).publicKey; diff --git a/meshagent.js b/meshagent.js index dcc80006..508816de 100644 --- a/meshagent.js +++ b/meshagent.js @@ -198,10 +198,10 @@ module.exports.CreateMeshAgent = function (parent, db, ws, req, args, domain) { obj.receivedCommands += 1; // Agent can't send the same command twice on the same connection ever. Block DOS attack path. // Check that the server hash matches our own web certificate hash (SHA386) - if (getWebCertHash(obj.domain) != msg.substring(2, 50)) { console.log('Agent connected with bad web certificate hash, holding connection (' + obj.remoteaddr + ').'); return; } + 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; } // Use our server private key to sign the ServerHash + AgentNonce + ServerNonce - obj.agentnonce = msg.substring(50); + obj.agentnonce = msg.substring(50, 98); // Check if we got the agent auth confirmation if ((obj.receivedCommands & 8) == 0) { diff --git a/meshcentral.js b/meshcentral.js index 18d28ee4..4f948b20 100644 --- a/meshcentral.js +++ b/meshcentral.js @@ -398,12 +398,45 @@ function CreateMeshCentralServer(config, args) { }); }; - // Start the server with the given certificates + // Start the server with the given certificates, but check if we have web certificates to load obj.StartEx3 = function (certs) { - var i; + var i, webCertLoadCount = 0; obj.certificates = certs; obj.certificateOperations.acceleratorStart(certs); // Set the state of the accelerators + // Load any domain web certificates + for (i in obj.config.domains) { + if (obj.config.domains[i].certurl != null) { + // Load web certs + webCertLoadCount++; + obj.certificateOperations.loadCertificate(obj.config.domains[i].certurl, obj.config.domains[i], function (url, cert, xdomain) { + if (cert != null) { + try { + // Decode a RSA certificate and hash the public key + var forgeCert = obj.certificateOperations.forge.pki.certificateFromAsn1(obj.certificateOperations.forge.asn1.fromDer(cert.raw.toString('binary'))); + var hash = obj.certificateOperations.forge.pki.getPublicKeyFingerprint(forgeCert.publicKey, { md: obj.certificateOperations.forge.md.sha384.create(), encoding: 'hex' }); + xdomain.certhash = hash; + } catch (ex) { + // This may be a ECDSA certificate, hash the entire cert + xdomain.certhash = obj.crypto.createHash('sha384').update(cert.raw).digest('hex'); + } + } else { + console.log('Failed to load web certificate at: ' + url); + } + webCertLoadCount--; + if (webCertLoadCount == 0) { obj.StartEx4(); } // Done loading all certificates + }); + } + } + + // No certificate to load, start the server + if (webCertLoadCount == 0) { obj.StartEx4(); } + } + + // Start the server with the given certificates + obj.StartEx4 = function () { + var i; + // If the certificate is un-configured, force LAN-only mode if (obj.certificates.CommonName == 'un-configured') { console.log('Server name not configured, running in LAN-only mode.'); obj.args.lanonly = true; } @@ -435,7 +468,7 @@ function CreateMeshCentralServer(config, args) { if ((obj.args.sessiontime != null) && ((typeof obj.args.sessiontime != 'number') || (obj.args.sessiontime < 1))) { delete obj.args.sessiontime; } if (!obj.args.sessionkey) { obj.args.sessionkey = buf.toString('hex').toUpperCase(); } - // Start eh web server and if needed, the redirection web server. + // Start the web server and if needed, the redirection web server. obj.webserver = require('./webserver.js').CreateWebServer(obj, obj.db, obj.args, obj.certificates); if (obj.redirserver != null) { obj.redirserver.hookMainWebServer(obj.certificates); } diff --git a/package.json b/package.json index b189f47c..4e2d61fb 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "meshcentral", - "version": "0.2.2-n", + "version": "0.2.2-o", "keywords": [ "Remote Management", "Intel AMT", diff --git a/sample-config.json b/sample-config.json index ecfd785c..e7173ed8 100644 --- a/sample-config.json +++ b/sample-config.json @@ -25,7 +25,8 @@ "userQuota": 1048576, "meshQuota": 248576, "newAccounts": 1, - "footer": "Twitter" + "footer": "Twitter", + "_certUrl": "https://192.168.2.106:443/" }, "customer1": { "dns": "customer1.myserver.com", @@ -33,7 +34,8 @@ "title2": "TestServer", "newAccounts": 1, "auth": "sspi", - "footer": "Test" + "footer": "Test", + "_certUrl": "https://192.168.2.106:443/" }, "info": { "share": "C:\\ExtraWebSite" diff --git a/webserver.js b/webserver.js index c7fbe152..4d433b35 100644 --- a/webserver.js +++ b/webserver.js @@ -104,18 +104,39 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) { // Perform hash on web certificate and agent certificate 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' }); obj.webCertificateHashs = { '': obj.webCertificateHash }; - 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' }); } } 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, '$'); 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' }); 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, '$'); obj.agentCertificateAsn1 = parent.certificateOperations.forge.asn1.toDer(parent.certificateOperations.forge.pki.certificateToAsn1(parent.certificateOperations.forge.pki.certificateFromPem(parent.certificates.agent.cert))).getBytes(); + + // Compute the hash of all of the web certificates for each domain + for (var i in obj.parent.config.domains) { + if (obj.parent.config.domains[i].certhash != null) { + // If the web certificate hash is provided, use it. + obj.webCertificateHashs[i] = new Buffer(obj.parent.config.domains[i].certhash, 'hex').toString('binary'); + } else if ((obj.parent.config.domains[i].dns != null) && (obj.parent.config.domains[i].certs != null)) { + // If the domain has a different DNS name, use a different certificate hash. + try { + // Decode a RSA certificate and hash the public key + 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' }); + } catch (ex) { + // This may be a ECDSA certificate, hash the entire cert + var x1 = obj.parent.config.domains[i].certs.cert.indexOf('-----BEGIN CERTIFICATE-----'), x2 = obj.parent.config.domains[i].certs.cert.indexOf('-----END CERTIFICATE-----'); + if ((x1 >= 0) && (x2 > x1)) { + obj.webCertificateHashs[i] = obj.crypto.createHash('sha384').update(new Buffer(obj.parent.config.domains[i].certs.cert.substring(x1 + 27, x2), 'base64')).digest('binary'); + } else { console.log('ERROR: Unable to decode certificate for domain "' + i + '".'); } + } + } + } + + // If we are running the legacy swarm server, compute the hash for that certificate if (parent.certificates.swarmserver != null) { obj.swarmCertificateAsn1 = parent.certificateOperations.forge.asn1.toDer(parent.certificateOperations.forge.pki.certificateToAsn1(parent.certificateOperations.forge.pki.certificateFromPem(parent.certificates.swarmserver.cert))).getBytes(); 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' }); 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' }); } - // Main lists + // Main lists obj.wsagents = {}; obj.wssessions = {}; // UserId --> Array Of Sessions obj.wssessions2 = {}; // "UserId + SessionRnd" --> Session (Note that the SessionId is the UserId + / + SessionRnd)