ribbit_client/
signature.rs

1//! ASN.1 signature parsing for Ribbit V1 responses
2//!
3//! This module provides basic parsing of PKCS#7/CMS signatures.
4
5use crate::error::Result;
6use tracing::{debug, trace};
7
8/// Parse a PKCS#7 signature and extract basic information
9///
10/// # Errors
11///
12/// Returns an error if:
13/// - The signature is too short (less than 20 bytes)
14/// - ASN.1 parsing fails
15/// - The signature structure is invalid
16pub fn parse_signature(signature_bytes: &[u8]) -> Result<SignatureInfo> {
17    trace!("Parsing {} bytes of signature data", signature_bytes.len());
18
19    // First few bytes tell us about the structure
20    if signature_bytes.len() < 20 {
21        return Err(crate::error::Error::Asn1Error(
22            "Signature too short".to_string(),
23        ));
24    }
25
26    // Parse as raw ASN.1 to extract basic structure information
27    let tlv = asn1::parse_single::<asn1::Tlv>(signature_bytes)
28        .map_err(|e| crate::error::Error::Asn1Error(format!("Failed to parse ASN.1: {e:?}")))?;
29
30    let tag = tlv.tag();
31    let len = tlv.data().len();
32    trace!("ASN.1 tag: {tag:?}, length: {len}");
33
34    // Try to parse inner content to get more details
35    let mut algorithm = "Unknown".to_string();
36    let mut signer_count = 0;
37    let mut certificate_count = 0;
38
39    // PKCS#7 has OID 1.2.840.113549.1.7.2 for signedData
40    // Let's look for this pattern
41    let data = tlv.data();
42    if data.len() > 20 {
43        // Look for the signedData OID pattern (06 09 2a 86 48 86 f7 0d 01 07 02)
44        if data[0] == 0x06 && data[1] == 0x09 {
45            let oid_bytes = &data[2..11];
46            if oid_bytes == [0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x07, 0x02] {
47                debug!("Found PKCS#7 signedData OID");
48
49                // Try to find algorithm identifiers (SHA-256 = 2.16.840.1.101.3.4.2.1)
50                if let Some(sha256_pos) = find_pattern(
51                    data,
52                    &[0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01],
53                ) {
54                    algorithm = "SHA-256".to_string();
55                    debug!("Found SHA-256 algorithm at position {}", sha256_pos);
56                }
57
58                // Count SEQUENCE tags that might be certificates (very rough estimate)
59                let mut pos = 0;
60                while pos < data.len() - 4 {
61                    if data[pos] == 0x30 && data[pos + 1] == 0x82 {
62                        // This might be a certificate (they're usually large SEQUENCEs)
63                        let len = ((data[pos + 2] as usize) << 8) | (data[pos + 3] as usize);
64                        if len > 300 && len < 2000 {
65                            certificate_count += 1;
66                        }
67                        pos += 4 + len;
68                    } else {
69                        pos += 1;
70                    }
71                }
72
73                // SignerInfo structures usually contain specific patterns
74                signer_count = 1; // Assume at least one signer if we have a valid signature
75            }
76        }
77    }
78
79    let signature_info = SignatureInfo {
80        format: "PKCS#7/CMS".to_string(),
81        size: signature_bytes.len(),
82        algorithm,
83        signer_count,
84        certificate_count,
85    };
86
87    debug!("Parsed signature: {:?}", signature_info);
88    Ok(signature_info)
89}
90
91/// Find a byte pattern in data
92fn find_pattern(data: &[u8], pattern: &[u8]) -> Option<usize> {
93    data.windows(pattern.len())
94        .position(|window| window == pattern)
95}
96
97/// Information extracted from a parsed signature
98#[derive(Debug, Clone)]
99pub struct SignatureInfo {
100    /// Signature format (e.g., "PKCS#7")
101    pub format: String,
102    /// Size of the signature in bytes
103    pub size: usize,
104    /// Signature algorithm (if detected)
105    pub algorithm: String,
106    /// Number of signers
107    pub signer_count: usize,
108    /// Number of certificates included
109    pub certificate_count: usize,
110}