From d1f97617c6f3c3dd54cab7aae8e2a409c8dd5bff Mon Sep 17 00:00:00 2001 From: CleverWild Date: Thu, 16 Apr 2026 20:28:38 +0200 Subject: [PATCH] feat(integrtity): introduce zero cost wrapper `Verified` --- .../arbiter-server/src/crypto/integrity/v1.rs | 271 ++++++++++++------ .../src/crypto/integrity/v1/verified.rs | 151 ++++++++++ .../arbiter-server/src/grpc/client/auth.rs | 5 +- server/crates/arbiter-server/src/lib.rs | 1 - .../arbiter-server/src/peers/client/auth.rs | 85 +++--- .../src/peers/client/session.rs | 14 +- .../src/peers/user_agent/auth/state.rs | 4 +- .../src/peers/user_agent/vault_gate/mod.rs | 2 +- 8 files changed, 381 insertions(+), 152 deletions(-) create mode 100644 server/crates/arbiter-server/src/crypto/integrity/v1/verified.rs diff --git a/server/crates/arbiter-server/src/crypto/integrity/v1.rs b/server/crates/arbiter-server/src/crypto/integrity/v1.rs index 7609e2c..bd1d643 100644 --- a/server/crates/arbiter-server/src/crypto/integrity/v1.rs +++ b/server/crates/arbiter-server/src/crypto/integrity/v1.rs @@ -1,9 +1,9 @@ -use crate::{ - actors::vault::{self, GetState}, - crypto::integrity::hashing::Hashable, -}; +use crate::{actors::vault::{self, GetState}, crypto::integrity::hashing::Hashable}; use hmac::Hmac; use sha2::Sha256; +use std::future::Future; +use std::ops::Deref; +use std::pin::Pin; use diesel::{ExpressionMethods as _, QueryDsl, dsl::insert_into, sqlite::Sqlite}; use diesel_async::{AsyncConnection, RunQueryDsl}; @@ -11,16 +11,23 @@ use kameo::{actor::ActorRef, error::SendError}; use sha2::Digest as _; pub mod hashing; +pub mod verified; use crate::{ actors::vault::{SignIntegrity, Vault, VerifyIntegrity}, db::{ self, - models::{IntegrityEnvelope, NewIntegrityEnvelope}, + models::{IntegrityEnvelope as IntegrityEnvelopeRow, NewIntegrityEnvelope}, schema::integrity_envelope, }, }; +pub const CURRENT_PAYLOAD_VERSION: i32 = 1; +pub const INTEGRITY_SUBKEY_TAG: &[u8] = b"arbiter/db-integrity-key/v1"; + +pub type HmacSha256 = Hmac; +pub use self::verified::{Nested, VerificationOrigin, Verified}; + #[derive(Debug, thiserror::Error)] pub enum Error { #[error("Database error: {0}")] @@ -49,71 +56,90 @@ pub enum Error { } #[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[must_use] pub enum AttestationStatus { Attested, Unavailable, } -pub const CURRENT_PAYLOAD_VERSION: i32 = 1; -pub const INTEGRITY_SUBKEY_TAG: &[u8] = b"arbiter/db-integrity-key/v1"; - -pub type HmacSha256 = Hmac; - pub trait Integrable: Hashable { const KIND: &'static str; const VERSION: i32 = 1; } -fn payload_hash(payload: &impl Hashable) -> [u8; 32] { - let mut hasher = Sha256::new(); - payload.hash(&mut hasher); - hasher.finalize().into() +impl Integrable for &T { + const KIND: &'static str = T::KIND; + const VERSION: i32 = T::VERSION; } -fn push_len_prefixed(out: &mut Vec, bytes: &[u8]) { - out.extend_from_slice(&(bytes.len() as u32).to_be_bytes()); - out.extend_from_slice(bytes); -} +#[derive(Debug, Clone)] +pub struct EntityId(Vec); -fn build_mac_input( - entity_kind: &str, - entity_id: &[u8], - payload_version: i32, - payload_hash: &[u8; 32], -) -> Vec { - let mut out = Vec::with_capacity(8 + entity_kind.len() + entity_id.len() + 32); - push_len_prefixed(&mut out, entity_kind.as_bytes()); - push_len_prefixed(&mut out, entity_id); - out.extend_from_slice(&payload_version.to_be_bytes()); - out.extend_from_slice(payload_hash); - out -} +impl Deref for EntityId { + type Target = [u8]; -pub trait IntoId { - fn into_id(self) -> Vec; -} - -impl IntoId for i32 { - fn into_id(self) -> Vec { - self.to_be_bytes().to_vec() + fn deref(&self) -> &Self::Target { + &self.0 } } -impl IntoId for &'_ [u8] { - fn into_id(self) -> Vec { - self.to_vec() +impl From for EntityId { + fn from(value: i32) -> Self { + Self(value.to_be_bytes().to_vec()) } } -pub async fn sign_entity( +impl From<&'_ [u8]> for EntityId { + fn from(bytes: &'_ [u8]) -> Self { + Self(bytes.to_vec()) + } +} + +pub async fn lookup_verified( + conn: &mut C, + vault: &ActorRef, + entity_id: Id, + load: F, +) -> Result, Error> +where + C: AsyncConnection, + E: Integrable, + Id: Into + Clone, + F: FnOnce(&mut C) -> Fut, + Fut: Future>, +{ + let entity = load(conn).await?; + verify_entity(conn, vault, entity, entity_id).await +} + +pub async fn lookup_verified_from_query( + conn: &mut C, + vault: &ActorRef, + load: F, +) -> Result, Error> +where + C: AsyncConnection + Send, + E: Integrable, + Id: Into + Clone, + F: for<'a> FnOnce( + &'a mut C, + ) -> Pin< + Box> + Send + 'a>, + >, +{ + let (entity_id, entity) = load(conn).await?; + verify_entity(conn, vault, entity, entity_id).await +} + +pub async fn sign_entity + Clone>( conn: &mut impl AsyncConnection, vault: &ActorRef, entity: &E, - entity_id: impl IntoId, -) -> Result<(), Error> { - let payload_hash = payload_hash(&entity); + as_entity_id: Id, +) -> Result>, Error> { + let payload_hash = payload_hash(entity); - let entity_id = entity_id.into_id(); + let entity_id = as_entity_id.clone().into(); let mac_input = build_mac_input(E::KIND, &entity_id, E::VERSION, &payload_hash); @@ -129,7 +155,7 @@ pub async fn sign_entity( insert_into(integrity_envelope::table) .values(NewIntegrityEnvelope { entity_kind: E::KIND.to_owned(), - entity_id, + entity_id: entity_id.to_vec(), payload_version: E::VERSION, key_version, mac: mac.to_vec(), @@ -148,19 +174,19 @@ pub async fn sign_entity( .await .map_err(db::DatabaseError::from)?; - Ok(()) + Ok(Verified::>::new(as_entity_id)) } -pub async fn verify_entity( +pub async fn check_entity_attestation( conn: &mut impl AsyncConnection, vault: &ActorRef, entity: &E, - entity_id: impl IntoId, + entity_id: impl Into, ) -> Result { - let entity_id = entity_id.into_id(); - let envelope: IntegrityEnvelope = integrity_envelope::table + let entity_id = entity_id.into(); + let envelope: IntegrityEnvelopeRow = integrity_envelope::table .filter(integrity_envelope::entity_kind.eq(E::KIND)) - .filter(integrity_envelope::entity_id.eq(&entity_id)) + .filter(integrity_envelope::entity_id.eq(&*entity_id)) .first(conn) .await .map_err(|err| match err { @@ -178,7 +204,7 @@ pub async fn verify_entity( }); } - 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 result = vault @@ -194,24 +220,111 @@ pub async fn verify_entity( Ok(false) => Err(Error::MacMismatch { entity_kind: E::KIND, }), - Err(SendError::HandlerError(vault::Error::Sealed)) => { - Ok(AttestationStatus::Unavailable) - } + Err(SendError::HandlerError(vault::Error::Sealed)) => Ok(AttestationStatus::Unavailable), Err(_) => Err(Error::VaultSend), } } +#[derive(Debug, Clone)] +#[repr(C)] +pub struct VerifiedEntity { + pub entity: Verified, + pub entity_id: Verified>, +} + +impl Deref for VerifiedEntity { + type Target = Verified; + + fn deref(&self) -> &Self::Target { + &self.entity + } +} + +pub async fn verify_entity + Clone>( + conn: &mut impl AsyncConnection, + vault: &ActorRef, + entity: E, + entity_id: Id, +) -> Result, Error> { + match check_entity_attestation(conn, vault, &entity, entity_id.clone()).await? { + AttestationStatus::Attested => Ok(VerifiedEntity { + entity: Verified::new(entity), + entity_id: Verified::new(entity_id), + }), + AttestationStatus::Unavailable => Err(Error::Vault(vault::Error::Sealed)), + } +} + +pub async fn verify_entity_ref<'e, E: Integrable, Id: Into + Clone>( + conn: &mut impl AsyncConnection, + vault: &ActorRef, + entity: &'e E, + entity_id: Id, +) -> Result, Nested>, Error> { + match check_entity_attestation(conn, vault, entity, entity_id.clone()).await? { + AttestationStatus::Attested => Ok(Verified::, Nested>::new( + VerifiedEntity { + entity: Verified::new(entity), + entity_id: Verified::new(entity_id), + }, + )), + AttestationStatus::Unavailable => Err(Error::Vault(vault::Error::Sealed)), + } +} + +pub async fn delete_envelope( + conn: &mut impl AsyncConnection, + entity_id: impl Into, +) -> Result { + let entity_id = entity_id.into(); + + let affected = diesel::delete( + integrity_envelope::table + .filter(integrity_envelope::entity_kind.eq(E::KIND)) + .filter(integrity_envelope::entity_id.eq(&*entity_id)), + ) + .execute(conn) + .await + .map_err(db::DatabaseError::from)?; + + Ok(affected) +} + pub async fn is_signing_available(vault: &ActorRef) -> Result { let state = vault.ask(GetState).await.map_err(|_| Error::VaultSend)?; Ok(matches!(state, vault::VaultState::Unsealed)) } +fn payload_hash(payload: &impl Hashable) -> [u8; 32] { + let mut hasher = Sha256::new(); + payload.hash(&mut hasher); + hasher.finalize().into() +} + +fn build_mac_input( + entity_kind: &str, + entity_id: &[u8], + payload_version: i32, + payload_hash: &[u8; 32], +) -> Vec { + let mut out = Vec::with_capacity(8 + entity_kind.len() + entity_id.len() + 32); + push_len_prefixed(&mut out, entity_kind.as_bytes()); + push_len_prefixed(&mut out, entity_id); + out.extend_from_slice(&payload_version.to_be_bytes()); + out.extend_from_slice(payload_hash); + out +} + +fn push_len_prefixed(out: &mut Vec, bytes: &[u8]) { + out.extend_from_slice(&(bytes.len() as u32).to_be_bytes()); + out.extend_from_slice(bytes); +} + #[cfg(test)] mod tests { use diesel::{ExpressionMethods as _, QueryDsl}; use diesel_async::RunQueryDsl; use kameo::{actor::ActorRef, prelude::Spawn}; - use sha2::Digest; use crate::{ @@ -224,7 +337,7 @@ mod tests { use arbiter_crypto::safecell::{SafeCell, SafeCellHandle as _}; use super::hashing::Hashable; - use super::{Error, Integrable, sign_entity, verify_entity}; + use super::{Error, Integrable, check_entity_attestation, sign_entity}; #[derive(Clone)] struct DummyEntity { @@ -272,7 +385,8 @@ mod tests { sign_entity(&mut conn, &vault, &entity, ENTITY_ID) .await - .unwrap(); + .unwrap() + .drop_verification_provenance(); let count: i64 = schema::integrity_envelope::table .filter(schema::integrity_envelope::entity_kind.eq("dummy_entity")) @@ -283,9 +397,11 @@ mod tests { .unwrap(); assert_eq!(count, 1, "envelope row must be created exactly once"); - verify_entity(&mut conn, &vault, &entity, ENTITY_ID) + + let status = check_entity_attestation(&mut conn, &vault, &entity, ENTITY_ID) .await .unwrap(); + assert!(matches!(status, super::AttestationStatus::Attested)); } #[tokio::test] @@ -303,7 +419,8 @@ mod tests { sign_entity(&mut conn, &vault, &entity, ENTITY_ID) .await - .unwrap(); + .unwrap() + .drop_verification_provenance(); diesel::update(schema::integrity_envelope::table) .filter(schema::integrity_envelope::entity_kind.eq("dummy_entity")) @@ -313,35 +430,7 @@ mod tests { .await .unwrap(); - let err = verify_entity(&mut conn, &vault, &entity, ENTITY_ID) - .await - .unwrap_err(); - assert!(matches!(err, Error::MacMismatch { .. })); - } - - #[tokio::test] - async fn changed_payload_fails_verification() { - let db = db::create_test_pool().await; - let vault = bootstrapped_vault(&db).await; - let mut conn = db.get().await.unwrap(); - - const ENTITY_ID: &[u8] = b"entity-id-21"; - - let entity = DummyEntity { - payload_version: 1, - payload: b"payload-v1".to_vec(), - }; - - sign_entity(&mut conn, &vault, &entity, ENTITY_ID) - .await - .unwrap(); - - let tampered = DummyEntity { - payload: b"payload-v1-but-tampered".to_vec(), - ..entity - }; - - let err = verify_entity(&mut conn, &vault, &tampered, ENTITY_ID) + let err = check_entity_attestation(&mut conn, &vault, &entity, ENTITY_ID) .await .unwrap_err(); assert!(matches!(err, Error::MacMismatch { .. })); diff --git a/server/crates/arbiter-server/src/crypto/integrity/v1/verified.rs b/server/crates/arbiter-server/src/crypto/integrity/v1/verified.rs new file mode 100644 index 0000000..7b330eb --- /dev/null +++ b/server/crates/arbiter-server/src/crypto/integrity/v1/verified.rs @@ -0,0 +1,151 @@ +use std::ops::Deref; + +use super::Integrable; + +mod private { + pub trait Sealed {} +} + +/// Marker trait for type-level verification provenance. +/// +/// This trait is intentionally sealed so external code cannot invent arbitrary +/// provenance tags and bypass the intended type-level guarantees. +pub trait VerificationOrigin: private::Sealed { + type Origin: VerificationOrigin; +} + +/// Root provenance marker for values directly produced by integrity APIs. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)] +pub struct Root; + +impl private::Sealed for Root {} +impl VerificationOrigin for Root { + type Origin = Self; +} + +/// Nested provenance marker carrying the source integrable type and previous +/// provenance marker in the chain. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct Nested(core::marker::PhantomData<(From, P)>); + +impl private::Sealed for Nested {} +impl VerificationOrigin for Nested { + type Origin = P::Origin; +} + +#[derive(Debug, Clone, PartialEq, Eq)] +// #[derive(Copy)] // fixme!: soundness: Unimplemented Copy helps to avoid accidentally origin-unqualifying due to Deref impl. +#[repr(transparent)] +#[must_use = "Verified is a proof-bearing wrapper; use self.drop_verification_provenance() to explicitly discard integrity provenance when needed"] +pub struct Verified { + inner: T, + origin: core::marker::PhantomData, +} + +impl AsRef for Verified { + fn as_ref(&self) -> &T { + &self.inner + } +} + +impl Deref for Verified> { + type Target = Verified; + + fn deref(&self) -> &Self::Target { + // SAFETY: `Verified` is `#[repr(transparent)]` over `T`, so + // `&Verified>` and `&Verified` have identical layout. + unsafe { &*(self as *const Self as *const Verified) } + } +} +impl Deref for Verified { + type Target = T; + + fn deref(&self) -> &Self::Target { + AsRef::as_ref(self) + } +} + +impl Verified { + /// Unwraps the verified value, discarding the integrity provenance. + pub fn drop_verification_provenance(self) -> T { + self.inner + } + + /// Downgrades the origin provenance by recursively resolving the terminal + /// origin of the verification chain. + pub fn unqualify_origin(self) -> Verified { + Verified { + inner: self.inner, + origin: core::marker::PhantomData, + } + } + + /// Constructs a `Verified` by wrapping a `T`. + #[cfg(not(test))] + pub(super) const fn new(value: T) -> Self { + Self { + inner: value, + origin: core::marker::PhantomData, + } + } + #[cfg(test)] + pub(crate) const fn new(value: T) -> Self { + Self { + inner: value, + origin: core::marker::PhantomData, + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + use crate::crypto::integrity::v1::hashing::Hashable; + use hmac::digest::Digest; + use std::mem::{align_of, size_of}; + + #[derive(Debug, Clone, Copy, PartialEq, Eq)] + struct Marker; + + impl Hashable for Marker { + fn hash(&self, hasher: &mut H) { + hasher.update(b"marker"); + } + } + + impl Integrable for Marker { + const KIND: &'static str = "marker"; + } + + #[test] + fn verified_root_exposes_inner_value() { + let verified = Verified::<_, Root>::new("root-value"); + + assert_eq!(verified.as_ref(), &"root-value"); + assert_eq!(*verified, "root-value"); + assert_eq!(verified.drop_verification_provenance(), "root-value"); + assert_eq!(size_of::>(), size_of::<&str>()); + assert_eq!(align_of::>(), align_of::<&str>()); + } + + #[test] + fn nested_verified_derefs_back_to_root() { + let verified: Verified<_, Nested>> = Verified::new("nested-value"); + + let _: &Verified<&str, Root> = &verified; + let root_view: Verified<&str, Root> = verified.unqualify_origin(); + + assert_eq!(root_view.as_ref(), &"nested-value"); + } + + #[test] + fn nested_verified_can_be_unqualified_to_root() { + let verified: Verified<_, Nested> = Verified::new("nested-value"); + + let downgraded = verified.unqualify_origin(); + + assert_eq!(downgraded.as_ref(), &"nested-value"); + assert_eq!(downgraded.drop_verification_provenance(), "nested-value"); + } +} diff --git a/server/crates/arbiter-server/src/grpc/client/auth.rs b/server/crates/arbiter-server/src/grpc/client/auth.rs index 000a9db..728b5a5 100644 --- a/server/crates/arbiter-server/src/grpc/client/auth.rs +++ b/server/crates/arbiter-server/src/grpc/client/auth.rs @@ -22,8 +22,9 @@ use tonic::Status; use tracing::warn; use crate::{ + crypto::integrity::{Nested, Verified}, grpc::request_tracker::RequestTracker, - peers::client::{self, ClientConnection, auth}, + peers::client::{self, ClientConnection, ClientCredentials, auth}, }; pub struct AuthTransportAdapter<'a> { @@ -197,7 +198,7 @@ pub async fn start( conn: &mut ClientConnection, bi: &mut GrpcBi, request_tracker: &mut RequestTracker, -) -> Result { +) -> Result>, auth::Error> { let mut transport = AuthTransportAdapter::new(bi, request_tracker); client::auth::authenticate(conn, &mut transport).await } diff --git a/server/crates/arbiter-server/src/lib.rs b/server/crates/arbiter-server/src/lib.rs index 466e639..df198d8 100644 --- a/server/crates/arbiter-server/src/lib.rs +++ b/server/crates/arbiter-server/src/lib.rs @@ -1,4 +1,3 @@ -#![forbid(unsafe_code)] use crate::context::ServerContext; pub mod actors; diff --git a/server/crates/arbiter-server/src/peers/client/auth.rs b/server/crates/arbiter-server/src/peers/client/auth.rs index 2152ace..03aa8e3 100644 --- a/server/crates/arbiter-server/src/peers/client/auth.rs +++ b/server/crates/arbiter-server/src/peers/client/auth.rs @@ -18,7 +18,7 @@ use crate::{ flow_coordinator::{self, RequestClientApproval}, vault::Vault, }, - crypto::integrity::{self, AttestationStatus}, + crypto::integrity::{self, Nested, Verified}, db::{ self, models::{ProgramClientMetadata, SqliteTimestamp}, @@ -104,44 +104,6 @@ async fn get_current_nonce_and_id( }) } -async fn verify_integrity( - db: &db::DatabasePool, - vault: &ActorRef, - pubkey: &authn::PublicKey, -) -> Result<(), Error> { - let mut db_conn = db.get().await.map_err(|e| { - error!(error = ?e, "Database pool error"); - Error::DatabasePoolUnavailable - })?; - - let (id, nonce) = get_current_nonce_and_id(db, pubkey).await?.ok_or_else(|| { - error!("Client not found during integrity verification"); - Error::DatabaseOperationFailed - })?; - - let attestation = integrity::verify_entity( - &mut db_conn, - vault, - &ClientCredentials { - pubkey: pubkey.clone(), - nonce, - }, - id, - ) - .await - .map_err(|e| { - error!(?e, "Integrity verification failed"); - Error::IntegrityCheckFailed - })?; - - if attestation != AttestationStatus::Attested { - error!("Integrity attestation unavailable for client {id}"); - return Err(Error::IntegrityCheckFailed); - } - - Ok(()) -} - /// Atomically increments the nonce and re-signs the integrity envelope. /// Returns the new nonce, which is used as the challenge nonce. async fn create_nonce( @@ -214,7 +176,7 @@ async fn insert_client( vault: &ActorRef, pubkey: &authn::PublicKey, metadata: &ClientMetadata, -) -> Result { +) -> Result>, Error> { use crate::db::schema::{client_metadata, program_client}; let pubkey = pubkey.clone(); let metadata = metadata.clone(); @@ -251,7 +213,7 @@ async fn insert_client( .get_result::(conn) .await?; - integrity::sign_entity( + let verified_id = integrity::sign_entity( conn, &vault, &ClientCredentials { @@ -266,7 +228,7 @@ async fn insert_client( Error::DatabaseOperationFailed })?; - Ok(client_id) + Ok(verified_id) }) }) .await @@ -274,7 +236,7 @@ async fn insert_client( async fn sync_client_metadata( db: &db::DatabasePool, - client_id: i32, + client_id: &Verified>, metadata: &ClientMetadata, ) -> Result<(), Error> { use crate::db::schema::{client_metadata, client_metadata_history}; @@ -291,7 +253,7 @@ async fn sync_client_metadata( Box::pin(async move { let (current_metadata_id, current): (i32, ProgramClientMetadata) = program_client::table - .find(client_id) + .find(client_id.as_ref()) .inner_join(client_metadata::table) .select(( program_client::metadata_id, @@ -310,7 +272,7 @@ async fn sync_client_metadata( insert_into(client_metadata_history::table) .values(( client_metadata_history::metadata_id.eq(current_metadata_id), - client_metadata_history::client_id.eq(client_id), + client_metadata_history::client_id.eq(client_id.as_ref()), )) .execute(conn) .await?; @@ -325,7 +287,7 @@ async fn sync_client_metadata( .get_result::(conn) .await?; - update(program_client::table.find(client_id)) + update(program_client::table.find(client_id.as_ref())) .set(( program_client::metadata_id.eq(metadata_id), program_client::updated_at.eq(now), @@ -380,7 +342,10 @@ where Ok(()) } -pub async fn authenticate(props: &mut ClientConnection, transport: &mut T) -> Result +pub async fn authenticate( + props: &mut ClientConnection, + transport: &mut T, +) -> Result>, Error> where T: Bi> + Send + ?Sized, { @@ -389,9 +354,27 @@ where }; let client_id = match get_current_nonce_and_id(&props.db, &pubkey).await? { - Some((id, _)) => { - verify_integrity(&props.db, &props.actors.vault, &pubkey).await?; - id + Some((id, nonce)) => { + let mut db_conn = props.db.get().await.map_err(|e| { + error!(error = ?e, "Database pool error"); + Error::DatabasePoolUnavailable + })?; + + integrity::verify_entity( + &mut db_conn, + &props.actors.vault, + ClientCredentials { + pubkey: pubkey.clone(), + nonce, + }, + id, + ) + .await + .map_err(|e| { + error!(?e, "Integrity verification failed"); + Error::IntegrityCheckFailed + })? + .entity_id } None => { approve_new_client( @@ -406,7 +389,7 @@ where } }; - 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.vault, &pubkey).await?; challenge_client(transport, pubkey, challenge_nonce).await?; diff --git a/server/crates/arbiter-server/src/peers/client/session.rs b/server/crates/arbiter-server/src/peers/client/session.rs index d09d844..df5e419 100644 --- a/server/crates/arbiter-server/src/peers/client/session.rs +++ b/server/crates/arbiter-server/src/peers/client/session.rs @@ -10,19 +10,24 @@ use crate::{ flow_coordinator::RegisterClient, vault::VaultState, }, + crypto::integrity::{Nested, Verified}, db, evm::VetError, }; use super::ClientConnection; +use super::ClientCredentials; pub struct ClientSession { props: ClientConnection, - client_id: i32, + client_id: Verified>, } impl ClientSession { - pub(crate) fn new(props: ClientConnection, client_id: i32) -> Self { + pub(crate) fn new( + props: ClientConnection, + client_id: Verified>, + ) -> Self { Self { props, client_id } } } @@ -55,7 +60,7 @@ impl ClientSession { .actors .evm .ask(ClientSignTransaction { - client_id: self.client_id, + client_id: *self.client_id.as_ref(), wallet_address, transaction, }) @@ -93,11 +98,12 @@ impl Actor for ClientSession { } impl ClientSession { + #[cfg(test)] pub fn new_test(db: db::DatabasePool, actors: GlobalActors) -> Self { let props = ClientConnection::new(db, actors); Self { props, - client_id: 0, + client_id: Verified::new(0), } } } diff --git a/server/crates/arbiter-server/src/peers/user_agent/auth/state.rs b/server/crates/arbiter-server/src/peers/user_agent/auth/state.rs index bb48291..2c2fc34 100644 --- a/server/crates/arbiter-server/src/peers/user_agent/auth/state.rs +++ b/server/crates/arbiter-server/src/peers/user_agent/auth/state.rs @@ -7,7 +7,7 @@ use kameo::actor::ActorRef; use tracing::error; use super::Error; -use crate::peers::user_agent::auth::Outbound; +use crate::{crypto::integrity::{Nested, Verified}, peers::user_agent::auth::Outbound}; use crate::{ actors::{bootstrap::ConsumeToken, vault::Vault}, crypto::integrity, @@ -131,7 +131,7 @@ async fn resign_credentials( id: i32, pubkey: &authn::PublicKey, new_nonce: i32, -) -> Result<(), Error> { +) -> Result>, Error> { integrity::sign_entity( conn, vault, diff --git a/server/crates/arbiter-server/src/peers/user_agent/vault_gate/mod.rs b/server/crates/arbiter-server/src/peers/user_agent/vault_gate/mod.rs index 02c5978..ebd2945 100644 --- a/server/crates/arbiter-server/src/peers/user_agent/vault_gate/mod.rs +++ b/server/crates/arbiter-server/src/peers/user_agent/vault_gate/mod.rs @@ -267,7 +267,7 @@ impl Message for VaultGate { ) -> Self::Reply { let result = async { let mut conn = self.db.get().await.map_err(|_| Error::internal("DB unavailable"))?; - match integrity::verify_entity( + match integrity::check_entity_attestation( &mut conn, &self.actors.vault, &self.auth_creds,