//! 生产密钥库后端:keyring → Windows Credential Manager(优先)/ Linux Secret Service //! (架构 §4.1) //! //! 错误映射时只保留中文说明,绝不把后端原始错误或密钥明文带入错误信息。 use crate::error::SecretStoreError; use crate::store::SecretStore; /// keyring 后端封装。 #[derive(Default)] pub struct KeyringSecretStore; impl KeyringSecretStore { pub fn new() -> Self { Self } } #[cfg(windows)] mod platform { use keyring::Entry; use crate::error::SecretStoreError; pub fn set(service: &str, account: &str, secret: &str) -> Result<(), SecretStoreError> { let entry = Entry::new(service, account).map_err(map_err)?; entry.set_password(secret).map_err(map_err) } pub fn get(service: &str, account: &str) -> Result { let entry = Entry::new(service, account).map_err(map_err)?; entry.get_password().map_err(map_err) } pub fn has(service: &str, account: &str) -> bool { Entry::new(service, account) .and_then(|e| e.get_password()) .is_ok() } pub fn delete(service: &str, account: &str) -> Result<(), SecretStoreError> { let entry = Entry::new(service, account).map_err(map_err)?; entry.delete_credential().map_err(map_err) } fn map_err(e: keyring::Error) -> SecretStoreError { // NoEntry 表示无此条目;其余错误统一映射为后端失败,绝不回显原始错误文本。 if matches!(e, keyring::Error::NoEntry) { SecretStoreError::NotFound } else { SecretStoreError::Backend("系统密钥库操作失败(Windows Credential Manager)".into()) } } } #[cfg(not(windows))] mod platform { use crate::error::SecretStoreError; pub fn set(_service: &str, _account: &str, _secret: &str) -> Result<(), SecretStoreError> { Err(SecretStoreError::Backend("Linux 密钥库后端自 Wave 2 起接入(Secret Service)".into())) } pub fn get(_service: &str, _account: &str) -> Result { Err(SecretStoreError::Backend("Linux 密钥库后端自 Wave 2 起接入(Secret Service)".into())) } pub fn has(_service: &str, _account: &str) -> bool { false } pub fn delete(_service: &str, _account: &str) -> Result<(), SecretStoreError> { Err(SecretStoreError::Backend("Linux 密钥库后端自 Wave 2 起接入(Secret Service)".into())) } } impl SecretStore for KeyringSecretStore { fn set(&self, service: &str, account: &str, secret: &str) -> Result<(), SecretStoreError> { platform::set(service, account, secret) } fn get(&self, service: &str, account: &str) -> Result { platform::get(service, account) } fn has(&self, service: &str, account: &str) -> bool { platform::has(service, account) } fn delete(&self, service: &str, account: &str) -> Result<(), SecretStoreError> { platform::delete(service, account) } } #[cfg(test)] mod tests { use super::*; /// 真机密钥库往返(Windows Credential Manager):写假 key → 读回一致 → 删除清理。 /// 运行:cargo test -p agentdock-secrets real_machine_keyring_roundtrip -- --ignored --nocapture #[test] #[ignore] fn real_machine_keyring_roundtrip() { let store = KeyringSecretStore::new(); let service = "agentdock.selftest"; let account = "api_key"; let fake = "sk-test-selftest-only"; store.set(service, account, fake).expect("写 Credential Manager 应成功"); assert!(store.has(service, account), "写后应可读到"); assert_eq!(store.get(service, account).unwrap(), fake, "读回应一致"); store.delete(service, account).expect("删除应成功"); assert!(!store.has(service, account), "删除后应不存在"); println!("真机密钥库往返通过(Windows Credential Manager,假 key 已清理)"); } }