1
0
Fork 0
mirror of https://github.com/Ylianst/MeshCentral.git synced 2025-02-12 11:01:52 +00:00

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 selfInfoUpdateTimer = null;
var http = require('http'); var http = require('http');
var fs = require('fs'); var fs = require('fs');
var rtc = require('ILibWebRTC');
var wifiScannerLib = null; var wifiScannerLib = null;
var wifiScanner = 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(); } if (len > 0) { this.write(buf.slice(0, len)); } else { fs.closeSync(this.httprequest.downloadFile); this.httprequest.downloadFile = undefined; this.end(); }
return; return;
} }
// Setup remote desktop & terminal without using native pipes // 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.terminal) && (obj.useNativePipes == false)) { this.httprequest.terminal.write(data); return; }
if (this.httprequest.state == 0) { if (this.httprequest.state == 0) {

View file

@ -13,23 +13,15 @@ module.exports.CertificateOperations = function () {
obj.getFilesizeInBytes = function(filename) { try { return obj.fs.statSync(filename)["size"]; } catch (err) { return -1; } } 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; } } obj.fileExists = function(filePath) { try { return obj.fs.statSync(filePath).isFile(); } catch (err) { return false; } }
// Return the SHA256 hash of the certificate public key // Return the SHA386 hash of the certificate public key
obj.getPublicKeyHash = function (cert) { obj.getPublicKeyHash = function (cert) {
var publickey = obj.pki.certificateFromPem(cert).publicKey; var publickey = obj.pki.certificateFromPem(cert).publicKey;
return obj.pki.getPublicKeyFingerprint(publickey, { encoding: 'hex', md: obj.forge.md.sha256.create() }); return obj.pki.getPublicKeyFingerprint(publickey, { encoding: 'hex', md: obj.forge.md.sha384.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;
} }
// Create a self-signed certificate // Create a self-signed certificate
obj.GenerateRootCertificate = function (addThumbPrintToName, commonName, country, organization) { 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(); var cert = obj.pki.createCertificate();
cert.publicKey = keys.publicKey; cert.publicKey = keys.publicKey;
cert.serialNumber = '' + Math.floor((Math.random() * 100000) + 1); ; cert.serialNumber = '' + Math.floor((Math.random() * 100000) + 1); ;
@ -55,14 +47,14 @@ module.exports.CertificateOperations = function () {
}, { }, {
name: 'subjectKeyIdentifier' 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 }; return { cert: cert, key: keys.privateKey };
} }
// Issue a certificate from a root // Issue a certificate from a root
obj.IssueWebServerCertificate = function (rootcert, addThumbPrintToName, commonName, country, organization, extKeyUsage) { obj.IssueWebServerCertificate = function (rootcert, addThumbPrintToName, commonName, country, organization, extKeyUsage, strong) {
var keys = obj.pki.rsa.generateKeyPair(2048); var keys = obj.pki.rsa.generateKeyPair((strong == true) ? 3072 : 2048);
var cert = obj.pki.createCertificate(); var cert = obj.pki.createCertificate();
cert.publicKey = keys.publicKey; cert.publicKey = keys.publicKey;
cert.serialNumber = '' + Math.floor((Math.random() * 100000) + 1); ; cert.serialNumber = '' + Math.floor((Math.random() * 100000) + 1); ;
@ -128,8 +120,7 @@ module.exports.CertificateOperations = function () {
}] }]
if (subjectAltName != null) extensions.push(subjectAltName); if (subjectAltName != null) extensions.push(subjectAltName);
cert.setExtensions(extensions); cert.setExtensions(extensions);
cert.sign(rootcert.key, obj.forge.md.sha384.create());
cert.sign(rootcert.key, obj.forge.md.sha256.create());
return { cert: cert, key: keys.privateKey }; return { cert: cert, key: keys.privateKey };
} }
@ -234,7 +225,7 @@ module.exports.CertificateOperations = function () {
if (xorganizationField != null) { xorganization = xorganizationField.value; } 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. 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; var rootCertAndKey, rootCertificate, rootPrivateKey, rootName;
if (r.root == undefined) { if (r.root == undefined) {
@ -255,7 +246,7 @@ module.exports.CertificateOperations = function () {
// If the web certificate does not exist, create one // If the web certificate does not exist, create one
var webCertAndKey, webCertificate, webPrivateKey; var webCertAndKey, webCertificate, webPrivateKey;
if ((r.web == null) || (forceWebCertGen == 1)) { 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); webCertificate = obj.pki.certificateToPem(webCertAndKey.cert);
webPrivateKey = obj.pki.privateKeyToPem(webCertAndKey.key); webPrivateKey = obj.pki.privateKeyToPem(webCertAndKey.key);
obj.fs.writeFileSync(directory + '/webserver-cert-public.crt', webCertificate); 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 // If the Intel AMT MPS certificate does not exist, create one
var mpsCertAndKey, mpsCertificate, mpsPrivateKey; var mpsCertAndKey, mpsCertificate, mpsPrivateKey;
if ((r.mps == null) || (forceWebCertGen == 1)) { 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); mpsCertificate = obj.pki.certificateToPem(mpsCertAndKey.cert);
mpsPrivateKey = obj.pki.privateKeyToPem(mpsCertAndKey.key); mpsPrivateKey = obj.pki.privateKeyToPem(mpsCertAndKey.key);
obj.fs.writeFileSync(directory + '/mpsserver-cert-public.crt', mpsCertificate); 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 // If the Intel AMT console certificate does not exist, create one
var consoleCertAndKey, consoleCertificate, consolePrivateKey, amtConsoleName = 'MeshCentral'; var consoleCertAndKey, consoleCertificate, consolePrivateKey, amtConsoleName = 'MeshCentral';
if (r.console == null) { 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); consoleCertificate = obj.pki.certificateToPem(consoleCertAndKey.cert);
consolePrivateKey = obj.pki.privateKeyToPem(consoleCertAndKey.key); consolePrivateKey = obj.pki.privateKeyToPem(consoleCertAndKey.key);
obj.fs.writeFileSync(directory + '/amtconsole-cert-public.crt', consoleCertificate); 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 // If the mesh agent server certificate does not exist, create one
var agentCertAndKey, agentCertificate, agentPrivateKey; var agentCertAndKey, agentCertificate, agentPrivateKey;
if (r.agent == null) { if (r.agent == null) {
agentCertAndKey = obj.IssueWebServerCertificate(rootCertAndKey, true, 'MeshCentralAgentServer'); agentCertAndKey = obj.IssueWebServerCertificate(rootCertAndKey, true, 'MeshCentralAgentServer', null, true);
agentCertificate = obj.pki.certificateToPem(agentCertAndKey.cert); agentCertificate = obj.pki.certificateToPem(agentCertAndKey.cert);
agentPrivateKey = obj.pki.privateKeyToPem(agentCertAndKey.key); agentPrivateKey = obj.pki.privateKeyToPem(agentCertAndKey.key);
obj.fs.writeFileSync(directory + '/agentserver-cert-public.crt', agentCertificate); obj.fs.writeFileSync(directory + '/agentserver-cert-public.crt', agentCertificate);

89
db.js
View file

@ -43,7 +43,7 @@ module.exports.CreateDB = function (args, datapath) {
if ((docs.length == 1) && (docs[0].value != null)) { if ((docs.length == 1) && (docs[0].value != null)) {
obj.identifier = docs[0].value; obj.identifier = docs[0].value;
} else { } 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 }); obj.Set({ _id: 'DatabaseIdentifier', value: obj.identifier });
} }
}); });
@ -53,95 +53,10 @@ module.exports.CreateDB = function (args, datapath) {
var ver = 0; var ver = 0;
if (docs && docs.length == 1) { ver = docs[0].value; } if (docs && docs.length == 1) { ver = docs[0].value; }
// Upgrade schema 0 to schema 1 // TODO: Any schema upgrades here...
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); func(ver);
}); });
});
} else { func(ver); }
});
} }
obj.cleanup = function () { obj.cleanup = function () {

View file

@ -27,6 +27,7 @@ module.exports.CreateMeshAgent = function (parent, db, ws, req, args, domain) {
obj.agentUpdate = null; obj.agentUpdate = null;
var agentUpdateBlockSize = 65520; var agentUpdateBlockSize = 65520;
obj.remoteaddr = obj.ws._socket.remoteAddress; obj.remoteaddr = obj.ws._socket.remoteAddress;
obj.useSHA386 = false;
if (obj.remoteaddr.startsWith('::ffff:')) { obj.remoteaddr = obj.remoteaddr.substring(7); } if (obj.remoteaddr.startsWith('::ffff:')) { obj.remoteaddr = obj.remoteaddr.substring(7); }
ws._socket.setKeepAlive(true, 0); // Set TCP keep alive 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 // When data is received from the mesh agent web socket
ws.on('message', function (msg) { ws.on('message', function (msg) {
if (msg.length < 2) return; if (msg.length < 2) return;
if (typeof msg == 'object') { if (typeof msg == 'object') { msg = msg.toString('binary'); } // TODO: Could change this entire method to use Buffer instead of binary string
// Convert the buffer into a string
var msg2 = "";
for (var i = 0; i < msg.length; i++) { msg2 += String.fromCharCode(msg[i]); }
msg = msg2;
}
if (obj.authenticated == 2) { // We are authenticated if (obj.authenticated == 2) { // We are authenticated
if (msg.charCodeAt(0) == 123) { processAgentData(msg); } 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. // We need to check if the core is current.
// TODO: Check if we have a mesh specific core. If so, use that. // TODO: Check if we have a mesh specific core. If so, use that.
var agentMeshCoreHash = null; 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 (agentMeshCoreHash != obj.parent.parent.defaultMeshCoreHash) {
if (obj.agentCoreCheck < 5) { // This check is in place to avoid a looping core update. if (obj.agentCoreCheck < 5) { // This check is in place to avoid a looping core update.
if (obj.parent.parent.defaultMeshCoreHash == null) { 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 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(); var agenthash = obj.common.rstr2hex(msg.substring(4)).toLowerCase();
if (agenthash != obj.agentInfo.hash) { if (agenthash != obj.agentExeInfo.hash) {
// Mesh agent update required // Mesh agent update required
console.log('Agent update required, NodeID=0x' + obj.nodeid.substring(0, 16) + ', ' + obj.agentInfo.desc); console.log('Agent update required, NodeID=0x' + obj.nodeid.substring(0, 16) + ', ' + obj.agentExeInfo.desc);
obj.fs.open(obj.agentInfo.path, 'r', function (err, fd) { obj.fs.open(obj.agentExeInfo.path, 'r', function (err, fd) {
if (err) { return console.error(err); } if (err) { return console.error(err); }
obj.agentUpdate = { oldHash: agenthash, ptr: 0, buf: new Buffer(agentUpdateBlockSize + 4), fd: fd }; 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."); //console.log("Agent update file open.");
obj.send(obj.common.ShortToStr(13) + obj.common.ShortToStr(0)); // Command 13, start mesh agent download 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) { if (len < agentUpdateBlockSize) {
//console.log("Agent update sent"); //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.fs.close(obj.agentUpdate.fd);
obj.agentUpdate = null; obj.agentUpdate = null;
} }
@ -152,18 +148,18 @@ module.exports.CreateMeshAgent = function (parent, db, ws, req, args, domain) {
var cmd = obj.common.ReadShort(msg, 0); var cmd = obj.common.ReadShort(msg, 0);
if (cmd == 1) { if (cmd == 1) {
// Agent authentication request // 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. 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 // Check that the server hash matches out own web certificate hash (SHA386)
if (obj.parent.webCertificatHash != msg.substring(2, 34)) { obj.close(); return; } if (obj.parent.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 + AgentNonce + ServerNonce
var privateKey = obj.forge.pki.privateKeyFromPem(obj.parent.certificates.agent.key); 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(msg.substring(2), 'binary');
md.update(obj.nonce, 'binary'); md.update(obj.nonce, 'binary');
obj.agentnonce = msg.substring(34); obj.agentnonce = msg.substring(50);
// Send back our certificate + signature // 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 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 = {};
obj.unauth.nodeCert = null; obj.unauth.nodeCert = null;
try { obj.unauth.nodeCert = obj.forge.pki.certificateFromAsn1(obj.forge.asn1.fromDer(msg.substring(4, 4 + certlen))); } catch (e) { return; } 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 // 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(); completeAgentConnection();
} }
else if (cmd == 3) { else if (cmd == 3) {
// Agent meshid // 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. obj.receivedCommands += 4; // Agent can't send the same command twice on the same connection ever. Block DOS attack path.
// Set the meshid // 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.agentId = obj.common.ReadInt(msg, 6);
obj.agentInfo.agentVersion = obj.common.ReadInt(msg, 10); obj.agentInfo.agentVersion = obj.common.ReadInt(msg, 10);
obj.agentInfo.platformType = obj.common.ReadInt(msg, 14); obj.agentInfo.platformType = obj.common.ReadInt(msg, 14);
obj.meshid = obj.common.rstr2hex(msg.substring(18, 50)).toUpperCase(); obj.meshid = obj.common.rstr2hex(msg.substring(18, 66)).toUpperCase();
obj.agentInfo.capabilities = obj.common.ReadInt(msg, 50); obj.agentInfo.capabilities = obj.common.ReadInt(msg, 66);
var computerNameLen = obj.common.ReadShort(msg, 54); var computerNameLen = obj.common.ReadShort(msg, 70);
obj.agentInfo.computerName = msg.substring(56, 56 + computerNameLen); obj.agentInfo.computerName = msg.substring(72, 72 + computerNameLen);
obj.dbMeshKey = 'mesh/' + obj.domain.id + '/' + obj.meshid; obj.dbMeshKey = 'mesh/' + obj.domain.id + '/' + obj.meshid;
completeAgentConnection(); 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 + ')'); }); // 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. // 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 // Send 384 bits SHA384 hash of TLS cert public key + 384 bits nonce
obj.nonce = obj.forge.random.getBytesSync(32); obj.nonce = obj.forge.random.getBytesSync(48);
obj.send(obj.common.ShortToStr(1) + parent.webCertificatHash + obj.nonce); // Command 1, hash + nonce 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 // 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 // Mark when we connected to this agent
obj.connectTime = Date.now(); obj.connectTime = Date.now();
if (nodes.length == 0) { if (nodes.length == 0) {
// This node does not exist, create it. // 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 }; 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. 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 // Check if we need to make an native update check
obj.agentInfo = obj.parent.parent.meshAgentBinaries[obj.agentInfo.agentId]; obj.agentExeInfo = 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 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 // Check if we already have IP location information for this node
obj.db.Get('iploc_' + obj.remoteaddr, function (err, iplocs) { 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 // Verify the agent signature
function processAgentSignature(msg) { 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.parent.webCertificatHash, 'binary');
md.update(obj.nonce, 'binary'); md.update(obj.nonce, 'binary');
md.update(obj.agentnonce, '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 // Connection is a success, clean up
obj.nodeid = obj.unauth.nodeid.toUpperCase(); obj.nodeid = obj.unauth.nodeid.toUpperCase();

View file

@ -29,8 +29,8 @@ function CreateMeshCentralServer() {
obj.certificateOperations = require('./certoperations.js').CertificateOperations(); obj.certificateOperations = require('./certoperations.js').CertificateOperations();
obj.defaultMeshCore = null; obj.defaultMeshCore = null;
obj.defaultMeshCoreHash = null; obj.defaultMeshCoreHash = null;
obj.meshAgentBinaries = {}; // Mesh Agent Binaries, Architecture type --> { 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:(sha256 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.multiServer = null;
obj.currentVer = null; obj.currentVer = null;
obj.maintenanceTimer = null; obj.maintenanceTimer = null;
@ -38,11 +38,11 @@ function CreateMeshCentralServer() {
// Setup the default configuration and files paths // 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\\'))) { 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.datapath = obj.path.join(__dirname, '../../meshcentral-data');
obj.filespath = obj.path.join(__dirname, '../../.meshcentral-files'); obj.filespath = obj.path.join(__dirname, '../../meshcentral-files');
} else { } else {
obj.datapath = obj.path.join(__dirname, '../.meshcentral-data'); obj.datapath = obj.path.join(__dirname, '../meshcentral-data');
obj.filespath = obj.path.join(__dirname, '../.meshcentral-files'); obj.filespath = obj.path.join(__dirname, '../meshcentral-files');
} }
// Create data and files folders if needed // 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. 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 // 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; } } 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.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)) { 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') { if (obj.platform == 'win32') {
console.log('Run as a Windows Service'); console.log('Run as a Windows Service');
console.log(' --install/uninstall Install Meshcentral as a background 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 // 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 = {}; }
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 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) { 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) { for (var i in obj.config.domains) {
@ -223,6 +223,7 @@ function CreateMeshCentralServer() {
// See if any database operations needs to be completed // 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.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.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.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.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; } 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(); } while (obj.dbconfig.amtWsEventSecret == null) { process.nextTick(); }
var username = buf.toString('hex'); var username = buf.toString('hex');
var nodeid = obj.args.getwspass; 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('--- Intel(r) AMT WSMAN eventing credentials ---');
console.log('Username: ' + username); console.log('Username: ' + username);
console.log('Password: ' + pass); console.log('Password: ' + pass);
@ -299,7 +300,7 @@ function CreateMeshCentralServer() {
obj.updateMeshAgentInstallScripts(); obj.updateMeshAgentInstallScripts();
// Setup and start the web server // 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 // Setup Mesh Multi-Server if needed
obj.multiServer = require('./multiserver.js').CreateMultiServer(obj, obj.args); obj.multiServer = require('./multiserver.js').CreateMultiServer(obj, obj.args);
if (obj.multiServer != null) { if (obj.multiServer != null) {
@ -657,7 +658,7 @@ function CreateMeshCentralServer() {
// Set the new default meshcore.js // 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) meshCore = obj.common.IntToStr(0) + moduleAdditions + meshCore; // Add the 4 bytes encoding type & flags (Set to 0 for raw)
obj.defaultMeshCore = meshCore; 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); } if (func != null) { func(true); }
} }
@ -690,7 +691,7 @@ function CreateMeshCentralServer() {
}); });
stream.info = meshAgentsInstallScriptList[scriptid]; stream.info = meshAgentsInstallScriptList[scriptid];
stream.agentpath = scriptpath; stream.agentpath = scriptpath;
stream.hash = obj.crypto.createHash('sha256', stream); stream.hash = obj.crypto.createHash('sha384', stream);
} catch (e) { } } catch (e) { }
} }
} }
@ -748,7 +749,7 @@ function CreateMeshCentralServer() {
}); });
stream.info = meshAgentsArchitectureNumbers[archid]; stream.info = meshAgentsArchitectureNumbers[archid];
stream.agentpath = agentpath; stream.agentpath = agentpath;
stream.hash = obj.crypto.createHash('sha256', stream); stream.hash = obj.crypto.createHash('sha384', stream);
} catch (e) { } } 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(); }); 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 // 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"); } 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. // 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 // Construct a MeshRelay object, called upon connection
module.exports.CreateMeshRelayKey = function (parent, func) { 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(); 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); func(key);
}); });
} }
@ -41,7 +41,7 @@ module.exports.CreateMeshRelay = function (parent, ws, req) {
// Check the identifier, if running without TLS, skip this. // Check the identifier, if running without TLS, skip this.
var ids = obj.id.split(':'); var ids = obj.id.split(':');
if (ids.length != 3) { obj.ws.close(); obj.id = null; return null; } // Invalid ID, drop this. 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. if ((Date.now() - parseInt(ids[1])) > 120000) { obj.ws.close(); obj.id = null; return null; } // Expired time, drop this.
obj.id = ids[0]; 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 // When data is received from the mesh relay web socket
ws.on('message', function (data) { 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) { } } if (this.peer != null) { try { this.pause(); this.peer.send(data, ws.flushSink); } catch (e) { } }
}); });

