Integration of agent code signing into MeshCentral.

Ylian Saint-Hilaire committed May 28, 2022 at 22:34 UTC 82254b80ed041a73ab7a7d9f554fefb3473e05a3
4 files changed +200 -136
agents/MeshService-signed.exe
Binary files a/agents/MeshService-signed.exe and /dev/null differ
agents/MeshService64-signed.exe
Binary files a/agents/MeshService64-signed.exe and /dev/null differ
authenticode.js
+32 -16
@@ -6,6 +6,13 @@
6 * @version v0.0.1
7 */
8
9 +/*jslint node: true */
10 +/*jshint node: true */
11 +/*jshint strict:false */
12 +/*jshint -W097 */
13 +/*jshint esversion: 6 */
14 +"use strict";
15 +
16 const fs = require('fs');
17 const crypto = require('crypto');
18 const forge = require('node-forge');
@@ -109,29 +116,29 @@ function createAuthenticodeHandler(path) {
116
117 // Open the file and read header information
118 function openFile() {
112 - if (obj.fd != null) return;
119 + if (obj.fd != null) return true;
120
121 // Open the file descriptor
122 obj.path = path;
116 - obj.fd = fs.openSync(path);
123 + try { obj.fd = fs.openSync(path); } catch (ex) { return false; } // Unable to open file
124 obj.stats = fs.fstatSync(obj.fd);
125 obj.filesize = obj.stats.size;
119 - if (obj.filesize < 64) { throw ('File too short.'); }
126 + if (obj.filesize < 64) { obj.close(); return false; } // File too short.
127
128 // Read the PE header size
129 var buf = readFileSlice(60, 4);
130 obj.header.header_size = buf.readUInt32LE(0);
131
132 // Check file size and PE header
126 - if (obj.filesize < (160 + obj.header.header_size)) { throw ('Invalid SizeOfHeaders.'); }
127 - if (readFileSlice(obj.header.header_size, 4).toString('hex') != '50450000') { throw ('Invalid PE File.'); }
133 + if (obj.filesize < (160 + obj.header.header_size)) { obj.close(); return false; } // Invalid SizeOfHeaders.
134 + if (readFileSlice(obj.header.header_size, 4).toString('hex') != '50450000') { obj.close(); return false; } // Invalid PE File.
135
136 // Check header magic data
137 var magic = readFileSlice(obj.header.header_size + 24, 2).readUInt16LE(0);
138 switch (magic) {
139 case 0x20b: obj.header.pe32plus = 1; break;
140 case 0x10b: obj.header.pe32plus = 0; break;
134 - default: throw ('Invalid Magic in PE');
141 + default: { obj.close(); return false; } // Invalid Magic in PE
142 }
143
144 // Read PE header information
@@ -146,7 +153,7 @@ function createAuthenticodeHandler(path) {
153 // Read signature block
154
155 // Check if the file size allows for the signature block
149 - if (obj.filesize < (obj.header.sigpos + obj.header.siglen)) { throw ('Executable file too short to contain the signature block.'); }
156 + if (obj.filesize < (obj.header.sigpos + obj.header.siglen)) { obj.close(); return false; } // Executable file too short to contain the signature block.
157
158 // Remove the padding if needed
159 var i, pkcs7raw = readFileSlice(obj.header.sigpos + 8, obj.header.siglen - 8);
@@ -207,12 +214,13 @@ function createAuthenticodeHandler(path) {
214 obj.fileHashSigned = Buffer.from(pkcs7content.value[1].value[1].value, 'binary')
215
216 // Compute the actual file hash
210 - if (obj.fileHashAlgo != null) { obj.fileHashActual = getHash(obj.fileHashAlgo); }
217 + if (obj.fileHashAlgo != null) { obj.fileHashActual = obj.getHash(obj.fileHashAlgo); }
218 }
219 + return true;
220 }
221
222 // Hash the file using the selected hashing system
215 - function getHash(algo) {
223 + obj.getHash = function(algo) {
224 var hash = crypto.createHash(algo);
225 runHash(hash, 0, obj.header.header_size + 88);
226 runHash(hash, obj.header.header_size + 88 + 4, obj.header.header_size + 152 + (obj.header.pe32plus * 16));
@@ -229,7 +237,7 @@ function createAuthenticodeHandler(path) {
237 // Sign the file using the certificate and key. If none is specified, generate a dummy one
238 obj.sign = function (cert, args) {
239 if (cert == null) { cert = createSelfSignedCert({ cn: 'Test' }); }
232 - var fileHash = getHash('sha384');
240 + var fileHash = obj.getHash('sha384');
241
242 // Create the signature block
243 var p7 = forge.pkcs7.createSignedData();
@@ -246,8 +254,12 @@ function createAuthenticodeHandler(path) {
254 { type: forge.pki.oids.messageDigest } // This value will populated at signing time by node-forge
255 ]
256 if ((typeof args.desc == 'string') || (typeof args.url == 'string')) {
249 - var codeSigningAttributes = { 'tagClass': 0, 'type': 16, 'constructed': true, 'composed': true, 'value': [ ] };
250 - if (args.desc != null) { codeSigningAttributes.value.push({ 'tagClass': 128, 'type': 0, 'constructed': true, 'composed': true, 'value': [{ 'tagClass': 128, 'type': 0, 'constructed': false, 'composed': false, 'value': Buffer.from(args.desc, 'ucs2').toString() }] }); }
257 + var codeSigningAttributes = { 'tagClass': 0, 'type': 16, 'constructed': true, 'composed': true, 'value': [] };
258 + if (args.desc != null) { // Encode description as big-endian unicode.
259 + var desc = "", ucs = Buffer.from(args.desc, 'ucs2').toString()
260 + for (var k = 0; k < ucs.length; k += 2) { desc += String.fromCharCode(ucs.charCodeAt(k + 1), ucs.charCodeAt(k)); }
261 + codeSigningAttributes.value.push({ 'tagClass': 128, 'type': 0, 'constructed': true, 'composed': true, 'value': [{ 'tagClass': 128, 'type': 0, 'constructed': false, 'composed': false, 'value': desc }] });
262 + }
263 if (args.url != null) { codeSigningAttributes.value.push({ 'tagClass': 128, 'type': 1, 'constructed': true, 'composed': true, 'value': [{ 'tagClass': 128, 'type': 0, 'constructed': false, 'composed': false, 'value': args.url }] }); }
264 authenticatedAttributes.push({ type: obj.Oids.SPC_SP_OPUS_INFO_OBJID, value: codeSigningAttributes });
265 }
@@ -330,8 +342,8 @@ function createAuthenticodeHandler(path) {
342 fs.closeSync(output);
343 }
344
333 - openFile();
334 - return obj;
345 + // Return null if we could not open the file
346 + return (openFile() ? obj : null);
347 }
348
349 function start() {
@@ -410,7 +422,7 @@ function start() {
422 if (typeof args.exe != 'string') { console.log("Missing --exe [filename]"); return; }
423 createOutFile(args, args.exe);
424 const cert = loadCertificates(args);
413 - if (cert == null) { console.log("Unable to load certificate and/or private key, generating text certificate."); }
425 + if (cert == null) { console.log("Unable to load certificate and/or private key, generating test certificate."); }
426 console.log("Signing to " + args.out); exe.sign(cert, args); console.log("Done.");
427 }
428 if (command == 'unsign') { // Unsign an executable
@@ -433,4 +445,8 @@ function start() {
445 if (exe != null) { exe.close(); }
446 }
447
436 -start();
\ No newline at end of file
448 +// If this is the main module, run the command line version
449 +if (require.main === module) { start(); }
450 +
451 +// Exports
452 +module.exports.createAuthenticodeHandler = createAuthenticodeHandler;
\ No newline at end of file
meshcentral.js
+168 -120
@@ -1613,11 +1613,11 @@ function CreateMeshCentralServer(config, args) {
1613 } catch (ex) { }
1614
1615 // Load any domain specific agents
1616 - for (var i in obj.config.domains) { if (i != '') { obj.updateMeshAgentsTable(obj.config.domains[i], function () { }); } }
1616 + for (var i in obj.config.domains) { if ((i != '') && (obj.config.domains[i].share == null)) { obj.updateMeshAgentsTable(obj.config.domains[i], function () { }); } }
1617
1618 // Load the list of mesh agents and install scripts
1619 if ((obj.args.noagentupdate == 1) || (obj.args.noagentupdate == true)) { for (i in obj.meshAgentsArchitectureNumbers) { obj.meshAgentsArchitectureNumbers[i].update = false; } }
1620 - obj.updateMeshAgentsTable(null, function () {
1620 + obj.updateMeshAgentsTable(obj.config.domains[''], function () {
1621 obj.updateMeshAgentInstallScripts();
1622
1623 // Setup and start the web server
@@ -2801,8 +2801,8 @@ function CreateMeshCentralServer(config, args) {
2801 0: { id: 0, localname: 'Unknown', rname: 'meshconsole.exe', desc: 'Unknown agent', update: false, amt: true, platform: 'unknown', core: 'linux-noamt', rcore: 'linux-recovery', arcore: 'linux-agentrecovery', tcore: 'linux-tiny' },
2802 1: { id: 1, localname: 'MeshConsole.exe', rname: 'meshconsole32.exe', desc: 'Windows x86-32 console', update: true, amt: true, platform: 'win32', core: 'windows-amt', rcore: 'windows-recovery', arcore: 'windows-agentrecovery', tcore: 'windows-tiny' },
2803 2: { id: 2, localname: 'MeshConsole64.exe', rname: 'meshconsole64.exe', desc: 'Windows x86-64 console', update: true, amt: true, platform: 'win32', core: 'windows-amt', rcore: 'windows-recovery', arcore: 'windows-agentrecovery', tcore: 'windows-tiny' },
2804 - 3: { id: 3, localname: 'MeshService-signed.exe', rname: 'meshagent32.exe', desc: 'Windows x86-32 service', update: true, amt: true, platform: 'win32', core: 'windows-amt', rcore: 'windows-recovery', arcore: 'windows-agentrecovery', tcore: 'windows-tiny' },
2805 - 4: { id: 4, localname: 'MeshService64-signed.exe', rname: 'meshagent64.exe', desc: 'Windows x86-64 service', update: true, amt: true, platform: 'win32', core: 'windows-amt', rcore: 'windows-recovery', arcore: 'windows-agentrecovery', tcore: 'windows-tiny' },
2804 + 3: { id: 3, localname: 'MeshService.exe', rname: 'meshagent32.exe', desc: 'Windows x86-32 service', update: true, amt: true, platform: 'win32', core: 'windows-amt', rcore: 'windows-recovery', arcore: 'windows-agentrecovery', tcore: 'windows-tiny', codesign: true },
2805 + 4: { id: 4, localname: 'MeshService64.exe', rname: 'meshagent64.exe', desc: 'Windows x86-64 service', update: true, amt: true, platform: 'win32', core: 'windows-amt', rcore: 'windows-recovery', arcore: 'windows-agentrecovery', tcore: 'windows-tiny', codesign: true },
2806 5: { id: 5, localname: 'meshagent_x86', rname: 'meshagent', desc: 'Linux x86-32', update: true, amt: true, platform: 'linux', core: 'linux-amt', rcore: 'linux-recovery', arcore: 'linux-agentrecovery', tcore: 'linux-tiny' },
2807 6: { id: 6, localname: 'meshagent_x86-64', rname: 'meshagent', desc: 'Linux x86-64', update: true, amt: true, platform: 'linux', core: 'linux-amt', rcore: 'linux-recovery', arcore: 'linux-agentrecovery', tcore: 'linux-tiny' },
2808 7: { id: 7, localname: 'meshagent_mips', rname: 'meshagent', desc: 'Linux MIPS', update: true, amt: false, platform: 'linux', core: 'linux-noamt', rcore: 'linux-recovery', arcore: 'linux-agentrecovery', tcore: 'linux-tiny' },
@@ -2837,8 +2837,6 @@ function CreateMeshCentralServer(config, args) {
2837 37: { id: 37, localname: 'meshagent_openbsd_x86-64', rname: 'meshagent', desc: 'OpenBSD x86-64', update: true, amt: false, platform: 'linux', core: 'linux-noamt', rcore: 'linux-recovery', arcore: 'linux-agentrecovery', tcore: 'linux-tiny' }, // OpenBSD x86-64
2838 40: { id: 40, localname: 'meshagent_mipsel24kc', rname: 'meshagent', desc: 'Linux MIPSEL24KC (OpenWRT)', update: true, amt: false, platform: 'linux', core: 'linux-noamt', rcore: 'linux-recovery', arcore: 'linux-agentrecovery', tcore: 'linux-tiny' }, // MIPS Router with OpenWRT
2839 41: { id: 41, localname: 'meshagent_aarch64-cortex-a53', rname: 'meshagent', desc: 'ARMADA/CORTEX-A53/MUSL (OpenWRT)', update: true, amt: false, platform: 'linux', core: 'linux-noamt', rcore: 'linux-recovery', arcore: 'linux-agentrecovery', tcore: 'linux-tiny' }, // OpenWRT Routers
2840 - 10003: { id: 3, localname: 'MeshService.exe', rname: 'meshagent.exe', desc: 'Win x86-32 service, unsigned', update: true, amt: true, platform: 'win32', core: 'windows-amt', rcore: 'linux-recovery', arcore: 'linux-agentrecovery', tcore: 'linux-tiny' }, // Unsigned version of the Windows MeshAgent x86
2841 - 10004: { id: 4, localname: 'MeshService64.exe', rname: 'meshagent.exe', desc: 'Win x86-64 service, unsigned', update: true, amt: true, platform: 'win32', core: 'windows-amt', rcore: 'linux-recovery', arcore: 'linux-agentrecovery', tcore: 'linux-tiny' }, // Unsigned version of the Windows MeshAgent x64
2840 10005: { id: 10005, localname: 'meshagent_osx-universal-64', rname: 'meshagent', desc: 'Apple macOS Universal Binary', update: true, amt: false, platform: 'osx', core: 'linux-noamt', rcore: 'linux-recovery', arcore: 'linux-agentrecovery', tcore: 'linux-tiny' }, // Apple Silicon + x86 universal binary
2841 10006: { id: 10006, localname: 'MeshCentralAssistant.exe', rname: 'MeshCentralAssistant.exe', desc: 'MeshCentral Assistant for Windows', update: false, amt: false, platform: 'win32' } // MeshCentral Assistant
2842 };
@@ -2847,8 +2845,28 @@ function CreateMeshCentralServer(config, args) {
2845 obj.updateMeshAgentsTable = function (domain, func) {
2846 // Setup the domain is specified
2847 var objx = domain, suffix = '';
2850 - if (objx == null) { objx = obj; } else { suffix = '-' + domain.id; objx.meshAgentBinaries = {}; }
2851 -
2848 + if (domain.id == '') { objx = obj; } else { suffix = '-' + domain.id; objx.meshAgentBinaries = {}; }
2849 +
2850 + // Get agent code signature certificate ready with the full cert chain
2851 + var agentSignCertInfo = null;
2852 + if (obj.certificates.codesign) {
2853 + agentSignCertInfo = {
2854 + cert: obj.certificateOperations.forge.pki.certificateFromPem(obj.certificates.codesign.cert),
2855 + key: obj.certificateOperations.forge.pki.privateKeyFromPem(obj.certificates.codesign.key),
2856 + extraCerts: [obj.certificateOperations.forge.pki.certificateFromPem(obj.certificates.root.cert) ]
2857 + }
2858 + }
2859 +
2860 + // Generate the agent signature description and URL
2861 + const serverSignedAgentsPath = obj.path.join(obj.datapath, 'signedagents' + suffix);
2862 + var signDesc = (domain.title ? domain.title : agentSignCertInfo.cert.subject.hash);
2863 + var httpsPort = ((obj.args.aliasport == null) ? obj.args.port : obj.args.aliasport); // Use HTTPS alias port is specified
2864 + var signUrl = 'https://' + ((domain.dns != null) ? domain.dns : obj.certificates.CommonName);
2865 + if (httpsPort != 443) { signUrl += ':' + httpsPort; }
2866 + var xdomain = (domain.dns == null) ? domain.id : '';
2867 + if (xdomain != '') xdomain += '/';
2868 + signUrl += '/' + xdomain;
2869 +
2870 // Load agent information file. This includes the data & time of the agent.
2871 const agentInfo = [];
2872 try { agentInfo = JSON.parse(obj.fs.readFileSync(obj.path.join(__dirname, 'agents', 'hashagents.json'), 'utf8')); } catch (ex) { }
@@ -2856,105 +2874,88 @@ function CreateMeshCentralServer(config, args) {
2874 var archcount = 0;
2875 for (var archid in obj.meshAgentsArchitectureNumbers) {
2876 var agentpath;
2859 - if (domain == null) {
2877 + if (domain.id == '') {
2878 + // Load all agents when processing the default domain
2879 agentpath = obj.path.join(__dirname, 'agents' + suffix, obj.meshAgentsArchitectureNumbers[archid].localname);
2880 var agentpath2 = obj.path.join(obj.datapath, 'agents' + suffix, obj.meshAgentsArchitectureNumbers[archid].localname);
2862 - if (obj.fs.existsSync(agentpath2)) { agentpath = agentpath2; } // If the agent is present in "meshcentral-data/agents", use that one instead.
2881 + if (obj.fs.existsSync(agentpath2)) { agentpath = agentpath2; delete obj.meshAgentsArchitectureNumbers[archid].codesign; } // If the agent is present in "meshcentral-data/agents", use that one instead.
2882 } else {
2883 + // When processing an extra domain, only load agents that are specific to that domain
2884 var agentpath = obj.path.join(obj.datapath, 'agents' + suffix, obj.meshAgentsArchitectureNumbers[archid].localname);
2865 - if (!obj.fs.existsSync(agentpath)) continue; // If the agent is not present in "meshcentral-data/agents" skip.
2885 + if (obj.fs.existsSync(agentpath)) { delete obj.meshAgentsArchitectureNumbers[archid].codesign; } else { continue; } // If the agent is not present in "meshcentral-data/agents" skip.
2886 }
2887
2868 - // Fetch all the agent binary information
2888 + // Fetch agent binary information
2889 var stats = null;
2890 try { stats = obj.fs.statSync(agentpath); } catch (ex) { }
2871 - if ((stats != null)) {
2872 - // If file exists
2873 - archcount++;
2874 - objx.meshAgentBinaries[archid] = Object.assign({}, obj.meshAgentsArchitectureNumbers[archid]);
2875 - objx.meshAgentBinaries[archid].path = agentpath;
2876 - objx.meshAgentBinaries[archid].url = 'http://' + obj.certificates.CommonName + ':' + ((typeof obj.args.aliasport == 'number') ? obj.args.aliasport : obj.args.port) + '/meshagents?id=' + archid;
2877 - objx.meshAgentBinaries[archid].size = stats.size;
2878 - if ((agentInfo[archid] != null) && (agentInfo[archid].mtime != null)) { objx.meshAgentBinaries[archid].mtime = new Date(agentInfo[archid].mtime); } // Set agent time if available
2879 -
2880 - // If this is a windows binary, pull binary information
2881 - if (obj.meshAgentsArchitectureNumbers[archid].platform == 'win32') {
2882 - try { objx.meshAgentBinaries[archid].pe = obj.exeHandler.parseWindowsExecutable(agentpath); } catch (ex) { }
2891 + if ((stats == null)) continue; // If this agent does not exist, skip it.
2892 +
2893 + // Check if we need to sign this agent, if so, check if it's already been signed
2894 + if (obj.meshAgentsArchitectureNumbers[archid].codesign === true) {
2895 + // Open the original agent with authenticode
2896 + var signeedagentpath = obj.path.join(serverSignedAgentsPath, obj.meshAgentsArchitectureNumbers[archid].localname);
2897 + const originalAgent = require('./authenticode.js').createAuthenticodeHandler(agentpath);
2898 + if (originalAgent != null) {
2899 + // Check if the agent is already signed correctly
2900 + const destinationAgent = require('./authenticode.js').createAuthenticodeHandler(signeedagentpath);
2901 + var destinationAgentOk = (
2902 + (destinationAgent != null) &&
2903 + (destinationAgent.fileHashSigned != null) &&
2904 + (Buffer.compare(destinationAgent.fileHashSigned, destinationAgent.fileHashActual) == 0) &&
2905 + ((Buffer.compare(destinationAgent.fileHashSigned, originalAgent.getHash(destinationAgent.fileHashAlgo))) == 0) &&
2906 + (destinationAgent.signingAttribs.indexOf(signUrl) >= 0) &&
2907 + (destinationAgent.signingAttribs.indexOf(signDesc) >= 0)
2908 + );
2909 + if (destinationAgent != null) { destinationAgent.close(); }
2910 + if (destinationAgentOk == false) {
2911 + // If not signed correctly, sign it. First, create the server signed agent folder if needed
2912 + try { obj.fs.mkdirSync(serverSignedAgentsPath); } catch (ex) { }
2913 + console.log(obj.common.format('Code signing agent {0}...', obj.meshAgentsArchitectureNumbers[archid].localname));
2914 + originalAgent.sign(agentSignCertInfo, { out: signeedagentpath, desc: signDesc, url: signUrl });
2915 + }
2916 + originalAgent.close();
2917 +
2918 + // Update agent path to signed agent
2919 + agentpath = signeedagentpath;
2920 }
2921 + }
2922
2885 - // If agents must be stored in RAM or if this is a Windows 32/64 agent, load the agent in RAM.
2886 - if ((obj.args.agentsinram === true) || (((archid == 3) || (archid == 4)) && (obj.args.agentsinram !== false))) {
2887 - if ((archid == 3) || (archid == 4)) {
2888 - // Load the agent with a random msh added to it.
2889 - const outStream = new require('stream').Duplex();
2890 - outStream.meshAgentBinary = objx.meshAgentBinaries[archid];
2891 - outStream.meshAgentBinary.randomMsh = Buffer.from(obj.crypto.randomBytes(64), 'binary').toString('base64');
2892 - outStream.bufferList = [];
2893 - outStream._write = function (chunk, encoding, callback) { this.bufferList.push(chunk); if (callback) callback(); }; // Append the chuck.
2894 - outStream._read = function (size) { }; // Do nothing, this is not going to be called.
2895 - outStream.on('finish', function () {
2896 - // Merge all chunks
2897 - this.meshAgentBinary.data = Buffer.concat(this.bufferList);
2898 - this.meshAgentBinary.size = this.meshAgentBinary.data.length;
2899 - delete this.bufferList;
2900 -
2901 - // Hash the uncompressed binary
2902 - const hash = obj.crypto.createHash('sha384').update(this.meshAgentBinary.data);
2903 - this.meshAgentBinary.fileHash = hash.digest('binary');
2904 - this.meshAgentBinary.fileHashHex = Buffer.from(this.meshAgentBinary.fileHash, 'binary').toString('hex');
2905 -
2906 - // Compress the agent using ZIP
2907 - const archive = require('archiver')('zip', { level: 9 }); // Sets the compression method.
2908 - const onZipData = function onZipData(buffer) { onZipData.x.zacc.push(buffer); }
2909 - const onZipEnd = function onZipEnd() {
2910 - // Concat all the buffer for create compressed zip agent
2911 - const concatData = Buffer.concat(onZipData.x.zacc);
2912 - delete onZipData.x.zacc;
2913 -
2914 - // Hash the compressed binary
2915 - const hash = obj.crypto.createHash('sha384').update(concatData);
2916 - onZipData.x.zhash = hash.digest('binary');
2917 - onZipData.x.zhashhex = Buffer.from(onZipData.x.zhash, 'binary').toString('hex');
2918 -
2919 - // Set the agent
2920 - onZipData.x.zdata = concatData;
2921 - onZipData.x.zsize = concatData.length;
2922 - }
2923 - const onZipError = function onZipError() { delete onZipData.x.zacc; }
2924 - this.meshAgentBinary.zacc = [];
2925 - onZipData.x = this.meshAgentBinary;
2926 - onZipEnd.x = this.meshAgentBinary;
2927 - onZipError.x = this.meshAgentBinary;
2928 - archive.on('data', onZipData);
2929 - archive.on('end', onZipEnd);
2930 - archive.on('error', onZipError);
2931 -
2932 - // Starting with NodeJS v16, passing in a buffer at archive.append() will result a compressed file with zero byte length. To fix this, we pass in the buffer as a stream.
2933 - // archive.append(this.meshAgentBinary.data, { name: 'meshagent' }); // This is the version that does not work on NodeJS v16.
2934 - const ReadableStream = require('stream').Readable;
2935 - const zipInputStream = new ReadableStream();
2936 - zipInputStream.push(this.meshAgentBinary.data);
2937 - zipInputStream.push(null);
2938 - archive.append(zipInputStream, { name: 'meshagent' });
2939 -
2940 - archive.finalize();
2941 - })
2942 - obj.exeHandler.streamExeWithMeshPolicy(
2943 - {
2944 - platform: 'win32',
2945 - sourceFileName: agentpath,
2946 - destinationStream: outStream,
2947 - randomPolicy: true, // Indicates that the msh policy is random data.
2948 - msh: outStream.meshAgentBinary.randomMsh,
2949 - peinfo: objx.meshAgentBinaries[archid].pe
2950 - });
2951 - } else {
2952 - // Load the agent as-is
2953 - objx.meshAgentBinaries[archid].data = obj.fs.readFileSync(agentpath);
2923 + // Setup agent information
2924 + archcount++;
2925 + objx.meshAgentBinaries[archid] = Object.assign({}, obj.meshAgentsArchitectureNumbers[archid]);
2926 + objx.meshAgentBinaries[archid].path = agentpath;
2927 + objx.meshAgentBinaries[archid].url = 'http://' + obj.certificates.CommonName + ':' + ((typeof obj.args.aliasport == 'number') ? obj.args.aliasport : obj.args.port) + '/meshagents?id=' + archid;
2928 + objx.meshAgentBinaries[archid].size = stats.size;
2929 + if ((agentInfo[archid] != null) && (agentInfo[archid].mtime != null)) { objx.meshAgentBinaries[archid].mtime = new Date(agentInfo[archid].mtime); } // Set agent time if available
2930 +
2931 + // If this is a windows binary, pull binary information
2932 + if (obj.meshAgentsArchitectureNumbers[archid].platform == 'win32') {
2933 + try { objx.meshAgentBinaries[archid].pe = obj.exeHandler.parseWindowsExecutable(agentpath); } catch (ex) { }
2934 + }
2935 +
2936 + // If agents must be stored in RAM or if this is a Windows 32/64 agent, load the agent in RAM.
2937 + if ((obj.args.agentsinram === true) || (((archid == 3) || (archid == 4)) && (obj.args.agentsinram !== false))) {
2938 + if ((archid == 3) || (archid == 4)) {
2939 + // Load the agent with a random msh added to it.
2940 + const outStream = new require('stream').Duplex();
2941 + outStream.meshAgentBinary = objx.meshAgentBinaries[archid];
2942 + outStream.meshAgentBinary.randomMsh = agentSignCertInfo.cert.subject.hash;
2943 + outStream.bufferList = [];
2944 + outStream._write = function (chunk, encoding, callback) { this.bufferList.push(chunk); if (callback) callback(); }; // Append the chuck.
2945 + outStream._read = function (size) { }; // Do nothing, this is not going to be called.
2946 + outStream.on('finish', function () {
2947 + // Merge all chunks
2948 + this.meshAgentBinary.data = Buffer.concat(this.bufferList);
2949 + this.meshAgentBinary.size = this.meshAgentBinary.data.length;
2950 + delete this.bufferList;
2951 +
2952 + // Hash the uncompressed binary
2953 + const hash = obj.crypto.createHash('sha384').update(this.meshAgentBinary.data);
2954 + this.meshAgentBinary.fileHash = hash.digest('binary');
2955 + this.meshAgentBinary.fileHashHex = Buffer.from(this.meshAgentBinary.fileHash, 'binary').toString('hex');
2956
2957 // Compress the agent using ZIP
2958 const archive = require('archiver')('zip', { level: 9 }); // Sets the compression method.
2957 -
2959 const onZipData = function onZipData(buffer) { onZipData.x.zacc.push(buffer); }
2960 const onZipEnd = function onZipEnd() {
2961 // Concat all the buffer for create compressed zip agent
@@ -2969,44 +2970,91 @@ function CreateMeshCentralServer(config, args) {
2970 // Set the agent
2971 onZipData.x.zdata = concatData;
2972 onZipData.x.zsize = concatData.length;
2972 -
2973 - //console.log('Packed', onZipData.x.size, onZipData.x.zsize);
2973 }
2974 const onZipError = function onZipError() { delete onZipData.x.zacc; }
2976 - objx.meshAgentBinaries[archid].zacc = [];
2977 - onZipData.x = objx.meshAgentBinaries[archid];
2978 - onZipEnd.x = objx.meshAgentBinaries[archid];
2979 - onZipError.x = objx.meshAgentBinaries[archid];
2975 + this.meshAgentBinary.zacc = [];
2976 + onZipData.x = this.meshAgentBinary;
2977 + onZipEnd.x = this.meshAgentBinary;
2978 + onZipError.x = this.meshAgentBinary;
2979 archive.on('data', onZipData);
2980 archive.on('end', onZipEnd);
2981 archive.on('error', onZipError);
2983 - archive.append(objx.meshAgentBinaries[archid].data, { name: 'meshagent' });
2982 +
2983 + // Starting with NodeJS v16, passing in a buffer at archive.append() will result a compressed file with zero byte length. To fix this, we pass in the buffer as a stream.
2984 + // archive.append(this.meshAgentBinary.data, { name: 'meshagent' }); // This is the version that does not work on NodeJS v16.
2985 + const ReadableStream = require('stream').Readable;
2986 + const zipInputStream = new ReadableStream();
2987 + zipInputStream.push(this.meshAgentBinary.data);
2988 + zipInputStream.push(null);
2989 + archive.append(zipInputStream, { name: 'meshagent' });
2990 +
2991 archive.finalize();
2992 + })
2993 + obj.exeHandler.streamExeWithMeshPolicy(
2994 + {
2995 + platform: 'win32',
2996 + sourceFileName: agentpath,
2997 + destinationStream: outStream,
2998 + randomPolicy: true, // Indicates that the msh policy is random data.
2999 + msh: outStream.meshAgentBinary.randomMsh,
3000 + peinfo: objx.meshAgentBinaries[archid].pe
3001 + });
3002 + } else {
3003 + // Load the agent as-is
3004 + objx.meshAgentBinaries[archid].data = obj.fs.readFileSync(agentpath);
3005 +
3006 + // Compress the agent using ZIP
3007 + const archive = require('archiver')('zip', { level: 9 }); // Sets the compression method.
3008 +
3009 + const onZipData = function onZipData(buffer) { onZipData.x.zacc.push(buffer); }
3010 + const onZipEnd = function onZipEnd() {
3011 + // Concat all the buffer for create compressed zip agent
3012 + const concatData = Buffer.concat(onZipData.x.zacc);
3013 + delete onZipData.x.zacc;
3014 +
3015 + // Hash the compressed binary
3016 + const hash = obj.crypto.createHash('sha384').update(concatData);
3017 + onZipData.x.zhash = hash.digest('binary');
3018 + onZipData.x.zhashhex = Buffer.from(onZipData.x.zhash, 'binary').toString('hex');
3019 +
3020 + // Set the agent
3021 + onZipData.x.zdata = concatData;
3022 + onZipData.x.zsize = concatData.length;
3023 +
3024 + //console.log('Packed', onZipData.x.size, onZipData.x.zsize);
3025 }
3026 + const onZipError = function onZipError() { delete onZipData.x.zacc; }
3027 + objx.meshAgentBinaries[archid].zacc = [];
3028 + onZipData.x = objx.meshAgentBinaries[archid];
3029 + onZipEnd.x = objx.meshAgentBinaries[archid];
3030 + onZipError.x = objx.meshAgentBinaries[archid];
3031 + archive.on('data', onZipData);
3032 + archive.on('end', onZipEnd);
3033 + archive.on('error', onZipError);
3034 + archive.append(objx.meshAgentBinaries[archid].data, { name: 'meshagent' });
3035 + archive.finalize();
3036 }
3037 + }
3038
2988 - // Hash the binary
2989 - const hashStream = obj.crypto.createHash('sha384');
2990 - hashStream.archid = archid;
2991 - hashStream.on('data', function (data) {
2992 - objx.meshAgentBinaries[this.archid].hash = data.toString('binary');
2993 - objx.meshAgentBinaries[this.archid].hashhex = data.toString('hex');
2994 - if ((--archcount == 0) && (func != null)) { func(); }
2995 - });
2996 - const options = { sourcePath: agentpath, targetStream: hashStream, platform: obj.meshAgentsArchitectureNumbers[archid].platform };
2997 - if (objx.meshAgentBinaries[archid].pe != null) { options.peinfo = objx.meshAgentBinaries[archid].pe; }
2998 - obj.exeHandler.hashExecutableFile(options);
3039 + // Hash the binary
3040 + const hashStream = obj.crypto.createHash('sha384');
3041 + hashStream.archid = archid;
3042 + hashStream.on('data', function (data) {
3043 + objx.meshAgentBinaries[this.archid].hash = data.toString('binary');
3044 + objx.meshAgentBinaries[this.archid].hashhex = data.toString('hex');
3045 + if ((--archcount == 0) && (func != null)) { func(); }
3046 + });
3047 + const options = { sourcePath: agentpath, targetStream: hashStream, platform: obj.meshAgentsArchitectureNumbers[archid].platform };
3048 + if (objx.meshAgentBinaries[archid].pe != null) { options.peinfo = objx.meshAgentBinaries[archid].pe; }
3049 + obj.exeHandler.hashExecutableFile(options);
3050
3000 - // If we are not loading Windows binaries to RAM, compute the RAW file hash of the signed binaries here.
3001 - if ((obj.args.agentsinram === false) && ((archid == 3) || (archid == 4))) {
3002 - const hash = obj.crypto.createHash('sha384').update(obj.fs.readFileSync(agentpath));
3003 - objx.meshAgentBinaries[archid].fileHash = hash.digest('binary');
3004 - objx.meshAgentBinaries[archid].fileHashHex = Buffer.from(objx.meshAgentBinaries[archid].fileHash, 'binary').toString('hex');
3005 - }
3051 + // If we are not loading Windows binaries to RAM, compute the RAW file hash of the signed binaries here.
3052 + if ((obj.args.agentsinram === false) && ((archid == 3) || (archid == 4))) {
3053 + const hash = obj.crypto.createHash('sha384').update(obj.fs.readFileSync(agentpath));
3054 + objx.meshAgentBinaries[archid].fileHash = hash.digest('binary');
3055 + objx.meshAgentBinaries[archid].fileHashHex = Buffer.from(objx.meshAgentBinaries[archid].fileHash, 'binary').toString('hex');
3056 }
3057 }
3008 - if ((objx.meshAgentBinaries[3] == null) && (objx.meshAgentBinaries[10003] != null)) { objx.meshAgentBinaries[3] = objx.meshAgentBinaries[10003]; } // If only the unsigned windows binaries are present, use them.
3009 - if ((objx.meshAgentBinaries[4] == null) && (objx.meshAgentBinaries[10004] != null)) { objx.meshAgentBinaries[4] = objx.meshAgentBinaries[10004]; } // If only the unsigned windows binaries are present, use them.
3058 };
3059
3060 // Generate a time limited user login token