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}
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}