#[cfg(not(feature = "std"))]
use alloc::format;
#[cfg(not(feature = "std"))]
use alloc::string::String;
#[cfg(not(feature = "std"))]
use alloc::vec::Vec;
use base64::engine::general_purpose::STANDARD;
use base64::Engine;
use mbedtls::alloc::List as MbedtlsList;
use mbedtls::hash::{Md, Type as MdType};
use mbedtls::pk::{Pk, Type as PkType};
use mbedtls::x509::Certificate as MbedtlsCertificate;
use rasn_cms::SignedAttributes;
use rasn_cms::{CertificateChoices, ContentInfo, SignedData, SignerIdentifier};
use super::gmssl_cms_native::gmssl_cms_content_digest;
use super::oids::{
ecdsa_sign_with_sha256_oid, gmssl_cms_signed_data_oid, pkcs9_message_digest_oid,
rsa_sign_with_sha256_oid, sha256_digest_oid, sm2_sign_with_sm3_oid, sm3_digest_oid,
};
use crate::error::Error;
use crate::limits::{LimitsError, VerifyLimits};
pub fn verify_sm2_cms(pkcs7_der: &[u8], plain: &[u8]) -> Result<(), Error> {
verify_cms_signature_with_ca_opt(pkcs7_der, plain, None)
}
pub fn verify_sm2_cms_with_ca_pem(
pkcs7_der: &[u8],
plain: &[u8],
ca_pem: &[u8],
) -> Result<(), Error> {
verify_cms_signature_with_ca_opt(pkcs7_der, plain, Some(ca_pem))
}
pub struct ExtractedCmsData {
pub signed_attrs_der: Vec<u8>,
pub signature_der: Vec<u8>,
pub leaf_cert_pem: String,
pub leaf_cert_der: Vec<u8>,
}
#[cfg(feature = "std")]
#[allow(dead_code)]
pub fn verify_sm2_cms_with_gmssl(
pkcs7_der: &[u8],
plain: &[u8],
_gmssl: &std::path::Path,
) -> Result<(), Error> {
verify_cms_signature_with_ca_opt(pkcs7_der, plain, None)
}
pub fn verify_cms_signature_with_ca_opt(
pkcs7_der: &[u8],
plain: &[u8],
ca_pem: Option<&[u8]>,
) -> Result<(), Error> {
verify_cms_signature_with_ca_opt_and_limits(pkcs7_der, plain, ca_pem, &VerifyLimits::default())
}
pub fn verify_cms_signature_with_ca_opt_and_limits(
pkcs7_der: &[u8],
plain: &[u8],
ca_pem: Option<&[u8]>,
limits: &VerifyLimits,
) -> Result<(), Error> {
limits.check_cms_der_len(pkcs7_der.len())?;
limits.check_plain_buffer_len(plain.len())?;
let ci: ContentInfo =
rasn::der::decode(pkcs7_der).map_err(|e| Error::CmsVerify(format!("ContentInfo: {e}")))?;
if ci.content_type == gmssl_cms_signed_data_oid() {
return verify_gmssl_compatible_cms(pkcs7_der, plain, ca_pem, limits);
}
let sd_bytes = ci.content.into_bytes();
let signed_data: SignedData =
rasn::der::decode(&sd_bytes).map_err(|e| Error::CmsVerify(format!("SignedData: {e}")))?;
assert_embedded_certificate_limits(&signed_data, limits)?;
let signer = signed_data
.signer_infos
.to_vec()
.into_iter()
.next()
.ok_or_else(|| Error::CmsVerify("no SignerInfo".into()))?;
let ias = match &signer.sid {
SignerIdentifier::IssuerAndSerialNumber(i) => i,
_ => {
return Err(Error::CmsVerify(
"only IssuerAndSerialNumber signer id supported".into(),
));
}
};
let extracted = extract_detached_parts(&signed_data, signer, ias)?;
let digest_md = if signer.digest_algorithm.algorithm == sm3_digest_oid() {
MdType::SM3
} else if signer.digest_algorithm.algorithm == sha256_digest_oid() {
MdType::Sha256
} else {
return Err(Error::CmsVerify("unsupported digest algorithm".into()));
};
let expected_digest = digest32(digest_md, plain)?;
let signed_attrs = signer
.signed_attrs
.as_ref()
.ok_or_else(|| Error::CmsVerify("missing signedAttrs".into()))?;
assert_signed_attrs(signed_attrs, &expected_digest)?;
if let Some(pem) = ca_pem {
verify_cert_chain_with_mbedtls(&signed_data, ias, pem)?;
}
if signer.signature_algorithm.algorithm == sm2_sign_with_sm3_oid() {
verify_with_leaf_public_key(
&extracted.leaf_cert_der,
&extracted.signed_attrs_der,
&extracted.signature_der,
MdType::SM3,
KeyAlgExpectation::Sm2OrEc,
)?;
} else {
verify_with_leaf_public_key(
&extracted.leaf_cert_der,
&extracted.signed_attrs_der,
&extracted.signature_der,
MdType::Sha256,
if signer.signature_algorithm.algorithm == rsa_sign_with_sha256_oid() {
KeyAlgExpectation::Rsa
} else if signer.signature_algorithm.algorithm == ecdsa_sign_with_sha256_oid() {
KeyAlgExpectation::Ecdsa
} else {
return Err(Error::CmsVerify("unsupported signature algorithm".into()));
},
)?;
}
if !digest_algs_contains_supported(&signed_data.digest_algorithms) {
return Err(Error::CmsVerify(
"digestAlgorithms must include SM3 or SHA256".into(),
));
}
Ok(())
}
pub fn verify_sm2_cms_with_ca_opt(
pkcs7_der: &[u8],
plain: &[u8],
ca_pem: Option<&[u8]>,
) -> Result<(), Error> {
verify_cms_signature_with_ca_opt(pkcs7_der, plain, ca_pem)
}
fn assert_embedded_certificate_limits(
signed_data: &SignedData,
limits: &VerifyLimits,
) -> Result<(), Error> {
let Some(certs) = signed_data.certificates.as_ref() else {
return Ok(());
};
let mut count = 0usize;
let mut total_der = 0usize;
for c in certs.to_vec() {
if let CertificateChoices::Certificate(cert) = c {
count += 1;
if count > limits.max_embedded_certificates {
return Err(LimitsError {
context: "SignedData 嵌入证书数量",
max: limits.max_embedded_certificates,
got: count,
}
.into());
}
let der = rasn::der::encode(cert.as_ref())
.map_err(|e| Error::CmsVerify(format!("encode cert: {e}")))?;
if der.len() > limits.max_certificate_der_bytes {
return Err(LimitsError {
context: "单张嵌入证书 DER 长度",
max: limits.max_certificate_der_bytes,
got: der.len(),
}
.into());
}
total_der = total_der
.checked_add(der.len())
.ok_or_else(|| LimitsError {
context: "嵌入证书 DER 累计长度",
max: limits.max_total_embedded_cert_der_bytes,
got: usize::MAX,
})?;
if total_der > limits.max_total_embedded_cert_der_bytes {
return Err(LimitsError {
context: "嵌入证书 DER 累计长度",
max: limits.max_total_embedded_cert_der_bytes,
got: total_der,
}
.into());
}
}
}
Ok(())
}
fn verify_cert_chain_with_mbedtls(
signed_data: &SignedData,
ias: &rasn_cms::IssuerAndSerialNumber,
ca_pem: &[u8],
) -> Result<(), Error> {
let certs = signed_data
.certificates
.as_ref()
.ok_or_else(|| Error::CmsVerify("no certificates in SignedData".into()))?;
let mut leaf_der: Option<Vec<u8>> = None;
let mut others = Vec::new();
for c in certs.to_vec() {
if let CertificateChoices::Certificate(cert) = c {
let der = rasn::der::encode(cert.as_ref())
.map_err(|e| Error::CmsVerify(format!("encode cert: {e}")))?;
if cert.tbs_certificate.issuer == ias.issuer
&& cert.tbs_certificate.serial_number == ias.serial_number
{
leaf_der = Some(der);
} else {
others.push(der);
}
}
}
let leaf_der = leaf_der
.ok_or_else(|| Error::CmsVerify("no embedded certificate matches signer".into()))?;
let mut chain = MbedtlsList::new();
chain.push(MbedtlsCertificate::from_der(&leaf_der)?);
for der in others {
chain.push(MbedtlsCertificate::from_der(&der)?);
}
let mut trust = ca_pem.to_vec();
if trust.last().copied() != Some(0) {
trust.push(0);
}
let trust_ca = MbedtlsCertificate::from_pem_multiple(&trust)?;
let mut err = String::new();
MbedtlsCertificate::verify(&chain, &trust_ca, None, Some(&mut err))
.map_err(|e| Error::CmsVerify(format!("certificate chain verify failed: {e}; {err}")))?;
Ok(())
}
fn verify_gmssl_compatible_cms(
pkcs7_der: &[u8],
plain: &[u8],
ca_pem: Option<&[u8]>,
limits: &VerifyLimits,
) -> Result<(), Error> {
let ci: ContentInfo =
rasn::der::decode(pkcs7_der).map_err(|e| Error::CmsVerify(format!("ContentInfo: {e}")))?;
let sd_bytes = ci.content.into_bytes();
let signed_data: SignedData =
rasn::der::decode(&sd_bytes).map_err(|e| Error::CmsVerify(format!("SignedData: {e}")))?;
assert_embedded_certificate_limits(&signed_data, limits)?;
let signer = signed_data
.signer_infos
.to_vec()
.into_iter()
.next()
.ok_or_else(|| Error::CmsVerify("no SignerInfo".into()))?;
let ias = match &signer.sid {
SignerIdentifier::IssuerAndSerialNumber(i) => i,
_ => {
return Err(Error::CmsVerify(
"only IssuerAndSerialNumber signer id supported".into(),
));
}
};
let content = signed_data
.encap_content_info
.content
.as_ref()
.ok_or_else(|| Error::CmsVerify("gmssl cms missing attached content".into()))?;
if content.as_ref() != plain {
return Err(Error::CmsVerify("gmssl cmsverify content mismatch".into()));
}
if let Some(pem) = ca_pem {
verify_cert_chain_with_mbedtls(&signed_data, ias, pem)?;
}
if signer.signature_algorithm.algorithm != sm2_sign_with_sm3_oid() {
return Err(Error::CmsVerify(
"gmssl cms signer signature algorithm is not sm2sign-with-sm3".into(),
));
}
if signer.digest_algorithm.algorithm != sm3_digest_oid() {
return Err(Error::CmsVerify(
"gmssl cms signer digest algorithm is not sm3".into(),
));
}
let leaf_der = find_cert_der_matching(&signed_data, ias)?;
let mut leaf_pem = der_to_pem("CERTIFICATE", &leaf_der).into_bytes();
leaf_pem.push(0);
let mut cert = MbedtlsCertificate::from_pem(&leaf_pem)?;
let pk: &mut Pk = cert.public_key_mut();
if pk.pk_type() != PkType::SM2 && pk.pk_type() != PkType::Eckey && pk.pk_type() != PkType::Ecdsa
{
return Err(Error::CmsVerify(
"gmssl cms leaf cert public key is not SM2/EC".into(),
));
}
let dgst = gmssl_cms_content_digest(plain)?;
pk.verify(MdType::SM3, &dgst, signer.signature.as_ref())
.map_err(|e| Error::CmsVerify(format!("gmssl cms sm2 verify failed: {e}")))?;
Ok(())
}
pub fn extract_cms_detached_parts(pkcs7_der: &[u8]) -> Result<ExtractedCmsData, Error> {
let ci: ContentInfo =
rasn::der::decode(pkcs7_der).map_err(|e| Error::CmsVerify(format!("ContentInfo: {e}")))?;
let sd_bytes = ci.content.into_bytes();
let signed_data: SignedData =
rasn::der::decode(&sd_bytes).map_err(|e| Error::CmsVerify(format!("SignedData: {e}")))?;
let signer = signed_data
.signer_infos
.to_vec()
.into_iter()
.next()
.ok_or_else(|| Error::CmsVerify("no SignerInfo".into()))?;
let ias = match &signer.sid {
SignerIdentifier::IssuerAndSerialNumber(i) => i,
_ => {
return Err(Error::CmsVerify(
"only IssuerAndSerialNumber signer id supported".into(),
));
}
};
extract_detached_parts(&signed_data, signer, ias)
}
fn extract_detached_parts(
signed_data: &SignedData,
signer: &rasn_cms::SignerInfo,
ias: &rasn_cms::IssuerAndSerialNumber,
) -> Result<ExtractedCmsData, Error> {
let leaf_der = find_cert_der_matching(signed_data, ias)?;
let leaf_pem = der_to_pem("CERTIFICATE", &leaf_der);
let signed_attrs = signer
.signed_attrs
.as_ref()
.ok_or_else(|| Error::CmsVerify("missing signedAttrs".into()))?;
let to_sign = encode_signed_attrs_for_verify(signed_attrs)?;
Ok(ExtractedCmsData {
signed_attrs_der: to_sign,
signature_der: signer.signature.as_ref().to_vec(),
leaf_cert_pem: leaf_pem,
leaf_cert_der: leaf_der,
})
}
fn encode_signed_attrs_for_verify(signed_attrs: &SignedAttributes) -> Result<Vec<u8>, Error> {
rasn::der::encode(signed_attrs)
.map_err(|e| Error::CmsVerify(format!("encode signedAttrs(set): {e}")))
}
fn find_cert_der_matching(
sd: &SignedData,
ias: &rasn_cms::IssuerAndSerialNumber,
) -> Result<Vec<u8>, Error> {
let certs = sd
.certificates
.as_ref()
.ok_or_else(|| Error::CmsVerify("no certificates in SignedData".into()))?;
for c in certs.to_vec() {
if let CertificateChoices::Certificate(cert) = c {
if cert.tbs_certificate.issuer == ias.issuer
&& cert.tbs_certificate.serial_number == ias.serial_number
{
return rasn::der::encode(cert.as_ref())
.map_err(|e| Error::CmsVerify(format!("encode cert: {e}")));
}
}
}
Err(Error::CmsVerify(
"no embedded certificate matches SignerInfo issuer/serial".into(),
))
}
fn digest_algs_contains_supported(algs: &rasn_cms::DigestAlgorithmIdentifiers) -> bool {
let sm3 = sm3_digest_oid();
let sha256 = sha256_digest_oid();
algs.to_vec()
.into_iter()
.any(|a| a.algorithm == sm3 || a.algorithm == sha256)
}
fn assert_signed_attrs(
attrs: &rasn_cms::SignedAttributes,
expected: &[u8; 32],
) -> Result<(), Error> {
let md_oid = pkcs9_message_digest_oid();
let mut found_md = false;
for attr in attrs.to_vec() {
if attr.r#type == md_oid {
let v = attr
.values
.to_vec()
.into_iter()
.next()
.ok_or_else(|| Error::CmsVerify("messageDigest empty".into()))?;
let oct: rasn::types::OctetString = rasn::der::decode(v.as_bytes())
.map_err(|e| Error::CmsVerify(format!("messageDigest decode: {e}")))?;
if oct.as_ref() != expected {
return Err(Error::CmsVerify("messageDigest mismatch".into()));
}
found_md = true;
}
}
if !found_md {
return Err(Error::CmsVerify("messageDigest attribute missing".into()));
}
Ok(())
}
fn digest32(md: MdType, data: &[u8]) -> Result<[u8; 32], Error> {
let mut out = [0u8; 32];
Md::hash(md, data, &mut out)?;
Ok(out)
}
enum KeyAlgExpectation {
Sm2OrEc,
Rsa,
Ecdsa,
}
fn verify_with_leaf_public_key(
cert_der: &[u8],
signed_attrs_der: &[u8],
sig_der: &[u8],
md: MdType,
expect: KeyAlgExpectation,
) -> Result<(), Error> {
let mut cert = mbedtls::x509::Certificate::from_der(cert_der)?;
let pk: &mut Pk = cert.public_key_mut();
match expect {
KeyAlgExpectation::Sm2OrEc => {
if pk.pk_type() != PkType::SM2
&& pk.pk_type() != PkType::Ecdsa
&& pk.pk_type() != PkType::Eckey
{
return Err(Error::CmsVerify(
"leaf cert public key is not SM2/EC".into(),
));
}
pk.sm2_verify(MdType::SM3, signed_attrs_der, sig_der)?;
return Ok(());
}
KeyAlgExpectation::Rsa => {
if pk.pk_type() != PkType::Rsa {
return Err(Error::CmsVerify("leaf cert public key is not RSA".into()));
}
}
KeyAlgExpectation::Ecdsa => {
if pk.pk_type() != PkType::Ecdsa && pk.pk_type() != PkType::Eckey {
return Err(Error::CmsVerify("leaf cert public key is not ECDSA".into()));
}
}
}
let dgst = digest32(md, signed_attrs_der)?;
pk.verify(md, &dgst, sig_der)?;
Ok(())
}
fn der_to_pem(label: &str, der: &[u8]) -> String {
let b64 = STANDARD.encode(der);
let mut out = String::new();
out.push_str(&format!("-----BEGIN {}-----\n", label));
for chunk in b64.as_bytes().chunks(64) {
out.push_str(core::str::from_utf8(chunk).unwrap_or(""));
out.push('\n');
}
out.push_str(&format!("-----END {}-----\n", label));
out
}