osslsigncode 0.1.1

In-process Rust bindings for the vendored osslsigncode Authenticode implementation
//! Drive upstream `FILE_FORMAT` / helpers directly — no `main_execute`, no argv.
//!
//! `GLOBAL_OPTIONS` is filled in Rust; promoted symbols from `osslsigncode.c`
//! plus the exported format vtables do the Authenticode work.

use std::ffi::CStr;
use std::os::raw::{c_char, c_int};
use std::ptr;

use openssl_sys::{BIO, BIO_free_all, BIO_new};

use crate::ffi::{BIO_f_md, BIO_set_md};
use crate::owned::{
    FormatCtx, OwnedPkcs7, UiGuard, format_ctx_from_ptr, format_ctx_null, free_bio_chain,
    owned_pkcs7_null,
};
use crate::sys::{
    self, GLOBAL_OPTIONS, cmd_type_t, file_format_appx, file_format_cab, file_format_cat,
    file_format_msi, file_format_pe, file_format_script,
};

macro_rules! fail {
    ($($arg:tt)*) => {{
        crate::native::set_last_error(format!($($arg)*));
        return 1;
    }};
}

/// Run a fully-populated `GLOBAL_OPTIONS` through the format vtables.
///
/// Returns `0` on success (same convention as upstream). Always calls
/// `free_options` before returning.
pub(crate) unsafe fn run(options: &mut GLOBAL_OPTIONS) -> c_int {
    let Some(_ui) = (unsafe { UiGuard::install() }) else {
        unsafe { sys::free_options(options) };
        return 1;
    };

    let status = unsafe { run_body(options) };
    unsafe { sys::providers_cleanup() };
    unsafe { sys::free_options(options) };
    status
}

unsafe fn run_body(options: &mut GLOBAL_OPTIONS) -> c_int {
    let mut ctx = format_ctx_null();
    let mut p7 = owned_pkcs7_null();
    let mut cursig = owned_pkcs7_null();
    let mut outdata: *mut BIO = ptr::null_mut();
    let mut hash: *mut BIO = ptr::null_mut();

    let ret = if unsafe { sys::read_password(options) } == 0 {
        crate::native::set_last_error(format!(
            "Failed to read password from file: {}",
            cstr(options.readpass)
        ));
        1
    } else if options.cmd == cmd_type_t::CMD_SIGN
        && unsafe { sys::read_crypto_params(options) } == 0
    {
        crate::native::set_last_error("Failed to read key or certificates");
        1
    } else {
        unsafe {
            dispatch(
                options,
                &mut ctx,
                &mut p7,
                &mut cursig,
                &mut outdata,
                &mut hash,
            )
        }
    };

    if !outdata.is_null() {
        unsafe { free_bio_chain(hash, outdata) };
        if !options.outfile.is_null() {
            unlink_c_path(options.outfile);
        }
    }

    drop((p7, cursig, ctx));
    ret
}

unsafe fn dispatch(
    options: &mut GLOBAL_OPTIONS,
    ctx: &mut FormatCtx,
    p7: &mut OwnedPkcs7,
    cursig: &mut OwnedPkcs7,
    outdata: &mut *mut BIO,
    hash: &mut *mut BIO,
) -> c_int {
    if options.cmd != cmd_type_t::CMD_VERIFY {
        *hash = unsafe { BIO_new(BIO_f_md()) };
        if unsafe { BIO_set_md(*hash, options.md.cast()) } == 0 {
            fail!("Unable to set the message digest of BIO");
        }
        *outdata = unsafe { sys::bio_new_file(options.outfile, c"w+b".as_ptr()) };
        if (*outdata).is_null() {
            unsafe { BIO_free_all(*hash) };
            *hash = ptr::null_mut();
            fail!("Failed to create file: {}", cstr(options.outfile));
        }
    }

    *ctx = format_ctx_from_ptr(unsafe { open_format(options, *hash, *outdata) });
    if ctx.is_null() {
        if !(*outdata).is_null() && !options.outfile.is_null() {
            unlink_c_path(options.outfile);
        }
        unsafe {
            BIO_free_all(*hash);
            BIO_free_all(*outdata);
        }
        *hash = ptr::null_mut();
        *outdata = ptr::null_mut();
        fail!("Initialization error or unsupported input file type.");
    }

    let ret = match options.cmd {
        cmd_type_t::CMD_VERIFY => unsafe { sys::verify_signed_file(ctx.as_ptr(), options, 1) },
        cmd_type_t::CMD_EXTRACT_DATA => unsafe {
            extract_contents(ctx.as_ptr(), p7, *hash, *outdata, options.md)
        },
        cmd_type_t::CMD_EXTRACT => unsafe { extract_signature(ctx.as_ptr(), p7, *outdata) },
        cmd_type_t::CMD_REMOVE => unsafe { remove_signature(ctx.as_ptr(), *hash, *outdata) },
        cmd_type_t::CMD_ADD | cmd_type_t::CMD_ATTACH | cmd_type_t::CMD_SIGN => {
            return unsafe { sign_like(options, ctx.as_ptr(), p7, cursig, hash, outdata) };
        }
        _ => fail!("Unsupported command"),
    };

    bio_free_via_format(ctx.as_ptr(), hash, outdata);
    ret
}

