feat(server::user-agent): Unseal implemented

This commit is contained in:
hdbg
2026-02-16 21:07:09 +01:00
parent dd716da4cd
commit eb9cbc88e9
5 changed files with 96 additions and 51 deletions

View File

@@ -1,25 +0,0 @@
syntax = "proto3";
package arbiter.unseal;
import "google/protobuf/empty.proto";
message UnsealStart {
bytes client_pubkey = 1;
}
message UnsealStartResponse {
bytes server_pubkey = 1;
}
message UnsealEncryptedKey {
bytes nonce = 1;
bytes ciphertext = 2;
bytes associated_data = 3;
}
enum UnsealResult {
UNSEAL_RESULT_UNSPECIFIED = 0;
UNSEAL_RESULT_SUCCESS = 1;
UNSEAL_RESULT_INVALID_KEY = 2;
UNSEAL_RESULT_UNBOOTSTRAPPED = 3;
}

View File

@@ -3,19 +3,49 @@ syntax = "proto3";
package arbiter;
import "auth.proto";
import "unseal.proto";
import "google/protobuf/empty.proto";
message UnsealStart {
bytes client_pubkey = 1;
}
message UnsealStartResponse {
bytes server_pubkey = 1;
}
message UnsealEncryptedKey {
bytes nonce = 1;
bytes ciphertext = 2;
bytes associated_data = 3;
}
enum UnsealResult {
UNSEAL_RESULT_UNSPECIFIED = 0;
UNSEAL_RESULT_SUCCESS = 1;
UNSEAL_RESULT_INVALID_KEY = 2;
UNSEAL_RESULT_UNBOOTSTRAPPED = 3;
}
enum VaultState {
VAULT_STATE_UNSPECIFIED = 0;
VAULT_STATE_UNBOOTSTRAPPED = 1;
VAULT_STATE_SEALED = 2;
VAULT_STATE_UNSEALED = 3;
VAULT_STATE_ERROR = 4;
}
message UserAgentRequest {
oneof payload {
arbiter.auth.ClientMessage auth_message = 1;
arbiter.unseal.UnsealStart unseal_start = 2;
arbiter.unseal.UnsealEncryptedKey unseal_encrypted_key = 3;
UnsealStart unseal_start = 2;
UnsealEncryptedKey unseal_encrypted_key = 3;
google.protobuf.Empty query_vault_state = 4;
}
}
message UserAgentResponse {
oneof payload {
arbiter.auth.ServerMessage auth_message = 1;
arbiter.unseal.UnsealStartResponse unseal_start_response = 2;
arbiter.unseal.UnsealResult unseal_result = 3;
UnsealStartResponse unseal_start_response = 2;
UnsealResult unseal_result = 3;
VaultState vault_state = 4;
}
}

View File

@@ -6,9 +6,6 @@ pub mod proto {
pub mod auth {
tonic::include_proto!("arbiter.auth");
}
pub mod unseal {
tonic::include_proto!("arbiter.unseal");
}
}
pub mod transport;

View File

