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#[cfg(not(feature = "test-root-ca"))]
48const TA_SIGN_ROOT_CA_PEM: &[u8] = include_bytes!("../../certs/kylin-xtee-ca-sign-root.pem");
49
50fn load_root_ca_cert() -> &'static [u8] {
58 #[cfg(not(feature = "test-root-ca"))]
59 {
60 debug!("使用内置 CA 根证书");
61 return TA_SIGN_ROOT_CA_PEM;
62 }
63
64 #[cfg(feature = "test-root-ca")]
65 {
66 debug!("使用 tasign 内置测试根证书");
67 return tasign::cert::CA_CERT_PEM;
68 }
69}
70
71#[derive(Debug, Clone, Hash, PartialEq, Eq)]
73struct CacheKey {
74 pid: i32,
75 ca_file_id: CaFileId,
76}
77
78#[derive(Debug, Clone, Hash, PartialEq, Eq)]
80struct CaFileId {
81 inode: u64, mtime: i64, mtime_nsec: i64, dev: u64, }
86
87fn get_ca_file_id(path: &str) -> Option<CaFileId> {
89 match fs::metadata(path) {
90 Ok(metadata) => Some(CaFileId {
91 inode: metadata.ino(),
92 mtime: metadata.mtime(),
93 mtime_nsec: metadata.mtime_nsec(),
94 dev: metadata.dev(),
95 }),
96 Err(_) => None,
97 }
98}
99
100static CA_AUTH_CACHE: LazyLock<DashMap<CacheKey, CaAuthInfo>> = LazyLock::new(DashMap::new);
103
104pub fn get_or_verify_ca() -> CaAuthInfo {
119 let pid = std::process::id() as i32;
121
122 let ca_path = match std::fs::read_link("/proc/self/exe") {
124 Ok(path) => path.to_string_lossy().to_string(),
125 Err(_) => "<unknown>".to_string(),
126 };
127
128 let ca_uuid = path_to_uuid(&ca_path);
130
131 let ca_file_id = match get_ca_file_id(&ca_path) {
133 Some(id) => id,
134 None => {
135 warn!("无法获取CA文件元数据: {}", ca_path);
137 return perform_ca_auth_internal(ca_uuid, &ca_path);
138 }
139 };
140
141 let key = CacheKey { pid, ca_file_id };
143
144 if let Some(result) = CA_AUTH_CACHE.get(&key) {
145 debug!(
146 "CA认证缓存命中: pid={}, inode={}",
147 pid, key.ca_file_id.inode
148 );
149 return result.value().clone();
150 }
151
152 debug!("CA认证缓存未命中,执行验签: pid={}, path={}", pid, ca_path);
154 let result = perform_ca_auth_internal(ca_uuid, &ca_path);
155
156 CA_AUTH_CACHE.insert(key, result.clone());
157
158 result
159}
160
161pub fn clear_cache() {
163 CA_AUTH_CACHE.clear();
164}
165
166fn perform_ca_auth_internal(ca_uuid: String, ca_path: &str) -> CaAuthInfo {
168 if ca_path == "<unknown>" {
169 warn!("CA path unknown, skipping verification");
170 return CaAuthInfo {
171 ca_uuid,
172 verified: false,
173 };
174 }
175
176 debug!("开始验证 CA ELF 签名: {}", ca_path);
177
178 let elf_data = match fs::read(ca_path) {
179 Ok(data) => data,
180 Err(e) => {
181 warn!("无法读取ELF文件: {}", e);
182 return CaAuthInfo {
183 ca_uuid,
184 verified: false,
185 };
186 }
187 };
188
189 let ca_pem = load_root_ca_cert();
190 debug!("CA 根证书已加载(编译期嵌入)");
191
192 let verified = match tasign::verify_elf_signature(&elf_data, Some(ca_pem)) {
193 Ok(_) => {
194 debug!("CA ELF 签名验证成功(含证书链验证)");
195 true
196 }
197 Err(e) => {
198 warn!("签名验证失败: {}", e);
199 false
200 }
201 };
202
203 CaAuthInfo { ca_uuid, verified }
204}
205
206#[cfg(test)]
207mod tests {
208 use super::*;
209
210 #[test]
213 fn test_ca_file_id_uniqueness() {
214 let id1 = CaFileId {
216 inode: 12345,
217 mtime: 1000,
218 mtime_nsec: 0,
219 dev: 1,
220 };
221
222 let id2 = CaFileId {
223 inode: 12345,
224 mtime: 1000,
225 mtime_nsec: 0,
226 dev: 1,
227 };
228
229 let id3 = CaFileId {
230 inode: 12345,
231 mtime: 2000, mtime_nsec: 0,
233 dev: 1,
234 };
235
236 assert_eq!(id1, id2);
237 assert_ne!(id1, id3);
238 }
239
240 #[test]
241 fn test_clear_cache() {
242 let test_key = CacheKey {
243 pid: 99999,
244 ca_file_id: CaFileId {
245 inode: 12345,
246 mtime: 1000,
247 mtime_nsec: 0,
248 dev: 67890,
249 },
250 };
251
252 let test_info = CaAuthInfo {
253 ca_uuid: "test-uuid".to_string(),
254 verified: true,
255 };
256
257 CA_AUTH_CACHE.insert(test_key.clone(), test_info);
258
259 clear_cache();
260 assert!(
261 !CA_AUTH_CACHE.contains_key(&test_key),
262 "cache should be cleared"
263 );
264 }
265
266 #[test]
267 fn test_get_or_verify_ca_returns_info() {
268 clear_cache();
269 let info = get_or_verify_ca();
270 assert!(!info.ca_uuid.is_empty());
271 }
272
273 #[test]
274 fn test_get_or_verify_ca_caches_result() {
275 clear_cache();
276 let info1 = get_or_verify_ca();
277 let info2 = get_or_verify_ca();
278 assert_eq!(info1.ca_uuid, info2.ca_uuid);
279 assert_eq!(info1.verified, info2.verified);
280 clear_cache();
281 }
282
283 #[test]
284 fn test_perform_ca_auth_internal_unknown_path() {
285 let result = perform_ca_auth_internal("test-uuid".to_string(), "<unknown>");
286 assert_eq!(result.ca_uuid, "test-uuid");
287 assert!(!result.verified);
288 }
289
290 #[test]
291 fn test_perform_ca_auth_internal_nonexistent_path() {
292 let result =
293 perform_ca_auth_internal("test-uuid".to_string(), "/nonexistent/path/to/binary");
294 assert_eq!(result.ca_uuid, "test-uuid");
295 assert!(!result.verified);
296 }
297
298 #[test]
299 fn test_perform_ca_auth_internal_current_exe() {
300 let exe_path = std::fs::read_link("/proc/self/exe")
301 .unwrap()
302 .to_string_lossy()
303 .to_string();
304 let result = perform_ca_auth_internal("test-uuid".to_string(), &exe_path);
305 assert_eq!(result.ca_uuid, "test-uuid");
306 }
307
308 #[test]
309 fn test_get_ca_file_id_nonexistent() {
310 assert!(get_ca_file_id("/nonexistent/file").is_none());
311 }
312
313 #[test]
314 fn test_get_ca_file_id_current_exe() {
315 let exe_path = std::fs::read_link("/proc/self/exe")
316 .unwrap()
317 .to_string_lossy()
318 .to_string();
319 let file_id = get_ca_file_id(&exe_path);
320 assert!(file_id.is_some());
321 let id = file_id.unwrap();
322 assert!(id.inode > 0);
323 }
324}