unsafe fn extract_contents(
    ctx: *mut sys::FILE_FORMAT_CTX,
    p7: &mut OwnedPkcs7,
    hash: *mut BIO,
    outdata: *mut BIO,
    md: *const openssl_sys::EVP_MD,
) -> c_int {
    let format = unsafe { (*ctx).format };
    let Some(get) = (unsafe { (*format).pkcs7_contents_get }) else {
        fail!("Unsupported command: extract-data");
    };
    p7.set(unsafe { get(ctx, hash, md) });
    if p7.is_null() {
        fail!("Unable to extract pkcs7 contents");
    }
    let ret = unsafe { sys::data_write_pkcs7(ctx, outdata, p7.as_ptr()) };
    p7.set(ptr::null_mut());
    ret
}

unsafe fn extract_signature(
    ctx: *mut sys::FILE_FORMAT_CTX,
    p7: &mut OwnedPkcs7,
    outdata: *mut BIO,
) -> c_int {
    let format = unsafe { (*ctx).format };
    let Some(extract) = (unsafe { (*format).pkcs7_extract }) else {
        fail!("Unsupported command: extract-signature");
    };
    p7.set(unsafe { extract(ctx) });
    if p7.is_null() {
        fail!("Unable to extract existing signature");
    }
    let ret = unsafe { sys::data_write_pkcs7(ctx, outdata, p7.as_ptr()) };
    p7.set(ptr::null_mut());
    ret
}

unsafe fn remove_signature(
    ctx: *mut sys::FILE_FORMAT_CTX,
    hash: *mut BIO,
    outdata: *mut BIO,
) -> c_int {
    let format = unsafe { (*ctx).format };
    let Some(remove) = (unsafe { (*format).remove_pkcs7 }) else {
        fail!("Unsupported command: remove-signature");
    };
    let ret = unsafe { remove(ctx, hash, outdata) };
    if ret != 0 {
        fail!("Unable to remove existing signature");
    }
    if let Some(update) = unsafe { (*format).update_data_size } {
        unsafe { update(ctx, outdata, ptr::null_mut()) };
    }
    ret
}

unsafe fn sign_like(
    options: &mut GLOBAL_OPTIONS,
    ctx: *mut sys::FILE_FORMAT_CTX,
    p7: &mut OwnedPkcs7,
    cursig: &mut OwnedPkcs7,
    hash: &mut *mut BIO,
    outdata: &mut *mut BIO,
) -> c_int {
    let format = unsafe { (*ctx).format };

    match options.cmd {
        cmd_type_t::CMD_ADD => {
            let Some(extract) = (unsafe { (*format).pkcs7_extract }) else {
                fail!("Unsupported command: add");
            };
            p7.set(unsafe { extract(ctx) });
            if p7.is_null() {
                fail!("Unable to extract existing signature");
            }
            if let Some(process) = unsafe { (*format).process_data }
                && unsafe { process(ctx, *hash, *outdata) } == 0
            {
                fail!("Unable to read input file");
            }
        }
        cmd_type_t::CMD_ATTACH | cmd_type_t::CMD_SIGN => {
            if options.nest != 0
                && let Some(extract_nest) = unsafe { (*format).pkcs7_extract_to_nest }
            {
                cursig.set(unsafe { extract_nest(ctx) });
                if cursig.is_null() {
                    fail!("Unable to extract existing signature");
                }
                options.nested_number =
                    unsafe { sys::nested_signatures_number_get(cursig.as_ptr()) };
                if options.nested_number < 0 {
                    cursig.set(ptr::null_mut());
                    fail!("Unable to get number of nested signatures");
                }
            }
            if options.cmd == cmd_type_t::CMD_ATTACH {
                p7.set(unsafe { sys::pkcs7_get_sigfile(ctx) });
                if p7.is_null() {
                    cursig.set(ptr::null_mut());
                    fail!("Unable to extract valid signature");
                }
            }
            if let Some(process) = unsafe { (*format).process_data }
                && unsafe { process(ctx, *hash, *outdata) } == 0
            {
                fail!("Unable to read input file");
            }
            if options.cmd == cmd_type_t::CMD_SIGN
                && let Some(sig_new) = unsafe { (*format).pkcs7_signature_new }
            {
                p7.set(unsafe { sig_new(ctx, *hash) });
                if p7.is_null() {
                    fail!("Unable to prepare new signature");
                }
            }
        }
        _ => fail!("Unsupported command"),
    }

    let ret = if options.index > 0 {
        unsafe { sys::add_nested_timestamp_and_blob(p7.as_ptr(), ctx, options.index) }
    } else {
        unsafe { sys::add_timestamp_and_blob(p7.as_ptr(), ctx) }
    };
    if ret != 0 {
        p7.set(ptr::null_mut());
        fail!("Unable to set unauthenticated attributes");
    }

    if !cursig.is_null() {
        if unsafe { sys::cursig_set_nested(cursig.as_ptr(), p7.as_ptr()) } == 0 {
            fail!("Unable to append the nested signature to the current signature");
        }
        // Keep the outer signature; free the nested PKCS7 that was just attached.
        p7.set(ptr::null_mut());
        p7.replace(cursig.take());
    }

    if let Some(append) = unsafe { (*format).append_pkcs7 } {
        let ret = unsafe { append(ctx, *outdata, p7.as_ptr()) };
        if ret != 0 {
            p7.set(ptr::null_mut());
            fail!("Append signature to outfile failed");
        }
    }
    if let Some(update) = unsafe { (*format).update_data_size } {
        unsafe { update(ctx, *outdata, p7.as_ptr()) };
    }
    p7.set(ptr::null_mut());

    // Match upstream: bio_free owns both streams; clear locals so run_body
    // does not double-free or unlink a successful outfile.
    if let Some(bio_free) = unsafe { (*format).bio_free } {
        unsafe { bio_free(*hash, *outdata) };
        *hash = ptr::null_mut();
        *outdata = ptr::null_mut();
    }

    if options.cmd == cmd_type_t::CMD_ATTACH {
        let ret = unsafe { sys::check_attached_data(options) };
        if ret != 0 && !options.outfile.is_null() {
            unlink_c_path(options.outfile);
        }
        ret
    } else {
        0
    }
}

fn bio_free_via_format(
    ctx: *mut sys::FILE_FORMAT_CTX,
    hash: &mut *mut BIO,
    outdata: &mut *mut BIO,
) {
    if ctx.is_null() {
        return;
    }
    unsafe {
        let format = (*ctx).format;
        if let Some(bio_free) = (*format).bio_free {
            bio_free(*hash, *outdata);
            *outdata = ptr::null_mut();
            *hash = ptr::null_mut();
        }
    }
}

unsafe fn open_format(
    options: &mut GLOBAL_OPTIONS,
    hash: *mut BIO,
    outdata: *mut BIO,
) -> *mut sys::FILE_FORMAT_CTX {
    let formats = [
        ptr::addr_of!(file_format_script),
        ptr::addr_of!(file_format_msi),
        ptr::addr_of!(file_format_pe),
        ptr::addr_of!(file_format_cab),
        ptr::addr_of!(file_format_appx),
        ptr::addr_of!(file_format_cat),
    ];
    for format in formats {
        let format = unsafe { &*format };
        if let Some(ctx_new) = format.ctx_new {
            let ctx = unsafe { ctx_new(options, hash, outdata) };
            if !ctx.is_null() {
                return ctx;
            }
        }
    }
    ptr::null_mut()
}

fn cstr(ptr: *mut c_char) -> String {
    if ptr.is_null() {
        return String::new();
    }
    unsafe { CStr::from_ptr(ptr) }
        .to_string_lossy()
        .into_owned()
}

fn unlink_c_path(path: *mut c_char) {
    if path.is_null() {
        return;
    }
    let path = unsafe { CStr::from_ptr(path) }.to_string_lossy();
    let _ = std::fs::remove_file(path.as_ref());
}