mirror of
https://github.com/Ylianst/MeshCentral.git
synced 2024-12-23 21:55:52 -05:00
Improved crypto and removed dependency on WebSocket library, using ws instead.
This commit is contained in:
parent
3632741d9e
commit
1952d75860
Binary file not shown.
Binary file not shown.
@ -29,6 +29,7 @@ function createMeshCore(agent) {
|
||||
var selfInfoUpdateTimer = null;
|
||||
var http = require('http');
|
||||
var fs = require('fs');
|
||||
var rtc = require('ILibWebRTC');
|
||||
var wifiScannerLib = null;
|
||||
var wifiScanner = null;
|
||||
|
||||
@ -417,8 +418,26 @@ function createMeshCore(agent) {
|
||||
if (len > 0) { this.write(buf.slice(0, len)); } else { fs.closeSync(this.httprequest.downloadFile); this.httprequest.downloadFile = undefined; this.end(); }
|
||||
return;
|
||||
}
|
||||
|
||||
// Setup remote desktop & terminal without using native pipes
|
||||
if ((this.httprequest.desktop) && (obj.useNativePipes == false)) { this.httprequest.desktop.kvm.write(data); return; }
|
||||
if ((this.httprequest.desktop) && (obj.useNativePipes == false)) {
|
||||
if (data.length > 21 && data.toString().startsWith('**********%%%%%%###**')) {
|
||||
var controlMsg = JSON.parse(data.toString().substring(21));
|
||||
if (controlMsg.type == 'offer') {
|
||||
this.webrtc = rtc.createConnection();
|
||||
this.webrtc.on('connected', function () { sendConsoleText('OnWebRTC_Connected'); });
|
||||
this.webrtc.on('dataChannel', function () { sendConsoleText('OnWebRTC_DataChannel'); });
|
||||
var counterOffer = this.webrtc.setOffer(controlMsg.sdp);
|
||||
this.write('**********%%%%%%###**' + JSON.stringify({ type: 'answer', sdp: counterOffer }));
|
||||
sendConsoleText('counterOfferSent');
|
||||
} else {
|
||||
sendConsoleText(JSON.stringify(controlMsg));
|
||||
}
|
||||
} else {
|
||||
this.httprequest.desktop.kvm.write(data);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if ((this.httprequest.terminal) && (obj.useNativePipes == false)) { this.httprequest.terminal.write(data); return; }
|
||||
|
||||
if (this.httprequest.state == 0) {
|
||||
|
@ -12,24 +12,16 @@ module.exports.CertificateOperations = function () {
|
||||
obj.dirExists = function (filePath) { try { return obj.fs.statSync(filePath).isDirectory(); } catch (err) { return false; } }
|
||||
obj.getFilesizeInBytes = function(filename) { try { return obj.fs.statSync(filename)["size"]; } catch (err) { return -1; } }
|
||||
obj.fileExists = function(filePath) { try { return obj.fs.statSync(filePath).isFile(); } catch (err) { return false; } }
|
||||
|
||||
// Return the SHA256 hash of the certificate public key
|
||||
obj.getPublicKeyHash = function(cert) {
|
||||
var publickey = obj.pki.certificateFromPem(cert).publicKey;
|
||||
return obj.pki.getPublicKeyFingerprint(publickey, { encoding: 'hex', md: obj.forge.md.sha256.create() });
|
||||
}
|
||||
|
||||
// Return a random nonce (TODO: weak crypto)
|
||||
obj.xxRandomNonceX = "abcdef0123456789";
|
||||
obj.xxRandomNonce = function (length) {
|
||||
var r = "";
|
||||
for (var i = 0; i < length; i++) { r += obj.xxRandomNonceX.charAt(Math.floor(Math.random() * obj.xxRandomNonceX.length)); }
|
||||
return r;
|
||||
// Return the SHA386 hash of the certificate public key
|
||||
obj.getPublicKeyHash = function (cert) {
|
||||
var publickey = obj.pki.certificateFromPem(cert).publicKey;
|
||||
return obj.pki.getPublicKeyFingerprint(publickey, { encoding: 'hex', md: obj.forge.md.sha384.create() });
|
||||
}
|
||||
|
||||
// Create a self-signed certificate
|
||||
obj.GenerateRootCertificate = function (addThumbPrintToName, commonName, country, organization) {
|
||||
var keys = obj.pki.rsa.generateKeyPair(2048);
|
||||
var keys = obj.pki.rsa.generateKeyPair(3072);
|
||||
var cert = obj.pki.createCertificate();
|
||||
cert.publicKey = keys.publicKey;
|
||||
cert.serialNumber = '' + Math.floor((Math.random() * 100000) + 1); ;
|
||||
@ -55,14 +47,14 @@ module.exports.CertificateOperations = function () {
|
||||
}, {
|
||||
name: 'subjectKeyIdentifier'
|
||||
}]);
|
||||
cert.sign(keys.privateKey, obj.forge.md.sha256.create());
|
||||
cert.sign(keys.privateKey, obj.forge.md.sha384.create());
|
||||
|
||||
return { cert: cert, key: keys.privateKey };
|
||||
}
|
||||
|
||||
// Issue a certificate from a root
|
||||
obj.IssueWebServerCertificate = function (rootcert, addThumbPrintToName, commonName, country, organization, extKeyUsage) {
|
||||
var keys = obj.pki.rsa.generateKeyPair(2048);
|
||||
obj.IssueWebServerCertificate = function (rootcert, addThumbPrintToName, commonName, country, organization, extKeyUsage, strong) {
|
||||
var keys = obj.pki.rsa.generateKeyPair((strong == true) ? 3072 : 2048);
|
||||
var cert = obj.pki.createCertificate();
|
||||
cert.publicKey = keys.publicKey;
|
||||
cert.serialNumber = '' + Math.floor((Math.random() * 100000) + 1); ;
|
||||
@ -128,8 +120,7 @@ module.exports.CertificateOperations = function () {
|
||||
}]
|
||||
if (subjectAltName != null) extensions.push(subjectAltName);
|
||||
cert.setExtensions(extensions);
|
||||
|
||||
cert.sign(rootcert.key, obj.forge.md.sha256.create());
|
||||
cert.sign(rootcert.key, obj.forge.md.sha384.create());
|
||||
|
||||
return { cert: cert, key: keys.privateKey };
|
||||
}
|
||||
@ -234,7 +225,7 @@ module.exports.CertificateOperations = function () {
|
||||
if (xorganizationField != null) { xorganization = xorganizationField.value; }
|
||||
if ((r.CommonName == commonName) && (xcountry == country) && (xorganization == organization) && (r.AmtMpsName == commonName)) { if (func != undefined) { func(r); } return r; } else { forceWebCertGen = 1; } // If the certificate matches what we want, keep it.
|
||||
}
|
||||
console.log('Generating certificates...');
|
||||
console.log('Generating certificates, may take a few minutes...');
|
||||
|
||||
var rootCertAndKey, rootCertificate, rootPrivateKey, rootName;
|
||||
if (r.root == undefined) {
|
||||
@ -255,7 +246,7 @@ module.exports.CertificateOperations = function () {
|
||||
// If the web certificate does not exist, create one
|
||||
var webCertAndKey, webCertificate, webPrivateKey;
|
||||
if ((r.web == null) || (forceWebCertGen == 1)) {
|
||||
webCertAndKey = obj.IssueWebServerCertificate(rootCertAndKey, false, commonName, country, organization);
|
||||
webCertAndKey = obj.IssueWebServerCertificate(rootCertAndKey, false, commonName, country, organization, null, true);
|
||||
webCertificate = obj.pki.certificateToPem(webCertAndKey.cert);
|
||||
webPrivateKey = obj.pki.privateKeyToPem(webCertAndKey.key);
|
||||
obj.fs.writeFileSync(directory + '/webserver-cert-public.crt', webCertificate);
|
||||
@ -270,7 +261,7 @@ module.exports.CertificateOperations = function () {
|
||||
// If the Intel AMT MPS certificate does not exist, create one
|
||||
var mpsCertAndKey, mpsCertificate, mpsPrivateKey;
|
||||
if ((r.mps == null) || (forceWebCertGen == 1)) {
|
||||
mpsCertAndKey = obj.IssueWebServerCertificate(rootCertAndKey, false, commonName, country, organization);
|
||||
mpsCertAndKey = obj.IssueWebServerCertificate(rootCertAndKey, false, commonName, country, organization, null, false);
|
||||
mpsCertificate = obj.pki.certificateToPem(mpsCertAndKey.cert);
|
||||
mpsPrivateKey = obj.pki.privateKeyToPem(mpsCertAndKey.key);
|
||||
obj.fs.writeFileSync(directory + '/mpsserver-cert-public.crt', mpsCertificate);
|
||||
@ -285,7 +276,7 @@ module.exports.CertificateOperations = function () {
|
||||
// If the Intel AMT console certificate does not exist, create one
|
||||
var consoleCertAndKey, consoleCertificate, consolePrivateKey, amtConsoleName = 'MeshCentral';
|
||||
if (r.console == null) {
|
||||
consoleCertAndKey = obj.IssueWebServerCertificate(rootCertAndKey, false, amtConsoleName, country, organization, { name: 'extKeyUsage', clientAuth: true, '2.16.840.1.113741.1.2.1': true, '2.16.840.1.113741.1.2.2': true, '2.16.840.1.113741.1.2.3': true }); // Intel AMT Remote, Agent and Activation usages
|
||||
consoleCertAndKey = obj.IssueWebServerCertificate(rootCertAndKey, false, amtConsoleName, country, organization, { name: 'extKeyUsage', clientAuth: true, '2.16.840.1.113741.1.2.1': true, '2.16.840.1.113741.1.2.2': true, '2.16.840.1.113741.1.2.3': true }, false); // Intel AMT Remote, Agent and Activation usages
|
||||
consoleCertificate = obj.pki.certificateToPem(consoleCertAndKey.cert);
|
||||
consolePrivateKey = obj.pki.privateKeyToPem(consoleCertAndKey.key);
|
||||
obj.fs.writeFileSync(directory + '/amtconsole-cert-public.crt', consoleCertificate);
|
||||
@ -301,7 +292,7 @@ module.exports.CertificateOperations = function () {
|
||||
// If the mesh agent server certificate does not exist, create one
|
||||
var agentCertAndKey, agentCertificate, agentPrivateKey;
|
||||
if (r.agent == null) {
|
||||
agentCertAndKey = obj.IssueWebServerCertificate(rootCertAndKey, true, 'MeshCentralAgentServer');
|
||||
agentCertAndKey = obj.IssueWebServerCertificate(rootCertAndKey, true, 'MeshCentralAgentServer', null, true);
|
||||
agentCertificate = obj.pki.certificateToPem(agentCertAndKey.cert);
|
||||
agentPrivateKey = obj.pki.privateKeyToPem(agentCertAndKey.key);
|
||||
obj.fs.writeFileSync(directory + '/agentserver-cert-public.crt', agentCertificate);
|
||||
|
91
db.js
91
db.js
@ -43,7 +43,7 @@ module.exports.CreateDB = function (args, datapath) {
|
||||
if ((docs.length == 1) && (docs[0].value != null)) {
|
||||
obj.identifier = docs[0].value;
|
||||
} else {
|
||||
obj.identifier = new Buffer(require('crypto').randomBytes(32), 'binary').toString('hex');
|
||||
obj.identifier = new Buffer(require('crypto').randomBytes(48), 'binary').toString('hex');
|
||||
obj.Set({ _id: 'DatabaseIdentifier', value: obj.identifier });
|
||||
}
|
||||
});
|
||||
@ -53,94 +53,9 @@ module.exports.CreateDB = function (args, datapath) {
|
||||
var ver = 0;
|
||||
if (docs && docs.length == 1) { ver = docs[0].value; }
|
||||
|
||||
// Upgrade schema 0 to schema 1
|
||||
if (ver == 0) {
|
||||
// Add the default domain to all users
|
||||
obj.GetAllType('user', function (err, docs) {
|
||||
for (var id in docs) {
|
||||
var oldid, changed = false;
|
||||
if (docs[id].subscriptions) { delete docs[id].subscriptions; changed = true; }
|
||||
if (docs[id].domain == undefined) {
|
||||
docs[id].domain = '';
|
||||
oldid = docs[id]._id;
|
||||
docs[id]._id = 'user//' + docs[id]._id.substring(5);
|
||||
changed = true;
|
||||
}
|
||||
if (docs[id].links) {
|
||||
for (var linkid in docs[id].links) {
|
||||
var linkid2 = 'mesh//' + linkid.substring(5);
|
||||
docs[id].links[linkid2] = docs[id].links[linkid];
|
||||
delete docs[id].links[linkid];
|
||||
}
|
||||
}
|
||||
if (changed == true) {
|
||||
if (oldid) obj.Remove(oldid);
|
||||
obj.Set(docs[id]);
|
||||
}
|
||||
}
|
||||
|
||||
// Add the default domain to all nodes
|
||||
obj.GetAllType('node', function (err, docs) {
|
||||
for (var id in docs) {
|
||||
var oldid, changed = false;
|
||||
if (docs[id].domain == undefined) {
|
||||
docs[id].domain = '';
|
||||
oldid = docs[id]._id;
|
||||
docs[id]._id = 'node//' + docs[id]._id.substring(5);
|
||||
docs[id].meshid = 'mesh//' + docs[id].meshid.substring(5);
|
||||
changed = true;
|
||||
}
|
||||
if (changed == true) {
|
||||
if (oldid) obj.Remove(oldid);
|
||||
obj.Set(docs[id]);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Add the default domain to all meshes
|
||||
obj.GetAllType('mesh', function (err, docs) {
|
||||
for (var id in docs) {
|
||||
var oldid, changed = false;
|
||||
if (docs[id].domain == undefined) {
|
||||
docs[id].domain = '';
|
||||
oldid = docs[id]._id;
|
||||
docs[id]._id = 'mesh//' + docs[id]._id.substring(5);
|
||||
if (docs[id].links) {
|
||||
for (var linkid in docs[id].links) {
|
||||
var linkid2 = 'user//' + linkid.substring(5);
|
||||
docs[id].links[linkid2] = docs[id].links[linkid];
|
||||
delete docs[id].links[linkid];
|
||||
}
|
||||
}
|
||||
changed = true;
|
||||
}
|
||||
if (changed == true) {
|
||||
if (oldid) obj.Remove(oldid);
|
||||
obj.Set(docs[id]);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Add the default domain to all events
|
||||
obj.GetAllType('event', function (err, docs) {
|
||||
var changed = false;
|
||||
for (var id in docs) {
|
||||
var oldid;
|
||||
changed = true;
|
||||
if (docs[id].domain == undefined) {
|
||||
docs[id].domain = '';
|
||||
obj.Set(docs[id]);
|
||||
}
|
||||
}
|
||||
|
||||
obj.Set({ _id: 'SchemaVersion', value: 1 });
|
||||
ver = 1;
|
||||
if (changed == true) { console.log('Upgraded database to version 1.'); }
|
||||
func(ver);
|
||||
});
|
||||
});
|
||||
// TODO: Any schema upgrades here...
|
||||
|
||||
} else { func(ver); }
|
||||
func(ver);
|
||||
});
|
||||
}
|
||||
|
||||
|
59
meshagent.js
59
meshagent.js
@ -27,6 +27,7 @@ module.exports.CreateMeshAgent = function (parent, db, ws, req, args, domain) {
|
||||
obj.agentUpdate = null;
|
||||
var agentUpdateBlockSize = 65520;
|
||||
obj.remoteaddr = obj.ws._socket.remoteAddress;
|
||||
obj.useSHA386 = false;
|
||||
if (obj.remoteaddr.startsWith('::ffff:')) { obj.remoteaddr = obj.remoteaddr.substring(7); }
|
||||
ws._socket.setKeepAlive(true, 0); // Set TCP keep alive
|
||||
|
||||
@ -49,12 +50,7 @@ module.exports.CreateMeshAgent = function (parent, db, ws, req, args, domain) {
|
||||
// When data is received from the mesh agent web socket
|
||||
ws.on('message', function (msg) {
|
||||
if (msg.length < 2) return;
|
||||
if (typeof msg == 'object') {
|
||||
// Convert the buffer into a string
|
||||
var msg2 = "";
|
||||
for (var i = 0; i < msg.length; i++) { msg2 += String.fromCharCode(msg[i]); }
|
||||
msg = msg2;
|
||||
}
|
||||
if (typeof msg == 'object') { msg = msg.toString('binary'); } // TODO: Could change this entire method to use Buffer instead of binary string
|
||||
|
||||
if (obj.authenticated == 2) { // We are authenticated
|
||||
if (msg.charCodeAt(0) == 123) { processAgentData(msg); }
|
||||
@ -67,7 +63,7 @@ module.exports.CreateMeshAgent = function (parent, db, ws, req, args, domain) {
|
||||
// We need to check if the core is current.
|
||||
// TODO: Check if we have a mesh specific core. If so, use that.
|
||||
var agentMeshCoreHash = null;
|
||||
if (msg.length == 36) { agentMeshCoreHash = msg.substring(4, 36); }
|
||||
if (msg.length == 52) { agentMeshCoreHash = msg.substring(4, 52); }
|
||||
if (agentMeshCoreHash != obj.parent.parent.defaultMeshCoreHash) {
|
||||
if (obj.agentCoreCheck < 5) { // This check is in place to avoid a looping core update.
|
||||
if (obj.parent.parent.defaultMeshCoreHash == null) {
|
||||
@ -84,16 +80,16 @@ module.exports.CreateMeshAgent = function (parent, db, ws, req, args, domain) {
|
||||
}
|
||||
}
|
||||
else if (cmdid == 12) { // MeshCommand_AgentHash
|
||||
if ((msg.length == 36) && (obj.agentInfo != null) && (obj.agentInfo.update == true)) {
|
||||
if ((msg.length == 52) && (obj.agentExeInfo != null) && (obj.agentExeInfo.update == true)) {
|
||||
var agenthash = obj.common.rstr2hex(msg.substring(4)).toLowerCase();
|
||||
if (agenthash != obj.agentInfo.hash) {
|
||||
if (agenthash != obj.agentExeInfo.hash) {
|
||||
// Mesh agent update required
|
||||
console.log('Agent update required, NodeID=0x' + obj.nodeid.substring(0, 16) + ', ' + obj.agentInfo.desc);
|
||||
obj.fs.open(obj.agentInfo.path, 'r', function (err, fd) {
|
||||
console.log('Agent update required, NodeID=0x' + obj.nodeid.substring(0, 16) + ', ' + obj.agentExeInfo.desc);
|
||||
obj.fs.open(obj.agentExeInfo.path, 'r', function (err, fd) {
|
||||
if (err) { return console.error(err); }
|
||||
obj.agentUpdate = { oldHash: agenthash, ptr: 0, buf: new Buffer(agentUpdateBlockSize + 4), fd: fd };
|
||||
|
||||
// We got the agent file open on the server side, tell the agent we are sending an update starting with the SHA256 hash of the result
|
||||
// We got the agent file open on the server side, tell the agent we are sending an update starting with the SHA384 hash of the result
|
||||
//console.log("Agent update file open.");
|
||||
obj.send(obj.common.ShortToStr(13) + obj.common.ShortToStr(0)); // Command 13, start mesh agent download
|
||||
|
||||
@ -136,7 +132,7 @@ module.exports.CreateMeshAgent = function (parent, db, ws, req, args, domain) {
|
||||
|
||||
if (len < agentUpdateBlockSize) {
|
||||
//console.log("Agent update sent");
|
||||
obj.send(obj.common.ShortToStr(13) + obj.common.ShortToStr(0) + obj.common.hex2rstr(obj.agentInfo.hash)); // Command 13, end mesh agent download, send agent SHA256 hash
|
||||
obj.send(obj.common.ShortToStr(13) + obj.common.ShortToStr(0) + obj.common.hex2rstr(obj.agentInfo.hash)); // Command 13, end mesh agent download, send agent SHA384 hash
|
||||
obj.fs.close(obj.agentUpdate.fd);
|
||||
obj.agentUpdate = null;
|
||||
}
|
||||
@ -152,18 +148,18 @@ module.exports.CreateMeshAgent = function (parent, db, ws, req, args, domain) {
|
||||
var cmd = obj.common.ReadShort(msg, 0);
|
||||
if (cmd == 1) {
|
||||
// Agent authentication request
|
||||
if ((msg.length != 66) || ((obj.receivedCommands & 1) != 0)) return;
|
||||
if ((msg.length != 98) || ((obj.receivedCommands & 1) != 0)) return;
|
||||
obj.receivedCommands += 1; // Agent can't send the same command twice on the same connection ever. Block DOS attack path.
|
||||
|
||||
// Check that the server hash matches out own web certificate hash
|
||||
if (obj.parent.webCertificatHash != msg.substring(2, 34)) { obj.close(); return; }
|
||||
// Check that the server hash matches out own web certificate hash (SHA386)
|
||||
if (obj.parent.webCertificatHash != msg.substring(2, 50)) { obj.close(); return; }
|
||||
|
||||
// Use our server private key to sign the ServerHash + AgentNonce + ServerNonce
|
||||
var privateKey = obj.forge.pki.privateKeyFromPem(obj.parent.certificates.agent.key);
|
||||
var md = obj.forge.md.sha256.create();
|
||||
var md = obj.forge.md.sha384.create();
|
||||
md.update(msg.substring(2), 'binary');
|
||||
md.update(obj.nonce, 'binary');
|
||||
obj.agentnonce = msg.substring(34);
|
||||
obj.agentnonce = msg.substring(50);
|
||||
|
||||
// Send back our certificate + signature
|
||||
obj.send(obj.common.ShortToStr(2) + obj.common.ShortToStr(parent.agentCertificatAsn1.length) + parent.agentCertificatAsn1 + privateKey.sign(md)); // Command 2, certificate + signature
|
||||
@ -183,15 +179,15 @@ module.exports.CreateMeshAgent = function (parent, db, ws, req, args, domain) {
|
||||
obj.unauth = {};
|
||||
obj.unauth.nodeCert = null;
|
||||
try { obj.unauth.nodeCert = obj.forge.pki.certificateFromAsn1(obj.forge.asn1.fromDer(msg.substring(4, 4 + certlen))); } catch (e) { return; }
|
||||
obj.unauth.nodeid = obj.forge.pki.getPublicKeyFingerprint(obj.unauth.nodeCert.publicKey, { encoding: 'hex', md: obj.forge.md.sha256.create() });
|
||||
obj.unauth.nodeid = obj.forge.pki.getPublicKeyFingerprint(obj.unauth.nodeCert.publicKey, { encoding: 'hex', md: obj.forge.md.sha384.create() });
|
||||
|
||||
// Check the agent signature if we can
|
||||
if (obj.agentnonce == null) { obj.unauthsign = msg.substring(4 + certlen); } else { if (processAgentSignature(msg.substring(4 + certlen)) == false) { disonnect(); return; } }
|
||||
if (obj.agentnonce == null) { obj.unauthsign = msg.substring(4 + certlen); } else { if (processAgentSignature(msg.substring(4 + certlen)) == false) { console.log('Bad Agent Signature'); obj.close(); return; } }
|
||||
completeAgentConnection();
|
||||
}
|
||||
else if (cmd == 3) {
|
||||
// Agent meshid
|
||||
if ((msg.length < 56) || ((obj.receivedCommands & 4) != 0)) return;
|
||||
if ((msg.length < 72) || ((obj.receivedCommands & 4) != 0)) return;
|
||||
obj.receivedCommands += 4; // Agent can't send the same command twice on the same connection ever. Block DOS attack path.
|
||||
|
||||
// Set the meshid
|
||||
@ -200,10 +196,10 @@ module.exports.CreateMeshAgent = function (parent, db, ws, req, args, domain) {
|
||||
obj.agentInfo.agentId = obj.common.ReadInt(msg, 6);
|
||||
obj.agentInfo.agentVersion = obj.common.ReadInt(msg, 10);
|
||||
obj.agentInfo.platformType = obj.common.ReadInt(msg, 14);
|
||||
obj.meshid = obj.common.rstr2hex(msg.substring(18, 50)).toUpperCase();
|
||||
obj.agentInfo.capabilities = obj.common.ReadInt(msg, 50);
|
||||
var computerNameLen = obj.common.ReadShort(msg, 54);
|
||||
obj.agentInfo.computerName = msg.substring(56, 56 + computerNameLen);
|
||||
obj.meshid = obj.common.rstr2hex(msg.substring(18, 66)).toUpperCase();
|
||||
obj.agentInfo.capabilities = obj.common.ReadInt(msg, 66);
|
||||
var computerNameLen = obj.common.ReadShort(msg, 70);
|
||||
obj.agentInfo.computerName = msg.substring(72, 72 + computerNameLen);
|
||||
obj.dbMeshKey = 'mesh/' + obj.domain.id + '/' + obj.meshid;
|
||||
completeAgentConnection();
|
||||
}
|
||||
@ -218,8 +214,8 @@ module.exports.CreateMeshAgent = function (parent, db, ws, req, args, domain) {
|
||||
// obj.ws._socket._parent.on('close', function (req) { obj.parent.parent.debug(1, 'Agent TCP disconnect ' + obj.nodeid + ' (' + obj.remoteaddr + ')'); });
|
||||
|
||||
// Start authenticate the mesh agent by sending a auth nonce & server TLS cert hash.
|
||||
// Send 256 bits SHA256 hash of TLS cert public key + 256 bits nonce
|
||||
obj.nonce = obj.forge.random.getBytesSync(32);
|
||||
// Send 384 bits SHA384 hash of TLS cert public key + 384 bits nonce
|
||||
obj.nonce = obj.forge.random.getBytesSync(48);
|
||||
obj.send(obj.common.ShortToStr(1) + parent.webCertificatHash + obj.nonce); // Command 1, hash + nonce
|
||||
|
||||
// Once we get all the information about an agent, run this to hook everything up to the server
|
||||
@ -237,7 +233,6 @@ module.exports.CreateMeshAgent = function (parent, db, ws, req, args, domain) {
|
||||
|
||||
// Mark when we connected to this agent
|
||||
obj.connectTime = Date.now();
|
||||
|
||||
if (nodes.length == 0) {
|
||||
// This node does not exist, create it.
|
||||
device = { type: 'node', mtype: mesh.mtype, _id: obj.dbNodeKey, icon: obj.agentInfo.platformType, meshid: obj.dbMeshKey, name: obj.agentInfo.computerName, domain: domain.id, agent: { ver: obj.agentInfo.agentVersion, id: obj.agentInfo.agentId, caps: obj.agentInfo.capabilities }, host: null };
|
||||
@ -292,8 +287,8 @@ module.exports.CreateMeshAgent = function (parent, db, ws, req, args, domain) {
|
||||
if ((obj.agentInfo.capabilities & 16) != 0) { obj.send(obj.common.ShortToStr(11) + obj.common.ShortToStr(0)); } // Command 11, ask for mesh core hash.
|
||||
|
||||
// Check if we need to make an native update check
|
||||
obj.agentInfo = obj.parent.parent.meshAgentBinaries[obj.agentInfo.agentId];
|
||||
if ((obj.agentInfo != null) && (obj.agentInfo.update == true)) { obj.send(obj.common.ShortToStr(12) + obj.common.ShortToStr(0)); } // Ask the agent for it's executable binary hash
|
||||
obj.agentExeInfo = obj.parent.parent.meshAgentBinaries[obj.agentInfo.agentId];
|
||||
if ((obj.agentExeInfo != null) && (obj.agentExeInfo.update == true)) { obj.send(obj.common.ShortToStr(12) + obj.common.ShortToStr(0)); } // Ask the agent for it's executable binary hash
|
||||
|
||||
// Check if we already have IP location information for this node
|
||||
obj.db.Get('iploc_' + obj.remoteaddr, function (err, iplocs) {
|
||||
@ -337,11 +332,11 @@ module.exports.CreateMeshAgent = function (parent, db, ws, req, args, domain) {
|
||||
|
||||
// Verify the agent signature
|
||||
function processAgentSignature(msg) {
|
||||
var md = obj.forge.md.sha256.create(); // TODO: Switch this to SHA256 on node instead of forge.
|
||||
var md = obj.forge.md.sha384.create(); // TODO: Switch this to SHA384 on node instead of forge.
|
||||
md.update(obj.parent.webCertificatHash, 'binary');
|
||||
md.update(obj.nonce, 'binary');
|
||||
md.update(obj.agentnonce, 'binary');
|
||||
if (obj.unauth.nodeCert.publicKey.verify(md.digest().bytes(), msg) == false) return false;
|
||||
if (obj.unauth.nodeCert.publicKey.verify(md.digest().bytes(), msg) == false) { return false; }
|
||||
|
||||
// Connection is a success, clean up
|
||||
obj.nodeid = obj.unauth.nodeid.toUpperCase();
|
||||
|
@ -29,8 +29,8 @@ function CreateMeshCentralServer() {
|
||||
obj.certificateOperations = require('./certoperations.js').CertificateOperations();
|
||||
obj.defaultMeshCore = null;
|
||||
obj.defaultMeshCoreHash = null;
|
||||
obj.meshAgentBinaries = {}; // Mesh Agent Binaries, Architecture type --> { hash:(sha256 hash), size:(binary size), path:(binary path) }
|
||||
obj.meshAgentInstallScripts = {}; // Mesh Install Scripts, Script ID -- { hash:(sha256 hash), size:(binary size), path:(binary path) }
|
||||
obj.meshAgentBinaries = {}; // Mesh Agent Binaries, Architecture type --> { hash:(sha384 hash), size:(binary size), path:(binary path) }
|
||||
obj.meshAgentInstallScripts = {}; // Mesh Install Scripts, Script ID -- { hash:(sha384 hash), size:(binary size), path:(binary path) }
|
||||
obj.multiServer = null;
|
||||
obj.currentVer = null;
|
||||
obj.maintenanceTimer = null;
|
||||
@ -38,11 +38,11 @@ function CreateMeshCentralServer() {
|
||||
|
||||
// Setup the default configuration and files paths
|
||||
if ((__dirname.endsWith('/node_modules/meshcentral')) || (__dirname.endsWith('\\node_modules\\meshcentral')) || (__dirname.endsWith('/node_modules/meshcentral/')) || (__dirname.endsWith('\\node_modules\\meshcentral\\'))) {
|
||||
obj.datapath = obj.path.join(__dirname, '../../.meshcentral-data');
|
||||
obj.filespath = obj.path.join(__dirname, '../../.meshcentral-files');
|
||||
obj.datapath = obj.path.join(__dirname, '../../meshcentral-data');
|
||||
obj.filespath = obj.path.join(__dirname, '../../meshcentral-files');
|
||||
} else {
|
||||
obj.datapath = obj.path.join(__dirname, '../.meshcentral-data');
|
||||
obj.filespath = obj.path.join(__dirname, '../.meshcentral-files');
|
||||
obj.datapath = obj.path.join(__dirname, '../meshcentral-data');
|
||||
obj.filespath = obj.path.join(__dirname, '../meshcentral-files');
|
||||
}
|
||||
|
||||
// Create data and files folders if needed
|
||||
@ -64,12 +64,12 @@ function CreateMeshCentralServer() {
|
||||
try { require('./pass').hash('test', function () { }); } catch (e) { console.log('Old version of node, must upgrade.'); return; } // TODO: Not sure if this test works or not.
|
||||
|
||||
// Check for invalid arguments
|
||||
var validArguments = ['_', 'notls', 'user', 'port', 'mpsport', 'redirport', 'cert', 'deletedomain', 'deletedefaultdomain', 'showusers', 'shownodes', 'showmeshes', 'showevents', 'showpower', 'showiplocations', 'help', 'exactports', 'install', 'uninstall', 'start', 'stop', 'restart', 'debug', 'filespath', 'datapath', 'noagentupdate', 'launch', 'noserverbackup', 'mongodb', 'mongodbcol', 'wanonly', 'lanonly', 'nousers', 'mpsdebug', 'mpspass', 'ciralocalfqdn', 'dbexport', 'dbimport', 'selfupdate', 'tlsoffload', 'userallowedip'];
|
||||
var validArguments = ['_', 'notls', 'user', 'port', 'mpsport', 'redirport', 'cert', 'deletedomain', 'deletedefaultdomain', 'showall', 'showusers', 'shownodes', 'showmeshes', 'showevents', 'showpower', 'showiplocations', 'help', 'exactports', 'install', 'uninstall', 'start', 'stop', 'restart', 'debug', 'filespath', 'datapath', 'noagentupdate', 'launch', 'noserverbackup', 'mongodb', 'mongodbcol', 'wanonly', 'lanonly', 'nousers', 'mpsdebug', 'mpspass', 'ciralocalfqdn', 'dbexport', 'dbimport', 'selfupdate', 'tlsoffload', 'userallowedip'];
|
||||
for (var arg in obj.args) { obj.args[arg.toLocaleLowerCase()] = obj.args[arg]; if (validArguments.indexOf(arg.toLocaleLowerCase()) == -1) { console.log('Invalid argument "' + arg + '", use --help.'); return; } }
|
||||
if (obj.args.mongodb == true) { console.log('Must specify: --mongodb [connectionstring] \r\nSee https://docs.mongodb.com/manual/reference/connection-string/ for MongoDB connection string.'); return; }
|
||||
|
||||
if ((obj.args.help == true) || (obj.args['?'] == true)) {
|
||||
console.log('MeshCentral2 Beta 1, a web-based remote computer management web portal.\r\n');
|
||||
console.log('MeshCentral2 Beta 2, a web-based remote computer management web portal.\r\n');
|
||||
if (obj.platform == 'win32') {
|
||||
console.log('Run as a Windows Service');
|
||||
console.log(' --install/uninstall Install Meshcentral as a background service.');
|
||||
@ -198,7 +198,7 @@ function CreateMeshCentralServer() {
|
||||
// Validate the domains, this is used for multi-hosting
|
||||
if (obj.config.domains == null) { obj.config.domains = {}; }
|
||||
if (obj.config.domains[''] == null) { obj.config.domains[''] = { }; }
|
||||
var xdomains = {}; for (var i in obj.config.domains) { if (!obj.config.domains[i].title) { obj.config.domains[i].title = 'MeshCentral'; } if (!obj.config.domains[i].title2) { obj.config.domains[i].title2 = '2.0 Beta 1'; } xdomains[i.toLowerCase()] = obj.config.domains[i]; } obj.config.domains = xdomains;
|
||||
var xdomains = {}; for (var i in obj.config.domains) { if (!obj.config.domains[i].title) { obj.config.domains[i].title = 'MeshCentral'; } if (!obj.config.domains[i].title2) { obj.config.domains[i].title2 = '2.0 Beta 2'; } xdomains[i.toLowerCase()] = obj.config.domains[i]; } obj.config.domains = xdomains;
|
||||
var bannedDomains = ['public', 'private', 'images', 'scripts', 'styles', 'views']; // List of banned domains
|
||||
for (var i in obj.config.domains) { for (var j in bannedDomains) { if (i == bannedDomains[j]) { console.log("ERROR: Domain '" + i + "' is not allowed domain name in ./data/config.json."); return; } } }
|
||||
for (var i in obj.config.domains) {
|
||||
@ -223,6 +223,7 @@ function CreateMeshCentralServer() {
|
||||
// See if any database operations needs to be completed
|
||||
if (obj.args.deletedomain) { obj.db.DeleteDomain(obj.args.deletedomain, function () { console.log('Deleted domain ' + obj.args.deletedomain + '.'); process.exit(); }); return; }
|
||||
if (obj.args.deletedefaultdomain) { obj.db.DeleteDomain('', function () { console.log('Deleted default domain.'); process.exit(); }); return; }
|
||||
if (obj.args.showall) { obj.db.GetAll(function (err, docs) { console.log(docs); process.exit(); }); return; }
|
||||
if (obj.args.showusers) { obj.db.GetAllType('user', function (err, docs) { console.log(docs); process.exit(); }); return; }
|
||||
if (obj.args.shownodes) { obj.db.GetAllType('node', function (err, docs) { console.log(docs); process.exit(); }); return; }
|
||||
if (obj.args.showmeshes) { obj.db.GetAllType('mesh', function (err, docs) { console.log(docs); process.exit(); }); return; }
|
||||
@ -269,7 +270,7 @@ function CreateMeshCentralServer() {
|
||||
while (obj.dbconfig.amtWsEventSecret == null) { process.nextTick(); }
|
||||
var username = buf.toString('hex');
|
||||
var nodeid = obj.args.getwspass;
|
||||
var pass = require('crypto').createHash('sha256').update(username.toLowerCase() + ":" + nodeid.toUpperCase() + ":" + obj.dbconfig.amtWsEventSecret).digest("base64").substring(0, 12).split("/").join("x").split("\\").join("x");
|
||||
var pass = require('crypto').createHash('sha384').update(username.toLowerCase() + ":" + nodeid.toUpperCase() + ":" + obj.dbconfig.amtWsEventSecret).digest("base64").substring(0, 12).split("/").join("x").split("\\").join("x");
|
||||
console.log('--- Intel(r) AMT WSMAN eventing credentials ---');
|
||||
console.log('Username: ' + username);
|
||||
console.log('Password: ' + pass);
|
||||
@ -299,7 +300,7 @@ function CreateMeshCentralServer() {
|
||||
obj.updateMeshAgentInstallScripts();
|
||||
|
||||
// Setup and start the web server
|
||||
require('crypto').randomBytes(32, function (err, buf) {
|
||||
require('crypto').randomBytes(48, function (err, buf) {
|
||||
// Setup Mesh Multi-Server if needed
|
||||
obj.multiServer = require('./multiserver.js').CreateMultiServer(obj, obj.args);
|
||||
if (obj.multiServer != null) {
|
||||
@ -657,7 +658,7 @@ function CreateMeshCentralServer() {
|
||||
// Set the new default meshcore.js
|
||||
meshCore = obj.common.IntToStr(0) + moduleAdditions + meshCore; // Add the 4 bytes encoding type & flags (Set to 0 for raw)
|
||||
obj.defaultMeshCore = meshCore;
|
||||
obj.defaultMeshCoreHash = obj.crypto.createHash('sha256').update(meshCore).digest("binary");
|
||||
obj.defaultMeshCoreHash = obj.crypto.createHash('sha384').update(meshCore).digest("binary");
|
||||
if (func != null) { func(true); }
|
||||
}
|
||||
|
||||
@ -690,7 +691,7 @@ function CreateMeshCentralServer() {
|
||||
});
|
||||
stream.info = meshAgentsInstallScriptList[scriptid];
|
||||
stream.agentpath = scriptpath;
|
||||
stream.hash = obj.crypto.createHash('sha256', stream);
|
||||
stream.hash = obj.crypto.createHash('sha384', stream);
|
||||
} catch (e) { }
|
||||
}
|
||||
}
|
||||
@ -748,7 +749,7 @@ function CreateMeshCentralServer() {
|
||||
});
|
||||
stream.info = meshAgentsArchitectureNumbers[archid];
|
||||
stream.agentpath = agentpath;
|
||||
stream.hash = obj.crypto.createHash('sha256', stream);
|
||||
stream.hash = obj.crypto.createHash('sha384', stream);
|
||||
} catch (e) { }
|
||||
}
|
||||
}
|
||||
@ -817,7 +818,7 @@ function InstallModule(modulename, func, tag1, tag2) {
|
||||
process.on('SIGINT', function () { if (meshserver != null) { meshserver.Stop(); meshserver = null; } console.log('Server Ctrl-C exit...'); process.exit(); });
|
||||
|
||||
// Build the list of required modules
|
||||
var modules = ['nedb', 'https', 'unzip', 'xmldom', 'express', 'mongojs', 'archiver', 'websocket', 'minimist', 'multiparty', 'node-forge', 'express-ws', 'compression', 'body-parser', 'connect-redis', 'express-session', 'express-handlebars'];
|
||||
var modules = ['nedb', 'https', 'unzip', 'xmldom', 'express', 'mongojs', 'archiver', 'minimist', 'multiparty', 'node-forge', 'express-ws', 'compression', 'body-parser', 'connect-redis', 'express-session', 'express-handlebars'];
|
||||
if (require('os').platform() == 'win32') { modules.push("node-windows"); }
|
||||
|
||||
// Run as a command line, if we are not using service arguments, don't need to install the service package.
|
||||
|
@ -6,9 +6,9 @@
|
||||
|
||||
// Construct a MeshRelay object, called upon connection
|
||||
module.exports.CreateMeshRelayKey = function (parent, func) {
|
||||
parent.crypto.randomBytes(16, function (err, buf) {
|
||||
parent.crypto.randomBytes(48, function (err, buf) {
|
||||
var key = buf.toString('hex').toUpperCase() + ':' + Date.now();
|
||||
key += ':' + parent.crypto.createHmac('SHA256', parent.relayRandom).update(key).digest('hex');
|
||||
key += ':' + parent.crypto.createHmac('SHA384', parent.relayRandom).update(key).digest('hex');
|
||||
func(key);
|
||||
});
|
||||
}
|
||||
@ -41,7 +41,7 @@ module.exports.CreateMeshRelay = function (parent, ws, req) {
|
||||
// Check the identifier, if running without TLS, skip this.
|
||||
var ids = obj.id.split(':');
|
||||
if (ids.length != 3) { obj.ws.close(); obj.id = null; return null; } // Invalid ID, drop this.
|
||||
if (parent.crypto.createHmac('SHA256', parent.relayRandom).update(ids[0] + ':' + ids[1]).digest('hex') != ids[2]) { obj.ws.close(); obj.id = null; return null; } // Invalid HMAC, drop this.
|
||||
if (parent.crypto.createHmac('SHA384', parent.relayRandom).update(ids[0] + ':' + ids[1]).digest('hex') != ids[2]) { obj.ws.close(); obj.id = null; return null; } // Invalid HMAC, drop this.
|
||||
if ((Date.now() - parseInt(ids[1])) > 120000) { obj.ws.close(); obj.id = null; return null; } // Expired time, drop this.
|
||||
obj.id = ids[0];
|
||||
}
|
||||
@ -107,6 +107,8 @@ module.exports.CreateMeshRelay = function (parent, ws, req) {
|
||||
|
||||
// When data is received from the mesh relay web socket
|
||||
ws.on('message', function (data) {
|
||||
//console.log(typeof data);
|
||||
//if (typeof data == 'string') console.log(data);
|
||||
if (this.peer != null) { try { this.pause(); this.peer.send(data, ws.flushSink); } catch (e) { } }
|
||||
});
|
||||
|
||||
|
@ -17,21 +17,22 @@ module.exports.CreateMeshScanner = function (parent) {
|
||||
var periodicScanTime = (60000 * 20); // Interval between scans, 20 minutes.
|
||||
var membershipIPv4 = '239.255.255.235';
|
||||
var membershipIPv6 = 'FF02:0:0:0:0:0:0:FE';
|
||||
obj.agentCertificatHashHex = parent.certificateOperations.forge.pki.getPublicKeyFingerprint(parent.certificateOperations.forge.pki.certificateFromPem(parent.certificates.agent.cert).publicKey, { md: parent.certificateOperations.forge.md.sha256.create(), encoding: 'hex' });
|
||||
obj.agentCertificatHashHex = parent.certificateOperations.forge.pki.getPublicKeyFingerprint(parent.certificateOperations.forge.pki.certificateFromPem(parent.certificates.agent.cert).publicKey, { md: parent.certificateOperations.forge.md.sha384.create(), encoding: 'hex' });
|
||||
obj.error = 0;
|
||||
|
||||
// Get a list of IPv4 and IPv6 interface addresses
|
||||
function getInterfaceList() {
|
||||
var ipv4 = [], ipv6 = [];
|
||||
if (parent.platform == 'win32') { ipv4.push('*'); ipv6.push('*'); } // Bind to IN_ADDR_ANY only on Windows
|
||||
var interfaces = require('os').networkInterfaces();
|
||||
for (var i in interfaces) {
|
||||
var interface = interfaces[i];
|
||||
for (var j in interface) {
|
||||
var interface2 = interface[j];
|
||||
if ((interface2.mac != '00:00:00:00:00:00') && (interface2.internal == false)) {
|
||||
if (interface2.family == 'IPv4') { ipv4.push(interface2.address); }
|
||||
if (interface2.family == 'IPv6') { ipv6.push(interface2.address + '%' + i); }
|
||||
var ipv4 = ['*'], ipv6 = ['*']; // Bind to IN_ADDR_ANY always
|
||||
if (parent.platform == 'win32') { // On Windows, also bind to each interface seperatly
|
||||
var interfaces = require('os').networkInterfaces();
|
||||
for (var i in interfaces) {
|
||||
var interface = interfaces[i];
|
||||
for (var j in interface) {
|
||||
var interface2 = interface[j];
|
||||
if ((interface2.mac != '00:00:00:00:00:00') && (interface2.internal == false)) {
|
||||
if (interface2.family == 'IPv4') { ipv4.push(interface2.address); }
|
||||
if (interface2.family == 'IPv6') { ipv6.push(interface2.address + '%' + i); }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -112,7 +112,7 @@ module.exports.CreateMpsServer = function (parent, db, args, certificates) {
|
||||
|
||||
socket.tag.domainid = domainid;
|
||||
socket.tag.meshid = 'mesh/' + domainid + '/' + meshid;
|
||||
socket.tag.nodeid = 'node/' + domainid + '/' + require('crypto').createHash('sha256').update(common.hex2rstr(socket.tag.clientCert.modulus, 'binary')).digest('hex').toUpperCase();
|
||||
socket.tag.nodeid = 'node/' + domainid + '/' + require('crypto').createHash('sha384').update(common.hex2rstr(socket.tag.clientCert.modulus, 'binary')).digest('hex').toUpperCase();
|
||||
socket.tag.name = socket.tag.clientCert.subject.CN;
|
||||
socket.tag.connectTime = Date.now();
|
||||
socket.tag.host = '';
|
||||
@ -170,7 +170,7 @@ module.exports.CreateMpsServer = function (parent, db, args, certificates) {
|
||||
console.log(e);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// Process one AFP command
|
||||
function ProcessCommand(socket) {
|
||||
var cmd = socket.tag.accumulator.charCodeAt(0);
|
||||
@ -228,7 +228,7 @@ module.exports.CreateMpsServer = function (parent, db, args, certificates) {
|
||||
// Intel AMT GUID (socket.tag.SystemId) will be used at NodeID
|
||||
var systemid = socket.tag.SystemId.split('-').join('').toUpperCase();
|
||||
socket.tag.name = '';
|
||||
socket.tag.nodeid = 'node/' + mesh.domain + '/' + systemid + systemid;
|
||||
socket.tag.nodeid = 'node/' + mesh.domain + '/' + systemid + systemid + systemid; // Turn 16bit systemid guid into 48bit nodeid
|
||||
socket.tag.meshid = mesh._id;
|
||||
|
||||
obj.db.Get(socket.tag.nodeid, function (err, nodes) {
|
||||
@ -318,7 +318,7 @@ module.exports.CreateMpsServer = function (parent, db, args, certificates) {
|
||||
if (len < 26 + requestLen + addrLen + oaddrLen + datalen) return 0;
|
||||
Debug(2, 'MPS:GLOBAL_REQUEST', request, addr + ':' + port, oaddr + ':' + oport, datalen);
|
||||
// TODO
|
||||
return ptr + 26 + requestLen + addrLen + oaddrLen + datalen;
|
||||
return 26 + requestLen + addrLen + oaddrLen + datalen;
|
||||
}
|
||||
|
||||
return 6 + requestLen;
|
||||
|
339
multiserver.js
339
multiserver.js
@ -7,6 +7,7 @@
|
||||
// Construct a Mesh Multi-Server object. This is used for MeshCentral-to-MeshCentral communication.
|
||||
module.exports.CreateMultiServer = function (parent, args) {
|
||||
var obj = {};
|
||||
const WebSocket = require('ws');
|
||||
obj.parent = parent;
|
||||
obj.crypto = require('crypto');
|
||||
obj.peerConfig = parent.config.peers;
|
||||
@ -22,7 +23,6 @@ module.exports.CreateMultiServer = function (parent, args) {
|
||||
obj.serverid = serverid;
|
||||
obj.url = url;
|
||||
obj.ws = null;
|
||||
obj.conn = null;
|
||||
obj.certificates = parent.parent.certificates;
|
||||
obj.common = require('./common.js');
|
||||
obj.forge = require('node-forge');
|
||||
@ -51,123 +51,107 @@ module.exports.CreateMultiServer = function (parent, args) {
|
||||
obj.connectionState = 1;
|
||||
|
||||
// Get the web socket setup
|
||||
const WebSocket = require('websocket');
|
||||
var WebSocketClient = require('websocket').client;
|
||||
obj.ws = new WebSocketClient();
|
||||
obj.ws = new WebSocket(obj.url + 'meshserver.ashx', { rejectUnauthorized: false, cert: obj.certificates.agent.cert, key: obj.certificates.agent.key });
|
||||
obj.parent.parent.debug(1, 'OutPeer ' + obj.serverid + ': Connecting to: ' + url + 'meshserver.ashx');
|
||||
|
||||
// Register the connection failed event
|
||||
obj.ws.on('connectFailed', function (error) { obj.parent.parent.debug(1, 'OutPeer ' + obj.serverid + ': Failed connection'); disconnect(); });
|
||||
obj.ws.on('error', function (error) { obj.parent.parent.debug(1, 'OutPeer ' + obj.serverid + ': Error: ' + error); disconnect(); });
|
||||
obj.ws.on('close', function () { obj.parent.parent.debug(1, 'OutPeer ' + obj.serverid + ': Disconnected'); disconnect(); });
|
||||
|
||||
// Register the connection event
|
||||
obj.ws.on('connect', function (connection) {
|
||||
obj.ws.on('open', function () {
|
||||
obj.parent.parent.debug(1, 'OutPeer ' + obj.serverid + ': Connected');
|
||||
obj.connectionState |= 2;
|
||||
obj.conn = connection;
|
||||
obj.nonce = obj.forge.random.getBytesSync(32);
|
||||
|
||||
// If the connection has an error or closes
|
||||
obj.conn.on('error', function (error) { obj.parent.parent.debug(1, 'OutPeer ' + obj.serverid + ': Error: ' + error); disconnect(); });
|
||||
obj.conn.on('close', function () { obj.parent.parent.debug(1, 'OutPeer ' + obj.serverid + ': Disconnected'); disconnect(); });
|
||||
obj.nonce = obj.forge.random.getBytesSync(48);
|
||||
|
||||
// Get the peer server's certificate and compute the server public key hash
|
||||
if (obj.ws.socket == null) return;
|
||||
var rawcertbuf = obj.ws.socket.getPeerCertificate().raw, rawcert = '';
|
||||
for (var i = 0; i < rawcertbuf.length; i++) { rawcert += String.fromCharCode(rawcertbuf[i]); }
|
||||
var serverCert = obj.forge.pki.certificateFromAsn1(obj.forge.asn1.fromDer(rawcert));
|
||||
obj.serverCertHash = obj.forge.pki.getPublicKeyFingerprint(serverCert.publicKey, { encoding: 'binary', md: obj.forge.md.sha256.create() });
|
||||
if (obj.ws._socket == null) return;
|
||||
var serverCert = obj.forge.pki.certificateFromAsn1(obj.forge.asn1.fromDer(obj.ws._socket.getPeerCertificate().raw.toString('binary')));
|
||||
obj.serverCertHash = obj.forge.pki.getPublicKeyFingerprint(serverCert.publicKey, { encoding: 'binary', md: obj.forge.md.sha384.create() });
|
||||
|
||||
// If a message is received
|
||||
obj.conn.on('message', function (msg) {
|
||||
if (msg.type == 'binary') { var msg2 = ""; for (var i = 0; i < msg.binaryData.length; i++) { msg2 += String.fromCharCode(msg.binaryData[i]); } msg = msg2; }
|
||||
else if (msg.type == 'utf8') { msg = msg.utf8Data; }
|
||||
if (msg.length < 2) return;
|
||||
|
||||
if (msg.charCodeAt(0) == 123) {
|
||||
if (obj.connectionState == 15) { processServerData(msg); }
|
||||
} else {
|
||||
var cmd = obj.common.ReadShort(msg, 0);
|
||||
switch (cmd) {
|
||||
case 1: {
|
||||
// Server authentication request
|
||||
if (msg.length != 66) { obj.parent.parent.debug(1, 'OutPeer: BAD MESSAGE(A1)'); return; }
|
||||
|
||||
// Check that the server hash matches the TLS server certificate public key hash
|
||||
if (obj.serverCertHash != msg.substring(2, 34)) { obj.parent.parent.debug(1, 'OutPeer: Server hash mismatch.'); disconnect(); return; }
|
||||
obj.servernonce = msg.substring(34);
|
||||
|
||||
// Use our agent root private key to sign the ServerHash + ServerNonce + AgentNonce
|
||||
var privateKey = obj.forge.pki.privateKeyFromPem(obj.certificates.agent.key);
|
||||
var md = obj.forge.md.sha256.create();
|
||||
md.update(msg.substring(2), 'binary');
|
||||
md.update(obj.nonce, 'binary');
|
||||
|
||||
// Send back our certificate + signature
|
||||
agentRootCertificatAsn1 = obj.forge.asn1.toDer(obj.forge.pki.certificateToAsn1(obj.forge.pki.certificateFromPem(obj.certificates.agent.cert))).getBytes();
|
||||
obj.conn.send(obj.common.ShortToStr(2) + obj.common.ShortToStr(agentRootCertificatAsn1.length) + agentRootCertificatAsn1 + privateKey.sign(md)); // Command 3, signature
|
||||
break;
|
||||
}
|
||||
case 2: {
|
||||
// Server certificate
|
||||
var certlen = obj.common.ReadShort(msg, 2), serverCert = null;
|
||||
try { serverCert = obj.forge.pki.certificateFromAsn1(obj.forge.asn1.fromDer(msg.substring(4, 4 + certlen))); } catch (e) { }
|
||||
if (serverCert == null) { obj.parent.parent.debug(1, 'OutPeer: Invalid server certificate.'); disconnect(); return; }
|
||||
var serverid = obj.forge.pki.getPublicKeyFingerprint(serverCert.publicKey, { encoding: 'hex', md: obj.forge.md.sha256.create() });
|
||||
if (serverid !== obj.agentCertificatHashHex) { obj.parent.parent.debug(1, 'OutPeer: Server hash mismatch.'); disconnect(); return; }
|
||||
|
||||
// Server signature, verify it
|
||||
var md = obj.forge.md.sha256.create();
|
||||
md.update(obj.serverCertHash, 'binary');
|
||||
md.update(obj.nonce, 'binary');
|
||||
md.update(obj.servernonce, 'binary');
|
||||
if (serverCert.publicKey.verify(md.digest().bytes(), msg.substring(4 + certlen)) == false) { obj.parent.parent.debug(1, 'OutPeer: Server sign check failed.'); disconnect(); return; }
|
||||
|
||||
// Connection is a success, clean up
|
||||
delete obj.nonce;
|
||||
delete obj.servernonce;
|
||||
obj.serverCertHash = obj.common.rstr2hex(obj.serverCertHash).toLowerCase(); // Change this value to hex
|
||||
obj.connectionState |= 4;
|
||||
obj.retryBackoff = 0; // Set backoff connection timer back to fast.
|
||||
obj.parent.parent.debug(1, 'OutPeer ' + obj.serverid + ': Verified peer connection to ' + obj.url);
|
||||
|
||||
// Send information about our server to the peer
|
||||
if (obj.connectionState == 15) { obj.conn.send(JSON.stringify({ action: 'info', serverid: obj.parent.serverid, dbid: obj.parent.parent.db.identifier, key: obj.parent.serverKey, serverCertHash: obj.parent.parent.webserver.webCertificatHashHex })); }
|
||||
//if ((obj.connectionState == 15) && (obj.connectHandler != null)) { obj.connectHandler(1); }
|
||||
break;
|
||||
}
|
||||
case 4: {
|
||||
// Server confirmed authentication, we are allowed to send commands to the server
|
||||
obj.connectionState |= 8;
|
||||
if (obj.connectionState == 15) { obj.conn.send(JSON.stringify({ action: 'info', serverid: obj.parent.serverid, dbid: obj.parent.parent.db.identifier, key: obj.parent.serverKey, serverCertHash: obj.parent.parent.webserver.webCertificatHashHex })); }
|
||||
//if ((obj.connectionState == 15) && (obj.connectHandler != null)) { obj.connectHandler(1); }
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
obj.parent.parent.debug(1, 'OutPeer ' + obj.serverid + ': Un-handled command: ' + cmd);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Not sure why, but we need to delay the first send
|
||||
setTimeout(function () {
|
||||
if ((obj.ws == null) || (obj.conn == null)) return;
|
||||
// Start authenticate the mesh agent by sending a auth nonce & server TLS cert hash.
|
||||
// Send 256 bits SHA256 hash of TLS cert public key + 256 bits nonce
|
||||
obj.conn.send(obj.common.ShortToStr(1) + obj.serverCertHash + obj.nonce); // Command 1, hash + nonce
|
||||
}, 10);
|
||||
// Start authenticate the peer server by sending a auth nonce & server TLS cert hash.
|
||||
// Send 384 bits SHA384 hash of TLS cert public key + 384 bits nonce
|
||||
obj.ws.send(obj.common.ShortToStr(1) + obj.serverCertHash + obj.nonce); // Command 1, hash + nonce
|
||||
});
|
||||
|
||||
obj.ws.connect(obj.url + 'meshserver.ashx', null, null, null, { rejectUnauthorized: false, cert: obj.certificates.agent.cert, key: obj.certificates.agent.key });
|
||||
// If a message is received
|
||||
obj.ws.on('message', function (msg) {
|
||||
if (typeof msg != 'string') { msg = msg.toString('binary'); }
|
||||
if (msg.length < 2) return;
|
||||
|
||||
if (msg.charCodeAt(0) == 123) {
|
||||
if (obj.connectionState == 15) { processServerData(msg); }
|
||||
} else {
|
||||
var cmd = obj.common.ReadShort(msg, 0);
|
||||
switch (cmd) {
|
||||
case 1: {
|
||||
// Server authentication request
|
||||
if (msg.length != 98) { obj.parent.parent.debug(1, 'OutPeer: BAD MESSAGE(A1)'); return; }
|
||||
|
||||
// Check that the server hash matches the TLS server certificate public key hash
|
||||
if (obj.serverCertHash != msg.substring(2, 50)) { obj.parent.parent.debug(1, 'OutPeer: Server hash mismatch.'); disconnect(); return; }
|
||||
obj.servernonce = msg.substring(50);
|
||||
|
||||
// Use our agent certificate root private key to sign the ServerHash + ServerNonce + PeerNonce
|
||||
var privateKey = obj.forge.pki.privateKeyFromPem(obj.certificates.agent.key);
|
||||
var md = obj.forge.md.sha384.create();
|
||||
md.update(msg.substring(2), 'binary');
|
||||
md.update(obj.nonce, 'binary');
|
||||
|
||||
// Send back our certificate + signature
|
||||
agentRootCertificatAsn1 = obj.forge.asn1.toDer(obj.forge.pki.certificateToAsn1(obj.forge.pki.certificateFromPem(obj.certificates.agent.cert))).getBytes();
|
||||
obj.ws.send(obj.common.ShortToStr(2) + obj.common.ShortToStr(agentRootCertificatAsn1.length) + agentRootCertificatAsn1 + privateKey.sign(md)); // Command 3, signature
|
||||
break;
|
||||
}
|
||||
case 2: {
|
||||
// Server certificate
|
||||
var certlen = obj.common.ReadShort(msg, 2), serverCert = null;
|
||||
try { serverCert = obj.forge.pki.certificateFromAsn1(obj.forge.asn1.fromDer(msg.substring(4, 4 + certlen))); } catch (e) { }
|
||||
if (serverCert == null) { obj.parent.parent.debug(1, 'OutPeer: Invalid server certificate.'); disconnect(); return; }
|
||||
var serverid = obj.forge.pki.getPublicKeyFingerprint(serverCert.publicKey, { encoding: 'hex', md: obj.forge.md.sha384.create() });
|
||||
if (serverid !== obj.agentCertificatHashHex) { obj.parent.parent.debug(1, 'OutPeer: Server hash mismatch.'); disconnect(); return; }
|
||||
|
||||
// Server signature, verify it
|
||||
var md = obj.forge.md.sha384.create();
|
||||
md.update(obj.serverCertHash, 'binary');
|
||||
md.update(obj.nonce, 'binary');
|
||||
md.update(obj.servernonce, 'binary');
|
||||
if (serverCert.publicKey.verify(md.digest().bytes(), msg.substring(4 + certlen)) == false) { obj.parent.parent.debug(1, 'OutPeer: Server sign check failed.'); disconnect(); return; }
|
||||
|
||||
// Connection is a success, clean up
|
||||
delete obj.nonce;
|
||||
delete obj.servernonce;
|
||||
obj.serverCertHash = obj.common.rstr2hex(obj.serverCertHash).toLowerCase(); // Change this value to hex
|
||||
obj.connectionState |= 4;
|
||||
obj.retryBackoff = 0; // Set backoff connection timer back to fast.
|
||||
obj.parent.parent.debug(1, 'OutPeer ' + obj.serverid + ': Verified peer connection to ' + obj.url);
|
||||
|
||||
// Send information about our server to the peer
|
||||
if (obj.connectionState == 15) { obj.ws.send(JSON.stringify({ action: 'info', serverid: obj.parent.serverid, dbid: obj.parent.parent.db.identifier, key: obj.parent.serverKey.toString('hex'), serverCertHash: obj.parent.parent.webserver.webCertificatHashHex })); }
|
||||
//if ((obj.connectionState == 15) && (obj.connectHandler != null)) { obj.connectHandler(1); }
|
||||
break;
|
||||
}
|
||||
case 4: {
|
||||
// Server confirmed authentication, we are allowed to send commands to the server
|
||||
obj.connectionState |= 8;
|
||||
if (obj.connectionState == 15) { obj.ws.send(JSON.stringify({ action: 'info', serverid: obj.parent.serverid, dbid: obj.parent.parent.db.identifier, key: obj.parent.serverKey.toString('hex'), serverCertHash: obj.parent.parent.webserver.webCertificatHashHex })); }
|
||||
//if ((obj.connectionState == 15) && (obj.connectHandler != null)) { obj.connectHandler(1); }
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
obj.parent.parent.debug(1, 'OutPeer ' + obj.serverid + ': Un-handled command: ' + cmd);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Disconnect from the server, if we need to, try again with a delay.
|
||||
function disconnect() {
|
||||
if (obj.authenticated == 3) { obj.parent.ClearPeerServer(obj, obj.peerServerId); obj.authenticated = 0; }
|
||||
if ((obj.connectionState == 15) && (obj.connectHandler != null)) { obj.connectHandler(0); }
|
||||
if (obj.conn != null) { obj.conn.close(); obj.conn = null; }
|
||||
if (obj.ws != null) { obj.ws = null; }
|
||||
if (obj.ws != null) { obj.ws.close(); obj.ws = null; }
|
||||
if (obj.retryTimer != null) { clearTimeout(obj.retryTimer); obj.retryTimer = null; }
|
||||
// Re-try connection
|
||||
if (obj.connectionState >= 1) { obj.connectionState = 1; if (obj.retryTimer == null) { obj.retryTimer = setTimeout(connect, getConnectRetryTime()); } }
|
||||
@ -182,9 +166,9 @@ module.exports.CreateMultiServer = function (parent, args) {
|
||||
// Send a JSON message to the peer server
|
||||
obj.send = function (msg) {
|
||||
try {
|
||||
if (obj.ws == null || obj.conn == null || obj.connectionState != 15) { return; }
|
||||
if (typeof msg == 'object') { obj.conn.send(JSON.stringify(msg)); return; }
|
||||
if (typeof msg == 'string') { obj.conn.send(msg); return; }
|
||||
if (obj.ws == null || obj.connectionState != 15) { return; }
|
||||
if (typeof msg == 'object') { obj.ws.send(JSON.stringify(msg)); return; }
|
||||
if (typeof msg == 'string') { obj.ws.send(msg); return; }
|
||||
} catch (e) { }
|
||||
}
|
||||
|
||||
@ -201,7 +185,7 @@ module.exports.CreateMultiServer = function (parent, args) {
|
||||
if (command.dbid != obj.parent.parent.db.identifier) { console.log('ERROR: Database ID mismatch. Trying to peer to a server with the wrong database. (' + obj.url + ', ' + command.serverid + ').'); return; }
|
||||
if (obj.serverCertHash != command.serverCertHash) { console.log('ERROR: Outer certificate hash mismatch. (' + obj.url + ', ' + command.serverid + ').'); return; }
|
||||
obj.peerServerId = command.serverid;
|
||||
obj.peerServerKey = command.key;
|
||||
obj.peerServerKey = new Buffer(command.key, 'hex');
|
||||
obj.authenticated = 3;
|
||||
obj.parent.SetupPeerServer(obj, obj.peerServerId);
|
||||
}
|
||||
@ -235,6 +219,7 @@ module.exports.CreateMultiServer = function (parent, args) {
|
||||
obj.peerServerId = null;
|
||||
obj.serverCertHash = null;
|
||||
if (obj.remoteaddr.startsWith('::ffff:')) { obj.remoteaddr = obj.remoteaddr.substring(7); }
|
||||
obj.parent.parent.debug(1, 'InPeer: Connected (' + obj.remoteaddr + ')');
|
||||
|
||||
// Send a message to the peer server
|
||||
obj.send = function (data) {
|
||||
@ -252,10 +237,9 @@ module.exports.CreateMultiServer = function (parent, args) {
|
||||
if (obj.authenticated == 3) { obj.parent.ClearPeerServer(obj, obj.peerServerId); obj.authenticated = 0; }
|
||||
}
|
||||
|
||||
// When data is received from the mesh agent web socket
|
||||
// When data is received from the peer server web socket
|
||||
ws.on('message', function (msg) {
|
||||
if (msg.type == 'binary') { var msg2 = ""; for (var i = 0; i < msg.binaryData.length; i++) { msg2 += String.fromCharCode(msg.binaryData[i]); } msg = msg2; }
|
||||
else if (msg.type == 'utf8') { msg = msg.utf8Data; }
|
||||
if (typeof msg != 'string') { msg = msg.toString('binary'); }
|
||||
if (msg.length < 2) return;
|
||||
|
||||
if (obj.authenticated >= 2) { // We are authenticated
|
||||
@ -267,48 +251,47 @@ module.exports.CreateMultiServer = function (parent, args) {
|
||||
else if (obj.authenticated < 2) { // We are not authenticated
|
||||
var cmd = obj.common.ReadShort(msg, 0);
|
||||
if (cmd == 1) {
|
||||
// Agent authentication request
|
||||
if ((msg.length != 66) || ((obj.receivedCommands & 1) != 0)) return;
|
||||
obj.receivedCommands += 1; // Agent can't send the same command twice on the same connection ever. Block DOS attack path.
|
||||
// Peer server authentication request
|
||||
if ((msg.length != 98) || ((obj.receivedCommands & 1) != 0)) return;
|
||||
obj.receivedCommands += 1; // Peer server can't send the same command twice on the same connection ever. Block DOS attack path.
|
||||
|
||||
// Check that the server hash matches out own web certificate hash
|
||||
if (obj.webCertificatHash != msg.substring(2, 34)) { obj.close(); return; }
|
||||
if (obj.webCertificatHash != msg.substring(2, 50)) { obj.close(); return; }
|
||||
|
||||
// Use our server private key to sign the ServerHash + AgentNonce + ServerNonce
|
||||
// Use our server private key to sign the ServerHash + PeerNonce + ServerNonce
|
||||
var privateKey = obj.forge.pki.privateKeyFromPem(obj.parent.parent.certificates.agent.key);
|
||||
var md = obj.forge.md.sha256.create();
|
||||
var md = obj.forge.md.sha384.create();
|
||||
md.update(msg.substring(2), 'binary');
|
||||
md.update(obj.nonce, 'binary');
|
||||
obj.agentnonce = msg.substring(34);
|
||||
obj.peernonce = msg.substring(50);
|
||||
|
||||
// Send back our certificate + signature
|
||||
obj.send(obj.common.ShortToStr(2) + obj.common.ShortToStr(obj.agentCertificatAsn1.length) + obj.agentCertificatAsn1 + privateKey.sign(md)); // Command 2, certificate + signature
|
||||
|
||||
// Check the agent signature if we can
|
||||
// Check the peer server signature if we can
|
||||
if (obj.unauthsign != null) {
|
||||
if (processAgentSignature(obj.unauthsign) == false) { disconnect(); return; } else { completePeerServerConnection(); }
|
||||
if (processPeerSignature(obj.unauthsign) == false) { disconnect(); return; } else { completePeerServerConnection(); }
|
||||
}
|
||||
}
|
||||
else if (cmd == 2) {
|
||||
// Agent certificate
|
||||
// Peer server certificate
|
||||
if ((msg.length < 4) || ((obj.receivedCommands & 2) != 0)) return;
|
||||
obj.receivedCommands += 2; // Agent can't send the same command twice on the same connection ever. Block DOS attack path.
|
||||
obj.receivedCommands += 2; // Peer server can't send the same command twice on the same connection ever. Block DOS attack path.
|
||||
|
||||
// Decode the certificate
|
||||
var certlen = obj.common.ReadShort(msg, 2);
|
||||
obj.unauth = {};
|
||||
obj.unauth.nodeCert = null;
|
||||
try { obj.unauth.nodeCert = obj.forge.pki.certificateFromAsn1(obj.forge.asn1.fromDer(msg.substring(4, 4 + certlen))); } catch (e) { return; }
|
||||
obj.unauth.nodeid = obj.forge.pki.getPublicKeyFingerprint(obj.unauth.nodeCert.publicKey, { encoding: 'hex', md: obj.forge.md.sha256.create() });
|
||||
obj.unauth.nodeid = obj.forge.pki.getPublicKeyFingerprint(obj.unauth.nodeCert.publicKey, { encoding: 'hex', md: obj.forge.md.sha384.create() });
|
||||
|
||||
// Check the agent signature if we can
|
||||
if (obj.agentnonce == null) { obj.unauthsign = msg.substring(4 + certlen); } else { if (processAgentSignature(msg.substring(4 + certlen)) == false) { disconnect(); return; } }
|
||||
// Check the peer server signature if we can
|
||||
if (obj.peernonce == null) { obj.unauthsign = msg.substring(4 + certlen); } else { if (processPeerSignature(msg.substring(4 + certlen)) == false) { disconnect(); return; } }
|
||||
completePeerServerConnection();
|
||||
}
|
||||
else if (cmd == 3) {
|
||||
// Agent meshid
|
||||
if ((msg.length < 56) || ((obj.receivedCommands & 4) != 0)) return;
|
||||
obj.receivedCommands += 4; // Agent can't send the same command twice on the same connection ever. Block DOS attack path.
|
||||
obj.receivedCommands += 4; // Peer server can't send the same command twice on the same connection ever. Block DOS attack path.
|
||||
completePeerServerConnection();
|
||||
}
|
||||
}
|
||||
@ -317,36 +300,36 @@ module.exports.CreateMultiServer = function (parent, args) {
|
||||
// If error, do nothing
|
||||
ws.on('error', function (err) { obj.parent.parent.debug(1, 'InPeer: Connection Error: ' + err); });
|
||||
|
||||
// If the mesh agent web socket is closed, clean up.
|
||||
// If the peer server web socket is closed, clean up.
|
||||
ws.on('close', function (req) { obj.parent.parent.debug(1, 'InPeer disconnect ' + obj.nodeid + ' (' + obj.remoteaddr + ')'); obj.close(0); });
|
||||
// obj.ws._socket._parent.on('close', function (req) { obj.parent.parent.debug(1, 'Agent TCP disconnect ' + obj.nodeid + ' (' + obj.remoteaddr + ')'); });
|
||||
// obj.ws._socket._parent.on('close', function (req) { obj.parent.parent.debug(1, 'Peer server TCP disconnect ' + obj.nodeid + ' (' + obj.remoteaddr + ')'); });
|
||||
|
||||
// Start authenticate the mesh agent by sending a auth nonce & server TLS cert hash.
|
||||
// Send 256 bits SHA256 hash of TLS cert public key + 256 bits nonce
|
||||
obj.nonce = obj.forge.random.getBytesSync(32);
|
||||
// Start authenticate the peer server by sending a auth nonce & server TLS cert hash.
|
||||
// Send 384 bits SHA382 hash of TLS cert public key + 384 bits nonce
|
||||
obj.nonce = obj.forge.random.getBytesSync(48);
|
||||
obj.send(obj.common.ShortToStr(1) + obj.webCertificatHash + obj.nonce); // Command 1, hash + nonce
|
||||
|
||||
// Once we get all the information about an agent, run this to hook everything up to the server
|
||||
// Once we get all the information about an peer server, run this to hook everything up to the server
|
||||
function completePeerServerConnection() {
|
||||
if (obj.authenticated != 1) return;
|
||||
obj.send(obj.common.ShortToStr(4));
|
||||
obj.send(JSON.stringify({ action: 'info', serverid: obj.parent.serverid, dbid: obj.parent.parent.db.identifier, key: obj.parent.serverKey, serverCertHash: obj.parent.parent.webserver.webCertificatHashHex }));
|
||||
obj.send(JSON.stringify({ action: 'info', serverid: obj.parent.serverid, dbid: obj.parent.parent.db.identifier, key: obj.parent.serverKey.toString('hex'), serverCertHash: obj.parent.parent.webserver.webCertificatHashHex }));
|
||||
obj.authenticated = 2;
|
||||
}
|
||||
|
||||
// Verify the agent signature
|
||||
function processAgentSignature(msg) {
|
||||
var md = obj.forge.md.sha256.create(); // TODO: Switch this to SHA256 on node instead of forge.
|
||||
// Verify the peer server signature
|
||||
function processPeerSignature(msg) {
|
||||
var md = obj.forge.md.sha384.create(); // TODO: Switch this to SHA384 on node instead of forge.
|
||||
md.update(obj.parent.parent.webserver.webCertificatHash, 'binary');
|
||||
md.update(obj.nonce, 'binary');
|
||||
md.update(obj.agentnonce, 'binary');
|
||||
md.update(obj.peernonce, 'binary');
|
||||
if (obj.unauth.nodeCert.publicKey.verify(md.digest().bytes(), msg) == false) { return false; }
|
||||
if (obj.unauth.nodeid !== obj.agentCertificatHashHex) { return false; }
|
||||
|
||||
// Connection is a success, clean up
|
||||
obj.nodeid = obj.unauth.nodeid.toUpperCase();
|
||||
delete obj.nonce;
|
||||
delete obj.agentnonce;
|
||||
delete obj.peernonce;
|
||||
delete obj.unauth;
|
||||
if (obj.unauthsign) delete obj.unauthsign;
|
||||
obj.authenticated = 1;
|
||||
@ -366,7 +349,7 @@ module.exports.CreateMultiServer = function (parent, args) {
|
||||
if (command.dbid != obj.parent.parent.db.identifier) { console.log('ERROR: Database ID mismatch. Trying to peer to a server with the wrong database. (' + obj.remoteaddr + ', ' + command.serverid + ').'); return; }
|
||||
if (obj.parent.peerConfig.servers[command.serverid] == null) { console.log('ERROR: Unknown peer serverid: ' + command.serverid + ' (' + obj.remoteaddr + ').'); return; }
|
||||
obj.peerServerId = command.serverid;
|
||||
obj.peerServerKey = command.key;
|
||||
obj.peerServerKey = new Buffer(command.key, 'hex');
|
||||
obj.serverCertHash = command.serverCertHash;
|
||||
obj.authenticated = 3;
|
||||
obj.parent.SetupPeerServer(obj, obj.peerServerId);
|
||||
@ -389,9 +372,7 @@ module.exports.CreateMultiServer = function (parent, args) {
|
||||
if (obj.parent.config.peers.servers[obj.serverid] == null) { console.log("Error: Unable to peer with other servers, \"" + obj.serverid + "\" not present in peer servers list."); return null; }
|
||||
|
||||
// Generate a cryptographic key used to encode and decode cookies
|
||||
obj.generateCookieKey = function () {
|
||||
return new Buffer(obj.crypto.randomBytes(32), 'binary').toString('hex');
|
||||
}
|
||||
obj.generateCookieKey = function () { return new Buffer(obj.crypto.randomBytes(32), 'binary'); }
|
||||
|
||||
// Return the private key of a peer server
|
||||
obj.getServerCookieKey = function (serverid) {
|
||||
@ -400,40 +381,25 @@ module.exports.CreateMultiServer = function (parent, args) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Encode an object as a cookie using a key
|
||||
// Encode an object as a cookie using a key. (key must be 32 bytes long)
|
||||
obj.encodeCookie = function (o, key) {
|
||||
try {
|
||||
if (key == null) { key = obj.serverKey; }
|
||||
key = require('./common.js').hex2rstr(key);
|
||||
o.time = Math.floor(Date.now() / 1000); // Add the cookie creation time
|
||||
var msg = JSON.stringify(o);
|
||||
msg = obj.crypto.createHmac('sha256', key.substring(16)).update(msg, 'binary', 'binary').digest('binary') + msg;
|
||||
var iv = new Buffer(obj.crypto.randomBytes(16), 'binary');
|
||||
var cipher = obj.crypto.createCipheriv('aes-128-cbc', new Buffer(key.substring(0, 16), 'binary'), iv);
|
||||
crypted = cipher.update(msg, 'binary', 'binary');
|
||||
crypted += cipher.final('binary');
|
||||
var total = new Buffer(iv, 'binary').toString('hex') + new Buffer(crypted, 'binary').toString('hex'); // HEX: This is not an efficient concat, but it's very compatible.
|
||||
var cookie = new Buffer(total, 'hex').toString('base64');
|
||||
return cookie.replace(/\+/g, '@').replace(/\//g, '$');
|
||||
var iv = new Buffer(obj.crypto.randomBytes(12), 'binary'), cipher = obj.crypto.createCipheriv('aes-256-gcm', key, iv);
|
||||
var crypted = Buffer.concat([cipher.update(JSON.stringify(o), 'utf8'), cipher.final()]);
|
||||
return Buffer.concat([iv, cipher.getAuthTag(), crypted]).toString('base64').replace(/\+/g, '@').replace(/\//g, '$');
|
||||
} catch (e) { return null; }
|
||||
}
|
||||
|
||||
// Decode a cookie back into an object using a key. Return null if it's not a valid cookie.
|
||||
// Decode a cookie back into an object using a key. Return null if it's not a valid cookie. (key must be 32 bytes long)
|
||||
obj.decodeCookie = function (cookie, key) {
|
||||
try {
|
||||
if (key == null) { key = obj.serverKey; }
|
||||
key = require('./common.js').hex2rstr(key);
|
||||
cookie = new Buffer(cookie.replace(/\@/g, '+').replace(/\$/g, '/'), 'base64').toString('hex'); // HEX: This is not an efficient split, but it's very compatible.
|
||||
var iv = new Buffer(cookie.substring(0, 32), 'hex');
|
||||
var msg = new Buffer(cookie.substring(32), 'hex');
|
||||
var decipher = obj.crypto.createDecipheriv('aes-128-cbc', new Buffer(key.substring(0, 16), 'binary'), iv)
|
||||
var dec = decipher.update(msg, 'binary', 'binary')
|
||||
dec += decipher.final('binary');
|
||||
var msg = dec.substring(32);
|
||||
var hash1 = dec.substring(0, 32);
|
||||
var hash2 = obj.crypto.createHmac('sha256', key.substring(16)).update(msg, 'binary', 'binary').digest('binary');
|
||||
if (hash1 !== hash2) { return null; }
|
||||
var o = JSON.parse(msg);
|
||||
cookie = new Buffer(cookie.replace(/\@/g, '+').replace(/\$/g, '/'), 'base64'); // HEX: This is not an efficient split, but it's very compatible.
|
||||
var decipher = obj.crypto.createDecipheriv('aes-256-gcm', key, cookie.slice(0, 12));
|
||||
decipher.setAuthTag(cookie.slice(12, 16));
|
||||
var o = JSON.parse(decipher.update(cookie.slice(28), 'binary', 'utf8') + decipher.final('utf8'));
|
||||
if ((o.time == null) || (o.time == null) || (typeof o.time != 'number')) { return null; }
|
||||
o.time = o.time * 1000; // Decode the cookie creation time
|
||||
o.dtime = Date.now() - o.time; // Decode how long ago the cookie was created
|
||||
@ -639,41 +605,30 @@ module.exports.CreateMultiServer = function (parent, args) {
|
||||
|
||||
peerTunnel.connect = function () {
|
||||
// Get the web socket setup
|
||||
var WebSocketClient = require('websocket').client;
|
||||
peerTunnel.wsclient = new WebSocketClient();
|
||||
peerTunnel.parent.parent.debug(1, 'FTunnel ' + peerTunnel.serverid + ': Start connect to ' + peerTunnel.url);
|
||||
peerTunnel.ws2 = new WebSocket(peerTunnel.url, { rejectUnauthorized: false });
|
||||
|
||||
// Register the connection failed event
|
||||
peerTunnel.wsclient.on('connectFailed', function (error) { peerTunnel.parent.parent.debug(1, 'FTunnel ' + obj.serverid + ': Failed connection'); peerTunnel.ws1.close(); });
|
||||
peerTunnel.ws2.on('error', function (error) { peerTunnel.parent.parent.debug(1, 'FTunnel ' + obj.serverid + ': Connection error'); peerTunnel.close(); });
|
||||
|
||||
// If the peer server web socket is closed, clean up.
|
||||
peerTunnel.ws2.on('close', function (req) { peerTunnel.parent.parent.debug(1, 'FTunnel disconnect ' + peerTunnel.nodeid); peerTunnel.close(); });
|
||||
|
||||
// If a message is received from the peer, Peer ---> Browser (TODO: Pipe this?)
|
||||
peerTunnel.ws2.on('message', function (msg) { try { peerTunnel.ws2.pause(); peerTunnel.ws1.send(msg, function () { peerTunnel.ws2.resume(); }); } catch (e) { } });
|
||||
|
||||
// Register the connection event
|
||||
peerTunnel.wsclient.on('connect', function (connection) {
|
||||
peerTunnel.ws2.on('open', function () {
|
||||
peerTunnel.parent.parent.debug(1, 'FTunnel ' + peerTunnel.serverid + ': Connected');
|
||||
|
||||
// Get the peer server's certificate and compute the server public key hash
|
||||
var rawcertbuf = connection.socket.getPeerCertificate().raw, rawcert = '';
|
||||
for (var i = 0; i < rawcertbuf.length; i++) { rawcert += String.fromCharCode(rawcertbuf[i]); }
|
||||
var serverCert = obj.forge.pki.certificateFromAsn1(obj.forge.asn1.fromDer(rawcert));
|
||||
var serverCertHashHex = obj.forge.pki.getPublicKeyFingerprint(serverCert.publicKey, { encoding: 'hex', md: obj.forge.md.sha256.create() });
|
||||
var serverCert = obj.forge.pki.certificateFromAsn1(obj.forge.asn1.fromDer(peerTunnel.ws2._socket.getPeerCertificate().raw.toString('binary')));
|
||||
var serverCertHashHex = obj.forge.pki.getPublicKeyFingerprint(serverCert.publicKey, { encoding: 'hex', md: obj.forge.md.sha384.create() });
|
||||
|
||||
// Check if the peer certificate is the expected one for this serverid
|
||||
if (obj.peerServers[serverid] == null || obj.peerServers[serverid].serverCertHash != serverCertHashHex) { console.log('ERROR: Outer certificate hash mismatch. (' + peerTunnel.url + ', ' + peerTunnel.serverid + ').'); peerTunnel.ws1.close(); return; }
|
||||
if (obj.peerServers[serverid] == null || obj.peerServers[serverid].serverCertHash != serverCertHashHex) { console.log('ERROR: Outer certificate hash mismatch. (' + peerTunnel.url + ', ' + peerTunnel.serverid + ').'); peerTunnel.close(); return; }
|
||||
|
||||
// Connection accepted.
|
||||
peerTunnel.ws2 = connection;
|
||||
|
||||
// If error, do nothing
|
||||
peerTunnel.ws2.on('error', function (err) { peerTunnel.parent.parent.debug(1, 'FTunnel: Connection Error: ' + err); peerTunnel.close(); });
|
||||
|
||||
// If the mesh agent web socket is closed, clean up.
|
||||
peerTunnel.ws2.on('close', function (req) { peerTunnel.parent.parent.debug(1, 'FTunnel disconnect ' + peerTunnel.nodeid); peerTunnel.close(); });
|
||||
|
||||
// If a message is received from the peer, Peer ---> Browser
|
||||
peerTunnel.ws2.on('message', function (msg) {
|
||||
try {
|
||||
if (msg.type == 'utf8') { peerTunnel.ws2.pause(); peerTunnel.ws1.send(msg.utf8Data, function () { peerTunnel.ws2.resume(); }); }
|
||||
else if (msg.type == 'binary') { peerTunnel.ws2.pause(); peerTunnel.ws1.send(msg.binaryData, function () { peerTunnel.ws2.resume(); }); }
|
||||
} catch (e) { }
|
||||
});
|
||||
|
||||
// Resume the web socket to start the data flow
|
||||
// Connection accepted, resume the web socket to start the data flow
|
||||
peerTunnel.ws1.resume();
|
||||
});
|
||||
|
||||
@ -681,12 +636,10 @@ module.exports.CreateMultiServer = function (parent, args) {
|
||||
peerTunnel.ws1.on('message', function (msg) { try { peerTunnel.ws1.pause(); peerTunnel.ws2.send(msg, function () { peerTunnel.ws1.resume(); }); } catch (e) { } });
|
||||
|
||||
// If error, do nothing
|
||||
peerTunnel.ws1.on('error', function (err) { console.log(err); peerTunnel.close(); });
|
||||
peerTunnel.ws1.on('error', function (err) { peerTunnel.close(); });
|
||||
|
||||
// If the web socket is closed, close the associated TCP connection.
|
||||
peerTunnel.ws1.on('close', function (req) { peerTunnel.parent.parent.debug(1, 'FTunnel disconnect ' + peerTunnel.nodeid); peerTunnel.close(); });
|
||||
|
||||
peerTunnel.wsclient.connect(peerTunnel.url, null, null, null, { rejectUnauthorized: false });
|
||||
}
|
||||
|
||||
// Disconnect both sides of the tunnel
|
||||
|
16
package.json
16
package.json
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "meshcentral",
|
||||
"version": "0.0.8-u",
|
||||
"version": "0.0.8-w",
|
||||
"keywords": [
|
||||
"Remote Management",
|
||||
"Intel AMT",
|
||||
@ -26,20 +26,22 @@
|
||||
],
|
||||
"dependencies": {
|
||||
"archiver": "^1.3.0",
|
||||
"body-parser": "^1.17.1",
|
||||
"compression": "^1.6.2",
|
||||
"connect-redis": "^3.2.0",
|
||||
"express": "^4.15.2",
|
||||
"body-parser": "^1.18.2",
|
||||
"compression": "^1.7.1",
|
||||
"connect-redis": "^3.3.2",
|
||||
"express": "^4.16.2",
|
||||
"express-handlebars": "^3.0.0",
|
||||
"express-session": "^1.15.1",
|
||||
"express-session": "^1.15.6",
|
||||
"express-ws": "^2.0.0",
|
||||
"meshcentral": "*",
|
||||
"minimist": "^1.2.0",
|
||||
"mongojs": "^2.4.1",
|
||||
"multiparty": "^4.1.3",
|
||||
"nedb": "^1.8.0",
|
||||
"node-forge": "^0.6.49",
|
||||
"node-windows": "^0.1.14",
|
||||
"unzip": "^0.1.11",
|
||||
"websocket": "^1.0.24",
|
||||
"ws": "^3.2.0",
|
||||
"xmldom": "^0.1.27"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
|
4
pass.js
4
pass.js
@ -21,7 +21,7 @@ var iterations = 12000;
|
||||
exports.hash = function (pwd, salt, fn) {
|
||||
if (3 == arguments.length) {
|
||||
try {
|
||||
crypto.pbkdf2(pwd, salt, iterations, len, 'sha256', function (err, hash) { fn(err, hash.toString('base64')); });
|
||||
crypto.pbkdf2(pwd, salt, iterations, len, 'sha384', function (err, hash) { fn(err, hash.toString('base64')); });
|
||||
} catch (e) {
|
||||
// If this previous call fails, it's probably because older pbkdf2 did not specify the hashing function, just use the default.
|
||||
crypto.pbkdf2(pwd, salt, iterations, len, function (err, hash) { fn(err, hash.toString('base64')); });
|
||||
@ -32,7 +32,7 @@ exports.hash = function (pwd, salt, fn) {
|
||||
if (err) return fn(err);
|
||||
salt = salt.toString('base64');
|
||||
try {
|
||||
crypto.pbkdf2(pwd, salt, iterations, len, 'sha256', function (err, hash) { if (err) { return fn(err); } fn(null, salt, hash.toString('base64')); });
|
||||
crypto.pbkdf2(pwd, salt, iterations, len, 'sha384', function (err, hash) { if (err) { return fn(err); } fn(null, salt, hash.toString('base64')); });
|
||||
} catch (e) {
|
||||
// If this previous call fails, it's probably because older pbkdf2 did not specify the hashing function, just use the default.
|
||||
crypto.pbkdf2(pwd, salt, iterations, len, function (err, hash) { if (err) { return fn(err); } fn(null, salt, hash.toString('base64')); });
|
||||
|
@ -16,7 +16,9 @@ var CreateAgentRedirect = function (meshserver, module, serverPublicNamePort) {
|
||||
obj.connectstate = -1;
|
||||
obj.tunnelid = Math.random().toString(36).substring(2); // Generate a random client tunnel id
|
||||
obj.protocol = module.protocol; // 1 = SOL, 2 = KVM, 3 = IDER, 4 = Files, 5 = FileTransfer
|
||||
|
||||
obj.attemptWebRTC = false;
|
||||
obj.webrtc = null;
|
||||
obj.webchannel = null;
|
||||
obj.onStateChanged = null;
|
||||
|
||||
// Private method
|
||||
@ -43,8 +45,68 @@ var CreateAgentRedirect = function (meshserver, module, serverPublicNamePort) {
|
||||
obj.xxStateChange(2);
|
||||
}
|
||||
|
||||
// Called to pass websocket control messages
|
||||
obj.xxOnControlCommand = function (msg) {
|
||||
var controlMsg = JSON.parse(msg);
|
||||
if ((controlMsg.type == 'answer') && (obj.webrtc != null)) {
|
||||
console.log('gotAnswer', JSON.stringify(controlMsg));
|
||||
obj.webrtc.setRemoteDescription(new RTCSessionDescription(controlMsg), function () { console.log('WebRTC remote ok'); }, obj.xxCloseWebRTC);
|
||||
}
|
||||
}
|
||||
|
||||
// Close the WebRTC connection, should be called if a problem occurs during WebRTC setup.
|
||||
obj.xxCloseWebRTC = function () {
|
||||
if (obj.webchannel != null) { obj.webchannel.close(); obj.webchannel = null; }
|
||||
if (obj.webrtc != null) { obj.webrtc.close(); obj.webrtc = null; }
|
||||
}
|
||||
|
||||
obj.xxOnMessage = function (e) {
|
||||
if (obj.State < 3) { if (e.data == 'c') { obj.socket.send(obj.protocol); obj.xxStateChange(3); return; } }
|
||||
if (obj.State < 3) {
|
||||
if (e.data == 'c') {
|
||||
obj.socket.send(obj.protocol);
|
||||
obj.xxStateChange(3);
|
||||
|
||||
if (obj.attemptWebRTC == true) {
|
||||
// Try to get WebRTC setup
|
||||
var configuration = null; //{ "iceServers": [ { 'urls': 'stun:stun.services.mozilla.com' }, { 'urls': 'stun:stun.l.google.com:19302' } ] };
|
||||
if (typeof RTCPeerConnection !== 'undefined') { obj.webrtc = new RTCPeerConnection(configuration); }
|
||||
else if (typeof webkitRTCPeerConnection !== 'undefined') { obj.webrtc = new webkitRTCPeerConnection(configuration); }
|
||||
|
||||
if (obj.webrtc != null) {
|
||||
obj.webchannel = obj.webrtc.createDataChannel("DataChannel", {}); // { ordered: false, maxRetransmits: 2 }
|
||||
obj.webchannel.onmessage = function (event) { console.log("DataChannel - onmessage", event.data); };
|
||||
obj.webchannel.onopen = function () { console.log("DataChannel - onopen"); };
|
||||
obj.webchannel.onclose = function (event) { console.log("DataChannel - onclose"); }
|
||||
obj.webrtc.ondatachannel = function (e) { console.log('ondatachannel'); } // TODO: Should not be needed
|
||||
obj.webrtc.onicecandidate = function (e) {
|
||||
if (e.candidate == null) {
|
||||
console.log('createOffer', JSON.stringify(obj.webrtcoffer));
|
||||
obj.socket.send('**********%%%%%%###**' + JSON.stringify(obj.webrtcoffer)); // End of candidates, send the offer
|
||||
} else {
|
||||
obj.webrtcoffer.sdp += ("a=" + e.candidate.candidate + "\r\n"); // New candidate, add it to the SDP
|
||||
}
|
||||
}
|
||||
obj.webrtc.oniceconnectionstatechange = function () {
|
||||
if (obj.webrtc != null) {
|
||||
console.log('oniceconnectionstatechange', obj.webrtc.iceConnectionState);
|
||||
if ((obj.webrtc.iceConnectionState == 'disconnected') || (obj.webrtc.iceConnectionState == 'failed')) { obj.xxCloseWebRTC(); }
|
||||
}
|
||||
}
|
||||
obj.webrtc.createOffer(function (offer) {
|
||||
// Got the offer
|
||||
obj.webrtcoffer = offer;
|
||||
obj.webrtc.setLocalDescription(offer, function () { console.log('WebRTC local ok'); }, obj.xxCloseWebRTC);
|
||||
}, obj.xxCloseWebRTC, { mandatory: { OfferToReceiveAudio: false, OfferToReceiveVideo: false } });
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (typeof e.data == 'string') {
|
||||
// Control messages, most likely WebRTC setup
|
||||
obj.xxOnControlCommand(e.data);
|
||||
}
|
||||
if (typeof e.data == 'object') {
|
||||
var f = new FileReader();
|
||||
if (f.readAsBinaryString) {
|
||||
@ -81,6 +143,9 @@ var CreateAgentRedirect = function (meshserver, module, serverPublicNamePort) {
|
||||
}
|
||||
else if (typeof data !== 'string') return;
|
||||
|
||||
// TODO: Don't use a prefix anymore, use string encoding instead
|
||||
if (data.length > 21 && data.startsWith('**********%%%%%%###**')) { obj.xxOnControlCommand(data.substring(21)); return; }
|
||||
|
||||
//console.log("xxOnSocketData", rstr2hex(data));
|
||||
|
||||
return obj.m.ProcessData(data);
|
||||
@ -115,6 +180,7 @@ var CreateAgentRedirect = function (meshserver, module, serverPublicNamePort) {
|
||||
//obj.debug("Agent Redir Socket Stopped");
|
||||
obj.xxStateChange(0);
|
||||
obj.connectstate = -1;
|
||||
obj.xxCloseWebRTC();
|
||||
if (obj.socket != null) { obj.socket.close(); obj.socket = null; }
|
||||
}
|
||||
|
||||
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -150,7 +150,7 @@
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
<div id="xdevices" style="max-height:calc(100vh - 228px);overflow-y:auto;-webkit-overflow-scrolling:touch"></div>
|
||||
<div id="xdevices" style="max-height:calc(100vh - 228px);overflow-y:auto;overflow-x:hidden;-webkit-overflow-scrolling:touch"></div>
|
||||
<div id="xdevicesmap" style="height:500px;width:100%;overflow:hidden;position:relative">
|
||||
<div id=xmapSearchResultsDlg style="position:absolute;display:none;max-height:280px;left:5px;top:5px;max-width:250px;z-index:1000;background-color:#EEE;box-shadow:0px 0px 15px #666">
|
||||
<div style="width:100%;background-color:#003366;color:#FFF;border-radius:5px 5px 0 0">
|
||||
@ -2760,6 +2760,7 @@
|
||||
} else {
|
||||
// Setup the Mesh Agent remote desktop
|
||||
desktop = CreateAgentRedirect(meshserver, CreateAgentRemoteDesktop('Desk'), serverPublicNamePort);
|
||||
desktop.attemptWebRTC = debugmode;
|
||||
desktop.onStateChanged = onDesktopStateChange;
|
||||
desktop.m.CompressionLevel = desktopsettings.quality; // Number from 1 to 100. 50 or less is best.
|
||||
desktop.m.ScalingLevel = desktopsettings.scaling;
|
||||
@ -2984,6 +2985,7 @@
|
||||
} else {
|
||||
// Setup a mesh agent terminal
|
||||
terminal = CreateAgentRedirect(meshserver, CreateAmtRemoteTerminal('Term'), serverPublicNamePort);
|
||||
terminal.attemptWebRTC = debugmode;
|
||||
terminal.onStateChanged = onTerminalStateChange;
|
||||
terminal.Start(terminalNode._id);
|
||||
terminal.contype = 1;
|
||||
@ -3088,6 +3090,7 @@
|
||||
if (!files) {
|
||||
// Setup a mesh agent files
|
||||
files = CreateAgentRedirect(meshserver, CreateRemoteFiles(p13gotFiles), serverPublicNamePort);
|
||||
files.attemptWebRTC = debugmode;
|
||||
files.onStateChanged = onFilesStateChange;
|
||||
files.Start(filesNode._id);
|
||||
} else {
|
||||
@ -3288,6 +3291,7 @@
|
||||
// Called by the html page to start a download, arguments are: path, file name and file size.
|
||||
function p13downloadfile(x, y, z) {
|
||||
downloadFile = CreateAgentRedirect(meshserver, CreateRemoteFiles(p13gotDownloadData), serverPublicNamePort); // Create our file transport
|
||||
downloadFile.attemptWebRTC = debugmode;
|
||||
downloadFile.onStateChanged = onFileDownloadStateChange;
|
||||
downloadFile.xpath = decodeURIComponent(x);
|
||||
downloadFile.xfile = decodeURIComponent(y);
|
||||
@ -3365,6 +3369,7 @@
|
||||
// Connect again
|
||||
function p13uploadReconnect() {
|
||||
uploadFile.ws = CreateAgentRedirect(meshserver, CreateRemoteFiles(p13gotUploadData), serverPublicNamePort);
|
||||
uploadFile.ws.attemptWebRTC = debugmode;
|
||||
uploadFile.ws.onStateChanged = onFileUploadStateChange;
|
||||
uploadFile.ws.Start(filesNode._id);
|
||||
}
|
||||
|
60
webserver.js
60
webserver.js
@ -69,9 +69,9 @@ module.exports.CreateWebServer = function (parent, db, args, secret, certificate
|
||||
obj.userAllowedIp = args.userallowedip; // List of allowed IP addresses for users
|
||||
|
||||
// Perform hash on web certificate and agent certificate
|
||||
obj.webCertificatHash = parent.certificateOperations.forge.pki.getPublicKeyFingerprint(parent.certificateOperations.forge.pki.certificateFromPem(obj.certificates.web.cert).publicKey, { md: parent.certificateOperations.forge.md.sha256.create(), encoding: 'binary' });
|
||||
obj.webCertificatHashHex = parent.certificateOperations.forge.pki.getPublicKeyFingerprint(parent.certificateOperations.forge.pki.certificateFromPem(obj.certificates.web.cert).publicKey, { md: parent.certificateOperations.forge.md.sha256.create(), encoding: 'hex' });
|
||||
obj.agentCertificatHashHex = parent.certificateOperations.forge.pki.getPublicKeyFingerprint(parent.certificateOperations.forge.pki.certificateFromPem(obj.certificates.agent.cert).publicKey, { md: parent.certificateOperations.forge.md.sha256.create(), encoding: 'hex' });
|
||||
obj.webCertificatHash = parent.certificateOperations.forge.pki.getPublicKeyFingerprint(parent.certificateOperations.forge.pki.certificateFromPem(obj.certificates.web.cert).publicKey, { md: parent.certificateOperations.forge.md.sha384.create(), encoding: 'binary' });
|
||||
obj.webCertificatHashHex = parent.certificateOperations.forge.pki.getPublicKeyFingerprint(parent.certificateOperations.forge.pki.certificateFromPem(obj.certificates.web.cert).publicKey, { md: parent.certificateOperations.forge.md.sha384.create(), encoding: 'hex' });
|
||||
obj.agentCertificatHashHex = parent.certificateOperations.forge.pki.getPublicKeyFingerprint(parent.certificateOperations.forge.pki.certificateFromPem(obj.certificates.agent.cert).publicKey, { md: parent.certificateOperations.forge.md.sha384.create(), encoding: 'hex' });
|
||||
obj.agentCertificatAsn1 = parent.certificateOperations.forge.asn1.toDer(parent.certificateOperations.forge.pki.certificateToAsn1(parent.certificateOperations.forge.pki.certificateFromPem(parent.certificates.agent.cert))).getBytes();
|
||||
|
||||
// Main lists
|
||||
@ -86,9 +86,9 @@ module.exports.CreateWebServer = function (parent, db, args, secret, certificate
|
||||
obj.wsPeerRelays = {}; // Id -> { ServerId, Time }
|
||||
|
||||
// Setup randoms
|
||||
obj.crypto.randomBytes(32, function (err, buf) { obj.httpAuthRandom = buf; });
|
||||
obj.crypto.randomBytes(48, function (err, buf) { obj.httpAuthRandom = buf; });
|
||||
obj.crypto.randomBytes(16, function (err, buf) { obj.httpAuthRealm = buf.toString('hex'); });
|
||||
obj.crypto.randomBytes(32, function (err, buf) { obj.relayRandom = buf; });
|
||||
obj.crypto.randomBytes(48, function (err, buf) { obj.relayRandom = buf; });
|
||||
|
||||
function EscapeHtml(x) { if (typeof x == "string") return x.replace(/&/g, '&').replace(/>/g, '>').replace(/</g, '<').replace(/"/g, '"').replace(/'/g, '''); if (typeof x == "boolean") return x; if (typeof x == "number") return x; }
|
||||
function EscapeHtmlBreaks(x) { if (typeof x == "string") return x.replace(/&/g, '&').replace(/>/g, '>').replace(/</g, '<').replace(/"/g, '"').replace(/'/g, ''').replace(/\r/g, '<br />').replace(/\n/g, '').replace(/\t/g, ' '); if (typeof x == "boolean") return x; if (typeof x == "number") return x; }
|
||||
@ -745,11 +745,8 @@ module.exports.CreateWebServer = function (parent, db, args, secret, certificate
|
||||
// let's chain up the TLSSocket <-> SerialTunnel <-> CIRA APF (chnl)
|
||||
// Anything that needs to be forwarded by SerialTunnel will be encapsulated by chnl write
|
||||
ser.forwardwrite = function (msg) {
|
||||
// Convert a buffer into a string, "msg = msg.toString('ascii');" does not work
|
||||
// TLS ---> CIRA
|
||||
var msg2 = "";
|
||||
for (var i = 0; i < msg.length; i++) { msg2 += String.fromCharCode(msg[i]); }
|
||||
chnl.write(msg2);
|
||||
chnl.write(msg.toString('binary'));
|
||||
};
|
||||
|
||||
// When APF tunnel return something, update SerialTunnel buffer
|
||||
@ -776,10 +773,9 @@ module.exports.CreateWebServer = function (parent, db, args, secret, certificate
|
||||
tlsock.on('data', function (data) {
|
||||
// AMT/TLS ---> WS
|
||||
try {
|
||||
var data2 = "";
|
||||
for (var i = 0; i < data.length; i++) { data2 += String.fromCharCode(data[i]); }
|
||||
if (ws.interceptor) { data2 = ws.interceptor.processAmtData(data2); } // Run data thru interceptor
|
||||
ws.send(data2);
|
||||
data = data.toString('binary');
|
||||
if (ws.interceptor) { data = ws.interceptor.processAmtData(data); } // Run data thru interceptor
|
||||
ws.send(data);
|
||||
} catch (e) { }
|
||||
});
|
||||
|
||||
@ -797,11 +793,9 @@ module.exports.CreateWebServer = function (parent, db, args, secret, certificate
|
||||
// If the CIRA connection is pending, the CIRA channel has built-in buffering, so we are ok sending anyway.
|
||||
ws.on('message', function (msg) {
|
||||
// WS ---> AMT/TLS
|
||||
// Convert a buffer into a string, "msg = msg.toString('ascii');" does not work
|
||||
var msg2 = "";
|
||||
for (var i = 0; i < msg.length; i++) { msg2 += String.fromCharCode(msg[i]); }
|
||||
if (ws.interceptor) { msg2 = ws.interceptor.processBrowserData(msg2); } // Run data thru interceptor
|
||||
if (ws.forwardclient.xtls == 1) { ws.forwardclient.write(Buffer.from(msg2, 'binary')); } else { ws.forwardclient.write(msg2); }
|
||||
msg = msg.toString('binary');
|
||||
if (ws.interceptor) { msg = ws.interceptor.processBrowserData(msg); } // Run data thru interceptor
|
||||
if (ws.forwardclient.xtls == 1) { ws.forwardclient.write(Buffer.from(msg, 'binary')); } else { ws.forwardclient.write(msg); }
|
||||
});
|
||||
|
||||
// If error, do nothing
|
||||
@ -849,11 +843,9 @@ module.exports.CreateWebServer = function (parent, db, args, secret, certificate
|
||||
// When data is received from the web socket, forward the data into the associated TCP connection.
|
||||
ws.on('message', function (msg) {
|
||||
Debug(1, 'TCP relay data to ' + node.host + ', ' + msg.length + ' bytes'); // DEBUG
|
||||
// Convert a buffer into a string, "msg = msg.toString('ascii');" does not work
|
||||
var msg2 = "";
|
||||
for (var i = 0; i < msg.length; i++) { msg2 += String.fromCharCode(msg[i]); }
|
||||
if (ws.interceptor) { msg2 = ws.interceptor.processBrowserData(msg2); } // Run data thru interceptor
|
||||
ws.forwardclient.write(new Buffer(msg2, "ascii")); // Forward data to the associated TCP connection.
|
||||
msg = msg.toString('binary');
|
||||
if (ws.interceptor) { msg = ws.interceptor.processBrowserData(msg); } // Run data thru interceptor
|
||||
ws.forwardclient.write(new Buffer(msg, 'binary')); // Forward data to the associated TCP connection.
|
||||
});
|
||||
|
||||
// If error, do nothing
|
||||
@ -1301,7 +1293,7 @@ module.exports.CreateWebServer = function (parent, db, args, secret, certificate
|
||||
// TODO: Right now, we only create type 1 Agent-less Intel AMT mesh, or type 2 Agent mesh
|
||||
if ((command.meshtype == 1) || (command.meshtype == 2)) {
|
||||
// Create a type 1 agent-less Intel AMT mesh.
|
||||
obj.crypto.randomBytes(32, function (err, buf) {
|
||||
obj.crypto.randomBytes(48, function (err, buf) {
|
||||
var meshid = 'mesh/' + domain.id + '/' + buf.toString('hex').toUpperCase();
|
||||
var links = {}
|
||||
links[user._id] = { name: user.name, rights: 0xFFFFFFFF };
|
||||
@ -1455,7 +1447,7 @@ module.exports.CreateWebServer = function (parent, db, args, secret, certificate
|
||||
if (mesh.links[user._id] == null || ((mesh.links[user._id].rights & 4) == 0)) return;
|
||||
|
||||
// Create a new nodeid
|
||||
obj.crypto.randomBytes(32, function (err, buf) {
|
||||
obj.crypto.randomBytes(48, function (err, buf) {
|
||||
// create the new node
|
||||
var nodeid = 'node/' + domain.id + '/' + buf.toString('hex').toUpperCase();
|
||||
var device = { type: 'node', mtype: 1, _id: nodeid, meshid: command.meshid, name: command.devicename, host: command.hostname, domain: domain.id, intelamt: { user: command.amtusername, pass: command.amtpassword, tls: parseInt(command.amttls) } };
|
||||
@ -1787,7 +1779,7 @@ module.exports.CreateWebServer = function (parent, db, args, secret, certificate
|
||||
var authstr = req.headers['authorization'];
|
||||
if (authstr.substring(0, 7) == "Digest ") {
|
||||
var auth = obj.common.parseNameValueList(obj.common.quoteSplit(authstr.substring(7)));
|
||||
if ((req.url === auth.uri) && (obj.httpAuthRealm === auth.realm) && (auth.opaque === obj.crypto.createHmac('SHA256', obj.httpAuthRandom).update(auth.nonce).digest('hex'))) {
|
||||
if ((req.url === auth.uri) && (obj.httpAuthRealm === auth.realm) && (auth.opaque === obj.crypto.createHmac('SHA384', obj.httpAuthRandom).update(auth.nonce).digest('hex'))) {
|
||||
|
||||
// Read the data, we need to get the arg field
|
||||
var eventData = '';
|
||||
@ -1806,7 +1798,7 @@ module.exports.CreateWebServer = function (parent, db, args, secret, certificate
|
||||
if (nodes.length == 1) {
|
||||
// Yes, the node exists, compute Intel AMT digest password
|
||||
var node = nodes[0];
|
||||
var amtpass = obj.crypto.createHash('sha256').update(auth.username.toLowerCase() + ":" + nodeid + ":" + obj.parent.dbconfig.amtWsEventSecret).digest("base64").substring(0, 12).split("/").join("x").split("\\").join("x");
|
||||
var amtpass = obj.crypto.createHash('sha384').update(auth.username.toLowerCase() + ":" + nodeid + ":" + obj.parent.dbconfig.amtWsEventSecret).digest("base64").substring(0, 12).split("/").join("x").split("\\").join("x");
|
||||
|
||||
// Check the MD5 hash
|
||||
if (auth.response === obj.common.ComputeDigesthash(auth.username, amtpass, auth.realm, "POST", auth.uri, auth.qop, auth.nonce, auth.nc, auth.cnonce)) {
|
||||
@ -1848,8 +1840,8 @@ module.exports.CreateWebServer = function (parent, db, args, secret, certificate
|
||||
} catch (e) { console.log(e); }
|
||||
|
||||
// Send authentication response
|
||||
obj.crypto.randomBytes(32, function (err, buf) {
|
||||
var nonce = buf.toString('hex'), opaque = obj.crypto.createHmac('SHA256', obj.httpAuthRandom).update(nonce).digest('hex');
|
||||
obj.crypto.randomBytes(48, function (err, buf) {
|
||||
var nonce = buf.toString('hex'), opaque = obj.crypto.createHmac('SHA384', obj.httpAuthRandom).update(nonce).digest('hex');
|
||||
res.set({ 'WWW-Authenticate': 'Digest realm="' + obj.httpAuthRealm + '", qop="auth,auth-int", nonce="' + nonce + '", opaque="' + opaque + '"' });
|
||||
res.sendStatus(401);
|
||||
});
|
||||
@ -1921,7 +1913,7 @@ module.exports.CreateWebServer = function (parent, db, args, secret, certificate
|
||||
} else {
|
||||
// Send a list of available mesh agents
|
||||
var response = '<html><head><title>Mesh Agents</title><style>table,th,td { border:1px solid black;border-collapse:collapse;padding:3px; }</style></head><body><table>';
|
||||
response += '<tr style="background-color:lightgray"><th>ID</th><th>Description</th><th>Link</th><th>Size</th><th>SHA256</th></tr>';
|
||||
response += '<tr style="background-color:lightgray"><th>ID</th><th>Description</th><th>Link</th><th>Size</th><th>SHA384</th></tr>';
|
||||
for (var agentid in obj.parent.meshAgentBinaries) {
|
||||
var agentinfo = obj.parent.meshAgentBinaries[agentid];
|
||||
response += '<tr><td>' + agentinfo.id + '</td><td>' + agentinfo.desc + '</td>';
|
||||
@ -2135,13 +2127,11 @@ module.exports.CreateWebServer = function (parent, db, args, secret, certificate
|
||||
agent.send(obj.common.ShortToStr(11) + obj.common.ShortToStr(0)); // Command 11, ask for mesh core hash.
|
||||
} else {
|
||||
agent.agentCoreCheck = 1000; // Tell the agent object we are not using a custom core.
|
||||
// Perform a SHA256 hash on the core module
|
||||
var buf = new Buffer(core, 'ascii');
|
||||
var hash = obj.crypto.createHash('sha256').update(buf).digest(), hash2 = "";
|
||||
for (var i = 0; i < hash.length; i++) { hash2 += String.fromCharCode(hash[i]); }
|
||||
// Perform a SHA384 hash on the core module
|
||||
var hash = obj.crypto.createHash('sha384').update(new Buffer(core, 'binary')).digest().toString('binary');
|
||||
|
||||
// Send the code module to the agent
|
||||
agent.send(obj.common.ShortToStr(10) + obj.common.ShortToStr(0) + hash2 + core); // TODO: Add core encoding short
|
||||
agent.send(obj.common.ShortToStr(10) + obj.common.ShortToStr(0) + hash + core);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user