Fixed many TLS-SNI problems, updated agents

This commit is contained in:
Ylian Saint-Hilaire 2018-11-30 16:42:58 -08:00
parent d30b5f348e
commit 7f76495d72
23 changed files with 103 additions and 36 deletions

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -413,7 +413,7 @@ function createMeshCore(agent) {
if (xurl != null) { if (xurl != null) {
var woptions = http.parseUri(xurl); var woptions = http.parseUri(xurl);
woptions.rejectUnauthorized = 0; woptions.rejectUnauthorized = 0;
//sendConsoleText(JSON.stringify(woptions)); sendConsoleText(JSON.stringify(woptions));
var tunnel = http.request(woptions); var tunnel = http.request(woptions);
tunnel.upgrade = onTunnelUpgrade; tunnel.upgrade = onTunnelUpgrade;
tunnel.onerror = function (e) { sendConsoleText('ERROR: ' + JSON.stringify(e)); } tunnel.onerror = function (e) { sendConsoleText('ERROR: ' + JSON.stringify(e)); }

View File

@ -28,14 +28,26 @@ module.exports.CertificateOperations = function () {
// Return the certificate of the remote HTTPS server // Return the certificate of the remote HTTPS server
obj.loadCertificate = function (url, tag, func) { obj.loadCertificate = function (url, tag, func) {
var u = require('url').parse(url); const u = require('url').parse(url);
if (u.protocol == 'https:') { if (u.protocol == 'https:') {
var tlssocket = obj.tls.connect((u.port ? u.port : 443), u.hostname, { rejectUnauthorized: false }, function () { this.xxcert = this.getPeerCertificate(); this.end(); }); // Read the certificate from HTTPS
const tlssocket = obj.tls.connect((u.port ? u.port : 443), u.hostname, { servername: u.hostname, rejectUnauthorized: false }, function () { this.xxcert = this.getPeerCertificate(); this.end(); });
tlssocket.xxurl = url; tlssocket.xxurl = url;
tlssocket.xxfunc = func; tlssocket.xxfunc = func;
tlssocket.xxtag = tag; tlssocket.xxtag = tag;
tlssocket.on('end', function () { this.xxfunc(this.xxurl, this.xxcert, this.xxtag); }); tlssocket.on('end', function () { this.xxfunc(this.xxurl, this.xxcert.raw.toString('binary'), this.xxtag); });
tlssocket.on('error', function () { this.xxfunc(this.xxurl, null, this.xxtag); }); tlssocket.on('error', function () { this.xxfunc(this.xxurl, null, this.xxtag); });
} else if (u.protocol == 'file:') {
// Read the certificate from a file
obj.fs.readFile(url.substring(7), 'utf8', function (err, data) {
if (err) { func(url, null, tag); return; }
var x1 = data.indexOf('-----BEGIN CERTIFICATE-----'), x2 = data.indexOf('-----END CERTIFICATE-----');
if ((x1 >= 0) && (x2 > x1)) {
func(url, new Buffer(data.substring(x1 + 27, x2), 'base64').toString('binary'), tag);
} else {
func(url, data, tag);
}
});
} else { func(url, null, tag); } } else { func(url, null, tag); }
}; };
@ -45,6 +57,43 @@ module.exports.CertificateOperations = function () {
return obj.pki.getPublicKeyFingerprint(publickey, { encoding: "hex", md: obj.forge.md.sha384.create() }); return obj.pki.getPublicKeyFingerprint(publickey, { encoding: "hex", md: obj.forge.md.sha384.create() });
}; };
// Return the SHA384 hash of the certificate, return hex
obj.getCertHash = function (cert) {
try {
var md = obj.forge.md.sha384.create();
md.update(obj.forge.asn1.toDer(obj.pki.certificateToAsn1(obj.pki.certificateFromPem(cert))).getBytes());
return md.digest().toHex();
} catch (ex) {
// If this is not an RSA certificate, hash the raw PKCS7 out of the PEM file
var x1 = cert.indexOf('-----BEGIN CERTIFICATE-----'), x2 = cert.indexOf('-----END CERTIFICATE-----');
if ((x1 >= 0) && (x2 > x1)) {
return obj.crypto.createHash('sha384').update(new Buffer(cert.substring(x1 + 27, x2), 'base64')).digest('hex');
} else { console.log('ERROR: Unable to decode certificate.'); return null; }
}
};
// Return the SHA384 hash of the certificate public key
obj.getPublicKeyHashBinary = function (cert) {
var publickey = obj.pki.certificateFromPem(cert).publicKey;
return obj.pki.getPublicKeyFingerprint(publickey, { encoding: "binary", md: obj.forge.md.sha384.create() });
};
// Return the SHA384 hash of the certificate, return binary
obj.getCertHashBinary = function (cert) {
try {
// If this is a RSA certificate, we can use Forge to hash the ASN1
var md = obj.forge.md.sha384.create();
md.update(obj.forge.asn1.toDer(obj.pki.certificateToAsn1(obj.pki.certificateFromPem(cert))).getBytes());
return md.digest().getBytes();
} catch (ex) {
// If this is not an RSA certificate, hash the raw PKCS7 out of the PEM file
var x1 = cert.indexOf('-----BEGIN CERTIFICATE-----'), x2 = cert.indexOf('-----END CERTIFICATE-----');
if ((x1 >= 0) && (x2 > x1)) {
return obj.crypto.createHash('sha384').update(new Buffer(cert.substring(x1 + 27, x2), 'base64')).digest('binary');
} else { console.log('ERROR: Unable to decode certificate.'); return null; }
}
};
// Create a self-signed certificate // Create a self-signed certificate
obj.GenerateRootCertificate = function (addThumbPrintToName, commonName, country, organization, strong) { obj.GenerateRootCertificate = function (addThumbPrintToName, commonName, country, organization, strong) {
var keys = obj.pki.rsa.generateKeyPair((strong == true) ? 3072 : 2048); var keys = obj.pki.rsa.generateKeyPair((strong == true) ? 3072 : 2048);

View File

@ -198,7 +198,7 @@ module.exports.CreateMeshAgent = function (parent, db, ws, req, args, domain) {
obj.receivedCommands += 1; // Agent can't send the same command twice on the same connection ever. Block DOS attack path. obj.receivedCommands += 1; // Agent can't send the same command twice on the same connection ever. Block DOS attack path.
// Check that the server hash matches our own web certificate hash (SHA384) // Check that the server hash matches our own web certificate hash (SHA384)
if (getWebCertHash(obj.domain) != msg.substring(2, 50)) { console.log('Agent connected with bad web certificate hash (' + (new Buffer(getWebCertHash(obj.domain), 'binary').toString('hex').substring(0, 10)) + ' != ' + (new Buffer(msg.substring(2, 50), 'binary').toString('hex').substring(0, 10)) + '), holding connection (' + obj.remoteaddrport + ').'); return; } if ((getWebCertHash(obj.domain) != msg.substring(2, 50)) && (getWebCertFullHash(obj.domain) != msg.substring(2, 50))) { console.log('Agent connected with bad web certificate hash (Agent:' + (new Buffer(msg.substring(2, 50), 'binary').toString('hex').substring(0, 10)) + ' != Server:' + (new Buffer(getWebCertHash(obj.domain), 'binary').toString('hex').substring(0, 10)) + ' or ' + (new Buffer(getWebCertFullHash(obj.domain), 'binary').toString('hex').substring(0, 10)) + '), holding connection (' + obj.remoteaddrport + ').'); return; }
// Use our server private key to sign the ServerHash + AgentNonce + ServerNonce // Use our server private key to sign the ServerHash + AgentNonce + ServerNonce
obj.agentnonce = msg.substring(50, 98); obj.agentnonce = msg.substring(50, 98);
@ -411,19 +411,30 @@ module.exports.CreateMeshAgent = function (parent, db, ws, req, args, domain) {
}); });
} }
// Get the web certificate hash for the speficied domain // Get the web certificate private key hash for the specified domain
function getWebCertHash(domain) { function getWebCertHash(domain) {
var hash = obj.parent.webCertificateHashs[domain.id]; var hash = obj.parent.webCertificateHashs[domain.id];
if (hash != null) return hash; if (hash != null) return hash;
return obj.parent.webCertificateHash; return obj.parent.webCertificateHash;
} }
// Get the web certificate hash for the specified domain
function getWebCertFullHash(domain) {
var hash = obj.parent.webCertificateFullHashs[domain.id];
if (hash != null) return hash;
return obj.parent.webCertificateFullHash;
}
// Verify the agent signature // Verify the agent signature
function processAgentSignature(msg) { function processAgentSignature(msg) {
// Verify the signature. This is the fast way, without using forge. // Verify the signature. This is the fast way, without using forge.
const verify = obj.parent.crypto.createVerify('SHA384'); const verify = obj.parent.crypto.createVerify('SHA384');
verify.end(new Buffer(getWebCertHash(obj.domain) + obj.nonce + obj.agentnonce, 'binary')); verify.end(new Buffer(getWebCertHash(obj.domain) + obj.nonce + obj.agentnonce, 'binary')); // Test using the private key hash
if (verify.verify(obj.unauth.nodeCertPem, new Buffer(msg, 'binary')) !== true) { return false; } if (verify.verify(obj.unauth.nodeCertPem, new Buffer(msg, 'binary')) !== true) {
const verify2 = obj.parent.crypto.createVerify('SHA384');
verify2.end(new Buffer(getWebCertFullHash(obj.domain) + obj.nonce + obj.agentnonce, 'binary')); // Test using the full cert hash
if (verify2.verify(obj.unauth.nodeCertPem, new Buffer(msg, 'binary')) !== true) { return false; }
}
// Connection is a success, clean up // Connection is a success, clean up
obj.nodeid = obj.unauth.nodeid; obj.nodeid = obj.unauth.nodeid;

View File

@ -417,22 +417,23 @@ function CreateMeshCentralServer(config, args) {
webCertLoadCount++; webCertLoadCount++;
obj.certificateOperations.loadCertificate(obj.config.domains[i].certurl, obj.config.domains[i], function (url, cert, xdomain) { obj.certificateOperations.loadCertificate(obj.config.domains[i].certurl, obj.config.domains[i], function (url, cert, xdomain) {
if (cert != null) { if (cert != null) {
// Hash the entire cert
var hash = obj.crypto.createHash('sha384').update(cert).digest('hex');
if (xdomain.certhash != hash) {
xdomain.certkeyhash = hash;
xdomain.certhash = hash;
}
try { try {
// Decode a RSA certificate and hash the public key // Decode a RSA certificate and hash the public key, if this is not RSA, skip this.
var forgeCert = obj.certificateOperations.forge.pki.certificateFromAsn1(obj.certificateOperations.forge.asn1.fromDer(cert.raw.toString('binary'))); var forgeCert = obj.certificateOperations.forge.pki.certificateFromAsn1(obj.certificateOperations.forge.asn1.fromDer(cert));
var hash = obj.certificateOperations.forge.pki.getPublicKeyFingerprint(forgeCert.publicKey, { md: obj.certificateOperations.forge.md.sha384.create(), encoding: 'hex' }); xdomain.certkeyhash = obj.certificateOperations.forge.pki.getPublicKeyFingerprint(forgeCert.publicKey, { md: obj.certificateOperations.forge.md.sha384.create(), encoding: 'hex' });
if (xdomain.certhash != hash) { console.log('V1: ' + xdomain.certkeyhash);
xdomain.certhash = hash; } catch (ex) { }
console.log('Loaded RSA web certificate at ' + url + ', SHA384: ' + xdomain.certhash + '.');
} console.log('Loaded web certificate from ' + url);
} catch (ex) { console.log(' SHA384 cert hash: ' + xdomain.certhash);
// This may be a ECDSA certificate, hash the entire cert if (xdomain.certhash != xdomain.certkeyhash) { console.log(' SHA384 key hash: ' + xdomain.certkeyhash); }
var hash = obj.crypto.createHash('sha384').update(cert.raw).digest('hex');
if (xdomain.certhash != hash) {
xdomain.certhash = hash;
console.log('Loaded non-RSA web certificate at ' + url + ', SHA384: ' + xdomain.certhash + '.');
}
}
} else { } else {
console.log('Failed to load web certificate at: ' + url); console.log('Failed to load web certificate at: ' + url);
} }

View File

@ -1,6 +1,6 @@
{ {
"name": "meshcentral", "name": "meshcentral",
"version": "0.2.3-r", "version": "0.2.3-v",
"keywords": [ "keywords": [
"Remote Management", "Remote Management",
"Intel AMT", "Intel AMT",

View File

@ -102,33 +102,39 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
} }
// Perform hash on web certificate and agent certificate // Perform hash on web certificate and agent certificate
obj.webCertificateHash = 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.webCertificateHash = parent.certificateOperations.getPublicKeyHashBinary(obj.certificates.web.cert);
obj.webCertificateHashs = { '': obj.webCertificateHash }; obj.webCertificateHashs = { '': obj.webCertificateHash };
obj.webCertificateHashBase64 = new Buffer(parent.certificateOperations.forge.pki.getPublicKeyFingerprint(parent.certificateOperations.forge.pki.certificateFromPem(obj.certificates.web.cert).publicKey, { md: parent.certificateOperations.forge.md.sha384.create(), encoding: 'binary' }), 'binary').toString('base64').replace(/\+/g, '@').replace(/\//g, '$'); obj.webCertificateHashBase64 = new Buffer(obj.webCertificateHash, 'binary').toString('base64').replace(/\+/g, '@').replace(/\//g, '$');
obj.agentCertificateHashHex = 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.webCertificateFullHash = parent.certificateOperations.getCertHashBinary(obj.certificates.web.cert);
obj.agentCertificateHashBase64 = new Buffer(parent.certificateOperations.forge.pki.getPublicKeyFingerprint(parent.certificateOperations.forge.pki.certificateFromPem(obj.certificates.agent.cert).publicKey, { md: parent.certificateOperations.forge.md.sha384.create(), encoding: 'binary' }), 'binary').toString('base64').replace(/\+/g, '@').replace(/\//g, '$'); obj.webCertificateFullHashs = { '': obj.webCertificateFullHash };
obj.webCertificateFullHashBase64 = new Buffer(obj.webCertificateFullHash, 'binary').toString('base64').replace(/\+/g, '@').replace(/\//g, '$');
obj.agentCertificateHashHex = parent.certificateOperations.getPublicKeyHash(obj.certificates.agent.cert);
obj.agentCertificateHashBase64 = new Buffer(obj.agentCertificateHashHex, 'hex').toString('base64').replace(/\+/g, '@').replace(/\//g, '$');
obj.agentCertificateAsn1 = parent.certificateOperations.forge.asn1.toDer(parent.certificateOperations.forge.pki.certificateToAsn1(parent.certificateOperations.forge.pki.certificateFromPem(parent.certificates.agent.cert))).getBytes(); obj.agentCertificateAsn1 = parent.certificateOperations.forge.asn1.toDer(parent.certificateOperations.forge.pki.certificateToAsn1(parent.certificateOperations.forge.pki.certificateFromPem(parent.certificates.agent.cert))).getBytes();
// Compute the hash of all of the web certificates for each domain // Compute the hash of all of the web certificates for each domain
for (var i in obj.parent.config.domains) { for (var i in obj.parent.config.domains) {
if (obj.parent.config.domains[i].certhash != null) { if (obj.parent.config.domains[i].certhash != null) {
// If the web certificate hash is provided, use it. // If the web certificate hash is provided, use it.
obj.webCertificateHashs[i] = new Buffer(obj.parent.config.domains[i].certhash, 'hex').toString('binary'); obj.webCertificateHashs[i] = obj.webCertificateFullHashs[i] = new Buffer(obj.parent.config.domains[i].certhash, 'hex').toString('binary');
if (obj.parent.config.domains[i].certkeyhash != null) { obj.webCertificateHashs[i] = new Buffer(obj.parent.config.domains[i].certkeyhash, 'hex').toString('binary'); }
} else if ((obj.parent.config.domains[i].dns != null) && (obj.parent.config.domains[i].certs != null)) { } else if ((obj.parent.config.domains[i].dns != null) && (obj.parent.config.domains[i].certs != null)) {
// If the domain has a different DNS name, use a different certificate hash. // If the domain has a different DNS name, use a different certificate hash.
// Hash the full certificate
obj.webCertificateFullHashs[i] = parent.certificateOperations.getCertHashBinary(obj.parent.config.domains[i].certs.cert);
try { try {
// Decode a RSA certificate and hash the public key // Decode a RSA certificate and hash the public key.
obj.webCertificateHashs[i] = parent.certificateOperations.forge.pki.getPublicKeyFingerprint(parent.certificateOperations.forge.pki.certificateFromPem(obj.parent.config.domains[i].certs.cert).publicKey, { md: parent.certificateOperations.forge.md.sha384.create(), encoding: 'binary' }); obj.webCertificateHashs[i] = parent.certificateOperations.getPublicKeyHashBinary(obj.parent.config.domains[i].certs.cert);
} catch (ex) { } catch (ex) {
// This may be a ECDSA certificate, hash the entire cert // This may be a ECDSA certificate, hash the entire cert.
var x1 = obj.parent.config.domains[i].certs.cert.indexOf('-----BEGIN CERTIFICATE-----'), x2 = obj.parent.config.domains[i].certs.cert.indexOf('-----END CERTIFICATE-----'); obj.webCertificateHashs[i] = obj.webCertificateFullHashs[i];
if ((x1 >= 0) && (x2 > x1)) {
obj.webCertificateHashs[i] = obj.crypto.createHash('sha384').update(new Buffer(obj.parent.config.domains[i].certs.cert.substring(x1 + 27, x2), 'base64')).digest('binary');
} else { console.log('ERROR: Unable to decode certificate for domain "' + i + '".'); }
} }
} }
} }
//console.log(new Buffer(obj.webCertificateHashs['devtest'], 'binary').toString('hex'));
//console.log(new Buffer(obj.webCertificateFullHashs['devtest'], 'binary').toString('hex'));
// If we are running the legacy swarm server, compute the hash for that certificate // If we are running the legacy swarm server, compute the hash for that certificate
if (parent.certificates.swarmserver != null) { if (parent.certificates.swarmserver != null) {
obj.swarmCertificateAsn1 = parent.certificateOperations.forge.asn1.toDer(parent.certificateOperations.forge.pki.certificateToAsn1(parent.certificateOperations.forge.pki.certificateFromPem(parent.certificates.swarmserver.cert))).getBytes(); obj.swarmCertificateAsn1 = parent.certificateOperations.forge.asn1.toDer(parent.certificateOperations.forge.pki.certificateToAsn1(parent.certificateOperations.forge.pki.certificateFromPem(parent.certificates.swarmserver.cert))).getBytes();