View file

@ -17,13 +17,13 @@ module.exports.CreateMeshScanner = function (parent) {
var periodicScanTime = (60000 * 20); // Interval between scans, 20 minutes. var periodicScanTime = (60000 * 20); // Interval between scans, 20 minutes.
var membershipIPv4 = '239.255.255.235'; var membershipIPv4 = '239.255.255.235';
var membershipIPv6 = 'FF02:0:0:0:0:0:0:FE'; 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; obj.error = 0;
// Get a list of IPv4 and IPv6 interface addresses // Get a list of IPv4 and IPv6 interface addresses
function getInterfaceList() { function getInterfaceList() {
var ipv4 = [], ipv6 = []; var ipv4 = ['*'], ipv6 = ['*']; // Bind to IN_ADDR_ANY always
if (parent.platform == 'win32') { ipv4.push('*'); ipv6.push('*'); } // Bind to IN_ADDR_ANY only on Windows if (parent.platform == 'win32') { // On Windows, also bind to each interface seperatly
var interfaces = require('os').networkInterfaces(); var interfaces = require('os').networkInterfaces();
for (var i in interfaces) { for (var i in interfaces) {
var interface = interfaces[i]; var interface = interfaces[i];
@ -35,6 +35,7 @@ module.exports.CreateMeshScanner = function (parent) {
} }
} }
} }
}
return { ipv4: ipv4, ipv6: ipv6 }; return { ipv4: ipv4, ipv6: ipv6 };
} }

