refactor(server): reorganized client/user_agent actors into separate module peers and added event MessageBus

This commit is contained in:
hdbg
2026-04-07 23:54:29 +02:00
parent f3cf6a9438
commit 1585f90cae
42 changed files with 332 additions and 286 deletions

View File

@@ -66,7 +66,7 @@ cargo insta review
The server is actor-based using the **kameo** crate. All long-lived state lives in `GlobalActors`: The server is actor-based using the **kameo** crate. All long-lived state lives in `GlobalActors`:
- **`Bootstrapper`** — Manages the one-time bootstrap token written to `~/.arbiter/bootstrap_token` on first run. - **`Bootstrapper`** — Manages the one-time bootstrap token written to `~/.arbiter/bootstrap_token` on first run.
- **`KeyHolder`** — Holds the encrypted root key and manages the Sealed/Unsealed vault state machine. On unseal, decrypts the root key into a `memsafe` hardened memory cell. - **`Vault`** — Holds the encrypted root key and manages the Sealed/Unsealed vault state machine. On unseal, decrypts the root key into a `memsafe` hardened memory cell.
- **`FlowCoordinator`** — Coordinates cross-connection flow between user agents and SDK clients. - **`FlowCoordinator`** — Coordinates cross-connection flow between user agents and SDK clients.
- **`EvmActor`** — Handles EVM transaction policy enforcement and signing. - **`EvmActor`** — Handles EVM transaction policy enforcement and signing.

View File

@@ -66,7 +66,7 @@ cargo insta review
The server is actor-based using the **kameo** crate. All long-lived state lives in `GlobalActors`: The server is actor-based using the **kameo** crate. All long-lived state lives in `GlobalActors`:
- **`Bootstrapper`** — Manages the one-time bootstrap token written to `~/.arbiter/bootstrap_token` on first run. - **`Bootstrapper`** — Manages the one-time bootstrap token written to `~/.arbiter/bootstrap_token` on first run.
- **`KeyHolder`** — Holds the encrypted root key and manages the Sealed/Unsealed vault state machine. On unseal, decrypts the root key into a `memsafe` hardened memory cell. - **`Vault`** — Holds the encrypted root key and manages the Sealed/Unsealed vault state machine. On unseal, decrypts the root key into a `memsafe` hardened memory cell.
- **`FlowCoordinator`** — Coordinates cross-connection flow between user agents and SDK clients. - **`FlowCoordinator`** — Coordinates cross-connection flow between user agents and SDK clients.
- **`EvmActor`** — Handles EVM transaction policy enforcement and signing. - **`EvmActor`** — Handles EVM transaction policy enforcement and signing.

24
server/Cargo.lock generated
View File

