1use crate::crypto::{generate_nonce, CryptoError, KeyStore};
42use crate::proof::{Action, ActionType};
43use serde::{Deserialize, Serialize};
44use std::sync::atomic::{AtomicU32, Ordering};
45use thiserror::Error;
46
47pub const MAX_VIOLATIONS: u32 = 10;
54
55const ATOMIC_ORDERING: Ordering = Ordering::SeqCst;
57
58#[derive(Debug, Error)]
63pub enum WatchdogError {
64 #[error("Rule violation detected: {rule} - {reason}")]
65 RuleViolation { rule: String, reason: String },
66
67 #[error("HARD RESET REQUIRED: {0} consecutive violations (max: {1})")]
68 HardResetRequired(u32, u32),
69
70 #[error("Crypto error: {0}")]
71 CryptoError(#[from] CryptoError),
72
73 #[error("Watchdog is locked after hard reset - restart required")]
74 WatchdogLocked,
75}
76
77pub type Result<T> = std::result::Result<T, WatchdogError>;
78
79#[derive(Debug)]
102pub struct ViolationCounter {
103 count: AtomicU32,
105
106 locked: AtomicU32, }
109
110impl Default for ViolationCounter {
111 fn default() -> Self {
112 Self::new()
113 }
114}
115
116impl ViolationCounter {
117 #[inline]
119 pub const fn new() -> Self {
120 ViolationCounter {
121 count: AtomicU32::new(0),
122 locked: AtomicU32::new(0),
123 }
124 }
125
126 #[inline]
128 pub fn count(&self) -> u32 {
129 self.count.load(ATOMIC_ORDERING)
130 }
131
132 #[inline]
137 pub fn increment(&self) -> u32 {
138 self.count.fetch_add(1, ATOMIC_ORDERING) + 1
139 }
140
141 #[inline]
145 pub fn reset(&self) {
146 self.count.store(0, ATOMIC_ORDERING);
147 }
148
149 #[inline]
151 pub fn is_max_reached(&self) -> bool {
152 self.count() >= MAX_VIOLATIONS
153 }
154
155 #[inline]
157 pub fn is_locked(&self) -> bool {
158 self.locked.load(ATOMIC_ORDERING) == 1
159 }
160
161 #[inline]
163 pub fn lock(&self) {
164 self.locked.store(1, ATOMIC_ORDERING);
165 }
166
167 #[inline]
169 pub fn unlock(&self) {
170 self.locked.store(0, ATOMIC_ORDERING);
171 self.reset();
172 }
173}
174
175#[derive(Debug, Clone, Serialize, Deserialize)]
205pub struct DenialProof {
206 pub nonce: [u8; 32],
208
209 pub timestamp: u64,
211
212 pub action_hash: [u8; 32],
214
215 pub action_type: ActionType,
217
218 pub violated_rule: String,
220
221 pub denial_reason: String,
223
224 pub violation_count: u32,
226
227 pub triggered_hard_reset: bool,
229
230 pub signature: Vec<u8>,
232}
233
234impl DenialProof {
235 pub fn new(
237 action: &Action,
238 violated_rule: String,
239 denial_reason: String,
240 violation_count: u32,
241 ) -> Self {
242 DenialProof {
243 nonce: generate_nonce(),
244 timestamp: chrono::Utc::now().timestamp() as u64,
245 action_hash: action.hash(),
246 action_type: action.action_type.clone(),
247 violated_rule,
248 denial_reason,
249 violation_count,
250 triggered_hard_reset: violation_count >= MAX_VIOLATIONS,
251 signature: Vec::new(),
252 }
253 }
254
255 pub fn signing_data(&self) -> Vec<u8> {
257 let mut data = Vec::with_capacity(256); data.extend_from_slice(&self.nonce);
259 data.extend_from_slice(&self.timestamp.to_le_bytes());
260 data.extend_from_slice(&self.action_hash);
261 data.extend_from_slice(self.violated_rule.as_bytes());
262 data.extend_from_slice(self.denial_reason.as_bytes());
263 data.extend_from_slice(&self.violation_count.to_le_bytes());
264 data.push(self.triggered_hard_reset as u8);
265 data
266 }
267
268 pub fn sign(&mut self, key_store: &dyn KeyStore) -> std::result::Result<(), CryptoError> {
270 let data = self.signing_data();
271 self.signature = key_store.sign(&data)?;
272 Ok(())
273 }
274
275 pub fn is_signed(&self) -> bool {
277 self.signature.len() == 64 }
279
280 pub fn verify(&self, key_store: &dyn KeyStore) -> std::result::Result<(), CryptoError> {
282 let data = self.signing_data();
283 key_store.verify(&data, &self.signature)
284 }
285
286 pub fn signature_hex(&self) -> String {
288 hex::encode(&self.signature)
289 }
290}
291
292#[derive(Debug, Clone, Serialize, Deserialize)]
304pub struct HardResetSignal {
305 pub final_denial: DenialProof,
307
308 pub total_violations: u32,
310
311 pub genome_hash: String,
313
314 pub reset_timestamp: u64,
316
317 pub signature: Vec<u8>,
319}
320
321impl HardResetSignal {
322 pub fn new(final_denial: DenialProof, genome_hash: String) -> Self {
324 HardResetSignal {
325 total_violations: final_denial.violation_count,
326 final_denial,
327 genome_hash,
328 reset_timestamp: chrono::Utc::now().timestamp() as u64,
329 signature: Vec::new(),
330 }
331 }
332
333 pub fn signing_data(&self) -> Vec<u8> {
335 let mut data = Vec::with_capacity(512);
336 data.extend_from_slice(&self.final_denial.signing_data());
337 data.extend_from_slice(self.genome_hash.as_bytes());
338 data.extend_from_slice(&self.reset_timestamp.to_le_bytes());
339 data
340 }
341
342 pub fn sign(&mut self, key_store: &dyn KeyStore) -> std::result::Result<(), CryptoError> {
344 let data = self.signing_data();
345 self.signature = key_store.sign(&data)?;
346 Ok(())
347 }
348}
349
350pub struct Watchdog {
380 rules: Vec<String>,
382
383 capsule_hash: String,
385
386 counter: ViolationCounter,
388
389 key_store: Box<dyn KeyStore>,
391}
392
393impl Watchdog {
394 pub fn new(rules: Vec<String>, capsule_hash: String, key_store: Box<dyn KeyStore>) -> Self {
396 Watchdog {
397 rules,
398 capsule_hash,
399 counter: ViolationCounter::new(),
400 key_store,
401 }
402 }
403
404 #[inline]
406 pub fn violation_count(&self) -> u32 {
407 self.counter.count()
408 }
409
410 #[inline]
412 pub fn is_locked(&self) -> bool {
413 self.counter.is_locked()
414 }
415
416 #[inline]
421 pub fn report_success(&self) {
422 self.counter.reset();
423 }
424
425 pub fn report_violation(
433 &self,
434 action: &Action,
435 violated_rule: &str,
436 reason: &str,
437 ) -> Result<DenialProof> {
438 if self.counter.is_locked() {
440 return Err(WatchdogError::WatchdogLocked);
441 }
442
443 let count = self.counter.increment();
445
446 let mut proof =
448 DenialProof::new(action, violated_rule.to_string(), reason.to_string(), count);
449
450 proof.sign(self.key_store.as_ref())?;
452
453 if count >= MAX_VIOLATIONS {
455 self.counter.lock();
456 return Err(WatchdogError::HardResetRequired(count, MAX_VIOLATIONS));
457 }
458
459 Ok(proof)
460 }
461
462 pub fn generate_hard_reset_signal(&self, final_denial: DenialProof) -> Result<HardResetSignal> {
467 let mut signal = HardResetSignal::new(final_denial, self.capsule_hash.clone());
468 signal.sign(self.key_store.as_ref())?;
469 Ok(signal)
470 }
471
472 pub fn acknowledge_reset(&self) {
479 self.counter.unlock();
480 }
481
482 pub fn check_action(&self, action: &Action) -> Option<(String, String)> {
490 for rule in &self.rules {
491 if rule.to_lowercase().contains("no harm") {
493 match action.action_type {
494 ActionType::Delete => {
495 if action.target.contains("system32")
497 || action.target.contains("/etc")
498 || action.target.contains("/bin")
499 || action.target.contains("boot")
500 {
501 return Some((
502 rule.clone(),
503 format!("Destructive action on critical path: {}", action.target),
504 ));
505 }
506 }
507 ActionType::Execute => {
508 let dangerous = ["rm -rf", "format", "mkfs", "dd if=", ":(){ :|:& };:"];
510 for cmd in &dangerous {
511 if action.target.contains(cmd) {
512 return Some((
513 rule.clone(),
514 format!("Dangerous command detected: {}", action.target),
515 ));
516 }
517 }
518 }
519 _ => {}
520 }
521 }
522
523 if rule.to_lowercase().contains("privacy") {
525 match action.action_type {
526 ActionType::Network => {
527 if action.target.contains("exfil")
528 || action.target.contains("upload")
529 || action.target.contains("pastebin")
530 {
531 return Some((
532 rule.clone(),
533 format!("Potential data exfiltration: {}", action.target),
534 ));
535 }
536 }
537 ActionType::Read => {
538 if action.target.contains("password")
539 || action.target.contains(".ssh")
540 || action.target.contains("credentials")
541 {
542 return Some((
543 rule.clone(),
544 format!("Accessing sensitive data: {}", action.target),
545 ));
546 }
547 }
548 _ => {}
549 }
550 }
551 }
552
553 None }
555
556 pub fn verify_action(&self, action: &Action) -> Result<Option<DenialProof>> {
565 if let Some((rule, reason)) = self.check_action(action) {
566 let proof = self.report_violation(action, &rule, &reason)?;
567 Ok(Some(proof))
568 } else {
569 self.report_success();
570 Ok(None)
571 }
572 }
573
574 pub fn rules(&self) -> &[String] {
576 &self.rules
577 }
578
579 pub fn capsule_hash(&self) -> &str {
581 &self.capsule_hash
582 }
583}
584
585#[cfg(test)]
590mod tests {
591 use super::*;
592 use crate::crypto::SoftwareKeyStore;
593
594 fn create_test_watchdog() -> Watchdog {
595 let key_store = SoftwareKeyStore::generate().unwrap();
596 Watchdog::new(
597 vec!["Do no harm".to_string(), "Respect privacy".to_string()],
598 "test_capsule_hash".to_string(),
599 Box::new(key_store),
600 )
601 }
602
603 #[test]
604 fn test_violation_counter_basic() {
605 let counter = ViolationCounter::new();
606 assert_eq!(counter.count(), 0);
607
608 counter.increment();
609 assert_eq!(counter.count(), 1);
610
611 counter.increment();
612 assert_eq!(counter.count(), 2);
613
614 counter.reset();
615 assert_eq!(counter.count(), 0);
616 }
617
618 #[test]
619 fn test_violation_counter_max_check() {
620 let counter = ViolationCounter::new();
621
622 for _ in 0..9 {
623 counter.increment();
624 assert!(!counter.is_max_reached());
625 }
626
627 counter.increment(); assert!(counter.is_max_reached());
629 }
630
631 #[test]
632 fn test_violation_counter_locking() {
633 let counter = ViolationCounter::new();
634 assert!(!counter.is_locked());
635
636 counter.lock();
637 assert!(counter.is_locked());
638
639 counter.unlock();
640 assert!(!counter.is_locked());
641 assert_eq!(counter.count(), 0); }
643
644 #[test]
645 fn test_denial_proof_creation() {
646 let action = Action::delete("system32");
647 let proof = DenialProof::new(
648 &action,
649 "Do no harm".to_string(),
650 "Destructive action".to_string(),
651 1,
652 );
653
654 assert!(!proof.is_signed());
655 assert_eq!(proof.violation_count, 1);
656 assert!(!proof.triggered_hard_reset);
657 }
658
659 #[test]
660 fn test_denial_proof_signing() {
661 let key_store = SoftwareKeyStore::generate().unwrap();
662 let action = Action::delete("test.txt");
663 let mut proof = DenialProof::new(
664 &action,
665 "Test rule".to_string(),
666 "Test reason".to_string(),
667 1,
668 );
669
670 assert!(!proof.is_signed());
671
672 proof.sign(&key_store).unwrap();
673 assert!(proof.is_signed());
674 assert_eq!(proof.signature.len(), 64); assert!(proof.verify(&key_store).is_ok());
678 }
679
680 #[test]
681 fn test_denial_proof_triggered_hard_reset() {
682 let action = Action::delete("test.txt");
683 let proof = DenialProof::new(
684 &action,
685 "Rule".to_string(),
686 "Reason".to_string(),
687 10, );
689
690 assert!(proof.triggered_hard_reset);
691 }
692
693 #[test]
694 fn test_watchdog_allows_safe_action() {
695 let watchdog = create_test_watchdog();
696 let action = Action::delete("temp_file.txt");
697
698 let result = watchdog.verify_action(&action);
699 assert!(result.is_ok());
700 assert!(result.unwrap().is_none()); assert_eq!(watchdog.violation_count(), 0);
702 }
703
704 #[test]
705 fn test_watchdog_blocks_harmful_action() {
706 let watchdog = create_test_watchdog();
707 let action = Action::delete("C:/Windows/system32/important.dll");
708
709 let result = watchdog.verify_action(&action);
710 assert!(result.is_ok());
711
712 let denial = result.unwrap();
713 assert!(denial.is_some());
714
715 let proof = denial.unwrap();
716 assert!(proof.is_signed());
717 assert_eq!(proof.violation_count, 1);
718 assert_eq!(watchdog.violation_count(), 1);
719 }
720
721 #[test]
722 fn test_watchdog_hard_reset_after_10_violations() {
723 let watchdog = create_test_watchdog();
724 let harmful_action = Action::delete("/etc/passwd");
725
726 for i in 1..=9 {
728 let result = watchdog.verify_action(&harmful_action);
729 assert!(result.is_ok());
730 let denial = result.unwrap();
731 assert!(denial.is_some());
732 assert_eq!(watchdog.violation_count(), i);
733 }
734
735 let result = watchdog.verify_action(&harmful_action);
737 assert!(result.is_err());
738
739 match result {
740 Err(WatchdogError::HardResetRequired(count, max)) => {
741 assert_eq!(count, 10);
742 assert_eq!(max, MAX_VIOLATIONS);
743 }
744 _ => panic!("Expected HardResetRequired error"),
745 }
746
747 assert!(watchdog.is_locked());
749 }
750
751 #[test]
752 fn test_watchdog_locked_state() {
753 let watchdog = create_test_watchdog();
754
755 watchdog.counter.lock();
757 assert!(watchdog.is_locked());
758
759 let action = Action::delete("/etc/passwd");
761 let _ = watchdog.verify_action(&action);
762
763 let result = watchdog.report_violation(&action, "rule", "reason");
767 assert!(matches!(result, Err(WatchdogError::WatchdogLocked)));
768 }
769
770 #[test]
771 fn test_watchdog_reset_counter_on_success() {
772 let watchdog = create_test_watchdog();
773 let harmful_action = Action::delete("/etc/passwd");
774 let safe_action = Action::read("readme.txt");
775
776 for _ in 0..5 {
778 let _ = watchdog.verify_action(&harmful_action);
779 }
780 assert_eq!(watchdog.violation_count(), 5);
781
782 let result = watchdog.verify_action(&safe_action);
784 assert!(result.is_ok());
785 assert!(result.unwrap().is_none());
786 assert_eq!(watchdog.violation_count(), 0);
787 }
788
789 #[test]
790 fn test_hard_reset_signal() {
791 let key_store = SoftwareKeyStore::generate().unwrap();
792 let action = Action::delete("system32");
793 let denial = DenialProof::new(
794 &action,
795 "Do no harm".to_string(),
796 "Critical violation".to_string(),
797 10,
798 );
799
800 let mut signal = HardResetSignal::new(denial, "capsule_hash".to_string());
801 assert_eq!(signal.total_violations, 10);
802
803 signal.sign(&key_store).unwrap();
804 assert_eq!(signal.signature.len(), 64);
805 }
806
807 #[test]
808 fn test_watchdog_acknowledge_reset() {
809 let watchdog = create_test_watchdog();
810
811 watchdog.counter.lock();
813 assert!(watchdog.is_locked());
814
815 watchdog.acknowledge_reset();
817 assert!(!watchdog.is_locked());
818 assert_eq!(watchdog.violation_count(), 0);
819 }
820
821 #[test]
822 fn test_privacy_rule_blocks_sensitive_read() {
823 let watchdog = create_test_watchdog();
824 let action = Action::read("/home/user/.ssh/id_rsa");
825
826 let result = watchdog.verify_action(&action);
827 assert!(result.is_ok());
828
829 let denial = result.unwrap();
830 assert!(denial.is_some());
831
832 let proof = denial.unwrap();
833 assert!(proof.violated_rule.contains("privacy"));
834 }
835
836 #[test]
837 fn test_dangerous_command_blocked() {
838 let watchdog = create_test_watchdog();
839 let action = Action::execute("rm -rf /");
840
841 let result = watchdog.verify_action(&action);
842 assert!(result.is_ok());
843
844 let denial = result.unwrap();
845 assert!(denial.is_some());
846 }
847}