View file

@ -112,7 +112,7 @@ module.exports.CreateMpsServer = function (parent, db, args, certificates) {
socket.tag.domainid = domainid; socket.tag.domainid = domainid;
socket.tag.meshid = 'mesh/' + domainid + '/' + meshid; 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.name = socket.tag.clientCert.subject.CN;
socket.tag.connectTime = Date.now(); socket.tag.connectTime = Date.now();
socket.tag.host = ''; socket.tag.host = '';
@ -228,7 +228,7 @@ module.exports.CreateMpsServer = function (parent, db, args, certificates) {
// Intel AMT GUID (socket.tag.SystemId) will be used at NodeID // Intel AMT GUID (socket.tag.SystemId) will be used at NodeID
var systemid = socket.tag.SystemId.split('-').join('').toUpperCase(); var systemid = socket.tag.SystemId.split('-').join('').toUpperCase();
socket.tag.name = ''; 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; socket.tag.meshid = mesh._id;
obj.db.Get(socket.tag.nodeid, function (err, nodes) { 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; if (len < 26 + requestLen + addrLen + oaddrLen + datalen) return 0;
Debug(2, 'MPS:GLOBAL_REQUEST', request, addr + ':' + port, oaddr + ':' + oport, datalen); Debug(2, 'MPS:GLOBAL_REQUEST', request, addr + ':' + port, oaddr + ':' + oport, datalen);
// TODO // TODO
return ptr + 26 + requestLen + addrLen + oaddrLen + datalen; return 26 + requestLen + addrLen + oaddrLen + datalen;
} }
return 6 + requestLen; return 6 + requestLen;

View file

@ -7,6 +7,7 @@
// Construct a Mesh Multi-Server object. This is used for MeshCentral-to-MeshCentral communication. // Construct a Mesh Multi-Server object. This is used for MeshCentral-to-MeshCentral communication.
module.exports.CreateMultiServer = function (parent, args) { module.exports.CreateMultiServer = function (parent, args) {
var obj = {}; var obj = {};
const WebSocket = require('ws');
obj.parent = parent; obj.parent = parent;
obj.crypto = require('crypto'); obj.crypto = require('crypto');
obj.peerConfig = parent.config.peers; obj.peerConfig = parent.config.peers;
@ -22,7 +23,6 @@ module.exports.CreateMultiServer = function (parent, args) {
obj.serverid = serverid; obj.serverid = serverid;
obj.url = url; obj.url = url;
obj.ws = null; obj.ws = null;
obj.conn = null;
obj.certificates = parent.parent.certificates; obj.certificates = parent.parent.certificates;
obj.common = require('./common.js'); obj.common = require('./common.js');
obj.forge = require('node-forge'); obj.forge = require('node-forge');
@ -51,36 +51,32 @@ module.exports.CreateMultiServer = function (parent, args) {
obj.connectionState = 1; obj.connectionState = 1;
// Get the web socket setup // Get the web socket setup
const WebSocket = require('websocket'); obj.ws = new WebSocket(obj.url + 'meshserver.ashx', { rejectUnauthorized: false, cert: obj.certificates.agent.cert, key: obj.certificates.agent.key });
var WebSocketClient = require('websocket').client;
obj.ws = new WebSocketClient();
obj.parent.parent.debug(1, 'OutPeer ' + obj.serverid + ': Connecting to: ' + url + 'meshserver.ashx'); obj.parent.parent.debug(1, 'OutPeer ' + obj.serverid + ': Connecting to: ' + url + 'meshserver.ashx');
// Register the connection failed event // 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 // 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.parent.parent.debug(1, 'OutPeer ' + obj.serverid + ': Connected');
obj.connectionState |= 2; obj.connectionState |= 2;
obj.conn = connection; obj.nonce = obj.forge.random.getBytesSync(48);
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(); });
// Get the peer server's certificate and compute the server public key hash // Get the peer server's certificate and compute the server public key hash
if (obj.ws.socket == null) return; if (obj.ws._socket == null) return;
var rawcertbuf = obj.ws.socket.getPeerCertificate().raw, rawcert = ''; var serverCert = obj.forge.pki.certificateFromAsn1(obj.forge.asn1.fromDer(obj.ws._socket.getPeerCertificate().raw.toString('binary')));
for (var i = 0; i < rawcertbuf.length; i++) { rawcert += String.fromCharCode(rawcertbuf[i]); } obj.serverCertHash = obj.forge.pki.getPublicKeyFingerprint(serverCert.publicKey, { encoding: 'binary', md: obj.forge.md.sha384.create() });
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() }); // 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
});
// If a message is received // If a message is received
obj.conn.on('message', function (msg) { obj.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; } if (typeof msg != 'string') { msg = msg.toString('binary'); }
else if (msg.type == 'utf8') { msg = msg.utf8Data; }
if (msg.length < 2) return; if (msg.length < 2) return;
if (msg.charCodeAt(0) == 123) { if (msg.charCodeAt(0) == 123) {
@ -90,21 +86,21 @@ module.exports.CreateMultiServer = function (parent, args) {
switch (cmd) { switch (cmd) {
case 1: { case 1: {
// Server authentication request // Server authentication request
if (msg.length != 66) { obj.parent.parent.debug(1, 'OutPeer: BAD MESSAGE(A1)'); return; } 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 // 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; } if (obj.serverCertHash != msg.substring(2, 50)) { obj.parent.parent.debug(1, 'OutPeer: Server hash mismatch.'); disconnect(); return; }
obj.servernonce = msg.substring(34); obj.servernonce = msg.substring(50);
// Use our agent root private key to sign the ServerHash + ServerNonce + AgentNonce // Use our agent certificate root private key to sign the ServerHash + ServerNonce + PeerNonce
var privateKey = obj.forge.pki.privateKeyFromPem(obj.certificates.agent.key); var privateKey = obj.forge.pki.privateKeyFromPem(obj.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(msg.substring(2), 'binary');
md.update(obj.nonce, 'binary'); md.update(obj.nonce, 'binary');
// Send back our certificate + signature // Send back our certificate + signature
agentRootCertificatAsn1 = obj.forge.asn1.toDer(obj.forge.pki.certificateToAsn1(obj.forge.pki.certificateFromPem(obj.certificates.agent.cert))).getBytes(); 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 obj.ws.send(obj.common.ShortToStr(2) + obj.common.ShortToStr(agentRootCertificatAsn1.length) + agentRootCertificatAsn1 + privateKey.sign(md)); // Command 3, signature
break; break;
} }
case 2: { case 2: {
@ -112,11 +108,11 @@ module.exports.CreateMultiServer = function (parent, args) {
var certlen = obj.common.ReadShort(msg, 2), serverCert = null; 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) { } 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; } 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() }); 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; } if (serverid !== obj.agentCertificatHashHex) { obj.parent.parent.debug(1, 'OutPeer: Server hash mismatch.'); disconnect(); return; }
// Server signature, verify it // Server signature, verify it
var md = obj.forge.md.sha256.create(); var md = obj.forge.md.sha384.create();
md.update(obj.serverCertHash, 'binary'); md.update(obj.serverCertHash, 'binary');
md.update(obj.nonce, 'binary'); md.update(obj.nonce, 'binary');
md.update(obj.servernonce, 'binary'); md.update(obj.servernonce, 'binary');
@ -131,14 +127,14 @@ module.exports.CreateMultiServer = function (parent, args) {
obj.parent.parent.debug(1, 'OutPeer ' + obj.serverid + ': Verified peer connection to ' + obj.url); obj.parent.parent.debug(1, 'OutPeer ' + obj.serverid + ': Verified peer connection to ' + obj.url);
// Send information about our server to the peer // 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.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); } //if ((obj.connectionState == 15) && (obj.connectHandler != null)) { obj.connectHandler(1); }
break; break;
} }
case 4: { case 4: {
// Server confirmed authentication, we are allowed to send commands to the server // Server confirmed authentication, we are allowed to send commands to the server
obj.connectionState |= 8; 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.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); } //if ((obj.connectionState == 15) && (obj.connectHandler != null)) { obj.connectHandler(1); }
break; break;
} }
@ -149,25 +145,13 @@ module.exports.CreateMultiServer = function (parent, args) {
} }
} }
}); });
// 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);
});
obj.ws.connect(obj.url + 'meshserver.ashx', null, null, null, { rejectUnauthorized: false, cert: obj.certificates.agent.cert, key: obj.certificates.agent.key });
} }
// Disconnect from the server, if we need to, try again with a delay. // Disconnect from the server, if we need to, try again with a delay.
function disconnect() { function disconnect() {
if (obj.authenticated == 3) { obj.parent.ClearPeerServer(obj, obj.peerServerId); obj.authenticated = 0; } 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.connectionState == 15) && (obj.connectHandler != null)) { obj.connectHandler(0); }
if (obj.conn != null) { obj.conn.close(); obj.conn = null; } if (obj.ws != null) { obj.ws.close(); obj.ws = null; }
if (obj.ws != null) { obj.ws = null; }
if (obj.retryTimer != null) { clearTimeout(obj.retryTimer); obj.retryTimer = null; } if (obj.retryTimer != null) { clearTimeout(obj.retryTimer); obj.retryTimer = null; }
// Re-try connection // Re-try connection
if (obj.connectionState >= 1) { obj.connectionState = 1; if (obj.retryTimer == null) { obj.retryTimer = setTimeout(connect, getConnectRetryTime()); } } 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 // Send a JSON message to the peer server
obj.send = function (msg) { obj.send = function (msg) {
try { try {
if (obj.ws == null || obj.conn == null || obj.connectionState != 15) { return; } if (obj.ws == null || obj.connectionState != 15) { return; }
if (typeof msg == 'object') { obj.conn.send(JSON.stringify(msg)); return; } if (typeof msg == 'object') { obj.ws.send(JSON.stringify(msg)); return; }
if (typeof msg == 'string') { obj.conn.send(msg); return; } if (typeof msg == 'string') { obj.ws.send(msg); return; }
} catch (e) { } } 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 (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; } if (obj.serverCertHash != command.serverCertHash) { console.log('ERROR: Outer certificate hash mismatch. (' + obj.url + ', ' + command.serverid + ').'); return; }
obj.peerServerId = command.serverid; obj.peerServerId = command.serverid;
obj.peerServerKey = command.key; obj.peerServerKey = new Buffer(command.key, 'hex');
obj.authenticated = 3; obj.authenticated = 3;
obj.parent.SetupPeerServer(obj, obj.peerServerId); obj.parent.SetupPeerServer(obj, obj.peerServerId);
} }
@ -235,6 +219,7 @@ module.exports.CreateMultiServer = function (parent, args) {
obj.peerServerId = null; obj.peerServerId = null;
obj.serverCertHash = null; obj.serverCertHash = null;
if (obj.remoteaddr.startsWith('::ffff:')) { obj.remoteaddr = obj.remoteaddr.substring(7); } 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 // Send a message to the peer server
obj.send = function (data) { 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; } 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) { 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; } if (typeof msg != 'string') { msg = msg.toString('binary'); }
else if (msg.type == 'utf8') { msg = msg.utf8Data; }
if (msg.length < 2) return; if (msg.length < 2) return;
if (obj.authenticated >= 2) { // We are authenticated 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 else if (obj.authenticated < 2) { // We are not authenticated
var cmd = obj.common.ReadShort(msg, 0); var cmd = obj.common.ReadShort(msg, 0);
if (cmd == 1) { if (cmd == 1) {
// Agent authentication request // Peer server 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. 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 // 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 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(msg.substring(2), 'binary');
md.update(obj.nonce, 'binary'); md.update(obj.nonce, 'binary');
obj.agentnonce = msg.substring(34); obj.peernonce = msg.substring(50);
// Send back our certificate + signature // 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 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 (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) { else if (cmd == 2) {
// Agent certificate // Peer server certificate
if ((msg.length < 4) || ((obj.receivedCommands & 2) != 0)) return; 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 // Decode the certificate
var certlen = obj.common.ReadShort(msg, 2); var certlen = obj.common.ReadShort(msg, 2);
obj.unauth = {}; obj.unauth = {};
obj.unauth.nodeCert = null; obj.unauth.nodeCert = null;
try { obj.unauth.nodeCert = obj.forge.pki.certificateFromAsn1(obj.forge.asn1.fromDer(msg.substring(4, 4 + certlen))); } catch (e) { return; } 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 // Check the peer server signature if we can
if (obj.agentnonce == null) { obj.unauthsign = msg.substring(4 + certlen); } else { if (processAgentSignature(msg.substring(4 + certlen)) == false) { disconnect(); return; } } if (obj.peernonce == null) { obj.unauthsign = msg.substring(4 + certlen); } else { if (processPeerSignature(msg.substring(4 + certlen)) == false) { disconnect(); return; } }
completePeerServerConnection(); completePeerServerConnection();
} }
else if (cmd == 3) { else if (cmd == 3) {
// Agent meshid
if ((msg.length < 56) || ((obj.receivedCommands & 4) != 0)) return; 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(); completePeerServerConnection();
} }
} }
@ -317,36 +300,36 @@ module.exports.CreateMultiServer = function (parent, args) {
// If error, do nothing // If error, do nothing
ws.on('error', function (err) { obj.parent.parent.debug(1, 'InPeer: Connection Error: ' + err); }); 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); }); 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. // Start authenticate the peer server by sending a auth nonce & server TLS cert hash.
// Send 256 bits SHA256 hash of TLS cert public key + 256 bits nonce // Send 384 bits SHA382 hash of TLS cert public key + 384 bits nonce
obj.nonce = obj.forge.random.getBytesSync(32); obj.nonce = obj.forge.random.getBytesSync(48);
obj.send(obj.common.ShortToStr(1) + obj.webCertificatHash + obj.nonce); // Command 1, hash + nonce 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() { function completePeerServerConnection() {
if (obj.authenticated != 1) return; if (obj.authenticated != 1) return;
obj.send(obj.common.ShortToStr(4)); 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; obj.authenticated = 2;
} }
// Verify the agent signature // Verify the peer server signature
function processAgentSignature(msg) { function processPeerSignature(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.parent.webserver.webCertificatHash, 'binary'); md.update(obj.parent.parent.webserver.webCertificatHash, 'binary');
md.update(obj.nonce, '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.nodeCert.publicKey.verify(md.digest().bytes(), msg) == false) { return false; }
if (obj.unauth.nodeid !== obj.agentCertificatHashHex) { return false; } if (obj.unauth.nodeid !== obj.agentCertificatHashHex) { return false; }
// Connection is a success, clean up // Connection is a success, clean up
obj.nodeid = obj.unauth.nodeid.toUpperCase(); obj.nodeid = obj.unauth.nodeid.toUpperCase();
delete obj.nonce; delete obj.nonce;
delete obj.agentnonce; delete obj.peernonce;
delete obj.unauth; delete obj.unauth;
if (obj.unauthsign) delete obj.unauthsign; if (obj.unauthsign) delete obj.unauthsign;
obj.authenticated = 1; 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 (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; } if (obj.parent.peerConfig.servers[command.serverid] == null) { console.log('ERROR: Unknown peer serverid: ' + command.serverid + ' (' + obj.remoteaddr + ').'); return; }
obj.peerServerId = command.serverid; obj.peerServerId = command.serverid;
obj.peerServerKey = command.key; obj.peerServerKey = new Buffer(command.key, 'hex');
obj.serverCertHash = command.serverCertHash; obj.serverCertHash = command.serverCertHash;
obj.authenticated = 3; obj.authenticated = 3;
obj.parent.SetupPeerServer(obj, obj.peerServerId); 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; } 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 // Generate a cryptographic key used to encode and decode cookies
obj.generateCookieKey = function () { obj.generateCookieKey = function () { return new Buffer(obj.crypto.randomBytes(32), 'binary'); }
return new Buffer(obj.crypto.randomBytes(32), 'binary').toString('hex');
}
// Return the private key of a peer server // Return the private key of a peer server
obj.getServerCookieKey = function (serverid) { obj.getServerCookieKey = function (serverid) {
@ -400,40 +381,25 @@ module.exports.CreateMultiServer = function (parent, args) {
return null; 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) { obj.encodeCookie = function (o, key) {
try { try {
if (key == null) { key = obj.serverKey; } if (key == null) { key = obj.serverKey; }
key = require('./common.js').hex2rstr(key);
o.time = Math.floor(Date.now() / 1000); // Add the cookie creation time o.time = Math.floor(Date.now() / 1000); // Add the cookie creation time
var msg = JSON.stringify(o); var iv = new Buffer(obj.crypto.randomBytes(12), 'binary'), cipher = obj.crypto.createCipheriv('aes-256-gcm', key, iv);
msg = obj.crypto.createHmac('sha256', key.substring(16)).update(msg, 'binary', 'binary').digest('binary') + msg; var crypted = Buffer.concat([cipher.update(JSON.stringify(o), 'utf8'), cipher.final()]);
var iv = new Buffer(obj.crypto.randomBytes(16), 'binary'); return Buffer.concat([iv, cipher.getAuthTag(), crypted]).toString('base64').replace(/\+/g, '@').replace(/\//g, '$');
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, '$');
} catch (e) { return null; } } 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) { obj.decodeCookie = function (cookie, key) {
try { try {
if (key == null) { key = obj.serverKey; } if (key == null) { key = obj.serverKey; }
key = require('./common.js').hex2rstr(key); cookie = new Buffer(cookie.replace(/\@/g, '+').replace(/\$/g, '/'), 'base64'); // HEX: This is not an efficient split, but it's very compatible.
cookie = new Buffer(cookie.replace(/\@/g, '+').replace(/\$/g, '/'), 'base64').toString('hex'); // 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));
var iv = new Buffer(cookie.substring(0, 32), 'hex'); decipher.setAuthTag(cookie.slice(12, 16));
var msg = new Buffer(cookie.substring(32), 'hex'); var o = JSON.parse(decipher.update(cookie.slice(28), 'binary', 'utf8') + decipher.final('utf8'));
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);
if ((o.time == null) || (o.time == null) || (typeof o.time != 'number')) { return null; } if ((o.time == null) || (o.time == null) || (typeof o.time != 'number')) { return null; }
o.time = o.time * 1000; // Decode the cookie creation time o.time = o.time * 1000; // Decode the cookie creation time
o.dtime = Date.now() - o.time; // Decode how long ago the cookie was created 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 () { peerTunnel.connect = function () {
// Get the web socket setup // Get the web socket setup
var WebSocketClient = require('websocket').client; peerTunnel.parent.parent.debug(1, 'FTunnel ' + peerTunnel.serverid + ': Start connect to ' + peerTunnel.url);
peerTunnel.wsclient = new WebSocketClient(); peerTunnel.ws2 = new WebSocket(peerTunnel.url, { rejectUnauthorized: false });
// Register the connection failed event // 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(); });
// Register the connection event // If the peer server web socket is closed, clean up.
peerTunnel.wsclient.on('connect', function (connection) {
// 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() });
// 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; }
// 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(); }); 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 // If a message is received from the peer, Peer ---> Browser (TODO: Pipe this?)
peerTunnel.ws2.on('message', function (msg) { peerTunnel.ws2.on('message', function (msg) { try { peerTunnel.ws2.pause(); peerTunnel.ws1.send(msg, function () { peerTunnel.ws2.resume(); }); } catch (e) { } });
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 // Register the connection event
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 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.close(); return; }
// Connection accepted, resume the web socket to start the data flow
peerTunnel.ws1.resume(); 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) { } }); peerTunnel.ws1.on('message', function (msg) { try { peerTunnel.ws1.pause(); peerTunnel.ws2.send(msg, function () { peerTunnel.ws1.resume(); }); } catch (e) { } });
// If error, do nothing // 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. // 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.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 // Disconnect both sides of the tunnel

View file

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

View file

@ -21,7 +21,7 @@ var iterations = 12000;
exports.hash = function (pwd, salt, fn) { exports.hash = function (pwd, salt, fn) {
if (3 == arguments.length) { if (3 == arguments.length) {
try { 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) { } catch (e) {
// If this previous call fails, it's probably because older pbkdf2 did not specify the hashing function, just use the default. // 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')); }); 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); if (err) return fn(err);
salt = salt.toString('base64'); salt = salt.toString('base64');
try { 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) { } catch (e) {
// If this previous call fails, it's probably because older pbkdf2 did not specify the hashing function, just use the default. // 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')); }); 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.connectstate = -1;
obj.tunnelid = Math.random().toString(36).substring(2); // Generate a random client tunnel id 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.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; obj.onStateChanged = null;
// Private method // Private method
@ -43,8 +45,68 @@ var CreateAgentRedirect = function (meshserver, module, serverPublicNamePort) {
obj.xxStateChange(2); 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) { 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') { if (typeof e.data == 'object') {
var f = new FileReader(); var f = new FileReader();
if (f.readAsBinaryString) { if (f.readAsBinaryString) {
@ -81,6 +143,9 @@ var CreateAgentRedirect = function (meshserver, module, serverPublicNamePort) {
} }
else if (typeof data !== 'string') return; 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)); //console.log("xxOnSocketData", rstr2hex(data));
return obj.m.ProcessData(data); return obj.m.ProcessData(data);
@ -115,6 +180,7 @@ var CreateAgentRedirect = function (meshserver, module, serverPublicNamePort) {
//obj.debug("Agent Redir Socket Stopped"); //obj.debug("Agent Redir Socket Stopped");
obj.xxStateChange(0); obj.xxStateChange(0);
obj.connectstate = -1; obj.connectstate = -1;
obj.xxCloseWebRTC();
if (obj.socket != null) { obj.socket.close(); obj.socket = null; } 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> </tr>
</table> </table>
</div> </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="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 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"> <div style="width:100%;background-color:#003366;color:#FFF;border-radius:5px 5px 0 0">
@ -2760,6 +2760,7 @@
} else { } else {
// Setup the Mesh Agent remote desktop // Setup the Mesh Agent remote desktop
desktop = CreateAgentRedirect(meshserver, CreateAgentRemoteDesktop('Desk'), serverPublicNamePort); desktop = CreateAgentRedirect(meshserver, CreateAgentRemoteDesktop('Desk'), serverPublicNamePort);
desktop.attemptWebRTC = debugmode;
desktop.onStateChanged = onDesktopStateChange; desktop.onStateChanged = onDesktopStateChange;
desktop.m.CompressionLevel = desktopsettings.quality; // Number from 1 to 100. 50 or less is best. desktop.m.CompressionLevel = desktopsettings.quality; // Number from 1 to 100. 50 or less is best.
desktop.m.ScalingLevel = desktopsettings.scaling; desktop.m.ScalingLevel = desktopsettings.scaling;
@ -2984,6 +2985,7 @@
} else { } else {
// Setup a mesh agent terminal // Setup a mesh agent terminal
terminal = CreateAgentRedirect(meshserver, CreateAmtRemoteTerminal('Term'), serverPublicNamePort); terminal = CreateAgentRedirect(meshserver, CreateAmtRemoteTerminal('Term'), serverPublicNamePort);
terminal.attemptWebRTC = debugmode;
terminal.onStateChanged = onTerminalStateChange; terminal.onStateChanged = onTerminalStateChange;
terminal.Start(terminalNode._id); terminal.Start(terminalNode._id);
terminal.contype = 1; terminal.contype = 1;
@ -3088,6 +3090,7 @@
if (!files) { if (!files) {
// Setup a mesh agent files // Setup a mesh agent files
files = CreateAgentRedirect(meshserver, CreateRemoteFiles(p13gotFiles), serverPublicNamePort); files = CreateAgentRedirect(meshserver, CreateRemoteFiles(p13gotFiles), serverPublicNamePort);
files.attemptWebRTC = debugmode;
files.onStateChanged = onFilesStateChange; files.onStateChanged = onFilesStateChange;
files.Start(filesNode._id); files.Start(filesNode._id);
} else { } else {
@ -3288,6 +3291,7 @@
// Called by the html page to start a download, arguments are: path, file name and file size. // Called by the html page to start a download, arguments are: path, file name and file size.
function p13downloadfile(x, y, z) { function p13downloadfile(x, y, z) {
downloadFile = CreateAgentRedirect(meshserver, CreateRemoteFiles(p13gotDownloadData), serverPublicNamePort); // Create our file transport downloadFile = CreateAgentRedirect(meshserver, CreateRemoteFiles(p13gotDownloadData), serverPublicNamePort); // Create our file transport
downloadFile.attemptWebRTC = debugmode;
downloadFile.onStateChanged = onFileDownloadStateChange; downloadFile.onStateChanged = onFileDownloadStateChange;
downloadFile.xpath = decodeURIComponent(x); downloadFile.xpath = decodeURIComponent(x);
downloadFile.xfile = decodeURIComponent(y); downloadFile.xfile = decodeURIComponent(y);
@ -3365,6 +3369,7 @@
// Connect again // Connect again
function p13uploadReconnect() { function p13uploadReconnect() {
uploadFile.ws = CreateAgentRedirect(meshserver, CreateRemoteFiles(p13gotUploadData), serverPublicNamePort); uploadFile.ws = CreateAgentRedirect(meshserver, CreateRemoteFiles(p13gotUploadData), serverPublicNamePort);
uploadFile.ws.attemptWebRTC = debugmode;
uploadFile.ws.onStateChanged = onFileUploadStateChange; uploadFile.ws.onStateChanged = onFileUploadStateChange;
uploadFile.ws.Start(filesNode._id); 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 obj.userAllowedIp = args.userallowedip; // List of allowed IP addresses for users
// Perform hash on web certificate and agent certificate // 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.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.sha256.create(), encoding: 'hex' }); 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.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.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(); obj.agentCertificatAsn1 = parent.certificateOperations.forge.asn1.toDer(parent.certificateOperations.forge.pki.certificateToAsn1(parent.certificateOperations.forge.pki.certificateFromPem(parent.certificates.agent.cert))).getBytes();
// Main lists // Main lists
@ -86,9 +86,9 @@ module.exports.CreateWebServer = function (parent, db, args, secret, certificate
obj.wsPeerRelays = {}; // Id -> { ServerId, Time } obj.wsPeerRelays = {}; // Id -> { ServerId, Time }
// Setup randoms // 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(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 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; } 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) // let's chain up the TLSSocket <-> SerialTunnel <-> CIRA APF (chnl)
// Anything that needs to be forwarded by SerialTunnel will be encapsulated by chnl write // Anything that needs to be forwarded by SerialTunnel will be encapsulated by chnl write
ser.forwardwrite = function (msg) { ser.forwardwrite = function (msg) {
// Convert a buffer into a string, "msg = msg.toString('ascii');" does not work
// TLS ---> CIRA // TLS ---> CIRA
var msg2 = ""; chnl.write(msg.toString('binary'));
for (var i = 0; i < msg.length; i++) { msg2 += String.fromCharCode(msg[i]); }
chnl.write(msg2);
}; };
// When APF tunnel return something, update SerialTunnel buffer // 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) { tlsock.on('data', function (data) {
// AMT/TLS ---> WS // AMT/TLS ---> WS
try { try {
var data2 = ""; data = data.toString('binary');
for (var i = 0; i < data.length; i++) { data2 += String.fromCharCode(data[i]); } if (ws.interceptor) { data = ws.interceptor.processAmtData(data); } // Run data thru interceptor
if (ws.interceptor) { data2 = ws.interceptor.processAmtData(data2); } // Run data thru interceptor ws.send(data);
ws.send(data2);
} catch (e) { } } 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. // 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.on('message', function (msg) {
// WS ---> AMT/TLS // WS ---> AMT/TLS
// Convert a buffer into a string, "msg = msg.toString('ascii');" does not work msg = msg.toString('binary');
var msg2 = ""; if (ws.interceptor) { msg = ws.interceptor.processBrowserData(msg); } // Run data thru interceptor
for (var i = 0; i < msg.length; i++) { msg2 += String.fromCharCode(msg[i]); } if (ws.forwardclient.xtls == 1) { ws.forwardclient.write(Buffer.from(msg, 'binary')); } else { ws.forwardclient.write(msg); }
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); }
}); });
// If error, do nothing // 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. // When data is received from the web socket, forward the data into the associated TCP connection.
ws.on('message', function (msg) { ws.on('message', function (msg) {
Debug(1, 'TCP relay data to ' + node.host + ', ' + msg.length + ' bytes'); // DEBUG 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 msg = msg.toString('binary');
var msg2 = ""; if (ws.interceptor) { msg = ws.interceptor.processBrowserData(msg); } // Run data thru interceptor
for (var i = 0; i < msg.length; i++) { msg2 += String.fromCharCode(msg[i]); } ws.forwardclient.write(new Buffer(msg, 'binary')); // Forward data to the associated TCP connection.
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.
}); });
// If error, do nothing // 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 // 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)) { if ((command.meshtype == 1) || (command.meshtype == 2)) {
// Create a type 1 agent-less Intel AMT mesh. // 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 meshid = 'mesh/' + domain.id + '/' + buf.toString('hex').toUpperCase();
var links = {} var links = {}
links[user._id] = { name: user.name, rights: 0xFFFFFFFF }; 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; if (mesh.links[user._id] == null || ((mesh.links[user._id].rights & 4) == 0)) return;
// Create a new nodeid // Create a new nodeid
obj.crypto.randomBytes(32, function (err, buf) { obj.crypto.randomBytes(48, function (err, buf) {
// create the new node // create the new node
var nodeid = 'node/' + domain.id + '/' + buf.toString('hex').toUpperCase(); 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) } }; 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']; var authstr = req.headers['authorization'];
if (authstr.substring(0, 7) == "Digest ") { if (authstr.substring(0, 7) == "Digest ") {
var auth = obj.common.parseNameValueList(obj.common.quoteSplit(authstr.substring(7))); 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 // Read the data, we need to get the arg field
var eventData = ''; var eventData = '';
@ -1806,7 +1798,7 @@ module.exports.CreateWebServer = function (parent, db, args, secret, certificate
if (nodes.length == 1) { if (nodes.length == 1) {
// Yes, the node exists, compute Intel AMT digest password // Yes, the node exists, compute Intel AMT digest password
var node = nodes[0]; 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 // 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)) { 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); } } catch (e) { console.log(e); }
// Send authentication response // Send authentication response
obj.crypto.randomBytes(32, function (err, buf) { obj.crypto.randomBytes(48, function (err, buf) {
var nonce = buf.toString('hex'), opaque = obj.crypto.createHmac('SHA256', obj.httpAuthRandom).update(nonce).digest('hex'); 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.set({ 'WWW-Authenticate': 'Digest realm="' + obj.httpAuthRealm + '", qop="auth,auth-int", nonce="' + nonce + '", opaque="' + opaque + '"' });
res.sendStatus(401); res.sendStatus(401);
}); });
@ -1921,7 +1913,7 @@ module.exports.CreateWebServer = function (parent, db, args, secret, certificate
} else { } else {
// Send a list of available mesh agents // 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>'; 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) { for (var agentid in obj.parent.meshAgentBinaries) {
var agentinfo = obj.parent.meshAgentBinaries[agentid]; var agentinfo = obj.parent.meshAgentBinaries[agentid];
response += '<tr><td>' + agentinfo.id + '</td><td>' + agentinfo.desc + '</td>'; 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. agent.send(obj.common.ShortToStr(11) + obj.common.ShortToStr(0)); // Command 11, ask for mesh core hash.
} else { } else {
agent.agentCoreCheck = 1000; // Tell the agent object we are not using a custom core. agent.agentCoreCheck = 1000; // Tell the agent object we are not using a custom core.
// Perform a SHA256 hash on the core module // Perform a SHA384 hash on the core module
var buf = new Buffer(core, 'ascii'); var hash = obj.crypto.createHash('sha384').update(new Buffer(core, 'binary')).digest().toString('binary');
var hash = obj.crypto.createHash('sha256').update(buf).digest(), hash2 = "";
for (var i = 0; i < hash.length; i++) { hash2 += String.fromCharCode(hash[i]); }
// Send the code module to the agent // 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);
} }
} }
} }