Skip to main content

oxidize_pdf/signatures/
cms.rs

1//! PKCS#7/CMS signature parsing
2//!
3//! This module provides functionality to parse CMS (Cryptographic Message Syntax)
4//! structures used in PDF digital signatures.
5//!
6//! # Features
7//!
8//! Requires the `signatures` feature to be enabled.
9//!
10//! # BER Support
11//!
12//! PDF signatures may use BER (indefinite-length) encoding. This module
13//! automatically converts BER to DER before parsing.
14
15#[cfg(feature = "signatures")]
16use cms::content_info::ContentInfo;
17#[cfg(feature = "signatures")]
18use cms::signed_data::SignedData;
19#[cfg(feature = "signatures")]
20use der::{Decode, Encode};
21#[cfg(feature = "signatures")]
22use x509_cert::Certificate;
23
24use super::error::{SignatureError, SignatureResult};
25
26// =============================================================================
27// BER Security Limits (DoS Protection)
28// =============================================================================
29
30/// Maximum nesting depth for BER structures.
31/// Prevents stack overflow from deeply nested malicious input.
32#[cfg(feature = "signatures")]
33const MAX_BER_DEPTH: usize = 100;
34
35/// Maximum output size for BER-to-DER conversion (10 MB).
36/// Prevents memory exhaustion from malicious BER expansion.
37#[cfg(feature = "signatures")]
38const MAX_BER_OUTPUT_SIZE: usize = 10 * 1024 * 1024;
39
40/// Convert BER-encoded data to DER by resolving indefinite-length encodings
41///
42/// BER allows indefinite-length encoding where a SEQUENCE or other constructed
43/// type has length byte 0x80 and is terminated by 0x00 0x00. DER requires
44/// definite-length encoding. This function recursively converts all indefinite
45/// lengths to definite lengths.
46///
47/// # Security
48///
49/// This function includes DoS protection:
50/// - Maximum nesting depth of 100 levels
51/// - Maximum output size of 10 MB
52#[cfg(feature = "signatures")]
53fn ber_to_der(input: &[u8]) -> SignatureResult<Vec<u8>> {
54    if input.is_empty() {
55        return Ok(Vec::new());
56    }
57
58    // Check if this is already DER (no indefinite length markers)
59    if !contains_indefinite_length(input) {
60        return Ok(input.to_vec());
61    }
62
63    // Calculate size budget: min of 100x expansion or absolute max
64    let size_budget = std::cmp::min(input.len().saturating_mul(100), MAX_BER_OUTPUT_SIZE);
65
66    // Parse and convert recursively with depth=0 and size budget
67    convert_element(input, 0, size_budget).map(|(output, _)| output)
68}
69
70/// Check if the data starts with indefinite-length BER encoding
71///
72/// Indefinite length (0x80) is only valid for constructed types (bit 6 set).
73/// This function verifies both conditions to avoid false positives when
74/// 0x80 appears as a regular length value in primitive types.
75#[cfg(feature = "signatures")]
76fn contains_indefinite_length(data: &[u8]) -> bool {
77    if data.len() < 2 {
78        return false;
79    }
80
81    let tag = data[0];
82    let length_byte = data[1];
83
84    // Indefinite length (0x80) is only valid for constructed types
85    // Bit 6 (0x20) indicates constructed encoding
86    let is_constructed = (tag & 0x20) != 0;
87
88    is_constructed && length_byte == 0x80
89}
90
91/// Parse and convert a single BER element to DER
92///
93/// # Arguments
94///
95/// * `input` - The BER-encoded bytes to convert
96/// * `depth` - Current nesting depth (for DoS protection)
97/// * `size_budget` - Remaining bytes allowed in output (for DoS protection)
98///
99/// # Returns
100///
101/// (converted_bytes, bytes_consumed) or error if limits exceeded
102#[cfg(feature = "signatures")]
103fn convert_element(
104    input: &[u8],
105    depth: usize,
106    size_budget: usize,
107) -> SignatureResult<(Vec<u8>, usize)> {
108    // Fix #1: Check recursion depth limit
109    if depth > MAX_BER_DEPTH {
110        return Err(SignatureError::CmsParsingFailed {
111            details: format!(
112                "BER nesting too deep: {} levels (max {})",
113                depth, MAX_BER_DEPTH
114            ),
115        });
116    }
117
118    if input.is_empty() {
119        return Err(SignatureError::CmsParsingFailed {
120            details: "Empty input in BER conversion".to_string(),
121        });
122    }
123
124    let tag = input[0];
125    if input.len() < 2 {
126        return Err(SignatureError::CmsParsingFailed {
127            details: "BER element too short".to_string(),
128        });
129    }
130
131    let length_byte = input[1];
132
133    // Check for indefinite length
134    if length_byte == 0x80 {
135        // Indefinite length - must find end-of-contents (0x00 0x00)
136        let is_constructed = (tag & 0x20) != 0;
137        if !is_constructed {
138            return Err(SignatureError::CmsParsingFailed {
139                details: "Indefinite length on primitive type".to_string(),
140            });
141        }
142
143        // Find the matching end-of-contents by parsing nested elements
144        let content_start = 2;
145        let (content, content_len) =
146            parse_indefinite_content(&input[content_start..], depth + 1, size_budget)?;
147
148        // Fix #2: Check output size limit
149        let output_size = 1 + 5 + content.len(); // tag + max_length_bytes + content
150        if output_size > size_budget {
151            return Err(SignatureError::CmsParsingFailed {
152                details: format!(
153                    "BER output exceeds size limit: {} bytes (max {} MB)",
154                    output_size,
155                    MAX_BER_OUTPUT_SIZE / 1024 / 1024
156                ),
157            });
158        }
159
160        // Build DER output with definite length
161        let mut output = vec![tag];
162        encode_der_length(&mut output, content.len());
163        output.extend(content);
164
165        // Total consumed: tag + 0x80 + content + 0x00 0x00
166        let consumed = 2 + content_len + 2;
167        Ok((output, consumed))
168    } else if length_byte < 0x80 {
169        // Short form length
170        let length = length_byte as usize;
171        let total = 2 + length;
172        if input.len() < total {
173            return Err(SignatureError::CmsParsingFailed {
174                details: format!(
175                    "BER element truncated (short form): need {} bytes, got {}",
176                    total,
177                    input.len()
178                ),
179            });
180        }
181
182        // Check if this is a constructed type that needs recursive conversion
183        let is_constructed = (tag & 0x20) != 0;
184        if is_constructed && length > 0 {
185            let content = &input[2..2 + length];
186            let converted_content = convert_constructed_content(content, depth + 1, size_budget)?;
187
188            // Fix #2: Check output size limit
189            let output_size = 1 + 5 + converted_content.len();
190            if output_size > size_budget {
191                return Err(SignatureError::CmsParsingFailed {
192                    details: format!(
193                        "BER output exceeds size limit: {} bytes (max {} MB)",
194                        output_size,
195                        MAX_BER_OUTPUT_SIZE / 1024 / 1024
196                    ),
197                });
198            }
199
200            let mut output = vec![tag];
201            encode_der_length(&mut output, converted_content.len());
202            output.extend(converted_content);
203            Ok((output, total))
204        } else {
205            // Primitive or empty - copy as-is
206            Ok((input[..total].to_vec(), total))
207        }
208    } else {
209        // Long form length
210        let num_octets = (length_byte & 0x7F) as usize;
211
212        // Fix #3: Validate length bytes BEFORE reading them
213        if num_octets == 0 {
214            return Err(SignatureError::CmsParsingFailed {
215                details: "Invalid BER length: zero octets in long form".to_string(),
216            });
217        }
218        if num_octets > 4 {
219            return Err(SignatureError::CmsParsingFailed {
220                details: format!(
221                    "BER length too large: {} octets (max 4, would exceed 4GB)",
222                    num_octets
223                ),
224            });
225        }
226        if input.len() < 2 + num_octets {
227            return Err(SignatureError::CmsParsingFailed {
228                details: format!(
229                    "BER truncated reading length: need {} bytes, got {}",
230                    2 + num_octets,
231                    input.len()
232                ),
233            });
234        }
235
236        // Now safe to read length bytes
237        let mut length: usize = 0;
238        for i in 0..num_octets {
239            length = (length << 8) | (input[2 + i] as usize);
240        }
241
242        let content_start = 2 + num_octets;
243        let total = content_start + length;
244        if input.len() < total {
245            return Err(SignatureError::CmsParsingFailed {
246                details: format!(
247                    "BER element truncated: declared {} bytes content, got {}",
248                    length,
249                    input.len().saturating_sub(content_start)
250                ),
251            });
252        }
253
254        // Check if this is a constructed type that needs recursive conversion
255        let is_constructed = (tag & 0x20) != 0;
256        if is_constructed && length > 0 {
257            let content = &input[content_start..total];
258            let converted_content = convert_constructed_content(content, depth + 1, size_budget)?;
259
260            // Fix #2: Check output size limit
261            let output_size = 1 + 5 + converted_content.len();
262            if output_size > size_budget {
263                return Err(SignatureError::CmsParsingFailed {
264                    details: format!(
265                        "BER output exceeds size limit: {} bytes (max {} MB)",
266                        output_size,
267                        MAX_BER_OUTPUT_SIZE / 1024 / 1024
268                    ),
269                });
270            }
271
272            let mut output = vec![tag];
273            encode_der_length(&mut output, converted_content.len());
274            output.extend(converted_content);
275            Ok((output, total))
276        } else {
277            // Primitive - copy as-is
278            Ok((input[..total].to_vec(), total))
279        }
280    }
281}
282
283/// Parse content with indefinite length until we find end-of-contents
284///
285/// # Arguments
286///
287/// * `input` - The content bytes (after the 0x80 length marker)
288/// * `depth` - Current nesting depth
289/// * `size_budget` - Remaining bytes allowed in output
290///
291/// # Returns
292///
293/// (converted_content, bytes_consumed_excluding_eoc)
294#[cfg(feature = "signatures")]
295fn parse_indefinite_content(
296    input: &[u8],
297    depth: usize,
298    size_budget: usize,
299) -> SignatureResult<(Vec<u8>, usize)> {
300    let mut output = Vec::new();
301    let mut pos = 0;
302
303    while pos < input.len() {
304        // Check for end-of-contents (0x00 0x00)
305        if input.len() >= pos + 2 && input[pos] == 0x00 && input[pos + 1] == 0x00 {
306            return Ok((output, pos));
307        }
308
309        // Check remaining budget before parsing next element
310        let remaining_budget = size_budget.saturating_sub(output.len());
311        if remaining_budget == 0 {
312            return Err(SignatureError::CmsParsingFailed {
313                details: format!(
314                    "BER output exceeds size limit during indefinite content parsing (max {} MB)",
315                    MAX_BER_OUTPUT_SIZE / 1024 / 1024
316                ),
317            });
318        }
319
320        // Parse next element with depth and budget propagation
321        let (element, consumed) = convert_element(&input[pos..], depth, remaining_budget)?;
322        output.extend(element);
323        pos += consumed;
324    }
325
326    Err(SignatureError::CmsParsingFailed {
327        details: "End-of-contents not found in indefinite-length encoding".to_string(),
328    })
329}
330
331/// Convert all elements in a constructed type's content
332///
333/// # Arguments
334///
335/// * `input` - The content bytes of a constructed type
336/// * `depth` - Current nesting depth
337/// * `size_budget` - Remaining bytes allowed in output
338#[cfg(feature = "signatures")]
339fn convert_constructed_content(
340    input: &[u8],
341    depth: usize,
342    size_budget: usize,
343) -> SignatureResult<Vec<u8>> {
344    let mut output = Vec::new();
345    let mut pos = 0;
346
347    while pos < input.len() {
348        // Check remaining budget before parsing next element
349        let remaining_budget = size_budget.saturating_sub(output.len());
350        if remaining_budget == 0 {
351            return Err(SignatureError::CmsParsingFailed {
352                details: format!(
353                    "BER output exceeds size limit during constructed content parsing (max {} MB)",
354                    MAX_BER_OUTPUT_SIZE / 1024 / 1024
355                ),
356            });
357        }
358
359        let (element, consumed) = convert_element(&input[pos..], depth, remaining_budget)?;
360        output.extend(element);
361        pos += consumed;
362    }
363
364    Ok(output)
365}
366
367/// Encode a length in DER format
368#[cfg(feature = "signatures")]
369fn encode_der_length(output: &mut Vec<u8>, length: usize) {
370    if length < 128 {
371        output.push(length as u8);
372    } else if length < 256 {
373        output.push(0x81);
374        output.push(length as u8);
375    } else if length < 65536 {
376        output.push(0x82);
377        output.push((length >> 8) as u8);
378        output.push(length as u8);
379    } else if length < 16777216 {
380        output.push(0x83);
381        output.push((length >> 16) as u8);
382        output.push((length >> 8) as u8);
383        output.push(length as u8);
384    } else {
385        output.push(0x84);
386        output.push((length >> 24) as u8);
387        output.push((length >> 16) as u8);
388        output.push((length >> 8) as u8);
389        output.push(length as u8);
390    }
391}
392
393/// Digest algorithm used for signature hash computation
394#[derive(Debug, Clone, Copy, PartialEq, Eq)]
395pub enum DigestAlgorithm {
396    /// SHA-256 (recommended)
397    Sha256,
398    /// SHA-384
399    Sha384,
400    /// SHA-512
401    Sha512,
402}
403
404impl DigestAlgorithm {
405    /// Returns the OID string for this algorithm
406    pub fn oid(&self) -> &'static str {
407        match self {
408            DigestAlgorithm::Sha256 => "2.16.840.1.101.3.4.2.1",
409            DigestAlgorithm::Sha384 => "2.16.840.1.101.3.4.2.2",
410            DigestAlgorithm::Sha512 => "2.16.840.1.101.3.4.2.3",
411        }
412    }
413
414    /// Returns the algorithm name
415    pub fn name(&self) -> &'static str {
416        match self {
417            DigestAlgorithm::Sha256 => "SHA-256",
418            DigestAlgorithm::Sha384 => "SHA-384",
419            DigestAlgorithm::Sha512 => "SHA-512",
420        }
421    }
422}
423
424/// Signature algorithm used for signing
425#[derive(Debug, Clone, Copy, PartialEq, Eq)]
426pub enum SignatureAlgorithm {
427    /// RSA with SHA-256
428    RsaSha256,
429    /// RSA with SHA-384
430    RsaSha384,
431    /// RSA with SHA-512
432    RsaSha512,
433    /// ECDSA with SHA-256 (P-256 curve)
434    EcdsaSha256,
435    /// ECDSA with SHA-384 (P-384 curve)
436    EcdsaSha384,
437}
438
439impl SignatureAlgorithm {
440    /// Returns the algorithm name
441    pub fn name(&self) -> &'static str {
442        match self {
443            SignatureAlgorithm::RsaSha256 => "RSA-SHA256",
444            SignatureAlgorithm::RsaSha384 => "RSA-SHA384",
445            SignatureAlgorithm::RsaSha512 => "RSA-SHA512",
446            SignatureAlgorithm::EcdsaSha256 => "ECDSA-SHA256",
447            SignatureAlgorithm::EcdsaSha384 => "ECDSA-SHA384",
448        }
449    }
450
451    /// Returns the digest algorithm used by this signature algorithm
452    pub fn digest_algorithm(&self) -> DigestAlgorithm {
453        match self {
454            SignatureAlgorithm::RsaSha256 | SignatureAlgorithm::EcdsaSha256 => {
455                DigestAlgorithm::Sha256
456            }
457            SignatureAlgorithm::RsaSha384 | SignatureAlgorithm::EcdsaSha384 => {
458                DigestAlgorithm::Sha384
459            }
460            SignatureAlgorithm::RsaSha512 => DigestAlgorithm::Sha512,
461        }
462    }
463}
464
465/// Parsed PKCS#7/CMS signature structure
466#[derive(Debug, Clone)]
467pub struct ParsedSignature {
468    /// The digest algorithm used
469    pub digest_algorithm: DigestAlgorithm,
470    /// The signature algorithm used
471    pub signature_algorithm: SignatureAlgorithm,
472    /// The raw signature value bytes
473    pub signature_value: Vec<u8>,
474    /// The signer's certificate in DER format
475    pub signer_certificate_der: Vec<u8>,
476    /// Optional signing time from signed attributes
477    pub signing_time: Option<String>,
478}
479
480impl ParsedSignature {
481    /// Returns the signer's common name from the certificate
482    #[cfg(feature = "signatures")]
483    pub fn signer_common_name(&self) -> SignatureResult<String> {
484        use der::asn1::{PrintableStringRef, Utf8StringRef};
485
486        let cert = Certificate::from_der(&self.signer_certificate_der).map_err(|e| {
487            SignatureError::CmsParsingFailed {
488                details: format!("Failed to parse certificate: {}", e),
489            }
490        })?;
491
492        // Extract CN from subject
493        for rdn in cert.tbs_certificate.subject.0.iter() {
494            for atv in rdn.0.iter() {
495                // OID for commonName: 2.5.4.3
496                if atv.oid.to_string() == "2.5.4.3" {
497                    // Try to decode as UTF8String first, then PrintableString
498                    if let Ok(utf8) = Utf8StringRef::try_from(&atv.value) {
499                        return Ok(utf8.as_str().to_string());
500                    }
501                    if let Ok(printable) = PrintableStringRef::try_from(&atv.value) {
502                        return Ok(printable.as_str().to_string());
503                    }
504                    // Fallback: return raw bytes as hex
505                    return Ok(format!("<binary CN: {} bytes>", atv.value.value().len()));
506                }
507            }
508        }
509
510        Err(SignatureError::CmsParsingFailed {
511            details: "Certificate has no common name".to_string(),
512        })
513    }
514
515    #[cfg(not(feature = "signatures"))]
516    pub fn signer_common_name(&self) -> SignatureResult<String> {
517        Err(SignatureError::CmsParsingFailed {
518            details: "signatures feature not enabled".to_string(),
519        })
520    }
521}
522
523/// Parses a PKCS#7/CMS signature from raw bytes (DER encoded)
524///
525/// # Arguments
526///
527/// * `contents` - The raw signature bytes from the PDF /Contents field
528///
529/// # Returns
530///
531/// A `ParsedSignature` containing the extracted signature information.
532///
533/// # Errors
534///
535/// Returns an error if the DER structure is invalid or unsupported.
536#[cfg(feature = "signatures")]
537pub fn parse_pkcs7_signature(contents: &[u8]) -> SignatureResult<ParsedSignature> {
538    use const_oid::ObjectIdentifier;
539
540    // Convert BER to DER if necessary (PDF signatures may use BER encoding)
541    let der_contents = ber_to_der(contents)?;
542
543    // Parse ContentInfo (top-level CMS structure)
544    let content_info =
545        ContentInfo::from_der(&der_contents).map_err(|e| SignatureError::CmsParsingFailed {
546            details: format!("Failed to parse ContentInfo: {}", e),
547        })?;
548
549    // Verify it's SignedData (OID 1.2.840.113549.1.7.2)
550    const SIGNED_DATA_OID: ObjectIdentifier = ObjectIdentifier::new_unwrap("1.2.840.113549.1.7.2");
551    if content_info.content_type != SIGNED_DATA_OID {
552        return Err(SignatureError::CmsParsingFailed {
553            details: format!(
554                "Expected SignedData, got OID: {}",
555                content_info.content_type
556            ),
557        });
558    }
559
560    // Extract SignedData
561    let signed_data_bytes =
562        content_info
563            .content
564            .to_der()
565            .map_err(|e| SignatureError::CmsParsingFailed {
566                details: format!("Failed to encode content: {}", e),
567            })?;
568
569    let signed_data =
570        SignedData::from_der(&signed_data_bytes).map_err(|e| SignatureError::CmsParsingFailed {
571            details: format!("Failed to parse SignedData: {}", e),
572        })?;
573
574    // Extract signer info (we expect exactly one)
575    let signer_infos: Vec<_> = signed_data.signer_infos.0.iter().collect();
576    if signer_infos.is_empty() {
577        return Err(SignatureError::CmsParsingFailed {
578            details: "No signer info found in SignedData".to_string(),
579        });
580    }
581    let signer_info = &signer_infos[0];
582
583    // Extract digest algorithm from signer info
584    let digest_algorithm = parse_digest_algorithm(&signer_info.digest_alg.oid.to_string())?;
585
586    // Extract signature algorithm
587    let signature_algorithm = parse_signature_algorithm(
588        &signer_info.signature_algorithm.oid.to_string(),
589        digest_algorithm,
590    )?;
591
592    // Extract signature value
593    let signature_value = signer_info.signature.as_bytes().to_vec();
594
595    // Extract certificate
596    let certificates =
597        signed_data
598            .certificates
599            .as_ref()
600            .ok_or_else(|| SignatureError::CmsParsingFailed {
601                details: "No certificates in SignedData".to_string(),
602            })?;
603
604    // Find the signer's certificate (first one for simplicity)
605    let cert_choices: Vec<_> = certificates.0.iter().collect();
606    if cert_choices.is_empty() {
607        return Err(SignatureError::CmsParsingFailed {
608            details: "No certificates found".to_string(),
609        });
610    }
611
612    // Get certificate DER
613    let signer_certificate_der = match &cert_choices[0] {
614        cms::cert::CertificateChoices::Certificate(cert) => {
615            cert.to_der()
616                .map_err(|e| SignatureError::CmsParsingFailed {
617                    details: format!("Failed to encode certificate: {}", e),
618                })?
619        }
620        _ => {
621            return Err(SignatureError::CmsParsingFailed {
622                details: "Unsupported certificate type".to_string(),
623            })
624        }
625    };
626
627    // Extract signing time from signed attributes if present
628    let signing_time = extract_signing_time(signer_info);
629
630    Ok(ParsedSignature {
631        digest_algorithm,
632        signature_algorithm,
633        signature_value,
634        signer_certificate_der,
635        signing_time,
636    })
637}
638
639#[cfg(not(feature = "signatures"))]
640pub fn parse_pkcs7_signature(_contents: &[u8]) -> SignatureResult<ParsedSignature> {
641    Err(SignatureError::CmsParsingFailed {
642        details: "signatures feature not enabled".to_string(),
643    })
644}
645
646/// Parses a digest algorithm OID string
647#[cfg(feature = "signatures")]
648fn parse_digest_algorithm(oid: &str) -> SignatureResult<DigestAlgorithm> {
649    match oid {
650        "2.16.840.1.101.3.4.2.1" => Ok(DigestAlgorithm::Sha256),
651        "2.16.840.1.101.3.4.2.2" => Ok(DigestAlgorithm::Sha384),
652        "2.16.840.1.101.3.4.2.3" => Ok(DigestAlgorithm::Sha512),
653        _ => Err(SignatureError::UnsupportedAlgorithm {
654            algorithm: format!("digest OID: {}", oid),
655        }),
656    }
657}
658
659/// Parses a signature algorithm OID string
660#[cfg(feature = "signatures")]
661fn parse_signature_algorithm(
662    oid: &str,
663    digest: DigestAlgorithm,
664) -> SignatureResult<SignatureAlgorithm> {
665    match oid {
666        // RSA PKCS#1 v1.5
667        "1.2.840.113549.1.1.1" => match digest {
668            DigestAlgorithm::Sha256 => Ok(SignatureAlgorithm::RsaSha256),
669            DigestAlgorithm::Sha384 => Ok(SignatureAlgorithm::RsaSha384),
670            DigestAlgorithm::Sha512 => Ok(SignatureAlgorithm::RsaSha512),
671        },
672        // RSA with SHA-256
673        "1.2.840.113549.1.1.11" => Ok(SignatureAlgorithm::RsaSha256),
674        // RSA with SHA-384
675        "1.2.840.113549.1.1.12" => Ok(SignatureAlgorithm::RsaSha384),
676        // RSA with SHA-512
677        "1.2.840.113549.1.1.13" => Ok(SignatureAlgorithm::RsaSha512),
678        // ECDSA with SHA-256
679        "1.2.840.10045.4.3.2" => Ok(SignatureAlgorithm::EcdsaSha256),
680        // ECDSA with SHA-384
681        "1.2.840.10045.4.3.3" => Ok(SignatureAlgorithm::EcdsaSha384),
682        _ => Err(SignatureError::UnsupportedAlgorithm {
683            algorithm: format!("signature OID: {}", oid),
684        }),
685    }
686}
687
688/// Extracts signing time from signer info signed attributes
689#[cfg(feature = "signatures")]
690fn extract_signing_time(signer_info: &cms::signed_data::SignerInfo) -> Option<String> {
691    // OID for signingTime: 1.2.840.113549.1.9.5
692    const SIGNING_TIME_OID: &str = "1.2.840.113549.1.9.5";
693
694    signer_info.signed_attrs.as_ref().and_then(|attrs| {
695        for attr in attrs.iter() {
696            if attr.oid.to_string() == SIGNING_TIME_OID {
697                // The attribute value contains the time
698                // For now, return a placeholder - full parsing would decode ASN.1 time
699                return Some("(signing time present)".to_string());
700            }
701        }
702        None
703    })
704}
705
706#[cfg(test)]
707mod tests {
708    use super::*;
709
710    // Algorithm enum tests
711
712    #[test]
713    fn test_digest_algorithm_oid() {
714        assert_eq!(DigestAlgorithm::Sha256.oid(), "2.16.840.1.101.3.4.2.1");
715        assert_eq!(DigestAlgorithm::Sha384.oid(), "2.16.840.1.101.3.4.2.2");
716        assert_eq!(DigestAlgorithm::Sha512.oid(), "2.16.840.1.101.3.4.2.3");
717    }
718
719    #[test]
720    fn test_digest_algorithm_name() {
721        assert_eq!(DigestAlgorithm::Sha256.name(), "SHA-256");
722        assert_eq!(DigestAlgorithm::Sha384.name(), "SHA-384");
723        assert_eq!(DigestAlgorithm::Sha512.name(), "SHA-512");
724    }
725
726    #[test]
727    fn test_signature_algorithm_name() {
728        assert_eq!(SignatureAlgorithm::RsaSha256.name(), "RSA-SHA256");
729        assert_eq!(SignatureAlgorithm::EcdsaSha256.name(), "ECDSA-SHA256");
730    }
731
732    #[test]
733    fn test_signature_algorithm_digest() {
734        assert_eq!(
735            SignatureAlgorithm::RsaSha256.digest_algorithm(),
736            DigestAlgorithm::Sha256
737        );
738        assert_eq!(
739            SignatureAlgorithm::RsaSha384.digest_algorithm(),
740            DigestAlgorithm::Sha384
741        );
742        assert_eq!(
743            SignatureAlgorithm::EcdsaSha384.digest_algorithm(),
744            DigestAlgorithm::Sha384
745        );
746    }
747
748    #[test]
749    fn test_digest_algorithm_clone_copy() {
750        let alg = DigestAlgorithm::Sha256;
751        let cloned = alg.clone();
752        let copied = alg;
753        assert_eq!(alg, cloned);
754        assert_eq!(alg, copied);
755    }
756
757    #[test]
758    fn test_signature_algorithm_clone_copy() {
759        let alg = SignatureAlgorithm::RsaSha256;
760        let cloned = alg.clone();
761        let copied = alg;
762        assert_eq!(alg, cloned);
763        assert_eq!(alg, copied);
764    }
765
766    #[test]
767    fn test_digest_algorithm_debug() {
768        let debug = format!("{:?}", DigestAlgorithm::Sha256);
769        assert!(debug.contains("Sha256"));
770    }
771
772    #[test]
773    fn test_signature_algorithm_debug() {
774        let debug = format!("{:?}", SignatureAlgorithm::EcdsaSha256);
775        assert!(debug.contains("EcdsaSha256"));
776    }
777
778    // Parsing tests (require signatures feature)
779
780    #[cfg(feature = "signatures")]
781    #[test]
782    fn test_parse_digest_algorithm_sha256() {
783        let result = parse_digest_algorithm("2.16.840.1.101.3.4.2.1");
784        assert!(result.is_ok());
785        assert_eq!(result.unwrap(), DigestAlgorithm::Sha256);
786    }
787
788    #[cfg(feature = "signatures")]
789    #[test]
790    fn test_parse_digest_algorithm_sha384() {
791        let result = parse_digest_algorithm("2.16.840.1.101.3.4.2.2");
792        assert!(result.is_ok());
793        assert_eq!(result.unwrap(), DigestAlgorithm::Sha384);
794    }
795
796    #[cfg(feature = "signatures")]
797    #[test]
798    fn test_parse_digest_algorithm_sha512() {
799        let result = parse_digest_algorithm("2.16.840.1.101.3.4.2.3");
800        assert!(result.is_ok());
801        assert_eq!(result.unwrap(), DigestAlgorithm::Sha512);
802    }
803
804    #[cfg(feature = "signatures")]
805    #[test]
806    fn test_parse_digest_algorithm_unsupported() {
807        let result = parse_digest_algorithm("1.2.3.4.5");
808        assert!(result.is_err());
809        let err = result.unwrap_err();
810        assert!(matches!(err, SignatureError::UnsupportedAlgorithm { .. }));
811    }
812
813    #[cfg(feature = "signatures")]
814    #[test]
815    fn test_parse_signature_algorithm_rsa_sha256() {
816        let result = parse_signature_algorithm("1.2.840.113549.1.1.11", DigestAlgorithm::Sha256);
817        assert!(result.is_ok());
818        assert_eq!(result.unwrap(), SignatureAlgorithm::RsaSha256);
819    }
820
821    #[cfg(feature = "signatures")]
822    #[test]
823    fn test_parse_signature_algorithm_ecdsa_sha256() {
824        let result = parse_signature_algorithm("1.2.840.10045.4.3.2", DigestAlgorithm::Sha256);
825        assert!(result.is_ok());
826        assert_eq!(result.unwrap(), SignatureAlgorithm::EcdsaSha256);
827    }
828
829    #[cfg(feature = "signatures")]
830    #[test]
831    fn test_parse_signature_algorithm_unsupported() {
832        let result = parse_signature_algorithm("1.2.3.4.5", DigestAlgorithm::Sha256);
833        assert!(result.is_err());
834        let err = result.unwrap_err();
835        assert!(matches!(err, SignatureError::UnsupportedAlgorithm { .. }));
836    }
837
838    #[cfg(feature = "signatures")]
839    #[test]
840    fn test_parse_pkcs7_invalid_der() {
841        let invalid = vec![0x00, 0x01, 0x02, 0x03];
842        let result = parse_pkcs7_signature(&invalid);
843        assert!(result.is_err());
844        let err = result.unwrap_err();
845        assert!(matches!(err, SignatureError::CmsParsingFailed { .. }));
846    }
847
848    #[cfg(feature = "signatures")]
849    #[test]
850    fn test_parse_pkcs7_empty_input() {
851        let result = parse_pkcs7_signature(&[]);
852        assert!(result.is_err());
853    }
854
855    #[test]
856    fn test_parsed_signature_debug() {
857        let sig = ParsedSignature {
858            digest_algorithm: DigestAlgorithm::Sha256,
859            signature_algorithm: SignatureAlgorithm::RsaSha256,
860            signature_value: vec![1, 2, 3],
861            signer_certificate_der: vec![4, 5, 6],
862            signing_time: Some("2024-01-01".to_string()),
863        };
864        let debug = format!("{:?}", sig);
865        assert!(debug.contains("Sha256"));
866        assert!(debug.contains("RsaSha256"));
867    }
868
869    #[test]
870    fn test_parsed_signature_clone() {
871        let sig = ParsedSignature {
872            digest_algorithm: DigestAlgorithm::Sha256,
873            signature_algorithm: SignatureAlgorithm::RsaSha256,
874            signature_value: vec![1, 2, 3],
875            signer_certificate_der: vec![4, 5, 6],
876            signing_time: None,
877        };
878        let cloned = sig.clone();
879        assert_eq!(sig.digest_algorithm, cloned.digest_algorithm);
880        assert_eq!(sig.signature_value, cloned.signature_value);
881    }
882
883    // ==========================================================================
884    // Security tests for BER-to-DER conversion (DoS protection)
885    // ==========================================================================
886
887    #[cfg(feature = "signatures")]
888    #[test]
889    fn test_ber_to_der_depth_limit_protection() {
890        // Fix #1: Create deeply nested BER structure (>MAX_BER_DEPTH levels)
891        // Each level: 0x30 0x80 (SEQUENCE indefinite) ... 0x00 0x00 (EOC)
892        let depth = MAX_BER_DEPTH + 50; // 150 levels, exceeds limit of 100
893
894        let mut ber = Vec::new();
895        // Open 150 nested SEQUENCEs with indefinite length
896        for _ in 0..depth {
897            ber.push(0x30); // SEQUENCE tag (constructed)
898            ber.push(0x80); // Indefinite length
899        }
900        // Add a primitive element at the deepest level
901        ber.extend_from_slice(&[0x02, 0x01, 0x00]); // INTEGER 0
902                                                    // Close all levels with end-of-contents
903        for _ in 0..depth {
904            ber.push(0x00);
905            ber.push(0x00);
906        }
907
908        let result = ber_to_der(&ber);
909        assert!(result.is_err(), "Should reject deeply nested BER");
910
911        let err_msg = result.unwrap_err().to_string();
912        assert!(
913            err_msg.contains("nesting too deep") || err_msg.contains("depth"),
914            "Error should mention depth limit, got: {}",
915            err_msg
916        );
917    }
918
919    #[cfg(feature = "signatures")]
920    #[test]
921    fn test_ber_to_der_size_limit_protection() {
922        // Fix #2: Create BER with indefinite length containing huge nested content
923        // The size limit is checked during BER-to-DER conversion when indefinite length is used
924        //
925        // Note: ber_to_der only processes data with indefinite length markers.
926        // For regular DER with definite lengths, it passes through unchanged.
927        // The size check happens during recursive conversion.
928
929        // Create a valid BER with indefinite length, but simulate the budget check
930        // by directly testing convert_element with a tiny budget
931        let ber = vec![
932            0x30, 0x80, // SEQUENCE indefinite length
933            0x04, 0x82, 0x00, 0x10, // OCTET STRING with 16 bytes
934        ];
935        // Add 16 bytes of content
936        let mut full_ber = ber;
937        full_ber.extend(vec![0x41; 16]);
938        full_ber.extend_from_slice(&[0x00, 0x00]); // End of contents
939
940        // With a tiny size budget of 5, this should fail
941        let result = convert_element(&full_ber, 0, 5);
942        assert!(
943            result.is_err(),
944            "Should reject BER when output exceeds size budget"
945        );
946
947        let err_msg = result.unwrap_err().to_string();
948        assert!(
949            err_msg.contains("exceeds") || err_msg.contains("limit"),
950            "Error should mention size limit, got: {}",
951            err_msg
952        );
953    }
954
955    #[cfg(feature = "signatures")]
956    #[test]
957    fn test_ber_to_der_buffer_overread_protection() {
958        // Fix #3: Create truncated BER inside indefinite content
959        // This tests that convert_element properly validates lengths before reading
960
961        // Outer SEQUENCE with indefinite length, containing truncated inner element
962        let truncated_ber = vec![
963            0x30, 0x80, // SEQUENCE indefinite
964            0x30, 0x85, // Inner SEQUENCE with long form (5 bytes for length - invalid)
965            0x01,
966            0x02, // Only 2 length bytes provided
967                  // Missing: 3 more length bytes, then content, then EOC
968        ];
969
970        let result = ber_to_der(&truncated_ber);
971        assert!(
972            result.is_err(),
973            "Should reject BER with invalid/truncated length"
974        );
975
976        let err_msg = result.unwrap_err().to_string();
977        // Should fail with "too large" (5 octets > 4 max) or truncation error
978        assert!(
979            err_msg.contains("too large")
980                || err_msg.contains("truncated")
981                || err_msg.contains("octets"),
982            "Error should mention length issue, got: {}",
983            err_msg
984        );
985    }
986
987    #[cfg(feature = "signatures")]
988    #[test]
989    fn test_ber_to_der_length_zero_octets() {
990        // Fix #3: Long form with 0 octets (0x80 without being indefinite)
991        // This is actually indefinite length for constructed types, but
992        // for primitive types it's invalid
993        let invalid = vec![0x02, 0x80]; // INTEGER with indefinite length (invalid)
994
995        let result = ber_to_der(&invalid);
996        // Either passes as DER (since primitive 0x80 = 128 byte length)
997        // or fails as truncated - both are acceptable
998        // The key is it shouldn't panic or buffer overread
999        let _ = result; // Just ensure no panic
1000    }
1001
1002    #[cfg(feature = "signatures")]
1003    #[test]
1004    fn test_contains_indefinite_length_primitive_false_positive() {
1005        // Fix #4: Primitive type with 0x80 as length (128 bytes) - NOT indefinite
1006        // OCTET STRING with 128 bytes of content
1007        let primitive = vec![0x04, 0x80]; // OCTET STRING, length 128 (short form max)
1008        assert!(
1009            !contains_indefinite_length(&primitive),
1010            "Should not detect indefinite length on primitive type"
1011        );
1012
1013        // INTEGER with 128-byte length
1014        let integer = vec![0x02, 0x80];
1015        assert!(
1016            !contains_indefinite_length(&integer),
1017            "Should not detect indefinite length on INTEGER"
1018        );
1019    }
1020
1021    #[cfg(feature = "signatures")]
1022    #[test]
1023    fn test_contains_indefinite_length_constructed_true() {
1024        // Fix #4: Constructed type with indefinite length - SHOULD detect
1025        let sequence = vec![0x30, 0x80]; // SEQUENCE indefinite
1026        assert!(
1027            contains_indefinite_length(&sequence),
1028            "Should detect indefinite length on SEQUENCE"
1029        );
1030
1031        let set = vec![0x31, 0x80]; // SET indefinite
1032        assert!(
1033            contains_indefinite_length(&set),
1034            "Should detect indefinite length on SET"
1035        );
1036
1037        // Context-specific constructed [0] with indefinite length
1038        let context = vec![0xA0, 0x80]; // [0] EXPLICIT/constructed
1039        assert!(
1040            contains_indefinite_length(&context),
1041            "Should detect indefinite length on context-specific constructed"
1042        );
1043    }
1044
1045    #[cfg(feature = "signatures")]
1046    #[test]
1047    fn test_ber_to_der_valid_conversion() {
1048        // Ensure valid BER still converts correctly
1049        // Simple SEQUENCE with INTEGER inside, using indefinite length
1050        let ber = vec![
1051            0x30, 0x80, // SEQUENCE indefinite length
1052            0x02, 0x01, 0x42, // INTEGER = 66
1053            0x00, 0x00, // End of contents
1054        ];
1055
1056        let result = ber_to_der(&ber);
1057        assert!(result.is_ok(), "Valid BER should convert successfully");
1058
1059        let der = result.unwrap();
1060        // DER should have definite length
1061        assert_eq!(der[0], 0x30, "Should be SEQUENCE");
1062        assert_eq!(der[1], 0x03, "Length should be 3 (definite)");
1063        assert_eq!(&der[2..5], &[0x02, 0x01, 0x42], "Content preserved");
1064    }
1065
1066    #[cfg(feature = "signatures")]
1067    #[test]
1068    fn test_ber_to_der_already_der() {
1069        // DER input should pass through unchanged
1070        let der = vec![
1071            0x30, 0x03, // SEQUENCE, length 3
1072            0x02, 0x01, 0x42, // INTEGER = 66
1073        ];
1074
1075        let result = ber_to_der(&der);
1076        assert!(result.is_ok());
1077        assert_eq!(result.unwrap(), der, "DER should pass through unchanged");
1078    }
1079
1080    #[cfg(feature = "signatures")]
1081    #[test]
1082    fn test_ber_to_der_empty_input() {
1083        let result = ber_to_der(&[]);
1084        assert!(result.is_ok());
1085        assert!(result.unwrap().is_empty());
1086    }
1087
1088    #[cfg(feature = "signatures")]
1089    #[test]
1090    fn test_ber_to_der_moderate_nesting_ok() {
1091        // Nesting within limits should work (e.g., 10 levels)
1092        let depth = 10;
1093
1094        let mut ber = Vec::new();
1095        for _ in 0..depth {
1096            ber.push(0x30);
1097            ber.push(0x80);
1098        }
1099        ber.extend_from_slice(&[0x02, 0x01, 0x00]); // INTEGER 0
1100        for _ in 0..depth {
1101            ber.push(0x00);
1102            ber.push(0x00);
1103        }
1104
1105        let result = ber_to_der(&ber);
1106        assert!(
1107            result.is_ok(),
1108            "Moderate nesting ({} levels) should work",
1109            depth
1110        );
1111    }
1112}