mirror of
https://github.com/Ylianst/MeshCentral.git
synced 2025-02-12 11:01:52 +00:00
Integration of agent code signing into MeshCentral.
This commit is contained in:
parent
e37b511eb3
commit
82254b80ed
4 changed files with 198 additions and 134 deletions
Binary file not shown.
Binary file not shown.
|
@ -6,6 +6,13 @@
|
||||||
* @version v0.0.1
|
* @version v0.0.1
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/*jslint node: true */
|
||||||
|
/*jshint node: true */
|
||||||
|
/*jshint strict:false */
|
||||||
|
/*jshint -W097 */
|
||||||
|
/*jshint esversion: 6 */
|
||||||
|
"use strict";
|
||||||
|
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
const crypto = require('crypto');
|
const crypto = require('crypto');
|
||||||
const forge = require('node-forge');
|
const forge = require('node-forge');
|
||||||
|
@ -109,29 +116,29 @@ function createAuthenticodeHandler(path) {
|
||||||
|
|
||||||
// Open the file and read header information
|
// Open the file and read header information
|
||||||
function openFile() {
|
function openFile() {
|
||||||
if (obj.fd != null) return;
|
if (obj.fd != null) return true;
|
||||||
|
|
||||||
// Open the file descriptor
|
// Open the file descriptor
|
||||||
obj.path = path;
|
obj.path = path;
|
||||||
obj.fd = fs.openSync(path);
|
try { obj.fd = fs.openSync(path); } catch (ex) { return false; } // Unable to open file
|
||||||
obj.stats = fs.fstatSync(obj.fd);
|
obj.stats = fs.fstatSync(obj.fd);
|
||||||
obj.filesize = obj.stats.size;
|
obj.filesize = obj.stats.size;
|
||||||
if (obj.filesize < 64) { throw ('File too short.'); }
|
if (obj.filesize < 64) { obj.close(); return false; } // File too short.
|
||||||
|
|
||||||
// Read the PE header size
|
// Read the PE header size
|
||||||
var buf = readFileSlice(60, 4);
|
var buf = readFileSlice(60, 4);
|
||||||
obj.header.header_size = buf.readUInt32LE(0);
|
obj.header.header_size = buf.readUInt32LE(0);
|
||||||
|
|
||||||
// Check file size and PE header
|
// Check file size and PE header
|
||||||
if (obj.filesize < (160 + obj.header.header_size)) { throw ('Invalid SizeOfHeaders.'); }
|
if (obj.filesize < (160 + obj.header.header_size)) { obj.close(); return false; } // Invalid SizeOfHeaders.
|
||||||
if (readFileSlice(obj.header.header_size, 4).toString('hex') != '50450000') { throw ('Invalid PE File.'); }
|
if (readFileSlice(obj.header.header_size, 4).toString('hex') != '50450000') { obj.close(); return false; } // Invalid PE File.
|
||||||
|
|
||||||
// Check header magic data
|
// Check header magic data
|
||||||
var magic = readFileSlice(obj.header.header_size + 24, 2).readUInt16LE(0);
|
var magic = readFileSlice(obj.header.header_size + 24, 2).readUInt16LE(0);
|
||||||
switch (magic) {
|
switch (magic) {
|
||||||
case 0x20b: obj.header.pe32plus = 1; break;
|
case 0x20b: obj.header.pe32plus = 1; break;
|
||||||
case 0x10b: obj.header.pe32plus = 0; break;
|
case 0x10b: obj.header.pe32plus = 0; break;
|
||||||
default: throw ('Invalid Magic in PE');
|
default: { obj.close(); return false; } // Invalid Magic in PE
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read PE header information
|
// Read PE header information
|
||||||
|
@ -146,7 +153,7 @@ function createAuthenticodeHandler(path) {
|
||||||
// Read signature block
|
// Read signature block
|
||||||
|
|
||||||
// Check if the file size allows for the signature block
|
// Check if the file size allows for the signature block
|
||||||
if (obj.filesize < (obj.header.sigpos + obj.header.siglen)) { throw ('Executable file too short to contain the signature block.'); }
|
if (obj.filesize < (obj.header.sigpos + obj.header.siglen)) { obj.close(); return false; } // Executable file too short to contain the signature block.
|
||||||
|
|
||||||
// Remove the padding if needed
|
// Remove the padding if needed
|
||||||
var i, pkcs7raw = readFileSlice(obj.header.sigpos + 8, obj.header.siglen - 8);
|
var i, pkcs7raw = readFileSlice(obj.header.sigpos + 8, obj.header.siglen - 8);
|
||||||
|
@ -207,12 +214,13 @@ function createAuthenticodeHandler(path) {
|
||||||
obj.fileHashSigned = Buffer.from(pkcs7content.value[1].value[1].value, 'binary')
|
obj.fileHashSigned = Buffer.from(pkcs7content.value[1].value[1].value, 'binary')
|
||||||
|
|
||||||
// Compute the actual file hash
|
// Compute the actual file hash
|
||||||
if (obj.fileHashAlgo != null) { obj.fileHashActual = getHash(obj.fileHashAlgo); }
|
if (obj.fileHashAlgo != null) { obj.fileHashActual = obj.getHash(obj.fileHashAlgo); }
|
||||||
}
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Hash the file using the selected hashing system
|
// Hash the file using the selected hashing system
|
||||||
function getHash(algo) {
|
obj.getHash = function(algo) {
|
||||||
var hash = crypto.createHash(algo);
|
var hash = crypto.createHash(algo);
|
||||||
runHash(hash, 0, obj.header.header_size + 88);
|
runHash(hash, 0, obj.header.header_size + 88);
|
||||||
runHash(hash, obj.header.header_size + 88 + 4, obj.header.header_size + 152 + (obj.header.pe32plus * 16));
|
runHash(hash, obj.header.header_size + 88 + 4, obj.header.header_size + 152 + (obj.header.pe32plus * 16));
|
||||||
|
@ -229,7 +237,7 @@ function createAuthenticodeHandler(path) {
|
||||||
// Sign the file using the certificate and key. If none is specified, generate a dummy one
|
// Sign the file using the certificate and key. If none is specified, generate a dummy one
|
||||||
obj.sign = function (cert, args) {
|
obj.sign = function (cert, args) {
|
||||||
if (cert == null) { cert = createSelfSignedCert({ cn: 'Test' }); }
|
if (cert == null) { cert = createSelfSignedCert({ cn: 'Test' }); }
|
||||||
var fileHash = getHash('sha384');
|
var fileHash = obj.getHash('sha384');
|
||||||
|
|
||||||
// Create the signature block
|
// Create the signature block
|
||||||
var p7 = forge.pkcs7.createSignedData();
|
var p7 = forge.pkcs7.createSignedData();
|
||||||
|
@ -246,8 +254,12 @@ function createAuthenticodeHandler(path) {
|
||||||
{ type: forge.pki.oids.messageDigest } // This value will populated at signing time by node-forge
|
{ type: forge.pki.oids.messageDigest } // This value will populated at signing time by node-forge
|
||||||
]
|
]
|
||||||
if ((typeof args.desc == 'string') || (typeof args.url == 'string')) {
|
if ((typeof args.desc == 'string') || (typeof args.url == 'string')) {
|
||||||
var codeSigningAttributes = { 'tagClass': 0, 'type': 16, 'constructed': true, 'composed': true, 'value': [ ] };
|
var codeSigningAttributes = { 'tagClass': 0, 'type': 16, 'constructed': true, 'composed': true, 'value': [] };
|
||||||
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() }] }); }
|
if (args.desc != null) { // Encode description as big-endian unicode.
|
||||||
|
var desc = "", ucs = Buffer.from(args.desc, 'ucs2').toString()
|
||||||
|
for (var k = 0; k < ucs.length; k += 2) { desc += String.fromCharCode(ucs.charCodeAt(k + 1), ucs.charCodeAt(k)); }
|
||||||
|
codeSigningAttributes.value.push({ 'tagClass': 128, 'type': 0, 'constructed': true, 'composed': true, 'value': [{ 'tagClass': 128, 'type': 0, 'constructed': false, 'composed': false, 'value': desc }] });
|
||||||
|
}
|
||||||
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 }] }); }
|
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 }] }); }
|
||||||
authenticatedAttributes.push({ type: obj.Oids.SPC_SP_OPUS_INFO_OBJID, value: codeSigningAttributes });
|
authenticatedAttributes.push({ type: obj.Oids.SPC_SP_OPUS_INFO_OBJID, value: codeSigningAttributes });
|
||||||
}
|
}
|
||||||
|
@ -330,8 +342,8 @@ function createAuthenticodeHandler(path) {
|
||||||
fs.closeSync(output);
|
fs.closeSync(output);
|
||||||
}
|
}
|
||||||
|
|
||||||
openFile();
|
// Return null if we could not open the file
|
||||||
return obj;
|
return (openFile() ? obj : null);
|
||||||
}
|
}
|
||||||
|
|
||||||
function start() {
|
function start() {
|
||||||
|
@ -410,7 +422,7 @@ function start() {
|
||||||
if (typeof args.exe != 'string') { console.log("Missing --exe [filename]"); return; }
|
if (typeof args.exe != 'string') { console.log("Missing --exe [filename]"); return; }
|
||||||
createOutFile(args, args.exe);
|
createOutFile(args, args.exe);
|
||||||
const cert = loadCertificates(args);
|
const cert = loadCertificates(args);
|
||||||
if (cert == null) { console.log("Unable to load certificate and/or private key, generating text certificate."); }
|
if (cert == null) { console.log("Unable to load certificate and/or private key, generating test certificate."); }
|
||||||
console.log("Signing to " + args.out); exe.sign(cert, args); console.log("Done.");
|
console.log("Signing to " + args.out); exe.sign(cert, args); console.log("Done.");
|
||||||
}
|
}
|
||||||
if (command == 'unsign') { // Unsign an executable
|
if (command == 'unsign') { // Unsign an executable
|
||||||
|
@ -433,4 +445,8 @@ function start() {
|
||||||
if (exe != null) { exe.close(); }
|
if (exe != null) { exe.close(); }
|
||||||
}
|
}
|
||||||
|
|
||||||
start();
|
// If this is the main module, run the command line version
|
||||||
|
if (require.main === module) { start(); }
|
||||||
|
|
||||||
|
// Exports
|
||||||
|
module.exports.createAuthenticodeHandler = createAuthenticodeHandler;
|
284
meshcentral.js
284
meshcentral.js
|
@ -1613,11 +1613,11 @@ function CreateMeshCentralServer(config, args) {
|
||||||
} catch (ex) { }
|
} catch (ex) { }
|
||||||
|
|
||||||
// Load any domain specific agents
|
// Load any domain specific agents
|
||||||
for (var i in obj.config.domains) { if (i != '') { obj.updateMeshAgentsTable(obj.config.domains[i], function () { }); } }
|
for (var i in obj.config.domains) { if ((i != '') && (obj.config.domains[i].share == null)) { obj.updateMeshAgentsTable(obj.config.domains[i], function () { }); } }
|
||||||
|
|
||||||
// Load the list of mesh agents and install scripts
|
// Load the list of mesh agents and install scripts
|
||||||
if ((obj.args.noagentupdate == 1) || (obj.args.noagentupdate == true)) { for (i in obj.meshAgentsArchitectureNumbers) { obj.meshAgentsArchitectureNumbers[i].update = false; } }
|
if ((obj.args.noagentupdate == 1) || (obj.args.noagentupdate == true)) { for (i in obj.meshAgentsArchitectureNumbers) { obj.meshAgentsArchitectureNumbers[i].update = false; } }
|
||||||
obj.updateMeshAgentsTable(null, function () {
|
obj.updateMeshAgentsTable(obj.config.domains[''], function () {
|
||||||
obj.updateMeshAgentInstallScripts();
|
obj.updateMeshAgentInstallScripts();
|
||||||
|
|
||||||
// Setup and start the web server
|
// Setup and start the web server
|
||||||
|
@ -2801,8 +2801,8 @@ function CreateMeshCentralServer(config, args) {
|
||||||
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' },
|
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' },
|
||||||
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' },
|
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' },
|
||||||
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' },
|
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' },
|
||||||
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' },
|
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 },
|
||||||
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' },
|
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 },
|
||||||
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' },
|
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' },
|
||||||
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' },
|
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' },
|
||||||
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' },
|
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) {
|
||||||
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
|
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
|
||||||
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
|
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
|
||||||
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
|
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
|
||||||
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
|
|
||||||
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
|
|
||||||
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
|
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
|
||||||
10006: { id: 10006, localname: 'MeshCentralAssistant.exe', rname: 'MeshCentralAssistant.exe', desc: 'MeshCentral Assistant for Windows', update: false, amt: false, platform: 'win32' } // MeshCentral Assistant
|
10006: { id: 10006, localname: 'MeshCentralAssistant.exe', rname: 'MeshCentralAssistant.exe', desc: 'MeshCentral Assistant for Windows', update: false, amt: false, platform: 'win32' } // MeshCentral Assistant
|
||||||
};
|
};
|
||||||
|
@ -2847,8 +2845,28 @@ function CreateMeshCentralServer(config, args) {
|
||||||
obj.updateMeshAgentsTable = function (domain, func) {
|
obj.updateMeshAgentsTable = function (domain, func) {
|
||||||
// Setup the domain is specified
|
// Setup the domain is specified
|
||||||
var objx = domain, suffix = '';
|
var objx = domain, suffix = '';
|
||||||
if (objx == null) { objx = obj; } else { suffix = '-' + domain.id; objx.meshAgentBinaries = {}; }
|
if (domain.id == '') { objx = obj; } else { suffix = '-' + domain.id; objx.meshAgentBinaries = {}; }
|
||||||
|
|
||||||
|
// Get agent code signature certificate ready with the full cert chain
|
||||||
|
var agentSignCertInfo = null;
|
||||||
|
if (obj.certificates.codesign) {
|
||||||
|
agentSignCertInfo = {
|
||||||
|
cert: obj.certificateOperations.forge.pki.certificateFromPem(obj.certificates.codesign.cert),
|
||||||
|
key: obj.certificateOperations.forge.pki.privateKeyFromPem(obj.certificates.codesign.key),
|
||||||
|
extraCerts: [obj.certificateOperations.forge.pki.certificateFromPem(obj.certificates.root.cert) ]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate the agent signature description and URL
|
||||||
|
const serverSignedAgentsPath = obj.path.join(obj.datapath, 'signedagents' + suffix);
|
||||||
|
var signDesc = (domain.title ? domain.title : agentSignCertInfo.cert.subject.hash);
|
||||||
|
var httpsPort = ((obj.args.aliasport == null) ? obj.args.port : obj.args.aliasport); // Use HTTPS alias port is specified
|
||||||
|
var signUrl = 'https://' + ((domain.dns != null) ? domain.dns : obj.certificates.CommonName);
|
||||||
|
if (httpsPort != 443) { signUrl += ':' + httpsPort; }
|
||||||
|
var xdomain = (domain.dns == null) ? domain.id : '';
|
||||||
|
if (xdomain != '') xdomain += '/';
|
||||||
|
signUrl += '/' + xdomain;
|
||||||
|
|
||||||
// Load agent information file. This includes the data & time of the agent.
|
// Load agent information file. This includes the data & time of the agent.
|
||||||
const agentInfo = [];
|
const agentInfo = [];
|
||||||
try { agentInfo = JSON.parse(obj.fs.readFileSync(obj.path.join(__dirname, 'agents', 'hashagents.json'), 'utf8')); } catch (ex) { }
|
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) {
|
||||||
var archcount = 0;
|
var archcount = 0;
|
||||||
for (var archid in obj.meshAgentsArchitectureNumbers) {
|
for (var archid in obj.meshAgentsArchitectureNumbers) {
|
||||||
var agentpath;
|
var agentpath;
|
||||||
if (domain == null) {
|
if (domain.id == '') {
|
||||||
|
// Load all agents when processing the default domain
|
||||||
agentpath = obj.path.join(__dirname, 'agents' + suffix, obj.meshAgentsArchitectureNumbers[archid].localname);
|
agentpath = obj.path.join(__dirname, 'agents' + suffix, obj.meshAgentsArchitectureNumbers[archid].localname);
|
||||||
var agentpath2 = obj.path.join(obj.datapath, 'agents' + suffix, obj.meshAgentsArchitectureNumbers[archid].localname);
|
var agentpath2 = obj.path.join(obj.datapath, 'agents' + suffix, obj.meshAgentsArchitectureNumbers[archid].localname);
|
||||||
if (obj.fs.existsSync(agentpath2)) { agentpath = agentpath2; } // If the agent is present in "meshcentral-data/agents", use that one instead.
|
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.
|
||||||
} else {
|
} else {
|
||||||
|
// When processing an extra domain, only load agents that are specific to that domain
|
||||||
var agentpath = obj.path.join(obj.datapath, 'agents' + suffix, obj.meshAgentsArchitectureNumbers[archid].localname);
|
var agentpath = obj.path.join(obj.datapath, 'agents' + suffix, obj.meshAgentsArchitectureNumbers[archid].localname);
|
||||||
if (!obj.fs.existsSync(agentpath)) continue; // If the agent is not present in "meshcentral-data/agents" skip.
|
if (obj.fs.existsSync(agentpath)) { delete obj.meshAgentsArchitectureNumbers[archid].codesign; } else { continue; } // If the agent is not present in "meshcentral-data/agents" skip.
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fetch all the agent binary information
|
// Fetch agent binary information
|
||||||
var stats = null;
|
var stats = null;
|
||||||
try { stats = obj.fs.statSync(agentpath); } catch (ex) { }
|
try { stats = obj.fs.statSync(agentpath); } catch (ex) { }
|
||||||
if ((stats != null)) {
|
if ((stats == null)) continue; // If this agent does not exist, skip it.
|
||||||
// If file exists
|
|
||||||
archcount++;
|
|
||||||
objx.meshAgentBinaries[archid] = Object.assign({}, obj.meshAgentsArchitectureNumbers[archid]);
|
|
||||||
objx.meshAgentBinaries[archid].path = agentpath;
|
|
||||||
objx.meshAgentBinaries[archid].url = 'http://' + obj.certificates.CommonName + ':' + ((typeof obj.args.aliasport == 'number') ? obj.args.aliasport : obj.args.port) + '/meshagents?id=' + archid;
|
|
||||||
objx.meshAgentBinaries[archid].size = stats.size;
|
|
||||||
if ((agentInfo[archid] != null) && (agentInfo[archid].mtime != null)) { objx.meshAgentBinaries[archid].mtime = new Date(agentInfo[archid].mtime); } // Set agent time if available
|
|
||||||
|
|
||||||
// If this is a windows binary, pull binary information
|
// Check if we need to sign this agent, if so, check if it's already been signed
|
||||||
if (obj.meshAgentsArchitectureNumbers[archid].platform == 'win32') {
|
if (obj.meshAgentsArchitectureNumbers[archid].codesign === true) {
|
||||||
try { objx.meshAgentBinaries[archid].pe = obj.exeHandler.parseWindowsExecutable(agentpath); } catch (ex) { }
|
// Open the original agent with authenticode
|
||||||
|
var signeedagentpath = obj.path.join(serverSignedAgentsPath, obj.meshAgentsArchitectureNumbers[archid].localname);
|
||||||
|
const originalAgent = require('./authenticode.js').createAuthenticodeHandler(agentpath);
|
||||||
|
if (originalAgent != null) {
|
||||||
|
// Check if the agent is already signed correctly
|
||||||
|
const destinationAgent = require('./authenticode.js').createAuthenticodeHandler(signeedagentpath);
|
||||||
|
var destinationAgentOk = (
|
||||||
|
(destinationAgent != null) &&
|
||||||
|
(destinationAgent.fileHashSigned != null) &&
|
||||||
|
(Buffer.compare(destinationAgent.fileHashSigned, destinationAgent.fileHashActual) == 0) &&
|
||||||
|
((Buffer.compare(destinationAgent.fileHashSigned, originalAgent.getHash(destinationAgent.fileHashAlgo))) == 0) &&
|
||||||
|
(destinationAgent.signingAttribs.indexOf(signUrl) >= 0) &&
|
||||||
|
(destinationAgent.signingAttribs.indexOf(signDesc) >= 0)
|
||||||
|
);
|
||||||
|
if (destinationAgent != null) { destinationAgent.close(); }
|
||||||
|
if (destinationAgentOk == false) {
|
||||||
|
// If not signed correctly, sign it. First, create the server signed agent folder if needed
|
||||||
|
try { obj.fs.mkdirSync(serverSignedAgentsPath); } catch (ex) { }
|
||||||
|
console.log(obj.common.format('Code signing agent {0}...', obj.meshAgentsArchitectureNumbers[archid].localname));
|
||||||
|
originalAgent.sign(agentSignCertInfo, { out: signeedagentpath, desc: signDesc, url: signUrl });
|
||||||
|
}
|
||||||
|
originalAgent.close();
|
||||||
|
|
||||||
|
// Update agent path to signed agent
|
||||||
|
agentpath = signeedagentpath;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// If agents must be stored in RAM or if this is a Windows 32/64 agent, load the agent in RAM.
|
// Setup agent information
|
||||||
if ((obj.args.agentsinram === true) || (((archid == 3) || (archid == 4)) && (obj.args.agentsinram !== false))) {
|
archcount++;
|
||||||
if ((archid == 3) || (archid == 4)) {
|
objx.meshAgentBinaries[archid] = Object.assign({}, obj.meshAgentsArchitectureNumbers[archid]);
|
||||||
// Load the agent with a random msh added to it.
|
objx.meshAgentBinaries[archid].path = agentpath;
|
||||||
const outStream = new require('stream').Duplex();
|
objx.meshAgentBinaries[archid].url = 'http://' + obj.certificates.CommonName + ':' + ((typeof obj.args.aliasport == 'number') ? obj.args.aliasport : obj.args.port) + '/meshagents?id=' + archid;
|
||||||
outStream.meshAgentBinary = objx.meshAgentBinaries[archid];
|
objx.meshAgentBinaries[archid].size = stats.size;
|
||||||
outStream.meshAgentBinary.randomMsh = Buffer.from(obj.crypto.randomBytes(64), 'binary').toString('base64');
|
if ((agentInfo[archid] != null) && (agentInfo[archid].mtime != null)) { objx.meshAgentBinaries[archid].mtime = new Date(agentInfo[archid].mtime); } // Set agent time if available
|
||||||
outStream.bufferList = [];
|
|
||||||
outStream._write = function (chunk, encoding, callback) { this.bufferList.push(chunk); if (callback) callback(); }; // Append the chuck.
|
|
||||||
outStream._read = function (size) { }; // Do nothing, this is not going to be called.
|
|
||||||
outStream.on('finish', function () {
|
|
||||||
// Merge all chunks
|
|
||||||
this.meshAgentBinary.data = Buffer.concat(this.bufferList);
|
|
||||||
this.meshAgentBinary.size = this.meshAgentBinary.data.length;
|
|
||||||
delete this.bufferList;
|
|
||||||
|
|
||||||
// Hash the uncompressed binary
|
// If this is a windows binary, pull binary information
|
||||||
const hash = obj.crypto.createHash('sha384').update(this.meshAgentBinary.data);
|
if (obj.meshAgentsArchitectureNumbers[archid].platform == 'win32') {
|
||||||
this.meshAgentBinary.fileHash = hash.digest('binary');
|
try { objx.meshAgentBinaries[archid].pe = obj.exeHandler.parseWindowsExecutable(agentpath); } catch (ex) { }
|
||||||
this.meshAgentBinary.fileHashHex = Buffer.from(this.meshAgentBinary.fileHash, 'binary').toString('hex');
|
}
|
||||||
|
|
||||||
// Compress the agent using ZIP
|
// If agents must be stored in RAM or if this is a Windows 32/64 agent, load the agent in RAM.
|
||||||
const archive = require('archiver')('zip', { level: 9 }); // Sets the compression method.
|
if ((obj.args.agentsinram === true) || (((archid == 3) || (archid == 4)) && (obj.args.agentsinram !== false))) {
|
||||||
const onZipData = function onZipData(buffer) { onZipData.x.zacc.push(buffer); }
|
if ((archid == 3) || (archid == 4)) {
|
||||||
const onZipEnd = function onZipEnd() {
|
// Load the agent with a random msh added to it.
|
||||||
// Concat all the buffer for create compressed zip agent
|
const outStream = new require('stream').Duplex();
|
||||||
const concatData = Buffer.concat(onZipData.x.zacc);
|
outStream.meshAgentBinary = objx.meshAgentBinaries[archid];
|
||||||
delete onZipData.x.zacc;
|
outStream.meshAgentBinary.randomMsh = agentSignCertInfo.cert.subject.hash;
|
||||||
|
outStream.bufferList = [];
|
||||||
|
outStream._write = function (chunk, encoding, callback) { this.bufferList.push(chunk); if (callback) callback(); }; // Append the chuck.
|
||||||
|
outStream._read = function (size) { }; // Do nothing, this is not going to be called.
|
||||||
|
outStream.on('finish', function () {
|
||||||
|
// Merge all chunks
|
||||||
|
this.meshAgentBinary.data = Buffer.concat(this.bufferList);
|
||||||
|
this.meshAgentBinary.size = this.meshAgentBinary.data.length;
|
||||||
|
delete this.bufferList;
|
||||||
|
|
||||||
// Hash the compressed binary
|
// Hash the uncompressed binary
|
||||||
const hash = obj.crypto.createHash('sha384').update(concatData);
|
const hash = obj.crypto.createHash('sha384').update(this.meshAgentBinary.data);
|
||||||
onZipData.x.zhash = hash.digest('binary');
|
this.meshAgentBinary.fileHash = hash.digest('binary');
|
||||||
onZipData.x.zhashhex = Buffer.from(onZipData.x.zhash, 'binary').toString('hex');
|
this.meshAgentBinary.fileHashHex = Buffer.from(this.meshAgentBinary.fileHash, 'binary').toString('hex');
|
||||||
|
|
||||||
// Set the agent
|
|
||||||
onZipData.x.zdata = concatData;
|
|
||||||
onZipData.x.zsize = concatData.length;
|
|
||||||
}
|
|
||||||
const onZipError = function onZipError() { delete onZipData.x.zacc; }
|
|
||||||
this.meshAgentBinary.zacc = [];
|
|
||||||
onZipData.x = this.meshAgentBinary;
|
|
||||||
onZipEnd.x = this.meshAgentBinary;
|
|
||||||
onZipError.x = this.meshAgentBinary;
|
|
||||||
archive.on('data', onZipData);
|
|
||||||
archive.on('end', onZipEnd);
|
|
||||||
archive.on('error', onZipError);
|
|
||||||
|
|
||||||
// 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.
|
|
||||||
// archive.append(this.meshAgentBinary.data, { name: 'meshagent' }); // This is the version that does not work on NodeJS v16.
|
|
||||||
const ReadableStream = require('stream').Readable;
|
|
||||||
const zipInputStream = new ReadableStream();
|
|
||||||
zipInputStream.push(this.meshAgentBinary.data);
|
|
||||||
zipInputStream.push(null);
|
|
||||||
archive.append(zipInputStream, { name: 'meshagent' });
|
|
||||||
|
|
||||||
archive.finalize();
|
|
||||||
})
|
|
||||||
obj.exeHandler.streamExeWithMeshPolicy(
|
|
||||||
{
|
|
||||||
platform: 'win32',
|
|
||||||
sourceFileName: agentpath,
|
|
||||||
destinationStream: outStream,
|
|
||||||
randomPolicy: true, // Indicates that the msh policy is random data.
|
|
||||||
msh: outStream.meshAgentBinary.randomMsh,
|
|
||||||
peinfo: objx.meshAgentBinaries[archid].pe
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
// Load the agent as-is
|
|
||||||
objx.meshAgentBinaries[archid].data = obj.fs.readFileSync(agentpath);
|
|
||||||
|
|
||||||
// Compress the agent using ZIP
|
// Compress the agent using ZIP
|
||||||
const archive = require('archiver')('zip', { level: 9 }); // Sets the compression method.
|
const archive = require('archiver')('zip', { level: 9 }); // Sets the compression method.
|
||||||
|
|
||||||
const onZipData = function onZipData(buffer) { onZipData.x.zacc.push(buffer); }
|
const onZipData = function onZipData(buffer) { onZipData.x.zacc.push(buffer); }
|
||||||
const onZipEnd = function onZipEnd() {
|
const onZipEnd = function onZipEnd() {
|
||||||
// Concat all the buffer for create compressed zip agent
|
// Concat all the buffer for create compressed zip agent
|
||||||
|
@ -2969,44 +2970,91 @@ function CreateMeshCentralServer(config, args) {
|
||||||
// Set the agent
|
// Set the agent
|
||||||
onZipData.x.zdata = concatData;
|
onZipData.x.zdata = concatData;
|
||||||
onZipData.x.zsize = concatData.length;
|
onZipData.x.zsize = concatData.length;
|
||||||
|
|
||||||
//console.log('Packed', onZipData.x.size, onZipData.x.zsize);
|
|
||||||
}
|
}
|
||||||
const onZipError = function onZipError() { delete onZipData.x.zacc; }
|
const onZipError = function onZipError() { delete onZipData.x.zacc; }
|
||||||
objx.meshAgentBinaries[archid].zacc = [];
|
this.meshAgentBinary.zacc = [];
|
||||||
onZipData.x = objx.meshAgentBinaries[archid];
|
onZipData.x = this.meshAgentBinary;
|
||||||
onZipEnd.x = objx.meshAgentBinaries[archid];
|
onZipEnd.x = this.meshAgentBinary;
|
||||||
onZipError.x = objx.meshAgentBinaries[archid];
|
onZipError.x = this.meshAgentBinary;
|
||||||
archive.on('data', onZipData);
|
archive.on('data', onZipData);
|
||||||
archive.on('end', onZipEnd);
|
archive.on('end', onZipEnd);
|
||||||
archive.on('error', onZipError);
|
archive.on('error', onZipError);
|
||||||
archive.append(objx.meshAgentBinaries[archid].data, { name: 'meshagent' });
|
|
||||||
|
// 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.
|
||||||
|
// archive.append(this.meshAgentBinary.data, { name: 'meshagent' }); // This is the version that does not work on NodeJS v16.
|
||||||
|
const ReadableStream = require('stream').Readable;
|
||||||
|
const zipInputStream = new ReadableStream();
|
||||||
|
zipInputStream.push(this.meshAgentBinary.data);
|
||||||
|
zipInputStream.push(null);
|
||||||
|
archive.append(zipInputStream, { name: 'meshagent' });
|
||||||
|
|
||||||
archive.finalize();
|
archive.finalize();
|
||||||
|
})
|
||||||
|
obj.exeHandler.streamExeWithMeshPolicy(
|
||||||
|
{
|
||||||
|
platform: 'win32',
|
||||||
|
sourceFileName: agentpath,
|
||||||
|
destinationStream: outStream,
|
||||||
|
randomPolicy: true, // Indicates that the msh policy is random data.
|
||||||
|
msh: outStream.meshAgentBinary.randomMsh,
|
||||||
|
peinfo: objx.meshAgentBinaries[archid].pe
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// Load the agent as-is
|
||||||
|
objx.meshAgentBinaries[archid].data = obj.fs.readFileSync(agentpath);
|
||||||
|
|
||||||
|
// Compress the agent using ZIP
|
||||||
|
const archive = require('archiver')('zip', { level: 9 }); // Sets the compression method.
|
||||||
|
|
||||||
|
const onZipData = function onZipData(buffer) { onZipData.x.zacc.push(buffer); }
|
||||||
|
const onZipEnd = function onZipEnd() {
|
||||||
|
// Concat all the buffer for create compressed zip agent
|
||||||
|
const concatData = Buffer.concat(onZipData.x.zacc);
|
||||||
|
delete onZipData.x.zacc;
|
||||||
|
|
||||||
|
// Hash the compressed binary
|
||||||
|
const hash = obj.crypto.createHash('sha384').update(concatData);
|
||||||
|
onZipData.x.zhash = hash.digest('binary');
|
||||||
|
onZipData.x.zhashhex = Buffer.from(onZipData.x.zhash, 'binary').toString('hex');
|
||||||
|
|
||||||
|
// Set the agent
|
||||||
|
onZipData.x.zdata = concatData;
|
||||||
|
onZipData.x.zsize = concatData.length;
|
||||||
|
|
||||||
|
//console.log('Packed', onZipData.x.size, onZipData.x.zsize);
|
||||||
}
|
}
|
||||||
}
|
const onZipError = function onZipError() { delete onZipData.x.zacc; }
|
||||||
|
objx.meshAgentBinaries[archid].zacc = [];
|
||||||
// Hash the binary
|
onZipData.x = objx.meshAgentBinaries[archid];
|
||||||
const hashStream = obj.crypto.createHash('sha384');
|
onZipEnd.x = objx.meshAgentBinaries[archid];
|
||||||
hashStream.archid = archid;
|
onZipError.x = objx.meshAgentBinaries[archid];
|
||||||
hashStream.on('data', function (data) {
|
archive.on('data', onZipData);
|
||||||
objx.meshAgentBinaries[this.archid].hash = data.toString('binary');
|
archive.on('end', onZipEnd);
|
||||||
objx.meshAgentBinaries[this.archid].hashhex = data.toString('hex');
|
archive.on('error', onZipError);
|
||||||
if ((--archcount == 0) && (func != null)) { func(); }
|
archive.append(objx.meshAgentBinaries[archid].data, { name: 'meshagent' });
|
||||||
});
|
archive.finalize();
|
||||||
const options = { sourcePath: agentpath, targetStream: hashStream, platform: obj.meshAgentsArchitectureNumbers[archid].platform };
|
|
||||||
if (objx.meshAgentBinaries[archid].pe != null) { options.peinfo = objx.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))) {
|
|
||||||
const hash = obj.crypto.createHash('sha384').update(obj.fs.readFileSync(agentpath));
|
|
||||||
objx.meshAgentBinaries[archid].fileHash = hash.digest('binary');
|
|
||||||
objx.meshAgentBinaries[archid].fileHashHex = Buffer.from(objx.meshAgentBinaries[archid].fileHash, 'binary').toString('hex');
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Hash the binary
|
||||||
|
const hashStream = obj.crypto.createHash('sha384');
|
||||||
|
hashStream.archid = archid;
|
||||||
|
hashStream.on('data', function (data) {
|
||||||
|
objx.meshAgentBinaries[this.archid].hash = data.toString('binary');
|
||||||
|
objx.meshAgentBinaries[this.archid].hashhex = data.toString('hex');
|
||||||
|
if ((--archcount == 0) && (func != null)) { func(); }
|
||||||
|
});
|
||||||
|
const options = { sourcePath: agentpath, targetStream: hashStream, platform: obj.meshAgentsArchitectureNumbers[archid].platform };
|
||||||
|
if (objx.meshAgentBinaries[archid].pe != null) { options.peinfo = objx.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))) {
|
||||||
|
const hash = obj.crypto.createHash('sha384').update(obj.fs.readFileSync(agentpath));
|
||||||
|
objx.meshAgentBinaries[archid].fileHash = hash.digest('binary');
|
||||||
|
objx.meshAgentBinaries[archid].fileHashHex = Buffer.from(objx.meshAgentBinaries[archid].fileHash, 'binary').toString('hex');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
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.
|
|
||||||
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.
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Generate a time limited user login token
|
// Generate a time limited user login token
|
||||||
|
|
Loading…
Reference in a new issue