1use std::{fs, os::unix::fs::MetadataExt, sync::LazyLock};
36
37use dashmap::DashMap;
38use log::{debug, warn};
39
40use teec_protocol::{CaAuthInfo, path_to_uuid};
41
42#[derive(Debug, Clone, Hash, PartialEq, Eq)]
44struct CacheKey {
45 pid: i32,
46 ca_file_id: CaFileId,
47}
48
49#[derive(Debug, Clone, Hash, PartialEq, Eq)]
51struct CaFileId {
52 inode: u64, mtime: i64, mtime_nsec: i64, dev: u64, }
57
58fn get_ca_file_id(path: &str) -> Option<CaFileId> {
60 match std::fs::metadata(path) {
61 Ok(metadata) => Some(CaFileId {
62 inode: metadata.ino(),
63 mtime: metadata.mtime(),
64 mtime_nsec: metadata.mtime_nsec(),
65 dev: metadata.dev(),
66 }),
67 Err(_) => None,
68 }
69}
70
71static CA_AUTH_CACHE: LazyLock<DashMap<CacheKey, CaAuthInfo>> = LazyLock::new(DashMap::new);
74
75pub fn get_or_verify_ca() -> CaAuthInfo {
90 let pid = std::process::id() as i32;
92
93 let ca_path = match std::fs::read_link("/proc/self/exe") {
95 Ok(path) => path.to_string_lossy().to_string(),
96 Err(_) => "<unknown>".to_string(),
97 };
98
99 let ca_uuid = path_to_uuid(&ca_path);
101
102 let ca_file_id = match get_ca_file_id(&ca_path) {
104 Some(id) => id,
105 None => {
106 warn!("无法获取CA文件元数据: {}", ca_path);
108 return perform_ca_auth_internal(&ca_uuid, &ca_path);
109 }
110 };
111
112 let key = CacheKey { pid, ca_file_id };
114
115 if let Some(result) = CA_AUTH_CACHE.get(&key) {
116 debug!(
117 "CA认证缓存命中: pid={}, inode={}",
118 pid, key.ca_file_id.inode
119 );
120 return result.value().clone();
121 }
122
123 debug!("CA认证缓存未命中,执行验签: pid={}, path={}", pid, ca_path);
125 let result = perform_ca_auth_internal(&ca_uuid, &ca_path);
126
127 CA_AUTH_CACHE.insert(key, result.clone());
128
129 result
130}
131
132pub fn clear_cache() {
134 CA_AUTH_CACHE.clear();
135}
136
137fn perform_ca_auth_internal(ca_uuid: &str, ca_path: &str) -> CaAuthInfo {
139 if ca_path == "<unknown>" {
140 warn!("CA path unknown, skipping verification");
141 return CaAuthInfo {
142 ca_uuid: ca_uuid.to_string(),
143 verified: false,
144 };
145 }
146
147 debug!("开始验证 CA ELF 签名: {}", ca_path);
148
149 let elf_data = match fs::read(ca_path) {
150 Ok(data) => data,
151 Err(e) => {
152 warn!("无法读取ELF文件: {}", e);
153 return CaAuthInfo {
154 ca_uuid: ca_uuid.to_string(),
155 verified: false,
156 };
157 }
158 };
159
160 match tasign::verify_elf_signature(&elf_data, None) {
161 Ok(_) => {
162 debug!("CA ELF 签名验证成功");
163 CaAuthInfo {
164 ca_uuid: ca_uuid.to_string(),
165 verified: true,
166 }
167 }
168 Err(e) => {
169 warn!("签名验证失败: {}", e);
170 CaAuthInfo {
171 ca_uuid: ca_uuid.to_string(),
172 verified: false,
173 }
174 }
175 }
176}
177
178#[cfg(test)]
179mod tests {
180 use super::*;
181
182 #[test]
185 fn test_ca_file_id_uniqueness() {
186 let id1 = CaFileId {
188 inode: 12345,
189 mtime: 1000,
190 mtime_nsec: 0,
191 dev: 1,
192 };
193
194 let id2 = CaFileId {
195 inode: 12345,
196 mtime: 1000,
197 mtime_nsec: 0,
198 dev: 1,
199 };
200
201 let id3 = CaFileId {
202 inode: 12345,
203 mtime: 2000, mtime_nsec: 0,
205 dev: 1,
206 };
207
208 assert_eq!(id1, id2);
209 assert_ne!(id1, id3);
210 }
211
212 #[test]
213 fn test_clear_cache() {
214 let test_key = CacheKey {
215 pid: 99999,
216 ca_file_id: CaFileId {
217 inode: 12345,
218 mtime: 1000,
219 mtime_nsec: 0,
220 dev: 67890,
221 },
222 };
223
224 let test_info = CaAuthInfo {
225 ca_uuid: "test-uuid".to_string(),
226 verified: true,
227 };
228
229 CA_AUTH_CACHE.insert(test_key.clone(), test_info);
230
231 clear_cache();
232 assert!(
233 !CA_AUTH_CACHE.contains_key(&test_key),
234 "cache should be cleared"
235 );
236 }
237
238 #[test]
239 fn test_get_or_verify_ca_returns_info() {
240 clear_cache();
241 let info = get_or_verify_ca();
242 assert!(!info.ca_uuid.is_empty());
243 }
244
245 #[test]
246 fn test_get_or_verify_ca_caches_result() {
247 clear_cache();
248 let info1 = get_or_verify_ca();
249 let info2 = get_or_verify_ca();
250 assert_eq!(info1.ca_uuid, info2.ca_uuid);
251 assert_eq!(info1.verified, info2.verified);
252 clear_cache();
253 }
254
255 #[test]
256 fn test_perform_ca_auth_internal_unknown_path() {
257 let result = perform_ca_auth_internal("test-uuid", "<unknown>");
258 assert_eq!(result.ca_uuid, "test-uuid");
259 assert!(!result.verified);
260 }
261
262 #[test]
263 fn test_perform_ca_auth_internal_nonexistent_path() {
264 let result = perform_ca_auth_internal("test-uuid", "/nonexistent/path/to/binary");
265 assert_eq!(result.ca_uuid, "test-uuid");
266 assert!(!result.verified);
267 }
268
269 #[test]
270 fn test_perform_ca_auth_internal_current_exe() {
271 let exe_path = std::fs::read_link("/proc/self/exe")
272 .unwrap()
273 .to_string_lossy()
274 .to_string();
275 let result = perform_ca_auth_internal("test-uuid", &exe_path);
276 assert_eq!(result.ca_uuid, "test-uuid");
277 }
278
279 #[test]
280 fn test_get_ca_file_id_nonexistent() {
281 assert!(get_ca_file_id("/nonexistent/file").is_none());
282 }
283
284 #[test]
285 fn test_get_ca_file_id_current_exe() {
286 let exe_path = std::fs::read_link("/proc/self/exe")
287 .unwrap()
288 .to_string_lossy()
289 .to_string();
290 let file_id = get_ca_file_id(&exe_path);
291 assert!(file_id.is_some());
292 let id = file_id.unwrap();
293 assert!(id.inode > 0);
294 }
295}