Improved crypto and removed dependency on WebSocket library, using ws instead.

This commit is contained in:
Ylian Saint-Hilaire 2017-10-14 23:22:19 -07:00
parent 3632741d9e
commit 1952d75860
19 changed files with 379 additions and 439 deletions

Binary file not shown.

Binary file not shown.

View File

@ -29,6 +29,7 @@ function createMeshCore(agent) {
var selfInfoUpdateTimer = null;
var http = require('http');
var fs = require('fs');
var rtc = require('ILibWebRTC');
var wifiScannerLib = null;
var wifiScanner = null;
@ -417,8 +418,26 @@ function createMeshCore(agent) {
if (len > 0) { this.write(buf.slice(0, len)); } else { fs.closeSync(this.httprequest.downloadFile); this.httprequest.downloadFile = undefined; this.end(); }
return;
}
// Setup remote desktop & terminal without using native pipes
if ((this.httprequest.desktop) && (obj.useNativePipes == false)) { this.httprequest.desktop.kvm.write(data); return; }
if ((this.httprequest.desktop) && (obj.useNativePipes == false)) {
if (data.length > 21 && data.toString().startsWith('**********%%%%%%###**')) {
var controlMsg = JSON.parse(data.toString().substring(21));
if (controlMsg.type == 'offer') {
this.webrtc = rtc.createConnection();
this.webrtc.on('connected', function () { sendConsoleText('OnWebRTC_Connected'); });
this.webrtc.on('dataChannel', function () { sendConsoleText('OnWebRTC_DataChannel'); });
var counterOffer = this.webrtc.setOffer(controlMsg.sdp);
this.write('**********%%%%%%###**' + JSON.stringify({ type: 'answer', sdp: counterOffer }));
sendConsoleText('counterOfferSent');
} else {
sendConsoleText(JSON.stringify(controlMsg));
}
} else {
this.httprequest.desktop.kvm.write(data);
}
return;
}
if ((this.httprequest.terminal) && (obj.useNativePipes == false)) { this.httprequest.terminal.write(data); return; }
if (this.httprequest.state == 0) {

View File

@ -12,24 +12,16 @@ module.exports.CertificateOperations = function () {
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 SHA256 hash of the certificate public key
obj.getPublicKeyHash = function(cert) {
var publickey = obj.pki.certificateFromPem(cert).publicKey;
return obj.pki.getPublicKeyFingerprint(publickey, { encoding: 'hex', md: obj.forge.md.sha256.create() });
}
// Return a random nonce (TODO: weak crypto)
obj.xxRandomNonceX = "abcdef0123456789";
obj.xxRandomNonce = function (length) {
var r = "";
for (var i = 0; i < length; i++) { r += obj.xxRandomNonceX.charAt(Math.floor(Math.random() * obj.xxRandomNonceX.length)); }
return r;
// Return the SHA386 hash of the certificate public key
obj.getPublicKeyHash = function (cert) {
var publickey = obj.pki.certificateFromPem(cert).publicKey;
return obj.pki.getPublicKeyFingerprint(publickey, { encoding: 'hex', md: obj.forge.md.sha384.create() });
}
// Create a self-signed certificate
obj.GenerateRootCertificate = function (addThumbPrintToName, commonName, country, organization) {
var keys = obj.pki.rsa.generateKeyPair(2048);
var keys = obj.pki.rsa.generateKeyPair(3072);
var cert = obj.pki.createCertificate();
cert.publicKey = keys.publicKey;
cert.serialNumber = '' + Math.floor((Math.random() * 100000) + 1); ;
@ -55,14 +47,14 @@ module.exports.CertificateOperations = function () {
}, {
name: 'subjectKeyIdentifier'
}]);
cert.sign(keys.privateKey, obj.forge.md.sha256.create());
cert.sign(keys.privateKey, obj.forge.md.sha384.create());
return { cert: cert, key: keys.privateKey };
}
// Issue a certificate from a root
obj.IssueWebServerCertificate = function (rootcert, addThumbPrintToName, commonName, country, organization, extKeyUsage) {
var keys = obj.pki.rsa.generateKeyPair(2048);
obj.IssueWebServerCertificate = function (rootcert, addThumbPrintToName, commonName, country, organization, extKeyUsage, strong) {
var keys = obj.pki.rsa.generateKeyPair((strong == true) ? 3072 : 2048);
var cert = obj.pki.createCertificate();
cert.publicKey = keys.publicKey;
cert.serialNumber = '' + Math.floor((Math.random() * 100000) + 1); ;
@ -128,8 +120,7 @@ module.exports.CertificateOperations = function () {
}]
if (subjectAltName != null) extensions.push(subjectAltName);
cert.setExtensions(extensions);
cert.sign(rootcert.key, obj.forge.md.sha256.create());
cert.sign(rootcert.key, obj.forge.md.sha384.create());
return { cert: cert, key: keys.privateKey };
}
@ -234,7 +225,7 @@ module.exports.CertificateOperations = function () {
if (xorganizationField != null) { xorganization = xorganizationField.value; }
if ((r.CommonName == commonName) && (xcountry == country) && (xorganization == organization) && (r.AmtMpsName == commonName)) { if (func != undefined) { func(r); } return r; } else { forceWebCertGen = 1; } // If the certificate matches what we want, keep it.
}
console.log('Generating certificates...');
console.log('Generating certificates, may take a few minutes...');
var rootCertAndKey, rootCertificate, rootPrivateKey, rootName;
if (r.root == undefined) {
@ -255,7 +246,7 @@ module.exports.CertificateOperations = function () {
// If the web certificate does not exist, create one
var webCertAndKey, webCertificate, webPrivateKey;
if ((r.web == null) || (forceWebCertGen == 1)) {
webCertAndKey = obj.IssueWebServerCertificate(rootCertAndKey, false, commonName, country, organization);
webCertAndKey = obj.IssueWebServerCertificate(rootCertAndKey, false, commonName, country, organization, null, true);
webCertificate = obj.pki.certificateToPem(webCertAndKey.cert);
webPrivateKey = obj.pki.privateKeyToPem(webCertAndKey.key);
obj.fs.writeFileSync(directory + '/webserver-cert-public.crt', webCertificate);
@ -270,7 +261,7 @@ module.exports.CertificateOperations = function () {
// If the Intel AMT MPS certificate does not exist, create one
var mpsCertAndKey, mpsCertificate, mpsPrivateKey;
if ((r.mps == null) || (forceWebCertGen == 1)) {
mpsCertAndKey = obj.IssueWebServerCertificate(rootCertAndKey, false, commonName, country, organization);
mpsCertAndKey = obj.IssueWebServerCertificate(rootCertAndKey, false, commonName, country, organization, null, false);
mpsCertificate = obj.pki.certificateToPem(mpsCertAndKey.cert);
mpsPrivateKey = obj.pki.privateKeyToPem(mpsCertAndKey.key);
obj.fs.writeFileSync(directory + '/mpsserver-cert-public.crt', mpsCertificate);
@ -285,7 +276,7 @@ module.exports.CertificateOperations = function () {
// If the Intel AMT console certificate does not exist, create one
var consoleCertAndKey, consoleCertificate, consolePrivateKey, amtConsoleName = 'MeshCentral';
if (r.console == null) {
consoleCertAndKey = obj.IssueWebServerCertificate(rootCertAndKey, false, amtConsoleName, country, organization, { name: 'extKeyUsage', clientAuth: true, '2.16.840.1.113741.1.2.1': true, '2.16.840.1.113741.1.2.2': true, '2.16.840.1.113741.1.2.3': true }); // Intel AMT Remote, Agent and Activation usages
consoleCertAndKey = obj.IssueWebServerCertificate(rootCertAndKey, false, amtConsoleName, country, organization, { name: 'extKeyUsage', clientAuth: true, '2.16.840.1.113741.1.2.1': true, '2.16.840.1.113741.1.2.2': true, '2.16.840.1.113741.1.2.3': true }, false); // Intel AMT Remote, Agent and Activation usages
consoleCertificate = obj.pki.certificateToPem(consoleCertAndKey.cert);
consolePrivateKey = obj.pki.privateKeyToPem(consoleCertAndKey.key);
obj.fs.writeFileSync(directory + '/amtconsole-cert-public.crt', consoleCertificate);
@ -301,7 +292,7 @@ module.exports.CertificateOperations = function () {
// If the mesh agent server certificate does not exist, create one
var agentCertAndKey, agentCertificate, agentPrivateKey;
if (r.agent == null) {
agentCertAndKey = obj.IssueWebServerCertificate(rootCertAndKey, true, 'MeshCentralAgentServer');
agentCertAndKey = obj.IssueWebServerCertificate(rootCertAndKey, true, 'MeshCentralAgentServer', null, true);
agentCertificate = obj.pki.certificateToPem(agentCertAndKey.cert);
agentPrivateKey = obj.pki.privateKeyToPem(agentCertAndKey.key);
obj.fs.writeFileSync(directory + '/agentserver-cert-public.crt', agentCertificate);

91
db.js
View File

@ -43,7 +43,7 @@ module.exports.CreateDB = function (args, datapath) {
if ((docs.length == 1) && (docs[0].value != null)) {
obj.identifier = docs[0].value;
} else {
obj.identifier = new Buffer(require('crypto').randomBytes(32), 'binary').toString('hex');
obj.identifier = new Buffer(require('crypto').randomBytes(48), 'binary').toString('hex');
obj.Set({ _id: 'DatabaseIdentifier', value: obj.identifier });
}
});
@ -53,94 +53,9 @@ module.exports.CreateDB = function (args, datapath) {
var ver = 0;
if (docs && docs.length == 1) { ver = docs[0].value; }
// Upgrade schema 0 to schema 1
if (ver == 0) {
// Add the default domain to all users
obj.GetAllType('user', function (err, docs) {
for (var id in docs) {
var oldid, changed = false;
if (docs[id].subscriptions) { delete docs[id].subscriptions; changed = true; }
if (docs[id].domain == undefined) {
docs[id].domain = '';
oldid = docs[id]._id;
docs[id]._id = 'user//' + docs[id]._id.substring(5);
changed = true;
}
if (docs[id].links) {
for (var linkid in docs[id].links) {
var linkid2 = 'mesh//' + linkid.substring(5);
docs[id].links[linkid2] = docs[id].links[linkid];
delete docs[id].links[linkid];
}
}
if (changed == true) {
if (oldid) obj.Remove(oldid);
obj.Set(docs[id]);
}
}
// Add the default domain to all nodes
obj.GetAllType('node', function (err, docs) {
for (var id in docs) {
var oldid, changed = false;
if (docs[id].domain == undefined) {
docs[id].domain = '';
oldid = docs[id]._id;
docs[id]._id = 'node//' + docs[id]._id.substring(5);
docs[id].meshid = 'mesh//' + docs[id].meshid.substring(5);
changed = true;
}
if (changed == true) {
if (oldid) obj.Remove(oldid);
obj.Set(docs[id]);
}
}
});
// Add the default domain to all meshes
obj.GetAllType('mesh', function (err, docs) {
for (var id in docs) {
var oldid, changed = false;
if (docs[id].domain == undefined) {
docs[id].domain = '';
oldid = docs[id]._id;
docs[id]._id = 'mesh//' + docs[id]._id.substring(5);
if (docs[id].links) {
for (var linkid in docs[id].links) {
var linkid2 = 'user//' + linkid.substring(5);
docs[id].links[linkid2] = docs[id].links[linkid];
delete docs[id].links[linkid];
}
}
changed = true;
}
if (changed == true) {
if (oldid) obj.Remove(oldid);
obj.Set(docs[id]);
}
}
});
// Add the default domain to all events
obj.GetAllType('event', function (err, docs) {
var changed = false;
for (var id in docs) {
var oldid;
changed = true;
if (docs[id].domain == undefined) {
docs[id].domain = '';
obj.Set(docs[id]);
}
}
obj.Set({ _id: 'SchemaVersion', value: 1 });
ver = 1;
if (changed == true) { console.log('Upgraded database to version 1.'); }
func(ver);
});
});
// TODO: Any schema upgrades here...
} else { func(ver); }
func(ver);
});
}

View File

@ -27,6 +27,7 @@ module.exports.CreateMeshAgent = function (parent, db, ws, req, args, domain) {
obj.agentUpdate = null;
var agentUpdateBlockSize = 65520;
obj.remoteaddr = obj.ws._socket.remoteAddress;
obj.useSHA386 = false;
if (obj.remoteaddr.startsWith('::ffff:')) { obj.remoteaddr = obj.remoteaddr.substring(7); }
ws._socket.setKeepAlive(true, 0); // Set TCP keep alive
@ -49,12 +50,7 @@ module.exports.CreateMeshAgent = function (parent, db, ws, req, args, domain) {
// When data is received from the mesh agent web socket
ws.on('message', function (msg) {
if (msg.length < 2) return;
if (typeof msg == 'object') {
// Convert the buffer into a string
var msg2 = "";
for (var i = 0; i < msg.length; i++) { msg2 += String.fromCharCode(msg[i]); }
msg = msg2;
}
if (typeof msg == 'object') { msg = msg.toString('binary'); } // TODO: Could change this entire method to use Buffer instead of binary string
if (obj.authenticated == 2) { // We are authenticated
if (msg.charCodeAt(0) == 123) { processAgentData(msg); }
@ -67,7 +63,7 @@ module.exports.CreateMeshAgent = function (parent, db, ws, req, args, domain) {
// We need to check if the core is current.
// TODO: Check if we have a mesh specific core. If so, use that.
var agentMeshCoreHash = null;
if (msg.length == 36) { agentMeshCoreHash = msg.substring(4, 36); }
if (msg.length == 52) { agentMeshCoreHash = msg.substring(4, 52); }
if (agentMeshCoreHash != obj.parent.parent.defaultMeshCoreHash) {
if (obj.agentCoreCheck < 5) { // This check is in place to avoid a looping core update.
if (obj.parent.parent.defaultMeshCoreHash == null) {
@ -84,16 +80,16 @@ module.exports.CreateMeshAgent = function (parent, db, ws, req, args, domain) {
}
}
else if (cmdid == 12) { // MeshCommand_AgentHash
if ((msg.length == 36) && (obj.agentInfo != null) && (obj.agentInfo.update == true)) {
if ((msg.length == 52) && (obj.agentExeInfo != null) && (obj.agentExeInfo.update == true)) {
var agenthash = obj.common.rstr2hex(msg.substring(4)).toLowerCase();
if (agenthash != obj.agentInfo.hash) {
if (agenthash != obj.agentExeInfo.hash) {
// Mesh agent update required
console.log('Agent update required, NodeID=0x' + obj.nodeid.substring(0, 16) + ', ' + obj.agentInfo.desc);
obj.fs.open(obj.agentInfo.path, 'r', function (err, fd) {
console.log('Agent update required, NodeID=0x' + obj.nodeid.substring(0, 16) + ', ' + obj.agentExeInfo.desc);
obj.fs.open(obj.agentExeInfo.path, 'r', function (err, fd) {
if (err) { return console.error(err); }
obj.agentUpdate = { oldHash: agenthash, ptr: 0, buf: new Buffer(agentUpdateBlockSize + 4), fd: fd };
// We got the agent file open on the server side, tell the agent we are sending an update starting with the SHA256 hash of the result
// We got the agent file open on the server side, tell the agent we are sending an update starting with the SHA384 hash of the result
//console.log("Agent update file open.");
obj.send(obj.common.ShortToStr(13) + obj.common.ShortToStr(0)); // Command 13, start mesh agent download
@ -136,7 +132,7 @@ module.exports.CreateMeshAgent = function (parent, db, ws, req, args, domain) {
if (len < agentUpdateBlockSize) {
//console.log("Agent update sent");
obj.send(obj.common.ShortToStr(13) + obj.common.ShortToStr(0) + obj.common.hex2rstr(obj.agentInfo.hash)); // Command 13, end mesh agent download, send agent SHA256 hash
obj.send(obj.common.ShortToStr(13) + obj.common.ShortToStr(0) + obj.common.hex2rstr(obj.agentInfo.hash)); // Command 13, end mesh agent download, send agent SHA384 hash
obj.fs.close(obj.agentUpdate.fd);
obj.agentUpdate = null;
}
@ -152,18 +148,18 @@ module.exports.CreateMeshAgent = function (parent, db, ws, req, args, domain) {
var cmd = obj.common.ReadShort(msg, 0);
if (cmd == 1) {
// Agent authentication request
if ((msg.length != 66) || ((obj.receivedCommands & 1) != 0)) return;
if ((msg.length != 98) || ((obj.receivedCommands & 1) != 0)) return;
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 out own web certificate hash
if (obj.parent.webCertificatHash != msg.substring(2, 34)) { obj.close(); return; }
// Check that the server hash matches out own web certificate hash (SHA386)
if (obj.parent.webCertificatHash != msg.substring(2, 50)) { obj.close(); return; }
// Use our server private key to sign the ServerHash + AgentNonce + ServerNonce
var privateKey = obj.forge.pki.privateKeyFromPem(obj.parent.certificates.agent.key);
var md = obj.forge.md.sha256.create();
var md = obj.forge.md.sha384.create();
md.update(msg.substring(2), 'binary');
md.update(obj.nonce, 'binary');
obj.agentnonce = msg.substring(34);
obj.agentnonce = msg.substring(50);
// Send back our certificate + signature
obj.send(obj.common.ShortToStr(2) + obj.common.ShortToStr(parent.agentCertificatAsn1.length) + parent.agentCertificatAsn1 + privateKey.sign(md)); // Command 2, certificate + signature
@ -183,15 +179,15 @@ module.exports.CreateMeshAgent = function (parent, db, ws, req, args, domain) {
obj.unauth = {};
obj.unauth.nodeCert = null;
try { obj.unauth.nodeCert = obj.forge.pki.certificateFromAsn1(obj.forge.asn1.fromDer(msg.substring(4, 4 + certlen))); } catch (e) { return; }
obj.unauth.nodeid = obj.forge.pki.getPublicKeyFingerprint(obj.unauth.nodeCert.publicKey, { encoding: 'hex', md: obj.forge.md.sha256.create() });
obj.unauth.nodeid = obj.forge.pki.getPublicKeyFingerprint(obj.unauth.nodeCert.publicKey, { encoding: 'hex', md: obj.forge.md.sha384.create() });
// Check the agent signature if we can
if (obj.agentnonce == null) { obj.unauthsign = msg.substring(4 + certlen); } else { if (processAgentSignature(msg.substring(4 + certlen)) == false) { disonnect(); return; } }
if (obj.agentnonce == null) { obj.unauthsign = msg.substring(4 + certlen); } else { if (processAgentSignature(msg.substring(4 + certlen)) == false) { console.log('Bad Agent Signature'); obj.close(); return; } }
completeAgentConnection();
}
else if (cmd == 3) {
// Agent meshid
if ((msg.length < 56) || ((obj.receivedCommands & 4) != 0)) return;
if ((msg.length < 72) || ((obj.receivedCommands & 4) != 0)) return;
obj.receivedCommands += 4; // Agent can't send the same command twice on the same connection ever. Block DOS attack path.
// Set the meshid
@ -200,10 +196,10 @@ module.exports.CreateMeshAgent = function (parent, db, ws, req, args, domain) {
obj.agentInfo.agentId = obj.common.ReadInt(msg, 6);
obj.agentInfo.agentVersion = obj.common.ReadInt(msg, 10);
obj.agentInfo.platformType = obj.common.ReadInt(msg, 14);
obj.meshid = obj.common.rstr2hex(msg.substring(18, 50)).toUpperCase();
obj.agentInfo.capabilities = obj.common.ReadInt(msg, 50);
var computerNameLen = obj.common.ReadShort(msg, 54);
obj.agentInfo.computerName = msg.substring(56, 56 + computerNameLen);
obj.meshid = obj.common.rstr2hex(msg.substring(18, 66)).toUpperCase();
obj.agentInfo.capabilities = obj.common.ReadInt(msg, 66);
var computerNameLen = obj.common.ReadShort(msg, 70);
obj.agentInfo.computerName = msg.substring(72, 72 + computerNameLen);
obj.dbMeshKey = 'mesh/' + obj.domain.id + '/' + obj.meshid;
completeAgentConnection();
}
@ -218,8 +214,8 @@ module.exports.CreateMeshAgent = function (parent, db, ws, req, args, domain) {
// obj.ws._socket._parent.on('close', function (req) { obj.parent.parent.debug(1, 'Agent TCP disconnect ' + obj.nodeid + ' (' + obj.remoteaddr + ')'); });
// Start authenticate the mesh agent by sending a auth nonce & server TLS cert hash.
// Send 256 bits SHA256 hash of TLS cert public key + 256 bits nonce
obj.nonce = obj.forge.random.getBytesSync(32);
// Send 384 bits SHA384 hash of TLS cert public key + 384 bits nonce
obj.nonce = obj.forge.random.getBytesSync(48);
obj.send(obj.common.ShortToStr(1) + parent.webCertificatHash + obj.nonce); // Command 1, hash + nonce
// Once we get all the information about an agent, run this to hook everything up to the server
@ -237,7 +233,6 @@ module.exports.CreateMeshAgent = function (parent, db, ws, req, args, domain) {
// Mark when we connected to this agent
obj.connectTime = Date.now();
if (nodes.length == 0) {
// This node does not exist, create it.
device = { type: 'node', mtype: mesh.mtype, _id: obj.dbNodeKey, icon: obj.agentInfo.platformType, meshid: obj.dbMeshKey, name: obj.agentInfo.computerName, domain: domain.id, agent: { ver: obj.agentInfo.agentVersion, id: obj.agentInfo.agentId, caps: obj.agentInfo.capabilities }, host: null };
@ -292,8 +287,8 @@ module.exports.CreateMeshAgent = function (parent, db, ws, req, args, domain) {
if ((obj.agentInfo.capabilities & 16) != 0) { obj.send(obj.common.ShortToStr(11) + obj.common.ShortToStr(0)); } // Command 11, ask for mesh core hash.
// Check if we need to make an native update check
obj.agentInfo = obj.parent.parent.meshAgentBinaries[obj.agentInfo.agentId];
if ((obj.agentInfo != null) && (obj.agentInfo.update == true)) { obj.send(obj.common.ShortToStr(12) + obj.common.ShortToStr(0)); } // Ask the agent for it's executable binary hash
obj.agentExeInfo = obj.parent.parent.meshAgentBinaries[obj.agentInfo.agentId];
if ((obj.agentExeInfo != null) && (obj.agentExeInfo.update == true)) { obj.send(obj.common.ShortToStr(12) + obj.common.ShortToStr(0)); } // Ask the agent for it's executable binary hash
// Check if we already have IP location information for this node
obj.db.Get('iploc_' + obj.remoteaddr, function (err, iplocs) {
@ -337,11 +332,11 @@ module.exports.CreateMeshAgent = function (parent, db, ws, req, args, domain) {
// Verify the agent signature
function processAgentSignature(msg) {
var md = obj.forge.md.sha256.create(); // TODO: Switch this to SHA256 on node instead of forge.
var md = obj.forge.md.sha384.create(); // TODO: Switch this to SHA384 on node instead of forge.
md.update(obj.parent.webCertificatHash, 'binary');
md.update(obj.nonce, 'binary');
md.update(obj.agentnonce, 'binary');
if (obj.unauth.nodeCert.publicKey.verify(md.digest().bytes(), msg) == false) return false;
if (obj.unauth.nodeCert.publicKey.verify(md.digest().bytes(), msg) == false) { return false; }
// Connection is a success, clean up
obj.nodeid = obj.unauth.nodeid.toUpperCase();

View File

@ -29,8 +29,8 @@ function CreateMeshCentralServer() {
obj.certificateOperations = require('./certoperations.js').CertificateOperations();
obj.defaultMeshCore = null;
obj.defaultMeshCoreHash = null;
obj.meshAgentBinaries = {}; // Mesh Agent Binaries, Architecture type --> { hash:(sha256 hash), size:(binary size), path:(binary path) }
obj.meshAgentInstallScripts = {}; // Mesh Install Scripts, Script ID -- { hash:(sha256 hash), size:(binary size), path:(binary path) }
obj.meshAgentBinaries = {}; // Mesh Agent Binaries, Architecture type --> { hash:(sha384 hash), size:(binary size), path:(binary path) }
obj.meshAgentInstallScripts = {}; // Mesh Install Scripts, Script ID -- { hash:(sha384 hash), size:(binary size), path:(binary path) }
obj.multiServer = null;
obj.currentVer = null;
obj.maintenanceTimer = null;
@ -38,11 +38,11 @@ function CreateMeshCentralServer() {
// Setup the default configuration and files paths
if ((__dirname.endsWith('/node_modules/meshcentral')) || (__dirname.endsWith('\\node_modules\\meshcentral')) || (__dirname.endsWith('/node_modules/meshcentral/')) || (__dirname.endsWith('\\node_modules\\meshcentral\\'))) {
obj.datapath = obj.path.join(__dirname, '../../.meshcentral-data');
obj.filespath = obj.path.join(__dirname, '../../.meshcentral-files');
obj.datapath = obj.path.join(__dirname, '../../meshcentral-data');
obj.filespath = obj.path.join(__dirname, '../../meshcentral-files');
} else {
obj.datapath = obj.path.join(__dirname, '../.meshcentral-data');
obj.filespath = obj.path.join(__dirname, '../.meshcentral-files');
obj.datapath = obj.path.join(__dirname, '../meshcentral-data');
obj.filespath = obj.path.join(__dirname, '../meshcentral-files');
}
// Create data and files folders if needed
@ -64,12 +64,12 @@ function CreateMeshCentralServer() {
try { require('./pass').hash('test', function () { }); } catch (e) { console.log('Old version of node, must upgrade.'); return; } // TODO: Not sure if this test works or not.
// Check for invalid arguments
var validArguments = ['_', 'notls', 'user', 'port', 'mpsport', 'redirport', 'cert', 'deletedomain', 'deletedefaultdomain', 'showusers', 'shownodes', 'showmeshes', 'showevents', 'showpower', 'showiplocations', 'help', 'exactports', 'install', 'uninstall', 'start', 'stop', 'restart', 'debug', 'filespath', 'datapath', 'noagentupdate', 'launch', 'noserverbackup', 'mongodb', 'mongodbcol', 'wanonly', 'lanonly', 'nousers', 'mpsdebug', 'mpspass', 'ciralocalfqdn', 'dbexport', 'dbimport', 'selfupdate', 'tlsoffload', 'userallowedip'];
var validArguments = ['_', 'notls', 'user', 'port', 'mpsport', 'redirport', 'cert', 'deletedomain', 'deletedefaultdomain', 'showall', 'showusers', 'shownodes', 'showmeshes', 'showevents', 'showpower', 'showiplocations', 'help', 'exactports', 'install', 'uninstall', 'start', 'stop', 'restart', 'debug', 'filespath', 'datapath', 'noagentupdate', 'launch', 'noserverbackup', 'mongodb', 'mongodbcol', 'wanonly', 'lanonly', 'nousers', 'mpsdebug', 'mpspass', 'ciralocalfqdn', 'dbexport', 'dbimport', 'selfupdate', 'tlsoffload', 'userallowedip'];
for (var arg in obj.args) { obj.args[arg.toLocaleLowerCase()] = obj.args[arg]; if (validArguments.indexOf(arg.toLocaleLowerCase()) == -1) { console.log('Invalid argument "' + arg + '", use --help.'); return; } }
if (obj.args.mongodb == true) { console.log('Must specify: --mongodb [connectionstring] \r\nSee https://docs.mongodb.com/manual/reference/connection-string/ for MongoDB connection string.'); return; }
if ((obj.args.help == true) || (obj.args['?'] == true)) {
console.log('MeshCentral2 Beta 1, a web-based remote computer management web portal.\r\n');
console.log('MeshCentral2 Beta 2, a web-based remote computer management web portal.\r\n');
if (obj.platform == 'win32') {
console.log('Run as a Windows Service');
console.log(' --install/uninstall Install Meshcentral as a background service.');
@ -198,7 +198,7 @@ function CreateMeshCentralServer() {
// Validate the domains, this is used for multi-hosting
if (obj.config.domains == null) { obj.config.domains = {}; }
if (obj.config.domains[''] == null) { obj.config.domains[''] = { }; }
var xdomains = {}; for (var i in obj.config.domains) { if (!obj.config.domains[i].title) { obj.config.domains[i].title = 'MeshCentral'; } if (!obj.config.domains[i].title2) { obj.config.domains[i].title2 = '2.0 Beta 1'; } xdomains[i.toLowerCase()] = obj.config.domains[i]; } obj.config.domains = xdomains;
var xdomains = {}; for (var i in obj.config.domains) { if (!obj.config.domains[i].title) { obj.config.domains[i].title = 'MeshCentral'; } if (!obj.config.domains[i].title2) { obj.config.domains[i].title2 = '2.0 Beta 2'; } xdomains[i.toLowerCase()] = obj.config.domains[i]; } obj.config.domains = xdomains;
var bannedDomains = ['public', 'private', 'images', 'scripts', 'styles', 'views']; // List of banned domains
for (var i in obj.config.domains) { for (var j in bannedDomains) { if (i == bannedDomains[j]) { console.log("ERROR: Domain '" + i + "' is not allowed domain name in ./data/config.json."); return; } } }
for (var i in obj.config.domains) {
@ -223,6 +223,7 @@ function CreateMeshCentralServer() {
// See if any database operations needs to be completed
if (obj.args.deletedomain) { obj.db.DeleteDomain(obj.args.deletedomain, function () { console.log('Deleted domain ' + obj.args.deletedomain + '.'); process.exit(); }); return; }
if (obj.args.deletedefaultdomain) { obj.db.DeleteDomain('', function () { console.log('Deleted default domain.'); process.exit(); }); return; }
if (obj.args.showall) { obj.db.GetAll(function (err, docs) { console.log(docs); process.exit(); }); return; }
if (obj.args.showusers) { obj.db.GetAllType('user', function (err, docs) { console.log(docs); process.exit(); }); return; }
if (obj.args.shownodes) { obj.db.GetAllType('node', function (err, docs) { console.log(docs); process.exit(); }); return; }
if (obj.args.showmeshes) { obj.db.GetAllType('mesh', function (err, docs) { console.log(docs); process.exit(); }); return; }
@ -269,7 +270,7 @@ function CreateMeshCentralServer() {
while (obj.dbconfig.amtWsEventSecret == null) { process.nextTick(); }
var username = buf.toString('hex');
var nodeid = obj.args.getwspass;
var pass = require('crypto').createHash('sha256').update(username.toLowerCase() + ":" + nodeid.toUpperCase() + ":" + obj.dbconfig.amtWsEventSecret).digest("base64").substring(0, 12).split("/").join("x").split("\\").join("x");
var pass = require('crypto').createHash('sha384').update(username.toLowerCase() + ":" + nodeid.toUpperCase() + ":" + obj.dbconfig.amtWsEventSecret).digest("base64").substring(0, 12).split("/").join("x").split("\\").join("x");
console.log('--- Intel(r) AMT WSMAN eventing credentials ---');
console.log('Username: ' + username);
console.log('Password: ' + pass);
@ -299,7 +300,7 @@ function CreateMeshCentralServer() {
obj.updateMeshAgentInstallScripts();
// Setup and start the web server
require('crypto').randomBytes(32, function (err, buf) {
require('crypto').randomBytes(48, function (err, buf) {
// Setup Mesh Multi-Server if needed
obj.multiServer = require('./multiserver.js').CreateMultiServer(obj, obj.args);
if (obj.multiServer != null) {
@ -657,7 +658,7 @@ function CreateMeshCentralServer() {
// Set the new default meshcore.js
meshCore = obj.common.IntToStr(0) + moduleAdditions + meshCore; // Add the 4 bytes encoding type & flags (Set to 0 for raw)
obj.defaultMeshCore = meshCore;
obj.defaultMeshCoreHash = obj.crypto.createHash('sha256').update(meshCore).digest("binary");
obj.defaultMeshCoreHash = obj.crypto.createHash('sha384').update(meshCore).digest("binary");
if (func != null) { func(true); }
}
@ -690,7 +691,7 @@ function CreateMeshCentralServer() {
});
stream.info = meshAgentsInstallScriptList[scriptid];
stream.agentpath = scriptpath;
stream.hash = obj.crypto.createHash('sha256', stream);
stream.hash = obj.crypto.createHash('sha384', stream);
} catch (e) { }
}
}
@ -748,7 +749,7 @@ function CreateMeshCentralServer() {
});
stream.info = meshAgentsArchitectureNumbers[archid];
stream.agentpath = agentpath;
stream.hash = obj.crypto.createHash('sha256', stream);
stream.hash = obj.crypto.createHash('sha384', stream);
} catch (e) { }
}
}
@ -817,7 +818,7 @@ function InstallModule(modulename, func, tag1, tag2) {
process.on('SIGINT', function () { if (meshserver != null) { meshserver.Stop(); meshserver = null; } console.log('Server Ctrl-C exit...'); process.exit(); });
// Build the list of required modules
var modules = ['nedb', 'https', 'unzip', 'xmldom', 'express', 'mongojs', 'archiver', 'websocket', 'minimist', 'multiparty', 'node-forge', 'express-ws', 'compression', 'body-parser', 'connect-redis', 'express-session', 'express-handlebars'];
var modules = ['nedb', 'https', 'unzip', 'xmldom', 'express', 'mongojs', 'archiver', 'minimist', 'multiparty', 'node-forge', 'express-ws', 'compression', 'body-parser', 'connect-redis', 'express-session', 'express-handlebars'];
if (require('os').platform() == 'win32') { modules.push("node-windows"); }
// Run as a command line, if we are not using service arguments, don't need to install the service package.

View File

@ -6,9 +6,9 @@
// Construct a MeshRelay object, called upon connection
module.exports.CreateMeshRelayKey = function (parent, func) {
parent.crypto.randomBytes(16, function (err, buf) {
parent.crypto.randomBytes(48, function (err, buf) {
var key = buf.toString('hex').toUpperCase() + ':' + Date.now();
key += ':' + parent.crypto.createHmac('SHA256', parent.relayRandom).update(key).digest('hex');
key += ':' + parent.crypto.createHmac('SHA384', parent.relayRandom).update(key).digest('hex');
func(key);
});
}
@ -41,7 +41,7 @@ module.exports.CreateMeshRelay = function (parent, ws, req) {
// Check the identifier, if running without TLS, skip this.
var ids = obj.id.split(':');
if (ids.length != 3) { obj.ws.close(); obj.id = null; return null; } // Invalid ID, drop this.
if (parent.crypto.createHmac('SHA256', parent.relayRandom).update(ids[0] + ':' + ids[1]).digest('hex') != ids[2]) { obj.ws.close(); obj.id = null; return null; } // Invalid HMAC, drop this.
if (parent.crypto.createHmac('SHA384', parent.relayRandom).update(ids[0] + ':' + ids[1]).digest('hex') != ids[2]) { obj.ws.close(); obj.id = null; return null; } // Invalid HMAC, drop this.
if ((Date.now() - parseInt(ids[1])) > 120000) { obj.ws.close(); obj.id = null; return null; } // Expired time, drop this.
obj.id = ids[0];
}
@ -107,6 +107,8 @@ module.exports.CreateMeshRelay = function (parent, ws, req) {
// When data is received from the mesh relay web socket
ws.on('message', function (data) {
//console.log(typeof data);
//if (typeof data == 'string') console.log(data);
if (this.peer != null) { try { this.pause(); this.peer.send(data, ws.flushSink); } catch (e) { } }
});

View File

@ -17,21 +17,22 @@ module.exports.CreateMeshScanner = function (parent) {
var periodicScanTime = (60000 * 20); // Interval between scans, 20 minutes.
var membershipIPv4 = '239.255.255.235';
var membershipIPv6 = 'FF02:0:0:0:0:0:0:FE';
obj.agentCertificatHashHex = parent.certificateOperations.forge.pki.getPublicKeyFingerprint(parent.certificateOperations.forge.pki.certificateFromPem(parent.certificates.agent.cert).publicKey, { md: parent.certificateOperations.forge.md.sha256.create(), encoding: 'hex' });
obj.agentCertificatHashHex = parent.certificateOperations.forge.pki.getPublicKeyFingerprint(parent.certificateOperations.forge.pki.certificateFromPem(parent.certificates.agent.cert).publicKey, { md: parent.certificateOperations.forge.md.sha384.create(), encoding: 'hex' });
obj.error = 0;
// Get a list of IPv4 and IPv6 interface addresses
function getInterfaceList() {
var ipv4 = [], ipv6 = [];
if (parent.platform == 'win32') { ipv4.push('*'); ipv6.push('*'); } // Bind to IN_ADDR_ANY only on Windows
var interfaces = require('os').networkInterfaces();
for (var i in interfaces) {
var interface = interfaces[i];
for (var j in interface) {
var interface2 = interface[j];
if ((interface2.mac != '00:00:00:00:00:00') && (interface2.internal == false)) {
if (interface2.family == 'IPv4') { ipv4.push(interface2.address); }
if (interface2.family == 'IPv6') { ipv6.push(interface2.address + '%' + i); }
var ipv4 = ['*'], ipv6 = ['*']; // Bind to IN_ADDR_ANY always
if (parent.platform == 'win32') { // On Windows, also bind to each interface seperatly
var interfaces = require('os').networkInterfaces();
for (var i in interfaces) {
var interface = interfaces[i];
for (var j in interface) {
var interface2 = interface[j];
if ((interface2.mac != '00:00:00:00:00:00') && (interface2.internal == false)) {
if (interface2.family == 'IPv4') { ipv4.push(interface2.address); }
if (interface2.family == 'IPv6') { ipv6.push(interface2.address + '%' + i); }
}
}
}
}

View File

@ -112,7 +112,7 @@ module.exports.CreateMpsServer = function (parent, db, args, certificates) {
socket.tag.domainid = domainid;
socket.tag.meshid = 'mesh/' + domainid + '/' + meshid;
socket.tag.nodeid = 'node/' + domainid + '/' + require('crypto').createHash('sha256').update(common.hex2rstr(socket.tag.clientCert.modulus, 'binary')).digest('hex').toUpperCase();
socket.tag.nodeid = 'node/' + domainid + '/' + require('crypto').createHash('sha384').update(common.hex2rstr(socket.tag.clientCert.modulus, 'binary')).digest('hex').toUpperCase();
socket.tag.name = socket.tag.clientCert.subject.CN;
socket.tag.connectTime = Date.now();
socket.tag.host = '';
@ -170,7 +170,7 @@ module.exports.CreateMpsServer = function (parent, db, args, certificates) {
console.log(e);
}
});
// Process one AFP command
function ProcessCommand(socket) {
var cmd = socket.tag.accumulator.charCodeAt(0);
@ -228,7 +228,7 @@ module.exports.CreateMpsServer = function (parent, db, args, certificates) {
// Intel AMT GUID (socket.tag.SystemId) will be used at NodeID
var systemid = socket.tag.SystemId.split('-').join('').toUpperCase();
socket.tag.name = '';
socket.tag.nodeid = 'node/' + mesh.domain + '/' + systemid + systemid;
socket.tag.nodeid = 'node/' + mesh.domain + '/' + systemid + systemid + systemid; // Turn 16bit systemid guid into 48bit nodeid
socket.tag.meshid = mesh._id;
obj.db.Get(socket.tag.nodeid, function (err, nodes) {
@ -318,7 +318,7 @@ module.exports.CreateMpsServer = function (parent, db, args, certificates) {
if (len < 26 + requestLen + addrLen + oaddrLen + datalen) return 0;
Debug(2, 'MPS:GLOBAL_REQUEST', request, addr + ':' + port, oaddr + ':' + oport, datalen);
// TODO
return ptr + 26 + requestLen + addrLen + oaddrLen + datalen;
return 26 + requestLen + addrLen + oaddrLen + datalen;
}
return 6 + requestLen;

View File

@ -7,6 +7,7 @@
// Construct a Mesh Multi-Server object. This is used for MeshCentral-to-MeshCentral communication.
module.exports.CreateMultiServer = function (parent, args) {
var obj = {};
const WebSocket = require('ws');
obj.parent = parent;
obj.crypto = require('crypto');
obj.peerConfig = parent.config.peers;
@ -22,7 +23,6 @@ module.exports.CreateMultiServer = function (parent, args) {
obj.serverid = serverid;
obj.url = url;
obj.ws = null;
obj.conn = null;
obj.certificates = parent.parent.certificates;
obj.common = require('./common.js');
obj.forge = require('node-forge');
@ -51,123 +51,107 @@ module.exports.CreateMultiServer = function (parent, args) {
obj.connectionState = 1;
// Get the web socket setup
const WebSocket = require('websocket');
var WebSocketClient = require('websocket').client;
obj.ws = new WebSocketClient();
obj.ws = new WebSocket(obj.url + 'meshserver.ashx', { rejectUnauthorized: false, cert: obj.certificates.agent.cert, key: obj.certificates.agent.key });
obj.parent.parent.debug(1, 'OutPeer ' + obj.serverid + ': Connecting to: ' + url + 'meshserver.ashx');
// Register the connection failed event
obj.ws.on('connectFailed', function (error) { obj.parent.parent.debug(1, 'OutPeer ' + obj.serverid + ': Failed connection'); disconnect(); });
obj.ws.on('error', function (error) { obj.parent.parent.debug(1, 'OutPeer ' + obj.serverid + ': Error: ' + error); disconnect(); });
obj.ws.on('close', function () { obj.parent.parent.debug(1, 'OutPeer ' + obj.serverid + ': Disconnected'); disconnect(); });
// Register the connection event
obj.ws.on('connect', function (connection) {
obj.ws.on('open', function () {
obj.parent.parent.debug(1, 'OutPeer ' + obj.serverid + ': Connected');
obj.connectionState |= 2;
obj.conn = connection;
obj.nonce = obj.forge.random.getBytesSync(32);
// If the connection has an error or closes
obj.conn.on('error', function (error) { obj.parent.parent.debug(1, 'OutPeer ' + obj.serverid + ': Error: ' + error); disconnect(); });
obj.conn.on('close', function () { obj.parent.parent.debug(1, 'OutPeer ' + obj.serverid + ': Disconnected'); disconnect(); });
obj.nonce = obj.forge.random.getBytesSync(48);
// Get the peer server's certificate and compute the server public key hash
if (obj.ws.socket == null) return;
var rawcertbuf = obj.ws.socket.getPeerCertificate().raw, rawcert = '';
for (var i = 0; i < rawcertbuf.length; i++) { rawcert += String.fromCharCode(rawcertbuf[i]); }
var serverCert = obj.forge.pki.certificateFromAsn1(obj.forge.asn1.fromDer(rawcert));
obj.serverCertHash = obj.forge.pki.getPublicKeyFingerprint(serverCert.publicKey, { encoding: 'binary', md: obj.forge.md.sha256.create() });
if (obj.ws._socket == null) return;
var serverCert = obj.forge.pki.certificateFromAsn1(obj.forge.asn1.fromDer(obj.ws._socket.getPeerCertificate().raw.toString('binary')));
obj.serverCertHash = obj.forge.pki.getPublicKeyFingerprint(serverCert.publicKey, { encoding: 'binary', md: obj.forge.md.sha384.create() });
// If a message is received
obj.conn.on('message', function (msg) {
if (msg.type == 'binary') { var msg2 = ""; for (var i = 0; i < msg.binaryData.length; i++) { msg2 += String.fromCharCode(msg.binaryData[i]); } msg = msg2; }
else if (msg.type == 'utf8') { msg = msg.utf8Data; }
if (msg.length < 2) return;
if (msg.charCodeAt(0) == 123) {
if (obj.connectionState == 15) { processServerData(msg); }
} else {
var cmd = obj.common.ReadShort(msg, 0);
switch (cmd) {
case 1: {
// Server authentication request
if (msg.length != 66) { obj.parent.parent.debug(1, 'OutPeer: BAD MESSAGE(A1)'); return; }
// Check that the server hash matches the TLS server certificate public key hash
if (obj.serverCertHash != msg.substring(2, 34)) { obj.parent.parent.debug(1, 'OutPeer: Server hash mismatch.'); disconnect(); return; }
obj.servernonce = msg.substring(34);
// Use our agent root private key to sign the ServerHash + ServerNonce + AgentNonce
var privateKey = obj.forge.pki.privateKeyFromPem(obj.certificates.agent.key);
var md = obj.forge.md.sha256.create();
md.update(msg.substring(2), 'binary');
md.update(obj.nonce, 'binary');
// Send back our certificate + signature
agentRootCertificatAsn1 = obj.forge.asn1.toDer(obj.forge.pki.certificateToAsn1(obj.forge.pki.certificateFromPem(obj.certificates.agent.cert))).getBytes();
obj.conn.send(obj.common.ShortToStr(2) + obj.common.ShortToStr(agentRootCertificatAsn1.length) + agentRootCertificatAsn1 + privateKey.sign(md)); // Command 3, signature
break;
}
case 2: {
// Server certificate
var certlen = obj.common.ReadShort(msg, 2), serverCert = null;
try { serverCert = obj.forge.pki.certificateFromAsn1(obj.forge.asn1.fromDer(msg.substring(4, 4 + certlen))); } catch (e) { }
if (serverCert == null) { obj.parent.parent.debug(1, 'OutPeer: Invalid server certificate.'); disconnect(); return; }
var serverid = obj.forge.pki.getPublicKeyFingerprint(serverCert.publicKey, { encoding: 'hex', md: obj.forge.md.sha256.create() });
if (serverid !== obj.agentCertificatHashHex) { obj.parent.parent.debug(1, 'OutPeer: Server hash mismatch.'); disconnect(); return; }
// Server signature, verify it
var md = obj.forge.md.sha256.create();
md.update(obj.serverCertHash, 'binary');
md.update(obj.nonce, 'binary');
md.update(obj.servernonce, 'binary');
if (serverCert.publicKey.verify(md.digest().bytes(), msg.substring(4 + certlen)) == false) { obj.parent.parent.debug(1, 'OutPeer: Server sign check failed.'); disconnect(); return; }
// Connection is a success, clean up
delete obj.nonce;
delete obj.servernonce;
obj.serverCertHash = obj.common.rstr2hex(obj.serverCertHash).toLowerCase(); // Change this value to hex
obj.connectionState |= 4;
obj.retryBackoff = 0; // Set backoff connection timer back to fast.
obj.parent.parent.debug(1, 'OutPeer ' + obj.serverid + ': Verified peer connection to ' + obj.url);
// Send information about our server to the peer
if (obj.connectionState == 15) { obj.conn.send(JSON.stringify({ action: 'info', serverid: obj.parent.serverid, dbid: obj.parent.parent.db.identifier, key: obj.parent.serverKey, serverCertHash: obj.parent.parent.webserver.webCertificatHashHex })); }
//if ((obj.connectionState == 15) && (obj.connectHandler != null)) { obj.connectHandler(1); }
break;
}
case 4: {
// Server confirmed authentication, we are allowed to send commands to the server
obj.connectionState |= 8;
if (obj.connectionState == 15) { obj.conn.send(JSON.stringify({ action: 'info', serverid: obj.parent.serverid, dbid: obj.parent.parent.db.identifier, key: obj.parent.serverKey, serverCertHash: obj.parent.parent.webserver.webCertificatHashHex })); }
//if ((obj.connectionState == 15) && (obj.connectHandler != null)) { obj.connectHandler(1); }
break;
}
default: {
obj.parent.parent.debug(1, 'OutPeer ' + obj.serverid + ': Un-handled command: ' + cmd);
break;
}
}
}
});
// Not sure why, but we need to delay the first send
setTimeout(function () {
if ((obj.ws == null) || (obj.conn == null)) return;
// Start authenticate the mesh agent by sending a auth nonce & server TLS cert hash.
// Send 256 bits SHA256 hash of TLS cert public key + 256 bits nonce
obj.conn.send(obj.common.ShortToStr(1) + obj.serverCertHash + obj.nonce); // Command 1, hash + nonce
}, 10);
// Start authenticate the peer server by sending a auth nonce & server TLS cert hash.
// Send 384 bits SHA384 hash of TLS cert public key + 384 bits nonce
obj.ws.send(obj.common.ShortToStr(1) + obj.serverCertHash + obj.nonce); // Command 1, hash + nonce
});
obj.ws.connect(obj.url + 'meshserver.ashx', null, null, null, { rejectUnauthorized: false, cert: obj.certificates.agent.cert, key: obj.certificates.agent.key });
// If a message is received
obj.ws.on('message', function (msg) {
if (typeof msg != 'string') { msg = msg.toString('binary'); }
if (msg.length < 2) return;
if (msg.charCodeAt(0) == 123) {
if (obj.connectionState == 15) { processServerData(msg); }
} else {
var cmd = obj.common.ReadShort(msg, 0);
switch (cmd) {
case 1: {
// Server authentication request
if (msg.length != 98) { obj.parent.parent.debug(1, 'OutPeer: BAD MESSAGE(A1)'); return; }
// Check that the server hash matches the TLS server certificate public key hash
if (obj.serverCertHash != msg.substring(2, 50)) { obj.parent.parent.debug(1, 'OutPeer: Server hash mismatch.'); disconnect(); return; }
obj.servernonce = msg.substring(50);
// Use our agent certificate root private key to sign the ServerHash + ServerNonce + PeerNonce
var privateKey = obj.forge.pki.privateKeyFromPem(obj.certificates.agent.key);
var md = obj.forge.md.sha384.create();
md.update(msg.substring(2), 'binary');
md.update(obj.nonce, 'binary');
// Send back our certificate + signature
agentRootCertificatAsn1 = obj.forge.asn1.toDer(obj.forge.pki.certificateToAsn1(obj.forge.pki.certificateFromPem(obj.certificates.agent.cert))).getBytes();
obj.ws.send(obj.common.ShortToStr(2) + obj.common.ShortToStr(agentRootCertificatAsn1.length) + agentRootCertificatAsn1 + privateKey.sign(md)); // Command 3, signature
break;
}
case 2: {
// Server certificate
var certlen = obj.common.ReadShort(msg, 2), serverCert = null;
try { serverCert = obj.forge.pki.certificateFromAsn1(obj.forge.asn1.fromDer(msg.substring(4, 4 + certlen))); } catch (e) { }
if (serverCert == null) { obj.parent.parent.debug(1, 'OutPeer: Invalid server certificate.'); disconnect(); return; }
var serverid = obj.forge.pki.getPublicKeyFingerprint(serverCert.publicKey, { encoding: 'hex', md: obj.forge.md.sha384.create() });
if (serverid !== obj.agentCertificatHashHex) { obj.parent.parent.debug(1, 'OutPeer: Server hash mismatch.'); disconnect(); return; }
// Server signature, verify it
var md = obj.forge.md.sha384.create();
md.update(obj.serverCertHash, 'binary');
md.update(obj.nonce, 'binary');
md.update(obj.servernonce, 'binary');
if (serverCert.publicKey.verify(md.digest().bytes(), msg.substring(4 + certlen)) == false) { obj.parent.parent.debug(1, 'OutPeer: Server sign check failed.'); disconnect(); return; }
// Connection is a success, clean up
delete obj.nonce;
delete obj.servernonce;
obj.serverCertHash = obj.common.rstr2hex(obj.serverCertHash).toLowerCase(); // Change this value to hex
obj.connectionState |= 4;
obj.retryBackoff = 0; // Set backoff connection timer back to fast.
obj.parent.parent.debug(1, 'OutPeer ' + obj.serverid + ': Verified peer connection to ' + obj.url);
// Send information about our server to the peer
if (obj.connectionState == 15) { obj.ws.send(JSON.stringify({ action: 'info', serverid: obj.parent.serverid, dbid: obj.parent.parent.db.identifier, key: obj.parent.serverKey.toString('hex'), serverCertHash: obj.parent.parent.webserver.webCertificatHashHex })); }
//if ((obj.connectionState == 15) && (obj.connectHandler != null)) { obj.connectHandler(1); }
break;
}
case 4: {
// Server confirmed authentication, we are allowed to send commands to the server
obj.connectionState |= 8;
if (obj.connectionState == 15) { obj.ws.send(JSON.stringify({ action: 'info', serverid: obj.parent.serverid, dbid: obj.parent.parent.db.identifier, key: obj.parent.serverKey.toString('hex'), serverCertHash: obj.parent.parent.webserver.webCertificatHashHex })); }
//if ((obj.connectionState == 15) && (obj.connectHandler != null)) { obj.connectHandler(1); }
break;
}
default: {
obj.parent.parent.debug(1, 'OutPeer ' + obj.serverid + ': Un-handled command: ' + cmd);
break;
}
}
}
});
}
// Disconnect from the server, if we need to, try again with a delay.
function disconnect() {
if (obj.authenticated == 3) { obj.parent.ClearPeerServer(obj, obj.peerServerId); obj.authenticated = 0; }
if ((obj.connectionState == 15) && (obj.connectHandler != null)) { obj.connectHandler(0); }
if (obj.conn != null) { obj.conn.close(); obj.conn = null; }
if (obj.ws != null) { obj.ws = null; }
if (obj.ws != null) { obj.ws.close(); obj.ws = null; }
if (obj.retryTimer != null) { clearTimeout(obj.retryTimer); obj.retryTimer = null; }
// Re-try connection
if (obj.connectionState >= 1) { obj.connectionState = 1; if (obj.retryTimer == null) { obj.retryTimer = setTimeout(connect, getConnectRetryTime()); } }
@ -182,9 +166,9 @@ module.exports.CreateMultiServer = function (parent, args) {
// Send a JSON message to the peer server
obj.send = function (msg) {
try {
if (obj.ws == null || obj.conn == null || obj.connectionState != 15) { return; }
if (typeof msg == 'object') { obj.conn.send(JSON.stringify(msg)); return; }
if (typeof msg == 'string') { obj.conn.send(msg); return; }
if (obj.ws == null || obj.connectionState != 15) { return; }
if (typeof msg == 'object') { obj.ws.send(JSON.stringify(msg)); return; }
if (typeof msg == 'string') { obj.ws.send(msg); return; }
} catch (e) { }
}
@ -201,7 +185,7 @@ module.exports.CreateMultiServer = function (parent, args) {
if (command.dbid != obj.parent.parent.db.identifier) { console.log('ERROR: Database ID mismatch. Trying to peer to a server with the wrong database. (' + obj.url + ', ' + command.serverid + ').'); return; }
if (obj.serverCertHash != command.serverCertHash) { console.log('ERROR: Outer certificate hash mismatch. (' + obj.url + ', ' + command.serverid + ').'); return; }
obj.peerServerId = command.serverid;
obj.peerServerKey = command.key;
obj.peerServerKey = new Buffer(command.key, 'hex');
obj.authenticated = 3;
obj.parent.SetupPeerServer(obj, obj.peerServerId);
}
@ -235,6 +219,7 @@ module.exports.CreateMultiServer = function (parent, args) {
obj.peerServerId = null;
obj.serverCertHash = null;
if (obj.remoteaddr.startsWith('::ffff:')) { obj.remoteaddr = obj.remoteaddr.substring(7); }
obj.parent.parent.debug(1, 'InPeer: Connected (' + obj.remoteaddr + ')');
// Send a message to the peer server
obj.send = function (data) {
@ -252,10 +237,9 @@ module.exports.CreateMultiServer = function (parent, args) {
if (obj.authenticated == 3) { obj.parent.ClearPeerServer(obj, obj.peerServerId); obj.authenticated = 0; }
}
// When data is received from the mesh agent web socket
// When data is received from the peer server web socket
ws.on('message', function (msg) {
if (msg.type == 'binary') { var msg2 = ""; for (var i = 0; i < msg.binaryData.length; i++) { msg2 += String.fromCharCode(msg.binaryData[i]); } msg = msg2; }
else if (msg.type == 'utf8') { msg = msg.utf8Data; }
if (typeof msg != 'string') { msg = msg.toString('binary'); }
if (msg.length < 2) return;
if (obj.authenticated >= 2) { // We are authenticated
@ -267,48 +251,47 @@ module.exports.CreateMultiServer = function (parent, args) {
else if (obj.authenticated < 2) { // We are not authenticated
var cmd = obj.common.ReadShort(msg, 0);
if (cmd == 1) {
// Agent authentication request
if ((msg.length != 66) || ((obj.receivedCommands & 1) != 0)) return;
obj.receivedCommands += 1; // Agent can't send the same command twice on the same connection ever. Block DOS attack path.
// Peer server authentication request
if ((msg.length != 98) || ((obj.receivedCommands & 1) != 0)) return;
obj.receivedCommands += 1; // Peer server can't send the same command twice on the same connection ever. Block DOS attack path.
// Check that the server hash matches out own web certificate hash
if (obj.webCertificatHash != msg.substring(2, 34)) { obj.close(); return; }
if (obj.webCertificatHash != msg.substring(2, 50)) { obj.close(); return; }
// Use our server private key to sign the ServerHash + AgentNonce + ServerNonce
// Use our server private key to sign the ServerHash + PeerNonce + ServerNonce
var privateKey = obj.forge.pki.privateKeyFromPem(obj.parent.parent.certificates.agent.key);
var md = obj.forge.md.sha256.create();
var md = obj.forge.md.sha384.create();
md.update(msg.substring(2), 'binary');
md.update(obj.nonce, 'binary');
obj.agentnonce = msg.substring(34);
obj.peernonce = msg.substring(50);
// Send back our certificate + signature
obj.send(obj.common.ShortToStr(2) + obj.common.ShortToStr(obj.agentCertificatAsn1.length) + obj.agentCertificatAsn1 + privateKey.sign(md)); // Command 2, certificate + signature
// Check the agent signature if we can
// Check the peer server signature if we can
if (obj.unauthsign != null) {
if (processAgentSignature(obj.unauthsign) == false) { disconnect(); return; } else { completePeerServerConnection(); }
if (processPeerSignature(obj.unauthsign) == false) { disconnect(); return; } else { completePeerServerConnection(); }
}
}
else if (cmd == 2) {
// Agent certificate
// Peer server certificate
if ((msg.length < 4) || ((obj.receivedCommands & 2) != 0)) return;
obj.receivedCommands += 2; // Agent can't send the same command twice on the same connection ever. Block DOS attack path.
obj.receivedCommands += 2; // Peer server can't send the same command twice on the same connection ever. Block DOS attack path.
// Decode the certificate
var certlen = obj.common.ReadShort(msg, 2);
obj.unauth = {};
obj.unauth.nodeCert = null;
try { obj.unauth.nodeCert = obj.forge.pki.certificateFromAsn1(obj.forge.asn1.fromDer(msg.substring(4, 4 + certlen))); } catch (e) { return; }
obj.unauth.nodeid = obj.forge.pki.getPublicKeyFingerprint(obj.unauth.nodeCert.publicKey, { encoding: 'hex', md: obj.forge.md.sha256.create() });
obj.unauth.nodeid = obj.forge.pki.getPublicKeyFingerprint(obj.unauth.nodeCert.publicKey, { encoding: 'hex', md: obj.forge.md.sha384.create() });
// Check the agent signature if we can
if (obj.agentnonce == null) { obj.unauthsign = msg.substring(4 + certlen); } else { if (processAgentSignature(msg.substring(4 + certlen)) == false) { disconnect(); return; } }
// Check the peer server signature if we can
if (obj.peernonce == null) { obj.unauthsign = msg.substring(4 + certlen); } else { if (processPeerSignature(msg.substring(4 + certlen)) == false) { disconnect(); return; } }
completePeerServerConnection();
}
else if (cmd == 3) {
// Agent meshid
if ((msg.length < 56) || ((obj.receivedCommands & 4) != 0)) return;
obj.receivedCommands += 4; // Agent can't send the same command twice on the same connection ever. Block DOS attack path.
obj.receivedCommands += 4; // Peer server can't send the same command twice on the same connection ever. Block DOS attack path.
completePeerServerConnection();
}
}
@ -317,36 +300,36 @@ module.exports.CreateMultiServer = function (parent, args) {
// If error, do nothing
ws.on('error', function (err) { obj.parent.parent.debug(1, 'InPeer: Connection Error: ' + err); });
// If the mesh agent web socket is closed, clean up.
// If the peer server web socket is closed, clean up.
ws.on('close', function (req) { obj.parent.parent.debug(1, 'InPeer disconnect ' + obj.nodeid + ' (' + obj.remoteaddr + ')'); obj.close(0); });
// obj.ws._socket._parent.on('close', function (req) { obj.parent.parent.debug(1, 'Agent TCP disconnect ' + obj.nodeid + ' (' + obj.remoteaddr + ')'); });
// obj.ws._socket._parent.on('close', function (req) { obj.parent.parent.debug(1, 'Peer server TCP disconnect ' + obj.nodeid + ' (' + obj.remoteaddr + ')'); });
// Start authenticate the mesh agent by sending a auth nonce & server TLS cert hash.
// Send 256 bits SHA256 hash of TLS cert public key + 256 bits nonce
obj.nonce = obj.forge.random.getBytesSync(32);
// Start authenticate the peer server by sending a auth nonce & server TLS cert hash.
// Send 384 bits SHA382 hash of TLS cert public key + 384 bits nonce
obj.nonce = obj.forge.random.getBytesSync(48);
obj.send(obj.common.ShortToStr(1) + obj.webCertificatHash + obj.nonce); // Command 1, hash + nonce
// Once we get all the information about an agent, run this to hook everything up to the server
// Once we get all the information about an peer server, run this to hook everything up to the server
function completePeerServerConnection() {
if (obj.authenticated != 1) return;
obj.send(obj.common.ShortToStr(4));
obj.send(JSON.stringify({ action: 'info', serverid: obj.parent.serverid, dbid: obj.parent.parent.db.identifier, key: obj.parent.serverKey, serverCertHash: obj.parent.parent.webserver.webCertificatHashHex }));
obj.send(JSON.stringify({ action: 'info', serverid: obj.parent.serverid, dbid: obj.parent.parent.db.identifier, key: obj.parent.serverKey.toString('hex'), serverCertHash: obj.parent.parent.webserver.webCertificatHashHex }));
obj.authenticated = 2;
}
// Verify the agent signature
function processAgentSignature(msg) {
var md = obj.forge.md.sha256.create(); // TODO: Switch this to SHA256 on node instead of forge.
// Verify the peer server signature
function processPeerSignature(msg) {
var md = obj.forge.md.sha384.create(); // TODO: Switch this to SHA384 on node instead of forge.
md.update(obj.parent.parent.webserver.webCertificatHash, 'binary');
md.update(obj.nonce, 'binary');
md.update(obj.agentnonce, 'binary');
md.update(obj.peernonce, 'binary');
if (obj.unauth.nodeCert.publicKey.verify(md.digest().bytes(), msg) == false) { return false; }
if (obj.unauth.nodeid !== obj.agentCertificatHashHex) { return false; }
// Connection is a success, clean up
obj.nodeid = obj.unauth.nodeid.toUpperCase();
delete obj.nonce;
delete obj.agentnonce;
delete obj.peernonce;
delete obj.unauth;
if (obj.unauthsign) delete obj.unauthsign;
obj.authenticated = 1;
@ -366,7 +349,7 @@ module.exports.CreateMultiServer = function (parent, args) {
if (command.dbid != obj.parent.parent.db.identifier) { console.log('ERROR: Database ID mismatch. Trying to peer to a server with the wrong database. (' + obj.remoteaddr + ', ' + command.serverid + ').'); return; }
if (obj.parent.peerConfig.servers[command.serverid] == null) { console.log('ERROR: Unknown peer serverid: ' + command.serverid + ' (' + obj.remoteaddr + ').'); return; }
obj.peerServerId = command.serverid;
obj.peerServerKey = command.key;
obj.peerServerKey = new Buffer(command.key, 'hex');
obj.serverCertHash = command.serverCertHash;
obj.authenticated = 3;
obj.parent.SetupPeerServer(obj, obj.peerServerId);
@ -389,9 +372,7 @@ module.exports.CreateMultiServer = function (parent, args) {
if (obj.parent.config.peers.servers[obj.serverid] == null) { console.log("Error: Unable to peer with other servers, \"" + obj.serverid + "\" not present in peer servers list."); return null; }
// Generate a cryptographic key used to encode and decode cookies
obj.generateCookieKey = function () {
return new Buffer(obj.crypto.randomBytes(32), 'binary').toString('hex');
}
obj.generateCookieKey = function () { return new Buffer(obj.crypto.randomBytes(32), 'binary'); }
// Return the private key of a peer server
obj.getServerCookieKey = function (serverid) {
@ -400,40 +381,25 @@ module.exports.CreateMultiServer = function (parent, args) {
return null;
}
// Encode an object as a cookie using a key
// Encode an object as a cookie using a key. (key must be 32 bytes long)
obj.encodeCookie = function (o, key) {
try {
if (key == null) { key = obj.serverKey; }
key = require('./common.js').hex2rstr(key);
o.time = Math.floor(Date.now() / 1000); // Add the cookie creation time
var msg = JSON.stringify(o);
msg = obj.crypto.createHmac('sha256', key.substring(16)).update(msg, 'binary', 'binary').digest('binary') + msg;
var iv = new Buffer(obj.crypto.randomBytes(16), 'binary');
var cipher = obj.crypto.createCipheriv('aes-128-cbc', new Buffer(key.substring(0, 16), 'binary'), iv);
crypted = cipher.update(msg, 'binary', 'binary');
crypted += cipher.final('binary');
var total = new Buffer(iv, 'binary').toString('hex') + new Buffer(crypted, 'binary').toString('hex'); // HEX: This is not an efficient concat, but it's very compatible.
var cookie = new Buffer(total, 'hex').toString('base64');
return cookie.replace(/\+/g, '@').replace(/\//g, '$');
var iv = new Buffer(obj.crypto.randomBytes(12), 'binary'), cipher = obj.crypto.createCipheriv('aes-256-gcm', key, iv);
var crypted = Buffer.concat([cipher.update(JSON.stringify(o), 'utf8'), cipher.final()]);
return Buffer.concat([iv, cipher.getAuthTag(), crypted]).toString('base64').replace(/\+/g, '@').replace(/\//g, '$');
} catch (e) { return null; }
}
// Decode a cookie back into an object using a key. Return null if it's not a valid cookie.
// Decode a cookie back into an object using a key. Return null if it's not a valid cookie. (key must be 32 bytes long)
obj.decodeCookie = function (cookie, key) {
try {
if (key == null) { key = obj.serverKey; }
key = require('./common.js').hex2rstr(key);
cookie = new Buffer(cookie.replace(/\@/g, '+').replace(/\$/g, '/'), 'base64').toString('hex'); // HEX: This is not an efficient split, but it's very compatible.
var iv = new Buffer(cookie.substring(0, 32), 'hex');
var msg = new Buffer(cookie.substring(32), 'hex');
var decipher = obj.crypto.createDecipheriv('aes-128-cbc', new Buffer(key.substring(0, 16), 'binary'), iv)
var dec = decipher.update(msg, 'binary', 'binary')
dec += decipher.final('binary');
var msg = dec.substring(32);
var hash1 = dec.substring(0, 32);
var hash2 = obj.crypto.createHmac('sha256', key.substring(16)).update(msg, 'binary', 'binary').digest('binary');
if (hash1 !== hash2) { return null; }
var o = JSON.parse(msg);
cookie = new Buffer(cookie.replace(/\@/g, '+').replace(/\$/g, '/'), 'base64'); // HEX: This is not an efficient split, but it's very compatible.
var decipher = obj.crypto.createDecipheriv('aes-256-gcm', key, cookie.slice(0, 12));
decipher.setAuthTag(cookie.slice(12, 16));
var o = JSON.parse(decipher.update(cookie.slice(28), 'binary', 'utf8') + decipher.final('utf8'));
if ((o.time == null) || (o.time == null) || (typeof o.time != 'number')) { return null; }
o.time = o.time * 1000; // Decode the cookie creation time
o.dtime = Date.now() - o.time; // Decode how long ago the cookie was created
@ -639,41 +605,30 @@ module.exports.CreateMultiServer = function (parent, args) {
peerTunnel.connect = function () {
// Get the web socket setup
var WebSocketClient = require('websocket').client;
peerTunnel.wsclient = new WebSocketClient();
peerTunnel.parent.parent.debug(1, 'FTunnel ' + peerTunnel.serverid + ': Start connect to ' + peerTunnel.url);
peerTunnel.ws2 = new WebSocket(peerTunnel.url, { rejectUnauthorized: false });
// Register the connection failed event
peerTunnel.wsclient.on('connectFailed', function (error) { peerTunnel.parent.parent.debug(1, 'FTunnel ' + obj.serverid + ': Failed connection'); peerTunnel.ws1.close(); });
peerTunnel.ws2.on('error', function (error) { peerTunnel.parent.parent.debug(1, 'FTunnel ' + obj.serverid + ': Connection error'); peerTunnel.close(); });
// If the peer server web socket is closed, clean up.
peerTunnel.ws2.on('close', function (req) { peerTunnel.parent.parent.debug(1, 'FTunnel disconnect ' + peerTunnel.nodeid); peerTunnel.close(); });
// If a message is received from the peer, Peer ---> Browser (TODO: Pipe this?)
peerTunnel.ws2.on('message', function (msg) { try { peerTunnel.ws2.pause(); peerTunnel.ws1.send(msg, function () { peerTunnel.ws2.resume(); }); } catch (e) { } });
// Register the connection event
peerTunnel.wsclient.on('connect', function (connection) {
peerTunnel.ws2.on('open', function () {
peerTunnel.parent.parent.debug(1, 'FTunnel ' + peerTunnel.serverid + ': Connected');
// Get the peer server's certificate and compute the server public key hash
var rawcertbuf = connection.socket.getPeerCertificate().raw, rawcert = '';
for (var i = 0; i < rawcertbuf.length; i++) { rawcert += String.fromCharCode(rawcertbuf[i]); }
var serverCert = obj.forge.pki.certificateFromAsn1(obj.forge.asn1.fromDer(rawcert));
var serverCertHashHex = obj.forge.pki.getPublicKeyFingerprint(serverCert.publicKey, { encoding: 'hex', md: obj.forge.md.sha256.create() });
var serverCert = obj.forge.pki.certificateFromAsn1(obj.forge.asn1.fromDer(peerTunnel.ws2._socket.getPeerCertificate().raw.toString('binary')));
var serverCertHashHex = obj.forge.pki.getPublicKeyFingerprint(serverCert.publicKey, { encoding: 'hex', md: obj.forge.md.sha384.create() });
// Check if the peer certificate is the expected one for this serverid
if (obj.peerServers[serverid] == null || obj.peerServers[serverid].serverCertHash != serverCertHashHex) { console.log('ERROR: Outer certificate hash mismatch. (' + peerTunnel.url + ', ' + peerTunnel.serverid + ').'); peerTunnel.ws1.close(); return; }
if (obj.peerServers[serverid] == null || obj.peerServers[serverid].serverCertHash != serverCertHashHex) { console.log('ERROR: Outer certificate hash mismatch. (' + peerTunnel.url + ', ' + peerTunnel.serverid + ').'); peerTunnel.close(); return; }
// Connection accepted.
peerTunnel.ws2 = connection;
// If error, do nothing
peerTunnel.ws2.on('error', function (err) { peerTunnel.parent.parent.debug(1, 'FTunnel: Connection Error: ' + err); peerTunnel.close(); });
// If the mesh agent web socket is closed, clean up.
peerTunnel.ws2.on('close', function (req) { peerTunnel.parent.parent.debug(1, 'FTunnel disconnect ' + peerTunnel.nodeid); peerTunnel.close(); });
// If a message is received from the peer, Peer ---> Browser
peerTunnel.ws2.on('message', function (msg) {
try {
if (msg.type == 'utf8') { peerTunnel.ws2.pause(); peerTunnel.ws1.send(msg.utf8Data, function () { peerTunnel.ws2.resume(); }); }
else if (msg.type == 'binary') { peerTunnel.ws2.pause(); peerTunnel.ws1.send(msg.binaryData, function () { peerTunnel.ws2.resume(); }); }
} catch (e) { }
});
// Resume the web socket to start the data flow
// Connection accepted, resume the web socket to start the data flow
peerTunnel.ws1.resume();
});
@ -681,12 +636,10 @@ module.exports.CreateMultiServer = function (parent, args) {
peerTunnel.ws1.on('message', function (msg) { try { peerTunnel.ws1.pause(); peerTunnel.ws2.send(msg, function () { peerTunnel.ws1.resume(); }); } catch (e) { } });
// If error, do nothing
peerTunnel.ws1.on('error', function (err) { console.log(err); peerTunnel.close(); });
peerTunnel.ws1.on('error', function (err) { peerTunnel.close(); });
// If the web socket is closed, close the associated TCP connection.
peerTunnel.ws1.on('close', function (req) { peerTunnel.parent.parent.debug(1, 'FTunnel disconnect ' + peerTunnel.nodeid); peerTunnel.close(); });
peerTunnel.wsclient.connect(peerTunnel.url, null, null, null, { rejectUnauthorized: false });
}
// Disconnect both sides of the tunnel

View File

@ -1,6 +1,6 @@
{
"name": "meshcentral",
"version": "0.0.8-u",
"version": "0.0.8-w",
"keywords": [
"Remote Management",
"Intel AMT",
@ -26,20 +26,22 @@
],
"dependencies": {
"archiver": "^1.3.0",
"body-parser": "^1.17.1",
"compression": "^1.6.2",
"connect-redis": "^3.2.0",
"express": "^4.15.2",
"body-parser": "^1.18.2",
"compression": "^1.7.1",
"connect-redis": "^3.3.2",
"express": "^4.16.2",
"express-handlebars": "^3.0.0",
"express-session": "^1.15.1",
"express-session": "^1.15.6",
"express-ws": "^2.0.0",
"meshcentral": "*",
"minimist": "^1.2.0",
"mongojs": "^2.4.1",
"multiparty": "^4.1.3",
"nedb": "^1.8.0",
"node-forge": "^0.6.49",
"node-windows": "^0.1.14",
"unzip": "^0.1.11",
"websocket": "^1.0.24",
"ws": "^3.2.0",
"xmldom": "^0.1.27"
},
"optionalDependencies": {

View File

@ -21,7 +21,7 @@ var iterations = 12000;
exports.hash = function (pwd, salt, fn) {
if (3 == arguments.length) {
try {
crypto.pbkdf2(pwd, salt, iterations, len, 'sha256', function (err, hash) { fn(err, hash.toString('base64')); });
crypto.pbkdf2(pwd, salt, iterations, len, 'sha384', function (err, hash) { fn(err, hash.toString('base64')); });
} catch (e) {
// If this previous call fails, it's probably because older pbkdf2 did not specify the hashing function, just use the default.
crypto.pbkdf2(pwd, salt, iterations, len, function (err, hash) { fn(err, hash.toString('base64')); });
@ -32,7 +32,7 @@ exports.hash = function (pwd, salt, fn) {
if (err) return fn(err);
salt = salt.toString('base64');
try {
crypto.pbkdf2(pwd, salt, iterations, len, 'sha256', function (err, hash) { if (err) { return fn(err); } fn(null, salt, hash.toString('base64')); });
crypto.pbkdf2(pwd, salt, iterations, len, 'sha384', function (err, hash) { if (err) { return fn(err); } fn(null, salt, hash.toString('base64')); });
} catch (e) {
// If this previous call fails, it's probably because older pbkdf2 did not specify the hashing function, just use the default.
crypto.pbkdf2(pwd, salt, iterations, len, function (err, hash) { if (err) { return fn(err); } fn(null, salt, hash.toString('base64')); });

View File

@ -16,7 +16,9 @@ var CreateAgentRedirect = function (meshserver, module, serverPublicNamePort) {
obj.connectstate = -1;
obj.tunnelid = Math.random().toString(36).substring(2); // Generate a random client tunnel id
obj.protocol = module.protocol; // 1 = SOL, 2 = KVM, 3 = IDER, 4 = Files, 5 = FileTransfer
obj.attemptWebRTC = false;
obj.webrtc = null;
obj.webchannel = null;
obj.onStateChanged = null;
// Private method
@ -43,8 +45,68 @@ var CreateAgentRedirect = function (meshserver, module, serverPublicNamePort) {
obj.xxStateChange(2);
}
// Called to pass websocket control messages
obj.xxOnControlCommand = function (msg) {
var controlMsg = JSON.parse(msg);
if ((controlMsg.type == 'answer') && (obj.webrtc != null)) {
console.log('gotAnswer', JSON.stringify(controlMsg));
obj.webrtc.setRemoteDescription(new RTCSessionDescription(controlMsg), function () { console.log('WebRTC remote ok'); }, obj.xxCloseWebRTC);
}
}
// Close the WebRTC connection, should be called if a problem occurs during WebRTC setup.
obj.xxCloseWebRTC = function () {
if (obj.webchannel != null) { obj.webchannel.close(); obj.webchannel = null; }
if (obj.webrtc != null) { obj.webrtc.close(); obj.webrtc = null; }
}
obj.xxOnMessage = function (e) {
if (obj.State < 3) { if (e.data == 'c') { obj.socket.send(obj.protocol); obj.xxStateChange(3); return; } }
if (obj.State < 3) {
if (e.data == 'c') {
obj.socket.send(obj.protocol);
obj.xxStateChange(3);
if (obj.attemptWebRTC == true) {
// Try to get WebRTC setup
var configuration = null; //{ "iceServers": [ { 'urls': 'stun:stun.services.mozilla.com' }, { 'urls': 'stun:stun.l.google.com:19302' } ] };
if (typeof RTCPeerConnection !== 'undefined') { obj.webrtc = new RTCPeerConnection(configuration); }
else if (typeof webkitRTCPeerConnection !== 'undefined') { obj.webrtc = new webkitRTCPeerConnection(configuration); }
if (obj.webrtc != null) {
obj.webchannel = obj.webrtc.createDataChannel("DataChannel", {}); // { ordered: false, maxRetransmits: 2 }
obj.webchannel.onmessage = function (event) { console.log("DataChannel - onmessage", event.data); };
obj.webchannel.onopen = function () { console.log("DataChannel - onopen"); };
obj.webchannel.onclose = function (event) { console.log("DataChannel - onclose"); }
obj.webrtc.ondatachannel = function (e) { console.log('ondatachannel'); } // TODO: Should not be needed
obj.webrtc.onicecandidate = function (e) {
if (e.candidate == null) {
console.log('createOffer', JSON.stringify(obj.webrtcoffer));
obj.socket.send('**********%%%%%%###**' + JSON.stringify(obj.webrtcoffer)); // End of candidates, send the offer
} else {
obj.webrtcoffer.sdp += ("a=" + e.candidate.candidate + "\r\n"); // New candidate, add it to the SDP
}
}
obj.webrtc.oniceconnectionstatechange = function () {
if (obj.webrtc != null) {
console.log('oniceconnectionstatechange', obj.webrtc.iceConnectionState);
if ((obj.webrtc.iceConnectionState == 'disconnected') || (obj.webrtc.iceConnectionState == 'failed')) { obj.xxCloseWebRTC(); }
}
}
obj.webrtc.createOffer(function (offer) {
// Got the offer
obj.webrtcoffer = offer;
obj.webrtc.setLocalDescription(offer, function () { console.log('WebRTC local ok'); }, obj.xxCloseWebRTC);
}, obj.xxCloseWebRTC, { mandatory: { OfferToReceiveAudio: false, OfferToReceiveVideo: false } });
}
}
return;
}
}
if (typeof e.data == 'string') {
// Control messages, most likely WebRTC setup
obj.xxOnControlCommand(e.data);
}
if (typeof e.data == 'object') {
var f = new FileReader();
if (f.readAsBinaryString) {
@ -81,6 +143,9 @@ var CreateAgentRedirect = function (meshserver, module, serverPublicNamePort) {
}
else if (typeof data !== 'string') return;
// TODO: Don't use a prefix anymore, use string encoding instead
if (data.length > 21 && data.startsWith('**********%%%%%%###**')) { obj.xxOnControlCommand(data.substring(21)); return; }
//console.log("xxOnSocketData", rstr2hex(data));
return obj.m.ProcessData(data);
@ -115,6 +180,7 @@ var CreateAgentRedirect = function (meshserver, module, serverPublicNamePort) {
//obj.debug("Agent Redir Socket Stopped");
obj.xxStateChange(0);
obj.connectstate = -1;
obj.xxCloseWebRTC();
if (obj.socket != null) { obj.socket.close(); obj.socket = null; }
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -150,7 +150,7 @@
</tr>
</table>
</div>
<div id="xdevices" style="max-height:calc(100vh - 228px);overflow-y:auto;-webkit-overflow-scrolling:touch"></div>
<div id="xdevices" style="max-height:calc(100vh - 228px);overflow-y:auto;overflow-x:hidden;-webkit-overflow-scrolling:touch"></div>
<div id="xdevicesmap" style="height:500px;width:100%;overflow:hidden;position:relative">
<div id=xmapSearchResultsDlg style="position:absolute;display:none;max-height:280px;left:5px;top:5px;max-width:250px;z-index:1000;background-color:#EEE;box-shadow:0px 0px 15px #666">
<div style="width:100%;background-color:#003366;color:#FFF;border-radius:5px 5px 0 0">
@ -2760,6 +2760,7 @@
} else {
// Setup the Mesh Agent remote desktop
desktop = CreateAgentRedirect(meshserver, CreateAgentRemoteDesktop('Desk'), serverPublicNamePort);
desktop.attemptWebRTC = debugmode;
desktop.onStateChanged = onDesktopStateChange;
desktop.m.CompressionLevel = desktopsettings.quality; // Number from 1 to 100. 50 or less is best.
desktop.m.ScalingLevel = desktopsettings.scaling;
@ -2984,6 +2985,7 @@
} else {
// Setup a mesh agent terminal
terminal = CreateAgentRedirect(meshserver, CreateAmtRemoteTerminal('Term'), serverPublicNamePort);
terminal.attemptWebRTC = debugmode;
terminal.onStateChanged = onTerminalStateChange;
terminal.Start(terminalNode._id);
terminal.contype = 1;
@ -3088,6 +3090,7 @@
if (!files) {
// Setup a mesh agent files
files = CreateAgentRedirect(meshserver, CreateRemoteFiles(p13gotFiles), serverPublicNamePort);
files.attemptWebRTC = debugmode;
files.onStateChanged = onFilesStateChange;
files.Start(filesNode._id);
} else {
@ -3288,6 +3291,7 @@
// Called by the html page to start a download, arguments are: path, file name and file size.
function p13downloadfile(x, y, z) {
downloadFile = CreateAgentRedirect(meshserver, CreateRemoteFiles(p13gotDownloadData), serverPublicNamePort); // Create our file transport
downloadFile.attemptWebRTC = debugmode;
downloadFile.onStateChanged = onFileDownloadStateChange;
downloadFile.xpath = decodeURIComponent(x);
downloadFile.xfile = decodeURIComponent(y);
@ -3365,6 +3369,7 @@
// Connect again
function p13uploadReconnect() {
uploadFile.ws = CreateAgentRedirect(meshserver, CreateRemoteFiles(p13gotUploadData), serverPublicNamePort);
uploadFile.ws.attemptWebRTC = debugmode;
uploadFile.ws.onStateChanged = onFileUploadStateChange;
uploadFile.ws.Start(filesNode._id);
}

View File

@ -69,9 +69,9 @@ module.exports.CreateWebServer = function (parent, db, args, secret, certificate
obj.userAllowedIp = args.userallowedip; // List of allowed IP addresses for users
// Perform hash on web certificate and agent certificate
obj.webCertificatHash = parent.certificateOperations.forge.pki.getPublicKeyFingerprint(parent.certificateOperations.forge.pki.certificateFromPem(obj.certificates.web.cert).publicKey, { md: parent.certificateOperations.forge.md.sha256.create(), encoding: 'binary' });
obj.webCertificatHashHex = parent.certificateOperations.forge.pki.getPublicKeyFingerprint(parent.certificateOperations.forge.pki.certificateFromPem(obj.certificates.web.cert).publicKey, { md: parent.certificateOperations.forge.md.sha256.create(), encoding: 'hex' });
obj.agentCertificatHashHex = parent.certificateOperations.forge.pki.getPublicKeyFingerprint(parent.certificateOperations.forge.pki.certificateFromPem(obj.certificates.agent.cert).publicKey, { md: parent.certificateOperations.forge.md.sha256.create(), encoding: 'hex' });
obj.webCertificatHash = 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.webCertificatHashHex = parent.certificateOperations.forge.pki.getPublicKeyFingerprint(parent.certificateOperations.forge.pki.certificateFromPem(obj.certificates.web.cert).publicKey, { md: parent.certificateOperations.forge.md.sha384.create(), encoding: 'hex' });
obj.agentCertificatHashHex = 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.agentCertificatAsn1 = parent.certificateOperations.forge.asn1.toDer(parent.certificateOperations.forge.pki.certificateToAsn1(parent.certificateOperations.forge.pki.certificateFromPem(parent.certificates.agent.cert))).getBytes();
// Main lists
@ -86,9 +86,9 @@ module.exports.CreateWebServer = function (parent, db, args, secret, certificate
obj.wsPeerRelays = {}; // Id -> { ServerId, Time }
// Setup randoms
obj.crypto.randomBytes(32, function (err, buf) { obj.httpAuthRandom = buf; });
obj.crypto.randomBytes(48, function (err, buf) { obj.httpAuthRandom = buf; });
obj.crypto.randomBytes(16, function (err, buf) { obj.httpAuthRealm = buf.toString('hex'); });
obj.crypto.randomBytes(32, function (err, buf) { obj.relayRandom = buf; });
obj.crypto.randomBytes(48, function (err, buf) { obj.relayRandom = buf; });
function EscapeHtml(x) { if (typeof x == "string") return x.replace(/&/g, '&amp;').replace(/>/g, '&gt;').replace(/</g, '&lt;').replace(/"/g, '&quot;').replace(/'/g, '&apos;'); if (typeof x == "boolean") return x; if (typeof x == "number") return x; }
function EscapeHtmlBreaks(x) { if (typeof x == "string") return x.replace(/&/g, '&amp;').replace(/>/g, '&gt;').replace(/</g, '&lt;').replace(/"/g, '&quot;').replace(/'/g, '&apos;').replace(/\r/g, '<br />').replace(/\n/g, '').replace(/\t/g, '&nbsp;&nbsp;'); if (typeof x == "boolean") return x; if (typeof x == "number") return x; }
@ -745,11 +745,8 @@ module.exports.CreateWebServer = function (parent, db, args, secret, certificate
// let's chain up the TLSSocket <-> SerialTunnel <-> CIRA APF (chnl)
// Anything that needs to be forwarded by SerialTunnel will be encapsulated by chnl write
ser.forwardwrite = function (msg) {
// Convert a buffer into a string, "msg = msg.toString('ascii');" does not work
// TLS ---> CIRA
var msg2 = "";
for (var i = 0; i < msg.length; i++) { msg2 += String.fromCharCode(msg[i]); }
chnl.write(msg2);
chnl.write(msg.toString('binary'));
};
// When APF tunnel return something, update SerialTunnel buffer
@ -776,10 +773,9 @@ module.exports.CreateWebServer = function (parent, db, args, secret, certificate
tlsock.on('data', function (data) {
// AMT/TLS ---> WS
try {
var data2 = "";
for (var i = 0; i < data.length; i++) { data2 += String.fromCharCode(data[i]); }
if (ws.interceptor) { data2 = ws.interceptor.processAmtData(data2); } // Run data thru interceptor
ws.send(data2);
data = data.toString('binary');
if (ws.interceptor) { data = ws.interceptor.processAmtData(data); } // Run data thru interceptor
ws.send(data);
} catch (e) { }
});
@ -797,11 +793,9 @@ module.exports.CreateWebServer = function (parent, db, args, secret, certificate
// If the CIRA connection is pending, the CIRA channel has built-in buffering, so we are ok sending anyway.
ws.on('message', function (msg) {
// WS ---> AMT/TLS
// Convert a buffer into a string, "msg = msg.toString('ascii');" does not work
var msg2 = "";
for (var i = 0; i < msg.length; i++) { msg2 += String.fromCharCode(msg[i]); }
if (ws.interceptor) { msg2 = ws.interceptor.processBrowserData(msg2); } // Run data thru interceptor
if (ws.forwardclient.xtls == 1) { ws.forwardclient.write(Buffer.from(msg2, 'binary')); } else { ws.forwardclient.write(msg2); }
msg = msg.toString('binary');
if (ws.interceptor) { msg = ws.interceptor.processBrowserData(msg); } // Run data thru interceptor
if (ws.forwardclient.xtls == 1) { ws.forwardclient.write(Buffer.from(msg, 'binary')); } else { ws.forwardclient.write(msg); }
});
// If error, do nothing
@ -849,11 +843,9 @@ module.exports.CreateWebServer = function (parent, db, args, secret, certificate
// When data is received from the web socket, forward the data into the associated TCP connection.
ws.on('message', function (msg) {
Debug(1, 'TCP relay data to ' + node.host + ', ' + msg.length + ' bytes'); // DEBUG
// Convert a buffer into a string, "msg = msg.toString('ascii');" does not work
var msg2 = "";
for (var i = 0; i < msg.length; i++) { msg2 += String.fromCharCode(msg[i]); }
if (ws.interceptor) { msg2 = ws.interceptor.processBrowserData(msg2); } // Run data thru interceptor
ws.forwardclient.write(new Buffer(msg2, "ascii")); // Forward data to the associated TCP connection.
msg = msg.toString('binary');
if (ws.interceptor) { msg = ws.interceptor.processBrowserData(msg); } // Run data thru interceptor
ws.forwardclient.write(new Buffer(msg, 'binary')); // Forward data to the associated TCP connection.
});
// If error, do nothing
@ -1301,7 +1293,7 @@ module.exports.CreateWebServer = function (parent, db, args, secret, certificate
// TODO: Right now, we only create type 1 Agent-less Intel AMT mesh, or type 2 Agent mesh
if ((command.meshtype == 1) || (command.meshtype == 2)) {
// Create a type 1 agent-less Intel AMT mesh.
obj.crypto.randomBytes(32, function (err, buf) {
obj.crypto.randomBytes(48, function (err, buf) {
var meshid = 'mesh/' + domain.id + '/' + buf.toString('hex').toUpperCase();
var links = {}
links[user._id] = { name: user.name, rights: 0xFFFFFFFF };
@ -1455,7 +1447,7 @@ module.exports.CreateWebServer = function (parent, db, args, secret, certificate
if (mesh.links[user._id] == null || ((mesh.links[user._id].rights & 4) == 0)) return;
// Create a new nodeid
obj.crypto.randomBytes(32, function (err, buf) {
obj.crypto.randomBytes(48, function (err, buf) {
// create the new node
var nodeid = 'node/' + domain.id + '/' + buf.toString('hex').toUpperCase();
var device = { type: 'node', mtype: 1, _id: nodeid, meshid: command.meshid, name: command.devicename, host: command.hostname, domain: domain.id, intelamt: { user: command.amtusername, pass: command.amtpassword, tls: parseInt(command.amttls) } };
@ -1787,7 +1779,7 @@ module.exports.CreateWebServer = function (parent, db, args, secret, certificate
var authstr = req.headers['authorization'];
if (authstr.substring(0, 7) == "Digest ") {
var auth = obj.common.parseNameValueList(obj.common.quoteSplit(authstr.substring(7)));
if ((req.url === auth.uri) && (obj.httpAuthRealm === auth.realm) && (auth.opaque === obj.crypto.createHmac('SHA256', obj.httpAuthRandom).update(auth.nonce).digest('hex'))) {
if ((req.url === auth.uri) && (obj.httpAuthRealm === auth.realm) && (auth.opaque === obj.crypto.createHmac('SHA384', obj.httpAuthRandom).update(auth.nonce).digest('hex'))) {
// Read the data, we need to get the arg field
var eventData = '';
@ -1806,7 +1798,7 @@ module.exports.CreateWebServer = function (parent, db, args, secret, certificate
if (nodes.length == 1) {
// Yes, the node exists, compute Intel AMT digest password
var node = nodes[0];
var amtpass = obj.crypto.createHash('sha256').update(auth.username.toLowerCase() + ":" + nodeid + ":" + obj.parent.dbconfig.amtWsEventSecret).digest("base64").substring(0, 12).split("/").join("x").split("\\").join("x");
var amtpass = obj.crypto.createHash('sha384').update(auth.username.toLowerCase() + ":" + nodeid + ":" + obj.parent.dbconfig.amtWsEventSecret).digest("base64").substring(0, 12).split("/").join("x").split("\\").join("x");
// Check the MD5 hash
if (auth.response === obj.common.ComputeDigesthash(auth.username, amtpass, auth.realm, "POST", auth.uri, auth.qop, auth.nonce, auth.nc, auth.cnonce)) {
@ -1848,8 +1840,8 @@ module.exports.CreateWebServer = function (parent, db, args, secret, certificate
} catch (e) { console.log(e); }
// Send authentication response
obj.crypto.randomBytes(32, function (err, buf) {
var nonce = buf.toString('hex'), opaque = obj.crypto.createHmac('SHA256', obj.httpAuthRandom).update(nonce).digest('hex');
obj.crypto.randomBytes(48, function (err, buf) {
var nonce = buf.toString('hex'), opaque = obj.crypto.createHmac('SHA384', obj.httpAuthRandom).update(nonce).digest('hex');
res.set({ 'WWW-Authenticate': 'Digest realm="' + obj.httpAuthRealm + '", qop="auth,auth-int", nonce="' + nonce + '", opaque="' + opaque + '"' });
res.sendStatus(401);
});
@ -1921,7 +1913,7 @@ module.exports.CreateWebServer = function (parent, db, args, secret, certificate
} else {
// Send a list of available mesh agents
var response = '<html><head><title>Mesh Agents</title><style>table,th,td { border:1px solid black;border-collapse:collapse;padding:3px; }</style></head><body><table>';
response += '<tr style="background-color:lightgray"><th>ID</th><th>Description</th><th>Link</th><th>Size</th><th>SHA256</th></tr>';
response += '<tr style="background-color:lightgray"><th>ID</th><th>Description</th><th>Link</th><th>Size</th><th>SHA384</th></tr>';
for (var agentid in obj.parent.meshAgentBinaries) {
var agentinfo = obj.parent.meshAgentBinaries[agentid];
response += '<tr><td>' + agentinfo.id + '</td><td>' + agentinfo.desc + '</td>';
@ -2135,13 +2127,11 @@ module.exports.CreateWebServer = function (parent, db, args, secret, certificate
agent.send(obj.common.ShortToStr(11) + obj.common.ShortToStr(0)); // Command 11, ask for mesh core hash.
} else {
agent.agentCoreCheck = 1000; // Tell the agent object we are not using a custom core.
// Perform a SHA256 hash on the core module
var buf = new Buffer(core, 'ascii');
var hash = obj.crypto.createHash('sha256').update(buf).digest(), hash2 = "";
for (var i = 0; i < hash.length; i++) { hash2 += String.fromCharCode(hash[i]); }
// Perform a SHA384 hash on the core module
var hash = obj.crypto.createHash('sha384').update(new Buffer(core, 'binary')).digest().toString('binary');
// Send the code module to the agent
agent.send(obj.common.ShortToStr(10) + obj.common.ShortToStr(0) + hash2 + core); // TODO: Add core encoding short
agent.send(obj.common.ShortToStr(10) + obj.common.ShortToStr(0) + hash + core);
}
}
}