1use std::collections::HashSet;
42
43use sha1::Sha1;
44use sha2::{Digest, Sha256, Sha384, Sha512};
45use zpdf_core::{ObjectId, PdfDict, PdfObject};
46use zpdf_parser::PdfFile;
47
48use crate::forms::pdf_string_to_unicode;
49
50const MAX_FIELD_DEPTH: usize = 50;
52const MAX_SIG_FIELDS: usize = 4_096;
53const MAX_CMS_BYTES: usize = 4 * 1024 * 1024;
57
58#[derive(Debug, Clone)]
60pub struct Signature {
61 pub field_name: String,
63 pub filter: Option<String>,
66 pub sub_filter: Option<String>,
70 pub name: Option<String>,
73 pub signing_time: Option<String>,
75 pub location: Option<String>,
77 pub reason: Option<String>,
79 pub contact_info: Option<String>,
81 pub coverage: ByteRangeCoverage,
83 pub digest: DigestStatus,
86 pub crypto: CryptoStatus,
89 pub digest_algorithm: Option<String>,
92 pub signature_algorithm: Option<String>,
96 pub signer_common_name: Option<String>,
100 pub cms_blob: Option<Vec<u8>>,
104 pub has_timestamp: bool,
108 pub cert_count: usize,
111 pub has_dss: bool,
114 pub revocation: RevocationStatus,
117}
118
119#[derive(Debug, Clone, Copy, PartialEq, Eq)]
124pub enum RevocationStatus {
125 Unknown,
127 NotRevoked,
129 Revoked,
131}
132
133impl Signature {
134 pub fn is_cryptographically_valid(&self) -> bool {
145 self.digest == DigestStatus::Verified && self.crypto == CryptoStatus::Valid
146 }
147}
148
149#[derive(Debug, Clone)]
151pub struct ByteRangeCoverage {
152 pub ranges: Vec<(usize, usize)>,
154 pub covers_whole_document: bool,
158 pub bytes_after_signature: usize,
163}
164
165#[derive(Debug, Clone, Copy, PartialEq, Eq)]
167pub enum DigestStatus {
168 Verified,
171 Mismatch,
173 Unsupported,
177}
178
179impl DigestStatus {
180 pub fn as_str(self) -> &'static str {
181 match self {
182 DigestStatus::Verified => "verified",
183 DigestStatus::Mismatch => "mismatch",
184 DigestStatus::Unsupported => "unsupported",
185 }
186 }
187}
188
189#[derive(Debug, Clone, Copy, PartialEq, Eq)]
191pub enum CryptoStatus {
192 Valid,
195 Invalid,
198 Unsupported,
204}
205
206impl CryptoStatus {
207 pub fn as_str(self) -> &'static str {
208 match self {
209 CryptoStatus::Valid => "valid",
210 CryptoStatus::Invalid => "invalid",
211 CryptoStatus::Unsupported => "unsupported",
212 }
213 }
214}
215
216pub fn parse_signatures(file: &PdfFile) -> Vec<Signature> {
220 let mut out = Vec::new();
221 let Some(fields) = acroform_fields(file) else {
222 return out;
223 };
224
225 let mut visited = HashSet::new();
226 for obj in &fields {
227 if let PdfObject::Ref(r) = obj {
228 walk(file, *r, "", None, 0, &mut visited, &mut out);
229 }
230 }
231 out
232}
233
234fn acroform_fields(file: &PdfFile) -> Option<Vec<PdfObject>> {
236 let root_ref = file.trailer.get_ref("Root").ok()?;
237 let root = file.resolve(root_ref).ok()?;
238 let root = root.as_dict().ok()?;
239 let af = deref(file, root.get("AcroForm")?);
240 let af = af.as_dict().ok()?;
241 match deref(file, af.get("Fields")?) {
242 PdfObject::Array(a) => Some(a),
243 _ => None,
244 }
245}
246
247fn walk(
251 file: &PdfFile,
252 id: ObjectId,
253 parent_name: &str,
254 inherited_ft: Option<&str>,
255 depth: usize,
256 visited: &mut HashSet<ObjectId>,
257 out: &mut Vec<Signature>,
258) {
259 if depth > MAX_FIELD_DEPTH || out.len() >= MAX_SIG_FIELDS || !visited.insert(id) {
260 return;
261 }
262 let Ok(obj) = file.resolve(id) else { return };
263 let Ok(dict) = obj.as_dict() else { return };
264
265 let partial = dict
266 .get("T")
267 .and_then(|o| text_string(file, o))
268 .unwrap_or_default();
269 let name = if partial.is_empty() {
270 parent_name.to_string()
271 } else if parent_name.is_empty() {
272 partial
273 } else {
274 format!("{parent_name}.{partial}")
275 };
276
277 let ft = dict
278 .get_name("FT")
279 .ok()
280 .map(String::from)
281 .or_else(|| inherited_ft.map(String::from));
282
283 let kids = match deref(file, dict.get("Kids").unwrap_or(&PdfObject::Null)) {
285 PdfObject::Array(a) => a,
286 _ => Vec::new(),
287 };
288 let mut has_child_field = false;
289 for kid in &kids {
290 if let PdfObject::Ref(r) = kid {
291 let has_t = file
292 .resolve(*r)
293 .ok()
294 .and_then(|o| o.as_dict().ok().map(|d| d.get("T").is_some()))
295 .unwrap_or(false);
296 if has_t {
297 has_child_field = true;
298 walk(file, *r, &name, ft.as_deref(), depth + 1, visited, out);
299 }
300 }
301 }
302 if has_child_field {
303 return;
304 }
305
306 if ft.as_deref() != Some("Sig") {
308 return;
309 }
310 let Some(sig_dict) = deref(file, dict.get("V").unwrap_or(&PdfObject::Null))
311 .as_dict()
312 .ok()
313 .cloned()
314 else {
315 return;
316 };
317 out.push(build_signature(file, name, &sig_dict));
318}
319
320fn build_signature(file: &PdfFile, field_name: String, sig: &PdfDict) -> Signature {
321 let sub_filter = sig.get_name("SubFilter").ok().map(String::from);
322 let contents = match deref(file, sig.get("Contents").unwrap_or(&PdfObject::Null)) {
323 PdfObject::String(s) => Some(s.as_bytes().to_vec()),
324 _ => None,
325 };
326
327 let coverage = parse_byte_range(file, sig, file.data().len());
328 let outcome = verify(file, &coverage, contents.as_deref(), sub_filter.as_deref());
329
330 let (has_timestamp, cert_count, revocation) = match contents.as_deref() {
333 Some(cms) => (
334 revocation::has_timestamp(cms),
335 revocation::cert_count(cms),
336 revocation::revocation_status(cms),
337 ),
338 None => (false, 0, RevocationStatus::Unknown),
339 };
340 let has_dss = catalog_has_dss(file);
341
342 Signature {
343 field_name,
344 filter: sig.get_name("Filter").ok().map(String::from),
345 sub_filter,
346 name: sig.get("Name").and_then(|o| text_string(file, o)),
347 signing_time: sig.get("M").and_then(|o| text_string(file, o)),
348 location: sig.get("Location").and_then(|o| text_string(file, o)),
349 reason: sig.get("Reason").and_then(|o| text_string(file, o)),
350 contact_info: sig.get("ContactInfo").and_then(|o| text_string(file, o)),
351 coverage,
352 digest: outcome.digest,
353 crypto: outcome.crypto,
354 digest_algorithm: outcome.digest_algorithm,
355 signature_algorithm: outcome.signature_algorithm,
356 signer_common_name: outcome.signer_common_name,
357 cms_blob: contents,
358 has_timestamp,
359 cert_count,
360 has_dss,
361 revocation,
362 }
363}
364
365fn catalog_has_dss(file: &PdfFile) -> bool {
367 let Ok(root) = file.trailer.get_ref("Root") else {
368 return false;
369 };
370 let Ok(catalog) = file.resolve(root).and_then(|o| o.as_dict().cloned()) else {
371 return false;
372 };
373 matches!(
374 deref(file, catalog.get("DSS").unwrap_or(&PdfObject::Null)),
375 PdfObject::Dict(_) | PdfObject::Ref(_)
376 )
377}
378
379struct VerifyOutcome {
381 digest: DigestStatus,
382 crypto: CryptoStatus,
383 digest_algorithm: Option<String>,
384 signature_algorithm: Option<String>,
385 signer_common_name: Option<String>,
386}
387
388fn parse_byte_range(file: &PdfFile, sig: &PdfDict, file_len: usize) -> ByteRangeCoverage {
390 let mut ranges = Vec::new();
391 if let PdfObject::Array(arr) = deref(file, sig.get("ByteRange").unwrap_or(&PdfObject::Null)) {
392 let nums: Vec<i64> = arr
393 .iter()
394 .filter_map(|o| match deref(file, o) {
395 PdfObject::Integer(n) => Some(n),
396 PdfObject::Real(r) if r.is_finite() => Some(r as i64),
397 _ => None,
398 })
399 .collect();
400 for pair in nums.chunks_exact(2) {
401 if let (Ok(off), Ok(len)) = (usize::try_from(pair[0]), usize::try_from(pair[1])) {
402 ranges.push((off, len));
403 }
404 }
405 }
406
407 let covers_whole_document = ranges.first().zip(ranges.last()).is_some_and(
409 |(&(first_off, _), &(last_off, last_len))| {
410 first_off == 0 && last_off.saturating_add(last_len) == file_len
411 },
412 );
413 let end = ranges
414 .last()
415 .map(|&(off, len)| off.saturating_add(len))
416 .unwrap_or(0);
417 let bytes_after_signature = file_len.saturating_sub(end);
418
419 ByteRangeCoverage {
420 ranges,
421 covers_whole_document,
422 bytes_after_signature,
423 }
424}
425
426fn verify(
429 file: &PdfFile,
430 coverage: &ByteRangeCoverage,
431 contents: Option<&[u8]>,
432 sub_filter: Option<&str>,
433) -> VerifyOutcome {
434 let unsupported = VerifyOutcome {
435 digest: DigestStatus::Unsupported,
436 crypto: CryptoStatus::Unsupported,
437 digest_algorithm: None,
438 signature_algorithm: None,
439 signer_common_name: None,
440 };
441
442 let Some(cms) = contents.filter(|c| !c.is_empty() && c.len() <= MAX_CMS_BYTES) else {
443 return unsupported;
444 };
445
446 let Some(parsed) = cms::parse(cms) else {
447 return unsupported;
448 };
449 let digest_algorithm = parsed.digest_alg.map(|a| a.name().to_string());
450 let signature_algorithm = signature_alg_name(&parsed);
451 let signer_common_name = parsed.signer_cn.clone();
452
453 let is_detached = matches!(
458 sub_filter,
459 Some("adbe.pkcs7.detached") | Some("ETSI.CAdES.detached")
460 );
461 if !is_detached {
462 return VerifyOutcome {
463 digest: DigestStatus::Unsupported,
464 crypto: CryptoStatus::Unsupported,
465 digest_algorithm,
466 signature_algorithm,
467 signer_common_name,
468 };
469 }
470
471 let digest = match (parsed.digest_alg, parsed.message_digest.as_deref()) {
473 (Some(alg), Some(embedded)) => match gather_ranges(file.data(), &coverage.ranges) {
474 Some(spans) => {
475 if alg.hash(&spans) == embedded {
476 DigestStatus::Verified
477 } else {
478 DigestStatus::Mismatch
479 }
480 }
481 None => DigestStatus::Unsupported, },
483 _ => DigestStatus::Unsupported,
484 };
485
486 let crypto = verify_crypto(&parsed);
488
489 VerifyOutcome {
490 digest,
491 crypto,
492 digest_algorithm,
493 signature_algorithm,
494 signer_common_name,
495 }
496}
497
498fn verify_crypto(p: &cms::Cms) -> CryptoStatus {
502 let (Some(attrs), Some(sig), Some(key), Some(dalg), Some(salg)) = (
503 p.signed_attrs_der.as_deref(),
504 p.signature.as_deref(),
505 p.signer_key.as_ref(),
506 p.digest_alg,
507 p.sig_alg,
508 ) else {
509 return CryptoStatus::Unsupported;
510 };
511
512 let hashed = dalg.hash(attrs);
515
516 let verified = match (salg, key.alg) {
517 (cms::SigAlg::Rsa, cms::KeyAlg::Rsa) => pk::rsa_verify(dalg, &key.key, &hashed, sig),
518 (cms::SigAlg::Ecdsa, cms::KeyAlg::EcP256) => pk::ecdsa_p256_verify(&key.key, &hashed, sig),
519 (cms::SigAlg::Ecdsa, cms::KeyAlg::EcP384) => pk::ecdsa_p384_verify(&key.key, &hashed, sig),
520 (cms::SigAlg::RsaPss, cms::KeyAlg::Rsa) => {
521 let Some(pp) = p.pss_params else {
525 return CryptoStatus::Unsupported;
526 };
527 if pp.hash != dalg {
528 return CryptoStatus::Unsupported;
529 }
530 pk::rsa_pss_verify(pp, &key.key, &hashed, sig)
531 }
532 _ => return CryptoStatus::Unsupported,
534 };
535
536 match verified {
537 Some(true) => CryptoStatus::Valid,
538 Some(false) => CryptoStatus::Invalid,
539 None => CryptoStatus::Unsupported, }
541}
542
543fn signature_alg_name(p: &cms::Cms) -> Option<String> {
546 let salg = p.sig_alg?;
547 Some(match salg {
548 cms::SigAlg::Rsa => "RSA".to_string(),
549 cms::SigAlg::RsaPss => "RSA-PSS".to_string(),
550 cms::SigAlg::Ecdsa => match p.signer_key.as_ref().map(|k| k.alg) {
551 Some(cms::KeyAlg::EcP256) => "ECDSA (P-256)".to_string(),
552 Some(cms::KeyAlg::EcP384) => "ECDSA (P-384)".to_string(),
553 _ => "ECDSA".to_string(),
554 },
555 })
556}
557
558fn gather_ranges(data: &[u8], ranges: &[(usize, usize)]) -> Option<Vec<u8>> {
561 if ranges.is_empty() {
562 return None;
563 }
564 let mut buf = Vec::new();
565 for &(off, len) in ranges {
566 let end = off.checked_add(len)?;
567 let slice = data.get(off..end)?;
568 buf.extend_from_slice(slice);
569 }
570 Some(buf)
571}
572
573#[derive(Debug, Clone, Copy, PartialEq, Eq)]
578enum DigestAlg {
579 Sha1,
580 Sha256,
581 Sha384,
582 Sha512,
583}
584
585impl DigestAlg {
586 fn name(self) -> &'static str {
587 match self {
588 DigestAlg::Sha1 => "SHA-1",
589 DigestAlg::Sha256 => "SHA-256",
590 DigestAlg::Sha384 => "SHA-384",
591 DigestAlg::Sha512 => "SHA-512",
592 }
593 }
594
595 fn hash(self, data: &[u8]) -> Vec<u8> {
596 match self {
597 DigestAlg::Sha1 => Sha1::digest(data).to_vec(),
598 DigestAlg::Sha256 => Sha256::digest(data).to_vec(),
599 DigestAlg::Sha384 => Sha384::digest(data).to_vec(),
600 DigestAlg::Sha512 => Sha512::digest(data).to_vec(),
601 }
602 }
603
604 fn from_oid(oid: &[u8]) -> Option<DigestAlg> {
606 match oid {
607 [0x2b, 0x0e, 0x03, 0x02, 0x1a] => Some(DigestAlg::Sha1),
608 [0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01] => Some(DigestAlg::Sha256),
609 [0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x02] => Some(DigestAlg::Sha384),
610 [0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x03] => Some(DigestAlg::Sha512),
611 _ => None,
612 }
613 }
614}
615
616mod cms {
626 use super::DigestAlg;
627
628 pub(super) struct Cms {
630 pub(super) digest_alg: Option<DigestAlg>,
631 pub(super) message_digest: Option<Vec<u8>>,
632 pub(super) signer_cn: Option<String>,
633 pub(super) signed_attrs_der: Option<Vec<u8>>,
638 pub(super) signature: Option<Vec<u8>>,
640 pub(super) sig_alg: Option<SigAlg>,
642 pub(super) pss_params: Option<PssParams>,
647 pub(super) signer_key: Option<PublicKeyInfo>,
649 }
650
651 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
655 pub(super) struct PssParams {
656 pub(super) hash: DigestAlg,
657 pub(super) mgf_hash: DigestAlg,
658 pub(super) salt_len: usize,
659 }
660
661 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
663 pub(super) enum SigAlg {
664 Rsa,
666 RsaPss,
669 Ecdsa,
671 }
672
673 pub(super) struct PublicKeyInfo {
675 pub(super) alg: KeyAlg,
676 pub(super) key: Vec<u8>,
679 }
680
681 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
683 pub(super) enum KeyAlg {
684 Rsa,
685 EcP256,
686 EcP384,
687 }
688
689 const SEQUENCE: u8 = 0x30;
691 const SET: u8 = 0x31;
692 const OID: u8 = 0x06;
693 const OCTET_STRING: u8 = 0x04;
694 const BIT_STRING: u8 = 0x03;
695 const CONTEXT_0: u8 = 0xA0; const OID_SIGNED_DATA: &[u8] = &[0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x07, 0x02];
699 const OID_MESSAGE_DIGEST: &[u8] = &[0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x09, 0x04];
700 const OID_CN: &[u8] = &[0x55, 0x04, 0x03];
701
702 const OID_RSA_PREFIX: &[u8] = &[0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01];
705 const OID_RSA_PSS: &[u8] = &[0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0a];
706 const OID_MGF1: &[u8] = &[0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x08];
708 const OID_RSA_PUBLIC_KEY: &[u8] = &[0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01];
710 const OID_EC_PUBLIC_KEY: &[u8] = &[0x2a, 0x86, 0x48, 0xce, 0x3d, 0x02, 0x01];
712 const OID_ECDSA_PREFIX: &[u8] = &[0x2a, 0x86, 0x48, 0xce, 0x3d, 0x04];
713 const OID_CURVE_P256: &[u8] = &[0x2a, 0x86, 0x48, 0xce, 0x3d, 0x03, 0x01, 0x07];
715 const OID_CURVE_P384: &[u8] = &[0x2b, 0x81, 0x04, 0x00, 0x22];
716
717 fn tlv(buf: &[u8]) -> Option<(u8, &[u8], &[u8])> {
720 if buf.len() < 2 {
721 return None;
722 }
723 let tag = buf[0];
724 let first = buf[1];
725 let (len, header) = if first < 0x80 {
726 (first as usize, 2)
727 } else {
728 let n = (first & 0x7f) as usize;
729 if n == 0 || n > 4 || buf.len() < 2 + n {
730 return None; }
732 let mut len = 0usize;
733 for &b in &buf[2..2 + n] {
734 len = (len << 8) | b as usize;
735 }
736 (len, 2 + n)
737 };
738 let end = header.checked_add(len)?;
739 if end > buf.len() {
740 return None;
741 }
742 Some((tag, &buf[header..end], &buf[end..]))
743 }
744
745 fn children(content: &[u8], max: usize) -> Vec<(u8, &[u8])> {
747 let mut out = Vec::new();
748 let mut rest = content;
749 while !rest.is_empty() && out.len() < max {
750 let Some((tag, body, next)) = tlv(rest) else {
751 break;
752 };
753 out.push((tag, body));
754 rest = next;
755 }
756 out
757 }
758
759 #[allow(clippy::type_complexity)]
763 fn children_raw(content: &[u8], max: usize) -> Vec<(u8, &[u8], &[u8])> {
764 let mut out = Vec::new();
765 let mut rest = content;
766 while !rest.is_empty() && out.len() < max {
767 let before = rest;
768 let Some((tag, body, next)) = tlv(rest) else {
769 break;
770 };
771 let consumed = before.len() - next.len();
772 out.push((tag, body, &before[..consumed]));
773 rest = next;
774 }
775 out
776 }
777
778 pub(super) fn parse(blob: &[u8]) -> Option<Cms> {
779 let (tag, ci, _) = tlv(blob)?;
781 if tag != SEQUENCE {
782 return None;
783 }
784 let ci = children(ci, 4);
785 let ctype = ci.iter().find(|(t, _)| *t == OID)?;
786 if ctype.1 != OID_SIGNED_DATA {
787 return None;
788 }
789 let content = ci.iter().find(|(t, _)| *t == CONTEXT_0)?;
790 let (tag, signed_data, _) = tlv(content.1)?;
792 if tag != SEQUENCE {
793 return None;
794 }
795
796 let sd = children(signed_data, 16);
799 let signer_infos = sd.iter().rev().find(|(t, _)| *t == SET)?;
801 let certs = sd.iter().find(|(t, _)| *t == CONTEXT_0).map(|(_, c)| *c);
802
803 let (tag, signer_info, _) = tlv(signer_infos.1)?;
805 if tag != SEQUENCE {
806 return None;
807 }
808 let si = children_raw(signer_info, 16);
809
810 let seq_oid = |seq: &[u8]| -> Option<Vec<u8>> {
817 children(seq, 2)
818 .iter()
819 .find(|(t, _)| *t == OID)
820 .map(|(_, oid)| oid.to_vec())
821 };
822 let digest_alg = si
823 .iter()
824 .filter(|(t, _, _)| *t == SEQUENCE)
825 .find_map(|(_, seq, _)| seq_oid(seq).and_then(|oid| DigestAlg::from_oid(&oid)));
826 let sig_alg_seq = si.iter().find_map(|(_, seq, _)| {
831 seq_oid(seq).and_then(|oid| sig_alg_from_oid(&oid).map(|alg| (alg, seq)))
832 });
833 let sig_alg = sig_alg_seq.map(|(alg, _)| alg);
834 let pss_params = sig_alg_seq.and_then(|(alg, seq)| {
838 (alg == SigAlg::RsaPss)
839 .then_some(())
840 .and_then(|_| parse_pss_params(seq))
841 });
842
843 let signed_attrs = si.iter().find(|(t, _, _)| *t == CONTEXT_0);
846 let message_digest = signed_attrs.and_then(|(_, attrs, _)| find_message_digest(attrs));
847 let signed_attrs_der = signed_attrs.map(|(_, _, full)| {
849 let mut der = full.to_vec();
850 der[0] = SET;
851 der
852 });
853
854 let signature = si
856 .iter()
857 .find(|(t, _, _)| *t == OCTET_STRING)
858 .map(|(_, body, _)| body.to_vec());
859
860 let signer_cn = certs.and_then(first_cert_cn);
861 let signer_key = certs.and_then(first_cert_public_key);
862
863 Some(Cms {
864 digest_alg,
865 message_digest,
866 signer_cn,
867 signed_attrs_der,
868 signature,
869 sig_alg,
870 pss_params,
871 signer_key,
872 })
873 }
874
875 fn sig_alg_from_oid(oid: &[u8]) -> Option<SigAlg> {
877 if oid == OID_RSA_PSS {
878 Some(SigAlg::RsaPss)
879 } else if oid.starts_with(OID_RSA_PREFIX) {
880 Some(SigAlg::Rsa)
882 } else if oid.starts_with(OID_ECDSA_PREFIX) {
883 Some(SigAlg::Ecdsa)
884 } else {
885 None
886 }
887 }
888
889 fn parse_pss_params(sig_alg_seq: &[u8]) -> Option<PssParams> {
895 let parts = children(sig_alg_seq, 2);
897 let params = parts.iter().find(|(t, _)| *t == SEQUENCE)?.1;
898
899 const EXPLICIT_0: u8 = 0xA0;
911 const EXPLICIT_1: u8 = 0xA1;
912 const IMPLICIT_0: u8 = 0x80;
913 const IMPLICIT_1: u8 = 0x81;
914 const IMPLICIT_2: u8 = 0x82;
915 let fields = children(params, 8);
916
917 fn alg_id_body(tag: u8, body: &[u8]) -> Option<&[u8]> {
921 match tag {
922 EXPLICIT_0 | EXPLICIT_1 => {
923 let (t, inner, _) = tlv(body)?;
925 (t == SEQUENCE).then_some(inner)
926 }
927 IMPLICIT_0 | IMPLICIT_1 => Some(body),
928 _ => None,
929 }
930 }
931
932 let hash = fields
933 .iter()
934 .find(|(t, _)| *t == EXPLICIT_0 || *t == IMPLICIT_0)
935 .and_then(|(t, body)| alg_id_body(*t, body))
936 .and_then(algorithm_identifier_digest)
937 .unwrap_or(DigestAlg::Sha1);
938
939 let mgf_hash = fields
942 .iter()
943 .find(|(t, _)| *t == EXPLICIT_1 || *t == IMPLICIT_1)
944 .and_then(|(t, body)| alg_id_body(*t, body))
945 .and_then(|body| {
946 let mgf_parts = children(body, 2);
947 let mgf_oid = mgf_parts.iter().find(|(t, _)| *t == OID)?.1;
948 (mgf_oid == OID_MGF1).then_some(())?;
949 let hash_alg = mgf_parts.iter().find(|(t, _)| *t == SEQUENCE)?.1;
951 algorithm_identifier_digest(hash_alg)
952 })
953 .unwrap_or(DigestAlg::Sha1);
954
955 let salt_len = fields
956 .iter()
957 .find(|(t, _)| *t == IMPLICIT_2)
958 .and_then(|(_, body)| read_unsigned_integer(body))
959 .unwrap_or(20);
960
961 Some(PssParams {
962 hash,
963 mgf_hash,
964 salt_len,
965 })
966 }
967
968 fn algorithm_identifier_digest(alg_id: &[u8]) -> Option<DigestAlg> {
972 let oid = children(alg_id, 2)
973 .iter()
974 .find(|(t, _)| *t == OID)
975 .map(|(_, oid)| *oid)?;
976 DigestAlg::from_oid(oid)
977 }
978
979 fn read_unsigned_integer(content: &[u8]) -> Option<usize> {
983 if content.is_empty() || content.len() > 4 {
984 return None;
985 }
986 if content[0] & 0x80 != 0 {
988 return None;
989 }
990 let mut v = 0usize;
991 for &b in content {
992 v = (v << 8) | b as usize;
993 }
994 Some(v)
995 }
996
997 fn find_message_digest(attrs: &[u8]) -> Option<Vec<u8>> {
1000 for (tag, attr) in children(attrs, 64) {
1001 if tag != SEQUENCE {
1002 continue;
1003 }
1004 let parts = children(attr, 4);
1006 let is_md = parts
1007 .iter()
1008 .find(|(t, _)| *t == OID)
1009 .is_some_and(|(_, oid)| *oid == OID_MESSAGE_DIGEST);
1010 if !is_md {
1011 continue;
1012 }
1013 let values = parts.iter().find(|(t, _)| *t == SET)?;
1014 let (vtag, digest, _) = tlv(values.1)?;
1015 if vtag == OCTET_STRING {
1016 return Some(digest.to_vec());
1017 }
1018 }
1019 None
1020 }
1021
1022 fn first_cert_cn(certs: &[u8]) -> Option<String> {
1025 let (tag, cert, _) = tlv(certs)?;
1027 if tag != SEQUENCE {
1028 return None;
1029 }
1030 let (tag, tbs, _) = tlv(cert)?;
1031 if tag != SEQUENCE {
1032 return None;
1033 }
1034 let subject = children(tbs, 16)
1037 .into_iter()
1038 .filter(|(t, _)| *t == SEQUENCE)
1039 .nth(3)?;
1040 for (tag, rdn) in children(subject.1, 32) {
1042 if tag != SET {
1043 continue;
1044 }
1045 for (tag, atv) in children(rdn, 8) {
1046 if tag != SEQUENCE {
1047 continue;
1048 }
1049 let parts = children(atv, 2);
1050 let is_cn = parts
1051 .iter()
1052 .find(|(t, _)| *t == OID)
1053 .is_some_and(|(_, oid)| *oid == OID_CN);
1054 if is_cn {
1055 if let Some((vtag, value)) = parts.iter().rev().find(|(t, _)| *t != OID) {
1056 return Some(decode_directory_string(*vtag, value));
1057 }
1058 }
1059 }
1060 }
1061 None
1062 }
1063
1064 fn first_cert_public_key(certs: &[u8]) -> Option<PublicKeyInfo> {
1068 let (tag, cert, _) = tlv(certs)?;
1070 if tag != SEQUENCE {
1071 return None;
1072 }
1073 let (tag, tbs, _) = tlv(cert)?;
1074 if tag != SEQUENCE {
1075 return None;
1076 }
1077 let spki = children(tbs, 16)
1080 .into_iter()
1081 .filter(|(t, _)| *t == SEQUENCE)
1082 .nth(4)?;
1083
1084 let spki_parts = children(spki.1, 2);
1087 let alg_id = spki_parts.iter().find(|(t, _)| *t == SEQUENCE)?.1;
1088 let bit_string = spki_parts.iter().find(|(t, _)| *t == BIT_STRING)?.1;
1089 let key_bytes = bit_string
1092 .split_first()
1093 .and_then(|(unused, rest)| (*unused == 0).then(|| rest.to_vec()))?;
1094
1095 let alg_parts = children(alg_id, 2);
1097 let alg_oid = alg_parts.iter().find(|(t, _)| *t == OID)?.1;
1098
1099 if alg_oid == OID_RSA_PUBLIC_KEY {
1100 Some(PublicKeyInfo {
1101 alg: KeyAlg::Rsa,
1102 key: key_bytes,
1103 })
1104 } else if alg_oid == OID_EC_PUBLIC_KEY {
1105 let curve = alg_parts
1108 .iter()
1109 .filter(|(t, _)| *t == OID)
1110 .nth(1)
1111 .map(|(_, oid)| *oid)?;
1112 let alg = if curve == OID_CURVE_P256 {
1113 KeyAlg::EcP256
1114 } else if curve == OID_CURVE_P384 {
1115 KeyAlg::EcP384
1116 } else {
1117 return None;
1118 };
1119 Some(PublicKeyInfo {
1120 alg,
1121 key: key_bytes,
1122 })
1123 } else {
1124 None
1125 }
1126 }
1127
1128 fn decode_directory_string(tag: u8, value: &[u8]) -> String {
1131 const BMP_STRING: u8 = 0x1e;
1132 if tag == BMP_STRING {
1133 let units: Vec<u16> = value
1134 .chunks_exact(2)
1135 .map(|c| u16::from_be_bytes([c[0], c[1]]))
1136 .collect();
1137 String::from_utf16_lossy(&units)
1138 } else {
1139 String::from_utf8_lossy(value).into_owned()
1140 }
1141 }
1142}
1143
1144mod pk {
1154 use super::DigestAlg;
1155 use rsa::pkcs1::DecodeRsaPublicKey;
1156 use rsa::{Pkcs1v15Sign, RsaPublicKey};
1157 use sha1::Sha1;
1158 use sha2::{Sha256, Sha384, Sha512};
1159
1160 pub(super) fn rsa_verify(
1164 alg: DigestAlg,
1165 key_der: &[u8],
1166 hashed: &[u8],
1167 sig: &[u8],
1168 ) -> Option<bool> {
1169 let key = RsaPublicKey::from_pkcs1_der(key_der).ok()?;
1170 let scheme = match alg {
1171 DigestAlg::Sha1 => Pkcs1v15Sign::new::<Sha1>(),
1172 DigestAlg::Sha256 => Pkcs1v15Sign::new::<Sha256>(),
1173 DigestAlg::Sha384 => Pkcs1v15Sign::new::<Sha384>(),
1174 DigestAlg::Sha512 => Pkcs1v15Sign::new::<Sha512>(),
1175 };
1176 Some(key.verify(scheme, hashed, sig).is_ok())
1177 }
1178
1179 pub(super) fn ecdsa_p256_verify(point: &[u8], hashed: &[u8], sig: &[u8]) -> Option<bool> {
1182 use p256::ecdsa::signature::hazmat::PrehashVerifier;
1183 use p256::ecdsa::{Signature, VerifyingKey};
1184 let key = VerifyingKey::from_sec1_bytes(point).ok()?;
1185 let sig = Signature::from_der(sig).ok()?;
1186 Some(key.verify_prehash(hashed, &sig).is_ok())
1187 }
1188
1189 pub(super) fn ecdsa_p384_verify(point: &[u8], hashed: &[u8], sig: &[u8]) -> Option<bool> {
1191 use p384::ecdsa::signature::hazmat::PrehashVerifier;
1192 use p384::ecdsa::{Signature, VerifyingKey};
1193 let key = VerifyingKey::from_sec1_bytes(point).ok()?;
1194 let sig = Signature::from_der(sig).ok()?;
1195 Some(key.verify_prehash(hashed, &sig).is_ok())
1196 }
1197
1198 pub(super) fn rsa_pss_verify(
1204 params: super::cms::PssParams,
1205 key_der: &[u8],
1206 hashed: &[u8],
1207 sig: &[u8],
1208 ) -> Option<bool> {
1209 use rsa::pkcs1::DecodeRsaPublicKey;
1210 use rsa::pss::Pss;
1211 use rsa::RsaPublicKey;
1212 use sha1::Sha1;
1213 use sha2::{Sha256, Sha384, Sha512};
1214
1215 let key = RsaPublicKey::from_pkcs1_der(key_der).ok()?;
1216 let valid = match params.hash {
1220 DigestAlg::Sha1 => {
1221 let scheme = Pss::new_with_salt::<Sha1>(params.salt_len);
1222 key.verify(scheme, hashed, sig).is_ok()
1223 }
1224 DigestAlg::Sha256 => {
1225 let scheme = Pss::new_with_salt::<Sha256>(params.salt_len);
1226 key.verify(scheme, hashed, sig).is_ok()
1227 }
1228 DigestAlg::Sha384 => {
1229 let scheme = Pss::new_with_salt::<Sha384>(params.salt_len);
1230 key.verify(scheme, hashed, sig).is_ok()
1231 }
1232 DigestAlg::Sha512 => {
1233 let scheme = Pss::new_with_salt::<Sha512>(params.salt_len);
1234 key.verify(scheme, hashed, sig).is_ok()
1235 }
1236 };
1237 let _ = params.mgf_hash;
1243 Some(valid)
1244 }
1245}
1246
1247fn deref(file: &PdfFile, obj: &PdfObject) -> PdfObject {
1252 match obj {
1253 PdfObject::Ref(r) => file.resolve(*r).unwrap_or(PdfObject::Null),
1254 other => other.clone(),
1255 }
1256}
1257
1258fn text_string(file: &PdfFile, obj: &PdfObject) -> Option<String> {
1259 match deref(file, obj) {
1260 PdfObject::String(s) => Some(pdf_string_to_unicode(s.as_bytes())),
1261 _ => None,
1262 }
1263}
1264
1265mod revocation {
1270 use super::RevocationStatus;
1271
1272 const OID_TIMESTAMP: &[u8] = &[
1274 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x09, 0x10, 0x02, 0x0e,
1275 ];
1276
1277 pub fn has_timestamp(cms: &[u8]) -> bool {
1279 cms.windows(OID_TIMESTAMP.len()).any(|w| w == OID_TIMESTAMP)
1280 }
1281
1282 pub fn cert_count(cms: &[u8]) -> usize {
1284 match signed_data(cms) {
1285 Some(sd) => match field(sd, 0xA0) {
1286 Some(certs_blob) => children(certs_blob).len(),
1287 None => 0,
1288 },
1289 None => 0,
1290 }
1291 }
1292
1293 pub fn revocation_status(cms: &[u8]) -> RevocationStatus {
1298 let Some(sd) = signed_data(cms) else {
1299 return RevocationStatus::Unknown;
1300 };
1301 let crls_blob = match field(sd, 0xA1) {
1302 Some(b) => b,
1303 None => return RevocationStatus::Unknown, };
1305 let crls = children(crls_blob);
1306 if crls.is_empty() {
1307 return RevocationStatus::Unknown;
1308 }
1309 let signer_serial = field(sd, 0xA0)
1311 .and_then(|certs| children(certs).into_iter().next())
1312 .and_then(cert_serial);
1313 let Some(signer_serial) = signer_serial else {
1314 return RevocationStatus::Unknown;
1315 };
1316 for crl in crls {
1317 for revoked in crl_revoked_serials(crl) {
1318 if revoked == signer_serial {
1319 return RevocationStatus::Revoked;
1320 }
1321 }
1322 }
1323 RevocationStatus::NotRevoked
1324 }
1325
1326 fn tlv_split(bytes: &[u8]) -> Option<(u8, &[u8], &[u8])> {
1328 let &tag = bytes.first()?;
1329 let len_byte = *bytes.get(1)?;
1330 let (len, hdr) = if len_byte < 0x80 {
1331 (len_byte as usize, 2)
1332 } else {
1333 let n = (len_byte & 0x7f) as usize;
1334 if !(1..=4).contains(&n) || bytes.len() < 2 + n {
1335 return None;
1336 }
1337 let mut l = 0usize;
1338 for i in 0..n {
1339 l = (l << 8) | bytes[2 + i] as usize;
1340 }
1341 (l, 2 + n)
1342 };
1343 let content = bytes.get(hdr..hdr + len)?;
1344 Some((tag, content, &bytes[hdr + len..]))
1345 }
1346
1347 fn children(content: &[u8]) -> Vec<&[u8]> {
1349 let mut out = Vec::new();
1350 let mut rest = content;
1351 while let Some((_, _, next)) = tlv_split(rest) {
1352 let total = rest.len() - next.len();
1353 out.push(&rest[..total]);
1354 rest = next;
1355 }
1356 out
1357 }
1358
1359 fn signed_data(cms: &[u8]) -> Option<&[u8]> {
1361 let (_, outer, _) = tlv_split(cms)?;
1363 let kids = children(outer);
1364 kids.into_iter().find_map(|k| {
1365 let (tag, content, _) = tlv_split(k)?;
1366 (tag == 0xA0).then_some(content)
1367 })
1368 }
1369
1370 fn field(sd: &[u8], ctx_tag: u8) -> Option<&[u8]> {
1372 let (_, content, _) = tlv_split(sd)?;
1373 for k in children(content) {
1374 let (tag, c, _) = tlv_split(k)?;
1375 if tag == ctx_tag {
1376 return Some(c);
1377 }
1378 }
1379 None
1380 }
1381
1382 fn cert_serial(cert: &[u8]) -> Option<Vec<u8>> {
1384 let (_, tbs, _) = tlv_split(cert)?;
1386 let (_, tbs_content, _) = tlv_split(tbs)?;
1387 let mut kids = children(tbs_content).into_iter();
1388 let first = kids.next()?;
1390 let (tag, _content, _) = tlv_split(first)?;
1391 let serial_field = if tag == 0xA0 {
1392 kids.next()?
1394 } else {
1395 first
1396 };
1397 let (st, sc, _) = tlv_split(serial_field)?;
1398 (st == 0x02).then_some(sc.to_vec())
1399 }
1400
1401 fn crl_revoked_serials(crl: &[u8]) -> Vec<Vec<u8>> {
1403 let (_, tbs, _) = match tlv_split(crl) {
1405 Some(x) => x,
1406 None => return Vec::new(),
1407 };
1408 let (_, tbs_content, _) = match tlv_split(tbs) {
1409 Some(x) => x,
1410 None => return Vec::new(),
1411 };
1412 let mut out = Vec::new();
1415 let mut seen_time = false;
1416 for k in children(tbs_content) {
1417 let (tag, content, _) = match tlv_split(k) {
1418 Some(x) => x,
1419 None => continue,
1420 };
1421 if tag == 0x17 || tag == 0x18 {
1422 seen_time = true;
1424 continue;
1425 }
1426 if tag == 0x30 && seen_time {
1427 for entry in children(content) {
1429 if let Some((_, ec, _)) = tlv_split(entry) {
1430 if let Some(serial_tlv) = children(ec).into_iter().next() {
1431 if let Some((st, sc, _)) = tlv_split(serial_tlv) {
1432 if st == 0x02 {
1433 out.push(sc.to_vec());
1434 }
1435 }
1436 }
1437 }
1438 }
1439 break;
1440 }
1441 }
1442 out
1443 }
1444}
1445
1446#[cfg(test)]
1447mod tests {
1448 use super::*;
1449
1450 fn der(tag: u8, content: &[u8]) -> Vec<u8> {
1454 let mut out = vec![tag];
1455 let len = content.len();
1456 if len < 0x80 {
1457 out.push(len as u8);
1458 } else if len < 0x100 {
1459 out.push(0x81);
1460 out.push(len as u8);
1461 } else {
1462 out.push(0x82);
1463 out.push((len >> 8) as u8);
1464 out.push((len & 0xff) as u8);
1465 }
1466 out.extend_from_slice(content);
1467 out
1468 }
1469
1470 const SEQ: u8 = 0x30;
1471 const SET: u8 = 0x31;
1472 const OID: u8 = 0x06;
1473 const OCTET: u8 = 0x04;
1474 const INT: u8 = 0x02;
1475 const CTX0: u8 = 0xA0;
1476
1477 fn synth_cms(digest: &[u8]) -> Vec<u8> {
1480 let sha256_oid = [0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01];
1481 let md_oid = [0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x09, 0x04];
1482
1483 let digest_alg = der(SEQ, &der(OID, &sha256_oid));
1485
1486 let md_attr = der(
1488 SEQ,
1489 &[der(OID, &md_oid), der(SET, &der(OCTET, digest))].concat(),
1490 );
1491 let signed_attrs = der(CTX0, &md_attr);
1493
1494 let signer_info = der(
1497 SEQ,
1498 &[
1499 der(INT, &[1]),
1500 der(SEQ, &[]), digest_alg.clone(),
1502 signed_attrs,
1503 der(SEQ, &der(OID, &[0x2a])), der(OCTET, &[0xde, 0xad]), ]
1506 .concat(),
1507 );
1508 let signer_infos = der(SET, &signer_info);
1509
1510 let signed_data = der(
1512 SEQ,
1513 &[
1514 der(INT, &[1]),
1515 der(SET, &digest_alg),
1516 der(
1517 SEQ,
1518 &der(OID, &[0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x07, 0x01]),
1519 ),
1520 signer_infos,
1521 ]
1522 .concat(),
1523 );
1524
1525 let signed_data_oid = [0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x07, 0x02];
1527 der(
1528 SEQ,
1529 &[der(OID, &signed_data_oid), der(CTX0, &signed_data)].concat(),
1530 )
1531 }
1532
1533 #[test]
1534 fn cms_extracts_digest_and_algorithm() {
1535 let digest: Vec<u8> = (0u8..32).collect();
1536 let blob = synth_cms(&digest);
1537 let parsed = cms::parse(&blob).expect("cms");
1538 assert_eq!(parsed.digest_alg, Some(DigestAlg::Sha256));
1539 assert_eq!(parsed.message_digest.as_deref(), Some(digest.as_slice()));
1540 }
1541
1542 #[test]
1543 fn cms_rejects_truncated_blob() {
1544 let blob = synth_cms(&[0u8; 32]);
1545 for cut in 1..blob.len() {
1548 let _ = cms::parse(&blob[..cut]);
1549 }
1550 }
1551
1552 #[test]
1553 fn cms_rejects_indefinite_length() {
1554 assert!(cms::parse(&[0x30, 0x80, 0x00, 0x00]).is_none());
1556 }
1557
1558 #[test]
1559 fn digest_alg_oid_mapping() {
1560 assert_eq!(
1561 DigestAlg::from_oid(&[0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01]),
1562 Some(DigestAlg::Sha256)
1563 );
1564 assert_eq!(
1565 DigestAlg::from_oid(&[0x2b, 0x0e, 0x03, 0x02, 0x1a]),
1566 Some(DigestAlg::Sha1)
1567 );
1568 assert_eq!(DigestAlg::from_oid(&[0x00]), None);
1569 }
1570
1571 #[test]
1572 fn gather_ranges_bounds_checked() {
1573 let data = b"0123456789";
1574 assert_eq!(
1575 gather_ranges(data, &[(0, 3), (7, 3)]).as_deref(),
1576 Some(&b"012789"[..])
1577 );
1578 assert!(gather_ranges(data, &[(0, 3), (7, 99)]).is_none());
1580 assert!(gather_ranges(data, &[]).is_none());
1581 }
1582
1583 #[test]
1584 fn sha256_matches_reference() {
1585 let d = DigestAlg::Sha256.hash(b"abc");
1587 assert_eq!(
1588 d,
1589 hex(b"ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad")
1590 );
1591 }
1592
1593 fn hex(h: &[u8]) -> Vec<u8> {
1594 h.chunks_exact(2)
1595 .map(|c| {
1596 let s = std::str::from_utf8(c).unwrap();
1597 u8::from_str_radix(s, 16).unwrap()
1598 })
1599 .collect()
1600 }
1601
1602 use crate::test_util::build_pdf;
1605 use zpdf_parser::PdfFile;
1606
1607 #[test]
1610 fn out_of_range_byte_range_reports_unsupported() {
1611 let pdf = build_pdf(&[
1612 "<< /Type /Catalog /Pages 2 0 R /AcroForm 4 0 R >>",
1613 "<< /Type /Pages /Kids [3 0 R] /Count 1 >>",
1614 "<< /Type /Page /Parent 2 0 R /MediaBox [0 0 100 100] >>",
1615 "<< /Fields [5 0 R] >>",
1616 "<< /FT /Sig /T (S1) /V << /ByteRange [0 100 200 999999] /Contents <aabbcc> >> >>",
1617 ]);
1618 let file = PdfFile::parse(pdf.as_slice()).expect("parse");
1619 let sigs = parse_signatures(&file);
1620 assert_eq!(sigs.len(), 1);
1621 assert_eq!(sigs[0].digest, DigestStatus::Unsupported);
1623 }
1624
1625 #[test]
1628 fn malformed_cms_contents_do_not_hang() {
1629 let pdf = build_pdf(&[
1630 "<< /Type /Catalog /Pages 2 0 R /AcroForm 4 0 R >>",
1631 "<< /Type /Pages /Kids [3 0 R] /Count 1 >>",
1632 "<< /Type /Page /Parent 2 0 R /MediaBox [0 0 100 100] >>",
1633 "<< /Fields [5 0 R 6 0 R 7 0 R] >>",
1634 "<< /FT /Sig /T (Truncated) /V << /ByteRange [0 10 20 30] /Contents <30304142> >> >>",
1636 "<< /FT /Sig /T (Empty) /V << /ByteRange [0 10 20 30] /Contents <> >> >>",
1638 "<< /FT /Sig /T (Indefinite) /V << /ByteRange [0 10 20 30] /Contents <308000> >> >>",
1640 ]);
1641 let file = PdfFile::parse(pdf.as_slice()).expect("parse");
1642 let sigs = parse_signatures(&file);
1643 assert_eq!(sigs.len(), 3);
1645 for s in &sigs {
1646 assert_eq!(s.digest, DigestStatus::Unsupported);
1647 }
1648 }
1649
1650 #[test]
1652 fn deep_field_tree_terminates() {
1653 let mut objs = vec![
1657 "<< /Type /Catalog /Pages 2 0 R /AcroForm 4 0 R >>".to_string(),
1658 "<< /Type /Pages /Kids [3 0 R] /Count 1 >>".to_string(),
1659 "<< /Type /Page /Parent 2 0 R /MediaBox [0 0 100 100] >>".to_string(),
1660 "<< /Fields [5 0 R] >>".to_string(),
1661 ];
1662 for i in 0..100 {
1664 let next = if i < 99 {
1665 format!("{} 0 R", 5 + i + 1)
1666 } else {
1667 "null".to_string()
1668 };
1669 objs.push(format!("<< /T (Field{i}) /FT /Sig /Kids [{}] >>", next));
1670 }
1671 let pdf = build_pdf(&objs.iter().map(|s| s.as_str()).collect::<Vec<_>>());
1672 let file = PdfFile::parse(pdf.as_slice()).expect("parse");
1673 let sigs = parse_signatures(&file);
1674 assert!(sigs.len() < 100);
1676 }
1677}