rust-libteec 0.4.1

Rust implementation of TEE Client API for secure communication with Trusted Applications.
Documentation
// SPDX-License-Identifier: Apache-2.0
// Copyright (C) 2025-2026 KylinSoft Co., Ltd. <https://www.kylinos.cn/>
// See LICENSES for license details.

//! TEE CA 认证模块
//!
//! 提供 CA(Client Application)身份认证功能,用于 TA 的 ACL 访问控制。
//!
//! ## 主要功能
//!
//! - **CA 身份标识**: 基于调用者路径生成 UUID v5
//! - **签名验证**: 使用 tasign 验证 ELF 文件签名
//! - **结果缓存**: 基于 (PID, inode, mtime, dev) 的复合键缓存验签结果
//!
//! ## CA 验签缓存
//!
//! 使用 DashMap 实现无锁并发访问,缓存键为 (进程ID, CA文件标识)。
//!
//! CA 文件标识基于 inode、mtime 和 dev,可检测:
//! - 文件被替换(inode 变化)
//! - 文件内容修改(mtime 变化)
//! - 不同设备上的同名文件(dev 区分)
//!
//! 缓存策略:
//! - 同一进程内,CA 文件未改变时复用验签结果
//! - 进程 fork 后,若执行文件被替换则重新验签
//! - 不同进程的 CA 文件各自独立缓存
//!
//! ## TA 访问控制
//!
//! TA 使用 `CaAuthInfo` 进行 ACL 决策,只关心:
//! - `ca_uuid`: 哪个 CA 发起的请求
//! - `verified`: 验签是否通过(包含签名验证和证书链验证)

use std::{
    fs,
    os::unix::fs::MetadataExt,
    sync::{Arc, LazyLock},
};

use dashmap::DashMap;
use log::{debug, warn};
use teec_protocol::{CaAuthInfo, path_to_uuid};

/// 缓存键:(进程ID, CA文件标识)
#[derive(Debug, Clone, Hash, PartialEq, Eq)]
struct CacheKey {
    pid: i32,
    ca_file_id: CaFileId,
}

/// CA 文件唯一标识(基于 inode + mtime + dev)
#[derive(Debug, Clone, Hash, PartialEq, Eq)]
struct CaFileId {
    inode: u64,      // inode 号
    mtime: i64,      // 修改时间(秒)
    mtime_nsec: i64, // 修改时间(纳秒部分)
    dev: u64,        // 设备号
}

/// 获取 CA 文件的唯一标识(读取文件元数据)
fn get_ca_file_id(path: &str) -> Option<CaFileId> {
    match std::fs::metadata(path) {
        Ok(metadata) => Some(CaFileId {
            inode: metadata.ino(),
            mtime: metadata.mtime(),
            mtime_nsec: metadata.mtime_nsec(),
            dev: metadata.dev(),
        }),
        Err(_) => None,
    }
}

/// 全局缓存:使用 DashMap 提供无锁并发访问
/// key为(PID, CA文件标识),value为CA认证信息(用Arc包裹避免克隆)
static CA_AUTH_CACHE: LazyLock<DashMap<CacheKey, Arc<CaAuthInfo>>> = LazyLock::new(DashMap::new);

/// 获取或执行 CA 认证(带缓存)
///
/// 返回 CA 认证信息,用于 TA 的 ACL 访问控制。
///
/// 缓存键基于进程 ID 和 CA 文件标识,可检测:
/// - 同一进程内文件未改变时复用结果
/// - 文件被替换(inode/mtime 变化)时重新验签
/// - 不同进程的 CA 文件各自独立缓存
///
/// # 返回
///
/// 返回 `CaAuthInfo`,包含:
/// - `ca_uuid`: CA 的唯一标识(基于调用者路径生成的 UUID v5)
/// - `verified`: 验签是否通过(包含签名验证和证书链验证)
pub fn get_or_verify_ca() -> CaAuthInfo {
    // 获取当前进程 ID
    // SAFETY: getpid() 是标准的 libc 函数,总是成功且无副作用
    let pid = unsafe { libc::getpid() };

    // 获取调用者可执行文件路径
    let ca_path = match std::fs::read_link("/proc/self/exe") {
        Ok(path) => path.to_string_lossy().to_string(),
        Err(_) => "<unknown>".to_string(),
    };

    // 生成 CA UUID(基于路径)
    let ca_uuid = path_to_uuid(&ca_path);

    // 获取 CA 文件标识
    let ca_file_id = match get_ca_file_id(&ca_path) {
        Some(id) => id,
        None => {
            // 无法获取文件元数据,跳过缓存直接验签
            warn!("无法获取CA文件元数据: {}", ca_path);
            return perform_ca_auth_internal(&ca_uuid);
        }
    };

    // 构建缓存键
    let key = CacheKey {
        pid,
        ca_file_id: ca_file_id.clone(),
    };

    // 尝试从缓存获取
    if let Some(result) = CA_AUTH_CACHE.get(&key) {
        debug!("CA认证缓存命中: pid={}, inode={}", pid, ca_file_id.inode);
        // 使用 Arc 避免克隆,直接返回引用克隆
        return (**result.value()).clone();
    }

    // 缓存未命中,执行验签
    debug!("CA认证缓存未命中,执行验签: pid={}, path={}", pid, ca_path);
    let result = perform_ca_auth_internal(&ca_uuid);

    // 存入缓存(Arc 包裹以减少克隆开销)
    CA_AUTH_CACHE.insert(key, Arc::new(result.clone()));

    result
}

