fix(vault): apply Shamir custody review feedback

This commit is contained in:
CleverWild
2026-09-11 14:13:08 +02:00
parent c722712166
commit d49c39130a
4 changed files with 91 additions and 94 deletions

View File

@@ -76,6 +76,9 @@ pub enum Error {
/// declared, so the token stays valid across several registrations and is
/// retired by the `Bootstrapped` event rather than by first use, whichever
/// bootstrap path fired it.
///
/// Every daemon start mints a fresh token and overwrites the file: a token
/// handed out by an earlier run is dead.
pub struct Bootstrapper {
token: Option<SafeCell<[u8; TOKEN_LENGTH]>>,
token_path: Option<PathBuf>,
@@ -116,41 +119,13 @@ impl Bootstrapper {
});
}
let registered = diesel::select(diesel::dsl::exists(
schema::operator_identity::table.select(schema::operator_identity::id),
))
.get_result::<bool>(&mut conn)
.await?;
let token = if registered {
match tokio::fs::read_to_string(&path).await {
Ok(existing)
if existing.len() == TOKEN_LENGTH
&& existing.chars().all(|c| c.is_ascii_alphanumeric()) =>
{
let mut cell = SafeCell::new([0u8; TOKEN_LENGTH]);
cell.write().copy_from_slice(existing.as_bytes());
cell
}
Ok(_) | Err(_) => generate_token(&path).await?,
}
} else {
generate_token(&path).await?
};
Ok(Self {
token: Some(token),
token: Some(generate_token(&path).await?),
token_path: Some(path),
events,
})
}
fn is_correct_token(&mut self, token: &[u8]) -> bool {
self.token.as_mut().is_some_and(|expected| {
expected.read_inline(|bytes| bool::from(bytes.as_ref().ct_eq(token)))
})
}
async fn forget(&mut self) {
self.token = None;
if let Some(path) = self.token_path.take() {
@@ -170,12 +145,14 @@ impl Message<events::Bootstrapped> for Bootstrapper {
self.forget().await;
}
}
#[messages]
impl Bootstrapper {
#[message]
pub fn verify_token(&mut self, token: Vec<u8>) -> bool {
self.is_correct_token(&token)
self.token.as_mut().is_some_and(|expected| {
expected.read_inline(|bytes| bool::from(bytes.as_ref().ct_eq(token.as_slice())))
})
}
#[message]

View File

@@ -192,12 +192,14 @@ async fn finalize_bootstrap(
let mut shares = shamir::split_key(threshold, total, &mut seal_key, UnwrapErr(SysRng))
.map_err(|error| Error::Shamir(error.to_string()))?;
if shares.len() < total {
return Err(Error::Shamir("missing share for operator".to_owned()));
}
let mut encrypted = Vec::with_capacity(total);
for (index, (operator_id, passphrase)) in contributions.0.iter_mut().enumerate() {
let share = shares
.read_inline(|shares| shares.get(index).cloned())
.ok_or_else(|| Error::Shamir("missing share for operator".to_owned()))?;
encrypted.push((*operator_id, encrypt_share(passphrase, &share)?));
for ((operator_id, passphrase), share) in contributions.0.iter_mut().zip(shares.iter_mut()) {
let share = share.read_inline(|share| encrypt_share(passphrase, share))?;
encrypted.push((*operator_id, share));
}
vault
@@ -227,12 +229,9 @@ async fn finalize_unseal(
.await?
};
let mut plaintext = SafeCell::new(Vec::with_capacity(stored.len()));
let mut plaintext = Vec::with_capacity(stored.len());
for ((_, passphrase), share) in contributions.0.iter_mut().zip(stored) {
let mut decrypted = decrypt_share(passphrase, share)?;
decrypted.read_inline(|share| {
plaintext.write_inline(|shares| shares.push(share.clone()));
});
plaintext.push(decrypt_share(passphrase, share)?);
}
let seal_key = shamir::combine_shares(threshold, &mut plaintext)