use crate::config::TrustAnchors;
use crate::net::Transport;
use crate::pdf::{
verify_all_embedded_signatures, verify_detached_signature, verify_embedded_signature,
ChainValidator, VerificationResult,
};
use crate::pki::{validate_chain, validate_chain_for_profile, TrustStore, TrustStoreCache};
use crate::Result;
fn with_chain_validator<R>(
transport: &Transport,
cache: &TrustStoreCache,
trust: &TrustAnchors,
f: impl FnOnce(Option<&ChainValidator<'_>>) -> R,
) -> R {
match trust {
TrustAnchors::Tsl(url) => {
let validator =
move |cms: &[u8]| Some(validate_chain_for_profile(transport, cache, cms, url));
f(Some(&validator))
}
TrustAnchors::Pinned(certs) if !certs.is_empty() => {
let store = TrustStore::from_pinned_cas(certs);
let validator = move |cms: &[u8]| Some(validate_chain(cms, &store));
f(Some(&validator))
}
TrustAnchors::None | TrustAnchors::Pinned(_) => f(None),
}
}
pub fn verify_pdf(
transport: &Transport,
cache: &TrustStoreCache,
pdf: &[u8],
expected_hash: Option<&[u8]>,
trust: &TrustAnchors,
) -> VerificationResult {
with_chain_validator(transport, cache, trust, |validator| {
verify_embedded_signature(pdf, expected_hash, validator)
})
}
pub fn verify_pdf_all(
transport: &Transport,
cache: &TrustStoreCache,
pdf: &[u8],
trust: &TrustAnchors,
) -> Result<Vec<VerificationResult>> {
with_chain_validator(transport, cache, trust, |validator| {
verify_all_embedded_signatures(pdf, validator)
})
}
pub fn verify_detached(
transport: &Transport,
cache: &TrustStoreCache,
data: &[u8],
cms_der: &[u8],
trust: &TrustAnchors,
) -> VerificationResult {
with_chain_validator(transport, cache, trust, |validator| {
verify_detached_signature(data, cms_der, validator)
})
}
#[cfg(test)]
mod tests {
use super::*;
use crate::pdf::{
compute_byterange_hash, insert_cms, prepare_pdf_with_sig_field, PrepareOptions, PreparedPdf,
};
use crate::pki::TrustStatus;
const BLANK_LETTER: &[u8] = include_bytes!("../pdf/testdata/blank_letter.pdf");
const CMS_LEAF_DIRECT: &[u8] = include_bytes!("../pki/testdata/cms_leaf_direct.der");
const ROOT_DER: &[u8] = include_bytes!("../pki/testdata/root.der");
fn prepare_and_sign() -> (Vec<u8>, [u8; 20]) {
let opts = PrepareOptions {
name: Some("Verify Wiring"),
..Default::default()
};
let PreparedPdf {
bytes: prepared,
contents_hex_offset: hex_start,
contents_hex_len: hex_len,
} = prepare_pdf_with_sig_field(BLANK_LETTER, &opts).unwrap();
let hash = compute_byterange_hash(&prepared, hex_start, hex_len).unwrap();
let mut byterange = Vec::with_capacity(prepared.len().saturating_sub(hex_len + 1));
byterange.extend_from_slice(&prepared[..hex_start]);
byterange.extend_from_slice(&prepared[hex_start + hex_len + 1..]);
let cms = crate::testutil::sign_cms_detached(&byterange);
let signed = insert_cms(&prepared, hex_start, hex_len, &cms).unwrap();
(signed, hash)
}
#[test]
fn verify_pdf_without_tsl_is_offline_and_intact() {
let (signed, hash) = prepare_and_sign();
let transport = Transport::new();
let cache = TrustStoreCache::new();
let result = verify_pdf(
&transport,
&cache,
&signed,
Some(&hash),
&TrustAnchors::None,
);
assert!(result.integrity_ok(), "{:?}", result.details);
assert!(result.valid(), "{:?}", result.details);
assert_eq!(result.trust_status, Some(TrustStatus::Indeterminate));
}
#[test]
fn verify_pdf_all_without_tsl_returns_one_result() {
let (signed, _hash) = prepare_and_sign();
let transport = Transport::new();
let cache = TrustStoreCache::new();
let results = verify_pdf_all(&transport, &cache, &signed, &TrustAnchors::None).unwrap();
assert_eq!(results.len(), 1);
assert!(results[0].structure_ok);
}
#[test]
fn verify_detached_without_tsl_matches_declared_digest() {
let transport = Transport::new();
let cache = TrustStoreCache::new();
let result = verify_detached(
&transport,
&cache,
b"test data",
CMS_LEAF_DIRECT,
&TrustAnchors::None,
);
assert!(result.structure_ok, "{:?}", result.details);
assert!(result.hash_ok, "{:?}", result.details);
}
#[test]
fn pinned_anchor_yields_trusted() {
let transport = Transport::new();
let cache = TrustStoreCache::new();
let trust = TrustAnchors::Pinned(vec![ROOT_DER.to_vec()]);
let result = verify_detached(&transport, &cache, b"test data", CMS_LEAF_DIRECT, &trust);
assert!(result.valid(), "{:?}", result.details);
assert_eq!(result.trust_status, Some(TrustStatus::Trusted));
let wrong =
TrustAnchors::Pinned(vec![include_bytes!("../pki/testdata/root2.der").to_vec()]);
let untrusted = verify_detached(&transport, &cache, b"test data", CMS_LEAF_DIRECT, &wrong);
assert_eq!(untrusted.trust_status, Some(TrustStatus::Untrusted));
let empty = TrustAnchors::Pinned(Vec::new());
let indet = verify_detached(&transport, &cache, b"test data", CMS_LEAF_DIRECT, &empty);
assert_eq!(indet.trust_status, Some(TrustStatus::Indeterminate));
}
}