oz_keystore/hashicorp/
vault.rs

1use reqwest::{Client, Error};
2use serde::{Deserialize, Serialize};
3use stellar_strkey::ed25519::PrivateKey;
4
5#[derive(Serialize, Deserialize, Debug)]
6struct SecretData {
7    secret: String,
8}
9
10#[derive(Serialize, Deserialize, Debug)]
11struct SecretRequest {
12    data: SecretData,
13}
14
15#[derive(Deserialize, Debug)]
16struct SecretResponseData {
17    data: Option<SecretData>,
18}
19
20#[derive(Deserialize, Debug)]
21struct SecretResponse {
22    data: Option<SecretResponseData>,
23}
24
25#[derive(Deserialize, Debug)]
26struct SecretListData {
27    keys: Vec<String>,
28}
29
30#[allow(dead_code)]
31#[derive(Deserialize, Debug)]
32struct SecretListResponse {
33    request_id: String,
34    lease_id: String,
35    renewable: bool,
36    lease_duration: u64,
37    data: SecretListData,
38    #[serde(default)]
39    wrap_info: Option<()>,
40    #[serde(default)]
41    warnings: Option<()>,
42    #[serde(default)]
43    auth: Option<()>,
44    mount_type: String,
45}
46
47#[derive(Debug)]
48pub enum KeyType {
49    EVM,
50    Stellar,
51    Solana,
52    Any,
53}
54
55pub struct HashicorpVaultClient {
56    client: Client,
57    base_url: String,
58    token: String,
59}
60
61impl HashicorpVaultClient {
62    pub fn new(base_url: &str, token: &str) -> Self {
63        Self {
64            client: Client::new(),
65            base_url: base_url.to_string(),
66            token: token.to_string(),
67        }
68    }
69
70    pub fn with_client(&self, client: Client) -> Self {
71        Self {
72            client,
73            base_url: self.base_url.clone(),
74            token: self.token.clone(),
75        }
76    }
77
78    fn encode_key(&self, key: Vec<u8>, key_type: KeyType) -> String {
79        match key_type {
80            KeyType::EVM => hex::encode(&key),
81            KeyType::Stellar => PrivateKey::from_payload(&key).unwrap().to_string(),
82            KeyType::Solana => bs58::encode(key).into_string(),
83            KeyType::Any => String::from_utf8(key).unwrap(),
84        }
85    }
86
87    fn decode_key(&self, key: String, key_type: KeyType) -> Vec<u8> {
88        match key_type {
89            KeyType::EVM => hex::decode(&key).unwrap(),
90            KeyType::Stellar => PrivateKey::from_string(&key).unwrap().0.to_vec(),
91            KeyType::Solana => bs58::decode(key).into_vec().unwrap(),
92            KeyType::Any => key.into_bytes(),
93        }
94    }
95
96    pub async fn store_secret(
97        &self,
98        id: &str,
99        secret: Vec<u8>,
100        key_type: KeyType,
101    ) -> Result<(), Error> {
102        let url = format!("{}/v1/secret/data/{}", self.base_url, id);
103
104        let body = SecretRequest {
105            data: SecretData {
106                secret: self.encode_key(secret, key_type),
107            },
108        };
109
110        self.client
111            .post(&url)
112            .header("X-Vault-Token", &self.token)
113            .json(&body)
114            .send()
115            .await?;
116
117        Ok(())
118    }
119
120    pub async fn list_secrets(&self) -> Result<Vec<String>, Error> {
121        let url = format!("{}/v1/secret/metadata?list=true", self.base_url);
122        let response = self
123            .client
124            .get(&url)
125            .header("X-Vault-Token", &self.token)
126            .send()
127            .await?
128            .json::<SecretListResponse>()
129            .await?;
130
131        Ok(response.data.keys)
132    }
133
134    pub async fn get_secret(&self, id: &str, key_type: KeyType) -> Result<Option<Vec<u8>>, Error> {
135        let url = format!("{}/v1/secret/data/{}", self.base_url, id);
136        let response: SecretResponse = self
137            .client
138            .get(&url)
139            .header("X-Vault-Token", &self.token)
140            .send()
141            .await?
142            .json()
143            .await?;
144
145        Ok(response
146            .data
147            .and_then(|d| d.data)
148            .map(|d| self.decode_key(d.secret, key_type)))
149    }
150
151    pub async fn delete_secret(&self, id: &str) -> Result<(), Error> {
152        let url = format!("{}/v1/secret/data/{}", self.base_url, id);
153        self.client
154            .delete(&url)
155            .header("X-Vault-Token", &self.token)
156            .send()
157            .await?;
158
159        Ok(())
160    }
161}