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}
53
54pub struct HashicorpVaultClient {
55    client: Client,
56    base_url: String,
57    token: String,
58}
59
60impl HashicorpVaultClient {
61    pub fn new(base_url: &str, token: &str) -> Self {
62      Self {
63        client: Client::new(),
64        base_url: base_url.to_string(),
65        token: token.to_string(),
66      }
67    }
68
69    fn encode_key(&self, key: Vec<u8>, key_type: KeyType) -> String {
70      match key_type {
71        KeyType::EVM => hex::encode(&key),
72        KeyType::Stellar => PrivateKey::from_payload(&key).unwrap().to_string(),
73        KeyType::Solana => bs58::encode(key).into_string(),
74      }
75    }
76
77    fn decode_key(&self, key: String, key_type: KeyType) -> Vec<u8> {
78      match key_type {
79        KeyType::EVM => hex::decode(&key).unwrap(),
80        KeyType::Stellar => PrivateKey::from_string(&key).unwrap().0.to_vec(),
81        KeyType::Solana => bs58::decode(key).into_vec().unwrap(),
82      }
83    }
84 
85    pub fn new_with_client(base_url: &str, token: &str, client: reqwest::Client) -> Self {
86      Self {
87          base_url: base_url.to_string(),
88          token: token.to_string(),
89          client,
90      }
91    }
92
93    pub async fn store_secret(&self, id: &str, secret: Vec<u8>, key_type: KeyType) -> Result<(), Error> {
94      let url = format!("{}/v1/secret/data/{}", self.base_url, id);
95
96      let body = SecretRequest { 
97        data: SecretData { 
98          secret: self.encode_key(secret, key_type)
99        } 
100      };
101      
102      self.client.post(&url)
103        .header("X-Vault-Token", &self.token)
104        .json(&body)
105        .send()
106        .await?;
107      
108      Ok(())
109    }
110
111    pub async fn list_secrets(&self) -> Result<Vec<String>, Error> {
112      let url = format!("{}/v1/secret/metadata?list=true", self.base_url);
113      let response = self.client.get(&url) 
114        .header("X-Vault-Token", &self.token)
115        .send()
116        .await?
117        .json::<SecretListResponse>()
118        .await?;
119      
120      Ok(response.data.keys)
121    }
122
123    pub async fn get_secret(&self, id: &str, key_type: KeyType) -> Result<Option<Vec<u8>>, Error> {
124      let url = format!("{}/v1/secret/data/{}", self.base_url, id);
125      let response: SecretResponse = self.client.get(&url)
126        .header("X-Vault-Token", &self.token)
127        .send()
128        .await?
129        .json()
130        .await?;
131      
132        Ok(response.data
133          .and_then(|d| d.data)
134          .map(|d| self.decode_key(d.secret, key_type)))
135    }
136
137    pub async fn delete_secret(&self, id: &str) -> Result<(), Error> {
138      let url = format!("{}/v1/secret/data/{}", self.base_url, id);
139      self.client.delete(&url)
140        .header("X-Vault-Token", &self.token)
141        .send()
142        .await?;
143      
144      Ok(())
145    }
146}