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::{
36    fs,
37    os::unix::fs::MetadataExt,
38    sync::{Arc, LazyLock},
39};
40
41use dashmap::DashMap;
42use log::{debug, warn};
43use teec_protocol::{path_to_uuid, CaAuthInfo};
44
45/// 缓存键:(进程ID, CA文件标识)
46#[derive(Debug, Clone, Hash, PartialEq, Eq)]
47struct CacheKey {
48    pid: i32,
49    ca_file_id: CaFileId,
50}
51
52/// CA 文件唯一标识(基于 inode + mtime + dev)
53#[derive(Debug, Clone, Hash, PartialEq, Eq)]
54struct CaFileId {
55    inode: u64,      // inode 号
56    mtime: i64,      // 修改时间(秒)
57    mtime_nsec: i64, // 修改时间(纳秒部分)
58    dev: u64,        // 设备号
59}
60
61/// 获取 CA 文件的唯一标识(读取文件元数据)
62fn get_ca_file_id(path: &str) -> Option<CaFileId> {
63    match std::fs::metadata(path) {
64        Ok(metadata) => Some(CaFileId {
65            inode: metadata.ino(),
66            mtime: metadata.mtime(),
67            mtime_nsec: metadata.mtime_nsec(),
68            dev: metadata.dev(),
69        }),
70        Err(_) => None,
71    }
72}
73
74/// 全局缓存:使用 DashMap 提供无锁并发访问
75/// key为(PID, CA文件标识),value为CA认证信息(用Arc包裹避免克隆)
76static CA_AUTH_CACHE: LazyLock<DashMap<CacheKey, Arc<CaAuthInfo>>> = LazyLock::new(DashMap::new);
77
78/// 获取或执行 CA 认证(带缓存)
79///
80/// 返回 CA 认证信息,用于 TA 的 ACL 访问控制。
81///
82/// 缓存键基于进程 ID 和 CA 文件标识,可检测:
83/// - 同一进程内文件未改变时复用结果
84/// - 文件被替换(inode/mtime 变化)时重新验签
85/// - 不同进程的 CA 文件各自独立缓存
86///
87/// # 返回
88///
89/// 返回 `CaAuthInfo`,包含:
90/// - `ca_uuid`: CA 的唯一标识(基于调用者路径生成的 UUID v5)
91/// - `verified`: 验签是否通过(包含签名验证和证书链验证)
92pub fn get_or_verify_ca() -> CaAuthInfo {
93    // 获取当前进程 ID
94    // SAFETY: getpid() 是标准的 libc 函数,总是成功且无副作用
95    let pid = unsafe { libc::getpid() };
96
97    // 获取调用者可执行文件路径
98    let ca_path = match std::fs::read_link("/proc/self/exe") {
99        Ok(path) => path.to_string_lossy().to_string(),
100        Err(_) => "<unknown>".to_string(),
101    };
102
103    // 生成 CA UUID(基于路径)
104    let ca_uuid = path_to_uuid(&ca_path);
105
106    // 获取 CA 文件标识
107    let ca_file_id = match get_ca_file_id(&ca_path) {
108        Some(id) => id,
109        None => {
110            // 无法获取文件元数据,跳过缓存直接验签
111            warn!("无法获取CA文件元数据: {}", ca_path);
112            return perform_ca_auth_internal(&ca_uuid);
113        }
114    };
115
116    // 构建缓存键
117    let key = CacheKey {
118        pid,
119        ca_file_id: ca_file_id.clone(),
120    };
121
122    // 尝试从缓存获取
123    if let Some(result) = CA_AUTH_CACHE.get(&key) {
124        debug!("CA认证缓存命中: pid={}, inode={}", pid, ca_file_id.inode);
125        // 使用 Arc 避免克隆,直接返回引用克隆
126        return (**result.value()).clone();
127    }
128
129    // 缓存未命中,执行验签
130    debug!("CA认证缓存未命中,执行验签: pid={}, path={}", pid, ca_path);
131    let result = perform_ca_auth_internal(&ca_uuid);
132
133    // 存入缓存(Arc 包裹以减少克隆开销)
134    CA_AUTH_CACHE.insert(key, Arc::new(result.clone()));
135
136    result
137}
138
139/// 清除所有缓存(主要用于测试)
140pub fn clear_cache() {
141    CA_AUTH_CACHE.clear();
142}
143
144/// 内部认证函数:调用 tasign 库进行签名验证,生成 CaAuthInfo
145fn perform_ca_auth_internal(ca_uuid: &str) -> CaAuthInfo {
146    let caller_path = match fs::read_link("/proc/self/exe") {
147        Ok(path) => path,
148        Err(e) => {
149            warn!("无法获取调用者可执行文件路径: {}", e);
150            return CaAuthInfo {
151                ca_uuid: ca_uuid.to_string(),
152                verified: false,
153            };
154        }
155    };
156
157    let path_str = caller_path.to_string_lossy().to_string();
158
159    debug!("开始验证 CA ELF 签名: {}", path_str);
160
161    let elf_data = match fs::read(&caller_path) {
162        Ok(data) => data,
163        Err(e) => {
164            warn!("无法读取ELF文件: {}", e);
165            return CaAuthInfo {
166                ca_uuid: ca_uuid.to_string(),
167                verified: false,
168            };
169        }
170    };
171
172    match tasign::verify_elf_signature(&elf_data, None) {
173        Ok(_) => {
174            debug!("CA ELF 签名验证成功");
175            CaAuthInfo {
176                ca_uuid: ca_uuid.to_string(),
177                verified: true,
178            }
179        }
180        Err(e) => {
181            warn!("签名验证失败: {}", e);
182            CaAuthInfo {
183                ca_uuid: ca_uuid.to_string(),
184                verified: false,
185            }
186        }
187    }
188}
189
190#[cfg(test)]
191mod tests {
192    use super::*;
193
194    // CA 验签缓存测试
195
196    #[test]
197    fn test_ca_file_id_uniqueness() {
198        // 测试 CaFileId 的比较逻辑
199        let id1 = CaFileId {
200            inode: 12345,
201            mtime: 1000,
202            mtime_nsec: 0,
203            dev: 1,
204        };
205
206        let id2 = CaFileId {
207            inode: 12345,
208            mtime: 1000,
209            mtime_nsec: 0,
210            dev: 1,
211        };
212
213        let id3 = CaFileId {
214            inode: 12345,
215            mtime: 2000, // mtime不同
216            mtime_nsec: 0,
217            dev: 1,
218        };
219
220        assert_eq!(id1, id2);
221        assert_ne!(id1, id3);
222    }
223
224    #[test]
225    fn test_get_ca_file_id_for_current_exe() {
226        // 测试获取当前执行文件的标识
227        let exe_path = "/proc/self/exe";
228        if let Ok(real_path) = fs::read_link(exe_path) {
229            let path_str = real_path.to_string_lossy();
230            let file_id = get_ca_file_id(&path_str);
231            assert!(file_id.is_some());
232
233            let id = file_id.unwrap();
234            assert!(id.inode > 0);
235            assert!(id.dev > 0);
236        }
237    }
238
239    #[test]
240    fn test_clear_cache() {
241        // 测试清除缓存功能
242
243        // 1. 先插入一些测试数据
244        let test_key = CacheKey {
245            pid: 99999, // 使用一个不会冲突的 PID
246            ca_file_id: CaFileId {
247                inode: 12345,
248                mtime: 1000,
249                mtime_nsec: 0,
250                dev: 67890,
251            },
252        };
253
254        let test_info = Arc::new(CaAuthInfo {
255            ca_uuid: "test-uuid".to_string(),
256            verified: true,
257        });
258
259        CA_AUTH_CACHE.insert(test_key.clone(), test_info);
260
261        // 2. 验证数据已插入
262        assert!(CA_AUTH_CACHE.contains_key(&test_key));
263        assert_eq!(CA_AUTH_CACHE.len(), 1);
264
265        // 3. 清除缓存
266        clear_cache();
267
268        // 4. 验证缓存已清空
269        assert!(!CA_AUTH_CACHE.contains_key(&test_key));
270        assert_eq!(CA_AUTH_CACHE.len(), 0);
271    }
272}