feat(vault): add recovery passphrase handling for bootstrap and unseal processes
This commit is contained in:
@@ -17,6 +17,11 @@ message ContributePassphrase {
|
|||||||
bytes passphrase = 1;
|
bytes passphrase = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
message ContributeRecoveryPassphrase {
|
||||||
|
int32 recovery_operator_id = 1;
|
||||||
|
bytes passphrase = 2;
|
||||||
|
}
|
||||||
|
|
||||||
enum BootstrapResult {
|
enum BootstrapResult {
|
||||||
BOOTSTRAP_RESULT_UNSPECIFIED = 0;
|
BOOTSTRAP_RESULT_UNSPECIFIED = 0;
|
||||||
BOOTSTRAP_RESULT_SUCCESS = 1;
|
BOOTSTRAP_RESULT_SUCCESS = 1;
|
||||||
@@ -30,6 +35,7 @@ message Request {
|
|||||||
BootstrapEncryptedKey encrypted_key = 2;
|
BootstrapEncryptedKey encrypted_key = 2;
|
||||||
DeclareCommittee declare_committee = 3;
|
DeclareCommittee declare_committee = 3;
|
||||||
ContributePassphrase contribute_passphrase = 4;
|
ContributePassphrase contribute_passphrase = 4;
|
||||||
|
ContributeRecoveryPassphrase contribute_recovery_passphrase = 5;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -19,6 +19,11 @@ message ContributePassphrase {
|
|||||||
bytes passphrase = 1;
|
bytes passphrase = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
message ContributeRecoveryPassphrase {
|
||||||
|
int32 recovery_operator_id = 1;
|
||||||
|
bytes passphrase = 2;
|
||||||
|
}
|
||||||
|
|
||||||
enum UnsealResult {
|
enum UnsealResult {
|
||||||
UNSEAL_RESULT_UNSPECIFIED = 0;
|
UNSEAL_RESULT_UNSPECIFIED = 0;
|
||||||
UNSEAL_RESULT_SUCCESS = 1;
|
UNSEAL_RESULT_SUCCESS = 1;
|
||||||
@@ -32,6 +37,7 @@ message Request {
|
|||||||
UnsealStart start = 1;
|
UnsealStart start = 1;
|
||||||
UnsealEncryptedKey encrypted_key = 2;
|
UnsealEncryptedKey encrypted_key = 2;
|
||||||
ContributePassphrase contribute_passphrase = 3;
|
ContributePassphrase contribute_passphrase = 3;
|
||||||
|
ContributeRecoveryPassphrase contribute_recovery_passphrase = 4;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -246,9 +246,19 @@ create table if not exists proposal_result (
|
|||||||
) STRICT;
|
) STRICT;
|
||||||
|
|
||||||
-- ===============================
|
-- ===============================
|
||||||
-- Recovery Operators (§3.5/§3.6)
|
-- Recovery Operators (§3.4/§3.5/§3.6)
|
||||||
-- ===============================
|
-- ===============================
|
||||||
|
|
||||||
|
-- Encrypted Shamir shares for recovery operators (mirrors the `operator` table).
|
||||||
|
create table if not exists recovery_operator (
|
||||||
|
id integer not null primary key references recovery_operator_identity(id) on delete restrict,
|
||||||
|
share blob not null,
|
||||||
|
share_nonce blob not null,
|
||||||
|
share_salt blob not null,
|
||||||
|
created_at integer not null default(unixepoch('now')),
|
||||||
|
updated_at integer not null default(unixepoch('now'))
|
||||||
|
) STRICT;
|
||||||
|
|
||||||
create table if not exists recovery_operator_identity (
|
create table if not exists recovery_operator_identity (
|
||||||
id integer not null primary key,
|
id integer not null primary key,
|
||||||
public_key blob not null unique,
|
public_key blob not null unique,
|
||||||
|
|||||||
@@ -192,6 +192,17 @@ diesel::table! {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
diesel::table! {
|
||||||
|
recovery_operator (id) {
|
||||||
|
id -> Integer,
|
||||||
|
share -> Binary,
|
||||||
|
share_nonce -> Binary,
|
||||||
|
share_salt -> Binary,
|
||||||
|
created_at -> Integer,
|
||||||
|
updated_at -> Integer,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
diesel::table! {
|
diesel::table! {
|
||||||
recovery_operator_identity (id) {
|
recovery_operator_identity (id) {
|
||||||
id -> Integer,
|
id -> Integer,
|
||||||
@@ -290,6 +301,7 @@ diesel::joinable!(proposal -> operator_identity (initiator_id));
|
|||||||
diesel::joinable!(proposal_result -> proposal (proposal_id));
|
diesel::joinable!(proposal_result -> proposal (proposal_id));
|
||||||
diesel::joinable!(proposal_vote -> proposal (proposal_id));
|
diesel::joinable!(proposal_vote -> proposal (proposal_id));
|
||||||
diesel::joinable!(proposal_vote -> operator_identity (operator_id));
|
diesel::joinable!(proposal_vote -> operator_identity (operator_id));
|
||||||
|
diesel::joinable!(recovery_operator -> recovery_operator_identity (id));
|
||||||
diesel::joinable!(recovery_proposal_vote -> proposal (proposal_id));
|
diesel::joinable!(recovery_proposal_vote -> proposal (proposal_id));
|
||||||
diesel::joinable!(recovery_proposal_vote -> recovery_operator_identity (recovery_operator_id));
|
diesel::joinable!(recovery_proposal_vote -> recovery_operator_identity (recovery_operator_id));
|
||||||
diesel::joinable!(recovery_wakeup_request -> operator_identity (requested_by));
|
diesel::joinable!(recovery_wakeup_request -> operator_identity (requested_by));
|
||||||
@@ -297,6 +309,7 @@ diesel::joinable!(recovery_wakeup_request -> operator_identity (requested_by));
|
|||||||
diesel::allow_tables_to_appear_in_same_query!(
|
diesel::allow_tables_to_appear_in_same_query!(
|
||||||
aead_encrypted,
|
aead_encrypted,
|
||||||
proposal_result,
|
proposal_result,
|
||||||
|
recovery_operator,
|
||||||
recovery_operator_identity,
|
recovery_operator_identity,
|
||||||
recovery_wakeup_request,
|
recovery_wakeup_request,
|
||||||
recovery_proposal_vote,
|
recovery_proposal_vote,
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ use crate::{
|
|||||||
grpc::{Convert, TryConvert},
|
grpc::{Convert, TryConvert},
|
||||||
peers::operator::vault_gate::{
|
peers::operator::vault_gate::{
|
||||||
self as vault_gate, HandleBootstrapEncryptedKey, HandleContributeBootstrapPassphrase,
|
self as vault_gate, HandleBootstrapEncryptedKey, HandleContributeBootstrapPassphrase,
|
||||||
|
HandleContributeRecoveryBootstrapPassphrase, HandleContributeRecoveryUnsealPassphrase,
|
||||||
HandleContributeUnsealPassphrase, HandleDeclareCommittee, HandleHandshake,
|
HandleContributeUnsealPassphrase, HandleDeclareCommittee, HandleHandshake,
|
||||||
HandleUnsealEncryptedKey,
|
HandleUnsealEncryptedKey,
|
||||||
},
|
},
|
||||||
@@ -82,6 +83,14 @@ impl TryConvert for UnsealRequestPayload {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
Self::ContributeRecoveryPassphrase(crp) => Ok(
|
||||||
|
vault_gate::Inbound::HandleContributeRecoveryUnsealPassphrase(
|
||||||
|
HandleContributeRecoveryUnsealPassphrase {
|
||||||
|
recovery_operator_id: crp.recovery_operator_id,
|
||||||
|
passphrase: crp.passphrase,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -142,6 +151,14 @@ impl TryConvert for BootstrapRequestPayload {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
Self::ContributeRecoveryPassphrase(crp) => Ok(
|
||||||
|
vault_gate::Inbound::HandleContributeRecoveryBootstrapPassphrase(
|
||||||
|
HandleContributeRecoveryBootstrapPassphrase {
|
||||||
|
recovery_operator_id: crp.recovery_operator_id,
|
||||||
|
passphrase: crp.passphrase,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -131,6 +131,19 @@ impl TryConvert for vault_gate::Outbound {
|
|||||||
};
|
};
|
||||||
Ok(wrap_bootstrap_response(proto_result))
|
Ok(wrap_bootstrap_response(proto_result))
|
||||||
}
|
}
|
||||||
|
Self::HandleContributeRecoveryBootstrapPassphrase(result) => {
|
||||||
|
let proto_result = match result {
|
||||||
|
Ok(true) => ProtoBootstrapResult::Success,
|
||||||
|
Ok(false) => ProtoBootstrapResult::AwaitingContributions,
|
||||||
|
Err(err) => {
|
||||||
|
warn!(?err, "contribute recovery bootstrap passphrase failed");
|
||||||
|
return Err(Status::internal(
|
||||||
|
"Failed to contribute recovery bootstrap passphrase",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Ok(wrap_bootstrap_response(proto_result))
|
||||||
|
}
|
||||||
Self::HandleContributeUnsealPassphrase(result) => {
|
Self::HandleContributeUnsealPassphrase(result) => {
|
||||||
let proto_result = match result {
|
let proto_result = match result {
|
||||||
Ok(true) => ProtoUnsealResult::Success,
|
Ok(true) => ProtoUnsealResult::Success,
|
||||||
@@ -144,6 +157,21 @@ impl TryConvert for vault_gate::Outbound {
|
|||||||
proto_result.into(),
|
proto_result.into(),
|
||||||
)))
|
)))
|
||||||
}
|
}
|
||||||
|
Self::HandleContributeRecoveryUnsealPassphrase(result) => {
|
||||||
|
let proto_result = match result {
|
||||||
|
Ok(true) => ProtoUnsealResult::Success,
|
||||||
|
Ok(false) => ProtoUnsealResult::AwaitingContributions,
|
||||||
|
Err(err) => {
|
||||||
|
warn!(?err, "contribute recovery unseal passphrase failed");
|
||||||
|
return Err(Status::internal(
|
||||||
|
"Failed to contribute recovery unseal passphrase",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Ok(wrap_unseal_response(UnsealResponsePayload::Result(
|
||||||
|
proto_result.into(),
|
||||||
|
)))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,10 @@ use crate::{
|
|||||||
actors::{
|
actors::{
|
||||||
GlobalActors,
|
GlobalActors,
|
||||||
vault::{self, Bootstrap, GetState, TryUnseal, VaultState, events},
|
vault::{self, Bootstrap, GetState, TryUnseal, VaultState, events},
|
||||||
vault_coordinator::{ContributeBootstrap, ContributeUnseal, StartBootstrap},
|
vault_coordinator::{
|
||||||
|
ContributeBootstrap, ContributeRecoveryBootstrap, ContributeRecoveryUnseal,
|
||||||
|
ContributeUnseal, StartBootstrap,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
crypto::{KeyCell, integrity::{self}},
|
crypto::{KeyCell, integrity::{self}},
|
||||||
db::DatabasePool,
|
db::DatabasePool,
|
||||||
@@ -266,6 +269,23 @@ impl VaultGate {
|
|||||||
.map_err(|_| Error::internal("VaultCoordinator unavailable"))
|
.map_err(|_| Error::internal("VaultCoordinator unavailable"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[message]
|
||||||
|
pub async fn handle_contribute_recovery_bootstrap_passphrase(
|
||||||
|
&mut self,
|
||||||
|
recovery_operator_id: i32,
|
||||||
|
passphrase: Vec<u8>,
|
||||||
|
) -> Result<bool, Error> {
|
||||||
|
let passphrase_cell = SafeCell::new(passphrase);
|
||||||
|
self.actors
|
||||||
|
.vault_coordinator
|
||||||
|
.ask(ContributeRecoveryBootstrap {
|
||||||
|
recovery_operator_id,
|
||||||
|
passphrase: passphrase_cell,
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.map_err(|_| Error::internal("VaultCoordinator unavailable"))
|
||||||
|
}
|
||||||
|
|
||||||
#[message]
|
#[message]
|
||||||
pub async fn handle_contribute_unseal_passphrase(
|
pub async fn handle_contribute_unseal_passphrase(
|
||||||
&mut self,
|
&mut self,
|
||||||
@@ -281,6 +301,23 @@ impl VaultGate {
|
|||||||
.await
|
.await
|
||||||
.map_err(|_| Error::internal("VaultCoordinator unavailable"))
|
.map_err(|_| Error::internal("VaultCoordinator unavailable"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[message]
|
||||||
|
pub async fn handle_contribute_recovery_unseal_passphrase(
|
||||||
|
&mut self,
|
||||||
|
recovery_operator_id: i32,
|
||||||
|
passphrase: Vec<u8>,
|
||||||
|
) -> Result<bool, Error> {
|
||||||
|
let passphrase_cell = SafeCell::new(passphrase);
|
||||||
|
self.actors
|
||||||
|
.vault_coordinator
|
||||||
|
.ask(ContributeRecoveryUnseal {
|
||||||
|
recovery_operator_id,
|
||||||
|
passphrase: passphrase_cell,
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.map_err(|_| Error::internal("VaultCoordinator unavailable"))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Message<events::Bootstrapped> for VaultGate {
|
impl Message<events::Bootstrapped> for VaultGate {
|
||||||
|
|||||||
Reference in New Issue
Block a user