Skip to main content

dig_keystore/hardware/
backend.rs

1//! [`HardwareBoundBackend`] — a [`KeychainBackend`] decorator that binds stored
2//! blobs to the host's hardware trusted component.
3//!
4//! # A tier above, not a replacement
5//!
6//! This wraps any existing backend (file, OS credential store, memory). What it
7//! stores is the *already sealed* keystore blob inside a hardware-wrapped
8//! envelope, so the AES-256-GCM + Argon2id passphrase envelope stays the floor
9//! on every path — a degraded host writes the same bytes it always wrote, never
10//! a bare secret.
11//!
12//! # The tier is decided once, by use
13//!
14//! [`HardwareBoundBackend::new`] probes the provider **and then self-tests it**
15//! (wrap a random key, unwrap it, require the round-trip) before it will report
16//! [`ProtectionTier::Hardware`]. A probe is a claim; the self-test is what makes
17//! the claim refutable. Resolving at construction also means "is this keystore
18//! hardware-bound?" is a settled fact rather than a failure that surfaces
19//! mid-`unlock`.
20
21use std::sync::Arc;
22
23use super::envelope::{self, Envelope};
24use super::provider::{ContentKey, HardwareProvider};
25use super::tier::{DegradeReason, HardwareKind, HardwarePolicy, HardwareProbe, ProtectionTier};
26use crate::backend::{BackendKey, Exclusivity, KeychainBackend};
27use crate::error::{KeystoreError, Result};
28
29/// A [`KeychainBackend`] that hardware-binds every blob it stores, degrading to
30/// the underlying software envelope when the host has no usable hardware.
31///
32/// # Example
33///
34/// ```no_run
35/// use std::sync::Arc;
36/// use dig_keystore::backend::{BackendKey, FileBackend, KeychainBackend};
37/// use dig_keystore::hardware::{HardwareBoundBackend, HardwarePolicy};
38///
39/// let inner = FileBackend::new("/var/lib/dig/keys");
40/// // No provider available in this build: opens, and says so honestly.
41/// let backend = HardwareBoundBackend::new(inner, None, HardwarePolicy::Optional)?;
42///
43/// // What the HOST can do — the tier a new write would get.
44/// println!("host: {}", backend.tier());
45///
46/// // What protects THIS key — the only answer fit to show a user, because a
47/// // capable host can still hold a keystore that was never wrapped.
48/// let key = BackendKey::new("identity");
49/// let tier = backend.blob_tier(&key)?;
50/// if tier.is_hardware_bound() {
51///     println!("this key is {tier}");
52/// } else {
53///     // Never claim protection this key does not have.
54///     println!("this key is {tier}");
55/// }
56/// # Ok::<(), dig_keystore::KeystoreError>(())
57/// ```
58pub struct HardwareBoundBackend {
59    /// The storage this decorates.
60    inner: Arc<dyn KeychainBackend>,
61    /// The self-tested provider — present only when [`tier`](Self::tier) is
62    /// [`ProtectionTier::Hardware`], so the two can never disagree.
63    provider: Option<Arc<dyn HardwareProvider>>,
64    /// The truthful, settled protection tier.
65    tier: ProtectionTier,
66}
67
68impl HardwareBoundBackend {
69    /// Decorate `inner`, resolving the protection tier once.
70    ///
71    /// Pass `provider = None` to store through `inner` unchanged while reporting
72    /// [`DegradeReason::NotRequested`].
73    ///
74    /// # Errors
75    ///
76    /// Fails closed rather than silently degrading, per `policy`:
77    /// - [`HardwarePolicy::Required`] — any outcome short of self-tested hardware
78    ///   is [`KeystoreError::HardwareRequired`].
79    /// - [`HardwarePolicy::Preferred`] (default) — a *confident* absence degrades,
80    ///   but an [`Indeterminate`](HardwareProbe::Indeterminate) probe is
81    ///   [`KeystoreError::HardwareProbeIndeterminate`]: "I could not determine
82    ///   whether this host has hardware" must not be downgraded into "it has
83    ///   none", which would quietly strip protection from a machine that has it.
84    /// - [`HardwarePolicy::Optional`] — always opens, always reports the reason.
85    pub fn new<B: KeychainBackend>(
86        inner: B,
87        provider: Option<Arc<dyn HardwareProvider>>,
88        policy: HardwarePolicy,
89    ) -> Result<Self> {
90        Self::with_inner(Arc::new(inner), provider, policy)
91    }
92
93    /// As [`new`](Self::new), for an already shared backend.
94    pub fn with_inner(
95        inner: Arc<dyn KeychainBackend>,
96        provider: Option<Arc<dyn HardwareProvider>>,
97        policy: HardwarePolicy,
98    ) -> Result<Self> {
99        let tier = resolve_tier(provider.as_deref(), policy)?;
100        // Hold the provider only where the tier actually claims hardware, so a
101        // degraded backend cannot accidentally reach for it later.
102        let provider = if tier.is_hardware_bound() {
103            provider
104        } else {
105            None
106        };
107        Ok(Self {
108            inner,
109            provider,
110            tier,
111        })
112    }
113
114    /// Decorate `inner` with a settled software tier and NO provider.
115    ///
116    /// For a caller that has already established why hardware is unavailable and
117    /// would otherwise lose that reason: passing `provider = None` to
118    /// [`new`](Self::new) reports [`DegradeReason::NotRequested`], which on a
119    /// host that *was* inspected and found wanting is simply untrue.
120    ///
121    /// This constructor cannot claim hardware — it takes a
122    /// [`DegradeReason`] and produces a [`ProtectionTier::Software`] — so it
123    /// widens what can be reported honestly without widening what can be
124    /// reported at all.
125    pub fn degraded<B: KeychainBackend>(inner: B, reason: DegradeReason) -> Self {
126        Self {
127            inner: Arc::new(inner),
128            provider: None,
129            tier: ProtectionTier::Software(reason),
130        }
131    }
132
133    /// What this **host** is bound to — the tier every *newly written* blob gets.
134    ///
135    /// This is a statement about the machine, not about any particular stored
136    /// key. On a hardware-capable host it reports `Hardware` even if a given
137    /// keystore predates hardware binding and is still a bare §3 blob, because a
138    /// capable host does not retroactively protect bytes already at rest.
139    ///
140    /// **Before telling a user that a specific key is hardware-protected, ask
141    /// [`blob_tier`](Self::blob_tier) instead.** Rendering "protected by your
142    /// TPM" from this method would claim copy-resistance that an unwrapped
143    /// legacy blob does not have.
144    pub fn tier(&self) -> &ProtectionTier {
145        &self.tier
146    }
147
148    /// What protects **the key material stored at `key`**, read from the blob
149    /// itself.
150    ///
151    /// This is the question a UI actually has, and it is not the same as
152    /// [`tier`](Self::tier): a hardware-capable host can hold a keystore written
153    /// before hardware binding existed, which is protected by the passphrase
154    /// envelope alone and *does* open on another machine. Answering from the
155    /// stored bytes is what keeps that distinction honest.
156    ///
157    /// The tier reported is the blob's own, independent of this host: a blob
158    /// sealed by an Apple Secure Enclave reads as `Hardware(MacSecureEnclave)`
159    /// even on Windows. Whether *this* host can open it is a separate question,
160    /// answered by [`read`](KeychainBackend::read).
161    ///
162    /// # Errors
163    ///
164    /// - The inner backend's error if `key` cannot be read (e.g. `NotFound`).
165    /// - [`KeystoreError::MalformedEnvelope`] if the blob claims to be an
166    ///   envelope but is structurally invalid.
167    /// - [`KeystoreError::UnknownHardwareClass`] if it was sealed by hardware
168    ///   this build cannot name.
169    ///
170    /// Both error cases **fail closed**: a wrapped blob is never reported as
171    /// software-protected just because this build cannot fully classify it.
172    /// Guessing in either direction is what this method exists to avoid.
173    pub fn blob_tier(&self, key: &BackendKey) -> Result<ProtectionTier> {
174        let bytes = self.inner.read(key)?;
175
176        // Not an envelope — the passphrase envelope is all that protects it,
177        // whatever this host is capable of.
178        if !envelope::is_envelope(&bytes) {
179            return Ok(ProtectionTier::Software(DegradeReason::BlobNotWrapped));
180        }
181
182        let env = envelope::decode(&bytes)?;
183        match env.hardware_kind() {
184            Some(kind) => Ok(ProtectionTier::Hardware(kind)),
185            None => Err(KeystoreError::UnknownHardwareClass {
186                wire_id: env.hardware_wire_id,
187            }),
188        }
189    }
190
191    /// The underlying storage, for callers that need it directly.
192    pub fn inner(&self) -> &Arc<dyn KeychainBackend> {
193        &self.inner
194    }
195
196    /// Return the key at `key` to the portable software form, so it opens on a
197    /// host that no longer has this hardware. Returns the tier the blob is in
198    /// afterwards.
199    ///
200    /// # Why this exists
201    ///
202    /// Hardware binding makes the trusted component a **second required
203    /// factor**. A TPM is cleared by a firmware update, a mainboard swap or a
204    /// BIOS reset — routine events — and after one the correct passphrase is no
205    /// longer enough: the sealed blob is unopenable, by design and permanently.
206    /// `unbind` is the way back, and it **must be taken while the hardware still
207    /// answers**. There is no recovery afterwards; that is what non-exportable
208    /// custody means.
209    ///
210    /// Unbinding does not expose a secret. What it stores is the AES-256-GCM +
211    /// Argon2id passphrase envelope that was always the floor (`SPEC.md` §3) —
212    /// the same bytes a host with no hardware writes. It gives up cross-machine
213    /// binding, nothing else.
214    ///
215    /// Nothing is written until the plaintext is in hand, and the result is
216    /// verified from storage before this reports success: telling a user their
217    /// seed is portable when it is not is the one failure here with a
218    /// catastrophic follow-on action, since they may then clear the TPM.
219    ///
220    /// # Errors
221    ///
222    /// - [`KeystoreError::NotHardwareBound`] — the blob is wrapped but this
223    ///   backend has no provider to open it (the hardware is already gone).
224    /// - [`KeystoreError::HardwareUnwrapFailed`] — the hardware would not open
225    ///   it. The stored bytes are left exactly as they were.
226    /// - [`KeystoreError::HardwareStillBound`] — the write did not take.
227    pub fn unbind(&self, key: &BackendKey) -> Result<ProtectionTier> {
228        let bytes = self.inner.read(key)?;
229        if !envelope::is_envelope(&bytes) {
230            return Ok(ProtectionTier::Software(DegradeReason::BlobNotWrapped));
231        }
232
233        let provider = self
234            .provider
235            .as_deref()
236            .ok_or_else(|| KeystoreError::NotHardwareBound {
237                tier: self.tier.to_string(),
238            })?;
239
240        // Unwrap BEFORE writing anything: a failure here must leave the envelope
241        // untouched, so hardware that comes back (a swapped-back board, a
242        // re-enrolled key) still finds the blob it sealed.
243        let plain = self.unwrap_blob(provider, &bytes)?;
244
245        // Write through `inner`, NOT through `self.write` — which, in the
246        // hardware tier, would seal these bytes straight back into a new
247        // envelope and report a successful unbind that changed nothing.
248        self.inner.write(key, &plain)?;
249
250        // Confirm from storage. A store that accepts a write and keeps the old
251        // bytes (a full disk, a read-only mount) would otherwise leave the user
252        // believing it is safe to retire the trusted component.
253        if envelope::is_envelope(&self.inner.read(key)?) {
254            return Err(KeystoreError::HardwareStillBound { key: key.0.clone() });
255        }
256        Ok(ProtectionTier::Software(DegradeReason::BlobNotWrapped))
257    }
258
259    /// Bind the key at `key` to this host's hardware, migrating a blob written
260    /// before hardware binding existed. Returns the tier the blob is in
261    /// afterwards.
262    ///
263    /// Already-bound blobs are left alone: sealing an envelope inside a second
264    /// envelope would produce a blob whose unwrap yields another envelope, which
265    /// nothing can open.
266    ///
267    /// **This is the operation that can strand a seed**, because it overwrites
268    /// the only copy with bytes only this hardware can open. So the new blob is
269    /// read back from storage and reopened through the hardware BEFORE the call
270    /// reports success, and the previous bytes are restored if it cannot be. See
271    /// [`unbind`](Self::unbind) for the way back out.
272    ///
273    /// # Errors
274    ///
275    /// - [`KeystoreError::NotHardwareBound`] — this backend resolved a software
276    ///   tier, so there is no hardware to bind to.
277    /// - [`KeystoreError::HardwareWrapFailed`] / [`KeystoreError::HardwareUnwrapFailed`]
278    ///   — the seal could not be made, or could not be proven reopenable. The
279    ///   previous bytes are restored in both cases.
280    pub fn bind(&self, key: &BackendKey) -> Result<ProtectionTier> {
281        let bytes = self.inner.read(key)?;
282        let provider = self
283            .provider
284            .as_deref()
285            .ok_or_else(|| KeystoreError::NotHardwareBound {
286                tier: self.tier.to_string(),
287            })?;
288
289        if envelope::is_envelope(&bytes) {
290            return Ok(ProtectionTier::Hardware(provider.kind()));
291        }
292
293        let sealed = self.wrap_blob(provider, &bytes)?;
294        self.inner.write(key, &sealed)?;
295
296        // Prove the migration from STORAGE, not from the value just computed: a
297        // seal this hardware cannot reopen has destroyed the only copy, and a
298        // success returned over that is the worst outcome this module has.
299        match self.reopens_to(provider, key, &bytes) {
300            Ok(()) => Ok(ProtectionTier::Hardware(provider.kind())),
301            Err(e) => {
302                // Put the openable bytes back. The restore is best-effort, but
303                // its failure must not mask the reason the bind was rejected.
304                let _ = self.inner.write(key, &bytes);
305                Err(e)
306            }
307        }
308    }
309
310    /// Whether the blob now stored at `key` unwraps, through the hardware, to
311    /// exactly `expected`.
312    fn reopens_to(
313        &self,
314        provider: &dyn HardwareProvider,
315        key: &BackendKey,
316        expected: &[u8],
317    ) -> Result<()> {
318        let stored = self.inner.read(key)?;
319        let reopened = self.unwrap_blob(provider, &stored)?;
320        if reopened != expected {
321            return Err(KeystoreError::HardwareUnwrapFailed {
322                detail: "the newly sealed blob did not reopen to the original bytes".to_owned(),
323            });
324        }
325        Ok(())
326    }
327
328    /// Seal `blob` into a hardware envelope. Only reachable in the hardware tier.
329    fn wrap_blob(&self, provider: &dyn HardwareProvider, blob: &[u8]) -> Result<Vec<u8>> {
330        let mut rng = rand_core::OsRng;
331        let content_key = envelope::random_content_key(&mut rng);
332        let nonce = envelope::random_nonce(&mut rng);
333        let wrapped_key = provider.wrap_key(&content_key)?;
334        envelope::encode(provider.kind(), &content_key, &wrapped_key, &nonce, blob)
335    }
336
337    /// Open a hardware envelope, requiring the hardware that sealed it.
338    fn unwrap_blob(&self, provider: &dyn HardwareProvider, bytes: &[u8]) -> Result<Vec<u8>> {
339        let env = envelope::decode(bytes)?;
340        require_matching_hardware(&env, provider.kind())?;
341        let content_key: ContentKey = provider.unwrap_key(&env.wrapped_key)?;
342        Ok(env.open(&content_key)?.to_vec())
343    }
344}
345
346/// Reject an envelope sealed by hardware other than ours before spending a
347/// hardware round-trip on it.
348///
349/// Covers the unrecognised-wire-id case as well as a known-but-different class:
350/// both mean "not openable by this host's component", which is a different fact
351/// from a corrupt file.
352fn require_matching_hardware(env: &Envelope, ours: HardwareKind) -> Result<()> {
353    match env.hardware_kind() {
354        Some(kind) if kind == ours => Ok(()),
355        Some(kind) => Err(KeystoreError::HardwareKindMismatch {
356            expected: ours.label(),
357            found: kind.label(),
358        }),
359        // Not a hardware refusal and not corruption — a class this build cannot
360        // name. Reported as such so `HardwareUnwrapFailed` keeps meaning exactly
361        // "the hardware refused".
362        None => Err(KeystoreError::UnknownHardwareClass {
363            wire_id: env.hardware_wire_id,
364        }),
365    }
366}
367
368/// Decide the protection tier from a provider's probe, its custody claim, and a
369/// live self-test — then apply `policy` to any negative outcome.
370fn resolve_tier(
371    provider: Option<&dyn HardwareProvider>,
372    policy: HardwarePolicy,
373) -> Result<ProtectionTier> {
374    match provider {
375        Some(provider) => resolve_provider_tier(provider, policy),
376        None => degrade_under(DegradeReason::NotRequested, policy),
377    }
378}
379
380/// Resolve the protection tier ONE provider earns on this host: probe it, then
381/// self-test it, then apply `policy` to whatever came back.
382///
383/// This is the same decision [`HardwareBoundBackend::new`] makes internally, and
384/// it is public so that a *ladder* of candidate providers — the `hardware/`
385/// workspace member (dig_ecosystem #1693) walks one — can ask the question per
386/// candidate without re-deriving the self-test. A second implementation of
387/// "is this provider trustworthy?" is precisely the rival that would eventually
388/// disagree with this one, and the disagreement would be silent.
389///
390/// A provider is never taken at its word: `probe()` is a claim, and the wrap /
391/// unwrap round-trip run here is what makes the claim refutable.
392///
393/// # Errors
394///
395/// Exactly as [`HardwareBoundBackend::new`]: fails closed per `policy` rather
396/// than silently degrading.
397pub fn resolve_provider_tier(
398    provider: &dyn HardwareProvider,
399    policy: HardwarePolicy,
400) -> Result<ProtectionTier> {
401    match provider.probe() {
402        HardwareProbe::Absent => degrade(DegradeReason::NoHardwarePresent, policy),
403
404        // "Could not determine" is its own outcome, and the only one that is an
405        // error under the default policy.
406        HardwareProbe::Indeterminate { detail } => {
407            if policy.allows_indeterminate_degrade() {
408                degrade(DegradeReason::ProbeIndeterminate { detail }, policy)
409            } else {
410                Err(KeystoreError::HardwareProbeIndeterminate { detail })
411            }
412        }
413
414        HardwareProbe::Available(kind) => match verify_hardware(provider, kind) {
415            Ok(()) => Ok(ProtectionTier::Hardware(kind)),
416            Err(detail) => degrade(DegradeReason::HardwareUnusable { detail }, policy),
417        },
418    }
419}
420
421/// Apply `policy` to a settled [`DegradeReason`], yielding a tier or the error
422/// the policy requires.
423///
424/// Public for the same reason as [`resolve_provider_tier`]: a candidate ladder
425/// applies the caller policy ONCE, to the reason it finally settles on, and must
426/// apply exactly the rule this crate applies rather than a lookalike.
427///
428/// # Both fail-closed rules, not just the obvious one
429///
430/// [`Required`](HardwarePolicy::Required) rejecting every software outcome is the
431/// visible rule. The second one is easier to lose: under the default
432/// [`Preferred`](HardwarePolicy::Preferred), a reason of
433/// [`ProbeIndeterminate`](DegradeReason::ProbeIndeterminate) is an **error**, not
434/// a degrade — otherwise a transient probe failure silently strips hardware
435/// protection from a machine that has it, and the resulting software blob then
436/// opens anywhere.
437///
438/// The private helper this wraps enforces only the first rule, because its one
439/// caller had already branched on the second. Anything reaching this function has
440/// not, so both are applied here.
441///
442/// # Errors
443///
444/// [`KeystoreError::HardwareRequired`] under `Required`;
445/// [`KeystoreError::HardwareProbeIndeterminate`] under `Preferred` when the host
446/// could not be inspected.
447pub fn degrade_under(reason: DegradeReason, policy: HardwarePolicy) -> Result<ProtectionTier> {
448    if let DegradeReason::ProbeIndeterminate { detail } = &reason {
449        if !policy.allows_indeterminate_degrade() && policy.allows_degrade() {
450            return Err(KeystoreError::HardwareProbeIndeterminate {
451                detail: detail.clone(),
452            });
453        }
454    }
455    degrade(reason, policy)
456}
457
458/// Refute or confirm a provider's "hardware is available" claim.
459///
460/// Three ways the claim fails, all of which must land on
461/// [`DegradeReason::HardwareUnusable`] rather than a hardware tier:
462/// a provider that disagrees with itself about which component it binds to; a
463/// wrapping key that is not actually non-exportable (which buys none of the
464/// cross-machine binding the tier promises); and a wrap/unwrap round-trip that
465/// does not reproduce the key.
466fn verify_hardware(
467    provider: &dyn HardwareProvider,
468    probed: HardwareKind,
469) -> std::result::Result<(), String> {
470    if provider.kind() != probed {
471        return Err(format!(
472            "provider binds {} but probed {}",
473            provider.kind().label(),
474            probed.label()
475        ));
476    }
477
478    if !provider.custody().is_hardware_grade() {
479        return Err(format!(
480            "wrapping key custody is {:?}, not NonExportable",
481            provider.custody()
482        ));
483    }
484
485    let mut rng = rand_core::OsRng;
486    let probe_key = envelope::random_content_key(&mut rng);
487    let wrapped = provider
488        .wrap_key(&probe_key)
489        .map_err(|e| format!("self-test wrap failed: {e}"))?;
490    if wrapped.is_empty() {
491        return Err("self-test wrap produced no wrapped key".to_owned());
492    }
493    if wrapped.as_slice() == probe_key.as_slice() {
494        return Err("self-test wrap returned the content key verbatim".to_owned());
495    }
496    let recovered = provider
497        .unwrap_key(&wrapped)
498        .map_err(|e| format!("self-test unwrap failed: {e}"))?;
499    if recovered.as_slice() != probe_key.as_slice() {
500        return Err("self-test round-trip did not reproduce the key".to_owned());
501    }
502    Ok(())
503}
504
505/// Apply `policy` to a negative outcome: degrade with the reason, or fail closed.
506fn degrade(reason: DegradeReason, policy: HardwarePolicy) -> Result<ProtectionTier> {
507    if policy.allows_degrade() {
508        Ok(ProtectionTier::Software(reason))
509    } else {
510        Err(KeystoreError::HardwareRequired { reason })
511    }
512}
513
514impl std::fmt::Debug for HardwareBoundBackend {
515    /// Redacted: reports the tier (which is not sensitive and is the point of
516    /// the type) but never the inner store or any key material.
517    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
518        f.debug_struct("HardwareBoundBackend")
519            .field("tier", &self.tier)
520            .field("inner", &"<redacted>")
521            .finish()
522    }
523}
524
525impl KeychainBackend for HardwareBoundBackend {
526    /// Read a blob, unwrapping it when it carries a hardware envelope.
527    ///
528    /// A blob **without** the envelope prefix is returned untouched, which is
529    /// what lets every keystore written before this feature — and any future
530    /// inner format — keep opening (§5.1).
531    ///
532    /// A blob **with** an envelope that this host cannot open is an error, never
533    /// the raw envelope bytes: an envelope copied to a machine without the
534    /// sealing hardware must fail loudly rather than hand back ciphertext that a
535    /// caller would then try to parse as a keystore.
536    fn read(&self, key: &BackendKey) -> Result<Vec<u8>> {
537        let bytes = self.inner.read(key)?;
538        if !envelope::is_envelope(&bytes) {
539            return Ok(bytes);
540        }
541        match self.provider.as_deref() {
542            Some(provider) => self.unwrap_blob(provider, &bytes),
543            None => Err(KeystoreError::NotHardwareBound {
544                tier: self.tier.to_string(),
545            }),
546        }
547    }
548
549    /// Write a blob, sealing it into a hardware envelope in the hardware tier
550    /// and passing the software-sealed bytes straight through otherwise.
551    fn write(&self, key: &BackendKey, data: &[u8]) -> Result<()> {
552        match self.provider.as_deref() {
553            Some(provider) => {
554                let sealed = self.wrap_blob(provider, data)?;
555                self.inner.write(key, &sealed)
556            }
557            None => self.inner.write(key, data),
558        }
559    }
560
561    /// Establish a blob, sealing it into a hardware envelope first when a
562    /// provider is in use — the same transformation [`write`](Self::write)
563    /// applies, so a record established here and one written there are the same
564    /// shape.
565    ///
566    /// Exclusivity is entirely the inner backend's, and so is the reported
567    /// claim: wrapping changes the bytes, never who wins a race for the name.
568    fn write_new(&self, key: &BackendKey, data: &[u8]) -> Result<()> {
569        match self.provider.as_deref() {
570            Some(provider) => {
571                let sealed = self.wrap_blob(provider, data)?;
572                self.inner.write_new(key, &sealed)
573            }
574            None => self.inner.write_new(key, data),
575        }
576    }
577
578    fn write_new_exclusivity(&self) -> Exclusivity {
579        self.inner.write_new_exclusivity()
580    }
581
582    fn delete(&self, key: &BackendKey) -> Result<()> {
583        self.inner.delete(key)
584    }
585
586    fn list(&self, prefix: &str) -> Result<Vec<BackendKey>> {
587        self.inner.list(prefix)
588    }
589
590    fn exists(&self, key: &BackendKey) -> Result<bool> {
591        self.inner.exists(key)
592    }
593}