@@ -1,25 +1,18 @@
use std::{
ops::DerefMut,
sync::Mutex,
};
use std::{ops::DerefMut, sync::Mutex};
use arbiter_proto::proto::{
UserAgentResponse,
UnsealEncryptedKey, UnsealResult, UnsealStart, UnsealStartResponse, UserAgentResponse,
auth::{
self, AuthChallengeRequest, AuthOk, ServerMessage as AuthServerMessage,
server_message::Payload as ServerAuthPayload,
},
unseal::{UnsealEncryptedKey, UnsealResult, UnsealStart, UnsealStartResponse},
user_agent_response::Payload as UserAgentResponsePayload,
};
use chacha20poly1305::{
AeadInPlace, XChaCha20Poly1305, XNonce,
aead::KeyInit,
};
use chacha20poly1305::{AeadInPlace, XChaCha20Poly1305, XNonce, aead::KeyInit};
use diesel::{ExpressionMethods as _, OptionalExtension as _, QueryDsl, dsl::update};
use diesel_async::{AsyncConnection, RunQueryDsl};
use ed25519_dalek::VerifyingKey;
use kameo::{Actor, actor::ActorRef, messages};
use kameo::{Actor, actor::ActorRef, error::SendError, messages};
use memsafe::MemSafe;
use tokio::sync::mpsc::Sender;
use tonic::Status;
@@ -30,6 +23,7 @@ use crate::{
ServerContext,
actors::{
bootstrap::{Bootstrapper, ConsumeToken},
keyholder::{self, KeyHolder, TryUnseal},
user_agent::state::{
AuthRequestContext, ChallengeContext, DummyContext, UnsealContext, UserAgentEvents,
UserAgentStateMachine, UserAgentStates,
@@ -50,6 +44,7 @@ pub(crate) use transport::handle_user_agent;
pub struct UserAgentActor {
db: db::DatabasePool,
bootstapper: ActorRef<Bootstrapper>,
keyholder: ActorRef<KeyHolder>,
state: UserAgentStateMachine<DummyContext>,
// will be used in future
_tx: Sender<Result<UserAgentResponse, Status>>,
@@ -63,6 +58,7 @@ impl UserAgentActor {
Self {
db: context.db.clone(),
bootstapper: context.bootstrapper.clone(),
keyholder: context.keyholder.clone(),
state: UserAgentStateMachine::new(DummyContext),
_tx: tx,
}
@@ -72,11 +68,13 @@ impl UserAgentActor {
pub(crate) fn new_manual(
db: db::DatabasePool,
bootstapper: ActorRef<Bootstrapper>,
keyholder: ActorRef<KeyHolder>,
tx: Sender<Result<UserAgentResponse, Status>>,
) -> Self {
Self {
db,
bootstapper,
keyholder,
state: UserAgentStateMachine::new(DummyContext),
_tx: tx,
}
@@ -280,22 +278,57 @@ impl UserAgentActor {
let shared_secret = ephemeral_secret.diffie_hellman(&unseal_context.client_public_key);
let cipher = XChaCha20Poly1305::new(shared_secret.as_bytes().into());
let mut root_key_buffer = MemSafe::new(req.ciphertext.clone()).unwrap();
let mut write_handle = root_key_buffer.write().unwrap();
let write_handle = write_handle.deref_mut();
let mut seal_key_buffer = MemSafe::new(req.ciphertext.clone()).unwrap();
let decryption_result = cipher
.decrypt_in_place(nonce, &req.associated_data, write_handle);
let decryption_result = {
let mut write_handle = seal_key_buffer.write().unwrap();
let write_handle = write_handle.deref_mut();
cipher.decrypt_in_place(nonce, &req.associated_data, write_handle)
};
match decryption_result {
Ok(_) => todo!("Send key to the keyguarding"),
Ok(_) => {
match self
.keyholder
.ask(TryUnseal {
seal_key_raw: seal_key_buffer,
})
.await
{
Ok(_) => {
info!("Successfully unsealed key with client-provided key");
self.transition(UserAgentEvents::ReceivedValidKey)?;
Ok(unseal_response(UserAgentResponsePayload::UnsealResult(
UnsealResult::Success.into(),
)))
}
Err(SendError::HandlerError(keyholder::Error::InvalidKey)) => {
self.transition(UserAgentEvents::ReceivedInvalidKey)?;
Ok(unseal_response(UserAgentResponsePayload::UnsealResult(
UnsealResult::InvalidKey.into(),
)))
}
Err(SendError::HandlerError(err)) => {
error!(?err, "Keyholder failed to unseal key");
self.transition(UserAgentEvents::ReceivedInvalidKey)?;
Ok(unseal_response(UserAgentResponsePayload::UnsealResult(
UnsealResult::InvalidKey.into(),
)))
}
Err(err) => {
error!(?err, "Failed to send unseal request to keyholder");
self.transition(UserAgentEvents::ReceivedInvalidKey)?;
Err(Status::internal("Vault is not available"))
}
}
}
Err(err) => {
error!(?err, "Failed to decrypt unseal key");
self.transition(UserAgentEvents::ReceivedInvalidKey)?;
return Ok(unseal_response(UserAgentResponsePayload::UnsealResult(
UnsealResult::InvalidKey.into(),
)));
},
}
}
}

View File

@@ -11,6 +11,7 @@ use kameo::actor::Spawn;
use crate::{
actors::{
bootstrap::Bootstrapper,
keyholder::{self, KeyHolder},
user_agent::{HandleAuthChallengeRequest, HandleAuthChallengeSolution},
},
db::{self, schema},
@@ -24,12 +25,15 @@ pub async fn test_bootstrap_token_auth() {
let db = db::create_test_pool().await;
// explicitly not installing any user_agent pubkeys
let bootstrapper = Bootstrapper::new(&db).await.unwrap(); // this will create bootstrap token
let keyholder = KeyHolder::new(db.clone()).await.unwrap();
let token = bootstrapper.get_token().unwrap();
let bootstrapper_ref = Bootstrapper::spawn(bootstrapper);
let keyholder_ref = KeyHolder::spawn(keyholder);
let user_agent = UserAgentActor::new_manual(
db.clone(),
bootstrapper_ref,
keyholder_ref,
tokio::sync::mpsc::channel(1).0, // dummy channel, we won't actually send responses in this test
);
let user_agent_ref = UserAgentActor::spawn(user_agent);
@@ -78,11 +82,15 @@ pub async fn test_bootstrap_invalid_token_auth() {
let db = db::create_test_pool().await;
// explicitly not installing any user_agent pubkeys
let bootstrapper = Bootstrapper::new(&db).await.unwrap(); // this will create bootstrap token
let keyholder = KeyHolder::new(db.clone()).await.unwrap();
let bootstrapper_ref = Bootstrapper::spawn(bootstrapper);
let keyholder_ref = KeyHolder::spawn(keyholder);
let user_agent = UserAgentActor::new_manual(
db.clone(),
bootstrapper_ref,
keyholder_ref,
tokio::sync::mpsc::channel(1).0, // dummy channel, we won't actually send responses in this test
);
let user_agent_ref = UserAgentActor::spawn(user_agent);
@@ -126,9 +134,11 @@ pub async fn test_challenge_auth() {
let db = db::create_test_pool().await;
let bootstrapper_ref = Bootstrapper::spawn(Bootstrapper::new(&db).await.unwrap());
let keyholder_ref = KeyHolder::spawn(KeyHolder::new(db.clone()).await.unwrap());
let user_agent = UserAgentActor::new_manual(
db.clone(),
bootstrapper_ref,
keyholder_ref,
tokio::sync::mpsc::channel(1).0, // dummy channel, we won't actually send responses in this test
);
let user_agent_ref = UserAgentActor::spawn(user_agent);