mirror of
				https://github.com/Ylianst/MeshCentral.git
				synced 2025-10-29 15:25:01 -04:00 
			
		
		
		
	Many updates to how agents connect, agent cert checking and tls on agent port.
This commit is contained in:
		
							parent
							
								
									9fdb62d862
								
							
						
					
					
						commit
						1ae52b6378
					
				| @ -436,6 +436,11 @@ module.exports.CertificateOperations = function (parent) { | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         // If web certificate exist, load it as default. This is useful for agent-only port. Load both certificate and private key
 | ||||
|         if (obj.fileExists('webserver-cert-public.crt') && obj.fileExists('webserver-cert-private.key')) { | ||||
|             r.webdefault = { cert: obj.fileLoad('webserver-cert-public.crt', 'utf8'), key: obj.fileLoad('webserver-cert-private.key', 'utf8') }; | ||||
|         } | ||||
| 
 | ||||
|         if (args.tlsoffload) { | ||||
|             // If the web certificate already exist, load it. Load just the certificate since we are in TLS offload situation
 | ||||
|             if (obj.fileExists('webserver-cert-public.crt')) { | ||||
| @ -674,7 +679,7 @@ module.exports.CertificateOperations = function (parent) { | ||||
|             mpsPrivateKey = r.mps.key; | ||||
|         } | ||||
| 
 | ||||
|         r = { root: { cert: rootCertificate, key: rootPrivateKey }, web: { cert: webCertificate, key: webPrivateKey, ca: [] }, mps: { cert: mpsCertificate, key: mpsPrivateKey }, agent: { cert: agentCertificate, key: agentPrivateKey }, ca: calist, CommonName: commonName, RootName: rootName, AmtMpsName: mpsCommonName, dns: {}, WebIssuer: webIssuer }; | ||||
|         r = { root: { cert: rootCertificate, key: rootPrivateKey }, web: { cert: webCertificate, key: webPrivateKey, ca: [] }, webdefault: { cert: webCertificate, key: webPrivateKey, ca: [] }, mps: { cert: mpsCertificate, key: mpsPrivateKey }, agent: { cert: agentCertificate, key: agentPrivateKey }, ca: calist, CommonName: commonName, RootName: rootName, AmtMpsName: mpsCommonName, dns: {}, WebIssuer: webIssuer }; | ||||
| 
 | ||||
|         // Fetch the certificates names for the main certificate
 | ||||
|         var webCertificate = obj.pki.certificateFromPem(r.web.cert); | ||||
|  | ||||
							
								
								
									
										37
									
								
								meshagent.js
									
									
									
									
									
								
							
							
						
						
									
										37
									
								
								meshagent.js
									
									
									
									
									
								
							| @ -389,10 +389,12 @@ module.exports.CreateMeshAgent = function (parent, db, ws, req, args, domain) { | ||||
| 
 | ||||
|                 if (args.ignoreagenthashcheck === true) { | ||||
|                     // Send the agent web hash back to the agent
 | ||||
|                     // Send 384 bits SHA384 hash of TLS cert + 384 bits nonce
 | ||||
|                     obj.sendBinary(common.ShortToStr(1) + msg.substring(2, 50) + obj.nonce); // Command 1, hash + nonce. Use the web hash given by the agent.
 | ||||
|                 } else { | ||||
|                     // Check that the server hash matches our own web certificate hash (SHA384)
 | ||||
|                     if ((getWebCertHash(domain) != msg.substring(2, 50)) && (getWebCertFullHash(domain) != msg.substring(2, 50))) { | ||||
|                     const agentSeenCerthash = msg.substring(2, 50); | ||||
|                     if ((getWebCertHash(domain) != agentSeenCerthash) && (getWebCertFullHash(domain) != agentSeenCerthash) && (parent.defaultWebCertificateHash != agentSeenCerthash) && (parent.defaultWebCertificateFullHash != agentSeenCerthash)) { | ||||
|                         if (parent.parent.supportsProxyCertificatesRequest !== false) { | ||||
|                             obj.badWebCert = Buffer.from(parent.crypto.randomBytes(16), 'binary').toString('base64'); | ||||
|                             parent.wsagentsWithBadWebCerts[obj.badWebCert] = obj; // Add this agent to the list of of agents with bad web certificates.
 | ||||
| @ -404,6 +406,11 @@ module.exports.CreateMeshAgent = function (parent, db, ws, req, args, domain) { | ||||
|                         console.log('Agent bad web cert hash (Agent:' + (Buffer.from(msg.substring(2, 50), 'binary').toString('hex').substring(0, 10)) + ' != Server:' + (Buffer.from(getWebCertHash(domain), 'binary').toString('hex').substring(0, 10)) + ' or ' + (Buffer.from(getWebCertFullHash(domain), 'binary').toString('hex').substring(0, 10)) + '), holding connection (' + obj.remoteaddrport + ').'); | ||||
|                         console.log('Agent reported web cert hash:' + (Buffer.from(msg.substring(2, 50), 'binary').toString('hex')) + '.'); | ||||
|                         return; | ||||
|                     } else { | ||||
|                         // The hash matched one of the acceptable values, send the agent web hash back to the agent
 | ||||
|                         // Send 384 bits SHA384 hash of TLS cert + 384 bits nonce
 | ||||
|                         // Command 1, hash + nonce. Use the web hash given by the agent.
 | ||||
|                         obj.sendBinary(common.ShortToStr(1) + agentSeenCerthash + obj.nonce); | ||||
|                     } | ||||
|                 } | ||||
| 
 | ||||
| @ -519,12 +526,6 @@ module.exports.CreateMeshAgent = function (parent, db, ws, req, args, domain) { | ||||
|         obj.close(0); | ||||
|     }); | ||||
| 
 | ||||
|     // Start authenticate the mesh agent by sending a auth nonce & server TLS cert hash.
 | ||||
|     // Send 384 bits SHA384 hash of TLS cert public key + 384 bits nonce
 | ||||
|     if (args.ignoreagenthashcheck !== true) { | ||||
|         obj.sendBinary(common.ShortToStr(1) + getWebCertHash(domain) + obj.nonce); // Command 1, hash + nonce
 | ||||
|     } | ||||
| 
 | ||||
|     // Return the mesh for this device, in some cases, we may auto-create the mesh.
 | ||||
|     function getMeshAutoCreate() { | ||||
|         var mesh = parent.meshes[obj.dbMeshKey]; | ||||
| @ -1045,22 +1046,36 @@ module.exports.CreateMeshAgent = function (parent, db, ws, req, args, domain) { | ||||
|                 try { msgDer = forge.asn1.fromDer(forge.util.createBuffer(msg, 'binary')); } catch (ex) { } | ||||
|                 if (msgDer != null) { | ||||
|                     try { | ||||
|                         var p7 = forge.pkcs7.messageFromAsn1(msgDer); | ||||
|                         var sig = p7.rawCapture.signature; | ||||
|                         const p7 = forge.pkcs7.messageFromAsn1(msgDer); | ||||
|                         const sig = p7.rawCapture.signature; | ||||
| 
 | ||||
|                         // Verify with key hash
 | ||||
|                         var buf = Buffer.from(getWebCertHash(domain) + obj.nonce + obj.agentnonce, 'binary'); | ||||
|                         var verifier = parent.crypto.createVerify('RSA-SHA384'); | ||||
|                         verifier.update(buf); | ||||
|                         verified = verifier.verify(obj.unauth.nodeCertPem, sig, 'binary'); | ||||
|                         if (verified == false) { | ||||
|                         if (verified !== true) { | ||||
|                             // Verify with full hash
 | ||||
|                             buf = Buffer.from(getWebCertFullHash(domain) + obj.nonce + obj.agentnonce, 'binary'); | ||||
|                             verifier = parent.crypto.createVerify('RSA-SHA384'); | ||||
|                             verifier.update(buf); | ||||
|                             verified = verifier.verify(obj.unauth.nodeCertPem, sig, 'binary'); | ||||
|                         } | ||||
|                         if (verified == false) { | ||||
|                         if (verified !== true) { | ||||
|                             // Verify with default key hash
 | ||||
|                             buf = Buffer.from(parent.defaultWebCertificateHash + obj.nonce + obj.agentnonce, 'binary'); | ||||
|                             verifier = parent.crypto.createVerify('RSA-SHA384'); | ||||
|                             verifier.update(buf); | ||||
|                             verified = verifier.verify(obj.unauth.nodeCertPem, sig, 'binary'); | ||||
|                         } | ||||
|                         if (verified !== true) { | ||||
|                             // Verify with default full hash
 | ||||
|                             buf = Buffer.from(parent.defaultWebCertificateFullHash + obj.nonce + obj.agentnonce, 'binary'); | ||||
|                             verifier = parent.crypto.createVerify('RSA-SHA384'); | ||||
|                             verifier.update(buf); | ||||
|                             verified = verifier.verify(obj.unauth.nodeCertPem, sig, 'binary'); | ||||
|                         } | ||||
|                         if (verified !== true) { | ||||
|                             // Not a valid signature
 | ||||
|                             parent.agentStats.invalidPkcsSignatureCount++; | ||||
|                             return false; | ||||
|  | ||||
| @ -27,6 +27,7 @@ | ||||
|     "_AgentPort": 1234, | ||||
|     "_AgentAliasPort": 1234, | ||||
|     "_AgentAliasDNS": "agents.myserver.mydomain.com", | ||||
|     "_AgentPortTls": true, | ||||
|     "_ExactPorts": true, | ||||
|     "_AllowLoginToken": true, | ||||
|     "_AllowFraming": true, | ||||
|  | ||||
							
								
								
									
										55
									
								
								webserver.js
									
									
									
									
									
								
							
							
						
						
									
										55
									
								
								webserver.js
									
									
									
									
									
								
							| @ -116,10 +116,10 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) { | ||||
|     } | ||||
| 
 | ||||
|     // Perform hash on web certificate and agent certificate
 | ||||
|     obj.webCertificateHash = parent.certificateOperations.getPublicKeyHashBinary(obj.certificates.web.cert); | ||||
|     obj.webCertificateHash = obj.defaultWebCertificateHash = parent.certificateOperations.getPublicKeyHashBinary(obj.certificates.web.cert); | ||||
|     obj.webCertificateHashs = { '': obj.webCertificateHash }; | ||||
|     obj.webCertificateHashBase64 = Buffer.from(obj.webCertificateHash, 'binary').toString('base64').replace(/\+/g, '@').replace(/\//g, '$'); | ||||
|     obj.webCertificateFullHash = parent.certificateOperations.getCertHashBinary(obj.certificates.web.cert); | ||||
|     obj.webCertificateFullHash = obj.defaultWebCertificateFullHash = parent.certificateOperations.getCertHashBinary(obj.certificates.web.cert); | ||||
|     obj.webCertificateFullHashs = { '': obj.webCertificateFullHash }; | ||||
|     obj.agentCertificateHashHex = parent.certificateOperations.getPublicKeyHash(obj.certificates.agent.cert); | ||||
|     obj.agentCertificateHashBase64 = Buffer.from(obj.agentCertificateHashHex, 'hex').toString('base64').replace(/\+/g, '@').replace(/\//g, '$'); | ||||
| @ -3974,13 +3974,19 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) { | ||||
| 
 | ||||
|         // Start a second agent-only server if needed
 | ||||
|         if (obj.args.agentport) { | ||||
|             if (obj.args.notls || obj.args.tlsoffload) { | ||||
|             var agentPortTls = true; | ||||
|             if ((obj.args.notls == 1) || (obj.args.notls == true)) { agentPortTls = false; } | ||||
|             if (obj.args.tlsoffload != null) { agentPortTls = false; } | ||||
|             if (typeof obj.args.agentporttls == 'boolean') { agentPortTls = obj.args.agentporttls; } | ||||
|             if (obj.certificates.webdefault == null) { agentPortTls = false; } | ||||
| 
 | ||||
|             if (agentPortTls == false) { | ||||
|                 // Setup the HTTP server without TLS
 | ||||
|                 obj.expressWsAlt = require('express-ws')(obj.agentapp); | ||||
|             } else { | ||||
|                 // Setup the agent HTTP server with TLS, use only TLS 1.2 and higher with perfect forward secrecy (PFS).
 | ||||
|                 const tlsOptions = { cert: obj.certificates.web.cert, key: obj.certificates.web.key, ca: obj.certificates.web.ca, rejectUnauthorized: true, ciphers: "HIGH:TLS_AES_256_GCM_SHA384:TLS_AES_128_GCM_SHA256:TLS_AES_128_CCM_8_SHA256:TLS_AES_128_CCM_SHA256:TLS_CHACHA20_POLY1305_SHA256", secureOptions: constants.SSL_OP_NO_SSLv2 | constants.SSL_OP_NO_SSLv3 | constants.SSL_OP_NO_COMPRESSION | constants.SSL_OP_CIPHER_SERVER_PREFERENCE | constants.SSL_OP_NO_TLSv1 | constants.SSL_OP_NO_TLSv1_1 }; | ||||
|                 if (obj.tlsSniCredentials != null) { tlsOptions.SNICallback = TlsSniCallback; } // We have multiple web server certificate used depending on the domain name
 | ||||
|                 // If TLS is used on the agent port, we always use the default TLS certificate.
 | ||||
|                 const tlsOptions = { cert: obj.certificates.webdefault.cert, key: obj.certificates.webdefault.key, ca: obj.certificates.webdefault.ca, rejectUnauthorized: true, ciphers: "HIGH:TLS_AES_256_GCM_SHA384:TLS_AES_128_GCM_SHA256:TLS_AES_128_CCM_8_SHA256:TLS_AES_128_CCM_SHA256:TLS_CHACHA20_POLY1305_SHA256", secureOptions: constants.SSL_OP_NO_SSLv2 | constants.SSL_OP_NO_SSLv3 | constants.SSL_OP_NO_COMPRESSION | constants.SSL_OP_CIPHER_SERVER_PREFERENCE | constants.SSL_OP_NO_TLSv1 | constants.SSL_OP_NO_TLSv1_1 }; | ||||
|                 obj.tlsAltServer = require('https').createServer(tlsOptions, obj.agentapp); | ||||
|                 obj.tlsAltServer.on('secureConnection', function () { /*console.log('tlsAltServer secureConnection');*/ }); | ||||
|                 obj.tlsAltServer.on('error', function (err) { console.log('tlsAltServer error', err); }); | ||||
| @ -4074,6 +4080,38 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) { | ||||
|             } | ||||
|         }); | ||||
| 
 | ||||
|         if (obj.agentapp) { | ||||
|             // Add HTTP security headers to all responses
 | ||||
|             obj.agentapp.use(function (req, res, next) { | ||||
|                 // Set the real IP address of the request
 | ||||
|                 // If a trusted reverse-proxy is sending us the remote IP address, use it.
 | ||||
|                 const ipex = (req.ip.startsWith('::ffff:')) ? req.ip.substring(7) : req.ip; | ||||
|                 if ( | ||||
|                     (obj.args.trustedproxy === true) || | ||||
|                     ((typeof obj.args.trustedproxy == 'object') && (obj.args.trustedproxy.indexOf(ipex) >= 0)) || | ||||
|                     ((typeof obj.args.tlsoffload == 'object') && (obj.args.tlsoffload.indexOf(ipex) >= 0)) | ||||
|                 ) { | ||||
|                     if (req.headers['cf-connecting-ip']) { // Use CloudFlare IP address if present
 | ||||
|                         req.clientIp = req.headers['cf-connecting-ip'].split(',')[0].trim(); | ||||
|                     } else if (res.headers['x-forwarded-for']) { | ||||
|                         req.clientIp = req.headers['x-forwarded-for'].split(',')[0].trim(); | ||||
|                     } else if (res.headers['x-real-ip']) { | ||||
|                         req.clientIp = req.headers['x-real-ip'].split(',')[0].trim(); | ||||
|                     } else { | ||||
|                         req.clientIp = ipex; | ||||
|                     } | ||||
|                 } else { | ||||
|                     req.clientIp = ipex; | ||||
|                 } | ||||
| 
 | ||||
|                 // Get the domain for this request
 | ||||
|                 const domain = req.xdomain = getDomain(req); | ||||
|                 parent.debug('webrequest', '(' + req.clientIp + ') AgentPort: ' + req.url); | ||||
|                 res.removeHeader('X-Powered-By'); | ||||
|                 return next(); | ||||
|             }); | ||||
|         } | ||||
| 
 | ||||
|         // Setup all HTTP handlers
 | ||||
|         if (parent.multiServer != null) { obj.app.ws('/meshserver.ashx', function (ws, req) { parent.multiServer.CreatePeerInServer(parent.multiServer, ws, req); }); } | ||||
|         for (var i in parent.config.domains) { | ||||
| @ -4392,7 +4430,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) { | ||||
|             } | ||||
| 
 | ||||
|             // Setup the alternative agent-only port
 | ||||
|             if (obj.args.agentport) { | ||||
|             if (obj.agentapp) { | ||||
|                 // Receive mesh agent connections on alternate port
 | ||||
|                 obj.agentapp.ws(url + 'agent.ashx', function (ws, req) { | ||||
|                     var domain = checkAgentIpAddress(ws, req); | ||||
| @ -4737,10 +4775,9 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) { | ||||
|     // Start the ExpressJS web server on agent-only alternative port
 | ||||
|     function StartAltWebServer(port) { | ||||
|         if ((port < 1) || (port > 65535)) return; | ||||
|         var agentAliasPort = null; | ||||
|         if (args.agentaliasport != null) { agentAliasPort = args.agentaliasport; } | ||||
|         if (obj.tlsAltServer != null) { | ||||
|             var agentAliasPort = null; | ||||
|             if (args.aliasport != null) { agentAliasPort = args.aliasport; } | ||||
|             if (args.agentaliasport != null) { agentAliasPort = args.agentaliasport; } | ||||
|             if (obj.args.lanonly == true) { | ||||
|                 obj.tcpAltServer = obj.tlsAltServer.listen(port, function () { console.log('MeshCentral HTTPS agent-only server running on port ' + port + ((agentAliasPort != null) ? (', alias port ' + agentAliasPort) : '') + '.'); }); | ||||
|             } else { | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user