From 0098c3c08a642fdef5ad8aad7668ab306ee6186c Mon Sep 17 00:00:00 2001 From: CleverWild Date: Fri, 12 Jun 2026 21:15:07 +0200 Subject: [PATCH] refactor(server::crypto): use fixed-size [u8; 32] and KeyCell throughout seal key API --- .../arbiter-server/src/actors/vault/mod.rs | 10 +++---- .../src/actors/vault_coordinator/mod.rs | 16 +++++------ .../arbiter-server/src/crypto/integrity/v1.rs | 4 +-- .../crates/arbiter-server/src/crypto/mod.rs | 9 +++++++ .../arbiter-server/src/crypto/shamir.rs | 12 +++++---- .../src/peers/operator/vault_gate/mod.rs | 27 +++++++------------ .../arbiter-server/tests/client/auth.rs | 7 ++--- .../crates/arbiter-server/tests/common/mod.rs | 3 +-- .../arbiter-server/tests/operator/auth.rs | 13 ++++----- .../arbiter-server/tests/operator/unseal.rs | 7 ++--- .../arbiter-server/tests/vault/concurrency.rs | 2 +- .../arbiter-server/tests/vault/lifecycle.rs | 12 ++++----- 12 files changed, 53 insertions(+), 69 deletions(-) diff --git a/server/crates/arbiter-server/src/actors/vault/mod.rs b/server/crates/arbiter-server/src/actors/vault/mod.rs index 94b7d9d..e29cb24 100644 --- a/server/crates/arbiter-server/src/actors/vault/mod.rs +++ b/server/crates/arbiter-server/src/actors/vault/mod.rs @@ -162,13 +162,12 @@ impl Vault { #[messages] impl Vault { #[message] - pub async fn bootstrap(&mut self, seal_key_raw: SafeCell>) -> Result<(), Error> { + pub async fn bootstrap(&mut self, mut seal_key: KeyCell) -> Result<(), Error> { if !matches!(&self.state, State::Unbootstrapped) { return Err(Error::AlreadyBootstrapped); } let mut root_key = KeyCell::new_secure_random(); - let mut seal_key = KeyCell::try_from(seal_key_raw).map_err(|()| Error::InvalidKey)?; // Zero nonces are fine because they are one-time let root_key_nonce = Nonce::default(); @@ -227,7 +226,7 @@ impl Vault { } #[message] - pub async fn try_unseal(&mut self, seal_key_raw: SafeCell>) -> Result<(), Error> { + pub async fn try_unseal(&mut self, mut seal_key: KeyCell) -> Result<(), Error> { let State::Sealed { root_key_history_id, } = &self.state @@ -246,8 +245,6 @@ impl Vault { .await? }; - let mut seal_key = KeyCell::try_from(seal_key_raw).map_err(|()| Error::InvalidKey)?; - let nonce = Nonce::try_from(current_key.root_key_encryption_nonce.as_slice()).map_err(|()| { error!("Broken database: invalid nonce for root key"); @@ -422,8 +419,7 @@ mod tests { let mut actor = Vault::new(db.clone(), GlobalActors::spawn_message_bus()) .await .unwrap(); - let seal_key = SafeCell::new([0u8; 32].to_vec()); - actor.bootstrap(seal_key).await.unwrap(); + actor.bootstrap(KeyCell::from([0u8; 32])).await.unwrap(); actor } diff --git a/server/crates/arbiter-server/src/actors/vault_coordinator/mod.rs b/server/crates/arbiter-server/src/actors/vault_coordinator/mod.rs index e9b44a3..77ed021 100644 --- a/server/crates/arbiter-server/src/actors/vault_coordinator/mod.rs +++ b/server/crates/arbiter-server/src/actors/vault_coordinator/mod.rs @@ -9,7 +9,7 @@ use tracing::error; use crate::{ actors::vault::{Bootstrap, TryUnseal, Vault}, - crypto::{derive_key, encryption::v1::Nonce, shamir}, + crypto::{KeyCell, derive_key, encryption::v1::Nonce, shamir}, db::{self, models, schema}, }; @@ -94,14 +94,14 @@ async fn finalize_bootstrap( let threshold = shamir_threshold(total); // Generate random 32-byte seal key - let mut seal_key_bytes = vec![0u8; 32]; + let mut seal_key_bytes = [0u8; 32]; OsRng.fill_bytes(&mut seal_key_bytes); // Split seal key into shares using Shamir (OsRng from rand_core 0.6, compatible with vsss-rs) let shares = shamir::split_key(threshold, total, &seal_key_bytes, OsRng) .map_err(|e| Error::Shamir(e.to_string()))?; - let seal_key = SafeCell::new(seal_key_bytes); + let seal_key = KeyCell::from(seal_key_bytes); let mut conn = db.get().await?; @@ -136,9 +136,7 @@ async fn finalize_bootstrap( } vault - .ask(Bootstrap { - seal_key_raw: seal_key, - }) + .ask(Bootstrap { seal_key }) .await .map_err(|err| { error!(?err, "Vault bootstrap failed"); @@ -189,12 +187,10 @@ async fn finalize_unseal( let seal_key_bytes = shamir::combine_shares(&shares).map_err(|e| Error::Shamir(e.to_string()))?; - let seal_key = SafeCell::new(seal_key_bytes); + let seal_key = KeyCell::from(seal_key_bytes); vault - .ask(TryUnseal { - seal_key_raw: seal_key, - }) + .ask(TryUnseal { seal_key }) .await .map_err(|err| { error!(?err, "Vault unseal failed"); diff --git a/server/crates/arbiter-server/src/crypto/integrity/v1.rs b/server/crates/arbiter-server/src/crypto/integrity/v1.rs index c777967..9feb840 100644 --- a/server/crates/arbiter-server/src/crypto/integrity/v1.rs +++ b/server/crates/arbiter-server/src/crypto/integrity/v1.rs @@ -215,8 +215,6 @@ mod tests { }, db::{self, schema}, }; - use arbiter_crypto::safecell::{SafeCell, SafeCellHandle as _}; - use super::{Error, Integrable, sign_entity, verify_entity}; #[derive(Clone, arbiter_macros::Hashable)] struct DummyEntity { @@ -235,7 +233,7 @@ mod tests { ); actor .ask(Bootstrap { - seal_key_raw: SafeCell::new([0u8; 32].to_vec()), + seal_key: crate::crypto::KeyCell::from([0u8; 32]), }) .await .unwrap(); diff --git a/server/crates/arbiter-server/src/crypto/mod.rs b/server/crates/arbiter-server/src/crypto/mod.rs index a100001..2db08d8 100644 --- a/server/crates/arbiter-server/src/crypto/mod.rs +++ b/server/crates/arbiter-server/src/crypto/mod.rs @@ -21,6 +21,15 @@ impl From> for KeyCell { Self(value) } } +impl From<[u8; 32]> for KeyCell { + fn from(bytes: [u8; 32]) -> Self { + let cell = SafeCell::new_inline_default(|key: &mut Key| { + key.copy_from_slice(&bytes); + }); + Self(cell) + } +} + impl TryFrom>> for KeyCell { type Error = (); diff --git a/server/crates/arbiter-server/src/crypto/shamir.rs b/server/crates/arbiter-server/src/crypto/shamir.rs index b369390..c379685 100644 --- a/server/crates/arbiter-server/src/crypto/shamir.rs +++ b/server/crates/arbiter-server/src/crypto/shamir.rs @@ -13,15 +13,17 @@ pub enum ShamirError { pub fn split_key( threshold: usize, total: usize, - key: &[u8], + key: &[u8; 32], rng: impl rand_core::RngCore + rand_core::CryptoRng, ) -> Result>, ShamirError> { - Gf256::split_array(threshold, total, key, rng) + Gf256::split_array(threshold, total, key.as_slice(), rng) .map_err(|e| ShamirError::Split(format!("{e:?}"))) } /// Reconstruct the secret from `threshold` or more shares. -pub fn combine_shares(shares: &[Vec]) -> Result, ShamirError> { - Gf256::combine_array(shares) - .map_err(|e| ShamirError::Combine(format!("{e:?}"))) +pub fn combine_shares(shares: &[Vec]) -> Result<[u8; 32], ShamirError> { + let bytes = Gf256::combine_array(shares) + .map_err(|e| ShamirError::Combine(format!("{e:?}")))?; + <[u8; 32]>::try_from(bytes.as_slice()) + .map_err(|_| ShamirError::Combine("unexpected reconstructed key length".to_owned())) } diff --git a/server/crates/arbiter-server/src/peers/operator/vault_gate/mod.rs b/server/crates/arbiter-server/src/peers/operator/vault_gate/mod.rs index b74ff3e..2e73555 100644 --- a/server/crates/arbiter-server/src/peers/operator/vault_gate/mod.rs +++ b/server/crates/arbiter-server/src/peers/operator/vault_gate/mod.rs @@ -5,7 +5,7 @@ use crate::{ vault::{self, Bootstrap, GetState, TryUnseal, VaultState, events}, vault_coordinator::{ContributeBootstrap, ContributeUnseal, StartBootstrap}, }, - crypto::integrity::{self}, + crypto::{KeyCell, integrity::{self}}, db::DatabasePool, }; use arbiter_crypto::safecell::{SafeCell, SafeCellHandle as _}; @@ -102,11 +102,9 @@ impl VaultGate { nonce: &[u8], ciphertext: &[u8], associated_data: &[u8], - ) -> Result>, ()> { + ) -> Result { let nonce = XNonce::from_slice(nonce); - let cipher = XChaCha20Poly1305::new(secret.as_bytes().into()); - let mut key_buffer = SafeCell::new(ciphertext.to_vec()); let decryption_result = key_buffer.write_inline(|write_handle| { @@ -114,7 +112,9 @@ impl VaultGate { }); match decryption_result { - Ok(()) => Ok(key_buffer), + Ok(()) => KeyCell::try_from(key_buffer).map_err(|()| { + error!("Decrypted key material has unexpected length"); + }), Err(err) => { error!(?err, "Failed to decrypt encrypted key material"); Err(()) @@ -122,6 +122,7 @@ impl VaultGate { } } } + #[messages(enum)] impl VaultGate { #[message] @@ -155,17 +156,14 @@ impl VaultGate { return Err(Error::State); }; - let Ok(seal_key_buffer) = Self::decrypt_key(secret, &nonce, &ciphertext, &associated_data) - else { + let Ok(seal_key) = Self::decrypt_key(secret, &nonce, &ciphertext, &associated_data) else { return Err(Error::InvalidKey); }; match self .actors .vault - .ask(TryUnseal { - seal_key_raw: seal_key_buffer, - }) + .ask(TryUnseal { seal_key }) .await { Ok(()) => { @@ -195,17 +193,14 @@ impl VaultGate { return Err(Error::State); }; - let Ok(seal_key_buffer) = Self::decrypt_key(secret, &nonce, &ciphertext, &associated_data) - else { + let Ok(seal_key) = Self::decrypt_key(secret, &nonce, &ciphertext, &associated_data) else { return Err(Error::InvalidKey); }; match self .actors .vault - .ask(Bootstrap { - seal_key_raw: seal_key_buffer, - }) + .ask(Bootstrap { seal_key }) .await { Ok(()) => { @@ -255,7 +250,6 @@ impl VaultGate { &mut self, passphrase: Vec, ) -> Result { - use arbiter_crypto::safecell::SafeCell; let passphrase_cell = SafeCell::new(passphrase); self.actors .vault_coordinator @@ -272,7 +266,6 @@ impl VaultGate { &mut self, passphrase: Vec, ) -> Result { - use arbiter_crypto::safecell::SafeCell; let passphrase_cell = SafeCell::new(passphrase); self.actors .vault_coordinator diff --git a/server/crates/arbiter-server/tests/client/auth.rs b/server/crates/arbiter-server/tests/client/auth.rs index 964f282..facc4e5 100644 --- a/server/crates/arbiter-server/tests/client/auth.rs +++ b/server/crates/arbiter-server/tests/client/auth.rs @@ -1,8 +1,5 @@ use super::common::ChannelTransport; -use arbiter_crypto::{ - authn::{self, AuthChallenge, CLIENT_CONTEXT}, - safecell::{SafeCell, SafeCellHandle as _}, -}; +use arbiter_crypto::authn::{self, AuthChallenge, CLIENT_CONTEXT}; use arbiter_proto::{ ClientMetadata, transport::{Receiver, Sender}, @@ -100,7 +97,7 @@ async fn spawn_test_actors(db: &db::DatabasePool) -> GlobalActors { actors .vault .ask(Bootstrap { - seal_key_raw: SafeCell::new([0u8; 32].to_vec()), + seal_key: arbiter_server::crypto::KeyCell::from([0u8; 32]), }) .await .unwrap(); diff --git a/server/crates/arbiter-server/tests/common/mod.rs b/server/crates/arbiter-server/tests/common/mod.rs index 33cca5e..598eee9 100644 --- a/server/crates/arbiter-server/tests/common/mod.rs +++ b/server/crates/arbiter-server/tests/common/mod.rs @@ -2,7 +2,6 @@ dead_code, reason = "Common test utilities that may not be used in every test" )] -use arbiter_crypto::safecell::{SafeCell, SafeCellHandle as _}; use arbiter_proto::transport::{Bi, Error, Receiver, Sender}; use arbiter_server::{ actors::{GlobalActors, vault::Vault}, @@ -19,7 +18,7 @@ pub(crate) async fn bootstrapped_vault(db: &db::DatabasePool) -> Vault { .await .unwrap(); actor - .bootstrap(SafeCell::new([0u8; 32].to_vec())) + .bootstrap(arbiter_server::crypto::KeyCell::from([0u8; 32])) .await .unwrap(); actor diff --git a/server/crates/arbiter-server/tests/operator/auth.rs b/server/crates/arbiter-server/tests/operator/auth.rs index 7d91a55..76afc1a 100644 --- a/server/crates/arbiter-server/tests/operator/auth.rs +++ b/server/crates/arbiter-server/tests/operator/auth.rs @@ -1,8 +1,5 @@ use super::common::ChannelTransport; -use arbiter_crypto::{ - authn::{self, AuthChallenge, OPERATOR_CONTEXT}, - safecell::{SafeCell, SafeCellHandle as _}, -}; +use arbiter_crypto::authn::{self, AuthChallenge, OPERATOR_CONTEXT}; use arbiter_proto::transport::{Error as TransportError, Receiver, Sender}; use arbiter_server::{ actors::{GlobalActors, bootstrap::GetToken, vault::Bootstrap}, @@ -157,7 +154,7 @@ pub async fn bootstrap_token_auth() { actors .vault .ask(Bootstrap { - seal_key_raw: SafeCell::new([0u8; 32].to_vec()), + seal_key: arbiter_server::crypto::KeyCell::from([0u8; 32]), }) .await .unwrap(); @@ -275,7 +272,7 @@ pub async fn challenge_auth() { actors .vault .ask(Bootstrap { - seal_key_raw: SafeCell::new([0u8; 32].to_vec()), + seal_key: arbiter_server::crypto::KeyCell::from([0u8; 32]), }) .await .unwrap(); @@ -361,7 +358,7 @@ pub async fn challenge_auth_rejects_integrity_tag_mismatch_when_unsealed() { actors .vault .ask(Bootstrap { - seal_key_raw: SafeCell::new([0u8; 32].to_vec()), + seal_key: arbiter_server::crypto::KeyCell::from([0u8; 32]), }) .await .unwrap(); @@ -434,7 +431,7 @@ pub async fn challenge_auth_rejects_invalid_signature() { actors .vault .ask(Bootstrap { - seal_key_raw: SafeCell::new([0u8; 32].to_vec()), + seal_key: arbiter_server::crypto::KeyCell::from([0u8; 32]), }) .await .unwrap(); diff --git a/server/crates/arbiter-server/tests/operator/unseal.rs b/server/crates/arbiter-server/tests/operator/unseal.rs index c2267d2..6acfdba 100644 --- a/server/crates/arbiter-server/tests/operator/unseal.rs +++ b/server/crates/arbiter-server/tests/operator/unseal.rs @@ -1,7 +1,4 @@ -use arbiter_crypto::{ - authn, - safecell::{SafeCell, SafeCellHandle as _}, -}; +use arbiter_crypto::authn; use arbiter_server::{ actors::{ GlobalActors, @@ -34,7 +31,7 @@ async fn setup_sealed_gate( actors .vault .ask(Bootstrap { - seal_key_raw: SafeCell::new(seal_key.to_vec()), + seal_key: arbiter_server::crypto::KeyCell::from(*seal_key), }) .await .unwrap(); diff --git a/server/crates/arbiter-server/tests/vault/concurrency.rs b/server/crates/arbiter-server/tests/vault/concurrency.rs index 48d9a34..4e94fa9 100644 --- a/server/crates/arbiter-server/tests/vault/concurrency.rs +++ b/server/crates/arbiter-server/tests/vault/concurrency.rs @@ -166,7 +166,7 @@ async fn decrypt_roundtrip_after_high_concurrency() { .await .unwrap(); decryptor - .try_unseal(SafeCell::new([0u8; 32].to_vec())) + .try_unseal(arbiter_server::crypto::KeyCell::from([0u8; 32])) .await .unwrap(); diff --git a/server/crates/arbiter-server/tests/vault/lifecycle.rs b/server/crates/arbiter-server/tests/vault/lifecycle.rs index 1f3dc38..6148590 100644 --- a/server/crates/arbiter-server/tests/vault/lifecycle.rs +++ b/server/crates/arbiter-server/tests/vault/lifecycle.rs @@ -5,7 +5,7 @@ use arbiter_server::{ GlobalActors, vault::{Error, Vault}, }, - crypto::encryption::v1::{Nonce, ROOT_KEY_TAG}, + crypto::{KeyCell, encryption::v1::{Nonce, ROOT_KEY_TAG}}, db::{self, models, schema}, }; @@ -20,7 +20,7 @@ async fn test_bootstrap() { .await .unwrap(); - let seal_key = SafeCell::new([0u8; 32].to_vec()); + let seal_key = KeyCell::from([0u8; 32]); actor.bootstrap(seal_key).await.unwrap(); let mut conn = db.get().await.unwrap(); @@ -43,7 +43,7 @@ async fn test_bootstrap_rejects_double() { let db = db::create_test_pool().await; let mut actor = common::bootstrapped_vault(&db).await; - let seal_key2 = SafeCell::new([0u8; 32].to_vec()); + let seal_key2 = KeyCell::from([0u8; 32]); let err = actor.bootstrap(seal_key2).await.unwrap_err(); assert!(matches!(err, Error::AlreadyBootstrapped)); } @@ -105,7 +105,7 @@ async fn test_unseal_correct_password() { let mut actor = Vault::new(db.clone(), GlobalActors::spawn_message_bus()) .await .unwrap(); - let seal_key = SafeCell::new([0u8; 32].to_vec()); + let seal_key = KeyCell::from([0u8; 32]); actor.try_unseal(seal_key).await.unwrap(); let mut decrypted = actor.decrypt(aead_id).await.unwrap(); @@ -129,11 +129,11 @@ async fn test_unseal_wrong_then_correct_password() { .await .unwrap(); - let bad_key = SafeCell::new([1u8; 32].to_vec()); + let bad_key = KeyCell::from([1u8; 32]); let err = actor.try_unseal(bad_key).await.unwrap_err(); assert!(matches!(err, Error::InvalidKey)); - let good_key = SafeCell::new([0u8; 32].to_vec()); + let good_key = KeyCell::from([0u8; 32]); actor.try_unseal(good_key).await.unwrap(); let mut decrypted = actor.decrypt(aead_id).await.unwrap();