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