tasign 0.1.4

TA ELF signing utilities with CMS/PKCS#7 support
Documentation
//! 构造 `SignedData` 并输出 PKCS#7 DER(SM2/RSA/ECDSA)。

use std::path::Path;

use chrono::Utc;
use mbedtls::hash::{Md, Type as MdType};
use mbedtls::pk::{Pk, Type as PkType, ECDSA_MAX_LEN};
use mbedtls::rng::{CtrDrbg, OsEntropy};
use rasn::types::{Any, Integer, OctetString, SetOf, UtcTime};
use rasn_cms::{
    CertificateChoices, ContentInfo, DigestAlgorithmIdentifiers, EncapsulatedContentInfo,
    IssuerAndSerialNumber, SignatureAlgorithmIdentifier, SignedAttributes, SignedData,
    SignerIdentifier, SignerInfo, SignerInfos,
};
use rasn_pkix::{AlgorithmIdentifier, Attribute, Certificate};

use super::oids::{
    ecdsa_sign_with_sha256_oid, gmssl_cms_data_oid, gmssl_cms_signed_data_oid, pkcs7_data_oid,
    pkcs9_content_type_oid, pkcs9_message_digest_oid, pkcs9_signing_time_oid,
    rsa_sign_with_sha256_oid, sha256_digest_oid, sm2_sign_with_sm3_oid, sm3_digest_oid,
};
#[cfg(feature = "bjca")]
use crate::bjca::bjca_sign::sign_signed_attrs_with_bjca;
use crate::error::Error;
use crate::gmssl_cli::resolve_gmssl_path;
use crate::key::sm2_pk_from_pkcs8_pem_with_pass;
use std::sync::Arc;

pub struct SignArtifacts {
    pub signature_der: Vec<u8>,
    pub signed_attrs_der: Vec<u8>,
}

/// 签名输入:证书链 DER,leaf 私钥文件路径与口令。
pub struct SignInputs<'a> {
    pub plain: &'a [u8],
    pub leaf_cert_der: Option<&'a [u8]>,
    pub intermediate_certs_der: &'a [&'a [u8]],
    pub cms_attached: bool,
    pub cms_use_gmssl_oid: bool,
    pub leaf_key_path: Option<&'a Path>,
    pub leaf_key_pass: Option<&'a str>,
    pub gmssl_path: Option<&'a Path>,
    pub algorithm: CmsSignAlgorithm,
    pub use_bjca: bool,
    pub bjca_config_path: Option<&'a Path>,
}

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum CmsSignAlgorithm {
    Sm2WithSm3,
    Rsa2048WithSha256,
    EcdsaWithSha256,
}

fn hash_fixed32(md: MdType, data: &[u8]) -> Result<[u8; 32], Error> {
    let mut ret = [0u8; 32];
    Md::hash(md, data, &mut ret)?;
    Ok(ret)
}

pub fn sign_sm2_cms(input: SignInputs<'_>) -> Result<Vec<u8>, Error> {
    Ok(sign_sm2_cms_with_artifacts(input)?.signature_der)
}

