mirror of
				https://github.com/Ylianst/MeshCentral.git
				synced 2025-03-09 15:40:18 +00:00 
			
		
		
		
	Improved crypto and removed dependency on WebSocket library, using ws instead.
This commit is contained in:
		
							parent
							
								
									3632741d9e
								
							
						
					
					
						commit
						1952d75860
					
				
					 19 changed files with 379 additions and 439 deletions
				
			
		
							
								
								
									
										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
 | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue