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;
}};
}
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");
}
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());
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());
}