"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.signBinary = signBinary;
exports.signIOCDBinaries = signIOCDBinaries;
exports.signWithSmctl = signWithSmctl;
exports.detectSigningCapabilities = detectSigningCapabilities;
const logger_1 = require("../../utils/logger");
const config_service_1 = require("../config/config.service");
const path_1 = require("../../utils/path");
const child_process_1 = require("child_process");
const fs_1 = require("fs");
const path_2 = __importDefault(require("path"));
const logger = logger_1.Logger.getInstance();
const signableExtensions = ['.exe', '.dll', '.ocx', '.sys', '.node'];
async function signBinary(binaryPath) {
    logger.debug(`Signing binary: ${path_2.default.basename(binaryPath)}`);
    if (!(0, fs_1.existsSync)(binaryPath)) {
        throw new Error(`Executable not found at path: ${binaryPath}`);
    }
    const config = config_service_1.ConfigManager.config;
    const codeSignConfig = config.win.codeSign;
    if (codeSignConfig.type === 'off') {
        logger.info('Code signing is disabled (type: off)');
        return;
    }
    const ext = path_2.default.extname(binaryPath).toLowerCase();
    if (!signableExtensions.includes(ext)) {
        logger.debug(`File extension ${ext} is not signable. Skipping signing for: ${binaryPath}`);
        return;
    }
    await signBinaryCore(binaryPath, codeSignConfig);
    await verifySignature(binaryPath);
    logger.debug(`Successfully signed: ${path_2.default.basename(binaryPath)}`);
}
async function signIOCDBinaries() {
    logger.info('Starting signing of all Windows binaries...');
    const config = config_service_1.ConfigManager.config;
    const codeSignConfig = config.win.codeSign;
    if (codeSignConfig.type === 'off') {
        logger.info('Code signing is disabled (type: off)');
        return;
    }
    storeUnsignedExeIfNeeded();
    try {
        const iocdDir = path_1.PathUtils.getIOCDDir();
        const binariesToSign = await findSignableBinaries(iocdDir);
        if (binariesToSign.length === 0) {
            logger.info('No signable binaries found');
            return;
        }
        logger.info(`Signing ${binariesToSign.length} binaries...`);
        for (const binaryPath of binariesToSign) {
            await signBinaryCore(binaryPath, codeSignConfig);
        }
        logger.info('Signing binaries completed successfully!');
    }
    catch (error) {
        logger.error(`Batch signing failed: ${error instanceof Error ? error.message : String(error)}`);
        throw error;
    }
}
function storeUnsignedExeIfNeeded() {
    // we store the original unsigned exe, because rcedits can be done on unsigned exe only.
    // this means that if we do rcedit->sign, we can not longer do rcedit without breaking the signature
    // to overcome this we are storing the original exe in a _temp folder and restore it before any modifications
    // see restoreOriginalUnsignedExeIfNeeded in modifications/windows.helper.ts for restore logic
    const tempDir = path_1.PathUtils.getComponentDir("_temp");
    const tempExePath = path_2.default.join(tempDir, path_2.default.basename(path_1.PathUtils.getIOCDExePath()));
    if (!(0, fs_1.existsSync)(tempDir)) {
        (0, fs_1.mkdirSync)(tempDir);
    }
    if (!(0, fs_1.existsSync)(tempExePath)) {
        logger.debug('Storing current executable into _temp directory before signing. Will restore before next modifications');
        (0, fs_1.copyFileSync)(path_1.PathUtils.getIOCDExePath(), tempExePath);
    }
}
async function findSignableBinaries(directory) {
    const binaries = [];
    // Common files that should NOT be signed
    const skipFiles = [
        'msvcp140.dll',
        'vcruntime140.dll',
        'vcruntime140_1.dll',
        'msvcp140_1.dll',
        'msvcp140_2.dll',
        'api-ms-win-*.dll',
        'ucrtbase.dll'
    ];
    function scanDirectory(dir) {
        try {
            if (!(0, fs_1.existsSync)(dir))
                return;
            const files = (0, fs_1.readdirSync)(dir);
            for (const file of files) {
                const fullPath = path_2.default.join(dir, file);
                const stat = (0, fs_1.statSync)(fullPath);
                if (stat.isDirectory()) {
                    // Recursively scan subdirectories
                    scanDirectory(fullPath);
                }
                else if (stat.isFile()) {
                    const ext = path_2.default.extname(file).toLowerCase();
                    const basename = path_2.default.basename(file).toLowerCase();
                    // Check if it's a signable extension
                    if (signableExtensions.includes(ext)) {
                        // Skip system files and runtime libraries
                        const shouldSkip = skipFiles.some(skipPattern => {
                            if (skipPattern.includes('*')) {
                                // Simple wildcard matching
                                const regex = new RegExp('^' + skipPattern.replace(/\*/g, '.*') + '$', 'i');
                                return regex.test(basename);
                            }
                            return basename === skipPattern.toLowerCase();
                        });
                        if (!shouldSkip) {
                            binaries.push(fullPath);
                        }
                    }
                }
            }
        }
        catch (error) {
            logger.debug(`Could not scan directory ${dir}: ${error instanceof Error ? error.message : String(error)}`);
        }
    }
    scanDirectory(directory);
    return binaries;
}
async function signBinaryCore(binaryPath, codeSignConfig) {
    switch (codeSignConfig.type) {
        case "off":
            // do nothing 
            break;
        case 'pfx':
            await signWithPfx(binaryPath, codeSignConfig);
            break;
        case 'wincert':
            await signWithWinCert(binaryPath, codeSignConfig);
            break;
        case 'smime':
            await signWithSmime(binaryPath, codeSignConfig);
            break;
        case 'smctl':
            await signWithSmctl(binaryPath);
            break;
        case 'custom':
            await signWithCustomScript(binaryPath, codeSignConfig);
            break;
        default:
            throw new Error(`Unsupported code signing type: ${codeSignConfig.type}`);
    }
}
async function signWithPfx(exePath, codeSignConfig) {
    logger.debug('Signing with PFX certificate...');
    if (!codeSignConfig.pfxPath) {
        throw new Error('PFX signing requires pfxPath to be configured');
    }
    if (!(0, fs_1.existsSync)(codeSignConfig.pfxPath)) {
        throw new Error(`PFX file not found: ${codeSignConfig.pfxPath}`);
    }
    const args = [
        'sign',
        // "/debug",
        '/fd', 'SHA256',
        '/td', 'SHA256',
        '/tr', config_service_1.ConfigManager.config.win.codeSign.tsaURL,
        '/f', codeSignConfig.pfxPath
    ];
    if (codeSignConfig.pfxPassword) {
        args.push('/p', codeSignConfig.pfxPassword);
    }
    args.push(exePath);
    await runSigntool(args);
}
async function signWithWinCert(exePath, codeSignConfig) {
    logger.debug('Signing with Windows Certificate Store...');
    if (!codeSignConfig.wincertSubjectName) {
        throw new Error('Windows Certificate Store signing requires wincertSubjectName to be configured');
    }
    const args = [
        'sign',
        '/fd', 'SHA256',
        '/td', 'SHA256',
        '/tr', config_service_1.ConfigManager.config.win.codeSign.tsaURL,
        '/n', codeSignConfig.wincertSubjectName,
        exePath
    ];
    await runSigntool(args);
}
async function signWithSmime(exePath, codeSignConfig) {
    logger.debug('Signing with S/MIME certificate...');
    if (!codeSignConfig.smimeCertificatePath) {
        throw new Error('S/MIME signing requires smimeCertificatePath to be configured');
    }
    if (!(0, fs_1.existsSync)(codeSignConfig.smimeCertificatePath)) {
        throw new Error(`S/MIME certificate file not found: ${codeSignConfig.smimeCertificatePath}`);
    }
    const args = [
        'sign',
        '/fd', 'SHA256',
        '/td', 'SHA256',
        '/tr', config_service_1.ConfigManager.config.win.codeSign.tsaURL,
        '/f', codeSignConfig.smimeCertificatePath
    ];
    if (codeSignConfig.smimeCertificatePassword) {
        args.push('/p', codeSignConfig.smimeCertificatePassword);
    }
    args.push(exePath);
    await runSigntool(args);
}
async function signWithSmctl(exePath) {
    logger.debug('Signing with DigiCert smctl (KeyLocker)...');
    const requiredEnvVars = ['SM_API_KEY', 'SM_CLIENT_CERT_FILE', 'SM_CLIENT_CERT_PASSWORD', 'SM_CODE_SIGNING_CERT_SHA1_HASH'];
    const missingVars = requiredEnvVars.filter(varName => !process.env[varName]);
    if (missingVars.length > 0) {
        throw new Error(`DigiCert smctl signing requires the following environment variables: ${missingVars.join(', ')}`);
    }
    try {
        // First, authenticate with DigiCert KeyLocker
        await runCommand('smctl', ['windows', 'certsync'], {
            env: {
                ...process.env,
                SM_API_KEY: process.env['SM_API_KEY'],
                SM_CLIENT_CERT_FILE: process.env['SM_CLIENT_CERT_FILE'],
                SM_CLIENT_CERT_PASSWORD: process.env['SM_CLIENT_CERT_PASSWORD']
            }
        });
        // Then sign the binary
        const args = [
            'sign',
            '--keypair-alias', process.env['SM_CODE_SIGNING_CERT_SHA1_HASH'],
            '--certificate', process.env['SM_CLIENT_CERT_FILE'],
            '--config-file', process.env['SM_CLIENT_CERT_FILE'],
            '--input', exePath
        ].filter((arg) => arg !== undefined);
        await runCommand('smctl', args, {
            env: {
                ...process.env,
                SM_API_KEY: process.env['SM_API_KEY'],
                SM_CLIENT_CERT_FILE: process.env['SM_CLIENT_CERT_FILE'],
                SM_CLIENT_CERT_PASSWORD: process.env['SM_CLIENT_CERT_PASSWORD']
            }
        });
        logger.debug('DigiCert smctl signing completed successfully!');
    }
    catch (error) {
        throw new Error(`DigiCert smctl signing failed: ${error instanceof Error ? error.message : String(error)}`);
    }
}
async function detectSigningCapabilities() {
    logger.debug('Detecting available code signing tools...');
    const result = {
        hasSigntool: false,
        hasSmctl: false,
        sdkPath: undefined,
        recommendations: []
    };
    // Check for signtool in PATH
    try {
        await runCommand('signtool', ['/?']);
        result.hasSigntool = true;
        logger.debug('signtool found in PATH');
    }
    catch (error) {
        // Try common Windows SDK locations
        const sdkPaths = [
            'C:\\Program Files (x86)\\Windows Kits\\10\\bin\\x64\\signtool.exe',
            'C:\\Program Files (x86)\\Windows Kits\\10\\bin\\x86\\signtool.exe',
            'C:\\Program Files\\Microsoft SDKs\\Windows\\v7.1\\Bin\\signtool.exe',
            'C:\\Program Files (x86)\\Microsoft SDKs\\Windows\\v7.1A\\Bin\\signtool.exe'
        ];
        for (const sdkPath of sdkPaths) {
            if ((0, fs_1.existsSync)(sdkPath)) {
                result.hasSigntool = true;
                result.sdkPath = sdkPath;
                logger.debug(`signtool found at: ${sdkPath}`);
                break;
            }
        }
    }
    // Check for DigiCert smctl
    try {
        await runCommand('smctl', ['--version']);
        result.hasSmctl = true;
        logger.debug('DigiCert smctl found');
    }
    catch (error) {
        result.recommendations.push('Install DigiCert smctl for cloud-based signing: https://docs.digicert.com/en/software-trust-manager/tools/smctl.html');
    }
    if (!result.hasSigntool) {
        result.recommendations.push('Install Windows SDK for signtool: https://developer.microsoft.com/en-us/windows/downloads/windows-sdk/');
    }
    return result;
}
async function signWithCustomScript(exePath, codeSignConfig) {
    logger.debug('Signing with custom script...');
    if (!codeSignConfig.customCodeSignScriptPath) {
        throw new Error('Custom signing requires customCodeSignScriptPath to be configured');
    }
    if (!(0, fs_1.existsSync)(codeSignConfig.customCodeSignScriptPath)) {
        throw new Error(`Custom signing script not found: ${codeSignConfig.customCodeSignScriptPath}`);
    }
    logger.debug(`Loading and executing custom signing script: ${codeSignConfig.customCodeSignScriptPath}`);
    try {
        // Resolve the absolute path for the require
        const scriptPath = path_2.default.resolve(codeSignConfig.customCodeSignScriptPath);
        // Clear require cache to ensure fresh execution
        delete require.cache[scriptPath];
        // Require the script module
        const customScript = require(scriptPath);
        // Check if the module exports a function (default export or named export)
        let signFunction;
        if (typeof customScript === 'function') {
            signFunction = customScript;
        }
        else if (typeof customScript.default === 'function') {
            signFunction = customScript.default;
        }
        else if (typeof customScript.sign === 'function') {
            signFunction = customScript.sign;
        }
        else {
            throw new Error('Custom signing script must export a function (either as default export, named "sign" export, or direct export)');
        }
        // Execute the signing function with the binary path and full config
        await signFunction(exePath, codeSignConfig);
        logger.debug('Custom signing script executed successfully');
    }
    catch (error) {
        if (error instanceof Error) {
            throw new Error(`Custom signing script failed: ${error.message}`);
        }
        else {
            throw new Error(`Custom signing script failed: ${String(error)}`);
        }
    }
}
async function runSigntool(args) {
    await runCommand('signtool', args);
}
async function verifySignature(exePath) {
    logger.debug(`Verifying signature for: ${exePath}`);
    try {
        await runSigntool(['verify', '/pa', '/all', exePath]);
        logger.debug('Signature verification successful');
    }
    catch (error) {
        logger.warn(`Signature verification failed! The binary at ${exePath} may not be signed correctly.`);
    }
}
async function runCommand(command, args, options = {}) {
    return new Promise((resolve, reject) => {
        logger.debug(`Executing: ${command} ${args.join(' ')}`);
        const process = (0, child_process_1.spawn)(command, args, {
            stdio: ['ignore', 'pipe', 'pipe'],
            ...options
        });
        let stdout = '';
        let stderr = '';
        if (process.stdout) {
            process.stdout.on('data', (data) => {
                stdout += data.toString();
            });
        }
        if (process.stderr) {
            process.stderr.on('data', (data) => {
                stderr += data.toString();
            });
        }
        process.on('close', (code) => {
            if (code === 0) {
                if (stdout)
                    logger.debug(`Command output: ${stdout.trim()}`);
                resolve();
            }
            else {
                const errorMessage = `Command failed with exit code ${code}`;
                if (stderr)
                    logger.error(`${stderr.trim()}`);
                if (stdout)
                    logger.debug(`${stdout.trim()}`);
                reject(new Error(errorMessage));
            }
        });
        process.on('error', (error) => {
            logger.error(`Failed to start command: ${error.message}`);
            reject(error);
        });
    });
}
//# sourceMappingURL=windows.helper.js.map