pub fn sign_sm2_cms_with_artifacts(input: SignInputs<'_>) -> Result<SignArtifacts, Error> {
    // 保留 `gmssl_path` 字段以兼容 CLI;此处仅校验路径可解析,签名计算不调用 GmSSL 子进程。
    let _gmssl = resolve_gmssl_path(input.gmssl_path);
    let (digest_alg_oid, digest_md, sig_alg_oid) = match input.algorithm {
        CmsSignAlgorithm::Sm2WithSm3 => (sm3_digest_oid(), MdType::SM3, sm2_sign_with_sm3_oid()),
        CmsSignAlgorithm::Rsa2048WithSha256 => (
            sha256_digest_oid(),
            MdType::Sha256,
            rsa_sign_with_sha256_oid(),
        ),
        CmsSignAlgorithm::EcdsaWithSha256 => (
            sha256_digest_oid(),
            MdType::Sha256,
            ecdsa_sign_with_sha256_oid(),
        ),
    };
    let digest = hash_fixed32(digest_md, input.plain)?;
    let signing_time: UtcTime = Utc::now();

    let data_oid = if input.cms_use_gmssl_oid {
        gmssl_cms_data_oid()
    } else {
        pkcs7_data_oid()
    };
    let attrs = build_signed_attributes(&digest, &signing_time, data_oid.clone())?;
    let signed_attrs = SetOf::from_vec(attrs);

    let signed_attrs_der = encode_signed_attrs_for_signing(&signed_attrs)?;

    let (leaf_der, sig_der) = if input.use_bjca {
        #[cfg(not(feature = "bjca"))]
        {
            return Err(Error::CmsSign(
                "当前二进制未启用 bjca feature,请使用 --features bjca 重新编译".into(),
            ));
        }
        #[cfg(feature = "bjca")]
        {
            if input.algorithm != CmsSignAlgorithm::Sm2WithSm3 {
                return Err(Error::CmsSign("--bjca 仅支持 --algorithm sm2-sm3".into()));
            }
            if !input.intermediate_certs_der.is_empty() {
                return Err(Error::CmsSign("--bjca 模式不支持中间证书".into()));
            }
            let bjca_out = sign_signed_attrs_with_bjca(&signed_attrs_der, input.bjca_config_path)?;
            (bjca_out.leaf_cert_der, bjca_out.signature_der)
        }
    } else {
        let leaf_der = input
            .leaf_cert_der
            .ok_or_else(|| Error::CmsSign("missing leaf cert for non-bjca signing".into()))?
            .to_vec();
        let leaf_key_path = input
            .leaf_key_path
            .ok_or_else(|| Error::CmsSign("missing leaf key for non-bjca signing".into()))?;
        let leaf_key_pass = input
            .leaf_key_pass
            .ok_or_else(|| Error::CmsSign("missing leaf key pass for non-bjca signing".into()))?;
        let sig = sign_signed_attrs(
            leaf_key_path,
            leaf_key_pass,
            input.algorithm,
            digest_md,
            &signed_attrs_der,
        )?;
        (leaf_der, sig)
    };
    let leaf: Certificate =
        rasn::der::decode(&leaf_der).map_err(|e| Error::CmsSign(format!("leaf cert: {e}")))?;

    let sid = SignerIdentifier::IssuerAndSerialNumber(IssuerAndSerialNumber {
        issuer: leaf.tbs_certificate.issuer.clone(),
        serial_number: leaf.tbs_certificate.serial_number.clone(),
    });
    let leaf_for_cert = leaf.clone();
    let mut cert_choices = vec![CertificateChoices::Certificate(Box::new(leaf_for_cert))];
    for (idx, der) in input.intermediate_certs_der.iter().enumerate() {
        let cert: Certificate = rasn::der::decode(der)
            .map_err(|e| Error::CmsSign(format!("intermediate cert #{idx}: {e}")))?;
        cert_choices.push(CertificateChoices::Certificate(Box::new(cert)));
    }

    let digest_alg = AlgorithmIdentifier {
        algorithm: digest_alg_oid,
        parameters: None,
    };

    let sig_alg = SignatureAlgorithmIdentifier {
        algorithm: sig_alg_oid,
        parameters: None,
    };

    let signer_info = SignerInfo {
        version: Integer::from(1),
        sid,
        digest_algorithm: digest_alg.clone(),
        signed_attrs: Some(signed_attrs),
        signature_algorithm: sig_alg,
        signature: OctetString::from(sig_der),
        unsigned_attrs: None,
    };

    let mut signer_infos = SignerInfos::new();
    signer_infos.insert(signer_info);

    let mut digest_algs = DigestAlgorithmIdentifiers::new();
    digest_algs.insert(digest_alg);

    let signed_data = SignedData {
        version: Integer::from(1),
        digest_algorithms: digest_algs,
        encap_content_info: EncapsulatedContentInfo {
            content_type: data_oid,
            content: if input.cms_attached {
                Some(OctetString::from(input.plain.to_vec()))
            } else {
                None
            },
        },
        certificates: Some(SetOf::from_vec(cert_choices)),
        crls: None,
        signer_infos,
    };

    let sd_der = rasn::der::encode(&signed_data)?;
    let ci = ContentInfo {
        content_type: if input.cms_use_gmssl_oid {
            gmssl_cms_signed_data_oid()
        } else {
            rasn::types::ObjectIdentifier::from(rasn_cms::CONTENT_SIGNED_DATA)
        },
        content: Any::new(sd_der),
    };

    let signature_der = rasn::der::encode(&ci).map_err(Error::from)?;
    Ok(SignArtifacts {
        signature_der,
        signed_attrs_der,
    })
}

