diff --git a/meshagent.js b/meshagent.js index e6c68001..5a5136ac 100644 --- a/meshagent.js +++ b/meshagent.js @@ -426,8 +426,8 @@ module.exports.CreateMeshAgent = function (parent, db, ws, req, args, domain) { 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) - const agentSeenCerthash = msg.substring(2, 50); - if ((getWebCertHash(domain) != agentSeenCerthash) && (getWebCertFullHash(domain) != agentSeenCerthash) && (parent.defaultWebCertificateHash != agentSeenCerthash) && (parent.defaultWebCertificateFullHash != agentSeenCerthash)) { + obj.agentSeenCerthash = msg.substring(2, 50); + if ((getWebCertHash(domain) != obj.agentSeenCerthash) && (getWebCertFullHash(domain) != obj.agentSeenCerthash) && (parent.defaultWebCertificateHash != obj.agentSeenCerthash) && (parent.defaultWebCertificateFullHash != obj.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. @@ -444,7 +444,7 @@ module.exports.CreateMeshAgent = function (parent, db, ws, req, args, domain) { // 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); + obj.sendBinary(common.ShortToStr(1) + obj.agentSeenCerthash + obj.nonce); } } @@ -1078,7 +1078,8 @@ module.exports.CreateMeshAgent = function (parent, db, ws, req, args, domain) { if (isIgnoreHashCheck() == false) { var verified = false; - if (msg.length != 384) { + // Raw RSA signatures have an exact length of 256 or 384. PKCS7 is larger. + if ((msg.length != 384) && (msg.length != 256)) { // Verify a PKCS7 signature. var msgDer = null; try { msgDer = forge.asn1.fromDer(forge.util.createBuffer(msg, 'binary')); } catch (ex) { } @@ -1088,31 +1089,10 @@ module.exports.CreateMeshAgent = function (parent, db, ws, req, args, domain) { const sig = p7.rawCapture.signature; // Verify with key hash - var buf = Buffer.from(getWebCertHash(domain) + obj.nonce + obj.agentnonce, 'binary'); + var buf = Buffer.from(obj.agentSeenCerthash + 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 !== 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 !== 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++; @@ -1126,15 +1106,11 @@ module.exports.CreateMeshAgent = function (parent, db, ws, req, args, domain) { if (verified == false) { // Verify the RSA signature. This is the fast way, without using forge. const verify = parent.crypto.createVerify('SHA384'); - verify.end(Buffer.from(getWebCertHash(domain) + obj.nonce + obj.agentnonce, 'binary')); // Test using the private key hash + verify.end(Buffer.from(obj.agentSeenCerthash + obj.nonce + obj.agentnonce, 'binary')); // Test using the private key hash if (verify.verify(obj.unauth.nodeCertPem, Buffer.from(msg, 'binary')) !== true) { - const verify2 = parent.crypto.createVerify('SHA384'); - verify2.end(Buffer.from(getWebCertFullHash(domain) + obj.nonce + obj.agentnonce, 'binary')); // Test using the full cert hash - if (verify2.verify(obj.unauth.nodeCertPem, Buffer.from(msg, 'binary')) !== true) { - parent.agentStats.invalidRsaSignatureCount++; - parent.setAgentIssue(obj, "invalidRsaSignature"); - return false; - } + parent.agentStats.invalidRsaSignatureCount++; + parent.setAgentIssue(obj, "invalidRsaSignature"); + return false; } } } @@ -1146,6 +1122,7 @@ module.exports.CreateMeshAgent = function (parent, db, ws, req, args, domain) { delete obj.agentnonce; delete obj.unauth; delete obj.receivedCommands; + delete obj.agentSeenCerthash; if (obj.unauthsign) delete obj.unauthsign; parent.agentStats.verifiedAgentConnectionCount++; parent.parent.debug('agent', 'Verified agent connection to ' + obj.nodeid + ' (' + obj.remoteaddrport + ').'); diff --git a/meshcentral.js b/meshcentral.js index 5ebd1361..61dfb31f 100644 --- a/meshcentral.js +++ b/meshcentral.js @@ -2572,7 +2572,7 @@ function CreateMeshCentralServer(config, args) { onZipData.x.zdata = concatData; onZipData.x.zsize = concatData.length; - console.log('Packed', onZipData.x.size, onZipData.x.zsize); + //console.log('Packed', onZipData.x.size, onZipData.x.zsize); } const onZipError = function onZipError() { delete onZipData.x.zacc; } obj.meshAgentBinaries[archid].zacc = []; @@ -2598,6 +2598,13 @@ function CreateMeshCentralServer(config, args) { var options = { sourcePath: agentpath, targetStream: hashStream, platform: obj.meshAgentsArchitectureNumbers[archid].platform }; if (obj.meshAgentBinaries[archid].pe != null) { options.peinfo = obj.meshAgentBinaries[archid].pe; } obj.exeHandler.hashExecutableFile(options); + + // If we are not loading Windows binaries to RAM, compute the RAW file hash of the signed binaries here. + if ((obj.args.agentsinram === false) && ((archid == 3) || (archid == 4))) { + var hash = obj.crypto.createHash('sha384').update(obj.fs.readFileSync(agentpath)); + obj.meshAgentBinaries[archid].fileHash = hash.digest('binary'); + obj.meshAgentBinaries[archid].fileHashHex = Buffer.from(obj.meshAgentBinaries[archid].fileHash, 'binary').toString('hex'); + } } } if ((obj.meshAgentBinaries[3] == null) && (obj.meshAgentBinaries[10003] != null)) { obj.meshAgentBinaries[3] = obj.meshAgentBinaries[10003]; } // If only the unsigned windows binaries are present, use them. diff --git a/meshuser.js b/meshuser.js index 65643e39..3f5e6e03 100644 --- a/meshuser.js +++ b/meshuser.js @@ -931,6 +931,8 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use r += 'AgentCertHash: ' + parent.agentCertificateHashHex; for (var i in parent.webCertificateHashs) { r += '\r\nwebCertificateHash (' + i + '): ' + common.rstr2hex(parent.webCertificateHashs[i]); } for (var i in parent.webCertificateFullHashs) { r += '\r\nwebCertificateFullHash (' + i + '): ' + common.rstr2hex(parent.webCertificateFullHashs[i]); } + r += '\r\ndefaultWebCertificateHash: ' + common.rstr2hex(parent.defaultWebCertificateHash); + r += '\r\ndefaultWebCertificateFullHash: ' + common.rstr2hex(parent.defaultWebCertificateFullHash); break; } case 'amtacm': { diff --git a/views/default.handlebars b/views/default.handlebars index e37a131b..d7bcfda0 100644 --- a/views/default.handlebars +++ b/views/default.handlebars @@ -1945,14 +1945,13 @@ // Return the number of 2nd factor for this account function count2factoraAuths() { + if (userinfo == null) return 0; var authFactorCount = 0; - if (userinfo != null) { - if (userinfo.otpsecret == 1) { authFactorCount++; } // Authenticator time factor - if (userinfo.otphkeys != null) { authFactorCount += userinfo.otphkeys; } // FIDO hardware factor - } + if (userinfo.otpsecret == 1) { authFactorCount++; } // Authenticator time factor + if (userinfo.otphkeys > 0) { authFactorCount += userinfo.otphkeys; } // FIDO hardware factor if ((features & 0x00800000) && (userinfo.otpekey == 1)) { authFactorCount++; } // EMail factor - if ((features & 0x04000000) && (userinfo.phone != null)) { authFactorCount++; } // SMS factor - if ((authFactorCount > 0) && (userinfo.otpkeys == 1)) { authFactorCount++; } // Backup keys + if ((features & 0x02000000) && (features & 0x04000000) && (userinfo.phone != null)) { authFactorCount++; } // SMS factor + if ((authFactorCount > 0) && (userinfo.otpkeys > 0)) { authFactorCount++; } // Backup keys return authFactorCount; } @@ -9786,7 +9785,7 @@ if ((userinfo.emailVerified !== true) && (serverinfo.emailcheck == true) && (userinfo.siteadmin != 0xFFFFFFFF)) { setDialogMode(2, "Account Security", 1, null, "Unable to access this feature until a email address is verified. This is required for password recovery. Go to the \"My Account\" tab to change and verify an email address."); return false; } // Remind the user to add two factor authentication - if ((features & 0x00040000) && !((userinfo.otpsecret == 1) || (userinfo.otphkeys > 0) || (userinfo.otpkeys > 0) || ((features & 0x02000000) && (features & 0x04000000) && (userinfo.phone != null)) || ((features & 0x00800000) && (userinfo.otpekey == 1)))) { setDialogMode(2, "Account Security", 1, null, "Unable to access this feature until two-factor authentication is enabled. This is required for extra security. Go to the \"My Account\" tab and look at the \"Account Security\" section."); return false; } + if ((features & 0x00040000) && (count2factoraAuths() == 0)) { setDialogMode(2, "Account Security", 1, null, "Unable to access this feature until two-factor authentication is enabled. This is required for extra security. Go to the \"My Account\" tab and look at the \"Account Security\" section."); return false; } // We are allowed, let's prompt to information var x = "Create a new device group using the options below." + '

'; diff --git a/webserver.js b/webserver.js index ffb604b9..a23aa3b4 100644 --- a/webserver.js +++ b/webserver.js @@ -108,14 +108,16 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) { } // Perform hash on web certificate and agent certificate - obj.webCertificateHash = obj.defaultWebCertificateHash = parent.certificateOperations.getPublicKeyHashBinary(obj.certificates.web.cert); + obj.webCertificateHash = 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 = obj.defaultWebCertificateFullHash = parent.certificateOperations.getCertHashBinary(obj.certificates.web.cert); + obj.webCertificateFullHash = 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, '$'); obj.agentCertificateAsn1 = parent.certificateOperations.forge.asn1.toDer(parent.certificateOperations.forge.pki.certificateToAsn1(parent.certificateOperations.forge.pki.certificateFromPem(parent.certificates.agent.cert))).getBytes(); + obj.defaultWebCertificateHash = parent.certificateOperations.getPublicKeyHashBinary(obj.certificates.webdefault.cert); + obj.defaultWebCertificateFullHash = parent.certificateOperations.getCertHashBinary(obj.certificates.webdefault.cert); // Compute the hash of all of the web certificates for each domain for (var i in obj.parent.config.domains) {