Many updates to how agents connect, agent cert checking and tls on agent port.
Ylian Saint-Hilaire committed
May 22, 2020 at 12:54 UTC
f03fbc54db1715569628c113af41c4e8fec9541d
4 files changed
+79
-21
certoperations.js
+6
-1
@@ -436,6 +436,11 @@ module.exports.CertificateOperations = function (parent) {
436
}
437
}
438
439
+ // If web certificate exist, load it as default. This is useful for agent-only port. Load both certificate and private key
440
+ if (obj.fileExists('webserver-cert-public.crt') && obj.fileExists('webserver-cert-private.key')) {
441
+ r.webdefault = { cert: obj.fileLoad('webserver-cert-public.crt', 'utf8'), key: obj.fileLoad('webserver-cert-private.key', 'utf8') };
442
+ }
443
+
444
if (args.tlsoffload) {
445
// If the web certificate already exist, load it. Load just the certificate since we are in TLS offload situation
446
if (obj.fileExists('webserver-cert-public.crt')) {
@@ -674,7 +679,7 @@ module.exports.CertificateOperations = function (parent) {
679
mpsPrivateKey = r.mps.key;
680
}
681
677
- 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 };
682
+ 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 };
683
684
// Fetch the certificates names for the main certificate
685
var webCertificate = obj.pki.certificateFromPem(r.web.cert);
meshagent.js
+26
-11
@@ -389,10 +389,12 @@ module.exports.CreateMeshAgent = function (parent, db, ws, req, args, domain) {
389
390
if (args.ignoreagenthashcheck === true) {
391
// Send the agent web hash back to the agent
392
+ // Send 384 bits SHA384 hash of TLS cert + 384 bits nonce
393
obj.sendBinary(common.ShortToStr(1) + msg.substring(2, 50) + obj.nonce); // Command 1, hash + nonce. Use the web hash given by the agent.
394
} else {
395
// Check that the server hash matches our own web certificate hash (SHA384)
395
- if ((getWebCertHash(domain) != msg.substring(2, 50)) && (getWebCertFullHash(domain) != msg.substring(2, 50))) {
396
+ const agentSeenCerthash = msg.substring(2, 50);
397
+ if ((getWebCertHash(domain) != agentSeenCerthash) && (getWebCertFullHash(domain) != agentSeenCerthash) && (parent.defaultWebCertificateHash != agentSeenCerthash) && (parent.defaultWebCertificateFullHash != agentSeenCerthash)) {
398
if (parent.parent.supportsProxyCertificatesRequest !== false) {
399
obj.badWebCert = Buffer.from(parent.crypto.randomBytes(16), 'binary').toString('base64');
400
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) {
406
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 + ').');
407
console.log('Agent reported web cert hash:' + (Buffer.from(msg.substring(2, 50), 'binary').toString('hex')) + '.');
408
return;
409
+ } else {
410
+ // The hash matched one of the acceptable values, send the agent web hash back to the agent
411
+ // Send 384 bits SHA384 hash of TLS cert + 384 bits nonce
412
+ // Command 1, hash + nonce. Use the web hash given by the agent.
413
+ obj.sendBinary(common.ShortToStr(1) + agentSeenCerthash + obj.nonce);
414
}
415
}
416
@@ -519,12 +526,6 @@ module.exports.CreateMeshAgent = function (parent, db, ws, req, args, domain) {
526
obj.close(0);
527
});
528
522
- // Start authenticate the mesh agent by sending a auth nonce & server TLS cert hash.
523
- // Send 384 bits SHA384 hash of TLS cert public key + 384 bits nonce
524
- if (args.ignoreagenthashcheck !== true) {
525
- obj.sendBinary(common.ShortToStr(1) + getWebCertHash(domain) + obj.nonce); // Command 1, hash + nonce
526
- }
527
-
529
// Return the mesh for this device, in some cases, we may auto-create the mesh.
530
function getMeshAutoCreate() {
531
var mesh = parent.meshes[obj.dbMeshKey];
@@ -1045,22 +1046,36 @@ module.exports.CreateMeshAgent = function (parent, db, ws, req, args, domain) {
1046
try { msgDer = forge.asn1.fromDer(forge.util.createBuffer(msg, 'binary')); } catch (ex) { }
1047
if (msgDer != null) {
1048
try {
1048
- var p7 = forge.pkcs7.messageFromAsn1(msgDer);
1049
- var sig = p7.rawCapture.signature;
1049
+ const p7 = forge.pkcs7.messageFromAsn1(msgDer);
1050
+ const sig = p7.rawCapture.signature;
1051
1052
// Verify with key hash
1053
var buf = Buffer.from(getWebCertHash(domain) + obj.nonce + obj.agentnonce, 'binary');
1054
var verifier = parent.crypto.createVerify('RSA-SHA384');
1055
verifier.update(buf);
1056
verified = verifier.verify(obj.unauth.nodeCertPem, sig, 'binary');
1056
- if (verified == false) {
1057
+ if (verified !== true) {
1058
// Verify with full hash
1059
buf = Buffer.from(getWebCertFullHash(domain) + obj.nonce + obj.agentnonce, 'binary');
1060
verifier = parent.crypto.createVerify('RSA-SHA384');
1061
verifier.update(buf);
1062
verified = verifier.verify(obj.unauth.nodeCertPem, sig, 'binary');
1063
}
1063
- if (verified == false) {
1064
+ if (verified !== true) {
1065
+ // Verify with default key hash
1066
+ buf = Buffer.from(parent.defaultWebCertificateHash + obj.nonce + obj.agentnonce, 'binary');
1067
+ verifier = parent.crypto.createVerify('RSA-SHA384');
1068
+ verifier.update(buf);
1069
+ verified = verifier.verify(obj.unauth.nodeCertPem, sig, 'binary');
1070
+ }
1071
+ if (verified !== true) {
1072
+ // Verify with default full hash
1073
+ buf = Buffer.from(parent.defaultWebCertificateFullHash + obj.nonce + obj.agentnonce, 'binary');
1074
+ verifier = parent.crypto.createVerify('RSA-SHA384');
1075
+ verifier.update(buf);
1076
+ verified = verifier.verify(obj.unauth.nodeCertPem, sig, 'binary');
1077
+ }
1078
+ if (verified !== true) {
1079
// Not a valid signature
1080
parent.agentStats.invalidPkcsSignatureCount++;
1081
return false;
sample-config-advanced.json
+1
@@ -27,6 +27,7 @@
27
"_AgentPort": 1234,
28
"_AgentAliasPort": 1234,
29
"_AgentAliasDNS": "agents.myserver.mydomain.com",
30
+ "_AgentPortTls": true,
31
"_ExactPorts": true,
32
"_AllowLoginToken": true,
33
"_AllowFraming": true,
webserver.js
+46
-9
@@ -116,10 +116,10 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
116
}
117
118
// Perform hash on web certificate and agent certificate
119
- obj.webCertificateHash = parent.certificateOperations.getPublicKeyHashBinary(obj.certificates.web.cert);
119
+ obj.webCertificateHash = obj.defaultWebCertificateHash = parent.certificateOperations.getPublicKeyHashBinary(obj.certificates.web.cert);
120
obj.webCertificateHashs = { '': obj.webCertificateHash };
121
obj.webCertificateHashBase64 = Buffer.from(obj.webCertificateHash, 'binary').toString('base64').replace(/\+/g, '@').replace(/\//g, '$');
122
- obj.webCertificateFullHash = parent.certificateOperations.getCertHashBinary(obj.certificates.web.cert);
122
+ obj.webCertificateFullHash = obj.defaultWebCertificateFullHash = parent.certificateOperations.getCertHashBinary(obj.certificates.web.cert);
123
obj.webCertificateFullHashs = { '': obj.webCertificateFullHash };
124
obj.agentCertificateHashHex = parent.certificateOperations.getPublicKeyHash(obj.certificates.agent.cert);
125
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) {
3974
3975
// Start a second agent-only server if needed
3976
if (obj.args.agentport) {
3977
- if (obj.args.notls || obj.args.tlsoffload) {
3977
+ var agentPortTls = true;
3978
+ if ((obj.args.notls == 1) || (obj.args.notls == true)) { agentPortTls = false; }
3979
+ if (obj.args.tlsoffload != null) { agentPortTls = false; }
3980
+ if (typeof obj.args.agentporttls == 'boolean') { agentPortTls = obj.args.agentporttls; }
3981
+ if (obj.certificates.webdefault == null) { agentPortTls = false; }
3982
+
3983
+ if (agentPortTls == false) {
3984
// Setup the HTTP server without TLS
3985
obj.expressWsAlt = require('express-ws')(obj.agentapp);
3986
} else {
3987
// Setup the agent HTTP server with TLS, use only TLS 1.2 and higher with perfect forward secrecy (PFS).
3982
- 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 };
3983
- if (obj.tlsSniCredentials != null) { tlsOptions.SNICallback = TlsSniCallback; } // We have multiple web server certificate used depending on the domain name
3988
+ // If TLS is used on the agent port, we always use the default TLS certificate.
3989
+ 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 };
3990
obj.tlsAltServer = require('https').createServer(tlsOptions, obj.agentapp);
3991
obj.tlsAltServer.on('secureConnection', function () { /*console.log('tlsAltServer secureConnection');*/ });
3992
obj.tlsAltServer.on('error', function (err) { console.log('tlsAltServer error', err); });
@@ -4074,6 +4080,38 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
4080
}
4081
});
4082
4083
+ if (obj.agentapp) {
4084
+ // Add HTTP security headers to all responses
4085
+ obj.agentapp.use(function (req, res, next) {
4086
+ // Set the real IP address of the request
4087
+ // If a trusted reverse-proxy is sending us the remote IP address, use it.
4088
+ const ipex = (req.ip.startsWith('::ffff:')) ? req.ip.substring(7) : req.ip;
4089
+ if (
4090
+ (obj.args.trustedproxy === true) ||
4091
+ ((typeof obj.args.trustedproxy == 'object') && (obj.args.trustedproxy.indexOf(ipex) >= 0)) ||
4092
+ ((typeof obj.args.tlsoffload == 'object') && (obj.args.tlsoffload.indexOf(ipex) >= 0))
4093
+ ) {
4094
+ if (req.headers['cf-connecting-ip']) { // Use CloudFlare IP address if present
4095
+ req.clientIp = req.headers['cf-connecting-ip'].split(',')[0].trim();
4096
+ } else if (res.headers['x-forwarded-for']) {
4097
+ req.clientIp = req.headers['x-forwarded-for'].split(',')[0].trim();
4098
+ } else if (res.headers['x-real-ip']) {
4099
+ req.clientIp = req.headers['x-real-ip'].split(',')[0].trim();
4100
+ } else {
4101
+ req.clientIp = ipex;
4102
+ }
4103
+ } else {
4104
+ req.clientIp = ipex;
4105
+ }
4106
+
4107
+ // Get the domain for this request
4108
+ const domain = req.xdomain = getDomain(req);
4109
+ parent.debug('webrequest', '(' + req.clientIp + ') AgentPort: ' + req.url);
4110
+ res.removeHeader('X-Powered-By');
4111
+ return next();
4112
+ });
4113
+ }
4114
+
4115
// Setup all HTTP handlers
4116
if (parent.multiServer != null) { obj.app.ws('/meshserver.ashx', function (ws, req) { parent.multiServer.CreatePeerInServer(parent.multiServer, ws, req); }); }
4117
for (var i in parent.config.domains) {
@@ -4392,7 +4430,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
4430
}
4431
4432
// Setup the alternative agent-only port
4395
- if (obj.args.agentport) {
4433
+ if (obj.agentapp) {
4434
// Receive mesh agent connections on alternate port
4435
obj.agentapp.ws(url + 'agent.ashx', function (ws, req) {
4436
var domain = checkAgentIpAddress(ws, req);
@@ -4737,10 +4775,9 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
4775
// Start the ExpressJS web server on agent-only alternative port
4776
function StartAltWebServer(port) {
4777
if ((port < 1) || (port > 65535)) return;
4778
+ var agentAliasPort = null;
4779
+ if (args.agentaliasport != null) { agentAliasPort = args.agentaliasport; }
4780
if (obj.tlsAltServer != null) {
4741
- var agentAliasPort = null;
4742
- if (args.aliasport != null) { agentAliasPort = args.aliasport; }
4743
- if (args.agentaliasport != null) { agentAliasPort = args.agentaliasport; }
4781
if (obj.args.lanonly == true) {
4782
obj.tcpAltServer = obj.tlsAltServer.listen(port, function () { console.log('MeshCentral HTTPS agent-only server running on port ' + port + ((agentAliasPort != null) ? (', alias port ' + agentAliasPort) : '') + '.'); });
4783
} else {