diff --git a/docs/docs/meshcentral/codesigning.md b/docs/docs/meshcentral/codesigning.md index 25e7a37a..5cb30d00 100644 --- a/docs/docs/meshcentral/codesigning.md +++ b/docs/docs/meshcentral/codesigning.md @@ -99,3 +99,50 @@ Now that MeshCentral customizes and signs the agent, you can set that value to a } } ``` + +## External Signing Job + +The externalsignjob feature allows you to perform additional operations on the agent after MeshCentral completes its code signing process. This is particularly useful for: + +1. Using hardware security tokens for signing +2. Performing signing on a separate server or cloud host +3. Archiving signed agents +4. Adding additional security measures + +The externalsignjob is called after MeshCentral completes its entire code signing process, including: +- Resource modification +- Digital signature application +- Timestamp application (if configured) + +To use this feature, add the following to your config.json: + +```json +"settings": { + "externalsignjob": "path/to/your/script.bat" +} +``` + +The script will receive the path to the agent as its first argument. Here are example scripts: + +### Batch File Example +```batch +@echo off +Echo External Signing Job +signtool sign /tr http://timestamp.sectigo.com /td SHA256 /fd SHA256 /a /v /f path/to/your/signing.cer /csp "eToken Base Cryptographic Provider" /k "[{{MyPassword}}]=PrivateKeyContainerName" "%~1" +``` + +### PowerShell Example +```powershell +$file = $args[0] +signtool sign /tr http://timestamp.sectigo.com /td SHA256 /fd SHA256 /a /v /f path/to/your/signing.cer /csp "eToken Base Cryptographic Provider" /k "[{{MyPassword}}]=PrivateKeyContainerName" $file +``` + +The externalsignjob can be used for more than just signing. For example, you could: + +1. Archive signed agents to a secure location +2. Upload signed agents to a distribution server +3. Perform additional security checks +4. Add custom metadata or watermarks +5. Integrate with your organization's build pipeline + +Note: The script must return a success exit code (0) for the process to be considered successful. Any non-zero exit code will be treated as a failure and will be logged. diff --git a/meshcentral.js b/meshcentral.js index ed5362df..ee6af05f 100644 --- a/meshcentral.js +++ b/meshcentral.js @@ -3415,6 +3415,7 @@ function CreateMeshCentralServer(config, args) { // Failed to sign agent addServerWarning('Failed to sign \"' + agentSignedFunc.objx.meshAgentsArchitectureNumbers[agentSignedFunc.archid].localname + '\": ' + err, 22, [agentSignedFunc.objx.meshAgentsArchitectureNumbers[agentSignedFunc.archid].localname, err]); } + obj.callExternalSignJob(agentSignedFunc.signingArguments); // Call external signing job regardless of success or failure if (--pendingOperations === 0) { agentSignedFunc.func(); } } pendingOperations++; @@ -3470,7 +3471,10 @@ function CreateMeshCentralServer(config, args) { } const signingArguments = { out: signeedagentpath, desc: signDesc, url: signUrl, time: timeStampUrl, proxy: timeStampProxy }; // Shallow clone + signingArguments.resChanges = resChanges; + obj.debug('main', "Code signing with arguments: " + JSON.stringify(signingArguments)); + xagentSignedFunc.signingArguments = signingArguments; // Attach the signing arguments to the callback function if (resChanges == false) { // Sign the agent the simple way, without changing any resources. originalAgent.sign(agentSignCertInfo, signingArguments, xagentSignedFunc); @@ -3479,16 +3483,40 @@ function CreateMeshCentralServer(config, args) { // NOTE: This is experimental and could corupt the agent. originalAgent.writeExecutable(signingArguments, agentSignCertInfo, xagentSignedFunc); } + } else { // Signed agent is already ok, use it. originalAgent.close(); } + + } } if (--pendingOperations === 0) { func(); } } + obj.callExternalSignJob = function (signingArguments) { + if (obj.config.settings && !obj.config.settings.externalsignjob) { + return; + } + obj.debug('main', "External signing job called for file: " + signingArguments.out); + + const { spawnSync } = require('child_process'); + + const signResult = spawnSync('"' + obj.config.settings.externalsignjob + '"', ['"' + signingArguments.out + '"'], { + encoding: 'utf-8', + shell: true, + stdio: 'inherit' + }); + + if (signResult.error || signResult.status !== 0) { + obj.debug('main', "External signing failed for file: " + signingArguments.out); + console.error("External signing failed for file: " + signingArguments.out); + return; + } + } + // Update the list of available mesh agents obj.updateMeshAgentsTable = function (domain, func) { // Check if a custom agent signing certificate is available