2022-05-27 00:08:50 -04:00
/ * *
2022-05-25 20:37:02 -04:00
* @ description Authenticode parsing
2022-05-28 15:58:35 -04:00
* @ author Ylian Saint - Hilaire & Bryan Roe
2022-05-25 20:37:02 -04:00
* @ copyright Intel Corporation 2018 - 2022
* @ license Apache - 2.0
* @ version v0 . 0.1
* /
2022-05-29 01:34:48 -04:00
/*jslint node: true */
/*jshint node: true */
/*jshint strict:false */
/*jshint -W097 */
/*jshint esversion: 6 */
"use strict" ;
2022-05-28 15:58:35 -04:00
const fs = require ( 'fs' ) ;
const crypto = require ( 'crypto' ) ;
const forge = require ( 'node-forge' ) ;
const pki = forge . pki ;
const p7 = require ( './pkcs7-modified' ) ;
// Generate a test self-signed certificate with code signing extension
function createSelfSignedCert ( args ) {
var keys = pki . rsa . generateKeyPair ( 2048 ) ;
var cert = pki . createCertificate ( ) ;
cert . publicKey = keys . publicKey ;
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.
cert . validity . notBefore = new Date ( ) ;
cert . validity . notAfter = new Date ( ) ;
cert . validity . notAfter . setFullYear ( cert . validity . notBefore . getFullYear ( ) + 10 ) ;
var attrs = [ ] ;
if ( typeof args . cn == 'string' ) { attrs . push ( { name : 'commonName' , value : args . cn } ) ; }
if ( typeof args . country == 'string' ) { attrs . push ( { name : 'countryName' , value : args . country } ) ; }
if ( typeof args . state == 'string' ) { attrs . push ( { name : 'ST' , value : args . state } ) ; }
if ( typeof args . locality == 'string' ) { attrs . push ( { name : 'localityName' , value : args . locality } ) ; }
if ( typeof args . org == 'string' ) { attrs . push ( { name : 'organizationName' , value : args . org } ) ; }
if ( typeof args . orgunit == 'string' ) { attrs . push ( { name : 'OU' , value : args . orgunit } ) ; }
cert . setSubject ( attrs ) ;
cert . setIssuer ( attrs ) ;
cert . setExtensions ( [ { name : 'basicConstraints' , cA : false } , { name : 'keyUsage' , keyCertSign : false , digitalSignature : true , nonRepudiation : false , keyEncipherment : false , dataEncipherment : false } , { name : 'extKeyUsage' , codeSigning : true } , { name : "subjectKeyIdentifier" } ] ) ;
cert . sign ( keys . privateKey , forge . md . sha384 . create ( ) ) ;
return { cert : cert , key : keys . privateKey , extraCerts : [ ] } ;
}
// Create the output filename if not already specified
function createOutFile ( args , filename ) {
if ( typeof args . out == 'string' ) return ;
var outputFileName = filename . split ( '.' ) ;
outputFileName [ outputFileName . length - 2 ] += '-out' ;
args . out = outputFileName . join ( '.' ) ;
}
// Load certificates and private key from PEM files
2022-05-29 01:59:21 -04:00
function loadCertificates ( pemFileNames ) {
var certs = [ ] , keys = [ ] ;
2022-05-28 15:58:35 -04:00
if ( pemFileNames == null ) return ;
if ( typeof pemFileNames == 'string' ) { pemFileNames = [ pemFileNames ] ; }
for ( var i in pemFileNames ) {
try {
// Read certificate
var pem = fs . readFileSync ( pemFileNames [ i ] ) . toString ( ) ;
var pemCerts = pem . split ( '-----BEGIN CERTIFICATE-----' ) ;
for ( var j in pemCerts ) {
var k = pemCerts [ j ] . indexOf ( '-----END CERTIFICATE-----' ) ;
if ( k >= 0 ) { certs . push ( pki . certificateFromPem ( '-----BEGIN CERTIFICATE-----' + pemCerts [ j ] . substring ( 0 , k ) + '-----END CERTIFICATE-----' ) ) ; }
}
var PemKeys = pem . split ( '-----BEGIN RSA PRIVATE KEY-----' ) ;
for ( var j in PemKeys ) {
var k = PemKeys [ j ] . indexOf ( '-----END RSA PRIVATE KEY-----' ) ;
if ( k >= 0 ) { keys . push ( pki . privateKeyFromPem ( '-----BEGIN RSA PRIVATE KEY-----' + PemKeys [ j ] . substring ( 0 , k ) + '-----END RSA PRIVATE KEY-----' ) ) ; }
}
} catch ( ex ) { }
}
if ( ( certs . length == 0 ) || ( keys . length != 1 ) ) return ; // No certificates or private keys
var r = { cert : certs [ 0 ] , key : keys [ 0 ] , extraCerts : [ ] }
if ( certs . length > 1 ) { for ( var i = 1 ; i < certs . length ; i ++ ) { r . extraCerts . push ( certs [ i ] ) ; } }
return r ;
}
2022-05-25 20:37:02 -04:00
function createAuthenticodeHandler ( path ) {
const obj = { } ;
obj . header = { path : path }
// Read a file slice
function readFileSlice ( start , length ) {
var buffer = Buffer . alloc ( length ) ;
var len = fs . readSync ( obj . fd , buffer , 0 , buffer . length , start ) ;
if ( len < buffer . length ) { buffer = buffer . slice ( 0 , len ) ; }
return buffer ;
}
// Close the file
obj . close = function ( ) {
if ( obj . fd == null ) return ;
fs . closeSync ( obj . fd ) ;
delete obj . fd ;
}
// Private OIDS
obj . Oids = {
SPC _INDIRECT _DATA _OBJID : '1.3.6.1.4.1.311.2.1.4' ,
SPC _STATEMENT _TYPE _OBJID : '1.3.6.1.4.1.311.2.1.11' ,
SPC _SP _OPUS _INFO _OBJID : '1.3.6.1.4.1.311.2.1.12' ,
SPC _INDIVIDUAL _SP _KEY _PURPOSE _OBJID : '1.3.6.1.4.1.311.2.1.21' ,
SPC _COMMERCIAL _SP _KEY _PURPOSE _OBJID : '1.3.6.1.4.1.311.2.1.22' ,
SPC _MS _JAVA _SOMETHING : '1.3.6.1.4.1.311.15.1' ,
SPC _PE _IMAGE _DATA _OBJID : '1.3.6.1.4.1.311.2.1.15' ,
SPC _CAB _DATA _OBJID : '1.3.6.1.4.1.311.2.1.25' ,
SPC _TIME _STAMP _REQUEST _OBJID : '1.3.6.1.4.1.311.3.2.1' ,
SPC _SIPINFO _OBJID : '1.3.6.1.4.1.311.2.1.30' ,
SPC _PE _IMAGE _PAGE _HASHES _V1 : '1.3.6.1.4.1.311.2.3.1' ,
SPC _PE _IMAGE _PAGE _HASHES _V2 : '1.3.6.1.4.1.311.2.3.2' ,
SPC _NESTED _SIGNATURE _OBJID : '1.3.6.1.4.1.311.2.4.1' ,
SPC _RFC3161 _OBJID : '1.3.6.1.4.1.311.3.3.1'
}
// Open the file and read header information
function openFile ( ) {
2022-05-29 01:34:48 -04:00
if ( obj . fd != null ) return true ;
2022-05-25 20:37:02 -04:00
// Open the file descriptor
2022-05-26 01:26:05 -04:00
obj . path = path ;
2022-05-29 01:34:48 -04:00
try { obj . fd = fs . openSync ( path ) ; } catch ( ex ) { return false ; } // Unable to open file
2022-05-25 20:37:02 -04:00
obj . stats = fs . fstatSync ( obj . fd ) ;
obj . filesize = obj . stats . size ;
2022-05-29 01:34:48 -04:00
if ( obj . filesize < 64 ) { obj . close ( ) ; return false ; } // File too short.
2022-05-25 20:37:02 -04:00
// Read the PE header size
var buf = readFileSlice ( 60 , 4 ) ;
obj . header . header _size = buf . readUInt32LE ( 0 ) ;
// Check file size and PE header
2022-05-29 01:34:48 -04:00
if ( obj . filesize < ( 160 + obj . header . header _size ) ) { obj . close ( ) ; return false ; } // Invalid SizeOfHeaders.
if ( readFileSlice ( obj . header . header _size , 4 ) . toString ( 'hex' ) != '50450000' ) { obj . close ( ) ; return false ; } // Invalid PE File.
2022-05-25 20:37:02 -04:00
// Check header magic data
var magic = readFileSlice ( obj . header . header _size + 24 , 2 ) . readUInt16LE ( 0 ) ;
switch ( magic ) {
case 0x20b : obj . header . pe32plus = 1 ; break ;
case 0x10b : obj . header . pe32plus = 0 ; break ;
2022-05-29 01:34:48 -04:00
default : { obj . close ( ) ; return false ; } // Invalid Magic in PE
2022-05-25 20:37:02 -04:00
}
// Read PE header information
obj . header . pe _checksum = readFileSlice ( obj . header . header _size + 88 , 4 ) . readUInt32LE ( 0 ) ;
obj . header . numRVA = readFileSlice ( obj . header . header _size + 116 + ( obj . header . pe32plus * 16 ) , 4 ) . readUInt32LE ( 0 ) ;
buf = readFileSlice ( obj . header . header _size + 152 + ( obj . header . pe32plus * 16 ) , 8 ) ;
obj . header . sigpos = buf . readUInt32LE ( 0 ) ;
obj . header . siglen = buf . readUInt32LE ( 4 ) ;
obj . header . signed = ( ( obj . header . sigpos != 0 ) && ( obj . header . siglen != 0 ) ) ;
2022-05-26 13:32:50 -04:00
if ( obj . header . signed ) {
2022-05-25 20:37:02 -04:00
// Read signature block
2022-05-26 01:26:05 -04:00
2022-05-27 00:08:50 -04:00
// Check if the file size allows for the signature block
2022-05-29 01:34:48 -04:00
if ( obj . filesize < ( obj . header . sigpos + obj . header . siglen ) ) { obj . close ( ) ; return false ; } // Executable file too short to contain the signature block.
2022-05-27 00:08:50 -04:00
2022-05-26 13:32:50 -04:00
// Remove the padding if needed
var i , pkcs7raw = readFileSlice ( obj . header . sigpos + 8 , obj . header . siglen - 8 ) ;
var derlen = forge . asn1 . getBerValueLength ( forge . util . createBuffer ( pkcs7raw . slice ( 1 , 5 ) ) ) + 4 ;
if ( derlen != pkcs7raw . length ) { pkcs7raw = pkcs7raw . slice ( 0 , derlen ) ; }
2022-05-27 17:51:48 -04:00
//console.log('pkcs7raw', Buffer.from(pkcs7raw, 'binary').toString('base64'));
2022-05-26 13:32:50 -04:00
// Decode the signature block
2022-05-25 20:37:02 -04:00
var pkcs7der = forge . asn1 . fromDer ( forge . util . createBuffer ( pkcs7raw ) ) ;
2022-05-27 15:35:41 -04:00
// To work around ForgeJS PKCS#7 limitation, this may break PKCS7 verify if ForjeJS adds support for it in the future
// Switch content type from "1.3.6.1.4.1.311.2.1.4" to "1.2.840.113549.1.7.1"
2022-05-25 20:37:02 -04:00
pkcs7der . value [ 1 ] . value [ 0 ] . value [ 2 ] . value [ 0 ] . value = forge . asn1 . oidToDer ( forge . pki . oids . data ) . data ;
// Decode the PKCS7 message
var pkcs7 = p7 . messageFromAsn1 ( pkcs7der ) ;
2022-05-27 15:35:41 -04:00
var pkcs7content = pkcs7 . rawCapture . content . value [ 0 ] ;
2022-05-27 00:08:50 -04:00
2022-05-27 15:35:41 -04:00
/ *
// Verify a PKCS#7 signature
// Verify is not currently supported in node-forge, but if implemented in the future, this code could work.
var caStore = forge . pki . createCaStore ( ) ;
for ( var i in obj . certificates ) { caStore . addCertificate ( obj . certificates [ i ] ) ; }
// Return is true if all signatures are valid and chain up to a provided CA
if ( ! pkcs7 . verify ( caStore ) ) { throw ( 'Executable file has an invalid signature.' ) ; }
* /
2022-05-27 00:08:50 -04:00
2022-05-27 20:34:25 -04:00
// Get the signing attributes
2022-05-27 17:51:48 -04:00
obj . signingAttribs = [ ] ;
for ( var i in pkcs7 . rawCapture . authenticatedAttributes ) {
if ( forge . asn1 . derToOid ( pkcs7 . rawCapture . authenticatedAttributes [ i ] . value [ 0 ] . value ) == obj . Oids . SPC _SP _OPUS _INFO _OBJID ) {
for ( var j in pkcs7 . rawCapture . authenticatedAttributes [ i ] . value [ 1 ] . value [ 0 ] . value ) {
2022-05-27 20:34:25 -04:00
var v = pkcs7 . rawCapture . authenticatedAttributes [ i ] . value [ 1 ] . value [ 0 ] . value [ j ] . value [ 0 ] . value ;
if ( v . startsWith ( 'http://' ) || v . startsWith ( 'https://' ) || ( ( v . length % 2 ) == 1 ) ) { obj . signingAttribs . push ( v ) ; } else {
var r = "" ; // This string value is in UCS2 format, convert it to a normal string.
for ( var k = 0 ; k < v . length ; k += 2 ) { r += String . fromCharCode ( ( v . charCodeAt ( k + 8 ) << 8 ) + v . charCodeAt ( k + 1 ) ) ; }
obj . signingAttribs . push ( r ) ;
}
2022-05-27 17:51:48 -04:00
}
}
}
2022-05-25 20:37:02 -04:00
// Set the certificate chain
obj . certificates = pkcs7 . certificates ;
// Get the file hashing algorithm
var hashAlgoOid = forge . asn1 . derToOid ( pkcs7content . value [ 1 ] . value [ 0 ] . value [ 0 ] . value ) ;
switch ( hashAlgoOid ) {
case forge . pki . oids . sha256 : { obj . fileHashAlgo = 'sha256' ; break ; }
case forge . pki . oids . sha384 : { obj . fileHashAlgo = 'sha384' ; break ; }
case forge . pki . oids . sha512 : { obj . fileHashAlgo = 'sha512' ; break ; }
case forge . pki . oids . sha224 : { obj . fileHashAlgo = 'sha224' ; break ; }
case forge . pki . oids . md5 : { obj . fileHashAlgo = 'md5' ; break ; }
}
// Get the signed file hash
obj . fileHashSigned = Buffer . from ( pkcs7content . value [ 1 ] . value [ 1 ] . value , 'binary' )
// Compute the actual file hash
2022-05-29 01:34:48 -04:00
if ( obj . fileHashAlgo != null ) { obj . fileHashActual = obj . getHash ( obj . fileHashAlgo ) ; }
2022-05-25 20:37:02 -04:00
}
2022-05-29 01:34:48 -04:00
return true ;
2022-05-25 20:37:02 -04:00
}
// Hash the file using the selected hashing system
2022-05-29 01:34:48 -04:00
obj . getHash = function ( algo ) {
2022-05-25 20:37:02 -04:00
var hash = crypto . createHash ( algo ) ;
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 + 152 + ( obj . header . pe32plus * 16 ) + 8 , obj . header . sigpos > 0 ? obj . header . sigpos : obj . filesize ) ;
return hash . digest ( ) ;
}
// Hash the file from start to end loading 64k chunks
function runHash ( hash , start , end ) {
var ptr = start ;
while ( ptr < end ) { const buf = readFileSlice ( ptr , Math . min ( 65536 , end - ptr ) ) ; hash . update ( buf ) ; ptr += buf . length ; }
}
// Sign the file using the certificate and key. If none is specified, generate a dummy one
2022-05-28 15:58:35 -04:00
obj . sign = function ( cert , args ) {
if ( cert == null ) { cert = createSelfSignedCert ( { cn : 'Test' } ) ; }
2022-05-29 01:34:48 -04:00
var fileHash = obj . getHash ( 'sha384' ) ;
2022-05-27 15:35:41 -04:00
// Create the signature block
2022-05-25 20:37:02 -04:00
var p7 = forge . pkcs7 . createSignedData ( ) ;
2022-05-28 15:58:35 -04:00
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' ) } ] } ] } ;
2022-05-27 15:35:41 -04:00
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 ( ) ) ] ) ;
p7 . contentInfo . value . push ( forge . asn1 . create ( forge . asn1 . Class . CONTEXT _SPECIFIC , 0 , true , [ content ] ) ) ;
p7 . content = { } ; // We set .contentInfo and have .content empty to bypass node-forge limitation on the type of content it can sign.
2022-05-28 15:58:35 -04:00
p7 . addCertificate ( cert . cert ) ;
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
2022-05-27 17:51:48 -04:00
// Build authenticated attributes
var authenticatedAttributes = [
{ type : forge . pki . oids . contentType , value : forge . pki . oids . data } ,
2022-05-28 15:58:35 -04:00
{ type : forge . pki . oids . messageDigest } // This value will populated at signing time by node-forge
2022-05-27 17:51:48 -04:00
]
2022-05-28 15:58:35 -04:00
if ( ( typeof args . desc == 'string' ) || ( typeof args . url == 'string' ) ) {
2022-05-29 01:34:48 -04:00
var codeSigningAttributes = { 'tagClass' : 0 , 'type' : 16 , 'constructed' : true , 'composed' : true , 'value' : [ ] } ;
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 } ] } ) ;
}
2022-05-28 15:58:35 -04:00
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 } ] } ) ; }
2022-05-27 17:51:48 -04:00
authenticatedAttributes . push ( { type : obj . Oids . SPC _SP _OPUS _INFO _OBJID , value : codeSigningAttributes } ) ;
}
// Add the signer and sign
2022-05-25 20:37:02 -04:00
p7 . addSigner ( {
2022-05-28 15:58:35 -04:00
key : cert . key ,
certificate : cert . cert ,
2022-05-25 20:37:02 -04:00
digestAlgorithm : forge . pki . oids . sha384 ,
2022-05-27 17:51:48 -04:00
authenticatedAttributes : authenticatedAttributes
2022-05-25 20:37:02 -04:00
} ) ;
p7 . sign ( ) ;
var p7signature = Buffer . from ( forge . pkcs7 . messageToPem ( p7 ) . split ( '-----BEGIN PKCS7-----' ) [ 1 ] . split ( '-----END PKCS7-----' ) [ 0 ] , 'base64' ) ;
2022-05-27 17:51:48 -04:00
//console.log('Signature', Buffer.from(p7signature, 'binary').toString('base64'));
2022-05-27 03:26:01 -04:00
2022-05-28 15:58:35 -04:00
// Open the outut file
var output = fs . openSync ( args . out , 'w' ) ;
2022-05-27 00:08:50 -04:00
var tmp , written = 0 ;
var executableSize = obj . header . sigpos ? obj . header . sigpos : this . filesize ;
// Compute pre-header length and copy that to the new file
var preHeaderLen = ( obj . header . header _size + 152 + ( obj . header . pe32plus * 16 ) ) ;
var tmp = readFileSlice ( written , preHeaderLen ) ;
fs . writeSync ( output , tmp ) ;
written += tmp . length ;
2022-05-26 13:32:50 -04:00
// Quad Align the results, adding padding if necessary
2022-05-27 00:08:50 -04:00
var len = executableSize + p7signature . length ;
2022-05-26 13:32:50 -04:00
var padding = ( 8 - ( ( len ) % 8 ) ) % 8 ;
2022-05-26 01:26:05 -04:00
2022-05-27 00:08:50 -04:00
// Write the signature header
2022-05-26 01:26:05 -04:00
var addresstable = Buffer . alloc ( 8 ) ;
2022-05-27 00:08:50 -04:00
addresstable . writeUInt32LE ( executableSize ) ;
2022-05-26 01:26:05 -04:00
addresstable . writeUInt32LE ( 8 + p7signature . length + padding , 4 ) ;
2022-05-27 00:08:50 -04:00
fs . writeSync ( output , addresstable ) ;
written += addresstable . length ;
2022-05-26 01:26:05 -04:00
2022-05-27 00:08:50 -04:00
// Copy the rest of the file until the start of the signature block
while ( ( executableSize - written ) > 0 ) {
tmp = readFileSlice ( written , Math . min ( executableSize - written , 65536 ) ) ;
2022-05-26 01:26:05 -04:00
fs . writeSync ( output , tmp ) ;
written += tmp . length ;
}
2022-05-26 13:32:50 -04:00
2022-05-27 00:08:50 -04:00
// Write the signature block header and signature
2022-05-26 01:26:05 -04:00
var win = Buffer . alloc ( 8 ) ; // WIN CERTIFICATE Structure
win . writeUInt32LE ( p7signature . length + padding + 8 ) ; // DWORD length
win . writeUInt16LE ( 512 , 4 ) ; // WORD revision
win . writeUInt16LE ( 2 , 6 ) ; // WORD type
fs . writeSync ( output , win ) ;
fs . writeSync ( output , p7signature ) ;
if ( padding > 0 ) { fs . writeSync ( output , Buffer . alloc ( padding , 0 ) ) ; }
2022-05-27 00:08:50 -04:00
// Close the file
fs . closeSync ( output ) ;
}
// Save an executable without the signature
2022-05-28 15:58:35 -04:00
obj . unsign = function ( args ) {
2022-05-27 00:08:50 -04:00
// Open the file
2022-05-28 15:58:35 -04:00
var output = fs . openSync ( args . out , 'w' ) ;
2022-05-27 00:08:50 -04:00
var written = 0 , totalWrite = obj . header . sigpos ;
// Compute pre-header length and copy that to the new file
var preHeaderLen = ( obj . header . header _size + 152 + ( obj . header . pe32plus * 16 ) ) ;
var tmp = readFileSlice ( written , preHeaderLen ) ;
fs . writeSync ( output , tmp ) ;
written += tmp . length ;
// Write the new signature header
fs . writeSync ( output , Buffer . alloc ( 8 ) ) ;
written += 8 ;
// Copy the rest of the file until the start of the signature block
while ( ( totalWrite - written ) > 0 ) {
tmp = readFileSlice ( written , Math . min ( totalWrite - written , 65536 ) ) ;
fs . writeSync ( output , tmp ) ;
written += tmp . length ;
}
2022-05-26 01:26:05 -04:00
fs . closeSync ( output ) ;
2022-05-25 20:37:02 -04:00
}
2022-05-29 01:34:48 -04:00
// Return null if we could not open the file
return ( openFile ( ) ? obj : null ) ;
2022-05-25 20:37:02 -04:00
}
function start ( ) {
2022-05-27 20:34:25 -04:00
// Parse the arguments
const args = require ( 'minimist' ) ( process . argv . slice ( 2 ) ) ;
2022-05-25 20:37:02 -04:00
// Show tool help
2022-05-27 20:34:25 -04:00
if ( process . argv . length < 3 ) {
2022-05-25 20:37:02 -04:00
console . log ( "MeshCentral Authenticode Tool." ) ;
console . log ( "Usage:" ) ;
2022-05-27 20:34:25 -04:00
console . log ( " node authenticode.js [command] [options]" ) ;
2022-05-25 20:37:02 -04:00
console . log ( "Commands:" ) ;
2022-05-27 20:34:25 -04:00
console . log ( " info: Show information about an executable." ) ;
2022-05-28 15:58:35 -04:00
console . log ( " --json Show information in JSON format." ) ;
2022-05-27 20:34:25 -04:00
console . log ( " sign: Sign an executable." ) ;
2022-05-28 15:58:35 -04:00
console . log ( " --exe [file] Required executable to sign." ) ;
console . log ( " --out [file] Resulting signed executable." ) ;
console . log ( " --pem [pemfile] Certificate & private key to sign the executable with." ) ;
console . log ( " --desc [description] Description string to embbed into signature." ) ;
console . log ( " --url [url] URL to embbed into signature." ) ;
2022-05-27 20:34:25 -04:00
console . log ( " unsign: Remove the signature from the executable." ) ;
2022-05-28 15:58:35 -04:00
console . log ( " --exe [file] Required executable to un-sign." ) ;
console . log ( " --out [file] Resulting executable with signature removed." ) ;
console . log ( " createcert: Create a code signging self-signed certificate and key." ) ;
console . log ( " --out [pemfile] Required certificate file to create." ) ;
console . log ( " --cn [value] Required certificate common name." ) ;
console . log ( " --country [value] Certificate country name." ) ;
console . log ( " --state [value] Certificate state name." ) ;
console . log ( " --locality [value] Certificate locality name." ) ;
console . log ( " --org [value] Certificate organization name." ) ;
console . log ( " --ou [value] Certificate organization unit name." ) ;
console . log ( " --serial [value] Certificate serial number." ) ;
console . log ( "" ) ;
console . log ( "Note that certificate PEM files must first have the signing certificate," ) ;
console . log ( "followed by all certificates that form the trust chain." ) ;
2022-05-25 20:37:02 -04:00
return ;
}
// Check that a valid command is passed in
2022-05-27 20:34:25 -04:00
if ( [ 'info' , 'sign' , 'unsign' , 'createcert' ] . indexOf ( process . argv [ 2 ] . toLowerCase ( ) ) == - 1 ) {
2022-05-25 20:37:02 -04:00
console . log ( "Invalid command: " + process . argv [ 2 ] ) ;
2022-05-27 20:34:25 -04:00
console . log ( "Valid commands are: info, sign, unsign, createcert" ) ;
2022-05-25 20:37:02 -04:00
return ;
}
2022-05-27 20:34:25 -04:00
var exe = null ;
if ( args . exe ) {
// Check the file exists and open the file
var stats = null ;
try { stats = require ( 'fs' ) . statSync ( args . exe ) ; } catch ( ex ) { }
if ( stats == null ) { console . log ( "Unable to executable open file: " + args . exe ) ; return ; }
exe = createAuthenticodeHandler ( args . exe ) ;
2022-05-25 20:37:02 -04:00
}
// Execute the command
var command = process . argv [ 2 ] . toLowerCase ( ) ;
2022-05-28 15:58:35 -04:00
if ( command == 'info' ) { // Get signature information about an executable
2022-05-27 20:34:25 -04:00
if ( exe == null ) { console . log ( "Missing --exe [filename]" ) ; return ; }
if ( args . json ) {
var r = { header : exe . header , filesize : exe . filesize }
if ( exe . fileHashAlgo != null ) { r . hashMethod = exe . fileHashAlgo ; }
if ( exe . fileHashSigned != null ) { r . hashSigned = exe . fileHashSigned . toString ( 'hex' ) ; }
if ( exe . fileHashActual != null ) { r . hashActual = exe . fileHashActual . toString ( 'hex' ) ; }
if ( exe . signingAttribs && exe . signingAttribs . length > 0 ) { r . signAttributes = exe . signingAttribs ; }
console . log ( JSON . stringify ( r , null , 2 ) ) ;
} else {
2022-05-28 15:58:35 -04:00
console . log ( "Header" , exe . header ) ;
if ( exe . fileHashAlgo != null ) { console . log ( "Hash Method:" , exe . fileHashAlgo ) ; }
if ( exe . fileHashSigned != null ) { console . log ( "Signed Hash:" , exe . fileHashSigned . toString ( 'hex' ) ) ; }
if ( exe . fileHashActual != null ) { console . log ( "Actual Hash:" , exe . fileHashActual . toString ( 'hex' ) ) ; }
if ( exe . signingAttribs && exe . signingAttribs . length > 0 ) { console . log ( "Signature Attributes:" ) ; for ( var i in exe . signingAttribs ) { console . log ( ' ' + exe . signingAttribs [ i ] ) ; } }
console . log ( "File Length: " + exe . filesize ) ;
2022-05-27 20:34:25 -04:00
}
2022-05-25 20:37:02 -04:00
}
2022-05-28 15:58:35 -04:00
if ( command == 'sign' ) { // Sign an executable
if ( typeof args . exe != 'string' ) { console . log ( "Missing --exe [filename]" ) ; return ; }
createOutFile ( args , args . exe ) ;
2022-05-29 01:59:21 -04:00
const cert = loadCertificates ( args . pem ) ;
2022-05-29 01:34:48 -04:00
if ( cert == null ) { console . log ( "Unable to load certificate and/or private key, generating test certificate." ) ; }
2022-05-28 15:58:35 -04:00
console . log ( "Signing to " + args . out ) ; exe . sign ( cert , args ) ; console . log ( "Done." ) ;
2022-05-27 00:08:50 -04:00
}
2022-05-28 15:58:35 -04:00
if ( command == 'unsign' ) { // Unsign an executable
if ( typeof args . exe != 'string' ) { console . log ( "Missing --exe [filename]" ) ; return ; }
createOutFile ( args , args . exe ) ;
if ( exe . header . signed ) { console . log ( "Unsigning to " + args . out ) ; exe . unsign ( args ) ; console . log ( "Done." ) ; } else { console . log ( "Executable is not signed." ) ; }
2022-05-25 20:37:02 -04:00
}
2022-05-28 15:58:35 -04:00
if ( command == 'createcert' ) { // Create a code signing certificate and private key
if ( typeof args . out != 'string' ) { console . log ( "Missing --out [filename]" ) ; return ; }
if ( typeof args . cn != 'string' ) { console . log ( "Missing --cn [name]" ) ; return ; }
if ( typeof args . serial == 'string' ) { if ( args . serial != parseInt ( args . serial ) ) { console . log ( "Invalid serial number." ) ; return ; } else { args . serial = parseInt ( args . serial ) ; } }
if ( typeof args . serial == 'number' ) { args . serial = '0' + args . serial ; } // Serial number must be a integer string with a single leading '0'
const cert = createSelfSignedCert ( args ) ;
console . log ( "Writing to " + args . out ) ;
fs . writeFileSync ( args . out , pki . certificateToPem ( cert . cert ) + '\r\n' + pki . privateKeyToPem ( cert . key ) ) ;
console . log ( "Done." ) ;
2022-05-27 20:34:25 -04:00
}
2022-05-25 20:37:02 -04:00
// Close the file
2022-05-27 20:34:25 -04:00
if ( exe != null ) { exe . close ( ) ; }
2022-05-25 20:37:02 -04:00
}
2022-05-29 01:34:48 -04:00
// If this is the main module, run the command line version
if ( require . main === module ) { start ( ) ; }
// Exports
2022-05-29 01:59:21 -04:00
module . exports . createAuthenticodeHandler = createAuthenticodeHandler ;
module . exports . loadCertificates = loadCertificates ;