oz_keystore/hashicorp/
vault.rs1use 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}