/// 清除所有缓存(主要用于测试)
pub fn clear_cache() {
    CA_AUTH_CACHE.clear();
}

/// 内部认证函数:调用 tasign 库进行签名验证,生成 CaAuthInfo
fn perform_ca_auth_internal(ca_uuid: &str) -> CaAuthInfo {
    let caller_path = match fs::read_link("/proc/self/exe") {
        Ok(path) => path,
        Err(e) => {
            warn!("无法获取调用者可执行文件路径: {}", e);
            return CaAuthInfo {
                ca_uuid: ca_uuid.to_string(),
                verified: false,
            };
        }
    };

    let path_str = caller_path.to_string_lossy().to_string();

    debug!("开始验证 CA ELF 签名: {}", path_str);

    let elf_data = match fs::read(&caller_path) {
        Ok(data) => data,
        Err(e) => {
            warn!("无法读取ELF文件: {}", e);
            return CaAuthInfo {
                ca_uuid: ca_uuid.to_string(),
                verified: false,
            };
        }
    };

    match tasign::verify_elf_signature(&elf_data, None) {
        Ok(_) => {
            debug!("CA ELF 签名验证成功");
            CaAuthInfo {
                ca_uuid: ca_uuid.to_string(),
                verified: true,
            }
        }
        Err(e) => {
            warn!("签名验证失败: {}", e);
            CaAuthInfo {
                ca_uuid: ca_uuid.to_string(),
                verified: false,
            }
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    // CA 验签缓存测试

    #[test]
    fn test_ca_file_id_uniqueness() {
        // 测试 CaFileId 的比较逻辑
        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不同
            mtime_nsec: 0,
            dev: 1,
        };

        assert_eq!(id1, id2);
        assert_ne!(id1, id3);
    }

    #[test]
    fn test_get_ca_file_id_for_current_exe() {
        // 测试获取当前执行文件的标识
        let exe_path = "/proc/self/exe";
        if let Ok(real_path) = fs::read_link(exe_path) {
            let path_str = real_path.to_string_lossy();
            let file_id = get_ca_file_id(&path_str);
            assert!(file_id.is_some());

            let id = file_id.unwrap();
            assert!(id.inode > 0);
            assert!(id.dev > 0);
        }
    }

    #[test]
    fn test_clear_cache() {
        // 测试清除缓存功能

        // 1. 先插入一些测试数据
        let test_key = CacheKey {
            pid: 99999, // 使用一个不会冲突的 PID
            ca_file_id: CaFileId {
                inode: 12345,
                mtime: 1000,
                mtime_nsec: 0,
                dev: 67890,
            },
        };

        let test_info = Arc::new(CaAuthInfo {
            ca_uuid: "test-uuid".to_string(),
            verified: true,
        });

        CA_AUTH_CACHE.insert(test_key.clone(), test_info);

        // 2. 验证数据已插入
        assert!(CA_AUTH_CACHE.contains_key(&test_key));
        assert_eq!(CA_AUTH_CACHE.len(), 1);

        // 3. 清除缓存
        clear_cache();

        // 4. 验证缓存已清空
        assert!(!CA_AUTH_CACHE.contains_key(&test_key));
        assert_eq!(CA_AUTH_CACHE.len(), 0);
    }
}