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