1use anyhow::{Result, bail};
4use sha2::{Sha256, Digest};
5use crate::built_info;
6
7pub fn verify_real_mode() -> Result<()> {
9 let mode = std::env::var("LENS_MODE").unwrap_or_else(|_| "real".to_string());
10
11 if mode != "real" {
12 bail!("TRIPWIRE VIOLATION: LENS_MODE must be 'real', got '{}'", mode);
13 }
14
15 Ok(())
16}
17
18pub fn get_build_info() -> crate::proto::BuildInfoResponse {
20 crate::proto::BuildInfoResponse {
21 version: env!("CARGO_PKG_VERSION").to_string(),
22 commit: built_info::GIT_VERSION.unwrap_or("unknown").to_string(),
23 build_timestamp: env!("BUILD_TIMESTAMP", "unknown").to_string(),
24 features: "default,lsp,benchmarks,attestation".to_string(), }
26}
27
28pub fn perform_handshake(nonce: &str) -> Result<String> {
30 let build_sha = built_info::GIT_VERSION.unwrap_or("unknown");
31 let challenge_input = format!("{}{}", nonce, build_sha);
32
33 let mut hasher = Sha256::new();
34 hasher.update(challenge_input.as_bytes());
35 let response = format!("{:x}", hasher.finalize());
36
37 Ok(response)
38}
39
40pub fn check_banned_patterns(text: &str) -> Vec<String> {
42 let banned = [
43 "generateMock", "simulate", "MOCK_RESULT", "mock_file_", "fake"
44 ];
45
46 let mut violations = Vec::new();
47 for pattern in &banned {
48 if text.to_lowercase().contains(&pattern.to_lowercase()) {
49 violations.push(format!("Banned pattern detected: {}", pattern));
50 }
51 }
52
53 violations
54}
55
56pub struct AttestationManager {
58 enabled: bool,
59 build_hash: String,
60 runtime_checks: Vec<String>,
61}
62
63impl AttestationManager {
64 pub fn new(enabled: bool) -> Result<Self> {
66 let build_hash = Self::compute_build_hash()?;
67
68 Ok(Self {
69 enabled,
70 build_hash,
71 runtime_checks: Vec::new(),
72 })
73 }
74
75 pub fn verify_integrity(&self) -> Result<AttestationReport> {
77 let mut violations = Vec::new();
78 let mut checks_passed = 0;
79 let mut total_checks = 0;
80
81 total_checks += 1;
83 if let Err(e) = verify_real_mode() {
84 violations.push(e.to_string());
85 } else {
86 checks_passed += 1;
87 }
88
89 total_checks += 1;
91 if let Err(e) = self.verify_build_integrity() {
92 violations.push(e.to_string());
93 } else {
94 checks_passed += 1;
95 }
96
97 total_checks += 1;
99 if let Err(e) = self.verify_environment() {
100 violations.push(e.to_string());
101 } else {
102 checks_passed += 1;
103 }
104
105 Ok(AttestationReport {
106 timestamp: chrono::Utc::now(),
107 total_checks,
108 checks_passed,
109 violations,
110 build_hash: self.build_hash.clone(),
111 integrity_score: (checks_passed as f64 / total_checks as f64) * 100.0,
112 })
113 }
114
115 pub fn handshake(&self, nonce: &str) -> Result<HandshakeResponse> {
117 if !self.enabled {
118 bail!("Attestation is disabled");
119 }
120
121 let response_hash = perform_handshake(nonce)?;
122 let build_info = get_build_info();
123
124 Ok(HandshakeResponse {
125 response_hash,
126 build_info,
127 attestation_enabled: self.enabled,
128 })
129 }
130
131 fn compute_build_hash() -> Result<String> {
132 let build_data = format!(
133 "{}{}{}{}",
134 built_info::GIT_VERSION.unwrap_or("unknown"),
135 env!("BUILD_TIMESTAMP", "unknown"),
136 built_info::RUSTC_VERSION,
137 built_info::CFG_TARGET_ARCH
138 );
139
140 let mut hasher = Sha256::new();
141 hasher.update(build_data.as_bytes());
142 Ok(format!("{:x}", hasher.finalize()))
143 }
144
145 fn verify_build_integrity(&self) -> Result<()> {
146 let current_hash = Self::compute_build_hash()?;
147 if current_hash != self.build_hash {
148 bail!("Build integrity check failed: hash mismatch");
149 }
150 Ok(())
151 }
152
153 fn verify_environment(&self) -> Result<()> {
154 let suspicious_vars = ["LENS_MOCK", "LENS_SIMULATE", "LENS_FAKE"];
156 for var in suspicious_vars {
157 if std::env::var(var).is_ok() {
158 bail!("Suspicious environment variable detected: {}", var);
159 }
160 }
161 Ok(())
162 }
163}
164
165#[derive(Debug, Clone)]
167pub struct AttestationReport {
168 pub timestamp: chrono::DateTime<chrono::Utc>,
169 pub total_checks: usize,
170 pub checks_passed: usize,
171 pub violations: Vec<String>,
172 pub build_hash: String,
173 pub integrity_score: f64,
174}
175
176impl AttestationReport {
177 pub fn is_valid(&self) -> bool {
178 self.violations.is_empty() && self.integrity_score >= 100.0
179 }
180}
181
182#[derive(Debug, Clone)]
184pub struct HandshakeResponse {
185 pub response_hash: String,
186 pub build_info: crate::proto::BuildInfoResponse,
187 pub attestation_enabled: bool,
188}
189
190#[cfg(test)]
191mod tests {
192 use super::*;
193 use std::env;
194 use std::sync::Mutex;
195
196 static ATTESTATION_ENV_MUTEX: Mutex<()> = Mutex::new(());
198
199 #[test]
200 fn test_verify_real_mode_default() {
201 let _lock = ATTESTATION_ENV_MUTEX.lock().unwrap();
202
203 env::remove_var("LENS_MODE");
205 assert!(verify_real_mode().is_ok());
206 }
207
208 #[test]
209 fn test_verify_real_mode_explicit() {
210 let _lock = ATTESTATION_ENV_MUTEX.lock().unwrap();
211
212 env::set_var("LENS_MODE", "real");
213 assert!(verify_real_mode().is_ok());
214 env::remove_var("LENS_MODE");
216 }
217
218 #[test]
219 fn test_verify_real_mode_rejects_mock() {
220 let _lock = ATTESTATION_ENV_MUTEX.lock().unwrap();
221
222 env::set_var("LENS_MODE", "mock");
223 let result = verify_real_mode();
224 assert!(result.is_err());
225 assert!(result.unwrap_err().to_string().contains("TRIPWIRE VIOLATION"));
226
227 env::remove_var("LENS_MODE");
229 }
230
231 #[test]
232 fn test_verify_real_mode_rejects_invalid() {
233 let _lock = ATTESTATION_ENV_MUTEX.lock().unwrap();
234
235 env::set_var("LENS_MODE", "test");
236 let result = verify_real_mode();
237 assert!(result.is_err());
238 assert!(result.unwrap_err().to_string().contains("TRIPWIRE VIOLATION"));
239
240 env::remove_var("LENS_MODE");
242 }
243
244 #[test]
245 fn test_get_build_info() {
246 let info = get_build_info();
247 assert!(!info.version.is_empty());
248 assert!(!info.commit.is_empty());
249 assert!(!info.build_timestamp.is_empty());
250 assert!(!info.features.is_empty());
251 }
252
253 #[test]
254 fn test_perform_handshake() {
255 let nonce = "test_nonce_123";
256 let response = perform_handshake(nonce);
257 assert!(response.is_ok());
258
259 let response_hash = response.unwrap();
260 assert_eq!(response_hash.len(), 64); let response2 = perform_handshake(nonce).unwrap();
264 assert_eq!(response_hash, response2);
265 }
266
267 #[test]
268 fn test_perform_handshake_different_nonces() {
269 let response1 = perform_handshake("nonce1").unwrap();
270 let response2 = perform_handshake("nonce2").unwrap();
271 assert_ne!(response1, response2);
272 }
273
274 #[test]
275 fn test_check_banned_patterns_clean() {
276 let clean_text = "This is a normal search query";
277 let violations = check_banned_patterns(clean_text);
278 assert!(violations.is_empty());
279 }
280
281 #[test]
282 fn test_check_banned_patterns_violations() {
283 let violations = check_banned_patterns("generateMock data");
284 assert_eq!(violations.len(), 1);
285 assert!(violations[0].contains("generateMock"));
286
287 let violations = check_banned_patterns("MOCK_RESULT and fake data");
288 assert_eq!(violations.len(), 2);
289 }
290
291 #[test]
292 fn test_check_banned_patterns_case_insensitive() {
293 let violations = check_banned_patterns("GenerateMOCK and FAKE");
294 assert_eq!(violations.len(), 2);
295 }
296
297 #[test]
298 fn test_attestation_manager_creation() {
299 let manager = AttestationManager::new(true);
300 assert!(manager.is_ok());
301
302 let manager = manager.unwrap();
303 assert!(manager.enabled);
304 assert!(!manager.build_hash.is_empty());
305 assert_eq!(manager.build_hash.len(), 64); }
307
308 #[test]
309 fn test_attestation_manager_disabled() {
310 let manager = AttestationManager::new(false).unwrap();
311 assert!(!manager.enabled);
312 }
313
314 #[test]
315 fn test_verify_integrity_clean_environment() {
316 let _lock = ATTESTATION_ENV_MUTEX.lock().unwrap();
317
318 let suspicious_vars = ["LENS_MOCK", "LENS_SIMULATE", "LENS_FAKE", "LENS_MODE"];
320 for var in suspicious_vars {
321 env::remove_var(var);
322 }
323
324 let manager = AttestationManager::new(true).unwrap();
325 let report = manager.verify_integrity();
326 assert!(report.is_ok());
327
328 let report = report.unwrap();
329 assert_eq!(report.total_checks, 3);
330 assert_eq!(report.checks_passed, 3);
331 assert!(report.violations.is_empty());
332 assert_eq!(report.integrity_score, 100.0);
333 assert!(report.is_valid());
334 }
335
336 #[test]
337 fn test_verify_integrity_with_violations() {
338 let _lock = ATTESTATION_ENV_MUTEX.lock().unwrap();
339
340 env::set_var("LENS_MOCK", "true");
342
343 let manager = AttestationManager::new(true).unwrap();
344 let report = manager.verify_integrity().unwrap();
345
346 assert!(report.checks_passed < report.total_checks);
347 assert!(!report.violations.is_empty());
348 assert!(report.integrity_score < 100.0);
349 assert!(!report.is_valid());
350
351 env::remove_var("LENS_MOCK");
353 }
354
355 #[test]
356 fn test_handshake_enabled() {
357 let manager = AttestationManager::new(true).unwrap();
358 let response = manager.handshake("test_nonce");
359 assert!(response.is_ok());
360
361 let response = response.unwrap();
362 assert!(!response.response_hash.is_empty());
363 assert_eq!(response.response_hash.len(), 64);
364 assert!(response.attestation_enabled);
365 assert!(!response.build_info.version.is_empty());
366 }
367
368 #[test]
369 fn test_handshake_disabled() {
370 let manager = AttestationManager::new(false).unwrap();
371 let response = manager.handshake("test_nonce");
372 assert!(response.is_err());
373 assert!(response.unwrap_err().to_string().contains("Attestation is disabled"));
374 }
375
376 #[test]
377 fn test_compute_build_hash_consistency() {
378 let hash1 = AttestationManager::compute_build_hash().unwrap();
379 let hash2 = AttestationManager::compute_build_hash().unwrap();
380 assert_eq!(hash1, hash2);
381 assert_eq!(hash1.len(), 64); }
383
384 #[test]
385 fn test_verify_build_integrity() {
386 let manager = AttestationManager::new(true).unwrap();
387 assert!(manager.verify_build_integrity().is_ok());
388 }
389
390 #[test]
391 fn test_verify_environment_clean() {
392 let _lock = ATTESTATION_ENV_MUTEX.lock().unwrap();
393
394 let suspicious_vars = ["LENS_MOCK", "LENS_SIMULATE", "LENS_FAKE"];
396 for var in suspicious_vars {
397 env::remove_var(var);
398 }
399
400 let manager = AttestationManager::new(true).unwrap();
401 assert!(manager.verify_environment().is_ok());
402 }
403
404 #[test]
405 fn test_verify_environment_suspicious() {
406 let _lock = ATTESTATION_ENV_MUTEX.lock().unwrap();
407
408 env::set_var("LENS_MOCK", "true");
409
410 let manager = AttestationManager::new(true).unwrap();
411 let result = manager.verify_environment();
412 assert!(result.is_err());
413 assert!(result.unwrap_err().to_string().contains("LENS_MOCK"));
414
415 env::remove_var("LENS_MOCK");
417 }
418
419 #[test]
420 fn test_attestation_report_validity() {
421 use chrono::Utc;
422
423 let valid_report = AttestationReport {
424 timestamp: Utc::now(),
425 total_checks: 3,
426 checks_passed: 3,
427 violations: Vec::new(),
428 build_hash: "test_hash".to_string(),
429 integrity_score: 100.0,
430 };
431 assert!(valid_report.is_valid());
432
433 let invalid_report = AttestationReport {
434 timestamp: Utc::now(),
435 total_checks: 3,
436 checks_passed: 2,
437 violations: vec!["test violation".to_string()],
438 build_hash: "test_hash".to_string(),
439 integrity_score: 66.67,
440 };
441 assert!(!invalid_report.is_valid());
442 }
443
444 #[test]
445 fn test_handshake_response_structure() {
446 let manager = AttestationManager::new(true).unwrap();
447 let response = manager.handshake("test").unwrap();
448
449 assert!(!response.response_hash.is_empty());
451 assert!(response.attestation_enabled);
452 assert!(!response.build_info.version.is_empty());
453 assert!(!response.build_info.commit.is_empty());
454 assert!(!response.build_info.build_timestamp.is_empty());
455 }
456}