rust-libteec 0.6.0

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::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认证信息
static CA_AUTH_CACHE: LazyLock<DashMap<CacheKey, 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
    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(),
    };

    // 生成 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, &ca_path);
        }
    };

    // 构建缓存键
    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, &ca_path);

    CA_AUTH_CACHE.insert(key, result.clone());

    result
}

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

/// 内部认证函数:调用 tasign 库进行签名验证,生成 CaAuthInfo
fn perform_ca_auth_internal(ca_uuid: &str, ca_path: &str) -> CaAuthInfo {
    if ca_path == "<unknown>" {
        warn!("CA path unknown, skipping verification");
        return CaAuthInfo {
            ca_uuid: ca_uuid.to_string(),
            verified: false,
        };
    }

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

    let elf_data = match fs::read(ca_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_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_unknown_path() {
        let result = perform_ca_auth_internal("test-uuid", "<unknown>");
        assert_eq!(result.ca_uuid, "test-uuid");
        assert!(!result.verified);
    }

    #[test]
    fn test_perform_ca_auth_internal_nonexistent_path() {
        let result = perform_ca_auth_internal("test-uuid", "/nonexistent/path/to/binary");
        assert_eq!(result.ca_uuid, "test-uuid");
        assert!(!result.verified);
    }

    #[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 result = perform_ca_auth_internal("test-uuid", &exe_path);
        assert_eq!(result.ca_uuid, "test-uuid");
    }

    #[test]
    fn test_get_ca_file_id_nonexistent() {
        assert!(get_ca_file_id("/nonexistent/file").is_none());
    }

    #[test]
    fn test_get_ca_file_id_current_exe() {
        let exe_path = std::fs::read_link("/proc/self/exe")
            .unwrap()
            .to_string_lossy()
            .to_string();
        let file_id = get_ca_file_id(&exe_path);
        assert!(file_id.is_some());
        let id = file_id.unwrap();
        assert!(id.inode > 0);
    }
}