Skip to main content

encryption/
guard.rs

1// Copyright (c) 2026 erik <erik@erik.xyz> — https://erik.xyz
2
3//! 请求守卫:交给处理器的一个共享加密句柄。
4//!
5//! 框架集成层(axum / actix-web / rocket / poem / salvo / warp / bee-rust / e-cat)
6//! 全都在这一层之上做薄适配 —— 把 [`Guard`] 按各自框架的惯例注入请求上下文,
7//! 处理器拿到它就能加解密,不必把主密钥或 Manager 一路透传下去。
8//!
9//! ```no_run
10//! use encryption::guard::Guard;
11//!
12//! let master_key = [7u8; 32]; // 生产环境从环境变量 / KMS 读取
13//! let guard = Guard::from_master_key(&master_key, "aes-256-gcm")?;
14//!
15//! // 处理器里
16//! let stored = guard.encrypt("13800138000".as_bytes())?;
17//! assert_eq!(guard.decrypt(&stored)?, "13800138000".as_bytes());
18//! # Ok::<(), encryption::Error>(())
19//! ```
20//!
21//! # 为什么是 `Arc`
22//!
23//! 每个请求都要拿到同一个 Manager,而框架普遍要求注入的状态是 `Send + Sync + 'static`
24//! 且常常要求 `Clone`。Manager 本身是只读的、构造后不再变,所以 `Arc` 共享即可 ——
25//! 子密钥只在构造时派生一次,每请求零成本。
26
27use std::sync::Arc;
28
29use base64::Engine as _;
30
31use crate::error::{Error, Result};
32use crate::manager::EncryptionManager;
33
34/// 主密钥环境变量名。
35const MASTER_KEY_ENV: &str = "ENCRYPTION_MASTER_KEY";
36/// 默认算法环境变量名(可选)。
37const ALGORITHM_ENV: &str = "ENCRYPTION_ALGORITHM";
38/// 未指定 `ENCRYPTION_ALGORITHM` 时使用的算法。
39const DEFAULT_ALGORITHM: &str = "aes-256-gcm";
40
41/// 请求守卫:一个可跨请求共享、可跨线程克隆的加密句柄。
42///
43/// 构造之后的一切加解密都经由它转发到 [`EncryptionManager`];克隆只是
44/// `Arc` 引用计数递增,不含任何密钥拷贝。
45#[derive(Debug, Clone)]
46pub struct Guard {
47    manager: Arc<EncryptionManager>,
48}
49
50impl Guard {
51    /// 从一个已经建好的 Manager 构造。
52    pub fn new(manager: EncryptionManager) -> Self {
53        Self {
54            manager: Arc::new(manager),
55        }
56    }
57
58    /// 从 32 字节主密钥构造(内部走 [`crate::factory::EncryptionManagerFactory`])。
59    ///
60    /// 构造时即完成全部校验(主密钥长度、默认算法是否注册),配置错了就在启动时炸。
61    pub fn from_master_key(master_key: &[u8], default_identifier: &str) -> Result<Self> {
62        Ok(Self::new(
63            crate::factory::EncryptionManagerFactory::from_master_key(
64                master_key,
65                default_identifier,
66            )?,
67        ))
68    }
69
70    /// 从进程环境变量构造。
71    ///
72    /// 读取 `ENCRYPTION_MASTER_KEY`(必需)与 `ENCRYPTION_ALGORITHM`(可选,
73    /// 缺省 `aes-256-gcm`)。主密钥值的格式见 [`Self::from_env_with`]。
74    pub fn from_env() -> Result<Self> {
75        Self::from_env_with(|key| std::env::var(key).ok())
76    }
77
78    /// 从可注入的查找函数构造:测试与自定义配置源(KMS / 配置中心)走这里。
79    ///
80    /// 主密钥取值 `lookup("ENCRYPTION_MASTER_KEY")`,格式为显式前缀
81    /// `hex:<十六进制>` / `base64:<Base64>`;无前缀时自动判定:去掉首尾空白后
82    /// 恰为 64 个十六进制字符按 hex,否则按 Base64。解码后必须恰好 32 字节,
83    /// 否则 [`Error::InvalidMasterKey`];算法名未注册时冒泡
84    /// [`Error::UnknownIdentifier`]。
85    ///
86    /// ```
87    /// use encryption::guard::Guard;
88    ///
89    /// // 假 lookup:真实场景用 std::env::var 或配置中心
90    /// let lookup = |key: &str| match key {
91    ///     "ENCRYPTION_MASTER_KEY" => Some("00".repeat(32)), // 裸 hex(64 位)
92    ///     _ => None,
93    /// };
94    ///
95    /// let guard = Guard::from_env_with(lookup)?;
96    /// assert_eq!(guard.manager().default_identifier(), "aes-256-gcm");
97    ///
98    /// let blob = guard.encrypt("13800138000".as_bytes())?;
99    /// assert_eq!(guard.decrypt(&blob)?, "13800138000".as_bytes());
100    /// # Ok::<(), encryption::Error>(())
101    /// ```
102    pub fn from_env_with<F: Fn(&str) -> Option<String>>(lookup: F) -> Result<Self> {
103        let raw = lookup(MASTER_KEY_ENV).ok_or(Error::MissingConfig {
104            key: MASTER_KEY_ENV,
105        })?;
106        let algorithm = lookup(ALGORITHM_ENV).unwrap_or_else(|| DEFAULT_ALGORITHM.to_owned());
107        Self::from_master_key(&decode_master_key(&raw)?, &algorithm)
108    }
109
110    /// 共享的 Manager。总是存在。
111    pub fn manager(&self) -> &EncryptionManager {
112        &self.manager
113    }
114
115    /// 用默认算法加密。
116    pub fn encrypt(&self, plaintext: &[u8]) -> Result<Vec<u8>> {
117        self.manager.encrypt(plaintext)
118    }
119
120    /// 用默认算法解密。
121    pub fn decrypt(&self, ciphertext: &[u8]) -> Result<Vec<u8>> {
122        self.manager.decrypt(ciphertext)
123    }
124
125    /// 用指定算法加密。
126    pub fn encrypt_with(&self, identifier: &str, plaintext: &[u8]) -> Result<Vec<u8>> {
127        self.manager.encrypt_with(identifier, plaintext)
128    }
129
130    /// 用指定算法解密。
131    pub fn decrypt_with(&self, identifier: &str, ciphertext: &[u8]) -> Result<Vec<u8>> {
132        self.manager.decrypt_with(identifier, ciphertext)
133    }
134
135    /// 已注册算法标识(健康检查 / 日志用)。
136    pub fn identifiers(&self) -> Vec<&str> {
137        self.manager.registry().identifiers()
138    }
139}
140
141/// 解析主密钥取值:显式前缀 `hex:` / `base64:`,无前缀自动判定,
142/// 解码失败或长度不为 32 字节一律 [`Error::InvalidMasterKey`]。
143fn decode_master_key(raw: &str) -> Result<[u8; 32]> {
144    let value = raw.trim();
145    let (is_hex, payload) = if let Some(hex) = value.strip_prefix("hex:") {
146        (true, hex)
147    } else if let Some(b64) = value.strip_prefix("base64:") {
148        (false, b64)
149    } else if value.len() == 64 && value.bytes().all(|b| b.is_ascii_hexdigit()) {
150        (true, value)
151    } else {
152        (false, value)
153    };
154
155    let decoded = if is_hex {
156        crate::internal::hex_decode(payload).ok()
157    } else {
158        base64::engine::general_purpose::STANDARD
159            .decode(payload)
160            .ok()
161    }
162    // 解码失败时字节长度未知,got 记 0。
163    .ok_or(Error::InvalidMasterKey { got: 0 })?;
164
165    decoded
166        .try_into()
167        .map_err(|v: Vec<u8>| Error::InvalidMasterKey { got: v.len() })
168}
169
170#[cfg(test)]
171mod tests {
172    use super::*;
173
174    fn guard() -> Guard {
175        Guard::from_master_key(&[7u8; 32], "aes-256-gcm").unwrap()
176    }
177
178    #[test]
179    fn wraps_the_factory_and_forwards() {
180        let guard = guard();
181        assert_eq!(guard.identifiers().len(), 5);
182        assert_eq!(guard.manager().default_identifier(), "aes-256-gcm");
183
184        let blob = guard.encrypt(b"phone").unwrap();
185        assert_eq!(guard.decrypt(&blob).unwrap(), b"phone");
186
187        let blob = guard.encrypt_with("sm4-cbc", b"phone").unwrap();
188        assert_eq!(guard.decrypt_with("sm4-cbc", &blob).unwrap(), b"phone");
189    }
190
191    #[test]
192    fn clones_share_one_manager() {
193        let guard = guard();
194        let clone = guard.clone();
195        let blob = guard.encrypt(b"shared").unwrap();
196        assert_eq!(clone.decrypt(&blob).unwrap(), b"shared");
197        assert_eq!(Arc::strong_count(&guard.manager), 2);
198    }
199
200    #[test]
201    fn invalid_master_key_fails_at_construction() {
202        assert!(Guard::from_master_key(&[0u8; 16], "aes-256-gcm").is_err());
203        assert!(Guard::from_master_key(&[0u8; 32], "no-such-algorithm").is_err());
204    }
205
206    /// 假 lookup:从一对固定取值里查,不碰 std::env。
207    fn fake_env(entries: Vec<(&str, String)>) -> impl Fn(&str) -> Option<String> {
208        move |key| {
209            entries
210                .iter()
211                .find(|(k, _)| *k == key)
212                .map(|(_, v)| v.clone())
213        }
214    }
215
216    fn roundtrip(guard: &Guard) {
217        let blob = guard.encrypt(b"phone").unwrap();
218        assert_eq!(guard.decrypt(&blob).unwrap(), b"phone");
219    }
220
221    #[test]
222    fn from_env_hex_prefix() {
223        let hex = "07".repeat(32);
224        let lookup = fake_env(vec![(MASTER_KEY_ENV, format!("hex:{hex}"))]);
225        let guard = Guard::from_env_with(lookup).unwrap();
226        assert_eq!(guard.manager().default_identifier(), "aes-256-gcm");
227        roundtrip(&guard);
228    }
229
230    #[test]
231    fn from_env_base64_prefix() {
232        // [7u8; 32] 的 Base64
233        let b64 = "BwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwc=";
234        let lookup = fake_env(vec![(MASTER_KEY_ENV, format!("base64:{b64}"))]);
235        let guard = Guard::from_env_with(lookup).unwrap();
236        roundtrip(&guard);
237    }
238
239    #[test]
240    fn from_env_bare_hex_auto_detected() {
241        let hex = "00".repeat(32);
242        let lookup = fake_env(vec![(MASTER_KEY_ENV, hex)]);
243        let guard = Guard::from_env_with(lookup).unwrap();
244        roundtrip(&guard);
245    }
246
247    #[test]
248    fn from_env_missing_master_key() {
249        let err = Guard::from_env_with(fake_env(vec![])).unwrap_err();
250        assert_eq!(
251            err,
252            Error::MissingConfig {
253                key: "ENCRYPTION_MASTER_KEY"
254            }
255        );
256    }
257
258    #[test]
259    fn from_env_wrong_length() {
260        // 16 字节(hex: 前缀)→ InvalidMasterKey { got: 16 }
261        let lookup = fake_env(vec![(MASTER_KEY_ENV, format!("hex:{}", "00".repeat(16)))]);
262        assert_eq!(
263            Guard::from_env_with(lookup).unwrap_err(),
264            Error::InvalidMasterKey { got: 16 }
265        );
266
267        // 24 字节(base64: 前缀)→ InvalidMasterKey { got: 24 }
268        let lookup = fake_env(vec![(MASTER_KEY_ENV, format!("base64:{}", "A".repeat(32)))]);
269        assert_eq!(
270            Guard::from_env_with(lookup).unwrap_err(),
271            Error::InvalidMasterKey { got: 24 }
272        );
273
274        // 非法 hex → 解码失败,got 记 0
275        let lookup = fake_env(vec![(MASTER_KEY_ENV, "hex:zz".to_owned())]);
276        assert_eq!(
277            Guard::from_env_with(lookup).unwrap_err(),
278            Error::InvalidMasterKey { got: 0 }
279        );
280    }
281
282    #[test]
283    fn from_env_respects_algorithm() {
284        let lookup = fake_env(vec![
285            (MASTER_KEY_ENV, "07".repeat(32)),
286            (ALGORITHM_ENV, "sm4-cbc".to_owned()),
287        ]);
288        let guard = Guard::from_env_with(lookup).unwrap();
289        assert_eq!(guard.manager().default_identifier(), "sm4-cbc");
290        roundtrip(&guard);
291    }
292
293    #[test]
294    fn from_env_unknown_algorithm_bubbles() {
295        let lookup = fake_env(vec![
296            (MASTER_KEY_ENV, "07".repeat(32)),
297            (ALGORITHM_ENV, "no-such-algorithm".to_owned()),
298        ]);
299        assert!(matches!(
300            Guard::from_env_with(lookup).unwrap_err(),
301            Error::UnknownIdentifier { .. }
302        ));
303    }
304}