use base64::engine::general_purpose::STANDARD;
use base64::Engine;
use std::path::Path;
use std::process::Command;
use tasign::{
sign_gmssl_cms_attached_native, sign_sm2_cms, verify_elf_signature_from_parts, verify_sm2_cms,
verify_sm2_cms_with_ca_pem, CmsSignAlgorithm, SignInputs,
};
static LEAF_PEM: &str = include_str!("fixtures/gmssl/leaf.crt");
static INT_PEM: &str = include_str!("fixtures/gmssl/intermediate.crt");
static CA_PEM: &str = include_str!("fixtures/gmssl/ca.crt");
const LEAF_KEY_PATH: &str = "tests/fixtures/gmssl/leaf.key";
static CA_LEAF_PEM: &str = include_str!("fixtures/gmssl/ca_leaf.crt");
const CA_LEAF_KEY_PATH: &str = "tests/fixtures/gmssl/ca_leaf.key";
const KEY_PASS: &str = "123456";
fn cert_der_from_pem(pem: &str) -> Vec<u8> {
let b64 = pem
.lines()
.filter(|l| !l.starts_with("-----"))
.collect::<String>();
STANDARD.decode(b64).expect("pem b64")
}
fn der_to_pem_cms(der: &[u8]) -> String {
let b64 = STANDARD.encode(der);
let mut out = String::new();
out.push_str("-----BEGIN CMS-----\n");
for chunk in b64.as_bytes().chunks(64) {
out.push_str(std::str::from_utf8(chunk).unwrap());
out.push('\n');
}
out.push_str("-----END CMS-----\n");
out
}
fn repo_root() -> std::path::PathBuf {
Path::new(env!("CARGO_MANIFEST_DIR")).join("../..")
}
fn gmssl_build_bin() -> std::path::PathBuf {
repo_root().join("GmSSL/build/bin/gmssl")
}
#[test]
fn cms_sign_verify_roundtrip() {
let plain = b"hello plain.bin payload for tasign";
let leaf_der = cert_der_from_pem(LEAF_PEM);
let int_der = cert_der_from_pem(INT_PEM);
let ints: Vec<&[u8]> = vec![&int_der];
let pkcs7 = sign_sm2_cms(SignInputs {
plain,
leaf_cert_der: Some(&leaf_der),
intermediate_certs_der: &ints,
cms_attached: false,
cms_use_gmssl_oid: false,
leaf_key_path: Some(Path::new(LEAF_KEY_PATH)),
leaf_key_pass: Some(KEY_PASS),
gmssl_path: None,
algorithm: CmsSignAlgorithm::Sm2WithSm3,
use_bjca: false,
bjca_config_path: None,
})
.expect("sign");
verify_elf_signature_from_parts(plain, &pkcs7, None).expect("verify");
}
#[test]
fn cms_sign_verify_roundtrip_with_ca_chain() {
let plain = b"hello plain.bin payload for tasign chain verify";
let leaf_der = cert_der_from_pem(LEAF_PEM);
let int_der = cert_der_from_pem(INT_PEM);
let ints: Vec<&[u8]> = vec![&int_der];
let pkcs7 = sign_sm2_cms(SignInputs {
plain,
leaf_cert_der: Some(&leaf_der),
intermediate_certs_der: &ints,
cms_attached: false,
cms_use_gmssl_oid: false,
leaf_key_path: Some(Path::new(LEAF_KEY_PATH)),
leaf_key_pass: Some(KEY_PASS),
gmssl_path: None,
algorithm: CmsSignAlgorithm::Sm2WithSm3,
use_bjca: false,
bjca_config_path: None,
})
.expect("sign");
verify_elf_signature_from_parts(plain, &pkcs7, Some(CA_PEM.as_bytes()))
.expect("verify with chain");
}
#[test]
fn cms_verify_fails_with_wrong_intermediate() {
let plain = b"hello plain.bin payload for tasign wrong intermediate";
let leaf_der = cert_der_from_pem(LEAF_PEM);
let wrong_int_der = leaf_der.clone();
let ints: Vec<&[u8]> = vec![&wrong_int_der];
let pkcs7 = sign_sm2_cms(SignInputs {
plain,
leaf_cert_der: Some(&leaf_der),
intermediate_certs_der: &ints,
cms_attached: false,
cms_use_gmssl_oid: false,
leaf_key_path: Some(Path::new(LEAF_KEY_PATH)),
leaf_key_pass: Some(KEY_PASS),
gmssl_path: None,
algorithm: CmsSignAlgorithm::Sm2WithSm3,
use_bjca: false,
bjca_config_path: None,
})
.expect("sign");
let err = verify_sm2_cms_with_ca_pem(&pkcs7, plain, CA_PEM.as_bytes()).unwrap_err();
let s = err.to_string();
assert!(
s.contains("chain verify failed") || s.contains("certificate") || s.contains("verify"),
"{s}"
);
}
#[test]
fn cms_rejects_tampered_plain() {
let plain = b"original";
let leaf_der = cert_der_from_pem(LEAF_PEM);
let int_der = cert_der_from_pem(INT_PEM);
let ints: Vec<&[u8]> = vec![&int_der];
let pkcs7 = sign_sm2_cms(SignInputs {
plain,
leaf_cert_der: Some(&leaf_der),
intermediate_certs_der: &ints,
cms_attached: false,
cms_use_gmssl_oid: false,
leaf_key_path: Some(Path::new(LEAF_KEY_PATH)),
leaf_key_pass: Some(KEY_PASS),
gmssl_path: None,
algorithm: CmsSignAlgorithm::Sm2WithSm3,
use_bjca: false,
bjca_config_path: None,
})
.expect("sign");
let err = verify_sm2_cms(&pkcs7, b"tampered").unwrap_err();
let s = err.to_string();
assert!(
s.contains("messageDigest") || s.contains("mismatch") || s.contains("SM2"),
"{s}"
);
}
#[test]
fn cms_sign_verify_without_intermediate() {
let plain = b"plain no intermediate";
let leaf_der = cert_der_from_pem(LEAF_PEM);
let ints: Vec<&[u8]> = vec![];
let pkcs7 = sign_sm2_cms(SignInputs {
plain,
leaf_cert_der: Some(&leaf_der),
intermediate_certs_der: &ints,
cms_attached: false,
cms_use_gmssl_oid: false,
leaf_key_path: Some(Path::new(LEAF_KEY_PATH)),
leaf_key_pass: Some(KEY_PASS),
gmssl_path: None,
algorithm: CmsSignAlgorithm::Sm2WithSm3,
use_bjca: false,
bjca_config_path: None,
})
.expect("sign");
verify_sm2_cms(&pkcs7, plain).expect("verify");
}
#[test]
fn cms_sign_verify_without_intermediate_ca_issued_leaf_with_chain() {
let plain = b"plain no intermediate with direct ca-issued leaf";
let leaf_der = cert_der_from_pem(CA_LEAF_PEM);
let ints: Vec<&[u8]> = vec![];
let pkcs7 = sign_sm2_cms(SignInputs {
plain,
leaf_cert_der: Some(&leaf_der),
intermediate_certs_der: &ints,
cms_attached: false,
cms_use_gmssl_oid: false,
leaf_key_path: Some(Path::new(CA_LEAF_KEY_PATH)),
leaf_key_pass: Some(KEY_PASS),
gmssl_path: None,
algorithm: CmsSignAlgorithm::Sm2WithSm3,
use_bjca: false,
bjca_config_path: None,
})
.expect("sign");
verify_sm2_cms_with_ca_pem(&pkcs7, plain, CA_PEM.as_bytes()).expect("verify with root CA");
}
#[test]
fn gmssl_cmsverify_leaf_only_native_signature() {
let plain = b"tasign: cmsverify closed-loop, leaf-only, no intermediate";
let leaf_der = cert_der_from_pem(LEAF_PEM);
let leaf_key_pem = include_str!("fixtures/gmssl/leaf.key");
let pkcs7_der = match sign_gmssl_cms_attached_native(&leaf_der, leaf_key_pem, KEY_PASS, plain) {
Ok(d) => d,
Err(e) => {
eprintln!("skip gmssl_cmsverify_leaf_only_native_signature: sign failed: {e}");
return;
}
};
let gmssl = gmssl_build_bin();
if !gmssl.exists() {
eprintln!(
"skip gmssl_cmsverify_leaf_only_native_signature: not found {}",
gmssl.display()
);
return;
}
let tmp = tempfile::tempdir().expect("tempdir");
let cms_pem = tmp.path().join("signed.cms.pem");
let out_plain = tmp.path().join("verified_content.bin");
std::fs::write(&cms_pem, der_to_pem_cms(&pkcs7_der)).expect("write cms pem");
let mut cmd = Command::new(&gmssl);
cmd.args([
"cmsverify",
"-in",
cms_pem.to_str().expect("utf8 path"),
"-out",
out_plain.to_str().expect("utf8 path"),
]);
#[cfg(unix)]
{
let lib = repo_root().join("GmSSL/build/lib");
if lib.is_dir() {
let new_ld = match std::env::var("LD_LIBRARY_PATH") {
Ok(old) if !old.is_empty() => format!("{}:{}", lib.display(), old),
_ => lib.display().to_string(),
};
cmd.env("LD_LIBRARY_PATH", new_ld);
}
}
let out = match cmd.output() {
Ok(o) => o,
Err(e) => {
eprintln!("skip gmssl_cmsverify_leaf_only_native_signature: {e}");
return;
}
};
if !out.status.success() {
let stderr = String::from_utf8_lossy(&out.stderr);
if stderr.contains("libgmssl") || stderr.contains("cannot open shared object") {
eprintln!("skip gmssl_cmsverify_leaf_only_native_signature: {stderr}");
return;
}
panic!(
"gmssl cmsverify failed (status {:?}):\nstdout={}\nstderr={}",
out.status,
String::from_utf8_lossy(&out.stdout),
stderr
);
}
let recovered = std::fs::read(&out_plain).expect("read verified content");
assert_eq!(
recovered.as_slice(),
plain.as_slice(),
"cmsverify output must match signed plaintext"
);
verify_sm2_cms(&pkcs7_der, plain).expect("tasign verify gmssl-compatible cms");
}