// refs. // - Windows Authenticode Portable Executable Signature Format // https://download.microsoft.com/download/9/c/5/9c5b2167-8017-4bae-9fde-d599bac8184a/authenticode_pe.docx // - RFC 2315 - PKCS #7: Cryptographic Message Syntax Version 1.5 // https://tools.ietf.org/html/rfc2315 // - RFC 3280 - Internet X.509 Public Key Infrastructure Certificate and Certificate Revocation List (CRL) Profile // https://tools.ietf.org/html/rfc3280 // - Object IDs associated with Microsoft cryptography // https://support.microsoft.com/en-us/help/287547/object-ids-associated-with-microsoft-cryptography // - OID repository // http://oid-info.com/ // - RFC 3161 - Internet X.509 Public Key Infrastructure Time-Stamp Protocol (TSP) // https://tools.ietf.org/html/rfc3161 // - mono/AuthenticodeDeformatter.cs // https://github.com/mono/mono/blob/master/mcs/class/Mono.Security/Mono.Security.Authenticode/AuthenticodeDeformatter.cs var __generator = (this && this.__generator) || function (thisArg, body) { var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; function verb(n) { return function (v) { return step([n, v]); }; } function step(op) { if (f) throw new TypeError("Generator is already executing."); while (g && (g = 0, op[0] && (_ = 0)), _) try { if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; if (y = 0, t) op = [op[0] & 2, t.value]; switch (op[0]) { case 0: case 1: t = op; break; case 4: _.label++; return { value: op[1], done: false }; case 5: _.label++; y = op[1]; op = [0]; continue; case 7: op = _.ops.pop(); _.trys.pop(); continue; default: if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } if (t[2]) _.ops.pop(); _.trys.pop(); continue; } op = body.call(thisArg, _); } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; } }; import { Format, calculateCheckSumForPE } from 'pe-library'; import { allocatePartialBinary, cloneToArrayBuffer, copyBuffer, roundUp, } from '../util/functions.js'; import { certBinToCertificatesDER, pickIssuerAndSerialNumberDERFromCert, toUint8Array, } from './certUtil.js'; import AlgorithmIdentifier from './data/AlgorithmIdentifier.js'; import CertificateDataRoot from './data/CertificateDataRoot.js'; import { RawDERObject } from './data/DERObject.js'; import DigestInfo from './data/DigestInfo.js'; import IssuerAndSerialNumber from './data/IssuerAndSerialNumber.js'; import * as KnownOids from './data/KnownOids.js'; import SignedData from './data/SignedData.js'; import SignerInfo from './data/SignerInfo.js'; import SpcIndirectDataContent, { SpcIndirectDataContentInfo, SPC_INDIRECT_DATA_OBJID, } from './data/SpcIndirectDataContent.js'; import SpcPeImageData, { SpcPeImageAttributeTypeAndOptionalValue, } from './data/SpcPeImageData.js'; import { SpcLinkFile } from './data/SpcLink.js'; import Attribute from './data/Attribute.js'; import { arrayToDERSet, makeDEROctetString, makeDERSequence, } from './data/derUtil.js'; import ContentInfo from './data/ContentInfo.js'; import ObjectIdentifier from './data/ObjectIdentifier.js'; import { createTimestampRequest, pickSignedDataFromTimestampResponse, } from './timestamp.js'; function makeSimpleIterator(data) { var done = false; return { next: function () { if (done) { return { done: true, value: undefined, }; } else { done = true; return { done: false, value: data, }; } }, }; } function validateSignerObject(signer) { if (!signer.encryptData && !signer.signData) { throw new Error('Signer object must implement either `encryptData` or `signData`.'); } } function calculateExecutableDigest(executable, signer, alignment) { function inner() { var checkSumOffset, certificateTableOffset, rawHeader, targetSections, sectionCount, sectionStartOffset, sectionEndOffset, sectionHeadersSize, secHeader, secArray_1, off, _i, targetSections_1, section, exData, alignedLength, diff; return __generator(this, function (_a) { switch (_a.label) { case 0: checkSumOffset = executable.dosHeader.newHeaderAddress + 88; certificateTableOffset = executable.dosHeader.newHeaderAddress + executable.newHeader.getDataDirectoryOffset() + Format.ImageDataDirectoryArray.itemSize * Format.ImageDirectoryEntry.Certificate; rawHeader = executable.getRawHeader(); targetSections = executable.getAllSections(); sectionCount = targetSections.length; sectionStartOffset = rawHeader.byteLength; sectionEndOffset = roundUp(sectionStartOffset + sectionCount * Format.ImageSectionHeaderArray.itemSize, executable.getFileAlignment()); sectionHeadersSize = sectionEndOffset - sectionStartOffset; secHeader = new ArrayBuffer(sectionHeadersSize); { secArray_1 = Format.ImageSectionHeaderArray.from(secHeader, sectionCount); targetSections.forEach(function (sec, i) { secArray_1.set(i, sec.info); }); } // pick from head to immediately before checksum return [4 /*yield*/, allocatePartialBinary(rawHeader, 0, checkSumOffset)]; case 1: // pick from head to immediately before checksum _a.sent(); // pick from the end of checksum to immediately before 'Certificate Table' header return [4 /*yield*/, allocatePartialBinary(rawHeader, checkSumOffset + 4, certificateTableOffset - (checkSumOffset + 4))]; case 2: // pick from the end of checksum to immediately before 'Certificate Table' header _a.sent(); off = certificateTableOffset + Format.ImageDataDirectoryArray.itemSize; return [4 /*yield*/, allocatePartialBinary(executable.getRawHeader(), off, executable.getTotalHeaderSize() - off)]; case 3: _a.sent(); // pick section header return [4 /*yield*/, secHeader]; case 4: // pick section header _a.sent(); _i = 0, targetSections_1 = targetSections; _a.label = 5; case 5: if (!(_i < targetSections_1.length)) return [3 /*break*/, 8]; section = targetSections_1[_i]; if (!section.data) return [3 /*break*/, 7]; return [4 /*yield*/, section.data]; case 6: _a.sent(); _a.label = 7; case 7: _i++; return [3 /*break*/, 5]; case 8: exData = executable.getExtraData(); if (!(exData !== null)) return [3 /*break*/, 11]; return [4 /*yield*/, exData]; case 9: _a.sent(); alignedLength = roundUp(exData.byteLength, alignment); diff = alignedLength - exData.byteLength; if (!(diff !== 0)) return [3 /*break*/, 11]; return [4 /*yield*/, new Uint8Array(diff).buffer]; case 10: _a.sent(); _a.label = 11; case 11: return [2 /*return*/]; } }); } return signer.digestData(inner()); } function getAlgorithmIdentifierObject(type) { if (typeof type !== 'string') { return new AlgorithmIdentifier(new ObjectIdentifier(type)); } switch (type) { case 'sha1': case 'SHA1': return new AlgorithmIdentifier(KnownOids.OID_SHA1_NO_SIGN); case 'sha256': case 'SHA256': return new AlgorithmIdentifier(KnownOids.OID_SHA256_NO_SIGN); case 'sha384': case 'SHA384': return new AlgorithmIdentifier(KnownOids.OID_SHA384_NO_SIGN); case 'sha512': case 'SHA512': return new AlgorithmIdentifier(KnownOids.OID_SHA512_NO_SIGN); case 'sha224': case 'SHA224': return new AlgorithmIdentifier(KnownOids.OID_SHA224_NO_SIGN); case 'sha512-224': case 'SHA512-224': return new AlgorithmIdentifier(KnownOids.OID_SHA512_224_NO_SIGN); case 'sha512-256': case 'SHA512-256': return new AlgorithmIdentifier(KnownOids.OID_SHA512_256_NO_SIGN); case 'sha3-224': case 'SHA3-224': return new AlgorithmIdentifier(KnownOids.OID_SHA3_224_NO_SIGN); case 'sha3-256': case 'SHA3-256': return new AlgorithmIdentifier(KnownOids.OID_SHA3_256_NO_SIGN); case 'sha3-384': case 'SHA3-384': return new AlgorithmIdentifier(KnownOids.OID_SHA3_384_NO_SIGN); case 'sha3-512': case 'SHA3-512': return new AlgorithmIdentifier(KnownOids.OID_SHA3_512_NO_SIGN); case 'shake128': case 'SHAKE128': return new AlgorithmIdentifier(KnownOids.OID_SHAKE128_NO_SIGN); case 'shake256': case 'SHAKE256': return new AlgorithmIdentifier(KnownOids.OID_SHAKE256_NO_SIGN); default: throw new Error('Invalid or unsupported digest algorithm'); } } function doSign(signer, digestAlgorithm, dataIterator) { if (signer.signData) { return signer.signData(dataIterator); } else { return signer.digestData(dataIterator).then(function (digestAttributes) { // encrypting DigestInfo with digest of 'attributes' set var digestInfoBin = new Uint8Array(new DigestInfo(digestAlgorithm, digestAttributes).toDER()).buffer; // (eencryptData should be defined here) return signer.encryptData(makeSimpleIterator(digestInfoBin)); }); } } /** * Generates the executable binary data with signed info. * This function is like an extension of `generate` method of `NtExecutable`. * @param executable a valid instance of `NtExecutable` * @param signer user-defined `SignerObject` instance for signing * @param alignment alignment value for placing certificate data * (using `executable.getFileAlignment()` if omitted) * @return Promise-like (Thenable) object which will resolve with generated executable binary */ export function generateExecutableWithSign(executable, signer, alignment) { validateSignerObject(signer); var certAlignment; if (typeof alignment === 'number') { if (alignment <= 0) { throw new Error('Invalid alignment value'); } certAlignment = alignment; } else { certAlignment = executable.getFileAlignment(); } var digestAlgorithm = getAlgorithmIdentifierObject(signer.getDigestAlgorithm()); var digestEncryptionAlgorithm; var a = signer.getEncryptionAlgorithm(); if (typeof a !== 'string') { digestEncryptionAlgorithm = new AlgorithmIdentifier(new ObjectIdentifier(a)); } else { switch (a) { case 'rsa': case 'RSA': digestEncryptionAlgorithm = new AlgorithmIdentifier(KnownOids.OID_RSA); break; case 'dsa': case 'DSA': digestEncryptionAlgorithm = new AlgorithmIdentifier(KnownOids.OID_DSA); break; default: throw new Error('Invalid or unsupported digest encryption algorithm'); } } // (for compatibility) // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions var cert = signer.getCertificateData ? signer.getCertificateData() : signer.getPublicKeyData(); var _a = pickIssuerAndSerialNumberDERFromCert(cert), issuer = _a[0], serialNumber = _a[1]; return ( // calculate digest calculateExecutableDigest(executable, signer, certAlignment) // make content, content's digest, and sign .then(function (digest) { var content = new SpcIndirectDataContent(new SpcPeImageAttributeTypeAndOptionalValue(new SpcPeImageData(0 /* SpcPeImageFlags.IncludeResources */, new SpcLinkFile(''))), new DigestInfo(digestAlgorithm, digest)); return (signer .digestData(makeSimpleIterator(new Uint8Array(content.toDERWithoutHeader()) .buffer)) // make sign .then(function (contentDigest) { var attributes = [ new Attribute(KnownOids.OID_SPC_SP_OPUS_INFO_OBJID, // (SpcSpOpusInfo) null sequence [new RawDERObject([0x30, 0x00])]), new Attribute(KnownOids.OID_CONTENT_TYPE, [ SPC_INDIRECT_DATA_OBJID, ]), new Attribute(KnownOids.OID_SPC_STATEMENT_TYPE_OBJID, [ new RawDERObject(makeDERSequence(KnownOids.OID_SPC_INDIVIDUAL_SP_KEY_PURPOSE_OBJID.toDER())), ]), new Attribute(KnownOids.OID_MESSAGE_DIGEST, [ new RawDERObject(makeDEROctetString(toUint8Array(contentDigest))), ]), ]; // get digest of 'attributes' set var attrBin = new Uint8Array(arrayToDERSet(attributes)).buffer; return doSign(signer, digestAlgorithm, makeSimpleIterator(attrBin)).then(function (signed) { return [content, attributes, signed]; }); })); }) // make cert bin .then(function (_a) { var content = _a[0], attributes = _a[1], signed = _a[2]; var signerInfo = new SignerInfo( // version 1, // issuerAndSerialNumber new IssuerAndSerialNumber(new RawDERObject(issuer), new RawDERObject(serialNumber)), // digestAlgorithm digestAlgorithm, // digestEncryptionAlgorithm digestEncryptionAlgorithm, // encryptedDigest toUint8Array(signed), // authenticatedAttributes attributes); if (!signer.timestampData) { return [content, signerInfo]; } // timestamp return (signer // make digest of encrypted data for make timestamp .digestData(makeSimpleIterator(cloneToArrayBuffer(signed))) .then(function (digestEncryptedBase) { var digestEncrypted = createTimestampRequest(digestEncryptedBase, digestAlgorithm); // request timestamp return signer.timestampData(digestEncrypted).then(function (timestamp) { // pick up signedData var timestampSignedData = pickSignedDataFromTimestampResponse(timestamp); // add timestamp to 'unauthenticatedAttributes' signerInfo.unauthenticatedAttributes = [ new Attribute(KnownOids.OID_RFC3161_COUNTER_SIGNATURE, [ new ContentInfo(KnownOids.OID_SIGNED_DATA, new RawDERObject(toUint8Array(timestampSignedData))), ]), ]; return [content, signerInfo]; }); })); }) .then(function (_a) { var content = _a[0], signerInfo = _a[1]; // make certificate data var root = new CertificateDataRoot(KnownOids.OID_SIGNED_DATA, new SignedData( // version 1, // digestAlgorithms [digestAlgorithm], // contentInfo new SpcIndirectDataContentInfo(content), // signerInfos [signerInfo], // certificates certBinToCertificatesDER(cert))); var certBin = new Uint8Array(root.toDER()); var resultBin = new ArrayBuffer(8 + certBin.length); // make WIN_CERTIFICATE var resultView = new DataView(resultBin); // dwLength resultView.setUint32(0, certBin.length + 8, true); // wRevision : 0x0200 (revision 2) resultView.setUint16(4, 0x200, true); // wCertificateType : 0x0002 resultView.setUint16(6, 0x2, true); copyBuffer(resultBin, 8, certBin, 0, certBin.byteLength); return resultBin; }) .then(function (certBin) { var alignedSize = roundUp(certBin.byteLength, certAlignment); // NOTE: The certificate data must follow the extra data. // To achieve this, the another size between them must be added to the padding size. // (The extra data may not be aligned, but the certificate data should be aligned.) var paddingSize = alignedSize; var exData = executable.getExtraData(); if (exData !== null) { var diffSize = roundUp(exData.byteLength, certAlignment) - exData.byteLength; paddingSize += diffSize; } var newBin = executable.generate(paddingSize); var certOffset = newBin.byteLength - alignedSize; var dirArray = Format.ImageDataDirectoryArray.from(newBin, executable.dosHeader.newHeaderAddress + executable.newHeader.getDataDirectoryOffset()); dirArray.set(Format.ImageDirectoryEntry.Certificate, { size: alignedSize, virtualAddress: certOffset, }); // recalculate checksum calculateCheckSumForPE(newBin, true); // write Certificate section data copyBuffer(newBin, certOffset, certBin, 0, certBin.byteLength); return newBin; })); }