use cms::{
cert::CertificateChoices,
content_info::ContentInfo,
signed_data::{SignedData, SignerIdentifier},
};
use const_oid::db::{
rfc5911::ID_MESSAGE_DIGEST,
rfc5912::{
ECDSA_WITH_SHA_256, ECDSA_WITH_SHA_384, ID_SHA_256, ID_SHA_384, ID_SHA_512, RSA_ENCRYPTION,
SHA_256_WITH_RSA_ENCRYPTION, SHA_384_WITH_RSA_ENCRYPTION, SHA_512_WITH_RSA_ENCRYPTION,
},
};
use der::{asn1::OctetString, Decode, Encode};
use sha2::{Digest, Sha256, Sha384, Sha512};
use x509_cert::Certificate;
use crate::{
cert::validate_chain,
error::{SignerResult, VerificationResult},
key::RevocationChecker,
sig_verify, SmimeError,
};
pub fn verify(
signed_content: &[u8],
signature_der: &[u8],
trust_anchors: &[Certificate],
now: std::time::SystemTime,
revocation: &dyn RevocationChecker,
) -> Result<VerificationResult, SmimeError> {
let ci = ContentInfo::from_der(signature_der)?;
let content_der = ci.content.to_der()?;
let sd = SignedData::from_der(content_der.as_slice())?;
let bag_certs: Vec<Certificate> = sd
.certificates
.as_ref()
.map(|cs| {
cs.0.iter()
.filter_map(|c| match c {
CertificateChoices::Certificate(cert) => Some(cert.clone()),
_ => None,
})
.collect()
})
.unwrap_or_default();
let signers: Vec<SignerResult> = sd
.signer_infos
.0
.iter()
.map(|si| {
verify_one(
signed_content,
si,
&bag_certs,
trust_anchors,
now,
revocation,
)
})
.collect();
if signers.is_empty() {
return Err(SmimeError::MalformedInput(
"no SignerInfo entries in SignedData".into(),
));
}
if signers.iter().all(|s| !s.verified) {
return Err(SmimeError::AllSignersFailed(signers));
}
Ok(VerificationResult { signers })
}
fn fail(subject: Option<String>, msg: impl Into<String>) -> SignerResult {
SignerResult {
verified: false,
subject,
error: Some(msg.into()),
}
}
fn verify_one(
signed_content: &[u8],
si: &cms::signed_data::SignerInfo,
bag_certs: &[Certificate],
trust_anchors: &[Certificate],
now: std::time::SystemTime,
revocation: &dyn RevocationChecker,
) -> SignerResult {
let hash = match compute_digest(signed_content, &si.digest_alg.oid) {
Ok(h) => h,
Err(e) => return fail(None, e.to_string()),
};
let signer_cert = match find_cert(bag_certs, trust_anchors, &si.sid) {
Ok(Some(c)) => c,
Ok(None) => {
return fail(
None,
"signer cert not found in certificate bag or trust anchors",
)
}
Err(e) => return fail(None, format!("signer identifier DER encode: {e}")),
};
let subject_str = signer_cert.tbs_certificate().subject().to_string();
let signed_attrs = match si.signed_attrs.as_ref() {
Some(a) => a,
None => return fail(Some(subject_str), "no signed attributes present"),
};
if let Err(e) = check_message_digest(signed_attrs, &hash) {
return fail(Some(subject_str), e.to_string());
}
let tbs_bytes = match signed_attrs.to_der() {
Ok(b) => b,
Err(e) => return fail(Some(subject_str), format!("signed_attrs DER encode: {e}")),
};
let sig_bytes = si.signature.as_bytes();
if let Err(e) = verify_sig(
&signer_cert,
&si.signature_algorithm.oid,
&si.digest_alg.oid,
&tbs_bytes,
sig_bytes,
) {
return fail(Some(subject_str), e.to_string());
}
if let Err(e) = validate_chain(&signer_cert, bag_certs, trust_anchors, now, revocation) {
return fail(Some(subject_str), e.to_string());
}
SignerResult {
verified: true,
subject: Some(subject_str),
error: None,
}
}
fn compute_digest(data: &[u8], oid: &der::asn1::ObjectIdentifier) -> Result<Vec<u8>, SmimeError> {
match *oid {
x if x == ID_SHA_256 => Ok(Sha256::digest(data).to_vec()),
x if x == ID_SHA_384 => Ok(Sha384::digest(data).to_vec()),
x if x == ID_SHA_512 => Ok(Sha512::digest(data).to_vec()),
_ => Err(SmimeError::UnsupportedAlgorithm(format!(
"digest OID {oid}"
))),
}
}
fn find_cert(
bag: &[Certificate],
trust_anchors: &[Certificate],
sid: &SignerIdentifier,
) -> Result<Option<Certificate>, SmimeError> {
let mut all_certs = bag.iter().chain(trust_anchors.iter());
match sid {
SignerIdentifier::IssuerAndSerialNumber(ias) => {
let sid_issuer_der = ias.issuer.to_der()?;
Ok(all_certs
.find(|cert| {
let issuer_ok = cert
.tbs_certificate()
.issuer()
.to_der()
.map(|a| a == sid_issuer_der)
.unwrap_or(false);
let serial_ok = cert.tbs_certificate().serial_number() == &ias.serial_number;
issuer_ok && serial_ok
})
.cloned())
}
SignerIdentifier::SubjectKeyIdentifier(sid_ski) => {
let sid_bytes = sid_ski.0.as_bytes();
Ok(all_certs
.find(|cert| {
cert.tbs_certificate()
.get_extension::<x509_cert::ext::pkix::SubjectKeyIdentifier>()
.ok()
.flatten()
.map(|(_critical, ext_ski)| ext_ski.0.as_bytes() == sid_bytes)
.unwrap_or(false)
})
.cloned())
}
}
}
fn check_message_digest(
signed_attrs: &x509_cert::attr::Attributes,
content_hash: &[u8],
) -> Result<(), SmimeError> {
let md_attr = signed_attrs
.iter()
.find(|a| a.oid == ID_MESSAGE_DIGEST)
.ok_or_else(|| SmimeError::MalformedInput("messageDigest attribute not found".into()))?;
let attr_value =
md_attr.values.iter().next().ok_or_else(|| {
SmimeError::MalformedInput("messageDigest attribute has no value".into())
})?;
let attr_der = attr_value.to_der()?;
let expected_bytes = OctetString::from_der(&attr_der)
.map_err(|_| {
SmimeError::MalformedInput(
"cannot decode messageDigest attribute value as OctetString".into(),
)
})?
.as_bytes()
.to_vec();
if expected_bytes != content_hash {
return Err(SmimeError::SignatureVerification);
}
Ok(())
}
fn verify_sig(
cert: &Certificate,
sig_alg_oid: &der::asn1::ObjectIdentifier,
digest_alg_oid: &der::asn1::ObjectIdentifier,
tbs_bytes: &[u8],
sig_bytes: &[u8],
) -> Result<(), SmimeError> {
let e = |msg: String| SmimeError::Other(msg);
match *sig_alg_oid {
x if x == SHA_256_WITH_RSA_ENCRYPTION => {
sig_verify::verify_rsa_pkcs1::<Sha256, _>(cert, tbs_bytes, sig_bytes, e)
}
x if x == SHA_384_WITH_RSA_ENCRYPTION => {
sig_verify::verify_rsa_pkcs1::<Sha384, _>(cert, tbs_bytes, sig_bytes, e)
}
x if x == SHA_512_WITH_RSA_ENCRYPTION => {
sig_verify::verify_rsa_pkcs1::<Sha512, _>(cert, tbs_bytes, sig_bytes, e)
}
x if x == RSA_ENCRYPTION => {
match *digest_alg_oid {
d if d == ID_SHA_256 => {
sig_verify::verify_rsa_pkcs1::<Sha256, _>(cert, tbs_bytes, sig_bytes, e)
}
d if d == ID_SHA_384 => {
sig_verify::verify_rsa_pkcs1::<Sha384, _>(cert, tbs_bytes, sig_bytes, e)
}
d if d == ID_SHA_512 => {
sig_verify::verify_rsa_pkcs1::<Sha512, _>(cert, tbs_bytes, sig_bytes, e)
}
_ => Err(SmimeError::UnsupportedAlgorithm(format!(
"rsaEncryption with digest OID {digest_alg_oid}"
))),
}
}
x if x == ECDSA_WITH_SHA_256 => {
sig_verify::verify_ecdsa_p256(cert, tbs_bytes, sig_bytes, e)
}
x if x == ECDSA_WITH_SHA_384 => {
sig_verify::verify_ecdsa_p384(cert, tbs_bytes, sig_bytes, e)
}
_ => Err(SmimeError::UnsupportedAlgorithm(format!(
"signature algorithm OID {sig_alg_oid}"
))),
}
}