Skip to main content

vault_client_rs/api/
transit.rs

1use base64::Engine;
2use base64::engine::general_purpose::STANDARD as B64;
3use reqwest::Method;
4use secrecy::{ExposeSecret, SecretString};
5
6use crate::VaultClient;
7use crate::api::traits::TransitOperations;
8use crate::client::{encode_path, to_body};
9use crate::types::error::VaultError;
10use crate::types::transit::*;
11
12#[derive(Debug)]
13pub struct TransitHandler<'a> {
14    pub(crate) client: &'a VaultClient,
15    pub(crate) mount: String,
16}
17
18impl TransitOperations for TransitHandler<'_> {
19    // --- Key management ---
20
21    async fn create_key(&self, name: &str, params: &TransitKeyParams) -> Result<(), VaultError> {
22        let body = to_body(params)?;
23        self.client
24            .exec_empty(
25                Method::POST,
26                &format!("{}/keys/{}", self.mount, encode_path(name)),
27                Some(&body),
28            )
29            .await
30    }
31
32    async fn read_key(&self, name: &str) -> Result<TransitKeyInfo, VaultError> {
33        self.client
34            .exec_with_data(
35                Method::GET,
36                &format!("{}/keys/{}", self.mount, encode_path(name)),
37                None,
38            )
39            .await
40    }
41
42    async fn list_keys(&self) -> Result<Vec<String>, VaultError> {
43        self.client.exec_list(&format!("{}/keys", self.mount)).await
44    }
45
46    async fn delete_key(&self, name: &str) -> Result<(), VaultError> {
47        self.client
48            .exec_empty(
49                Method::DELETE,
50                &format!("{}/keys/{}", self.mount, encode_path(name)),
51                None,
52            )
53            .await
54    }
55
56    async fn update_key_config(
57        &self,
58        name: &str,
59        cfg: &TransitKeyConfig,
60    ) -> Result<(), VaultError> {
61        let body = to_body(cfg)?;
62        self.client
63            .exec_empty(
64                Method::POST,
65                &format!("{}/keys/{}/config", self.mount, encode_path(name)),
66                Some(&body),
67            )
68            .await
69    }
70
71    async fn rotate_key(&self, name: &str) -> Result<(), VaultError> {
72        self.client
73            .exec_empty(
74                Method::POST,
75                &format!("{}/keys/{}/rotate", self.mount, encode_path(name)),
76                None,
77            )
78            .await
79    }
80
81    async fn export_key(
82        &self,
83        name: &str,
84        key_type: &str,
85        version: Option<u64>,
86    ) -> Result<TransitExportedKey, VaultError> {
87        let path = match version {
88            Some(v) => format!(
89                "{}/export/{}/{}/{}",
90                self.mount,
91                encode_path(key_type),
92                encode_path(name),
93                v
94            ),
95            None => format!(
96                "{}/export/{}/{}",
97                self.mount,
98                encode_path(key_type),
99                encode_path(name)
100            ),
101        };
102        self.client.exec_with_data(Method::GET, &path, None).await
103    }
104
105    // --- Encrypt / decrypt ---
106
107    async fn encrypt(&self, name: &str, plaintext: &SecretString) -> Result<String, VaultError> {
108        let body = serde_json::json!({
109            "plaintext": B64.encode(plaintext.expose_secret()),
110        });
111        let resp: TransitEncryptResponse = self
112            .client
113            .exec_with_data(
114                Method::POST,
115                &format!("{}/encrypt/{}", self.mount, encode_path(name)),
116                Some(&body),
117            )
118            .await?;
119        Ok(resp.ciphertext)
120    }
121
122    async fn decrypt(&self, name: &str, ciphertext: &str) -> Result<SecretString, VaultError> {
123        let body = serde_json::json!({ "ciphertext": ciphertext });
124        let resp: TransitDecryptResponse = self
125            .client
126            .exec_with_data(
127                Method::POST,
128                &format!("{}/decrypt/{}", self.mount, encode_path(name)),
129                Some(&body),
130            )
131            .await?;
132        // Vault returns base64-encoded plaintext
133        let decoded = B64
134            .decode(resp.plaintext.expose_secret())
135            .map_err(|e| VaultError::Config(format!("base64 decode: {e}")))?;
136        let s = String::from_utf8(decoded)
137            .map_err(|e| VaultError::Config(format!("utf-8 decode: {e}")))?;
138        Ok(SecretString::from(s))
139    }
140
141    async fn rewrap(&self, name: &str, ciphertext: &str) -> Result<String, VaultError> {
142        let body = serde_json::json!({ "ciphertext": ciphertext });
143        let resp: TransitRewrapResponse = self
144            .client
145            .exec_with_data(
146                Method::POST,
147                &format!("{}/rewrap/{}", self.mount, encode_path(name)),
148                Some(&body),
149            )
150            .await?;
151        Ok(resp.ciphertext)
152    }
153
154    // --- Batch operations ---
155
156    async fn batch_encrypt(
157        &self,
158        name: &str,
159        items: &[TransitBatchPlaintext],
160    ) -> Result<Vec<TransitBatchCiphertext>, VaultError> {
161        let body = serde_json::json!({ "batch_input": items });
162        let resp: TransitBatchEncryptResponse = self
163            .client
164            .exec_with_data(
165                Method::POST,
166                &format!("{}/encrypt/{}", self.mount, encode_path(name)),
167                Some(&body),
168            )
169            .await?;
170        Ok(resp.batch_results)
171    }
172
173    async fn batch_decrypt(
174        &self,
175        name: &str,
176        items: &[TransitBatchCiphertext],
177    ) -> Result<Vec<TransitBatchDecryptItem>, VaultError> {
178        let body = serde_json::json!({ "batch_input": items });
179        let resp: TransitBatchDecryptResponse = self
180            .client
181            .exec_with_data(
182                Method::POST,
183                &format!("{}/decrypt/{}", self.mount, encode_path(name)),
184                Some(&body),
185            )
186            .await?;
187        Ok(resp.batch_results)
188    }
189
190    // --- Signing ---
191
192    async fn sign(
193        &self,
194        name: &str,
195        input: &[u8],
196        params: &TransitSignParams,
197    ) -> Result<String, VaultError> {
198        let mut body = to_body(params)?;
199        body["input"] = serde_json::Value::String(B64.encode(input));
200        let resp: TransitSignResponse = self
201            .client
202            .exec_with_data(
203                Method::POST,
204                &format!("{}/sign/{}", self.mount, encode_path(name)),
205                Some(&body),
206            )
207            .await?;
208        Ok(resp.signature)
209    }
210
211    async fn verify(&self, name: &str, input: &[u8], signature: &str) -> Result<bool, VaultError> {
212        let body = serde_json::json!({
213            "input": B64.encode(input),
214            "signature": signature,
215        });
216        let resp: TransitVerifyResponse = self
217            .client
218            .exec_with_data(
219                Method::POST,
220                &format!("{}/verify/{}", self.mount, encode_path(name)),
221                Some(&body),
222            )
223            .await?;
224        Ok(resp.valid)
225    }
226
227    // --- Hash / HMAC / random ---
228
229    async fn hash(&self, input: &[u8], algorithm: &str) -> Result<String, VaultError> {
230        let body = serde_json::json!({
231            "input": B64.encode(input),
232            "algorithm": algorithm,
233        });
234        let resp: TransitHashResponse = self
235            .client
236            .exec_with_data(Method::POST, &format!("{}/hash", self.mount), Some(&body))
237            .await?;
238        Ok(resp.sum)
239    }
240
241    async fn hmac(&self, name: &str, input: &[u8], algorithm: &str) -> Result<String, VaultError> {
242        let body = serde_json::json!({
243            "input": B64.encode(input),
244            "algorithm": algorithm,
245        });
246        let resp: TransitHmacResponse = self
247            .client
248            .exec_with_data(
249                Method::POST,
250                &format!("{}/hmac/{}", self.mount, encode_path(name)),
251                Some(&body),
252            )
253            .await?;
254        Ok(resp.hmac)
255    }
256
257    async fn random(&self, num_bytes: u32, format: &str) -> Result<String, VaultError> {
258        let body = serde_json::json!({
259            "bytes": num_bytes,
260            "format": format,
261        });
262        let resp: TransitRandomResponse = self
263            .client
264            .exec_with_data(Method::POST, &format!("{}/random", self.mount), Some(&body))
265            .await?;
266        Ok(resp.random_bytes)
267    }
268
269    async fn generate_data_key(
270        &self,
271        name: &str,
272        key_type: &str,
273    ) -> Result<TransitDataKey, VaultError> {
274        self.client
275            .exec_with_data(
276                Method::POST,
277                &format!(
278                    "{}/datakey/{}/{}",
279                    self.mount,
280                    encode_path(key_type),
281                    encode_path(name)
282                ),
283                None,
284            )
285            .await
286    }
287
288    // --- Key maintenance ---
289
290    async fn trim_key(&self, name: &str, min_version: u64) -> Result<(), VaultError> {
291        let body = serde_json::json!({ "min_available_version": min_version });
292        self.client
293            .exec_empty(
294                Method::POST,
295                &format!("{}/keys/{}/trim", self.mount, encode_path(name)),
296                Some(&body),
297            )
298            .await
299    }
300
301    async fn backup_key(&self, name: &str) -> Result<SecretString, VaultError> {
302        let resp: TransitBackupResponse = self
303            .client
304            .exec_with_data(
305                Method::GET,
306                &format!("{}/backup/{}", self.mount, encode_path(name)),
307                None,
308            )
309            .await?;
310        Ok(resp.backup.clone())
311    }
312
313    async fn restore_key(&self, name: &str, backup: &SecretString) -> Result<(), VaultError> {
314        let body = serde_json::json!({ "backup": backup.expose_secret() });
315        self.client
316            .exec_empty(
317                Method::POST,
318                &format!("{}/restore/{}", self.mount, encode_path(name)),
319                Some(&body),
320            )
321            .await
322    }
323
324    async fn batch_sign(
325        &self,
326        name: &str,
327        items: &[TransitBatchSignInput],
328        params: &TransitSignParams,
329    ) -> Result<Vec<TransitBatchSignResult>, VaultError> {
330        let mut body = to_body(params)?;
331        body["batch_input"] = serde_json::to_value(items)
332            .map_err(|e| VaultError::Config(format!("serialize batch sign input: {e}")))?;
333        let resp: TransitBatchSignResponse = self
334            .client
335            .exec_with_data(
336                Method::POST,
337                &format!("{}/sign/{}", self.mount, encode_path(name)),
338                Some(&body),
339            )
340            .await?;
341        Ok(resp.batch_results)
342    }
343
344    async fn batch_verify(
345        &self,
346        name: &str,
347        items: &[TransitBatchVerifyInput],
348    ) -> Result<Vec<TransitBatchVerifyResult>, VaultError> {
349        let body = serde_json::json!({ "batch_input": items });
350        let resp: TransitBatchVerifyResponse = self
351            .client
352            .exec_with_data(
353                Method::POST,
354                &format!("{}/verify/{}", self.mount, encode_path(name)),
355                Some(&body),
356            )
357            .await?;
358        Ok(resp.batch_results)
359    }
360
361    async fn read_cache_config(&self) -> Result<TransitCacheConfig, VaultError> {
362        self.client
363            .exec_with_data(Method::GET, &format!("{}/cache-config", self.mount), None)
364            .await
365    }
366
367    async fn write_cache_config(&self, size: u64) -> Result<(), VaultError> {
368        let body = serde_json::json!({ "size": size });
369        self.client
370            .exec_empty(
371                Method::POST,
372                &format!("{}/cache-config", self.mount),
373                Some(&body),
374            )
375            .await
376    }
377}