Skip to main content

cc_teec/teec/
ca_auth.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright (C) 2025-2026 KylinSoft Co., Ltd. <https://www.kylinos.cn/>
3// See LICENSES for license details.
4
5//! TEE CA 认证模块
6//!
7//! 提供 CA(Client Application)身份认证功能,用于 TA 的 ACL 访问控制。
8//!
9//! ## 主要功能
10//!
11//! - **CA 身份标识**: 基于调用者路径生成 UUID v5
12//! - **签名验证**: 使用 tasign 验证 ELF 文件签名
13//! - **结果缓存**: 基于 (PID, inode, mtime, dev) 的复合键缓存验签结果
14//!
15//! ## CA 验签缓存
16//!
17//! 使用 DashMap 实现无锁并发访问,缓存键为 (进程ID, CA文件标识)。
18//!
19//! CA 文件标识基于 inode、mtime 和 dev,可检测:
20//! - 文件被替换(inode 变化)
21//! - 文件内容修改(mtime 变化)
22//! - 不同设备上的同名文件(dev 区分)
23//!
24//! 缓存策略:
25//! - 同一进程内,CA 文件未改变时复用验签结果
26//! - 进程 fork 后,若执行文件被替换则重新验签
27//! - 不同进程的 CA 文件各自独立缓存
28//!
29//! ## TA 访问控制
30//!
31//! TA 使用 `CaAuthInfo` 进行 ACL 决策,只关心:
32//! - `ca_uuid`: 哪个 CA 发起的请求
33//! - `verified`: 验签是否通过(包含签名验证和证书链验证)
34
35use 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/// 缓存键:(进程ID, CA文件标识)
43#[derive(Debug, Clone, Hash, PartialEq, Eq)]
44struct CacheKey {
45    pid: i32,
46    ca_file_id: CaFileId,
47}
48
49/// CA 文件唯一标识(基于 inode + mtime + dev)
50#[derive(Debug, Clone, Hash, PartialEq, Eq)]
51struct CaFileId {
52    inode: u64,      // inode 号
53    mtime: i64,      // 修改时间(秒)
54    mtime_nsec: i64, // 修改时间(纳秒部分)
55    dev: u64,        // 设备号
56}
57
58/// 获取 CA 文件的唯一标识(读取文件元数据)
59fn 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
71/// 全局缓存:使用 DashMap 提供无锁并发访问
72/// key为(PID, CA文件标识),value为CA认证信息
73static CA_AUTH_CACHE: LazyLock<DashMap<CacheKey, CaAuthInfo>> = LazyLock::new(DashMap::new);
74
75/// 获取或执行 CA 认证(带缓存)
76///
77/// 返回 CA 认证信息,用于 TA 的 ACL 访问控制。
78///
79/// 缓存键基于进程 ID 和 CA 文件标识,可检测:
80/// - 同一进程内文件未改变时复用结果
81/// - 文件被替换(inode/mtime 变化)时重新验签
82/// - 不同进程的 CA 文件各自独立缓存
83///
84/// # 返回
85///
86/// 返回 `CaAuthInfo`,包含:
87/// - `ca_uuid`: CA 的唯一标识(基于调用者路径生成的 UUID v5)
88/// - `verified`: 验签是否通过(包含签名验证和证书链验证)
89pub fn get_or_verify_ca() -> CaAuthInfo {
90    // 获取当前进程 ID
91    let pid = std::process::id() as i32;
92
93    // 获取调用者可执行文件路径
94    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    // 生成 CA UUID(基于路径)
100    let ca_uuid = path_to_uuid(&ca_path);
101
102    // 获取 CA 文件标识
103    let ca_file_id = match get_ca_file_id(&ca_path) {
104        Some(id) => id,
105        None => {
106            // 无法获取文件元数据,跳过缓存直接验签
107            warn!("无法获取CA文件元数据: {}", ca_path);
108            return perform_ca_auth_internal(&ca_uuid, &ca_path);
109        }
110    };
111
112    // 构建缓存键
113    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    // 缓存未命中,执行验签
124    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
132/// 清除所有缓存(主要用于测试)
133pub fn clear_cache() {
134    CA_AUTH_CACHE.clear();
135}
136
137/// 内部认证函数:调用 tasign 库进行签名验证,生成 CaAuthInfo
138fn 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    // CA 验签缓存测试
183
184    #[test]
185    fn test_ca_file_id_uniqueness() {
186        // 测试 CaFileId 的比较逻辑
187        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不同
204            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}