Skip to main content

confium_store/
backend.rs

1//! Backend trait + compile-time registration.
2//!
3//! The Store is itself a Confium plugin, but the *backends* it ships
4//! with are registered at compile time inside this crate. This keeps the
5//! keystore's own extensibility story separate from the Engine's plugin
6//! registry: the Engine loads the keystore plugin; the keystore in turn
7//! dispatches to one of its registered backends.
8//!
9//! Adding a new in-tree backend is open/closed-compliant: create a new
10//! module under `backends/`, implement [`StoreBackend`], and call
11//! [`register_backend!`](crate::register_backend!). No edit to this file
12//! is required.
13
14use std::collections::HashMap;
15use std::ffi::c_void;
16
17use crate::error::Result;
18
19/// Which compartment an operation targets.
20///
21/// Wire encoding (matches the FFI `compartment` parameter):
22/// - `0` — public, identity-indexed, signed
23/// - `1` — private, key-id-indexed, optionally hardware-backed
24#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
25pub enum Compartment {
26    Public,
27    Private,
28}
29
30impl Compartment {
31    /// Decode the wire value used by the FFI. Unknown values return
32    /// [`crate::error::Error::InvalidCompartment`].
33    pub fn from_wire(value: u32) -> Result<Self> {
34        match value {
35            0 => Ok(Compartment::Public),
36            1 => Ok(Compartment::Private),
37            other => Err(crate::error::InvalidCompartmentSnafu { value: other }.build()),
38        }
39    }
40}
41
42/// Per-backend options. A thin alias over a `String → String` map so
43/// backends can pull path/slot/pin-style configuration without depending
44/// on the Engine's richer options model.
45pub type Options = HashMap<String, String>;
46
47/// A backend factory: knows how to open a connection to a keystore.
48///
49/// Implementations are stateless factories; per-keystore mutable state
50/// lives in the [`StoreInstance`] they produce. `Send + Sync` so the
51/// registry can hold a `&'static dyn StoreBackend` safely.
52pub trait StoreBackend: Send + Sync {
53    /// Wire name advertised to the FFI caller, e.g. `"memory"`,
54    /// `"filesystem"`. ASCII, case-sensitive.
55    fn name(&self) -> &'static str;
56
57    /// Open the backend and return a per-keystore instance handle.
58    fn open(&self, opts: &Options) -> Result<Box<dyn StoreInstance>>;
59}
60
61/// One open keystore connection. All mutation flows through `&mut self`;
62/// reads take `&self` so concurrent get/enumerate is sound when the
63/// underlying backend allows it.
64///
65/// Key material is opaque to the Store: it carries the key as a
66/// `*mut c_void` (the same handle the Engine's `keyfmt` interface
67/// produces). Ownership of that handle stays with the caller that
68/// produced it — backends store the raw pointer and return it verbatim
69/// on get. Lifetime discipline is the caller's responsibility, matching
70/// the rest of the Confium FFI.
71pub trait StoreInstance: Send + Sync {
72    /// Insert a secret key into the private compartment, indexed by
73    /// `key_id`.
74    fn put_secret(&mut self, module: &str, app: &str, key_id: &str, key: *mut c_void)
75    -> Result<()>;
76
77    /// Fetch a secret key from the private compartment by `key_id`.
78    /// Returns [`crate::error::Error::ValueNotFound`] if absent.
79    fn get_secret(&self, module: &str, app: &str, key_id: &str) -> Result<*mut c_void>;
80
81    /// Insert a public key into the public compartment, indexed by
82    /// `identity`, with a detached signature over the identity.
83    fn put_public(
84        &mut self,
85        module: &str,
86        app: &str,
87        identity: &str,
88        key: *mut c_void,
89        sig: &[u8],
90    ) -> Result<()>;
91
92    /// Fetch a public key from the public compartment by `identity`.
93    /// Returns the key handle and the stored signature bytes.
94    fn get_public(&self, module: &str, app: &str, identity: &str)
95    -> Result<(*mut c_void, Vec<u8>)>;
96
97    /// Enumerate entries in one compartment of one `(module, app)`
98    /// scope. Each entry is the opaque key handle paired with its index
99    /// string (`key_id` for private, canonical identity for public).
100    fn enumerate(
101        &self,
102        module: &str,
103        app: &str,
104        compartment: Compartment,
105    ) -> Result<Vec<(*mut c_void, String)>>;
106
107    /// Sign `message` with the remote key named by `key_id`, using the
108    /// provider-specific `algorithm` name (e.g. `"ECDSA_SHA_256"` on
109    /// AWS KMS, `"EC_SIGN_P256_SHA256"` on Cloud KMS, `"ES256"` on Key
110    /// Vault). Input is the raw message — providers that sign digests
111    /// hash it themselves or the backend does. This is the remote-sign
112    /// half of the sign-with-handle contract: backends that hold keys
113    /// out-of-process (cloud KMS, PKCS#11, TPM) implement it; local
114    /// backends keep the default.
115    fn sign(
116        &self,
117        _module: &str,
118        _app: &str,
119        _key_id: &str,
120        _algorithm: &str,
121        _message: &[u8],
122    ) -> Result<Vec<u8>> {
123        Err(crate::error::Error::NotImplemented {
124            what: "sign (backend does not support remote signing)",
125        })
126    }
127}
128
129// --- link-time registry --------------------------------------------------
130
131/// Wrapper around `&'static dyn StoreBackend` so a backend can be
132/// registered with `inventory` and discovered at link time.
133pub struct RegisteredBackend {
134    pub backend: &'static dyn StoreBackend,
135}
136
137inventory::collect!(RegisteredBackend);
138
139/// Iterate every backend registered at link time.
140pub fn iter() -> impl Iterator<Item = &'static dyn StoreBackend> {
141    inventory::iter::<RegisteredBackend>().map(|r| r.backend)
142}
143
144/// Look up a backend by wire name. Returns
145/// [`crate::error::Error::UnknownBackend`] if no registered backend
146/// matches.
147pub fn find(name: &str) -> Result<&'static dyn StoreBackend> {
148    iter()
149        .find(|b| b.name() == name)
150        .ok_or_else(|| crate::error::UnknownBackendSnafu { name }.build())
151}
152
153/// Submit a backend to the link-time registry.
154///
155/// ```no_run
156/// # use confium_store::backend::{StoreBackend, StoreInstance, Options};
157/// # use confium_store::error::Result;
158/// # use std::ffi::c_void;
159/// # struct MyBackend;
160/// # impl StoreBackend for MyBackend {
161/// #     fn name(&self) -> &'static str { "mine" }
162/// #     fn open(&self, _: &Options) -> Result<Box<dyn StoreInstance>> { unimplemented!() }
163/// # }
164/// confium_store::register_backend!(MyBackend);
165/// ```
166#[macro_export]
167macro_rules! register_backend {
168    ($backend:ident) => {
169        ::inventory::submit! {
170            $crate::backend::RegisteredBackend { backend: &$backend }
171        }
172    };
173}