@@ -753,6 +753,7 @@ dependencies = [
"insta", "insta",
"k256", "k256",
"kameo", "kameo",
"kameo_actors",
"ml-dsa", "ml-dsa",
"mutants", "mutants",
"pem", "pem",
@@ -2962,9 +2963,9 @@ dependencies = [
[[package]] [[package]]
name = "kameo" name = "kameo"
version = "0.19.2" version = "0.20.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c4af7638c67029fd6821d02813c3913c803784648725d4df4082c9b91d7cbb1" checksum = "b1dfd134d7a2c6ec05ee696dcbf3f7a034bdb97ecc623e981014652dcd124d77"
dependencies = [ dependencies = [
"downcast-rs", "downcast-rs",
"dyn-clone", "dyn-clone",
@@ -2976,10 +2977,23 @@ dependencies = [
] ]
[[package]] [[package]]
name = "kameo_macros" name = "kameo_actors"
version = "0.19.0" version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a13c324e2d8c8e126e63e66087448b4267e263e6cb8770c56d10a9d0d279d9e2" checksum = "220bdd75769f0a9b752a91123e58bf42f595e3c36ff4b13818d7a87d962076e6"
dependencies = [
"futures",
"glob",
"kameo",
"thiserror 2.0.18",
"tokio",
]
[[package]]
name = "kameo_macros"
version = "0.20.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "16c9002c9ecd16e1636f566c0bf62db48e70d86ed0d0a91b398955e883217a23"
dependencies = [ dependencies = [
"heck", "heck",
"proc-macro2", "proc-macro2",

View File

@@ -26,7 +26,7 @@ thiserror = "2.0.18"
async-trait = "0.1.89" async-trait = "0.1.89"
futures = "0.3.32" futures = "0.3.32"
tokio-stream = { version = "0.1.18", features = ["full"] } tokio-stream = { version = "0.1.18", features = ["full"] }
kameo = "0.19.2" kameo = "0.20"
prost-types = { version = "0.14.3", features = ["chrono"] } prost-types = { version = "0.14.3", features = ["chrono"] }
x25519-dalek = { version = "2.0.1", features = ["getrandom"] } x25519-dalek = { version = "2.0.1", features = ["getrandom"] }
rstest = "0.26.1" rstest = "0.26.1"

View File

@@ -58,6 +58,7 @@ ml-dsa.workspace = true
ed25519-dalek.workspace = true ed25519-dalek.workspace = true
x25519-dalek.workspace = true x25519-dalek.workspace = true
k256.workspace = true k256.workspace = true
kameo_actors = "0.5.0"
[dev-dependencies] [dev-dependencies]
insta = "1.46.3" insta = "1.46.3"

View File

@@ -7,7 +7,7 @@ use kameo::{Actor, actor::ActorRef, messages};
use rand::{SeedableRng, rng, rngs::StdRng}; use rand::{SeedableRng, rng, rngs::StdRng};
use crate::{ use crate::{
actors::keyholder::{CreateNew, Decrypt, KeyHolder}, actors::vault::{CreateNew, Decrypt, Vault},
crypto::integrity, crypto::integrity,
db::{ db::{
DatabaseError, DatabasePool, DatabaseError, DatabasePool,
@@ -34,11 +34,11 @@ pub enum SignTransactionError {
#[error("Database error: {0}")] #[error("Database error: {0}")]
Database(#[from] DatabaseError), Database(#[from] DatabaseError),
#[error("Keyholder error: {0}")] #[error("Vault error: {0}")]
Keyholder(#[from] crate::actors::keyholder::Error), Vault(#[from] crate::actors::vault::Error),
#[error("Keyholder mailbox error")] #[error("Vault mailbox error")]
KeyholderSend, VaultSend,
#[error("Signing error: {0}")] #[error("Signing error: {0}")]
Signing(#[from] alloy::signers::Error), Signing(#[from] alloy::signers::Error),
@@ -49,11 +49,11 @@ pub enum SignTransactionError {
#[derive(Debug, thiserror::Error)] #[derive(Debug, thiserror::Error)]
pub enum Error { pub enum Error {
#[error("Keyholder error: {0}")] #[error("Vault error: {0}")]
Keyholder(#[from] crate::actors::keyholder::Error), Vault(#[from] crate::actors::vault::Error),
#[error("Keyholder mailbox error")] #[error("Vault mailbox error")]
KeyholderSend, VaultSend,
#[error("Database error: {0}")] #[error("Database error: {0}")]
Database(#[from] DatabaseError), Database(#[from] DatabaseError),
@@ -64,20 +64,20 @@ pub enum Error {
#[derive(Actor)] #[derive(Actor)]
pub struct EvmActor { pub struct EvmActor {
pub keyholder: ActorRef<KeyHolder>, pub vault: ActorRef<Vault>,
pub db: DatabasePool, pub db: DatabasePool,
pub rng: StdRng, pub rng: StdRng,
pub engine: evm::Engine, pub engine: evm::Engine,
} }
impl EvmActor { impl EvmActor {
pub fn new(keyholder: ActorRef<KeyHolder>, db: DatabasePool) -> Self { pub fn new(vault: ActorRef<Vault>, db: DatabasePool) -> Self {
// is it safe to seed rng from system once? // is it safe to seed rng from system once?
// todo: audit // todo: audit
let rng = StdRng::from_rng(&mut rng()); let rng = StdRng::from_rng(&mut rng());
let engine = evm::Engine::new(db.clone(), keyholder.clone()); let engine = evm::Engine::new(db.clone(), vault.clone());
Self { Self {
keyholder, vault,
db, db,
rng, rng,
engine, engine,
@@ -94,10 +94,10 @@ impl EvmActor {
let plaintext = key_cell.read_inline(|reader| SafeCell::new(reader.to_vec())); let plaintext = key_cell.read_inline(|reader| SafeCell::new(reader.to_vec()));
let aead_id: i32 = self let aead_id: i32 = self
.keyholder .vault
.ask(CreateNew { plaintext }) .ask(CreateNew { plaintext })
.await .await
.map_err(|_| Error::KeyholderSend)?; .map_err(|_| Error::VaultSend)?;
let mut conn = self.db.get().await.map_err(DatabaseError::from)?; let mut conn = self.db.get().await.map_err(DatabaseError::from)?;
let wallet_id = insert_into(schema::evm_wallet::table) let wallet_id = insert_into(schema::evm_wallet::table)
@@ -160,7 +160,7 @@ impl EvmActor {
#[message] #[message]
pub async fn useragent_delete_grant(&mut self, _grant_id: i32) -> Result<(), Error> { pub async fn useragent_delete_grant(&mut self, _grant_id: i32) -> Result<(), Error> {
// let mut conn = self.db.get().await.map_err(DatabaseError::from)?; // let mut conn = self.db.get().await.map_err(DatabaseError::from)?;
// let keyholder = self.keyholder.clone(); // let vault = self.vault.clone();
// diesel_async::AsyncConnection::transaction(&mut conn, |conn| { // diesel_async::AsyncConnection::transaction(&mut conn, |conn| {
// Box::pin(async move { // Box::pin(async move {
@@ -254,12 +254,12 @@ impl EvmActor {
drop(conn); drop(conn);
let raw_key: SafeCell<Vec<u8>> = self let raw_key: SafeCell<Vec<u8>> = self
.keyholder .vault
.ask(Decrypt { .ask(Decrypt {
aead_id: wallet.aead_encrypted_id, aead_id: wallet.aead_encrypted_id,
}) })
.await .await
.map_err(|_| SignTransactionError::KeyholderSend)?; .map_err(|_| SignTransactionError::VaultSend)?;
let signer = safe_signer::SafeSigner::from_cell(raw_key)?; let signer = safe_signer::SafeSigner::from_cell(raw_key)?;

View File

@@ -6,10 +6,9 @@ use kameo::{
reply::ReplySender, reply::ReplySender,
}; };
use crate::actors::{ use crate::{
client::ClientProfile, actors::flow_coordinator::ApprovalError,
flow_coordinator::ApprovalError, peers::{client::ClientProfile, user_agent::{UserAgentSession, session::BeginNewClientApproval}},
user_agent::{UserAgentSession, session::BeginNewClientApproval},
}; };
pub struct Args { pub struct Args {

View File

@@ -9,11 +9,7 @@ use kameo::{
}; };
use tracing::info; use tracing::info;
use crate::actors::{ use crate::{actors::flow_coordinator::client_connect_approval::ClientApprovalController, peers::{client::{ClientProfile, session::ClientSession}, user_agent::UserAgentSession}};
client::{ClientProfile, session::ClientSession},
flow_coordinator::client_connect_approval::ClientApprovalController,
user_agent::session::UserAgentSession,
};
pub mod client_connect_approval; pub mod client_connect_approval;

View File

@@ -1,47 +1,53 @@
use kameo::actor::{ActorRef, Spawn}; use kameo::actor::{ActorRef, Spawn};
use kameo_actors::{DeliveryStrategy, message_bus::MessageBus};
use thiserror::Error; use thiserror::Error;
use crate::{ use crate::{
actors::{ actors::{
bootstrap::Bootstrapper, evm::EvmActor, flow_coordinator::FlowCoordinator, bootstrap::Bootstrapper, evm::EvmActor, flow_coordinator::FlowCoordinator,
keyholder::KeyHolder, vault::Vault,
}, },
db, db,
}; };
pub mod bootstrap; pub mod bootstrap;
pub mod client; pub mod evm;
mod evm;
pub mod flow_coordinator; pub mod flow_coordinator;
pub mod keyholder; pub mod vault;
pub mod user_agent;
#[derive(Error, Debug)] #[derive(Error, Debug)]
pub enum SpawnError { pub enum SpawnError {
#[error("Failed to spawn Bootstrapper actor")] #[error("Failed to spawn Bootstrapper actor")]
Bootstrapper(#[from] bootstrap::Error), Bootstrapper(#[from] bootstrap::Error),
#[error("Failed to spawn KeyHolder actor")] #[error("Failed to spawn Vault actor")]
KeyHolder(#[from] keyholder::Error), Vault(#[from] vault::Error),
} }
/// Long-lived actors that are shared across all connections and handle global state and operations /// Long-lived actors that are shared across all connections and handle global state and operations
#[derive(Clone)] #[derive(Clone)]
pub struct GlobalActors { pub struct GlobalActors {
pub key_holder: ActorRef<KeyHolder>, pub vault: ActorRef<Vault>,
pub bootstrapper: ActorRef<Bootstrapper>, pub bootstrapper: ActorRef<Bootstrapper>,
pub flow_coordinator: ActorRef<FlowCoordinator>, pub flow_coordinator: ActorRef<FlowCoordinator>,
pub evm: ActorRef<EvmActor>, pub evm: ActorRef<EvmActor>,
pub events: ActorRef<MessageBus>,
} }
impl GlobalActors { impl GlobalActors {
pub fn spawn_message_bus() -> ActorRef<MessageBus> {
MessageBus::spawn(MessageBus::new(DeliveryStrategy::Guaranteed))
}
pub async fn spawn(db: db::DatabasePool) -> Result<Self, SpawnError> { pub async fn spawn(db: db::DatabasePool) -> Result<Self, SpawnError> {
let key_holder = KeyHolder::spawn(KeyHolder::new(db.clone()).await?); let message_bus = Self::spawn_message_bus();
let key_holder = Vault::spawn(Vault::new(db.clone(), message_bus.clone()).await?);
Ok(Self { Ok(Self {
bootstrapper: Bootstrapper::spawn(Bootstrapper::new(&db).await?), bootstrapper: Bootstrapper::spawn(Bootstrapper::new(&db).await?),
evm: EvmActor::spawn(EvmActor::new(key_holder.clone(), db)), evm: EvmActor::spawn(EvmActor::new(key_holder.clone(), db)),
key_holder, vault: key_holder,
flow_coordinator: FlowCoordinator::spawn(FlowCoordinator::default()), flow_coordinator: FlowCoordinator::spawn(FlowCoordinator::default()),
events: message_bus,
}) })
} }
} }

View File

@@ -5,7 +5,8 @@ use diesel::{
}; };
use diesel_async::{AsyncConnection, RunQueryDsl}; use diesel_async::{AsyncConnection, RunQueryDsl};
use hmac::Mac as _; use hmac::Mac as _;
use kameo::{Actor, Reply, messages}; use kameo::{Actor, Reply, actor::ActorRef, messages};
use kameo_actors::message_bus::{MessageBus, Publish};
use strum::{EnumDiscriminants, IntoDiscriminant}; use strum::{EnumDiscriminants, IntoDiscriminant};
use tracing::{error, info}; use tracing::{error, info};
@@ -21,26 +22,26 @@ use crate::db::{
}; };
use arbiter_crypto::safecell::{SafeCell, SafeCellHandle as _}; use arbiter_crypto::safecell::{SafeCell, SafeCellHandle as _};
#[derive(Default, EnumDiscriminants)] pub mod events {
#[strum_discriminants(derive(Reply), vis(pub), name(KeyHolderState))]
enum State { #[derive(Clone, Copy)]
#[default] pub struct VaultBootstrapped;
Unbootstrapped,
Sealed { #[derive(Clone, Copy)]
root_key_history_id: i32, pub struct VaultUnsealed;
},
Unsealed { #[derive(Clone, Copy)]
root_key_history_id: i32, pub struct VaultResealed;
root_key: KeyCell,
},
} }
#[derive(Debug, thiserror::Error)] #[derive(Debug, thiserror::Error)]
pub enum Error { pub enum Error {
#[error("Keyholder is already bootstrapped")] #[error("Vault is already bootstrapped")]
AlreadyBootstrapped, AlreadyBootstrapped,
#[error("Keyholder is not bootstrapped")] #[error("Vault is not bootstrapped")]
NotBootstrapped, NotBootstrapped,
#[error("Vault is sealed")]
Sealed,
#[error("Invalid key provided")] #[error("Invalid key provided")]
InvalidKey, InvalidKey,
@@ -60,18 +61,35 @@ pub enum Error {
BrokenDatabase, BrokenDatabase,
} }
struct Unsealed {
root_key_history_id: i32,
root_key: KeyCell,
}
#[derive(Default, EnumDiscriminants)]
#[strum_discriminants(derive(Reply), vis(pub), name(VaultState))]
enum State {
#[default]
Unbootstrapped,
Sealed {
root_key_history_id: i32,
},
Unsealed(Unsealed),
}
/// Manages vault root key and tracks current state of the vault (bootstrapped/unbootstrapped, sealed/unsealed). /// Manages vault root key and tracks current state of the vault (bootstrapped/unbootstrapped, sealed/unsealed).
/// Provides API for encrypting and decrypting data using the vault root key. /// Provides API for encrypting and decrypting data using the vault root key.
/// Abstraction over database to make sure nonces are never reused and encryption keys are never exposed in plaintext outside of this actor. /// Abstraction over database to make sure nonces are never reused and encryption keys are never exposed in plaintext outside of this actor.
#[derive(Actor)] #[derive(Actor)]
pub struct KeyHolder { pub struct Vault {
db: db::DatabasePool, db: db::DatabasePool,
state: State, state: State,
events: ActorRef<MessageBus>,
} }
#[messages] #[messages]
impl KeyHolder { impl Vault {
pub async fn new(db: db::DatabasePool) -> Result<Self, Error> { pub async fn new(db: db::DatabasePool, events: ActorRef<MessageBus>) -> Result<Self, Error> {
let state = { let state = {
let mut conn = db.get().await?; let mut conn = db.get().await?;
@@ -89,10 +107,10 @@ impl KeyHolder {
} }
}; };
Ok(Self { db, state }) Ok(Self { db, state, events })
} }
// Exclusive transaction to avoid race condtions if multiple keyholders write // Exclusive transaction to avoid race condtions if multiple vaults write
// additional layer of protection against nonce-reuse // additional layer of protection against nonce-reuse
async fn get_new_nonce(pool: &db::DatabasePool, root_key_id: i32) -> Result<Nonce, Error> { async fn get_new_nonce(pool: &db::DatabasePool, root_key_id: i32) -> Result<Nonce, Error> {
let mut conn = pool.get().await?; let mut conn = pool.get().await?;
@@ -129,6 +147,14 @@ impl KeyHolder {
Ok(nonce) Ok(nonce)
} }
fn expect_unsealed<'a>(state: &'a mut State) -> Result<&'a mut Unsealed, Error> {
match state {
State::Unsealed(unsealed) => Ok(unsealed),
State::Unbootstrapped => Err(Error::NotBootstrapped),
State::Sealed { .. } => Err(Error::Sealed),
}
}
#[message] #[message]
pub async fn bootstrap(&mut self, seal_key_raw: SafeCell<Vec<u8>>) -> Result<(), Error> { pub async fn bootstrap(&mut self, seal_key_raw: SafeCell<Vec<u8>>) -> Result<(), Error> {
if !matches!(self.state, State::Unbootstrapped) { if !matches!(self.state, State::Unbootstrapped) {
@@ -181,12 +207,13 @@ impl KeyHolder {
}) })
.await?; .await?;
self.state = State::Unsealed { self.state = State::Unsealed(Unsealed {
root_key, root_key,
root_key_history_id, root_key_history_id,
}; });
info!("Keyholder bootstrapped successfully"); info!("Vault bootstrapped successfully");
self.events.tell(Publish(events::VaultBootstrapped)).await;
Ok(()) Ok(())
} }
@@ -233,24 +260,23 @@ impl KeyHolder {
Error::InvalidKey Error::InvalidKey
})?; })?;
self.state = State::Unsealed { self.state = State::Unsealed(Unsealed {
root_key_history_id: current_key.id, root_key_history_id: current_key.id,
root_key: KeyCell::try_from(root_key).map_err(|err| { root_key: KeyCell::try_from(root_key).map_err(|err| {
error!(?err, "Broken database: invalid encryption key size"); error!(?err, "Broken database: invalid encryption key size");
Error::BrokenDatabase Error::BrokenDatabase
})?, })?,
}; });
info!("Keyholder unsealed successfully"); info!("Vault unsealed successfully");
self.events.tell(Publish(events::VaultUnsealed)).await;
Ok(()) Ok(())
} }
#[message] #[message]
pub async fn decrypt(&mut self, aead_id: i32) -> Result<SafeCell<Vec<u8>>, Error> { pub async fn decrypt(&mut self, aead_id: i32) -> Result<SafeCell<Vec<u8>>, Error> {
let State::Unsealed { root_key, .. } = &mut self.state else { let Unsealed { root_key, .. } = Self::expect_unsealed(&mut self.state)?;
return Err(Error::NotBootstrapped);
};
let row: models::AeadEncrypted = { let row: models::AeadEncrypted = {
let mut conn = self.db.get().await?; let mut conn = self.db.get().await?;
@@ -278,14 +304,10 @@ impl KeyHolder {
// Creates new `aead_encrypted` entry in the database and returns it's ID // Creates new `aead_encrypted` entry in the database and returns it's ID
#[message] #[message]
pub async fn create_new(&mut self, mut plaintext: SafeCell<Vec<u8>>) -> Result<i32, Error> { pub async fn create_new(&mut self, mut plaintext: SafeCell<Vec<u8>>) -> Result<i32, Error> {
let State::Unsealed { let Unsealed {
root_key, root_key,
root_key_history_id, root_key_history_id,
.. } = Self::expect_unsealed(&mut self.state)?;
} = &mut self.state
else {
return Err(Error::NotBootstrapped);
};
// Order matters here - `get_new_nonce` acquires connection, so we need to call it before next acquire // Order matters here - `get_new_nonce` acquires connection, so we need to call it before next acquire
// Borrow checker note: &mut borrow a few lines above is disjoint from this field // Borrow checker note: &mut borrow a few lines above is disjoint from this field
@@ -315,19 +337,16 @@ impl KeyHolder {
} }
#[message] #[message]
pub fn get_state(&self) -> KeyHolderState { pub fn get_state(&self) -> VaultState {
self.state.discriminant() self.state.discriminant()
} }
#[message] #[message]
pub fn sign_integrity(&mut self, mac_input: Vec<u8>) -> Result<(i32, Vec<u8>), Error> { pub fn sign_integrity(&mut self, mac_input: Vec<u8>) -> Result<(i32, Vec<u8>), Error> {
let State::Unsealed { let Unsealed {
root_key, root_key,
root_key_history_id, root_key_history_id,
} = &mut self.state } = Self::expect_unsealed(&mut self.state)?;
else {
return Err(Error::NotBootstrapped);
};
let mut hmac = root_key let mut hmac = root_key
.0 .0
@@ -349,13 +368,10 @@ impl KeyHolder {
expected_mac: Vec<u8>, expected_mac: Vec<u8>,
key_version: i32, key_version: i32,
) -> Result<bool, Error> { ) -> Result<bool, Error> {
let State::Unsealed { let Unsealed {
root_key, root_key,
root_key_history_id, root_key_history_id,
} = &mut self.state } = Self::expect_unsealed(&mut self.state)?;
else {
return Err(Error::NotBootstrapped);
};
if *root_key_history_id != key_version { if *root_key_history_id != key_version {
return Ok(false); return Ok(false);
@@ -374,17 +390,16 @@ impl KeyHolder {
} }
#[message] #[message]
pub fn seal(&mut self) -> Result<(), Error> { pub async fn seal(&mut self) -> Result<(), Error> {
let State::Unsealed { let Unsealed {
root_key_history_id, root_key_history_id,
.. ..
} = &self.state } = Self::expect_unsealed(&mut self.state)?;
else {
return Err(Error::NotBootstrapped);
};
self.state = State::Sealed { self.state = State::Sealed {
root_key_history_id: *root_key_history_id, root_key_history_id: *root_key_history_id,
}; };
self.events.tell(Publish(events::VaultResealed)).await;
Ok(()) Ok(())
} }
} }
@@ -395,13 +410,18 @@ mod tests {
use diesel_async::RunQueryDsl; use diesel_async::RunQueryDsl;
use crate::db::{self}; use crate::{
actors::GlobalActors,
db::{self},
};
use arbiter_crypto::safecell::{SafeCell, SafeCellHandle as _}; use arbiter_crypto::safecell::{SafeCell, SafeCellHandle as _};
use super::*; use super::*;
async fn bootstrapped_actor(db: &db::DatabasePool) -> KeyHolder { async fn bootstrapped_actor(db: &db::DatabasePool) -> Vault {
let mut actor = KeyHolder::new(db.clone()).await.unwrap(); let mut actor = Vault::new(db.clone(), GlobalActors::spawn_message_bus())
.await
.unwrap();
let seal_key = SafeCell::new(b"test-seal-key".to_vec()); let seal_key = SafeCell::new(b"test-seal-key".to_vec());
actor.bootstrap(seal_key).await.unwrap(); actor.bootstrap(seal_key).await.unwrap();
actor actor
@@ -413,17 +433,17 @@ mod tests {
let db = db::create_test_pool().await; let db = db::create_test_pool().await;
let mut actor = bootstrapped_actor(&db).await; let mut actor = bootstrapped_actor(&db).await;
let root_key_history_id = match actor.state { let root_key_history_id = match actor.state {
State::Unsealed { State::Unsealed(Unsealed {
root_key_history_id, root_key_history_id,
.. ..
} => root_key_history_id, }) => root_key_history_id,
_ => panic!("expected unsealed state"), _ => panic!("expected unsealed state"),
}; };
let n1 = KeyHolder::get_new_nonce(&db, root_key_history_id) let n1 = Vault::get_new_nonce(&db, root_key_history_id)
.await .await
.unwrap(); .unwrap();
let n2 = KeyHolder::get_new_nonce(&db, root_key_history_id) let n2 = Vault::get_new_nonce(&db, root_key_history_id)
.await .await
.unwrap(); .unwrap();
assert!(n2.to_vec() > n1.to_vec(), "nonce must increase"); assert!(n2.to_vec() > n1.to_vec(), "nonce must increase");

View File

@@ -1,4 +1,4 @@
use crate::{actors::keyholder, crypto::integrity::hashing::Hashable}; use crate::{actors::vault, crypto::integrity::hashing::Hashable};
use arbiter_crypto::safecell::SafeCellHandle as _; use arbiter_crypto::safecell::SafeCellHandle as _;
use hmac::{Hmac, Mac as _}; use hmac::{Hmac, Mac as _};
use sha2::Sha256; use sha2::Sha256;
@@ -11,7 +11,7 @@ use sha2::Digest as _;
pub mod hashing; pub mod hashing;
use crate::{ use crate::{
actors::keyholder::{KeyHolder, SignIntegrity, VerifyIntegrity}, actors::vault::{SignIntegrity, Vault, VerifyIntegrity},
db::{ db::{
self, self,
models::{IntegrityEnvelope, NewIntegrityEnvelope}, models::{IntegrityEnvelope, NewIntegrityEnvelope},
@@ -24,11 +24,11 @@ pub enum Error {
#[error("Database error: {0}")] #[error("Database error: {0}")]
Database(#[from] db::DatabaseError), Database(#[from] db::DatabaseError),
#[error("KeyHolder error: {0}")] #[error("Vault error: {0}")]
Keyholder(#[from] keyholder::Error), Vault(#[from] vault::Error),
#[error("KeyHolder mailbox error")] #[error("Vault mailbox error")]
KeyholderSend, VaultSend,
#[error("Integrity envelope is missing for entity {entity_kind}")] #[error("Integrity envelope is missing for entity {entity_kind}")]
MissingEnvelope { entity_kind: &'static str }, MissingEnvelope { entity_kind: &'static str },
@@ -105,7 +105,7 @@ impl IntoId for &'_ [u8] {
pub async fn sign_entity<E: Integrable>( pub async fn sign_entity<E: Integrable>(
conn: &mut impl AsyncConnection<Backend = Sqlite>, conn: &mut impl AsyncConnection<Backend = Sqlite>,
keyholder: &ActorRef<KeyHolder>, vault: &ActorRef<Vault>,
entity: &E, entity: &E,
entity_id: impl IntoId, entity_id: impl IntoId,
) -> Result<(), Error> { ) -> Result<(), Error> {
@@ -115,13 +115,14 @@ pub async fn sign_entity<E: Integrable>(
let mac_input = build_mac_input(E::KIND, &entity_id, E::VERSION, &payload_hash); let mac_input = build_mac_input(E::KIND, &entity_id, E::VERSION, &payload_hash);
let (key_version, mac) = keyholder let (key_version, mac) =
.ask(SignIntegrity { mac_input }) vault
.await .ask(SignIntegrity { mac_input })
.map_err(|err| match err { .await
kameo::error::SendError::HandlerError(inner) => Error::Keyholder(inner), .map_err(|err| match err {
_ => Error::KeyholderSend, kameo::error::SendError::HandlerError(inner) => Error::Vault(inner),
})?; _ => Error::VaultSend,
})?;
insert_into(integrity_envelope::table) insert_into(integrity_envelope::table)
.values(NewIntegrityEnvelope { .values(NewIntegrityEnvelope {
@@ -150,7 +151,7 @@ pub async fn sign_entity<E: Integrable>(
pub async fn verify_entity<E: Integrable>( pub async fn verify_entity<E: Integrable>(
conn: &mut impl AsyncConnection<Backend = Sqlite>, conn: &mut impl AsyncConnection<Backend = Sqlite>,
keyholder: &ActorRef<KeyHolder>, vault: &ActorRef<Vault>,
entity: &E, entity: &E,
entity_id: impl IntoId, entity_id: impl IntoId,
) -> Result<AttestationStatus, Error> { ) -> Result<AttestationStatus, Error> {
@@ -178,7 +179,7 @@ pub async fn verify_entity<E: Integrable>(
let payload_hash = payload_hash(&entity); let payload_hash = payload_hash(&entity);
let mac_input = build_mac_input(E::KIND, &entity_id, envelope.payload_version, &payload_hash); let mac_input = build_mac_input(E::KIND, &entity_id, envelope.payload_version, &payload_hash);
let result = keyholder let result = vault
.ask(VerifyIntegrity { .ask(VerifyIntegrity {
mac_input, mac_input,
expected_mac: envelope.mac, expected_mac: envelope.mac,
@@ -191,10 +192,10 @@ pub async fn verify_entity<E: Integrable>(
Ok(false) => Err(Error::MacMismatch { Ok(false) => Err(Error::MacMismatch {
entity_kind: E::KIND, entity_kind: E::KIND,
}), }),
Err(SendError::HandlerError(keyholder::Error::NotBootstrapped)) => { Err(SendError::HandlerError(vault::Error::NotBootstrapped)) => {
Ok(AttestationStatus::Unavailable) Ok(AttestationStatus::Unavailable)
} }
Err(_) => Err(Error::KeyholderSend), Err(_) => Err(Error::VaultSend),
} }
} }
@@ -203,19 +204,17 @@ mod tests {
use diesel::{ExpressionMethods as _, QueryDsl}; use diesel::{ExpressionMethods as _, QueryDsl};
use diesel_async::RunQueryDsl; use diesel_async::RunQueryDsl;
use kameo::{actor::ActorRef, prelude::Spawn}; use kameo::{actor::ActorRef, prelude::Spawn};
use sha2::Digest; use sha2::Digest;
use crate::{ use crate::{
actors::keyholder::{Bootstrap, KeyHolder}, actors::{GlobalActors, vault::{Bootstrap, Vault}},
db::{self, schema}, db::{self, schema},
}; };
use arbiter_crypto::safecell::{SafeCell, SafeCellHandle as _}; use arbiter_crypto::safecell::{SafeCell, SafeCellHandle as _};
use super::{Error, Integrable, sign_entity, verify_entity};
use super::hashing::Hashable; use super::hashing::Hashable;
use super::{Error, Integrable, sign_entity, verify_entity};
#[derive(Clone)] #[derive(Clone)]
struct DummyEntity { struct DummyEntity {
@@ -233,8 +232,12 @@ mod tests {
const KIND: &'static str = "dummy_entity"; const KIND: &'static str = "dummy_entity";
} }
async fn bootstrapped_keyholder(db: &db::DatabasePool) -> ActorRef<KeyHolder> { async fn bootstrapped_vault(db: &db::DatabasePool) -> ActorRef<Vault> {
let actor = KeyHolder::spawn(KeyHolder::new(db.clone()).await.unwrap()); let actor = Vault::spawn(
Vault::new(db.clone(), GlobalActors::spawn_message_bus())
.await
.unwrap(),
);
actor actor
.ask(Bootstrap { .ask(Bootstrap {
seal_key_raw: SafeCell::new(b"integrity-test-seal-key".to_vec()), seal_key_raw: SafeCell::new(b"integrity-test-seal-key".to_vec()),
@@ -247,7 +250,7 @@ mod tests {
#[tokio::test] #[tokio::test]
async fn sign_writes_envelope_and_verify_passes() { async fn sign_writes_envelope_and_verify_passes() {
let db = db::create_test_pool().await; let db = db::create_test_pool().await;
let keyholder = bootstrapped_keyholder(&db).await; let vault = bootstrapped_vault(&db).await;
let mut conn = db.get().await.unwrap(); let mut conn = db.get().await.unwrap();
const ENTITY_ID: &[u8] = b"entity-id-7"; const ENTITY_ID: &[u8] = b"entity-id-7";
@@ -257,7 +260,7 @@ mod tests {
payload: b"payload-v1".to_vec(), payload: b"payload-v1".to_vec(),
}; };
sign_entity(&mut conn, &keyholder, &entity, ENTITY_ID) sign_entity(&mut conn, &vault, &entity, ENTITY_ID)
.await .await
.unwrap(); .unwrap();
@@ -270,7 +273,7 @@ mod tests {
.unwrap(); .unwrap();
assert_eq!(count, 1, "envelope row must be created exactly once"); assert_eq!(count, 1, "envelope row must be created exactly once");
verify_entity(&mut conn, &keyholder, &entity, ENTITY_ID) verify_entity(&mut conn, &vault, &entity, ENTITY_ID)
.await .await
.unwrap(); .unwrap();
} }
@@ -278,7 +281,7 @@ mod tests {
#[tokio::test] #[tokio::test]
async fn tampered_mac_fails_verification() { async fn tampered_mac_fails_verification() {
let db = db::create_test_pool().await; let db = db::create_test_pool().await;
let keyholder = bootstrapped_keyholder(&db).await; let vault = bootstrapped_vault(&db).await;
let mut conn = db.get().await.unwrap(); let mut conn = db.get().await.unwrap();
const ENTITY_ID: &[u8] = b"entity-id-11"; const ENTITY_ID: &[u8] = b"entity-id-11";
@@ -288,7 +291,7 @@ mod tests {
payload: b"payload-v1".to_vec(), payload: b"payload-v1".to_vec(),
}; };
sign_entity(&mut conn, &keyholder, &entity, ENTITY_ID) sign_entity(&mut conn, &vault, &entity, ENTITY_ID)
.await .await
.unwrap(); .unwrap();
@@ -300,7 +303,7 @@ mod tests {
.await .await
.unwrap(); .unwrap();
let err = verify_entity(&mut conn, &keyholder, &entity, ENTITY_ID) let err = verify_entity(&mut conn, &vault, &entity, ENTITY_ID)
.await .await
.unwrap_err(); .unwrap_err();
assert!(matches!(err, Error::MacMismatch { .. })); assert!(matches!(err, Error::MacMismatch { .. }));
@@ -309,7 +312,7 @@ mod tests {
#[tokio::test] #[tokio::test]
async fn changed_payload_fails_verification() { async fn changed_payload_fails_verification() {
let db = db::create_test_pool().await; let db = db::create_test_pool().await;
let keyholder = bootstrapped_keyholder(&db).await; let vault = bootstrapped_vault(&db).await;
let mut conn = db.get().await.unwrap(); let mut conn = db.get().await.unwrap();
const ENTITY_ID: &[u8] = b"entity-id-21"; const ENTITY_ID: &[u8] = b"entity-id-21";
@@ -319,7 +322,7 @@ mod tests {
payload: b"payload-v1".to_vec(), payload: b"payload-v1".to_vec(),
}; };
sign_entity(&mut conn, &keyholder, &entity, ENTITY_ID) sign_entity(&mut conn, &vault, &entity, ENTITY_ID)
.await .await
.unwrap(); .unwrap();
@@ -328,7 +331,7 @@ mod tests {
..entity ..entity
}; };
let err = verify_entity(&mut conn, &keyholder, &tampered, ENTITY_ID) let err = verify_entity(&mut conn, &vault, &tampered, ENTITY_ID)
.await .await
.unwrap_err(); .unwrap_err();
assert!(matches!(err, Error::MacMismatch { .. })); assert!(matches!(err, Error::MacMismatch { .. }));

View File

@@ -11,7 +11,7 @@ use diesel_async::{AsyncConnection, RunQueryDsl};
use kameo::actor::ActorRef; use kameo::actor::ActorRef;
use crate::{ use crate::{
actors::keyholder::KeyHolder, actors::vault::Vault,
crypto::integrity, crypto::integrity,
db::{ db::{
self, DatabaseError, self, DatabaseError,
@@ -138,7 +138,7 @@ async fn check_shared_constraints(
// Supporting only EIP-1559 transactions for now, but we can easily extend this to support legacy transactions if needed // Supporting only EIP-1559 transactions for now, but we can easily extend this to support legacy transactions if needed
pub struct Engine { pub struct Engine {
db: db::DatabasePool, db: db::DatabasePool,
keyholder: ActorRef<KeyHolder>, vault: ActorRef<Vault>,
} }
impl Engine { impl Engine {
@@ -158,7 +158,7 @@ impl Engine {
.map_err(DatabaseError::from)? .map_err(DatabaseError::from)?
.ok_or(PolicyError::NoMatchingGrant)?; .ok_or(PolicyError::NoMatchingGrant)?;
integrity::verify_entity(&mut conn, &self.keyholder, &grant.settings, grant.id).await?; integrity::verify_entity(&mut conn, &self.vault, &grant.settings, grant.id).await?;
let mut violations = check_shared_constraints( let mut violations = check_shared_constraints(
&context, &context,
@@ -207,8 +207,8 @@ impl Engine {
} }
impl Engine { impl Engine {
pub fn new(db: db::DatabasePool, keyholder: ActorRef<KeyHolder>) -> Self { pub fn new(db: db::DatabasePool, vault: ActorRef<Vault>) -> Self {
Self { db, keyholder } Self { db, vault }
} }
pub async fn create_grant<P: Policy>( pub async fn create_grant<P: Policy>(
@@ -219,7 +219,7 @@ impl Engine {
P::Settings: Clone, P::Settings: Clone,
{ {
let mut conn = self.db.get().await?; let mut conn = self.db.get().await?;
let keyholder = self.keyholder.clone(); let vault = self.vault.clone();
let id = conn let id = conn
.transaction(|conn| { .transaction(|conn| {
@@ -258,7 +258,7 @@ impl Engine {
P::create_grant(&basic_grant, &full_grant.specific, conn).await?; P::create_grant(&basic_grant, &full_grant.specific, conn).await?;
integrity::sign_entity(conn, &keyholder, &full_grant, basic_grant.id) integrity::sign_entity(conn, &vault, &full_grant, basic_grant.id)
.await .await
.map_err(|_| diesel::result::Error::RollbackTransaction)?; .map_err(|_| diesel::result::Error::RollbackTransaction)?;
@@ -283,7 +283,7 @@ impl Engine {
// Verify integrity of all grants before returning any results // Verify integrity of all grants before returning any results
for grant in &all_grants { for grant in &all_grants {
integrity::verify_entity(conn, &self.keyholder, &grant.settings, grant.id).await?; integrity::verify_entity(conn, &self.vault, &grant.settings, grant.id).await?;
} }
Ok(all_grants.into_iter().map(|g| Grant { Ok(all_grants.into_iter().map(|g| Grant {

View File

@@ -10,7 +10,7 @@ use tonic::Status;
use tracing::{info, warn}; use tracing::{info, warn};
use crate::{ use crate::{
actors::client::{ClientConnection, session::ClientSession}, peers::client::{ClientConnection, session::ClientSession},
grpc::request_tracker::RequestTracker, grpc::request_tracker::RequestTracker,
}; };

View File

@@ -22,7 +22,7 @@ use tonic::Status;
use tracing::warn; use tracing::warn;
use crate::{ use crate::{
actors::client::{self, ClientConnection, auth}, peers::client::{self, ClientConnection, auth},
grpc::request_tracker::RequestTracker, grpc::request_tracker::RequestTracker,
}; };

View File

@@ -16,7 +16,7 @@ use tonic::Status;
use tracing::warn; use tracing::warn;
use crate::{ use crate::{
actors::client::session::{ClientSession, HandleSignTransaction, SignTransactionRpcError}, peers::client::session::{ClientSession, HandleSignTransaction, SignTransactionRpcError},
grpc::{ grpc::{
Convert, TryConvert, Convert, TryConvert,
common::inbound::{RawEvmAddress, RawEvmTransaction}, common::inbound::{RawEvmAddress, RawEvmTransaction},

View File

@@ -12,9 +12,9 @@ use kameo::{actor::ActorRef, error::SendError};
use tonic::Status; use tonic::Status;
use tracing::warn; use tracing::warn;
use crate::actors::{ use crate::{
client::session::{ClientSession, Error, HandleQueryVaultState}, peers::client::session::{ClientSession, Error, HandleQueryVaultState},
keyholder::KeyHolderState, actors::vault::VaultState,
}; };
pub(super) async fn dispatch( pub(super) async fn dispatch(
@@ -30,9 +30,9 @@ pub(super) async fn dispatch(
match payload { match payload {
VaultRequestPayload::QueryState(_) => { VaultRequestPayload::QueryState(_) => {
let state = match actor.ask(HandleQueryVaultState {}).await { let state = match actor.ask(HandleQueryVaultState {}).await {
Ok(KeyHolderState::Unbootstrapped) => ProtoVaultState::Unbootstrapped, Ok(VaultState::Unbootstrapped) => ProtoVaultState::Unbootstrapped,
Ok(KeyHolderState::Sealed) => ProtoVaultState::Sealed, Ok(VaultState::Sealed) => ProtoVaultState::Sealed,
Ok(KeyHolderState::Unsealed) => ProtoVaultState::Unsealed, Ok(VaultState::Unsealed) => ProtoVaultState::Unsealed,
Err(SendError::HandlerError(Error::Internal)) => ProtoVaultState::Error, Err(SendError::HandlerError(Error::Internal)) => ProtoVaultState::Error,
Err(err) => { Err(err) => {
warn!(error = ?err, "Failed to query vault state"); warn!(error = ?err, "Failed to query vault state");

View File

@@ -10,7 +10,7 @@ use tonic::{Request, Response, Status, async_trait};
use tracing::info; use tracing::info;
use crate::{ use crate::{
actors::{client::ClientConnection, user_agent::UserAgentConnection}, peers::{client::ClientConnection, user_agent::UserAgentConnection},
grpc::user_agent::start, grpc::user_agent::start,
}; };

View File

@@ -14,7 +14,7 @@ use tonic::Status;
use tracing::{error, info, warn}; use tracing::{error, info, warn};
use crate::{ use crate::{
actors::user_agent::{OutOfBand, UserAgentConnection, UserAgentSession}, peers::user_agent::{OutOfBand, UserAgentConnection, UserAgentSession},
grpc::request_tracker::RequestTracker, grpc::request_tracker::RequestTracker,
}; };

View File

@@ -18,7 +18,7 @@ use tonic::Status;
use tracing::warn; use tracing::warn;
use crate::{ use crate::{
actors::user_agent::{UserAgentConnection, auth}, peers::user_agent::{UserAgentConnection, auth},
grpc::request_tracker::RequestTracker, grpc::request_tracker::RequestTracker,
}; };

View File

@@ -23,7 +23,7 @@ use tonic::Status;
use tracing::warn; use tracing::warn;
use crate::{ use crate::{
actors::user_agent::{ peers::user_agent::{
UserAgentSession, UserAgentSession,
session::connection::{ session::connection::{
GrantMutationError, HandleEvmWalletCreate, HandleEvmWalletList, HandleGrantCreate, GrantMutationError, HandleEvmWalletCreate, HandleEvmWalletList, HandleGrantCreate,

View File

@@ -21,7 +21,7 @@ use tonic::Status;
use tracing::{info, warn}; use tracing::{info, warn};
use crate::{ use crate::{
actors::user_agent::{ peers::user_agent::{
OutOfBand, UserAgentSession, OutOfBand, UserAgentSession,
session::connection::{ session::connection::{
HandleGrantEvmWalletAccess, HandleListWalletAccess, HandleNewClientApprove, HandleGrantEvmWalletAccess, HandleListWalletAccess, HandleNewClientApprove,

View File

@@ -20,9 +20,9 @@ use kameo::{actor::ActorRef, error::SendError};
use tonic::Status; use tonic::Status;
use tracing::warn; use tracing::warn;
use crate::actors::{ use crate::{
keyholder::KeyHolderState, actors::vault::VaultState,
user_agent::{ peers::user_agent::{
UserAgentSession, UserAgentSession,
session::connection::{ session::connection::{
BootstrapError, HandleBootstrapEncryptedKey, HandleQueryVaultState, BootstrapError, HandleBootstrapEncryptedKey, HandleQueryVaultState,
@@ -166,9 +166,9 @@ async fn handle_query_vault_state(
actor: &ActorRef<UserAgentSession>, actor: &ActorRef<UserAgentSession>,
) -> Result<Option<UserAgentResponsePayload>, Status> { ) -> Result<Option<UserAgentResponsePayload>, Status> {
let state = match actor.ask(HandleQueryVaultState {}).await { let state = match actor.ask(HandleQueryVaultState {}).await {
Ok(KeyHolderState::Unbootstrapped) => ProtoVaultState::Unbootstrapped, Ok(VaultState::Unbootstrapped) => ProtoVaultState::Unbootstrapped,
Ok(KeyHolderState::Sealed) => ProtoVaultState::Sealed, Ok(VaultState::Sealed) => ProtoVaultState::Sealed,
Ok(KeyHolderState::Unsealed) => ProtoVaultState::Unsealed, Ok(VaultState::Unsealed) => ProtoVaultState::Unsealed,
Err(err) => { Err(err) => {
warn!(error = ?err, "Failed to query vault state"); warn!(error = ?err, "Failed to query vault state");
ProtoVaultState::Error ProtoVaultState::Error

View File

@@ -2,6 +2,7 @@
use crate::context::ServerContext; use crate::context::ServerContext;
pub mod actors; pub mod actors;
pub mod peers;
pub mod context; pub mod context;
pub mod crypto; pub mod crypto;
pub mod db; pub mod db;

View File

@@ -14,9 +14,7 @@ use tracing::error;
use crate::{ use crate::{
actors::{ actors::{
client::{ClientConnection, ClientCredentials, ClientProfile}, GlobalActors, flow_coordinator::{self, RequestClientApproval}, vault::Vault
flow_coordinator::{self, RequestClientApproval},
keyholder::KeyHolder,
}, },
crypto::integrity::{self, AttestationStatus}, crypto::integrity::{self, AttestationStatus},
db::{ db::{
@@ -26,6 +24,8 @@ use crate::{
}, },
}; };
use super::{ClientConnection, ClientCredentials, ClientProfile};
#[derive(thiserror::Error, Debug, Clone, PartialEq, Eq)] #[derive(thiserror::Error, Debug, Clone, PartialEq, Eq)]
pub enum Error { pub enum Error {
#[error("Database pool unavailable")] #[error("Database pool unavailable")]
@@ -104,7 +104,7 @@ async fn get_current_nonce_and_id(
async fn verify_integrity( async fn verify_integrity(
db: &db::DatabasePool, db: &db::DatabasePool,
keyholder: &ActorRef<KeyHolder>, vault: &ActorRef<Vault>,
pubkey: &authn::PublicKey, pubkey: &authn::PublicKey,
) -> Result<(), Error> { ) -> Result<(), Error> {
let mut db_conn = db.get().await.map_err(|e| { let mut db_conn = db.get().await.map_err(|e| {
@@ -119,7 +119,7 @@ async fn verify_integrity(
let attestation = integrity::verify_entity( let attestation = integrity::verify_entity(
&mut db_conn, &mut db_conn,
keyholder, vault,
&ClientCredentials { &ClientCredentials {
pubkey: pubkey.clone(), pubkey: pubkey.clone(),
nonce, nonce,
@@ -144,7 +144,7 @@ async fn verify_integrity(
/// Returns the new nonce, which is used as the challenge nonce. /// Returns the new nonce, which is used as the challenge nonce.
async fn create_nonce( async fn create_nonce(
db: &db::DatabasePool, db: &db::DatabasePool,
keyholder: &ActorRef<KeyHolder>, vault: &ActorRef<Vault>,
pubkey: &authn::PublicKey, pubkey: &authn::PublicKey,
) -> Result<i32, Error> { ) -> Result<i32, Error> {
let pubkey_bytes = pubkey.to_bytes(); let pubkey_bytes = pubkey.to_bytes();
@@ -156,7 +156,7 @@ async fn create_nonce(
})?; })?;
conn.exclusive_transaction(|conn| { conn.exclusive_transaction(|conn| {
let keyholder = keyholder.clone(); let vault = vault.clone();
let pubkey = pubkey.clone(); let pubkey = pubkey.clone();
Box::pin(async move { Box::pin(async move {
let (id, new_nonce): (i32, i32) = update(program_client::table) let (id, new_nonce): (i32, i32) = update(program_client::table)
@@ -168,7 +168,7 @@ async fn create_nonce(
integrity::sign_entity( integrity::sign_entity(
conn, conn,
&keyholder, &vault,
&ClientCredentials { &ClientCredentials {
pubkey: pubkey.clone(), pubkey: pubkey.clone(),
nonce: new_nonce, nonce: new_nonce,
@@ -188,7 +188,7 @@ async fn create_nonce(
} }
async fn approve_new_client( async fn approve_new_client(
actors: &crate::actors::GlobalActors, actors: &GlobalActors,
profile: ClientProfile, profile: ClientProfile,
) -> Result<(), Error> { ) -> Result<(), Error> {
let result = actors let result = actors
@@ -212,7 +212,7 @@ async fn approve_new_client(
async fn insert_client( async fn insert_client(
db: &db::DatabasePool, db: &db::DatabasePool,
keyholder: &ActorRef<KeyHolder>, vault: &ActorRef<Vault>,
pubkey: &authn::PublicKey, pubkey: &authn::PublicKey,
metadata: &ClientMetadata, metadata: &ClientMetadata,
) -> Result<i32, Error> { ) -> Result<i32, Error> {
@@ -226,7 +226,7 @@ async fn insert_client(
})?; })?;
conn.exclusive_transaction(|conn| { conn.exclusive_transaction(|conn| {
let keyholder = keyholder.clone(); let vault = vault.clone();
let pubkey = pubkey.clone(); let pubkey = pubkey.clone();
Box::pin(async move { Box::pin(async move {
const NONCE_START: i32 = 1; const NONCE_START: i32 = 1;
@@ -254,7 +254,7 @@ async fn insert_client(
integrity::sign_entity( integrity::sign_entity(
conn, conn,
&keyholder, &vault,
&ClientCredentials { &ClientCredentials {
pubkey: pubkey.clone(), pubkey: pubkey.clone(),
nonce: NONCE_START, nonce: NONCE_START,
@@ -391,7 +391,7 @@ where
let client_id = match get_current_nonce_and_id(&props.db, &pubkey).await? { let client_id = match get_current_nonce_and_id(&props.db, &pubkey).await? {
Some((id, _)) => { Some((id, _)) => {
verify_integrity(&props.db, &props.actors.key_holder, &pubkey).await?; verify_integrity(&props.db, &props.actors.vault, &pubkey).await?;
id id
} }
None => { None => {
@@ -403,12 +403,12 @@ where
}, },
) )
.await?; .await?;
insert_client(&props.db, &props.actors.key_holder, &pubkey, &metadata).await? insert_client(&props.db, &props.actors.vault, &pubkey, &metadata).await?
} }
}; };
sync_client_metadata(&props.db, client_id, &metadata).await?; sync_client_metadata(&props.db, client_id, &metadata).await?;
let challenge_nonce = create_nonce(&props.db, &props.actors.key_holder, &pubkey).await?; let challenge_nonce = create_nonce(&props.db, &props.actors.vault, &pubkey).await?;
challenge_client(transport, pubkey, challenge_nonce).await?; challenge_client(transport, pubkey, challenge_nonce).await?;
transport transport

View File

@@ -4,9 +4,9 @@ use kameo::actor::Spawn;
use tracing::{error, info}; use tracing::{error, info};
use crate::{ use crate::{
actors::{GlobalActors, client::session::ClientSession}, actors::GlobalActors,
crypto::integrity::{Integrable, hashing::Hashable}, crypto::integrity::{Integrable, hashing::Hashable},
db, db, peers::client::session::ClientSession,
}; };
#[derive(Debug, Clone)] #[derive(Debug, Clone)]

View File

@@ -6,15 +6,16 @@ use alloy::{consensus::TxEip1559, primitives::Address, signers::Signature};
use crate::{ use crate::{
actors::{ actors::{
GlobalActors, GlobalActors,
client::ClientConnection,
evm::{ClientSignTransaction, SignTransactionError}, evm::{ClientSignTransaction, SignTransactionError},
flow_coordinator::RegisterClient, flow_coordinator::RegisterClient,
keyholder::KeyHolderState, vault::VaultState,
}, },
db, db,
evm::VetError, evm::VetError,
}; };
use super::ClientConnection;
pub struct ClientSession { pub struct ClientSession {
props: ClientConnection, props: ClientConnection,
client_id: i32, client_id: i32,
@@ -29,13 +30,13 @@ impl ClientSession {
#[messages] #[messages]
impl ClientSession { impl ClientSession {
#[message] #[message]
pub(crate) async fn handle_query_vault_state(&mut self) -> Result<KeyHolderState, Error> { pub(crate) async fn handle_query_vault_state(&mut self) -> Result<VaultState, Error> {
use crate::actors::keyholder::GetState; use crate::actors::vault::GetState;
let vault_state = match self.props.actors.key_holder.ask(GetState {}).await { let vault_state = match self.props.actors.vault.ask(GetState {}).await {
Ok(state) => state, Ok(state) => state,
Err(err) => { Err(err) => {
error!(?err, actor = "client", "keyholder.query.failed"); error!(?err, actor = "client", "vault.query.failed");
return Err(Error::Internal); return Err(Error::Internal);
} }
}; };

View File

@@ -0,0 +1,2 @@
pub mod user_agent;
pub mod client;

View File

@@ -2,13 +2,11 @@ use arbiter_crypto::authn;
use arbiter_proto::transport::Bi; use arbiter_proto::transport::Bi;
use tracing::error; use tracing::error;
use crate::actors::user_agent::{
UserAgentConnection,
auth::state::{AuthContext, AuthStateMachine},
};
mod state; mod state;
use state::*; use state::*;
use super::UserAgentConnection;
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub enum Inbound { pub enum Inbound {
AuthChallengeRequest { AuthChallengeRequest {

View File

@@ -4,13 +4,14 @@ use diesel::{ExpressionMethods as _, OptionalExtension as _, QueryDsl, update};
use diesel_async::{AsyncConnection, RunQueryDsl}; use diesel_async::{AsyncConnection, RunQueryDsl};
use kameo::actor::ActorRef; use kameo::actor::ActorRef;
use tracing::error; use tracing::error;
use super::super::{UserAgentCredentials, UserAgentConnection};
use super::Error; use super::Error;
use crate::peers::user_agent::auth::Outbound;
use crate::{ use crate::{
actors::{ actors::{
bootstrap::ConsumeToken, bootstrap::ConsumeToken,
keyholder::KeyHolder, vault::Vault,
user_agent::{UserAgentConnection, UserAgentCredentials, auth::Outbound},
}, },
crypto::integrity, crypto::integrity,
db::{DatabasePool, schema::useragent_client}, db::{DatabasePool, schema::useragent_client},
@@ -77,7 +78,7 @@ async fn get_current_nonce_and_id(
async fn verify_integrity( async fn verify_integrity(
db: &DatabasePool, db: &DatabasePool,
keyholder: &ActorRef<KeyHolder>, vault: &ActorRef<Vault>,
pubkey: &authn::PublicKey, pubkey: &authn::PublicKey,
) -> Result<(), Error> { ) -> Result<(), Error> {
let mut db_conn = db.get().await.map_err(|e| { let mut db_conn = db.get().await.map_err(|e| {
@@ -89,7 +90,7 @@ async fn verify_integrity(
let _result = integrity::verify_entity( let _result = integrity::verify_entity(
&mut db_conn, &mut db_conn,
keyholder, vault,
&UserAgentCredentials { &UserAgentCredentials {
pubkey: pubkey.clone(), pubkey: pubkey.clone(),
nonce, nonce,
@@ -107,7 +108,7 @@ async fn verify_integrity(
async fn create_nonce( async fn create_nonce(
db: &DatabasePool, db: &DatabasePool,
keyholder: &ActorRef<KeyHolder>, vault: &ActorRef<Vault>,
pubkey: &authn::PublicKey, pubkey: &authn::PublicKey,
) -> Result<i32, Error> { ) -> Result<i32, Error> {
let mut db_conn = db.get().await.map_err(|e| { let mut db_conn = db.get().await.map_err(|e| {
@@ -130,7 +131,7 @@ async fn create_nonce(
integrity::sign_entity( integrity::sign_entity(
conn, conn,
keyholder, vault,
&UserAgentCredentials { &UserAgentCredentials {
pubkey: pubkey.clone(), pubkey: pubkey.clone(),
nonce: new_nonce, nonce: new_nonce,
@@ -152,7 +153,7 @@ async fn create_nonce(
async fn register_key( async fn register_key(
db: &DatabasePool, db: &DatabasePool,
keyholder: &ActorRef<KeyHolder>, vault: &ActorRef<Vault>,
pubkey: &authn::PublicKey, pubkey: &authn::PublicKey,
) -> Result<(), Error> { ) -> Result<(), Error> {
let pubkey_bytes = pubkey.to_bytes(); let pubkey_bytes = pubkey.to_bytes();
@@ -183,7 +184,7 @@ async fn register_key(
nonce: NONCE_START, nonce: NONCE_START,
}; };
integrity::sign_entity(conn, keyholder, &entity, id) integrity::sign_entity(conn, vault, &entity, id)
.await .await
.map_err(|e| { .map_err(|e| {
error!(error = ?e, "Failed to sign integrity tag for new user-agent key"); error!(error = ?e, "Failed to sign integrity tag for new user-agent key");
@@ -219,9 +220,9 @@ where
&mut self, &mut self,
ChallengeRequest { pubkey }: ChallengeRequest, ChallengeRequest { pubkey }: ChallengeRequest,
) -> Result<ChallengeContext, Self::Error> { ) -> Result<ChallengeContext, Self::Error> {
verify_integrity(&self.conn.db, &self.conn.actors.key_holder, &pubkey).await?; verify_integrity(&self.conn.db, &self.conn.actors.vault, &pubkey).await?;
let nonce = create_nonce(&self.conn.db, &self.conn.actors.key_holder, &pubkey).await?; let nonce = create_nonce(&self.conn.db, &self.conn.actors.vault, &pubkey).await?;
self.transport self.transport
.send(Ok(Outbound::AuthChallenge { nonce })) .send(Ok(Outbound::AuthChallenge { nonce }))
@@ -263,7 +264,7 @@ where
match token_ok { match token_ok {
true => { true => {
register_key(&self.conn.db, &self.conn.actors.key_holder, &pubkey).await?; register_key(&self.conn.db, &self.conn.actors.vault, &pubkey).await?;
self.transport self.transport
.send(Ok(Outbound::AuthSuccess)) .send(Ok(Outbound::AuthSuccess))
.await .await

View File

@@ -1,7 +1,7 @@
use crate::{ use crate::{
actors::{GlobalActors, client::ClientProfile}, actors::GlobalActors,
crypto::integrity::Integrable, crypto::integrity::Integrable,
db, db, peers::client::ClientProfile,
}; };
use arbiter_crypto::authn; use arbiter_crypto::authn;

View File

@@ -8,14 +8,12 @@ use kameo::{Actor, actor::ActorRef, messages};
use thiserror::Error; use thiserror::Error;
use tracing::error; use tracing::error;
use crate::actors::{ use crate::{actors::flow_coordinator::{RegisterUserAgent, client_connect_approval::ClientApprovalController}, peers::client::ClientProfile};
client::ClientProfile,
flow_coordinator::{RegisterUserAgent, client_connect_approval::ClientApprovalController},
user_agent::{OutOfBand, UserAgentConnection},
};
mod state; mod state;
use state::{DummyContext, UserAgentEvents, UserAgentStateMachine}; use state::{DummyContext, UserAgentEvents, UserAgentStateMachine};
use super::{OutOfBand, UserAgentConnection};
#[derive(Debug, Error)] #[derive(Debug, Error)]
pub enum Error { pub enum Error {
#[error("State transition failed")] #[error("State transition failed")]

View File

@@ -14,28 +14,27 @@ use kameo::prelude::Context;
use tracing::{error, info}; use tracing::{error, info};
use x25519_dalek::{EphemeralSecret, PublicKey}; use x25519_dalek::{EphemeralSecret, PublicKey};
use crate::actors::flow_coordinator::client_connect_approval::ClientApprovalAnswer; use crate::{actors::vault::VaultState, peers::user_agent::session::state::{UnsealContext, UserAgentEvents}};
use crate::actors::keyholder::KeyHolderState;
use crate::actors::user_agent::session::Error;
use crate::actors::{ use crate::actors::{
evm::{ evm::{
ClientSignTransaction, Generate, ListWallets, SignTransactionError as EvmSignError, ClientSignTransaction, Generate, ListWallets, SignTransactionError as EvmSignError,
UseragentCreateGrant, UseragentListGrants, UseragentCreateGrant, UseragentListGrants,
}, },
keyholder::{self, Bootstrap, TryUnseal}, vault::{self, Bootstrap, TryUnseal},
user_agent::session::{
UserAgentSession,
state::{UnsealContext, UserAgentEvents, UserAgentStates},
},
}; };
use crate::db::models::{ use crate::db::models::{
EvmWalletAccess, NewEvmWalletAccess, ProgramClient, ProgramClientMetadata, EvmWalletAccess, NewEvmWalletAccess, ProgramClient, ProgramClientMetadata,
}; };
use crate::evm::policies::{Grant, SpecificGrant}; use crate::evm::policies::{Grant, SpecificGrant};
use crate::{
actors::flow_coordinator::client_connect_approval::ClientApprovalAnswer,
};
use super::{UserAgentSession, state, Error};
impl UserAgentSession { impl UserAgentSession {
fn take_unseal_secret(&mut self) -> Result<(EphemeralSecret, PublicKey), Error> { fn take_unseal_secret(&mut self) -> Result<(EphemeralSecret, PublicKey), Error> {
let UserAgentStates::WaitingForUnsealKey(unseal_context) = self.state.state() else { let state::UserAgentStates::WaitingForUnsealKey(unseal_context) = self.state.state() else {
error!("Received encrypted key in invalid state"); error!("Received encrypted key in invalid state");
return Err(Error::internal("Invalid state for unseal encrypted key")); return Err(Error::internal("Invalid state for unseal encrypted key"));
}; };
@@ -184,7 +183,7 @@ impl UserAgentSession {
match self match self
.props .props
.actors .actors
.key_holder .vault
.ask(TryUnseal { .ask(TryUnseal {
seal_key_raw: seal_key_buffer, seal_key_raw: seal_key_buffer,
}) })
@@ -195,17 +194,17 @@ impl UserAgentSession {
self.transition(UserAgentEvents::ReceivedValidKey)?; self.transition(UserAgentEvents::ReceivedValidKey)?;
Ok(()) Ok(())
} }
Err(SendError::HandlerError(keyholder::Error::InvalidKey)) => { Err(SendError::HandlerError(vault::Error::InvalidKey)) => {
self.transition(UserAgentEvents::ReceivedInvalidKey)?; self.transition(UserAgentEvents::ReceivedInvalidKey)?;
Err(UnsealError::InvalidKey) Err(UnsealError::InvalidKey)
} }
Err(SendError::HandlerError(err)) => { Err(SendError::HandlerError(err)) => {
error!(?err, "Keyholder failed to unseal key"); error!(?err, "Vault failed to unseal key");
self.transition(UserAgentEvents::ReceivedInvalidKey)?; self.transition(UserAgentEvents::ReceivedInvalidKey)?;
Err(UnsealError::InvalidKey) Err(UnsealError::InvalidKey)
} }
Err(err) => { Err(err) => {
error!(?err, "Failed to send unseal request to keyholder"); error!(?err, "Failed to send unseal request to vault");
self.transition(UserAgentEvents::ReceivedInvalidKey)?; self.transition(UserAgentEvents::ReceivedInvalidKey)?;
Err(Error::internal("Vault actor error").into()) Err(Error::internal("Vault actor error").into())
} }
@@ -245,7 +244,7 @@ impl UserAgentSession {
match self match self
.props .props
.actors .actors
.key_holder .vault
.ask(Bootstrap { .ask(Bootstrap {
seal_key_raw: seal_key_buffer, seal_key_raw: seal_key_buffer,
}) })
@@ -256,17 +255,17 @@ impl UserAgentSession {
self.transition(UserAgentEvents::ReceivedValidKey)?; self.transition(UserAgentEvents::ReceivedValidKey)?;
Ok(()) Ok(())
} }
Err(SendError::HandlerError(keyholder::Error::AlreadyBootstrapped)) => { Err(SendError::HandlerError(vault::Error::AlreadyBootstrapped)) => {
self.transition(UserAgentEvents::ReceivedInvalidKey)?; self.transition(UserAgentEvents::ReceivedInvalidKey)?;
Err(BootstrapError::AlreadyBootstrapped) Err(BootstrapError::AlreadyBootstrapped)
} }
Err(SendError::HandlerError(err)) => { Err(SendError::HandlerError(err)) => {
error!(?err, "Keyholder failed to bootstrap vault"); error!(?err, "Vault failed to bootstrap vault");
self.transition(UserAgentEvents::ReceivedInvalidKey)?; self.transition(UserAgentEvents::ReceivedInvalidKey)?;
Err(BootstrapError::InvalidKey) Err(BootstrapError::InvalidKey)
} }
Err(err) => { Err(err) => {
error!(?err, "Failed to send bootstrap request to keyholder"); error!(?err, "Failed to send bootstrap request to vault");
self.transition(UserAgentEvents::ReceivedInvalidKey)?; self.transition(UserAgentEvents::ReceivedInvalidKey)?;
Err(BootstrapError::General(Error::internal( Err(BootstrapError::General(Error::internal(
"Vault actor error", "Vault actor error",
@@ -279,13 +278,13 @@ impl UserAgentSession {
#[messages] #[messages]
impl UserAgentSession { impl UserAgentSession {
#[message] #[message]
pub(crate) async fn handle_query_vault_state(&mut self) -> Result<KeyHolderState, Error> { pub(crate) async fn handle_query_vault_state(&mut self) -> Result<VaultState, Error> {
use crate::actors::keyholder::GetState; use crate::actors::vault::GetState;
let vault_state = match self.props.actors.key_holder.ask(GetState {}).await { let vault_state = match self.props.actors.vault.ask(GetState {}).await {
Ok(state) => state, Ok(state) => state,
Err(err) => { Err(err) => {
error!(?err, actor = "useragent", "keyholder.query.failed"); error!(?err, actor = "useragent", "vault.query.failed");
return Err(Error::internal("Vault is in broken state")); return Err(Error::internal("Vault is in broken state"));
} }
}; };
@@ -374,7 +373,7 @@ impl UserAgentSession {
// Err(GrantMutationError::Internal) // Err(GrantMutationError::Internal)
// } // }
// } // }
let _ = grant_id; let _ = grant_id;
todo!() todo!()
} }

View File

@@ -7,9 +7,10 @@ use arbiter_proto::transport::{Receiver, Sender};
use arbiter_server::{ use arbiter_server::{
actors::{ actors::{
GlobalActors, GlobalActors,
client::{ClientConnection, ClientCredentials, auth, connect_client},
keyholder::Bootstrap, vault::Bootstrap,
}, },
peers::client::{ClientConnection, ClientCredentials, auth, connect_client},
crypto::integrity, crypto::integrity,
db::{self, schema}, db::{self, schema},
}; };
@@ -58,7 +59,7 @@ async fn insert_registered_client(
integrity::sign_entity( integrity::sign_entity(
&mut conn, &mut conn,
&actors.key_holder, &actors.vault,
&ClientCredentials { &ClientCredentials {
pubkey: pubkey.into(), pubkey: pubkey.into(),
nonce: 1, nonce: 1,
@@ -103,7 +104,7 @@ async fn spawn_test_actors(db: &db::DatabasePool) -> GlobalActors {
let actors = GlobalActors::spawn(db.clone()).await.unwrap(); let actors = GlobalActors::spawn(db.clone()).await.unwrap();
actors actors
.key_holder .vault
.ask(Bootstrap { .ask(Bootstrap {
seal_key_raw: SafeCell::new(b"test-seal-key".to_vec()), seal_key_raw: SafeCell::new(b"test-seal-key".to_vec()),
}) })

View File

@@ -1,7 +1,7 @@
use arbiter_crypto::safecell::{SafeCell, SafeCellHandle as _}; use arbiter_crypto::safecell::{SafeCell, SafeCellHandle as _};
use arbiter_proto::transport::{Bi, Error, Receiver, Sender}; use arbiter_proto::transport::{Bi, Error, Receiver, Sender};
use arbiter_server::{ use arbiter_server::{
actors::keyholder::KeyHolder, actors::{GlobalActors, vault::Vault},
db::{self, schema}, db::{self, schema},
}; };
@@ -11,8 +11,8 @@ use diesel_async::RunQueryDsl;
use tokio::sync::mpsc; use tokio::sync::mpsc;
#[allow(dead_code)] #[allow(dead_code)]
pub async fn bootstrapped_keyholder(db: &db::DatabasePool) -> KeyHolder { pub async fn bootstrapped_vault(db: &db::DatabasePool) -> Vault {
let mut actor = KeyHolder::new(db.clone()).await.unwrap(); let mut actor = Vault::new(db.clone(), GlobalActors::spawn_message_bus()).await.unwrap();
actor actor
.bootstrap(SafeCell::new(b"test-seal-key".to_vec())) .bootstrap(SafeCell::new(b"test-seal-key".to_vec()))
.await .await

View File

@@ -1,8 +0,0 @@
mod common;
#[path = "keyholder/concurrency.rs"]
mod concurrency;
#[path = "keyholder/lifecycle.rs"]
mod lifecycle;
#[path = "keyholder/storage.rs"]
mod storage;

View File

@@ -8,9 +8,10 @@ use arbiter_server::{
actors::{ actors::{
GlobalActors, GlobalActors,
bootstrap::GetToken, bootstrap::GetToken,
keyholder::Bootstrap, vault::Bootstrap,
user_agent::{UserAgentConnection, UserAgentCredentials, auth},
}, },
peers::user_agent::{UserAgentConnection, UserAgentCredentials, auth},
crypto::integrity, crypto::integrity,
db::{self, schema}, db::{self, schema},
}; };
@@ -38,7 +39,7 @@ pub async fn test_bootstrap_token_auth() {
let db = db::create_test_pool().await; let db = db::create_test_pool().await;
let actors = GlobalActors::spawn(db.clone()).await.unwrap(); let actors = GlobalActors::spawn(db.clone()).await.unwrap();
actors actors
.key_holder .vault
.ask(Bootstrap { .ask(Bootstrap {
seal_key_raw: SafeCell::new(b"test-seal-key".to_vec()), seal_key_raw: SafeCell::new(b"test-seal-key".to_vec()),
}) })
@@ -124,7 +125,7 @@ pub async fn test_challenge_auth() {
let db = db::create_test_pool().await; let db = db::create_test_pool().await;
let actors = GlobalActors::spawn(db.clone()).await.unwrap(); let actors = GlobalActors::spawn(db.clone()).await.unwrap();
actors actors
.key_holder .vault
.ask(Bootstrap { .ask(Bootstrap {
seal_key_raw: SafeCell::new(b"test-seal-key".to_vec()), seal_key_raw: SafeCell::new(b"test-seal-key".to_vec()),
}) })
@@ -147,7 +148,7 @@ pub async fn test_challenge_auth() {
.unwrap(); .unwrap();
integrity::sign_entity( integrity::sign_entity(
&mut conn, &mut conn,
&actors.key_holder, &actors.vault,
&UserAgentCredentials { &UserAgentCredentials {
pubkey: new_key.verifying_key().into(), pubkey: new_key.verifying_key().into(),
nonce: 1, nonce: 1,
@@ -213,7 +214,7 @@ pub async fn test_challenge_auth_rejects_integrity_tag_mismatch_when_unsealed()
let actors = GlobalActors::spawn(db.clone()).await.unwrap(); let actors = GlobalActors::spawn(db.clone()).await.unwrap();
actors actors
.key_holder .vault
.ask(Bootstrap { .ask(Bootstrap {
seal_key_raw: SafeCell::new(b"test-seal-key".to_vec()), seal_key_raw: SafeCell::new(b"test-seal-key".to_vec()),
}) })
@@ -262,7 +263,7 @@ pub async fn test_challenge_auth_rejects_invalid_signature() {
let db = db::create_test_pool().await; let db = db::create_test_pool().await;
let actors = GlobalActors::spawn(db.clone()).await.unwrap(); let actors = GlobalActors::spawn(db.clone()).await.unwrap();
actors actors
.key_holder .vault
.ask(Bootstrap { .ask(Bootstrap {
seal_key_raw: SafeCell::new(b"test-seal-key".to_vec()), seal_key_raw: SafeCell::new(b"test-seal-key".to_vec()),
}) })
@@ -285,7 +286,7 @@ pub async fn test_challenge_auth_rejects_invalid_signature() {
.unwrap(); .unwrap();
integrity::sign_entity( integrity::sign_entity(
&mut conn, &mut conn,
&actors.key_holder, &actors.vault,
&UserAgentCredentials { &UserAgentCredentials {
pubkey: new_key.verifying_key().into(), pubkey: new_key.verifying_key().into(),
nonce: 1, nonce: 1,

View File

@@ -2,12 +2,13 @@ use arbiter_crypto::safecell::{SafeCell, SafeCellHandle as _};
use arbiter_server::{ use arbiter_server::{
actors::{ actors::{
GlobalActors, GlobalActors,
keyholder::{Bootstrap, Seal}, vault::{Bootstrap, Seal},
user_agent::{
},
peers::user_agent::{
UserAgentSession, UserAgentSession,
session::connection::{HandleUnsealEncryptedKey, HandleUnsealRequest, UnsealError}, session::connection::{HandleUnsealEncryptedKey, HandleUnsealRequest, UnsealError},
}, },
},
db, db,
}; };
@@ -22,13 +23,13 @@ async fn setup_sealed_user_agent(
let actors = GlobalActors::spawn(db.clone()).await.unwrap(); let actors = GlobalActors::spawn(db.clone()).await.unwrap();
actors actors
.key_holder .vault
.ask(Bootstrap { .ask(Bootstrap {
seal_key_raw: SafeCell::new(seal_key.to_vec()), seal_key_raw: SafeCell::new(seal_key.to_vec()),
}) })
.await .await
.unwrap(); .unwrap();
actors.key_holder.ask(Seal).await.unwrap(); actors.vault.ask(Seal).await.unwrap();
let session = UserAgentSession::spawn(UserAgentSession::new_test(db.clone(), actors)); let session = UserAgentSession::spawn(UserAgentSession::new_test(db.clone(), actors));

View File

@@ -0,0 +1,8 @@
mod common;
#[path = "vault/concurrency.rs"]
mod concurrency;
#[path = "vault/lifecycle.rs"]
mod lifecycle;
#[path = "vault/storage.rs"]
mod storage;

View File

@@ -2,7 +2,7 @@ use std::collections::{HashMap, HashSet};
use arbiter_crypto::safecell::{SafeCell, SafeCellHandle as _}; use arbiter_crypto::safecell::{SafeCell, SafeCellHandle as _};
use arbiter_server::{ use arbiter_server::{
actors::keyholder::{CreateNew, Error, KeyHolder}, actors::{GlobalActors, vault::{CreateNew, Error, Vault}},
db::{self, models, schema}, db::{self, models, schema},
}; };
@@ -14,7 +14,7 @@ use tokio::task::JoinSet;
use crate::common; use crate::common;
async fn write_concurrently( async fn write_concurrently(
actor: ActorRef<KeyHolder>, actor: ActorRef<Vault>,
prefix: &'static str, prefix: &'static str,
count: usize, count: usize,
) -> Vec<(i32, Vec<u8>)> { ) -> Vec<(i32, Vec<u8>)> {
@@ -44,7 +44,7 @@ async fn write_concurrently(
#[test_log::test] #[test_log::test]
async fn concurrent_create_new_no_duplicate_nonces_() { async fn concurrent_create_new_no_duplicate_nonces_() {
let db = db::create_test_pool().await; let db = db::create_test_pool().await;
let actor = KeyHolder::spawn(common::bootstrapped_keyholder(&db).await); let actor = Vault::spawn(common::bootstrapped_vault(&db).await);
let writes = write_concurrently(actor, "nonce-unique", 32).await; let writes = write_concurrently(actor, "nonce-unique", 32).await;
assert_eq!(writes.len(), 32); assert_eq!(writes.len(), 32);
@@ -66,7 +66,7 @@ async fn concurrent_create_new_no_duplicate_nonces_() {
#[test_log::test] #[test_log::test]
async fn concurrent_create_new_root_nonce_never_moves_backward() { async fn concurrent_create_new_root_nonce_never_moves_backward() {
let db = db::create_test_pool().await; let db = db::create_test_pool().await;
let actor = KeyHolder::spawn(common::bootstrapped_keyholder(&db).await); let actor = Vault::spawn(common::bootstrapped_vault(&db).await);
write_concurrently(actor, "root-max", 24).await; write_concurrently(actor, "root-max", 24).await;
@@ -94,7 +94,7 @@ async fn concurrent_create_new_root_nonce_never_moves_backward() {
#[test_log::test] #[test_log::test]
async fn insert_failure_does_not_create_partial_row() { async fn insert_failure_does_not_create_partial_row() {
let db = db::create_test_pool().await; let db = db::create_test_pool().await;
let mut actor = common::bootstrapped_keyholder(&db).await; let mut actor = common::bootstrapped_vault(&db).await;
let root_key_history_id = common::root_key_history_id(&db).await; let root_key_history_id = common::root_key_history_id(&db).await;
let mut conn = db.get().await.unwrap(); let mut conn = db.get().await.unwrap();
@@ -156,12 +156,12 @@ async fn insert_failure_does_not_create_partial_row() {
#[test_log::test] #[test_log::test]
async fn decrypt_roundtrip_after_high_concurrency() { async fn decrypt_roundtrip_after_high_concurrency() {
let db = db::create_test_pool().await; let db = db::create_test_pool().await;
let actor = KeyHolder::spawn(common::bootstrapped_keyholder(&db).await); let actor = Vault::spawn(common::bootstrapped_vault(&db).await);
let writes = write_concurrently(actor, "roundtrip", 40).await; let writes = write_concurrently(actor, "roundtrip", 40).await;
let expected: HashMap<i32, Vec<u8>> = writes.into_iter().collect(); let expected: HashMap<i32, Vec<u8>> = writes.into_iter().collect();
let mut decryptor = KeyHolder::new(db.clone()).await.unwrap(); let mut decryptor = Vault::new(db.clone(), GlobalActors::spawn_message_bus()).await.unwrap();
decryptor decryptor
.try_unseal(SafeCell::new(b"test-seal-key".to_vec())) .try_unseal(SafeCell::new(b"test-seal-key".to_vec()))
.await .await

View File

@@ -1,8 +1,12 @@
use arbiter_crypto::safecell::{SafeCell, SafeCellHandle as _}; use arbiter_crypto::safecell::{SafeCell, SafeCellHandle as _};
use arbiter_server::{ use arbiter_server::{
actors::keyholder::{Error, KeyHolder}, actors::{GlobalActors, vault::{Error, Vault}},
crypto::encryption::v1::{Nonce, ROOT_KEY_TAG}, crypto::encryption::v1::{Nonce, ROOT_KEY_TAG},
db::{self, models, schema}, db::{self, models, schema},
peers::user_agent::{
UserAgentSession,
session::connection::{HandleUnsealEncryptedKey, HandleUnsealRequest, UnsealError},
},
}; };
use diesel::{QueryDsl, SelectableHelper}; use diesel::{QueryDsl, SelectableHelper};
@@ -14,7 +18,7 @@ use crate::common;
#[test_log::test] #[test_log::test]
async fn test_bootstrap() { async fn test_bootstrap() {
let db = db::create_test_pool().await; let db = db::create_test_pool().await;
let mut actor = KeyHolder::new(db.clone()).await.unwrap(); let mut actor = Vault::new(db.clone(), GlobalActors::spawn_message_bus()).await.unwrap();
let seal_key = SafeCell::new(b"test-seal-key".to_vec()); let seal_key = SafeCell::new(b"test-seal-key".to_vec());
actor.bootstrap(seal_key).await.unwrap(); actor.bootstrap(seal_key).await.unwrap();
@@ -37,7 +41,7 @@ async fn test_bootstrap() {
#[test_log::test] #[test_log::test]
async fn test_bootstrap_rejects_double() { async fn test_bootstrap_rejects_double() {
let db = db::create_test_pool().await; let db = db::create_test_pool().await;
let mut actor = common::bootstrapped_keyholder(&db).await; let mut actor = common::bootstrapped_vault(&db).await;
let seal_key2 = SafeCell::new(b"test-seal-key".to_vec()); let seal_key2 = SafeCell::new(b"test-seal-key".to_vec());
let err = actor.bootstrap(seal_key2).await.unwrap_err(); let err = actor.bootstrap(seal_key2).await.unwrap_err();
@@ -48,7 +52,7 @@ async fn test_bootstrap_rejects_double() {
#[test_log::test] #[test_log::test]
async fn test_create_new_before_bootstrap_fails() { async fn test_create_new_before_bootstrap_fails() {
let db = db::create_test_pool().await; let db = db::create_test_pool().await;
let mut actor = KeyHolder::new(db).await.unwrap(); let mut actor = Vault::new(db, GlobalActors::spawn_message_bus()).await.unwrap();
let err = actor let err = actor
.create_new(SafeCell::new(b"data".to_vec())) .create_new(SafeCell::new(b"data".to_vec()))
@@ -61,7 +65,7 @@ async fn test_create_new_before_bootstrap_fails() {
#[test_log::test] #[test_log::test]
async fn test_decrypt_before_bootstrap_fails() { async fn test_decrypt_before_bootstrap_fails() {
let db = db::create_test_pool().await; let db = db::create_test_pool().await;
let mut actor = KeyHolder::new(db).await.unwrap(); let mut actor = Vault::new(db, GlobalActors::spawn_message_bus()).await.unwrap();
let err = actor.decrypt(1).await.unwrap_err(); let err = actor.decrypt(1).await.unwrap_err();
assert!(matches!(err, Error::NotBootstrapped)); assert!(matches!(err, Error::NotBootstrapped));
@@ -71,10 +75,10 @@ async fn test_decrypt_before_bootstrap_fails() {
#[test_log::test] #[test_log::test]
async fn test_new_restores_sealed_state() { async fn test_new_restores_sealed_state() {
let db = db::create_test_pool().await; let db = db::create_test_pool().await;
let actor = common::bootstrapped_keyholder(&db).await; let actor = common::bootstrapped_vault(&db).await;
drop(actor); drop(actor);
let mut actor2 = KeyHolder::new(db).await.unwrap(); let mut actor2 = Vault::new(db, GlobalActors::spawn_message_bus()).await.unwrap();
let err = actor2.decrypt(1).await.unwrap_err(); let err = actor2.decrypt(1).await.unwrap_err();
assert!(matches!(err, Error::NotBootstrapped)); assert!(matches!(err, Error::NotBootstrapped));
} }
@@ -83,7 +87,7 @@ async fn test_new_restores_sealed_state() {
#[test_log::test] #[test_log::test]
async fn test_unseal_correct_password() { async fn test_unseal_correct_password() {
let db = db::create_test_pool().await; let db = db::create_test_pool().await;
let mut actor = common::bootstrapped_keyholder(&db).await; let mut actor = common::bootstrapped_vault(&db).await;
let plaintext = b"survive a restart"; let plaintext = b"survive a restart";
let aead_id = actor let aead_id = actor
@@ -92,7 +96,7 @@ async fn test_unseal_correct_password() {
.unwrap(); .unwrap();
drop(actor); drop(actor);
let mut actor = KeyHolder::new(db.clone()).await.unwrap(); let mut actor = Vault::new(db.clone(), GlobalActors::spawn_message_bus()).await.unwrap();
let seal_key = SafeCell::new(b"test-seal-key".to_vec()); let seal_key = SafeCell::new(b"test-seal-key".to_vec());
actor.try_unseal(seal_key).await.unwrap(); actor.try_unseal(seal_key).await.unwrap();
@@ -104,7 +108,7 @@ async fn test_unseal_correct_password() {
#[test_log::test] #[test_log::test]
async fn test_unseal_wrong_then_correct_password() { async fn test_unseal_wrong_then_correct_password() {
let db = db::create_test_pool().await; let db = db::create_test_pool().await;
let mut actor = common::bootstrapped_keyholder(&db).await; let mut actor = common::bootstrapped_vault(&db).await;
let plaintext = b"important data"; let plaintext = b"important data";
let aead_id = actor let aead_id = actor
@@ -113,7 +117,7 @@ async fn test_unseal_wrong_then_correct_password() {
.unwrap(); .unwrap();
drop(actor); drop(actor);
let mut actor = KeyHolder::new(db.clone()).await.unwrap(); let mut actor = Vault::new(db.clone(), GlobalActors::spawn_message_bus()).await.unwrap();
let bad_key = SafeCell::new(b"wrong-password".to_vec()); let bad_key = SafeCell::new(b"wrong-password".to_vec());
let err = actor.try_unseal(bad_key).await.unwrap_err(); let err = actor.try_unseal(bad_key).await.unwrap_err();

View File

@@ -2,7 +2,7 @@ use std::collections::HashSet;
use arbiter_crypto::safecell::{SafeCell, SafeCellHandle as _}; use arbiter_crypto::safecell::{SafeCell, SafeCellHandle as _};
use arbiter_server::{ use arbiter_server::{
actors::keyholder::Error, actors::vault::Error,
crypto::encryption::v1::Nonce, crypto::encryption::v1::Nonce,
db::{self, models, schema}, db::{self, models, schema},
}; };
@@ -16,7 +16,7 @@ use crate::common;
#[test_log::test] #[test_log::test]
async fn test_create_decrypt_roundtrip() { async fn test_create_decrypt_roundtrip() {
let db = db::create_test_pool().await; let db = db::create_test_pool().await;
let mut actor = common::bootstrapped_keyholder(&db).await; let mut actor = common::bootstrapped_vault(&db).await;
let plaintext = b"hello arbiter"; let plaintext = b"hello arbiter";
let aead_id = actor let aead_id = actor
@@ -32,7 +32,7 @@ async fn test_create_decrypt_roundtrip() {
#[test_log::test] #[test_log::test]
async fn test_decrypt_nonexistent_returns_not_found() { async fn test_decrypt_nonexistent_returns_not_found() {
let db = db::create_test_pool().await; let db = db::create_test_pool().await;
let mut actor = common::bootstrapped_keyholder(&db).await; let mut actor = common::bootstrapped_vault(&db).await;
let err = actor.decrypt(9999).await.unwrap_err(); let err = actor.decrypt(9999).await.unwrap_err();
assert!(matches!(err, Error::NotFound)); assert!(matches!(err, Error::NotFound));
@@ -42,7 +42,7 @@ async fn test_decrypt_nonexistent_returns_not_found() {
#[test_log::test] #[test_log::test]
async fn test_ciphertext_differs_across_entries() { async fn test_ciphertext_differs_across_entries() {
let db = db::create_test_pool().await; let db = db::create_test_pool().await;
let mut actor = common::bootstrapped_keyholder(&db).await; let mut actor = common::bootstrapped_vault(&db).await;
let plaintext = b"same content"; let plaintext = b"same content";
let id1 = actor let id1 = actor
@@ -80,7 +80,7 @@ async fn test_ciphertext_differs_across_entries() {
#[test_log::test] #[test_log::test]
async fn test_nonce_never_reused() { async fn test_nonce_never_reused() {
let db = db::create_test_pool().await; let db = db::create_test_pool().await;
let mut actor = common::bootstrapped_keyholder(&db).await; let mut actor = common::bootstrapped_vault(&db).await;
let n = 5; let n = 5;
for i in 0..n { for i in 0..n {
@@ -124,7 +124,7 @@ async fn test_nonce_never_reused() {
#[test_log::test] #[test_log::test]
async fn broken_db_nonce_format_fails_closed() { async fn broken_db_nonce_format_fails_closed() {
let db = db::create_test_pool().await; let db = db::create_test_pool().await;
let mut actor = common::bootstrapped_keyholder(&db).await; let mut actor = common::bootstrapped_vault(&db).await;
let root_key_history_id = common::root_key_history_id(&db).await; let root_key_history_id = common::root_key_history_id(&db).await;
let mut conn = db.get().await.unwrap(); let mut conn = db.get().await.unwrap();
@@ -145,7 +145,7 @@ async fn broken_db_nonce_format_fails_closed() {
assert!(matches!(err, Error::BrokenDatabase)); assert!(matches!(err, Error::BrokenDatabase));
let db = db::create_test_pool().await; let db = db::create_test_pool().await;
let mut actor = common::bootstrapped_keyholder(&db).await; let mut actor = common::bootstrapped_vault(&db).await;
let id = actor let id = actor
.create_new(SafeCell::new(b"decrypt target".to_vec())) .create_new(SafeCell::new(b"decrypt target".to_vec()))
.await .await