1use aes_gcm::aead::{Aead, KeyInit, OsRng};
17use aes_gcm::{AeadCore, Aes256Gcm, Key, Nonce};
18
19pub fn mcp_enc_key() -> Option<[u8; 32]> {
23 let raw = std::env::var("MCP_TOKEN_ENC_KEY").ok()?;
24 let trimmed = raw.trim();
25 if trimmed.is_empty() {
26 return None;
27 }
28 let bytes = hex::decode(trimmed).ok()?;
29 if bytes.len() == 32 {
30 let mut arr = [0u8; 32];
31 arr.copy_from_slice(&bytes);
32 Some(arr)
33 } else {
34 None
35 }
36}
37
38pub fn encrypt_token(plaintext: &str) -> Option<String> {
42 let key_bytes = mcp_enc_key()?;
43 let cipher = Aes256Gcm::new(Key::<Aes256Gcm>::from_slice(&key_bytes));
44 let nonce = Aes256Gcm::generate_nonce(&mut OsRng); let ct = cipher.encrypt(&nonce, plaintext.as_bytes()).ok()?;
46 let mut buf = Vec::with_capacity(nonce.len() + ct.len());
47 buf.extend_from_slice(nonce.as_slice());
48 buf.extend_from_slice(&ct);
49 Some(hex::encode(buf))
50}
51
52pub fn decrypt_token(enc_hex: &str) -> Option<String> {
57 let key_bytes = mcp_enc_key()?;
58 let buf = hex::decode(enc_hex).ok()?;
59 if buf.len() < 12 {
61 return None;
62 }
63 let (nonce_bytes, ct) = buf.split_at(12);
64 let cipher = Aes256Gcm::new(Key::<Aes256Gcm>::from_slice(&key_bytes));
65 let nonce = Nonce::from_slice(nonce_bytes);
66 let pt = cipher.decrypt(nonce, ct).ok()?;
67 String::from_utf8(pt).ok()
68}
69
70#[cfg(test)]
71mod tests {
72 use super::*;
73
74 fn with_key<F: FnOnce() -> R, R>(key: &[u8; 32], body: F) -> R {
78 let prev = std::env::var("MCP_TOKEN_ENC_KEY").ok();
79 std::env::set_var("MCP_TOKEN_ENC_KEY", hex::encode(key));
80 struct Restore(Option<String>);
82 impl Drop for Restore {
83 fn drop(&mut self) {
84 match &self.0 {
85 Some(v) => std::env::set_var("MCP_TOKEN_ENC_KEY", v),
86 None => std::env::remove_var("MCP_TOKEN_ENC_KEY"),
87 }
88 }
89 }
90 let _r = Restore(prev);
91 body()
92 }
93
94 #[test]
95 #[serial_test::serial]
96 fn roundtrip_basic() {
97 let key = [42u8; 32];
98 with_key(&key, || {
99 let enc = encrypt_token("ygg_secret_token_value").expect("encrypt");
100 let dec = decrypt_token(&enc).expect("decrypt");
101 assert_eq!(dec, "ygg_secret_token_value");
102 });
103 }
104
105 #[test]
106 #[serial_test::serial]
107 fn roundtrip_unicode_and_long() {
108 let key = [7u8; 32];
109 let plaintext = "ygg_".to_string() + &"中文标题-漢字-🔑 ".repeat(500);
110 with_key(&key, || {
111 let enc = encrypt_token(&plaintext).expect("encrypt");
112 assert_eq!(decrypt_token(&enc).expect("decrypt"), plaintext);
113 });
114 }
115
116 #[test]
117 #[serial_test::serial]
118 fn distinct_nonces_per_encryption() {
119 let key = [9u8; 32];
121 with_key(&key, || {
122 let a = encrypt_token("same").expect("encrypt");
123 let b = encrypt_token("same").expect("encrypt");
124 assert_ne!(a, b, "nonce must differ → ciphertext differs");
125 assert_eq!(decrypt_token(&a).unwrap(), "same");
126 assert_eq!(decrypt_token(&b).unwrap(), "same");
127 });
128 }
129
130 #[test]
131 #[serial_test::serial]
132 fn tampered_ciphertext_fails() {
133 let key = [1u8; 32];
134 with_key(&key, || {
135 let mut enc = encrypt_token("payload").expect("encrypt");
136 let mut bytes = hex::decode(&enc).unwrap();
138 let last = bytes.len() - 1;
139 bytes[last] ^= 0xff;
140 enc = hex::encode(&bytes);
141 assert_eq!(decrypt_token(&enc), None);
142 });
143 }
144
145 #[test]
146 #[serial_test::serial]
147 fn missing_key_returns_none() {
148 std::env::remove_var("MCP_TOKEN_ENC_KEY");
149 assert_eq!(mcp_enc_key(), None);
150 assert_eq!(encrypt_token("x"), None);
151 assert_eq!(decrypt_token("deadbeef"), None);
152 }
153
154 #[test]
155 #[serial_test::serial]
156 fn wrong_key_cannot_decrypt() {
157 let enc = {
158 let key = [1u8; 32];
159 with_key(&key, || encrypt_token("secret").unwrap())
160 };
161 with_key(&[2u8; 32], || {
163 assert_eq!(decrypt_token(&enc), None);
164 });
165 }
166
167 #[test]
168 #[serial_test::serial]
169 fn accepts_hex_encoding() {
170 let key = [5u8; 32];
171 std::env::set_var("MCP_TOKEN_ENC_KEY", hex::encode(key));
172 assert_eq!(mcp_enc_key(), Some(key));
173 std::env::remove_var("MCP_TOKEN_ENC_KEY");
174 }
175
176 #[test]
177 #[serial_test::serial]
178 fn rejects_odd_length_hex() {
179 std::env::set_var("MCP_TOKEN_ENC_KEY", "abc");
181 assert_eq!(mcp_enc_key(), None);
182 std::env::remove_var("MCP_TOKEN_ENC_KEY");
183 }
184
185 #[test]
186 #[serial_test::serial]
187 fn rejects_short_key() {
188 std::env::set_var("MCP_TOKEN_ENC_KEY", hex::encode([0u8; 16]));
190 assert_eq!(mcp_enc_key(), None, "16 字节非 AES-256 密钥,必须拒绝");
191 std::env::remove_var("MCP_TOKEN_ENC_KEY");
192 }
193}