rust_sign/
verifier.rs

1//! Signature verification functionality.
2
3use crate::error::{Result, SignError};
4use crate::hash::{hash_bytes, hash_file, hash_reader, DocumentHash};
5use crate::signature::DocumentSignature;
6use chrono::{DateTime, Utc};
7use std::io::Read;
8use std::path::Path;
9
10/// Result of verifying a single signature.
11#[derive(Debug, Clone)]
12pub struct SignatureResult {
13    /// The index of this signature in the document.
14    pub index: usize,
15    
16    /// Whether the signature is valid.
17    pub valid: bool,
18    
19    /// The signer ID, if present.
20    pub signer_id: Option<String>,
21    
22    /// The timestamp of the signature.
23    pub timestamp: DateTime<Utc>,
24    
25    /// Error message if verification failed.
26    pub error: Option<String>,
27}
28
29/// Result of verifying all signatures on a document.
30#[derive(Debug)]
31pub struct VerificationResult {
32    /// Whether all signatures are valid.
33    pub all_valid: bool,
34    
35    /// The document hash that was verified.
36    pub document_hash: DocumentHash,
37    
38    /// Results for each individual signature.
39    pub signatures: Vec<SignatureResult>,
40}
41
42impl VerificationResult {
43    /// Get the number of valid signatures.
44    pub fn valid_count(&self) -> usize {
45        self.signatures.iter().filter(|s| s.valid).count()
46    }
47
48    /// Get the number of invalid signatures.
49    pub fn invalid_count(&self) -> usize {
50        self.signatures.iter().filter(|s| !s.valid).count()
51    }
52
53    /// Get all valid signer IDs.
54    pub fn valid_signers(&self) -> Vec<Option<String>> {
55        self.signatures
56            .iter()
57            .filter(|s| s.valid)
58            .map(|s| s.signer_id.clone())
59            .collect()
60    }
61}
62
63/// Verifier for document signatures.
64pub struct Verifier;
65
66impl Verifier {
67    /// Verify signatures on a byte slice.
68    pub fn verify_bytes(data: &[u8], doc_sig: &DocumentSignature) -> Result<VerificationResult> {
69        let actual_hash = hash_bytes(data);
70        Self::verify_with_hash(actual_hash, doc_sig)
71    }
72
73    /// Verify signatures on a file.
74    pub fn verify_file<P: AsRef<Path>>(
75        path: P,
76        signature_path: P,
77    ) -> Result<VerificationResult> {
78        let doc_sig = DocumentSignature::load(signature_path)?;
79        let actual_hash = hash_file(path)?;
80        Self::verify_with_hash(actual_hash, &doc_sig)
81    }
82
83    /// Verify signatures using a reader (streaming).
84    pub fn verify_reader<R: Read>(
85        reader: &mut R,
86        doc_sig: &DocumentSignature,
87    ) -> Result<VerificationResult> {
88        let actual_hash = hash_reader(reader)?;
89        Self::verify_with_hash(actual_hash, doc_sig)
90    }
91
92    /// Verify signatures against a known hash.
93    pub fn verify_with_hash(
94        actual_hash: DocumentHash,
95        doc_sig: &DocumentSignature,
96    ) -> Result<VerificationResult> {
97        // Check that document hash matches
98        let expected_hash = doc_sig.get_hash()?;
99        if actual_hash != expected_hash {
100            return Err(SignError::HashMismatch {
101                expected: expected_hash.to_hex(),
102                actual: actual_hash.to_hex(),
103            });
104        }
105
106        if doc_sig.signatures.is_empty() {
107            return Err(SignError::NoSignatures);
108        }
109
110        // Verify each signature
111        let mut results = Vec::new();
112        let mut all_valid = true;
113
114        for (index, entry) in doc_sig.signatures.iter().enumerate() {
115            let (valid, error) = match entry.verify(&actual_hash) {
116                Ok(()) => (true, None),
117                Err(e) => {
118                    all_valid = false;
119                    (false, Some(e.to_string()))
120                }
121            };
122
123            results.push(SignatureResult {
124                index,
125                valid,
126                signer_id: entry.signer_id.clone(),
127                timestamp: entry.timestamp,
128                error,
129            });
130        }
131
132        Ok(VerificationResult {
133            all_valid,
134            document_hash: actual_hash,
135            signatures: results,
136        })
137    }
138
139    /// Quick check if all signatures are valid for bytes.
140    pub fn is_valid_bytes(data: &[u8], doc_sig: &DocumentSignature) -> bool {
141        Self::verify_bytes(data, doc_sig)
142            .map(|r| r.all_valid)
143            .unwrap_or(false)
144    }
145
146    /// Quick check if all signatures are valid for a file.
147    pub fn is_valid_file<P: AsRef<Path>>(path: P, signature_path: P) -> bool {
148        Self::verify_file(path, signature_path)
149            .map(|r| r.all_valid)
150            .unwrap_or(false)
151    }
152}
153
154/// Convenience function to verify bytes against a signature.
155pub fn verify_bytes(data: &[u8], doc_sig: &DocumentSignature) -> Result<VerificationResult> {
156    Verifier::verify_bytes(data, doc_sig)
157}
158
159/// Convenience function to verify a file against its signature file.
160pub fn verify_file<P: AsRef<Path>>(path: P, signature_path: P) -> Result<VerificationResult> {
161    Verifier::verify_file(path, signature_path)
162}
163
164#[cfg(test)]
165mod tests {
166    use super::*;
167    use crate::keys::KeyPair;
168    use crate::signer::Signer;
169
170    #[test]
171    fn test_verify_valid_signature() {
172        let keypair = KeyPair::generate();
173        let data = b"Test document content";
174        
175        let doc_sig = Signer::new(&keypair)
176            .with_signer_id("test@example.com")
177            .sign_bytes(data)
178            .unwrap();
179        
180        let result = Verifier::verify_bytes(data, &doc_sig).unwrap();
181        
182        assert!(result.all_valid);
183        assert_eq!(result.valid_count(), 1);
184        assert_eq!(result.invalid_count(), 0);
185    }
186
187    #[test]
188    fn test_verify_multiple_signatures() {
189        let keypair1 = KeyPair::generate();
190        let keypair2 = KeyPair::generate();
191        let data = b"Test document content";
192        
193        let mut doc_sig = Signer::new(&keypair1)
194            .with_signer_id("alice@example.com")
195            .sign_bytes(data)
196            .unwrap();
197        
198        Signer::new(&keypair2)
199            .with_signer_id("bob@example.com")
200            .cosign(&mut doc_sig)
201            .unwrap();
202        
203        let result = Verifier::verify_bytes(data, &doc_sig).unwrap();
204        
205        assert!(result.all_valid);
206        assert_eq!(result.valid_count(), 2);
207    }
208
209    #[test]
210    fn test_verify_tampered_document() {
211        let keypair = KeyPair::generate();
212        let original_data = b"Original content";
213        let tampered_data = b"Tampered content";
214        
215        let doc_sig = Signer::new(&keypair).sign_bytes(original_data).unwrap();
216        
217        // Verification should fail with tampered data
218        let result = Verifier::verify_bytes(tampered_data, &doc_sig);
219        assert!(result.is_err());
220        
221        if let Err(SignError::HashMismatch { .. }) = result {
222            // Expected error
223        } else {
224            panic!("Expected HashMismatch error");
225        }
226    }
227
228    #[test]
229    fn test_is_valid_convenience() {
230        let keypair = KeyPair::generate();
231        let data = b"Test document content";
232        
233        let doc_sig = Signer::new(&keypair).sign_bytes(data).unwrap();
234        
235        assert!(Verifier::is_valid_bytes(data, &doc_sig));
236        assert!(!Verifier::is_valid_bytes(b"wrong data", &doc_sig));
237    }
238
239    #[test]
240    fn test_valid_signers() {
241        let keypair1 = KeyPair::generate();
242        let keypair2 = KeyPair::generate();
243        let data = b"Test document content";
244        
245        let mut doc_sig = Signer::new(&keypair1)
246            .with_signer_id("alice@example.com")
247            .sign_bytes(data)
248            .unwrap();
249        
250        Signer::new(&keypair2)
251            .with_signer_id("bob@example.com")
252            .cosign(&mut doc_sig)
253            .unwrap();
254        
255        let result = Verifier::verify_bytes(data, &doc_sig).unwrap();
256        let signers = result.valid_signers();
257        
258        assert!(signers.contains(&Some("alice@example.com".to_string())));
259        assert!(signers.contains(&Some("bob@example.com".to_string())));
260    }
261}
262