fn encode_signed_attrs_for_signing(signed_attrs: &SignedAttributes) -> Result<Vec<u8>, Error> {
    // 标准 PKCS#7/CMS(非 GmSSL 兼容路径)统一按 SET OF SignedAttributes 编码待签字节。
    rasn::der::encode(signed_attrs)
        .map_err(|e| Error::CmsSign(format!("encode signedAttrs(set): {e}")))
}

fn sign_signed_attrs(
    leaf_key_path: &Path,
    leaf_key_pass: &str,
    algorithm: CmsSignAlgorithm,
    digest_md: MdType,
    signed_attrs_der: &[u8],
) -> Result<Vec<u8>, Error> {
    let entropy = Arc::new(OsEntropy::new());
    let mut rng = CtrDrbg::new(entropy, None)?;
    let pwd = if leaf_key_pass.is_empty() {
        None
    } else {
        Some(leaf_key_pass.to_string())
    };
    let mut pk =
        Pk::parse_keyfile(leaf_key_path.to_string_lossy().to_string(), pwd).or_else(|e| {
            if algorithm == CmsSignAlgorithm::Sm2WithSm3 {
                let pem = std::fs::read_to_string(leaf_key_path)?;
                sm2_pk_from_pkcs8_pem_with_pass(&pem, leaf_key_pass)
            } else {
                Err(e.into())
            }
        })?;

    let mut sig = vec![0u8; ECDSA_MAX_LEN.max((pk.len() / 8) + 32)];
    let sig_len = match algorithm {
        CmsSignAlgorithm::Sm2WithSm3 => {
            pk.sm2_sign(digest_md, signed_attrs_der, &mut sig, &mut rng)?
        }
        CmsSignAlgorithm::Rsa2048WithSha256 | CmsSignAlgorithm::EcdsaWithSha256 => {
            if algorithm == CmsSignAlgorithm::Rsa2048WithSha256 && pk.pk_type() != PkType::Rsa {
                return Err(Error::CmsSign("leaf key is not RSA".into()));
            }
            if algorithm == CmsSignAlgorithm::EcdsaWithSha256
                && pk.pk_type() != PkType::Ecdsa
                && pk.pk_type() != PkType::Eckey
            {
                return Err(Error::CmsSign("leaf key is not ECDSA".into()));
            }
            let dgst = hash_fixed32(digest_md, signed_attrs_der)?;
            pk.sign(digest_md, &dgst, &mut sig, &mut rng)?
        }
    };
    sig.truncate(sig_len);
    Ok(sig)
}

fn build_signed_attributes(
    digest: &[u8; 32],
    signing_time: &UtcTime,
    data_oid: rasn::types::ObjectIdentifier,
) -> Result<Vec<Attribute>, Error> {
    let data_oid_der = rasn::der::encode(&data_oid).map_err(|e| Error::CmsSign(e.to_string()))?;

    let a_content = Attribute {
        r#type: pkcs9_content_type_oid(),
        values: SetOf::from_vec(vec![Any::new(data_oid_der)]),
    };

    let md_der = rasn::der::encode(&OctetString::from(digest.as_slice()))
        .map_err(|e| Error::CmsSign(e.to_string()))?;
    let a_md = Attribute {
        r#type: pkcs9_message_digest_oid(),
        values: SetOf::from_vec(vec![Any::new(md_der)]),
    };

    let st_der = rasn::der::encode(signing_time).map_err(|e| Error::CmsSign(e.to_string()))?;
    let a_time = Attribute {
        r#type: pkcs9_signing_time_oid(),
        values: SetOf::from_vec(vec![Any::new(st_der)]),
    };

    Ok(vec![a_content, a_md, a_time])
}