Completed authenticode standalone tool.

Ylian Saint-Hilaire committed May 28, 2022 at 12:58 UTC 23cacb0aacdb484ea01315003d23fe06ebcc15c0
2 files changed +128 -84
MeshCentralServer.njsproj
+1
@@ -125,6 +125,7 @@
125 <Compile Include="mpsserver.js" />
126 <Compile Include="mqttbroker.js" />
127 <Compile Include="apprelays.js" />
128 + <Compile Include="pkcs7-modified.js" />
129 <Compile Include="pluginHandler.js" />
130 <Compile Include="public\mstsc\client.js" />
131 <Compile Include="public\mstsc\js\keyboard.js" />
authenticode.js
+127 -84
@@ -1,18 +1,77 @@
1 /**
2 * @description Authenticode parsing
3 -* @author Bryan Roe & Ylian Saint-Hilaire
3 +* @author Ylian Saint-Hilaire & Bryan Roe
4 * @copyright Intel Corporation 2018-2022
5 * @license Apache-2.0
6 * @version v0.0.1
7 */
8
9 +const fs = require('fs');
10 +const crypto = require('crypto');
11 +const forge = require('node-forge');
12 +const pki = forge.pki;
13 +const p7 = require('./pkcs7-modified');
14 +
15 +// Generate a test self-signed certificate with code signing extension
16 +function createSelfSignedCert(args) {
17 + var keys = pki.rsa.generateKeyPair(2048);
18 + var cert = pki.createCertificate();
19 + cert.publicKey = keys.publicKey;
20 + cert.serialNumber = (typeof args.serial == 'string')?args.serial:'012345'; // Serial number must always have a single leading '0', otherwise toPEM/fromPEM will not work right.
21 + cert.validity.notBefore = new Date();
22 + cert.validity.notAfter = new Date();
23 + cert.validity.notAfter.setFullYear(cert.validity.notBefore.getFullYear() + 10);
24 + var attrs = [];
25 + if (typeof args.cn == 'string') { attrs.push({ name: 'commonName', value: args.cn }); }
26 + if (typeof args.country == 'string') { attrs.push({ name: 'countryName', value: args.country }); }
27 + if (typeof args.state == 'string') { attrs.push({ name: 'ST', value: args.state }); }
28 + if (typeof args.locality == 'string') { attrs.push({ name: 'localityName', value: args.locality }); }
29 + if (typeof args.org == 'string') { attrs.push({ name: 'organizationName', value: args.org }); }
30 + if (typeof args.orgunit == 'string') { attrs.push({ name: 'OU', value: args.orgunit }); }
31 + cert.setSubject(attrs);
32 + cert.setIssuer(attrs);
33 + cert.setExtensions([{ name: 'basicConstraints', cA: false }, { name: 'keyUsage', keyCertSign: false, digitalSignature: true, nonRepudiation: false, keyEncipherment: false, dataEncipherment: false }, { name: 'extKeyUsage', codeSigning: true }, { name: "subjectKeyIdentifier" }]);
34 + cert.sign(keys.privateKey, forge.md.sha384.create());
35 + return { cert: cert, key: keys.privateKey, extraCerts: [] };
36 +}
37 +
38 +// Create the output filename if not already specified
39 +function createOutFile(args, filename) {
40 + if (typeof args.out == 'string') return;
41 + var outputFileName = filename.split('.');
42 + outputFileName[outputFileName.length - 2] += '-out';
43 + args.out = outputFileName.join('.');
44 +}
45 +
46 +// Load certificates and private key from PEM files
47 +function loadCertificates(args) {
48 + var certs = [], keys = [], pemFileNames = args.pem;
49 + if (pemFileNames == null) return;
50 + if (typeof pemFileNames == 'string') { pemFileNames = [pemFileNames]; }
51 + for (var i in pemFileNames) {
52 + try {
53 + // Read certificate
54 + var pem = fs.readFileSync(pemFileNames[i]).toString();
55 + var pemCerts = pem.split('-----BEGIN CERTIFICATE-----');
56 + for (var j in pemCerts) {
57 + var k = pemCerts[j].indexOf('-----END CERTIFICATE-----');
58 + if (k >= 0) { certs.push(pki.certificateFromPem('-----BEGIN CERTIFICATE-----' + pemCerts[j].substring(0, k) + '-----END CERTIFICATE-----')); }
59 + }
60 + var PemKeys = pem.split('-----BEGIN RSA PRIVATE KEY-----');
61 + for (var j in PemKeys) {
62 + var k = PemKeys[j].indexOf('-----END RSA PRIVATE KEY-----');
63 + if (k >= 0) { keys.push(pki.privateKeyFromPem('-----BEGIN RSA PRIVATE KEY-----' + PemKeys[j].substring(0, k) + '-----END RSA PRIVATE KEY-----')); }
64 + }
65 + } catch (ex) { }
66 + }
67 + if ((certs.length == 0) || (keys.length != 1)) return; // No certificates or private keys
68 + var r = { cert: certs[0], key: keys[0], extraCerts: [] }
69 + if (certs.length > 1) { for (var i = 1; i < certs.length; i++) { r.extraCerts.push(certs[i]); } }
70 + return r;
71 +}
72 +
73 function createAuthenticodeHandler(path) {
74 const obj = {};
11 - const fs = require('fs');
12 - const crypto = require('crypto');
13 - const forge = require('node-forge');
14 - const pki = forge.pki;
15 - const p7 = require('./pkcs7-modified');
75 obj.header = { path: path }
76
77 // Read a file slice
@@ -167,59 +226,36 @@ function createAuthenticodeHandler(path) {
226 while (ptr < end) { const buf = readFileSlice(ptr, Math.min(65536, end - ptr)); hash.update(buf); ptr += buf.length; }
227 }
228
170 - // Generate a test self-signed certificate with code signing extension
171 - obj.createSelfSignedCert = function () {
172 - var keys = pki.rsa.generateKeyPair(2048);
173 - var cert = pki.createCertificate();
174 - cert.publicKey = keys.publicKey;
175 - cert.serialNumber = '00000001';
176 - cert.validity.notBefore = new Date();
177 - cert.validity.notAfter = new Date();
178 - cert.validity.notAfter.setFullYear(cert.validity.notBefore.getFullYear() + 3);
179 - var attrs = [
180 - { name: 'commonName', value: 'example.org' },
181 - { name: 'countryName', value: 'US' },
182 - { shortName: 'ST', value: 'California' },
183 - { name: 'localityName', value: 'Santa Clara' },
184 - { name: 'organizationName', value: 'Test' },
185 - { shortName: 'OU', value: 'Test' }
186 - ];
187 - cert.setSubject(attrs);
188 - cert.setIssuer(attrs);
189 - cert.setExtensions([{ name: 'basicConstraints', cA: false }, { name: 'keyUsage', keyCertSign: false, digitalSignature: true, nonRepudiation: false, keyEncipherment: false, dataEncipherment: false }, { name: 'extKeyUsage', codeSigning: true }, { name: "subjectKeyIdentifier" }]);
190 - cert.sign(keys.privateKey, forge.md.sha384.create());
191 - return { cert: cert, key: keys.privateKey };
192 - }
193 -
229 // Sign the file using the certificate and key. If none is specified, generate a dummy one
195 - obj.sign = function (cert, key, desc, url) {
196 - if ((cert == null) || (key == null)) { var c = obj.createSelfSignedCert(); cert = c.cert; key = c.key; }
230 + obj.sign = function (cert, args) {
231 + if (cert == null) { cert = createSelfSignedCert({ cn: 'Test' }); }
232 var fileHash = getHash('sha384');
233
234 // Create the signature block
235 var p7 = forge.pkcs7.createSignedData();
201 - var content = { "tagClass": 0, "type": 16, "constructed": true, "composed": true, "value": [{ "tagClass": 0, "type": 16, "constructed": true, "composed": true, "value": [{ "tagClass": 0, "type": 6, "constructed": false, "composed": false, "value": forge.asn1.oidToDer("1.3.6.1.4.1.311.2.1.15").data }, { "tagClass": 0, "type": 16, "constructed": true, "composed": true, "value": [{ "tagClass": 0, "type": 3, "constructed": false, "composed": false, "value": "\u0000", "bitStringContents": "\u0000", "original": { "tagClass": 0, "type": 3, "constructed": false, "composed": false, "value": "\u0000" } }, { "tagClass": 128, "type": 0, "constructed": true, "composed": true, "value": [{ "tagClass": 128, "type": 2, "constructed": true, "composed": true, "value": [{ "tagClass": 128, "type": 0, "constructed": false, "composed": false, "value": "" }] }] }] }] }, { "tagClass": 0, "type": 16, "constructed": true, "composed": true, "value": [{ "tagClass": 0, "type": 16, "constructed": true, "composed": true, "value": [{ "tagClass": 0, "type": 6, "constructed": false, "composed": false, "value": forge.asn1.oidToDer(forge.pki.oids.sha384).data }, { "tagClass": 0, "type": 5, "constructed": false, "composed": false, "value": "" }] }, { "tagClass": 0, "type": 4, "constructed": false, "composed": false, "value": fileHash.toString('binary') }] }] };
236 + var content = { 'tagClass': 0, 'type': 16, 'constructed': true, 'composed': true, 'value': [{ 'tagClass': 0, 'type': 16, 'constructed': true, 'composed': true, 'value': [{ 'tagClass': 0, 'type': 6, 'constructed': false, 'composed': false, 'value': forge.asn1.oidToDer('1.3.6.1.4.1.311.2.1.15').data }, { 'tagClass': 0, 'type': 16, 'constructed': true, 'composed': true, 'value': [{ 'tagClass': 0, 'type': 3, 'constructed': false, 'composed': false, 'value': '\u0000', 'bitStringContents': '\u0000', 'original': { 'tagClass': 0, 'type': 3, 'constructed': false, 'composed': false, 'value': '\u0000' } }, { 'tagClass': 128, 'type': 0, 'constructed': true, 'composed': true, 'value': [{ 'tagClass': 128, 'type': 2, 'constructed': true, 'composed': true, 'value': [{ 'tagClass': 128, 'type': 0, 'constructed': false, 'composed': false, 'value': '' }] }] }] }] }, { 'tagClass': 0, 'type': 16, 'constructed': true, 'composed': true, 'value': [{ 'tagClass': 0, 'type': 16, 'constructed': true, 'composed': true, 'value': [{ 'tagClass': 0, 'type': 6, 'constructed': false, 'composed': false, 'value': forge.asn1.oidToDer(forge.pki.oids.sha384).data }, { 'tagClass': 0, 'type': 5, 'constructed': false, 'composed': false, 'value': '' }] }, { 'tagClass': 0, 'type': 4, 'constructed': false, 'composed': false, 'value': fileHash.toString('binary') }] }] };
237 p7.contentInfo = forge.asn1.create(forge.asn1.Class.UNIVERSAL, forge.asn1.Type.SEQUENCE, true, [forge.asn1.create(forge.asn1.Class.UNIVERSAL, forge.asn1.Type.OID, false, forge.asn1.oidToDer('1.3.6.1.4.1.311.2.1.4').getBytes())]);
238 p7.contentInfo.value.push(forge.asn1.create(forge.asn1.Class.CONTEXT_SPECIFIC, 0, true, [content]));
239 p7.content = {}; // We set .contentInfo and have .content empty to bypass node-forge limitation on the type of content it can sign.
205 - p7.addCertificate(cert);
240 + p7.addCertificate(cert.cert);
241 + if (cert.extraCerts) { for (var i = 0; i < cert.extraCerts.length; i++) { p7.addCertificate(cert.extraCerts[0]); } } // Add any extra certificates that form the cert chain
242
243 // Build authenticated attributes
244 var authenticatedAttributes = [
245 { type: forge.pki.oids.contentType, value: forge.pki.oids.data },
210 - { type: forge.pki.oids.messageDigest } // value will be auto-populated at signing time
246 + { type: forge.pki.oids.messageDigest } // This value will populated at signing time by node-forge
247 ]
212 - if ((desc != null) || (url != null)) {
213 - var codeSigningAttributes = { "tagClass": 0, "type": 16, "constructed": true, "composed": true, "value": [ ] };
214 - if (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(desc, 'ucs2').toString() }] }); }
215 - if (url != null) { codeSigningAttributes.value.push({ "tagClass": 128, "type": 1, "constructed": true, "composed": true, "value": [{ "tagClass": 128, "type": 0, "constructed": false, "composed": false, "value": url }] }); }
248 + 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() }] }); }
251 + 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 }] }); }
252 authenticatedAttributes.push({ type: obj.Oids.SPC_SP_OPUS_INFO_OBJID, value: codeSigningAttributes });
253 }
254
255 // Add the signer and sign
256 p7.addSigner({
221 - key: key,
222 - certificate: cert,
257 + key: cert.key,
258 + certificate: cert.cert,
259 digestAlgorithm: forge.pki.oids.sha384,
260 authenticatedAttributes: authenticatedAttributes
261 });
@@ -227,13 +263,8 @@ function createAuthenticodeHandler(path) {
263 var p7signature = Buffer.from(forge.pkcs7.messageToPem(p7).split('-----BEGIN PKCS7-----')[1].split('-----END PKCS7-----')[0], 'base64');
264 //console.log('Signature', Buffer.from(p7signature, 'binary').toString('base64'));
265
230 - // Create the output filename
231 - var outputFileName = this.path.split('.');
232 - outputFileName[outputFileName.length - 2] += '-jsigned';
233 - outputFileName = outputFileName.join('.');
234 -
235 - // Open the file
236 - var output = fs.openSync(outputFileName, 'w');
266 + // Open the outut file
267 + var output = fs.openSync(args.out, 'w');
268 var tmp, written = 0;
269 var executableSize = obj.header.sigpos ? obj.header.sigpos : this.filesize;
270
@@ -275,14 +306,9 @@ function createAuthenticodeHandler(path) {
306 }
307
308 // Save an executable without the signature
278 - obj.unsign = function (cert, key) {
279 - // Create the output filename
280 - var outputFileName = this.path.split('.');
281 - outputFileName[outputFileName.length - 2] += '-junsigned';
282 - outputFileName = outputFileName.join('.');
283 -
309 + obj.unsign = function (args) {
310 // Open the file
285 - var output = fs.openSync(outputFileName, 'w');
311 + var output = fs.openSync(args.out, 'w');
312 var written = 0, totalWrite = obj.header.sigpos;
313
314 // Compute pre-header length and copy that to the new file
@@ -319,19 +345,28 @@ function start() {
345 console.log(" node authenticode.js [command] [options]");
346 console.log("Commands:");
347 console.log(" info: Show information about an executable.");
322 - console.log(" --json Optional, Show information in JSON format.");
348 + console.log(" --json Show information in JSON format.");
349 console.log(" sign: Sign an executable.");
324 - console.log(" --exe [file] Executable to sign.");
325 - console.log(" --out [file] Optional resulting signed executable.");
326 - console.log(" --cert [pemfile] Certificate to sign the executable with.");
327 - console.log(" --key [pemfile] Private key to use to sign the executable.");
328 - console.log(" --desc [description] Optional description string to embbed into signature.");
329 - console.log(" --url [url] Optional URL to embbed into signature.");
350 + console.log(" --exe [file] Required executable to sign.");
351 + console.log(" --out [file] Resulting signed executable.");
352 + console.log(" --pem [pemfile] Certificate & private key to sign the executable with.");
353 + console.log(" --desc [description] Description string to embbed into signature.");
354 + console.log(" --url [url] URL to embbed into signature.");
355 console.log(" unsign: Remove the signature from the executable.");
331 - console.log(" --exe [file] Executable to un-sign.");
332 - console.log(" --out [file] Optional resulting executable with signature removed.");
333 - console.log(" createcert: Create a self-signed certificate and key.");
334 - console.log(" --cn [commonName] Certificate common name.");
356 + console.log(" --exe [file] Required executable to un-sign.");
357 + console.log(" --out [file] Resulting executable with signature removed.");
358 + console.log(" createcert: Create a code signging self-signed certificate and key.");
359 + console.log(" --out [pemfile] Required certificate file to create.");
360 + console.log(" --cn [value] Required certificate common name.");
361 + console.log(" --country [value] Certificate country name.");
362 + console.log(" --state [value] Certificate state name.");
363 + console.log(" --locality [value] Certificate locality name.");
364 + console.log(" --org [value] Certificate organization name.");
365 + console.log(" --ou [value] Certificate organization unit name.");
366 + console.log(" --serial [value] Certificate serial number.");
367 + console.log("");
368 + console.log("Note that certificate PEM files must first have the signing certificate,");
369 + console.log("followed by all certificates that form the trust chain.");
370 return;
371 }
372
@@ -353,7 +388,7 @@ function start() {
388
389 // Execute the command
390 var command = process.argv[2].toLowerCase();
356 - if (command == 'info') {
391 + if (command == 'info') { // Get signature information about an executable
392 if (exe == null) { console.log("Missing --exe [filename]"); return; }
393 if (args.json) {
394 var r = { header: exe.header, filesize: exe.filesize }
@@ -363,27 +398,35 @@ function start() {
398 if (exe.signingAttribs && exe.signingAttribs.length > 0) { r.signAttributes = exe.signingAttribs; }
399 console.log(JSON.stringify(r, null, 2));
400 } else {
366 - console.log('Header', exe.header);
367 - if (exe.fileHashAlgo != null) { console.log('fileHashMethod:', exe.fileHashAlgo); }
368 - if (exe.fileHashSigned != null) { console.log('fileHashSigned:', exe.fileHashSigned.toString('hex')); }
369 - if (exe.fileHashActual != null) { console.log('fileHashActual:', exe.fileHashActual.toString('hex')); }
370 - if (exe.signingAttribs && exe.signingAttribs.length > 0) { console.log('Signature Attributes:'); for (var i in exe.signingAttribs) { console.log(' ' + exe.signingAttribs[i]); } }
371 - console.log('FileLen: ' + exe.filesize);
401 + console.log("Header", exe.header);
402 + if (exe.fileHashAlgo != null) { console.log("Hash Method:", exe.fileHashAlgo); }
403 + if (exe.fileHashSigned != null) { console.log("Signed Hash:", exe.fileHashSigned.toString('hex')); }
404 + if (exe.fileHashActual != null) { console.log("Actual Hash:", exe.fileHashActual.toString('hex')); }
405 + if (exe.signingAttribs && exe.signingAttribs.length > 0) { console.log("Signature Attributes:"); for (var i in exe.signingAttribs) { console.log(' ' + exe.signingAttribs[i]); } }
406 + console.log("File Length: " + exe.filesize);
407 }
408 }
374 - if (command == 'sign') {
375 - if (exe == null) { console.log("Missing --exe [filename]"); return; }
376 - var desc = null, url = null;
377 - if (process.argv.length > 4) { desc = process.argv[4]; }
378 - if (process.argv.length > 5) { url = process.argv[5]; }
379 - console.log('Signing...'); exe.sign(null, null, desc, url); console.log('Done.');
409 + if (command == 'sign') { // Sign an executable
410 + if (typeof args.exe != 'string') { console.log("Missing --exe [filename]"); return; }
411 + createOutFile(args, args.exe);
412 + const cert = loadCertificates(args);
413 + if (cert == null) { console.log("Unable to load certificate and/or private key, generating text certificate."); }
414 + console.log("Signing to " + args.out); exe.sign(cert, args); console.log("Done.");
415 }
381 - if (command == 'unsign') {
382 - if (exe == null) { console.log("Missing --exe [filename]"); return; }
383 - if (exe.header.signed) { console.log('Unsigning...'); exe.unsign(); console.log('Done.'); } else { console.log('Executable is not signed.'); }
416 + if (command == 'unsign') { // Unsign an executable
417 + if (typeof args.exe != 'string') { console.log("Missing --exe [filename]"); return; }
418 + createOutFile(args, args.exe);
419 + if (exe.header.signed) { console.log("Unsigning to " + args.out); exe.unsign(args); console.log("Done."); } else { console.log("Executable is not signed."); }
420 }
385 - if (command == 'createcert') {
386 -
421 + if (command == 'createcert') { // Create a code signing certificate and private key
422 + if (typeof args.out != 'string') { console.log("Missing --out [filename]"); return; }
423 + if (typeof args.cn != 'string') { console.log("Missing --cn [name]"); return; }
424 + if (typeof args.serial == 'string') { if (args.serial != parseInt(args.serial)) { console.log("Invalid serial number."); return; } else { args.serial = parseInt(args.serial); } }
425 + if (typeof args.serial == 'number') { args.serial = '0' + args.serial; } // Serial number must be a integer string with a single leading '0'
426 + const cert = createSelfSignedCert(args);
427 + console.log("Writing to " + args.out);
428 + fs.writeFileSync(args.out, pki.certificateToPem(cert.cert) + '\r\n' + pki.privateKeyToPem(cert.key));
429 + console.log("Done.");
430 }
431
432 // Close the file