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