117 lines
4.0 KiB
Rust
117 lines
4.0 KiB
Rust
//! 生产密钥库后端: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<String, SecretStoreError> {
|
||
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<String, SecretStoreError> {
|
||
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<String, SecretStoreError> {
|
||
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 已清理)");
|
||
}
|
||
}
|