basil_core/core/seal/unlock/mod.rs
1// SPDX-FileCopyrightText: 2026 OpenBasil Contributors
2//
3// SPDX-License-Identifier: Apache-2.0
4
5//! Unlock-method abstraction (§3 of `designs/unlock-and-bundle.html`).
6//!
7//! One [`UnlockMethod`] = the logic behind one slot kind. A method turns its
8//! non-secret [`MethodParams`](super::format::MethodParams) plus an out-of-band
9//! secret (`YubiKey` touch / BIP39 phrase / passphrase file) into the ability to unwrap
10//! *this slot's* `KekWrap`, recovering the 32-byte master KEK. Every fallible
11//! step returns a `Result`; there is no `unwrap`/`expect`/panicking index on any
12//! path (§1.3).
13
14use super::{MasterKek, format};
15
16#[cfg(feature = "unlock-age-yubikey")]
17pub mod age_yubikey;
18#[cfg(feature = "unlock-bip39")]
19pub mod bip39;
20mod kdf;
21pub mod passphrase;
22pub mod tpm;
23
24/// Errors any unlock method may return (§3).
25#[derive(Debug, thiserror::Error)]
26pub enum UnlockError {
27 /// Method not usable right now on this host (no token, no key file, …).
28 #[error("method unavailable: {0}")]
29 Unavailable(String),
30
31 /// Wrong PIN / phrase / passphrase: authentication of the wrap failed.
32 #[error("authentication failed")]
33 AuthFailed,
34
35 /// Reserved method (e.g. TPM) not yet implemented. Fails closed.
36 #[error("method not implemented: {0}")]
37 NotImplemented(format::MethodKind),
38
39 /// The slot params did not match the method (malformed bundle).
40 #[error("slot params mismatch: {0}")]
41 ParamsMismatch(String),
42
43 /// A crypto/KDF failure.
44 #[error("crypto: {0}")]
45 Crypto(String),
46
47 /// An I/O failure (reading a key file, invoking a plugin).
48 #[error("io: {0}")]
49 Io(#[from] std::io::Error),
50}
51
52/// One unlock method = the logic behind one slot kind (§3).
53///
54/// Implementations are stateless w.r.t. the master KEK; they only know how to
55/// turn their `params` + an out-of-band secret into the ability to unwrap this
56/// slot's `KekWrap`, or to build a fresh wrap over a given KEK.
57pub trait UnlockMethod: Send + Sync {
58 /// Which slot kind this method handles.
59 fn kind(&self) -> format::MethodKind;
60
61 /// Is this method usable right now on this host? (token present, key file
62 /// readable, phrase source available). Used to order attempts and report
63 /// status.
64 fn available(&self) -> bool;
65
66 /// Recover the master KEK from one slot, performing whatever interaction the
67 /// method needs. Returns the unwrapped KEK or an [`UnlockError`] (no panic).
68 ///
69 /// # Errors
70 /// Any unlock failure (auth, unavailable, crypto, …), failing closed.
71 fn recover_kek(&self, slot: &format::Slot, header_aad: &[u8])
72 -> Result<MasterKek, UnlockError>;
73
74 /// Build a fresh `(params, wrap)` for this method over an existing master
75 /// KEK (used by `init` / add-slot / re-seal). `slot_id` is the id the new
76 /// slot will carry; it is bound into the KEK-wrap AAD (`header || slot_id`,
77 /// §2.4) so `recover_kek` for the same slot reproduces the AAD exactly.
78 ///
79 /// # Errors
80 /// Any wrap failure (unavailable method, crypto): fail closed.
81 fn wrap_kek(
82 &self,
83 kek: &MasterKek,
84 header_aad: &[u8],
85 slot_id: u32,
86 ) -> Result<(format::MethodParams, format::KekWrap), UnlockError>;
87}