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/// TA 签名根证书(生产环境)。
43///
44/// 使用麒麟软件生产根证书(Kylin Software Root Cert),
45/// 与 x-kernel 当前使用的证书不同,x-kernel 需要替换为此生产证书。
46/// 默认启用;当 `test-root-ca` feature 启用时改用 tasign 内置测试根证书。
47#[cfg(not(feature = "test-root-ca"))]
48const TA_SIGN_ROOT_CA_PEM: &[u8] = include_bytes!("../../certs/kylin-xtee-ca-sign-root.pem");
49
50/// 加载 CA 根证书 PEM 字节,用于证书链验证。
51///
52/// 编译期通过 feature flag 决定使用哪个根证书:
53/// - 默认(未启用 `test-root-ca`):嵌入麒麟软件生产根证书
54///   (`certs/kylin-xtee-ca-sign-root.pem`,x-kernel 需同步替换为此证书);
55/// - 启用 `test-root-ca`:tasign 内置测试根证书(`tasign::cert::CA_CERT_PEM`),
56///   以支持 TEST 签名 ELF 的完整证书链验证。
57fn 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/// 缓存键:(进程ID, CA文件标识)
72#[derive(Debug, Clone, Hash, PartialEq, Eq)]
73struct CacheKey {
74    pid: i32,
75    ca_file_id: CaFileId,
76}
77
78/// CA 文件唯一标识(基于 inode + mtime + dev)
79#[derive(Debug, Clone, Hash, PartialEq, Eq)]
80struct CaFileId {
81    inode: u64,      // inode 号
82    mtime: i64,      // 修改时间(秒)
83    mtime_nsec: i64, // 修改时间(纳秒部分)
84    dev: u64,        // 设备号
85}
86
87/// 获取 CA 文件的唯一标识(读取文件元数据)
88fn 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
100/// 全局缓存:使用 DashMap 提供无锁并发访问
101/// key为(PID, CA文件标识),value为CA认证信息
102static CA_AUTH_CACHE: LazyLock<DashMap<CacheKey, CaAuthInfo>> = LazyLock::new(DashMap::new);
103
104/// 获取或执行 CA 认证(带缓存)
105///
106/// 返回 CA 认证信息,用于 TA 的 ACL 访问控制。
107///
108/// 缓存键基于进程 ID 和 CA 文件标识,可检测:
109/// - 同一进程内文件未改变时复用结果
110/// - 文件被替换(inode/mtime 变化)时重新验签
111/// - 不同进程的 CA 文件各自独立缓存
112///
113/// # 返回
114///
115/// 返回 `CaAuthInfo`,包含:
116/// - `ca_uuid`: CA 的唯一标识(基于调用者路径生成的 UUID v5)
117/// - `verified`: 验签是否通过(包含签名验证和证书链验证)
118pub fn get_or_verify_ca() -> CaAuthInfo {
119    // 获取当前进程 ID
120    let pid = std::process::id() as i32;
121
122    // 获取调用者可执行文件路径
123    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    // 生成 CA UUID(基于路径)
129    let ca_uuid = path_to_uuid(&ca_path);
130
131    // 获取 CA 文件标识
132    let ca_file_id = match get_ca_file_id(&ca_path) {
133        Some(id) => id,
134        None => {
135            // 无法获取文件元数据,跳过缓存直接验签
136            warn!("无法获取CA文件元数据: {}", ca_path);
137            return perform_ca_auth_internal(ca_uuid, &ca_path);
138        }
139    };
140
141    // 构建缓存键
142    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    // 缓存未命中,执行验签
153    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
161/// 清除所有缓存(主要用于测试)
162pub fn clear_cache() {
163    CA_AUTH_CACHE.clear();
164}
165
166/// 内部认证函数:调用 tasign 库进行签名验证,生成 CaAuthInfo
167fn 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    // CA 验签缓存测试
211
212    #[test]
213    fn test_ca_file_id_uniqueness() {
214        // 测试 CaFileId 的比较逻辑
215        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不同
232            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}