1use hmac::{Hmac, Mac};
2use serde::{Deserialize, Serialize};
3use sha2::Sha256;
4use std::fs::{self, OpenOptions};
5use std::io::{BufRead, BufReader, Write};
6use std::path::Path;
7
8use crate::error::{AuthyError, Result};
9use crate::types::*;
10
11type HmacSha256 = Hmac<Sha256>;
12
13#[derive(Debug, Clone, Serialize, Deserialize)]
15pub struct AuditEntry {
16 pub timestamp: DateTime<Utc>,
17 pub operation: String,
18 pub secret: Option<String>,
19 pub actor: String,
20 pub outcome: String,
21 pub detail: Option<String>,
22 pub chain_hmac: String,
23}
24
25pub fn log_event(
27 audit_path: &Path,
28 operation: &str,
29 secret: Option<&str>,
30 actor: &str,
31 outcome: &str,
32 detail: Option<&str>,
33 hmac_key: &[u8],
34) -> Result<()> {
35 let prev_hmac = read_last_hmac(audit_path);
36
37 let entry = AuditEntry {
38 timestamp: Utc::now(),
39 operation: operation.to_string(),
40 secret: secret.map(|s| s.to_string()),
41 actor: actor.to_string(),
42 outcome: outcome.to_string(),
43 detail: detail.map(|s| s.to_string()),
44 chain_hmac: String::new(), };
46
47 let chain_data = format!(
49 "{}|{}|{}|{:?}|{}|{}|{:?}",
50 prev_hmac,
51 entry.timestamp.to_rfc3339(),
52 entry.operation,
53 entry.secret,
54 entry.actor,
55 entry.outcome,
56 entry.detail,
57 );
58
59 let chain_hmac = compute_chain_hmac(&chain_data, hmac_key);
60
61 let final_entry = AuditEntry {
62 chain_hmac,
63 ..entry
64 };
65
66 let json_line =
67 serde_json::to_string(&final_entry).map_err(|e| AuthyError::Serialization(e.to_string()))?;
68
69 if let Some(dir) = audit_path.parent() {
70 fs::create_dir_all(dir)?;
71 }
72
73 let mut file = OpenOptions::new()
74 .create(true)
75 .append(true)
76 .open(audit_path)?;
77 writeln!(file, "{}", json_line)?;
78
79 Ok(())
80}
81
82pub fn read_entries(audit_path: &Path) -> Result<Vec<AuditEntry>> {
84 if !audit_path.exists() {
85 return Ok(Vec::new());
86 }
87
88 let file = fs::File::open(audit_path)?;
89 let reader = BufReader::new(file);
90 let mut entries = Vec::new();
91
92 for line in reader.lines() {
93 let line = line?;
94 if line.trim().is_empty() {
95 continue;
96 }
97 let entry: AuditEntry =
98 serde_json::from_str(&line).map_err(|e| AuthyError::Serialization(e.to_string()))?;
99 entries.push(entry);
100 }
101
102 Ok(entries)
103}
104
105pub fn verify_chain(audit_path: &Path, hmac_key: &[u8]) -> Result<(usize, bool)> {
107 let entries = read_entries(audit_path)?;
108 let mut prev_hmac = String::new();
109
110 for (i, entry) in entries.iter().enumerate() {
111 let chain_data = format!(
112 "{}|{}|{}|{:?}|{}|{}|{:?}",
113 prev_hmac,
114 entry.timestamp.to_rfc3339(),
115 entry.operation,
116 entry.secret,
117 entry.actor,
118 entry.outcome,
119 entry.detail,
120 );
121
122 let expected_hmac = compute_chain_hmac(&chain_data, hmac_key);
123 if expected_hmac != entry.chain_hmac {
124 return Err(AuthyError::AuditChainBroken(i));
125 }
126 prev_hmac = entry.chain_hmac.clone();
127 }
128
129 Ok((entries.len(), true))
130}
131
132fn read_last_hmac(audit_path: &Path) -> String {
133 if !audit_path.exists() {
134 return String::new();
135 }
136
137 if let Ok(content) = fs::read_to_string(audit_path) {
139 for line in content.lines().rev() {
140 if !line.trim().is_empty() {
141 if let Ok(entry) = serde_json::from_str::<AuditEntry>(line) {
142 return entry.chain_hmac;
143 }
144 }
145 }
146 }
147
148 String::new()
149}
150
151fn compute_chain_hmac(data: &str, hmac_key: &[u8]) -> String {
152 let mut mac = HmacSha256::new_from_slice(hmac_key).expect("HMAC can take key of any size");
153 mac.update(data.as_bytes());
154 hex::encode(mac.finalize().into_bytes())
155}
156
157pub fn derive_audit_key(master_material: &[u8]) -> Vec<u8> {
159 crate::vault::crypto::derive_key(master_material, b"audit-hmac", 32)
160}
161
162pub fn key_material(key: &crate::vault::VaultKey) -> Vec<u8> {
164 match key {
165 crate::vault::VaultKey::Passphrase(p) => p.as_bytes().to_vec(),
166 crate::vault::VaultKey::Keyfile { identity, .. } => identity.as_bytes().to_vec(),
167 }
168}