#![cfg(feature = "signatures")]
use super::crypto::{
hash_with_oid, is_rsa_pkcs1v15_sig_oid, OID_ECDSA_SHA256, OID_ECDSA_SHA384, OID_ECDSA_SHA512,
OID_EC_PUBLIC_KEY, OID_P256, OID_P384, OID_RSASSA_PSS, OID_RSA_ENCRYPTION,
};
use crate::crypto::{self, EcCurve, HashAlgorithm};
use crate::error::{Error, Result};
use cms::cert::x509::Certificate;
use cms::cert::CertificateChoices;
use cms::content_info::ContentInfo;
use cms::signed_data::{SignedData, SignerIdentifier, SignerInfo};
use der::asn1::OctetString;
use der::oid::db::rfc5912::{ID_SHA_1, ID_SHA_256, ID_SHA_384, ID_SHA_512};
use der::oid::ObjectIdentifier;
use der::{Decode, Encode};
use rsa::pkcs8::DecodePublicKey;
use rsa::traits::PublicKeyParts;
use rsa::RsaPublicKey;
fn digest_oid_to_hash(oid: ObjectIdentifier) -> Option<HashAlgorithm> {
if oid == ID_SHA_256 {
Some(HashAlgorithm::Sha256)
} else if oid == ID_SHA_384 {
Some(HashAlgorithm::Sha384)
} else if oid == ID_SHA_512 {
Some(HashAlgorithm::Sha512)
} else if oid == ID_SHA_1 {
Some(HashAlgorithm::Sha1)
} else {
None
}
}
fn project_rsa_public_key(key: &RsaPublicKey) -> (Vec<u8>, Vec<u8>) {
(key.n().to_bytes_be(), key.e().to_bytes_be())
}
#[must_use]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SignerVerify {
Valid,
Invalid,
Unknown,
}
const OID_MESSAGE_DIGEST: ObjectIdentifier = ObjectIdentifier::new_unwrap("1.2.840.113549.1.9.4");
fn find_signer_certificate<'a>(sd: &'a SignedData, signer: &SignerInfo) -> Option<&'a Certificate> {
let certs = sd.certificates.as_ref()?;
for choice in certs.0.iter() {
let CertificateChoices::Certificate(cert) = choice else {
continue;
};
match &signer.sid {
SignerIdentifier::IssuerAndSerialNumber(isn) => {
if cert.tbs_certificate.issuer == isn.issuer
&& cert.tbs_certificate.serial_number == isn.serial_number
{
return Some(cert);
}
},
SignerIdentifier::SubjectKeyIdentifier(_) => {
return Some(cert);
},
}
}
for choice in certs.0.iter() {
if let CertificateChoices::Certificate(cert) = choice {
return Some(cert);
}
}
None
}
fn parse_signed_data(contents: &[u8]) -> Result<SignedData> {
let ci = ContentInfo::from_der(contents).map_err(|e| {
Error::InvalidPdf(format!("signature /Contents is not valid CMS ContentInfo: {e}"))
})?;
let sd_bytes = ci
.content
.to_der()
.map_err(|e| Error::InvalidPdf(format!("failed to re-encode ContentInfo content: {e}")))?;
SignedData::from_der(&sd_bytes)
.map_err(|e| Error::InvalidPdf(format!("CMS content is not valid SignedData: {e}")))
}
fn verify_rsa_pss(
pub_key: RsaPublicKey,
digest_oid: ObjectIdentifier,
signed_attrs: &[u8],
sig_bytes: &[u8],
) -> SignerVerify {
let Some(hash) = digest_oid_to_hash(digest_oid) else {
return SignerVerify::Unknown;
};
let (n, e) = project_rsa_public_key(&pub_key);
let pubkey = crypto::RsaPublicKey {
modulus_be: &n,
exponent_be: &e,
};
match crypto::active()
.verifier()
.verify_rsa_pss(&pubkey, hash, signed_attrs, sig_bytes)
{
Ok(()) => SignerVerify::Valid,
Err(crypto::Error::Verification(_)) => SignerVerify::Invalid,
Err(_) => SignerVerify::Unknown,
}
}
fn verify_ecdsa_p256(pub_key_bits: &[u8], signed_attrs: &[u8], sig_bytes: &[u8]) -> SignerVerify {
match crypto::active().verifier().verify_ecdsa(
EcCurve::P256,
pub_key_bits,
signed_attrs,
sig_bytes,
) {
Ok(()) => SignerVerify::Valid,
Err(crypto::Error::Verification(_)) => SignerVerify::Invalid,
Err(_) => SignerVerify::Unknown,
}
}
fn verify_ecdsa_p384(pub_key_bits: &[u8], signed_attrs: &[u8], sig_bytes: &[u8]) -> SignerVerify {
match crypto::active().verifier().verify_ecdsa(
EcCurve::P384,
pub_key_bits,
signed_attrs,
sig_bytes,
) {
Ok(()) => SignerVerify::Valid,
Err(crypto::Error::Verification(_)) => SignerVerify::Invalid,
Err(_) => SignerVerify::Unknown,
}
}
fn run_signer_crypto(sd: &SignedData) -> Result<(SignerVerify, Option<ObjectIdentifier>)> {
let signer = sd
.signer_infos
.0
.iter()
.next()
.ok_or_else(|| Error::InvalidPdf("SignedData has no SignerInfo".into()))?;
let Some(signed_attrs) = signer.signed_attrs.as_ref() else {
return Ok((SignerVerify::Unknown, None));
};
let digest_oid = signer.digest_alg.oid;
let Some(hash_algo) = digest_oid_to_hash(digest_oid) else {
return Ok((SignerVerify::Unknown, Some(digest_oid)));
};
let signed_attrs_bytes = signed_attrs
.to_der()
.map_err(|e| Error::InvalidPdf(format!("failed to re-encode signed_attrs: {e}")))?;
let sig_alg_oid = signer.signature_algorithm.oid;
let sig_bytes = signer.signature.as_bytes();
if sig_alg_oid == OID_ECDSA_SHA256
|| sig_alg_oid == OID_ECDSA_SHA384
|| sig_alg_oid == OID_ECDSA_SHA512
{
let Some(cert) = find_signer_certificate(sd, signer) else {
return Ok((SignerVerify::Unknown, Some(digest_oid)));
};
let spki = &cert.tbs_certificate.subject_public_key_info;
if spki.algorithm.oid != OID_EC_PUBLIC_KEY {
return Ok((SignerVerify::Unknown, Some(digest_oid)));
}
let curve_oid: ObjectIdentifier = match spki
.algorithm
.parameters
.as_ref()
.and_then(|p| p.to_der().ok())
.and_then(|b| ObjectIdentifier::from_der(&b).ok())
{
Some(oid) => oid,
None => return Ok((SignerVerify::Unknown, Some(digest_oid))),
};
let pub_key_bits = spki.subject_public_key.raw_bytes();
let outcome = if curve_oid == OID_P256 && sig_alg_oid == OID_ECDSA_SHA256 {
verify_ecdsa_p256(pub_key_bits, &signed_attrs_bytes, sig_bytes)
} else if curve_oid == OID_P384 && sig_alg_oid == OID_ECDSA_SHA384 {
verify_ecdsa_p384(pub_key_bits, &signed_attrs_bytes, sig_bytes)
} else {
SignerVerify::Unknown
};
return Ok((outcome, Some(digest_oid)));
}
if sig_alg_oid == OID_RSASSA_PSS {
let Some(cert) = find_signer_certificate(sd, signer) else {
return Ok((SignerVerify::Unknown, Some(digest_oid)));
};
let key_alg_oid = cert.tbs_certificate.subject_public_key_info.algorithm.oid;
if key_alg_oid != OID_RSA_ENCRYPTION && key_alg_oid != OID_RSASSA_PSS {
return Ok((SignerVerify::Unknown, Some(digest_oid)));
}
let spki_der = cert
.tbs_certificate
.subject_public_key_info
.to_der()
.map_err(|e| Error::InvalidPdf(format!("failed to re-encode signer SPKI: {e}")))?;
let pub_key = match RsaPublicKey::from_public_key_der(&spki_der) {
Ok(k) => k,
Err(_) => return Ok((SignerVerify::Unknown, Some(digest_oid))),
};
let outcome = verify_rsa_pss(pub_key, digest_oid, &signed_attrs_bytes, sig_bytes);
return Ok((outcome, Some(digest_oid)));
}
if !is_rsa_pkcs1v15_sig_oid(sig_alg_oid) {
return Ok((SignerVerify::Unknown, Some(digest_oid)));
}
let Some(cert) = find_signer_certificate(sd, signer) else {
return Ok((SignerVerify::Unknown, Some(digest_oid)));
};
if cert.tbs_certificate.subject_public_key_info.algorithm.oid != OID_RSA_ENCRYPTION {
return Ok((SignerVerify::Unknown, Some(digest_oid)));
}
let spki_der = cert
.tbs_certificate
.subject_public_key_info
.to_der()
.map_err(|e| Error::InvalidPdf(format!("failed to re-encode signer SPKI: {e}")))?;
let pub_key = match RsaPublicKey::from_public_key_der(&spki_der) {
Ok(k) => k,
Err(_) => return Ok((SignerVerify::Unknown, Some(digest_oid))),
};
let (n, e) = project_rsa_public_key(&pub_key);
let pubkey = crypto::RsaPublicKey {
modulus_be: &n,
exponent_be: &e,
};
let outcome = match crypto::active().verifier().verify_rsa_pkcs1v15(
&pubkey,
hash_algo,
&signed_attrs_bytes,
sig_bytes,
) {
Ok(()) => SignerVerify::Valid,
Err(crypto::Error::Verification(_)) => SignerVerify::Invalid,
Err(_) => SignerVerify::Unknown,
};
Ok((outcome, Some(digest_oid)))
}
fn extract_message_digest(sd: &SignedData) -> Option<Vec<u8>> {
let signer = sd.signer_infos.0.iter().next()?;
let signed_attrs = signer.signed_attrs.as_ref()?;
for attr in signed_attrs.iter() {
if attr.oid != OID_MESSAGE_DIGEST {
continue;
}
let value = attr.values.iter().next()?;
let value_der = value.to_der().ok()?;
let octet = OctetString::from_der(&value_der).ok()?;
return Some(octet.as_bytes().to_vec());
}
None
}
pub fn verify_signer(contents: &[u8]) -> Result<SignerVerify> {
let sd = parse_signed_data(contents)?;
Ok(run_signer_crypto(&sd)?.0)
}
pub fn verify_signer_detached(contents: &[u8], content: &[u8]) -> Result<SignerVerify> {
let sd = parse_signed_data(contents)?;
let (crypto_outcome, digest_oid) = run_signer_crypto(&sd)?;
match crypto_outcome {
SignerVerify::Valid => {},
other => return Ok(other),
}
let digest_oid = digest_oid.expect("Valid outcome implies known digest OID");
let Some(expected) = extract_message_digest(&sd) else {
return Ok(SignerVerify::Unknown);
};
let Some(actual) = hash_with_oid(digest_oid, content) else {
return Ok(SignerVerify::Unknown);
};
if actual.len() == expected.len()
&& actual
.iter()
.zip(expected.iter())
.fold(0u8, |acc, (a, b)| acc | (a ^ b))
== 0
{
Ok(SignerVerify::Valid)
} else {
Ok(SignerVerify::Invalid)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn rejects_non_cms_bytes() {
let err = verify_signer(b"not a CMS blob").unwrap_err();
assert!(matches!(err, Error::InvalidPdf(_)));
}
#[test]
fn rejects_empty_bytes() {
let err = verify_signer(&[]).unwrap_err();
assert!(matches!(err, Error::InvalidPdf(_)));
}
#[test]
fn detached_rejects_non_cms_bytes() {
let err = verify_signer_detached(b"not a CMS blob", b"content").unwrap_err();
assert!(matches!(err, Error::InvalidPdf(_)));
}
#[test]
fn ecdsa_p256_invalid_key_returns_unknown() {
assert_eq!(
verify_ecdsa_p256(b"not-a-sec1-point", b"hello", b"not-a-sig"),
SignerVerify::Unknown,
);
}
#[test]
fn ecdsa_p256_round_trip() {
use p256::ecdsa::{signature::Signer, Signature, SigningKey};
let sk = SigningKey::from_slice(&[1u8; 32]).expect("valid test key");
let vk = *sk.verifying_key();
let pub_key_bytes = vk.to_encoded_point(false).as_bytes().to_vec();
let msg = b"round-trip test";
let sig: Signature = sk.sign(msg);
let sig_der = sig.to_der();
assert_eq!(verify_ecdsa_p256(&pub_key_bytes, msg, sig_der.as_bytes()), SignerVerify::Valid,);
assert_eq!(
verify_ecdsa_p256(&pub_key_bytes, b"wrong message", sig_der.as_bytes()),
SignerVerify::Invalid,
);
}
#[test]
fn ecdsa_p384_invalid_key_returns_unknown() {
assert_eq!(
verify_ecdsa_p384(b"not-a-sec1-point", b"hello", b"not-a-sig"),
SignerVerify::Unknown,
);
}
#[test]
fn ecdsa_p384_round_trip() {
use p384::ecdsa::{signature::Signer, Signature, SigningKey};
let sk = SigningKey::from_slice(&[2u8; 48]).expect("valid test key");
let vk = *sk.verifying_key();
let pub_key_bytes = vk.to_encoded_point(false).as_bytes().to_vec();
let msg = b"round-trip test";
let sig: Signature = sk.sign(msg);
let sig_der = sig.to_der();
assert_eq!(verify_ecdsa_p384(&pub_key_bytes, msg, sig_der.as_bytes()), SignerVerify::Valid,);
assert_eq!(
verify_ecdsa_p384(&pub_key_bytes, b"wrong message", sig_der.as_bytes()),
SignerVerify::Invalid,
);
}
}