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, SignerIdentifier, SignerInfo, SignerInfos,
SignedAttributes, SignedData,
};
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,
};
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>,
}
pub struct SignInputs<'a> {
pub plain: &'a [u8],
pub leaf_cert_der: &'a [u8],
pub intermediate_certs_der: &'a [&'a [u8]],
pub cms_attached: bool,
pub cms_use_gmssl_oid: bool,
pub leaf_key_path: &'a Path,
pub leaf_key_pass: &'a str,
pub gmssl_path: Option<&'a Path>,
pub algorithm: CmsSignAlgorithm,
}
#[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> {
let leaf: Certificate = rasn::der::decode(input.leaf_cert_der)
.map_err(|e| Error::CmsSign(format!("leaf cert: {e}")))?;
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 sig_der = sign_signed_attrs(
input.leaf_key_path,
input.leaf_key_pass,
input.algorithm,
digest_md,
&signed_attrs_der,
)?;
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> {
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])
}