use crate::cert::Certificate as RustCert;
use crate::cms::envelope::CmsError;
use crate::cms::signed_data::{SignedData, SignerIdentifier};
#[derive(Debug, Clone, Default)]
pub struct CmsVerificationResult {
pub all_verified: bool,
pub per_signer: Vec<SignerVerification>,
}
#[derive(Debug, Clone)]
pub struct SignerVerification {
pub signer_index: usize,
pub verified: bool,
pub error: Option<String>,
pub cert_index: Option<usize>,
}
pub fn resolve_signer_certificate(
signer: &crate::cms::signed_data::SignerInfo,
certificates: &[Vec<u8>],
) -> Result<usize, CmsError> {
match &signer.sid {
SignerIdentifier::IssuerAndSerialNumber { serial_number, .. } => {
for (i, cert_der) in certificates.iter().enumerate() {
let cert = match RustCert::from_der(cert_der) {
Ok(c) => c,
Err(_) => continue,
};
if cert.serial_bytes() == serial_number.as_slice() {
return Ok(i);
}
}
}
SignerIdentifier::SubjectKeyIdentifier { key_identifier } => {
for (i, cert_der) in certificates.iter().enumerate() {
if let Some(ski) = extract_ski_extension(cert_der) {
if ski.as_slice() == key_identifier.as_slice() {
return Ok(i);
}
}
}
}
}
Err(CmsError::Verify(format!(
"could not resolve signer certificate (sid: {:?})",
signer.sid
)))
}
fn extract_ski_extension(cert_der: &[u8]) -> Option<Vec<u8>> {
let cert = match RustCert::from_der(cert_der) {
Ok(c) => c,
Err(_) => return None,
};
if let Some(exts) = cert.as_inner().tbs_certificate().extensions() {
for ext in exts.iter() {
if ext.extn_id.to_string() == "2.5.29.14" {
let raw = ext.extn_value.as_bytes();
if raw.len() >= 2 && raw[0] == 0x04 {
let len = raw[1] as usize;
if 2 + len <= raw.len() {
return Some(raw[2..2 + len].to_vec());
}
}
}
}
}
None
}
pub fn verify_signed_data<F>(
signed_data: &SignedData,
payload: &[u8],
verifier: F,
) -> Result<CmsVerificationResult, CmsError>
where
F: Fn(usize, &[u8], &[u8], &[u8]) -> Result<(), String>,
{
let mut all_verified = true;
let mut per_signer = Vec::new();
for (i, signer) in signed_data.signer_infos.iter().enumerate() {
let cert_index = match resolve_signer_certificate(signer, &signed_data.certificates) {
Ok(idx) => idx,
Err(e) => {
all_verified = false;
per_signer.push(SignerVerification {
signer_index: i,
verified: false,
error: Some(format!("unresolved signer: {e}")),
cert_index: None,
});
continue;
}
};
let cert_der = &signed_data.certificates[cert_index];
let pubkey_owned: Vec<u8>;
let pubkey: &[u8] = match RustCert::from_der(cert_der) {
Ok(c) => {
pubkey_owned = c.public_key_bytes().to_vec();
&pubkey_owned
}
Err(e) => {
all_verified = false;
per_signer.push(SignerVerification {
signer_index: i,
verified: false,
error: Some(format!("cert parse: {e}")),
cert_index: Some(cert_index),
});
continue;
}
};
let signed_bytes: Vec<u8> = if !signer.signed_attrs.is_empty() {
canonical_signed_attrs(&signer.signed_attrs)
} else if let Some(content) = &signed_data.encap_content_info.content {
content.clone()
} else {
payload.to_vec()
};
match verifier(i, pubkey, &signed_bytes, &signer.signature) {
Ok(()) => per_signer.push(SignerVerification {
signer_index: i,
verified: true,
error: None,
cert_index: Some(cert_index),
}),
Err(e) => {
all_verified = false;
per_signer.push(SignerVerification {
signer_index: i,
verified: false,
error: Some(e),
cert_index: Some(cert_index),
});
}
}
}
Ok(CmsVerificationResult {
all_verified,
per_signer,
})
}
fn canonical_signed_attrs(attrs: &[crate::cms::signed_data::Attribute]) -> Vec<u8> {
let mut sorted: Vec<&crate::cms::signed_data::Attribute> = attrs.iter().collect();
sorted.sort_by(|a, b| a.oid.cmp(&b.oid));
let mut out = Vec::new();
for attr in sorted {
out.extend_from_slice(attr.oid.as_bytes());
for v in &attr.values {
out.extend_from_slice(v);
}
}
out
}
#[cfg(test)]
mod tests {
use super::*;
use crate::cms::envelope::build_detached_signature;
#[test]
fn verify_with_accepting_callback_passes() {
let sd = build_detached_signature(
vec![0u8; 32],
"1.2.840.113549.1.1.11",
vec![0u8; 256],
vec![vec![0u8; 100]],
)
.unwrap();
let result = verify_signed_data(&sd, b"hello", |_, _, _, _| Ok(())).unwrap();
assert!(!result.all_verified);
}
#[test]
fn verify_with_rejecting_callback_fails() {
let sd = build_detached_signature(
vec![0u8; 32],
"1.2.840.113549.1.1.11",
vec![0u8; 256],
vec![vec![0u8; 100]],
)
.unwrap();
let result = verify_signed_data(&sd, b"hello", |_, _, _, _| Err("bad".into())).unwrap();
assert!(!result.all_verified);
}
#[test]
fn empty_signer_infos_succeeds() {
let sd = SignedData {
version: 1,
digest_algorithms: vec![],
encap_content_info: crate::cms::signed_data::EncapContentInfo {
content_type: "1.2.840.113549.1.7.1".to_string(),
content: None,
},
certificates: vec![],
signer_infos: vec![],
};
let result = verify_signed_data(&sd, b"hello", |_, _, _, _| Ok(())).unwrap();
assert!(result.all_verified);
assert!(result.per_signer.is_empty());
}
}