tasign 0.2.0

TA ELF signing utilities with CMS/PKCS#7 support
use std::path::Path;

use crate::bjca::bindings;
use crate::error::Error;

const BJCA_SIGN_BUF_LEN: usize = 16 * 1024;
const BJCA_CERT_BUF_LEN: usize = 16 * 1024;
const BJCA_DECODE_BUF_LEN: usize = 16 * 1024;

pub struct BjcaSignOutput {
    pub signature_der: Vec<u8>,
    pub leaf_cert_der: Vec<u8>,
}

struct BjcaHandleGuard {
    handle: bindings::BJCA_HANDLE,
}

impl Drop for BjcaHandleGuard {
    fn drop(&mut self) {
        unsafe {
            let mut h = self.handle;
            let _ = bindings::BJCA_SVS_Final(&mut h);
            self.handle = std::ptr::null_mut();
        }
    }
}

pub fn sign_signed_attrs_with_bjca(
    signed_attrs_der: &[u8],
    config_path: Option<&Path>,
) -> Result<BjcaSignOutput, Error> {
    let mut handle: bindings::BJCA_HANDLE = std::ptr::null_mut();
    let default_ini = Path::new("/etc/BJCA_SVS_Config.ini");
    let mut cfg_owned: Option<Vec<u8>> = None;
    let cfg_ptr: *mut bindings::BJCA_CHAR;

    match config_path {
        Some(path) => {
            let mut ini_bytes = path.to_string_lossy().into_owned().into_bytes();
            ini_bytes.push(0);
            cfg_ptr = ini_bytes.as_mut_ptr() as *mut bindings::BJCA_CHAR;
            cfg_owned = Some(ini_bytes);
        }
        None => {
            if !default_ini.exists() {
                return Err(Error::CmsSign(format!(
                    "BJCA config not found: {}; pass --bjca-config explicitly",
                    default_ini.display()
                )));
            }
            // 与 C 侧示例一致:默认路径存在时传 NULL,让 SDK 使用默认配置。
            cfg_ptr = std::ptr::null_mut();
        }
    }

    let ret = unsafe { bindings::BJCA_SVS_Init_Default(&mut handle, cfg_ptr) };
    // 保持字节缓冲区在调用结束前有效(仅 Some 场景)。
    let _keepalive = cfg_owned;
    if ret != 0 {
        let cfg_desc = match config_path {
            Some(path) => path.display().to_string(),
            None => format!("NULL(default:{})", default_ini.display()),
        };
        return Err(Error::CmsSign(format!("BJCA_SVS_Init_Default failed: {ret}, config={cfg_desc}")));
    }
    let guard = BjcaHandleGuard { handle };

    let mut sign_b64 = vec![0u8; BJCA_SIGN_BUF_LEN];
    let mut sign_b64_len = sign_b64.len() as bindings::BJCA_ULONG;
    let ret = unsafe {
        bindings::BJCA_SVS_SignData(
            guard.handle,
            signed_attrs_der.as_ptr() as *mut bindings::BJCA_UCHAR,
            signed_attrs_der.len() as bindings::BJCA_ULONG,
            sign_b64.as_mut_ptr(),
            &mut sign_b64_len,
        )
    };
    if ret != 0 {
        return Err(Error::CmsSign(format!("BJCA_SVS_SignData failed: {ret}")));
    }
    sign_b64.truncate(sign_b64_len as usize);

    let signature_der = base64_decode(guard.handle, &sign_b64, BJCA_DECODE_BUF_LEN)?;

    let mut cert_b64 = vec![0u8; BJCA_CERT_BUF_LEN];
    let mut cert_b64_len = cert_b64.len() as bindings::BJCA_ULONG;
    let ret = unsafe {
        bindings::BJCA_SVS_GetServerCertificate(
            guard.handle,
            cert_b64.as_mut_ptr(),
            &mut cert_b64_len,
        )
    };
    if ret != 0 {
        return Err(Error::CmsSign(format!(
            "BJCA_SVS_GetServerCertificate failed: {ret}"
        )));
    }
    cert_b64.truncate(cert_b64_len as usize);
    let leaf_cert_der = base64_decode(guard.handle, &cert_b64, BJCA_DECODE_BUF_LEN)?;

    Ok(BjcaSignOutput {
        signature_der,
        leaf_cert_der,
    })
}

fn base64_decode(
    handle: bindings::BJCA_HANDLE,
    input: &[u8],
    initial_out_len: usize,
) -> Result<Vec<u8>, Error> {
    let mut out = vec![0u8; initial_out_len.max(input.len() + 256)];
    let mut out_len = out.len() as bindings::BJCA_ULONG;
    let ret = unsafe {
        bindings::BJCA_SVS_Base64Decode(
            handle,
            input.as_ptr() as *mut bindings::BJCA_UCHAR,
            input.len() as bindings::BJCA_ULONG,
            out.as_mut_ptr(),
            &mut out_len,
        )
    };
    if ret != 0 {
        return Err(Error::CmsSign(format!(
            "BJCA_SVS_Base64Decode failed: {ret}"
        )));
    }
    out.truncate(out_len as usize);
    Ok(out)
}