use std::{fs, os::unix::fs::MetadataExt, sync::LazyLock};
use dashmap::DashMap;
use log::{debug, warn};
use teec_protocol::{CaAuthInfo, path_to_uuid};
#[cfg(not(feature = "test-root-ca"))]
const TA_SIGN_ROOT_CA_PEM: &[u8] = include_bytes!("../../certs/kylin-xtee-ca-sign-root.pem");
fn load_root_ca_cert() -> &'static [u8] {
#[cfg(not(feature = "test-root-ca"))]
{
debug!("使用内置 CA 根证书");
TA_SIGN_ROOT_CA_PEM
}
#[cfg(feature = "test-root-ca")]
{
debug!("使用 tasign 内置测试根证书");
tasign::cert::CA_CERT_PEM
}
}
#[derive(Debug, Clone, Hash, PartialEq, Eq)]
struct CacheKey {
pid: i32,
ca_file_id: CaFileId,
}
#[derive(Debug, Clone, Hash, PartialEq, Eq)]
struct CaFileId {
inode: u64, mtime: i64, mtime_nsec: i64, dev: u64, }
fn get_ca_file_id_from_file(file: &fs::File) -> Option<CaFileId> {
match file.metadata() {
Ok(metadata) => Some(CaFileId {
inode: metadata.ino(),
mtime: metadata.mtime(),
mtime_nsec: metadata.mtime_nsec(),
dev: metadata.dev(),
}),
Err(_) => None,
}
}
static CA_AUTH_CACHE: LazyLock<DashMap<CacheKey, CaAuthInfo>> = LazyLock::new(DashMap::new);
pub fn get_or_verify_ca() -> CaAuthInfo {
let pid = std::process::id() as i32;
let ca_path = match std::fs::read_link("/proc/self/exe") {
Ok(path) => path.to_string_lossy().to_string(),
Err(_) => "<unknown>".to_string(),
};
let ca_uuid = path_to_uuid(&ca_path);
if ca_path == "<unknown>" {
warn!("CA path unknown, skipping verification");
return CaAuthInfo {
ca_uuid,
verified: false,
};
}
let mut file = match fs::File::open(&ca_path) {
Ok(f) => f,
Err(e) => {
warn!("无法打开CA文件 {}: {}", ca_path, e);
return CaAuthInfo {
ca_uuid,
verified: false,
};
}
};
let ca_file_id = match get_ca_file_id_from_file(&file) {
Some(id) => id,
None => {
warn!("无法获取CA文件元数据: {}", ca_path);
return CaAuthInfo {
ca_uuid,
verified: false,
};
}
};
let key = CacheKey { pid, ca_file_id };
if let Some(result) = CA_AUTH_CACHE.get(&key) {
debug!(
"CA认证缓存命中: pid={}, inode={}",
pid, key.ca_file_id.inode
);
return result.value().clone();
}
debug!("CA认证缓存未命中,执行验签: pid={}, path={}", pid, ca_path);
let result = perform_ca_auth_internal(ca_uuid, &mut file);
CA_AUTH_CACHE.insert(key, result.clone());
result
}
pub fn clear_cache() {
CA_AUTH_CACHE.clear();
}
fn perform_ca_auth_internal(ca_uuid: String, file: &mut fs::File) -> CaAuthInfo {
debug!("开始验证 CA ELF 签名");
let mut elf_data = Vec::new();
if let Err(e) = std::io::Read::read_to_end(file, &mut elf_data) {
warn!("无法读取ELF文件: {}", e);
return CaAuthInfo {
ca_uuid,
verified: false,
};
}
let ca_pem = load_root_ca_cert();
debug!("CA 根证书已加载(编译期嵌入)");
let verified = match tasign::verify_elf_signature(&elf_data, Some(ca_pem)) {
Ok(_) => {
debug!("CA ELF 签名验证成功(含证书链验证)");
true
}
Err(e) => {
warn!("签名验证失败: {}", e);
false
}
};
CaAuthInfo { ca_uuid, verified }
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_ca_file_id_uniqueness() {
let id1 = CaFileId {
inode: 12345,
mtime: 1000,
mtime_nsec: 0,
dev: 1,
};
let id2 = CaFileId {
inode: 12345,
mtime: 1000,
mtime_nsec: 0,
dev: 1,
};
let id3 = CaFileId {
inode: 12345,
mtime: 2000, mtime_nsec: 0,
dev: 1,
};
assert_eq!(id1, id2);
assert_ne!(id1, id3);
}
#[test]
fn test_clear_cache() {
let test_key = CacheKey {
pid: 99999,
ca_file_id: CaFileId {
inode: 12345,
mtime: 1000,
mtime_nsec: 0,
dev: 67890,
},
};
let test_info = CaAuthInfo {
ca_uuid: "test-uuid".to_string(),
verified: true,
};
CA_AUTH_CACHE.insert(test_key.clone(), test_info);
clear_cache();
assert!(
!CA_AUTH_CACHE.contains_key(&test_key),
"cache should be cleared"
);
}
#[test]
fn test_get_or_verify_ca_returns_info() {
clear_cache();
let info = get_or_verify_ca();
assert!(!info.ca_uuid.is_empty());
}
#[test]
fn test_get_or_verify_ca_caches_result() {
clear_cache();
let info1 = get_or_verify_ca();
let info2 = get_or_verify_ca();
assert_eq!(info1.ca_uuid, info2.ca_uuid);
assert_eq!(info1.verified, info2.verified);
clear_cache();
}
#[test]
fn test_perform_ca_auth_internal_current_exe() {
let exe_path = std::fs::read_link("/proc/self/exe")
.unwrap()
.to_string_lossy()
.to_string();
let mut file = fs::File::open(&exe_path).unwrap();
let result = perform_ca_auth_internal("test-uuid".to_string(), &mut file);
assert_eq!(result.ca_uuid, "test-uuid");
}
#[test]
fn test_perform_ca_auth_internal_empty_content_unverified() {
let dir = std::env::temp_dir();
let path = dir.join("rust-libteec-ca-auth-empty-test.bin");
std::fs::write(&path, b"").unwrap();
let mut file = fs::File::open(&path).unwrap();
let result = perform_ca_auth_internal("test-uuid".to_string(), &mut file);
assert_eq!(result.ca_uuid, "test-uuid");
assert!(!result.verified);
std::fs::remove_file(&path).unwrap();
}
#[test]
fn test_get_ca_file_id_from_file_current_exe() {
let exe_path = std::fs::read_link("/proc/self/exe")
.unwrap()
.to_string_lossy()
.to_string();
let file = fs::File::open(&exe_path).unwrap();
let file_id = get_ca_file_id_from_file(&file);
assert!(file_id.is_some());
let id = file_id.unwrap();
assert!(id.inode > 0);
}
#[test]
fn test_cache_key_and_content_from_same_fd() {
let dir = std::env::temp_dir();
let path = dir.join("rust-libteec-ca-auth-toctou-test.bin");
let new_path = dir.join("rust-libteec-ca-auth-toctou-test-new.bin");
std::fs::write(&path, b"original").unwrap();
let mut file = fs::File::open(&path).unwrap();
let id1 = get_ca_file_id_from_file(&file).unwrap();
std::fs::write(&new_path, b"replaced").unwrap();
std::fs::rename(&new_path, &path).unwrap();
let id2 = get_ca_file_id_from_file(&file).unwrap();
assert_eq!(id1, id2, "fd 元数据必须与打开时的文件实例一致");
let mut data = Vec::new();
std::io::Read::read_to_end(&mut file, &mut data).unwrap();
assert_eq!(data, b"original", "fd 内容必须来自打开时的文件实例");
std::fs::remove_file(&path).unwrap();
}
}