use std::sync::Mutex;
use log::{debug, warn};
use xtee_psk::PskResult;
pub(crate) struct ServerIdentityCache {
key: Mutex<Option<Vec<u8>>>,
}
impl ServerIdentityCache {
pub(crate) const fn new() -> Self {
Self {
key: Mutex::new(None),
}
}
pub(crate) fn verify(&self, long_term_point: &[u8]) -> PskResult<()> {
let mut guard = self
.key
.lock()
.unwrap_or_else(|poisoned| poisoned.into_inner());
match guard.as_ref() {
None => {
*guard = Some(long_term_point.to_vec());
debug!("首次连接,已记录服务端长期公钥(TOFU)");
}
Some(stored) if stored.as_slice() == long_term_point => {
}
Some(_) => {
warn!(
"服务端长期公钥与已记录的不一致(vsock-manager 可能已重启),重新记录新公钥;若非预期轮换请核查 VSOCK 对端"
);
*guard = Some(long_term_point.to_vec());
}
}
Ok(())
}
}
static SERVER_IDENTITY: ServerIdentityCache = ServerIdentityCache::new();
pub(crate) fn verify_server_identity(long_term_point: &[u8]) -> PskResult<()> {
SERVER_IDENTITY.verify(long_term_point)
}
#[cfg(test)]
mod psk_tests {
use super::*;
use xtee_psk::SM2_POINT_LEN;
#[test]
fn test_server_identity_cache_new_instance() {
let cache = ServerIdentityCache::new();
let test_point = vec![0xDEu8; SM2_POINT_LEN];
cache.verify(&test_point).expect("首次 TOFU 设置应该成功");
cache
.verify(&test_point)
.expect("相同身份应该通过(TOFU 一致)");
}
#[test]
fn test_server_identity_cache_rotation() {
let cache = ServerIdentityCache::new();
let first = vec![0xDEu8; SM2_POINT_LEN];
let second = {
let mut p = first.clone();
p[0] ^= 0xFF;
p
};
cache.verify(&first).expect("首次 TOFU 设置应该成功");
cache
.verify(&second)
.expect("服务端重启轮换的新公钥应被接受");
cache
.verify(&second)
.expect("轮换后的新公钥应通过(TOFU 一致)");
cache.verify(&first).expect("回退到旧公钥应被接受");
}
#[test]
fn test_server_identity_cache_concurrent() {
let cache = std::sync::Arc::new(ServerIdentityCache::new());
let mut handles = Vec::new();
for t in 0..8u8 {
let cache = std::sync::Arc::clone(&cache);
handles.push(std::thread::spawn(move || {
for round in 0..200u16 {
let mut point = vec![t; SM2_POINT_LEN];
if round % 2 == 0 {
point[0] ^= 0xFF;
}
cache.verify(&point).expect("并发 verify 不应失败");
}
}));
}
for handle in handles {
handle.join().expect("并发线程不应 panic");
}
let probe = vec![0xABu8; SM2_POINT_LEN];
cache.verify(&probe).expect("并发结束后缓存仍可用");
}
#[test]
fn test_server_identity_cache_poison_recovery() {
let cache = std::sync::Arc::new(ServerIdentityCache::new());
let point = vec![0xD1u8; SM2_POINT_LEN];
cache.verify(&point).expect("首连记录应该成功");
let worker = {
let cache = std::sync::Arc::clone(&cache);
std::thread::spawn(move || {
let _guard = cache.key.lock().unwrap();
panic!("simulated panic while holding lock");
})
};
assert!(worker.join().is_err(), "子线程应 panic 以使锁中毒");
cache
.verify(&point)
.expect("锁中毒后应通过 into_inner 恢复");
let rotated = {
let mut p = point.clone();
p[0] ^= 0xFF;
p
};
cache.verify(&rotated).expect("锁中毒后的轮换也应正常处理");
}
#[test]
fn test_verify_server_identity_global() {
let probe = vec![0xAAu8; SM2_POINT_LEN];
verify_server_identity(&probe).expect("验证应成功(首连或轮换)");
verify_server_identity(&probe).expect("一致身份应该通过");
let probe2 = vec![0xBBu8; SM2_POINT_LEN];
verify_server_identity(&probe2).expect("再次轮换应被接受");
verify_server_identity(&probe2).expect("轮换后的新公钥应通过");
}
}