1use aes_gcm::aead::{Aead, Generate, KeyInit};
17use aes_gcm::{Aes256Gcm, 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_bytes).into());
44 let nonce = Nonce::try_generate().ok()?; 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_bytes).into());
65 let nonce = nonce_bytes.try_into().ok()?;
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 decrypt_legacy_nist_empty_plaintext() {
97 let key: [u8; 32] =
101 hex::decode("b52c505a37d78eda5dd34f20c22540ea1b58963cf8e5bf8ffa85f9f2492505b4")
102 .unwrap()
103 .try_into()
104 .unwrap();
105 let enc = concat!(
106 "516c33929df5a3284ff463d7",
107 "bdc1ac884d332457a1d2664f168c76f0"
108 );
109 with_key(&key, || {
110 assert_eq!(decrypt_token(enc).as_deref(), Some(""));
111 });
112 }
113
114 #[test]
115 #[serial_test::serial]
116 fn roundtrip_basic() {
117 let key = [42u8; 32];
118 with_key(&key, || {
119 let enc = encrypt_token("ygg_secret_token_value").expect("encrypt");
120 let dec = decrypt_token(&enc).expect("decrypt");
121 assert_eq!(dec, "ygg_secret_token_value");
122 });
123 }
124
125 #[test]
126 #[serial_test::serial]
127 fn roundtrip_unicode_and_long() {
128 let key = [7u8; 32];
129 let plaintext = "ygg_".to_string() + &"中文标题-漢字-🔑 ".repeat(500);
130 with_key(&key, || {
131 let enc = encrypt_token(&plaintext).expect("encrypt");
132 assert_eq!(decrypt_token(&enc).expect("decrypt"), plaintext);
133 });
134 }
135
136 #[test]
137 #[serial_test::serial]
138 fn distinct_nonces_per_encryption() {
139 let key = [9u8; 32];
141 with_key(&key, || {
142 let a = encrypt_token("same").expect("encrypt");
143 let b = encrypt_token("same").expect("encrypt");
144 assert_ne!(a, b, "nonce must differ → ciphertext differs");
145 assert_eq!(decrypt_token(&a).unwrap(), "same");
146 assert_eq!(decrypt_token(&b).unwrap(), "same");
147 });
148 }
149
150 #[test]
151 #[serial_test::serial]
152 fn tampered_ciphertext_fails() {
153 let key = [1u8; 32];
154 with_key(&key, || {
155 let mut enc = encrypt_token("payload").expect("encrypt");
156 let mut bytes = hex::decode(&enc).unwrap();
158 let last = bytes.len() - 1;
159 bytes[last] ^= 0xff;
160 enc = hex::encode(&bytes);
161 assert_eq!(decrypt_token(&enc), None);
162 });
163 }
164
165 #[test]
166 #[serial_test::serial]
167 fn missing_key_returns_none() {
168 std::env::remove_var("MCP_TOKEN_ENC_KEY");
169 assert_eq!(mcp_enc_key(), None);
170 assert_eq!(encrypt_token("x"), None);
171 assert_eq!(decrypt_token("deadbeef"), None);
172 }
173
174 #[test]
175 #[serial_test::serial]
176 fn wrong_key_cannot_decrypt() {
177 let enc = {
178 let key = [1u8; 32];
179 with_key(&key, || encrypt_token("secret").unwrap())
180 };
181 with_key(&[2u8; 32], || {
183 assert_eq!(decrypt_token(&enc), None);
184 });
185 }
186
187 #[test]
188 #[serial_test::serial]
189 fn accepts_hex_encoding() {
190 let key = [5u8; 32];
191 std::env::set_var("MCP_TOKEN_ENC_KEY", hex::encode(key));
192 assert_eq!(mcp_enc_key(), Some(key));
193 std::env::remove_var("MCP_TOKEN_ENC_KEY");
194 }
195
196 #[test]
197 #[serial_test::serial]
198 fn rejects_odd_length_hex() {
199 std::env::set_var("MCP_TOKEN_ENC_KEY", "abc");
201 assert_eq!(mcp_enc_key(), None);
202 std::env::remove_var("MCP_TOKEN_ENC_KEY");
203 }
204
205 #[test]
206 #[serial_test::serial]
207 fn rejects_short_key() {
208 std::env::set_var("MCP_TOKEN_ENC_KEY", hex::encode([0u8; 16]));
210 assert_eq!(mcp_enc_key(), None, "16 字节非 AES-256 密钥,必须拒绝");
211 std::env::remove_var("MCP_TOKEN_ENC_KEY");
212 }
213}