use crate::error::{Error, Result};
use cms::cert::CertificateChoices;
use cms::content_info::ContentInfo;
use cms::signed_data::SignerInfo;
use der::{Decode, Encode};
use rsa::RsaPublicKey;
use rsa::pkcs1::DecodeRsaPublicKey;
use rsa::signature::Verifier;
use rsa::traits::PublicKeyParts;
use sha2::{Sha256, Sha384, Sha512};
use tracing::{debug, trace, warn};
use x509_cert::certificate::Certificate;
use x509_cert::spki::SubjectPublicKeyInfoRef;
#[derive(Debug, Clone)]
pub struct CmsSignatureInfo {
pub signed_data: SignedDataInfo,
pub signers: Vec<SignerDetails>,
pub certificates: Vec<CertificateDetails>,
pub raw_signed_data: Vec<u8>,
}
#[derive(Debug, Clone)]
pub struct SignedDataInfo {
pub version: u8,
pub digest_algorithms: Vec<String>,
pub is_detached: bool,
}
#[derive(Debug, Clone)]
pub struct SignerDetails {
pub identifier: SignerIdentifier,
pub digest_algorithm: String,
pub signature_algorithm: String,
pub signature: Vec<u8>,
pub certificate: Option<CertificateDetails>,
pub public_key: Option<PublicKeyInfo>,
pub has_signed_attributes: bool,
pub signed_attributes_der: Option<Vec<u8>>,
}
#[derive(Debug, Clone)]
pub struct SignerIdentifier {
pub issuer: String,
pub serial_number: String,
}
#[derive(Debug, Clone)]
pub struct CertificateDetails {
pub subject: String,
pub issuer: String,
pub serial_number: String,
pub public_key: Option<PublicKeyInfo>,
}
#[derive(Debug, Clone)]
pub struct PublicKeyInfo {
pub algorithm: String,
pub key_size: usize,
pub key_bytes: Vec<u8>,
}
pub fn parse_cms_signature(signature_bytes: &[u8]) -> Result<CmsSignatureInfo> {
trace!("Parsing CMS signature: {} bytes", signature_bytes.len());
let content_info = ContentInfo::from_der(signature_bytes)
.map_err(|e| Error::Asn1Error(format!("Failed to parse ContentInfo: {e:?}")))?;
let signed_data_oid = der::asn1::ObjectIdentifier::new("1.2.840.113549.1.7.2")
.map_err(|e| Error::Asn1Error(format!("Invalid OID: {e}")))?;
if content_info.content_type != signed_data_oid {
return Err(Error::Asn1Error(
"ContentInfo is not SignedData".to_string(),
));
}
let signed_data_bytes = content_info
.content
.to_der()
.map_err(|e| Error::Asn1Error(format!("Failed to encode content: {e:?}")))?;
let signed_data = cms::signed_data::SignedData::from_der(&signed_data_bytes)
.map_err(|e| Error::Asn1Error(format!("Failed to parse SignedData: {e:?}")))?;
debug!(
"Parsed SignedData: {} signers",
signed_data.signer_infos.0.len()
);
let digest_algorithms: Vec<String> = signed_data
.digest_algorithms
.iter()
.map(|alg| oid_to_algorithm_name(&alg.oid))
.collect();
let is_detached = signed_data.encap_content_info.econtent.is_none();
let mut certificates = Vec::new();
if let Some(cert_set) = &signed_data.certificates {
debug!("Certificate set present with {} entries", cert_set.0.len());
for (i, cert_choice) in cert_set.0.iter().enumerate() {
match cert_choice {
CertificateChoices::Certificate(cert) => {
debug!("Entry {} is a Certificate", i);
if let Ok(details) = extract_certificate_details(cert) {
certificates.push(details);
}
}
CertificateChoices::Other(_) => {
debug!(
"Entry {} is not a Certificate (different CertificateChoice variant)",
i
);
}
}
}
} else {
debug!("No certificate set in SignedData");
}
debug!("Found {} certificates", certificates.len());
let mut signers = Vec::new();
debug!("Processing {} signers", signed_data.signer_infos.0.len());
for (i, signer_info) in signed_data.signer_infos.0.iter().enumerate() {
debug!("Processing signer #{}", i);
match parse_signer_info(signer_info, &certificates) {
Ok(signer) => {
debug!("Successfully parsed signer #{}", i);
signers.push(signer);
}
Err(e) => {
warn!("Failed to parse signer #{}: {}", i, e);
}
}
}
Ok(CmsSignatureInfo {
signed_data: SignedDataInfo {
version: 1, digest_algorithms,
is_detached,
},
signers,
certificates,
raw_signed_data: signed_data_bytes,
})
}
fn parse_signer_info(
signer_info: &SignerInfo,
certificates: &[CertificateDetails],
) -> Result<SignerDetails> {
let identifier = match &signer_info.sid {
cms::signed_data::SignerIdentifier::IssuerAndSerialNumber(isn) => {
debug!("Signer uses IssuerAndSerialNumber");
SignerIdentifier {
issuer: isn.issuer.to_string(),
serial_number: hex::encode(isn.serial_number.as_bytes()),
}
}
cms::signed_data::SignerIdentifier::SubjectKeyIdentifier(ski) => {
debug!("Signer uses SubjectKeyIdentifier");
let ski_hex = hex::encode(ski.0.as_bytes());
SignerIdentifier {
issuer: format!("SubjectKeyIdentifier: {ski_hex}"),
serial_number: ski_hex,
}
}
};
debug!(
"Looking for certificate matching issuer='{}', serial='{}'",
identifier.issuer, identifier.serial_number
);
let certificate = certificates
.iter()
.find(|cert| {
let matches =
cert.issuer == identifier.issuer && cert.serial_number == identifier.serial_number;
if !matches {
trace!(
"Certificate mismatch: cert.issuer='{}', cert.serial='{}'",
cert.issuer, cert.serial_number
);
}
matches
})
.cloned();
let public_key = certificate
.as_ref()
.and_then(|cert| cert.public_key.clone());
if certificate.is_none() {
warn!(
"No certificate found for signer: issuer='{}', serial='{}'",
identifier.issuer, identifier.serial_number
);
debug!("Available certificates: {}", certificates.len());
} else {
debug!("Found certificate for signer");
}
let (has_signed_attributes, signed_attributes_der) = if let Some(signed_attrs) =
&signer_info.signed_attrs
{
debug!("Signer has {} signed attributes", signed_attrs.len());
let mut attr_bytes = Vec::new();
let mut encoded_attrs = Vec::new();
for attr in signed_attrs.iter() {
encoded_attrs.push(
attr.to_der()
.map_err(|e| Error::Asn1Error(format!("Failed to encode attribute: {e}")))?,
);
}
encoded_attrs.sort();
attr_bytes.push(0x31);
let content_len: usize = encoded_attrs.iter().map(std::vec::Vec::len).sum();
if content_len < 128 {
#[allow(clippy::cast_possible_truncation)]
{
attr_bytes.push(content_len as u8);
}
} else {
let len_bytes = content_len.to_be_bytes();
let len_bytes = &len_bytes[len_bytes.iter().position(|&b| b != 0).unwrap_or(0)..];
#[allow(clippy::cast_possible_truncation)]
{
attr_bytes.push(0x80 | len_bytes.len() as u8);
}
attr_bytes.extend_from_slice(len_bytes);
}
for attr in encoded_attrs {
attr_bytes.extend_from_slice(&attr);
}
(true, Some(attr_bytes))
} else {
debug!("Signer has no signed attributes - signature is directly over content");
(false, None)
};
Ok(SignerDetails {
identifier,
digest_algorithm: oid_to_algorithm_name(&signer_info.digest_alg.oid),
signature_algorithm: oid_to_algorithm_name(&signer_info.signature_algorithm.oid),
signature: signer_info.signature.as_bytes().to_vec(),
certificate,
public_key,
has_signed_attributes,
signed_attributes_der,
})
}
fn extract_certificate_details(cert: &Certificate) -> Result<CertificateDetails> {
let tbs = &cert.tbs_certificate;
let spki_der = tbs
.subject_public_key_info
.to_der()
.map_err(|e| Error::Asn1Error(format!("Failed to encode SPKI: {e}")))?;
let spki_ref = SubjectPublicKeyInfoRef::from_der(&spki_der)
.map_err(|e| Error::Asn1Error(format!("Failed to parse SPKI: {e}")))?;
let public_key = extract_public_key_info(&spki_ref);
Ok(CertificateDetails {
subject: tbs.subject.to_string(),
issuer: tbs.issuer.to_string(),
serial_number: hex::encode(tbs.serial_number.as_bytes()),
public_key: Some(public_key),
})
}
fn extract_public_key_info(spki: &SubjectPublicKeyInfoRef<'_>) -> PublicKeyInfo {
let algorithm = oid_to_algorithm_name(&spki.algorithm.oid);
let key_bytes = spki.subject_public_key.raw_bytes().to_vec();
let key_size = match algorithm.as_str() {
"RSA" => {
if let Ok(rsa_key) = RsaPublicKey::from_pkcs1_der(spki.subject_public_key.raw_bytes()) {
rsa_key.size() * 8
} else {
key_bytes.len() * 8
}
}
_ => key_bytes.len() * 8,
};
PublicKeyInfo {
algorithm,
key_size,
key_bytes,
}
}
fn oid_to_algorithm_name(oid: &der::asn1::ObjectIdentifier) -> String {
match oid.to_string().as_str() {
"2.16.840.1.101.3.4.2.1" => "SHA-256".to_string(),
"2.16.840.1.101.3.4.2.2" => "SHA-384".to_string(),
"2.16.840.1.101.3.4.2.3" => "SHA-512".to_string(),
"1.3.14.3.2.26" => "SHA-1".to_string(),
"1.2.840.113549.1.1.11" => "RSA with SHA-256".to_string(),
"1.2.840.113549.1.1.12" => "RSA with SHA-384".to_string(),
"1.2.840.113549.1.1.13" => "RSA with SHA-512".to_string(),
"1.2.840.113549.1.1.5" => "RSA with SHA-1".to_string(),
"1.2.840.113549.1.1.1" => "RSA".to_string(),
"1.2.840.10045.4.3.2" => "ECDSA with SHA-256".to_string(),
"1.2.840.10045.4.3.3" => "ECDSA with SHA-384".to_string(),
"1.2.840.10045.4.3.4" => "ECDSA with SHA-512".to_string(),
_ => format!("OID: {oid}"),
}
}
pub fn verify_with_public_key(
public_key: &PublicKeyInfo,
signed_data: &[u8],
signature: &[u8],
digest_algorithm: &str,
) -> Result<bool> {
match public_key.algorithm.as_str() {
algo if algo == "RSA" || algo.starts_with("RSA with") => {
verify_rsa_signature(public_key, signed_data, signature, digest_algorithm)
}
_ => Err(Error::Asn1Error(format!(
"Unsupported algorithm for verification: {}",
public_key.algorithm
))),
}
}
fn verify_rsa_signature(
public_key: &PublicKeyInfo,
signed_data: &[u8],
signature: &[u8],
digest_algorithm: &str,
) -> Result<bool> {
let rsa_key = if let Ok(key) = RsaPublicKey::from_pkcs1_der(&public_key.key_bytes) {
key
} else {
let spki = x509_cert::spki::SubjectPublicKeyInfoOwned::from_der(&public_key.key_bytes)
.map_err(|e| Error::Asn1Error(format!("Failed to parse SubjectPublicKeyInfo: {e}")))?;
RsaPublicKey::from_pkcs1_der(spki.subject_public_key.raw_bytes())
.map_err(|e| Error::Asn1Error(format!("Failed to decode RSA key from SPKI: {e}")))?
};
let result = match digest_algorithm {
"SHA-256" => {
let verifying_key = rsa::pkcs1v15::VerifyingKey::<Sha256>::new(rsa_key);
let signature_obj = rsa::pkcs1v15::Signature::try_from(signature)
.map_err(|e| Error::Asn1Error(format!("Invalid signature format: {e}")))?;
verifying_key.verify(signed_data, &signature_obj).is_ok()
}
"SHA-384" => {
let verifying_key = rsa::pkcs1v15::VerifyingKey::<Sha384>::new(rsa_key);
let signature_obj = rsa::pkcs1v15::Signature::try_from(signature)
.map_err(|e| Error::Asn1Error(format!("Invalid signature format: {e}")))?;
verifying_key.verify(signed_data, &signature_obj).is_ok()
}
"SHA-512" => {
let verifying_key = rsa::pkcs1v15::VerifyingKey::<Sha512>::new(rsa_key);
let signature_obj = rsa::pkcs1v15::Signature::try_from(signature)
.map_err(|e| Error::Asn1Error(format!("Invalid signature format: {e}")))?;
verifying_key.verify(signed_data, &signature_obj).is_ok()
}
_ => {
return Err(Error::Asn1Error(format!(
"Unsupported digest algorithm: {digest_algorithm}"
)));
}
};
debug!(
"RSA signature verification with {}: {}",
digest_algorithm,
if result { "SUCCESS" } else { "FAILED" }
);
Ok(result)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_oid_to_algorithm_name() {
use der::asn1::ObjectIdentifier;
let sha256_oid = ObjectIdentifier::new("2.16.840.1.101.3.4.2.1").unwrap();
assert_eq!(oid_to_algorithm_name(&sha256_oid), "SHA-256");
let rsa_sha256_oid = ObjectIdentifier::new("1.2.840.113549.1.1.11").unwrap();
assert_eq!(oid_to_algorithm_name(&rsa_sha256_oid), "RSA with SHA-256");
}
}