ferrox_guards/
session_replay_guard.rs1use serde::{Deserialize, Serialize};
7use sha2::{Sha256, Digest};
8
9#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
11pub enum SessionStatus {
12 Valid,
13 HijackDetected { expected_fingerprint: String, actual_fingerprint: String },
14}
15
16#[derive(Debug, Clone, Serialize, Deserialize)]
18pub struct ClientFingerprint {
19 pub hash: String,
20 pub user_agent: String,
21}
22
23impl ClientFingerprint {
24 pub fn new(user_agent: &str, accept_language: &str, accept_encoding: &str) -> Self {
25 let mut hasher = Sha256::new();
26 hasher.update(user_agent.as_bytes());
27 hasher.update(b"|");
28 hasher.update(accept_language.as_bytes());
29 hasher.update(b"|");
30 hasher.update(accept_encoding.as_bytes());
31 let hash = format!("{:x}", hasher.finalize());
32
33 Self {
34 hash,
35 user_agent: user_agent.to_string(),
36 }
37 }
38}
39
40pub struct SessionReplayDetector;
42
43impl SessionReplayDetector {
44 pub fn evaluate_session(
45 bound_fingerprint_hash: &str,
46 current_fingerprint: &ClientFingerprint,
47 ) -> SessionStatus {
48 if bound_fingerprint_hash == current_fingerprint.hash {
49 SessionStatus::Valid
50 } else {
51 SessionStatus::HijackDetected {
52 expected_fingerprint: bound_fingerprint_hash.to_string(),
53 actual_fingerprint: current_fingerprint.hash.clone(),
54 }
55 }
56 }
57}
58
59#[cfg(test)]
60mod tests {
61 use super::*;
62
63 #[test]
64 fn test_session_replay_detector() {
65 let fp1 = ClientFingerprint::new("Mozilla/5.0 (Windows NT 10.0)", "it-IT,it;q=0.9", "gzip, deflate");
66 let fp2 = ClientFingerprint::new("Python-urllib/3.9", "en-US", "gzip");
67
68 let res_valid = SessionReplayDetector::evaluate_session(&fp1.hash, &fp1);
70 assert_eq!(res_valid, SessionStatus::Valid);
71
72 let res_hijack = SessionReplayDetector::evaluate_session(&fp1.hash, &fp2);
74 match res_hijack {
75 SessionStatus::HijackDetected { .. } => {}
76 _ => panic!("Expected HijackDetected"),
77 }
78 }
79}