1use 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#[derive(Debug, Clone)]
12pub struct SignatureResult {
13 pub index: usize,
15
16 pub valid: bool,
18
19 pub signer_id: Option<String>,
21
22 pub timestamp: DateTime<Utc>,
24
25 pub error: Option<String>,
27}
28
29#[derive(Debug)]
31pub struct VerificationResult {
32 pub all_valid: bool,
34
35 pub document_hash: DocumentHash,
37
38 pub signatures: Vec<SignatureResult>,
40}
41
42impl VerificationResult {
43 pub fn valid_count(&self) -> usize {
45 self.signatures.iter().filter(|s| s.valid).count()
46 }
47
48 pub fn invalid_count(&self) -> usize {
50 self.signatures.iter().filter(|s| !s.valid).count()
51 }
52
53 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
63pub struct Verifier;
65
66impl Verifier {
67 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 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 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 pub fn verify_with_hash(
94 actual_hash: DocumentHash,
95 doc_sig: &DocumentSignature,
96 ) -> Result<VerificationResult> {
97 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 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 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 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
154pub fn verify_bytes(data: &[u8], doc_sig: &DocumentSignature) -> Result<VerificationResult> {
156 Verifier::verify_bytes(data, doc_sig)
157}
158
159pub 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 let result = Verifier::verify_bytes(tampered_data, &doc_sig);
219 assert!(result.is_err());
220
221 if let Err(SignError::HashMismatch { .. }) = result {
222 } 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