Skip to main content

basil_core/core/backend/
vault.rs

1// SPDX-FileCopyrightText: 2026 OpenBasil Contributors
2//
3// SPDX-License-Identifier: Apache-2.0
4
5//! Vault-compatible (`HashiCorp` Vault or `OpenBao`) `transit` backend,
6//! authenticated with a **static token** (`X-Vault-Token`).
7//!
8//! All the transit wire logic lives in [`super::transit::TransitClient`]; this
9//! backend just supplies a fixed token. For SPIFFE/SVID-based auth against the
10//! same engine, see [`super::spiffe::SpiffeVaultBackend`].
11
12use async_trait::async_trait;
13use zeroize::Zeroizing;
14
15use basil_proto::{AeadAlgorithm, CiphertextEnvelope, KeyMaterial, KeyType};
16
17use super::pki::PkiClient;
18use super::transit::{TransitClient, transit_aead_type};
19use super::{
20    Backend, BackendError, KeyMetadata, KvValue, NewKey, PublicKey, SignOptions, X509Bundle,
21    X509Svid,
22};
23
24pub struct VaultBackend {
25    transit: TransitClient,
26    pki: PkiClient,
27    token: String,
28}
29
30impl VaultBackend {
31    /// Build a backend talking to the vault at `addr` with `token`, using the
32    /// transit engine mounted at `mount`.
33    pub fn new(
34        addr: impl Into<String>,
35        token: impl Into<String>,
36        mount: impl Into<String>,
37    ) -> Result<Self, BackendError> {
38        crate::ensure_crypto_provider();
39        let http = reqwest::Client::builder()
40            .build()
41            .map_err(|e| BackendError::Transport(e.to_string()))?;
42        let addr = addr.into();
43        Ok(Self {
44            transit: TransitClient::new(http.clone(), &addr, &mount.into()),
45            pki: PkiClient::new(http, &addr),
46            token: token.into(),
47        })
48    }
49}
50
51#[async_trait]
52impl Backend for VaultBackend {
53    fn kind(&self) -> &'static str {
54        "vault"
55    }
56
57    async fn new_key(&self, key_type: KeyType) -> Result<NewKey, BackendError> {
58        self.transit.new_key(&self.token, key_type).await
59    }
60
61    async fn create_named_key(
62        &self,
63        key_id: &str,
64        key_type: KeyType,
65    ) -> Result<NewKey, BackendError> {
66        self.transit
67            .create_named_key(&self.token, key_id, key_type)
68            .await
69    }
70
71    async fn create_named_aead(
72        &self,
73        key_id: &str,
74        aead: AeadAlgorithm,
75    ) -> Result<(), BackendError> {
76        self.transit
77            .create_named_aead(&self.token, key_id, transit_aead_type(aead))
78            .await
79    }
80
81    async fn public_key(&self, key_id: &str) -> Result<Vec<u8>, BackendError> {
82        self.transit.read_public_key(&self.token, key_id).await
83    }
84
85    async fn public_key_with_meta(&self, key_id: &str) -> Result<PublicKey, BackendError> {
86        self.transit
87            .read_public_key_with_meta(&self.token, key_id)
88            .await
89    }
90
91    async fn key_metadata(&self, key_id: &str) -> Result<KeyMetadata, BackendError> {
92        self.transit.read_key_metadata(&self.token, key_id).await
93    }
94
95    async fn public_keys(
96        &self,
97        key_id: &str,
98    ) -> Result<std::collections::BTreeMap<u32, Vec<u8>>, BackendError> {
99        self.transit.read_public_keys(&self.token, key_id).await
100    }
101
102    async fn import(
103        &self,
104        key_id: &str,
105        key_type: KeyType,
106        material: &KeyMaterial,
107    ) -> Result<NewKey, BackendError> {
108        self.transit
109            .import(&self.token, key_id, key_type, material)
110            .await
111    }
112
113    async fn sign(&self, key_id: &str, message: &[u8]) -> Result<Vec<u8>, BackendError> {
114        self.transit.sign(&self.token, key_id, message).await
115    }
116
117    async fn sign_with_options(
118        &self,
119        key_id: &str,
120        message: &[u8],
121        options: SignOptions,
122    ) -> Result<Vec<u8>, BackendError> {
123        self.transit
124            .sign_with_options(&self.token, key_id, message, options)
125            .await
126    }
127
128    async fn verify(
129        &self,
130        key_id: &str,
131        message: &[u8],
132        signature: &[u8],
133    ) -> Result<bool, BackendError> {
134        self.transit
135            .verify(&self.token, key_id, message, signature)
136            .await
137    }
138
139    async fn verify_with_options(
140        &self,
141        key_id: &str,
142        message: &[u8],
143        signature: &[u8],
144        options: SignOptions,
145    ) -> Result<bool, BackendError> {
146        self.transit
147            .verify_with_options(&self.token, key_id, message, signature, options)
148            .await
149    }
150
151    async fn encrypt(
152        &self,
153        key_id: &str,
154        algorithm: AeadAlgorithm,
155        plaintext: &[u8],
156        aad: Option<&[u8]>,
157    ) -> Result<CiphertextEnvelope, BackendError> {
158        self.transit
159            .encrypt(&self.token, key_id, algorithm, plaintext, aad)
160            .await
161    }
162
163    async fn decrypt(
164        &self,
165        key_id: &str,
166        envelope: &CiphertextEnvelope,
167        aad: Option<&[u8]>,
168    ) -> Result<Vec<u8>, BackendError> {
169        self.transit
170            .decrypt(&self.token, key_id, envelope, aad)
171            .await
172    }
173
174    async fn rotate(&self, key_id: &str) -> Result<u32, BackendError> {
175        self.transit.rotate(&self.token, key_id).await
176    }
177
178    async fn kv_get(&self, key_id: &str, version: Option<u32>) -> Result<KvValue, BackendError> {
179        self.transit.kv_get(&self.token, key_id, version).await
180    }
181
182    async fn kv_get_secret(
183        &self,
184        key_id: &str,
185        version: Option<u32>,
186    ) -> Result<Zeroizing<Vec<u8>>, BackendError> {
187        self.transit
188            .kv_get_secret(&self.token, key_id, version)
189            .await
190    }
191
192    async fn kv_put(&self, key_id: &str, value: &[u8]) -> Result<u32, BackendError> {
193        self.transit.kv_put(&self.token, key_id, value).await
194    }
195
196    async fn configure_versions(
197        &self,
198        key_id: &str,
199        min_decryption_version: Option<u32>,
200        min_available_version: Option<u32>,
201    ) -> Result<(), BackendError> {
202        self.transit
203            .configure_versions(
204                &self.token,
205                key_id,
206                min_decryption_version,
207                min_available_version,
208            )
209            .await
210    }
211
212    async fn issue_x509_svid(
213        &self,
214        key_id: &str,
215        spiffe_id: &str,
216        ttl_seconds: u64,
217    ) -> Result<X509Svid, BackendError> {
218        self.pki
219            .issue_x509_svid(&self.token, key_id, spiffe_id, ttl_seconds)
220            .await
221    }
222
223    async fn issue_x509_cert(
224        &self,
225        key_id: &str,
226        request: &super::X509CertRequest,
227    ) -> Result<X509Svid, BackendError> {
228        self.pki.issue_x509_cert(&self.token, key_id, request).await
229    }
230
231    async fn x509_bundle(&self, key_id: &str) -> Result<X509Bundle, BackendError> {
232        self.pki.x509_bundle(&self.token, key_id).await
233    }
234}