Compare commits
7 Commits
critical-f
...
e5916faacf
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e5916faacf | ||
|
|
06279e1368 | ||
|
|
297fcfefe1 | ||
|
|
59d10334c5 | ||
|
|
2e989a977b | ||
|
|
e2570ada49 | ||
|
|
f77ac2ea12 |
@@ -6,6 +6,20 @@ This document covers concrete technology choices and dependencies. For the archi
|
|||||||
|
|
||||||
## Client Connection Flow
|
## Client Connection Flow
|
||||||
|
|
||||||
|
### Authentication Result Semantics
|
||||||
|
|
||||||
|
Authentication no longer uses an implicit success-only response shape. Both `client` and `user-agent` return explicit auth status enums over the wire.
|
||||||
|
|
||||||
|
- **Client:** `AuthResult` may return `SUCCESS`, `INVALID_KEY`, `INVALID_SIGNATURE`, `APPROVAL_DENIED`, `NO_USER_AGENTS_ONLINE`, or `INTERNAL`
|
||||||
|
- **User-agent:** `AuthResult` may return `SUCCESS`, `INVALID_KEY`, `INVALID_SIGNATURE`, `BOOTSTRAP_REQUIRED`, `TOKEN_INVALID`, or `INTERNAL`
|
||||||
|
|
||||||
|
This makes transport-level failures and actor/domain-level auth failures distinct:
|
||||||
|
|
||||||
|
- **Transport/protocol failures** are surfaced as stream/status errors
|
||||||
|
- **Authentication failures** are surfaced as successful protocol responses carrying an explicit auth status
|
||||||
|
|
||||||
|
Clients are expected to handle these status codes directly and present the concrete failure reason to the user.
|
||||||
|
|
||||||
### New Client Approval
|
### New Client Approval
|
||||||
|
|
||||||
When a client whose public key is not yet in the database connects, all connected user agents are asked to approve the connection. The first agent to respond determines the outcome; remaining requests are cancelled via a watch channel.
|
When a client whose public key is not yet in the database connects, all connected user agents are asked to approve the connection. The first agent to respond determines the outcome; remaining requests are cancelled via a watch channel.
|
||||||
@@ -68,9 +82,21 @@ The `program_client.nonce` column stores the **next usable nonce** — i.e. it i
|
|||||||
## Communication
|
## Communication
|
||||||
|
|
||||||
- **Protocol:** gRPC with Protocol Buffers
|
- **Protocol:** gRPC with Protocol Buffers
|
||||||
|
- **Request/response matching:** multiplexed over a single bidirectional stream using per-connection request IDs
|
||||||
- **Server identity distribution:** `ServerInfo` protobuf struct containing the TLS public key fingerprint
|
- **Server identity distribution:** `ServerInfo` protobuf struct containing the TLS public key fingerprint
|
||||||
- **Future consideration:** grpc-web lacks bidirectional stream support, so a browser-based wallet may require protojson over WebSocket
|
- **Future consideration:** grpc-web lacks bidirectional stream support, so a browser-based wallet may require protojson over WebSocket
|
||||||
|
|
||||||
|
### Request Multiplexing
|
||||||
|
|
||||||
|
Both `client` and `user-agent` connections support multiple in-flight requests over one gRPC bidi stream.
|
||||||
|
|
||||||
|
- Every request carries a monotonically increasing request ID
|
||||||
|
- Every normal response echoes the request ID it corresponds to
|
||||||
|
- Out-of-band server messages omit the response ID entirely
|
||||||
|
- The server rejects already-seen request IDs at the transport adapter boundary before business logic sees the message
|
||||||
|
|
||||||
|
This keeps request correlation entirely in transport/client connection code while leaving actor and domain handlers unaware of request IDs.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## EVM Policy Engine
|
## EVM Policy Engine
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ syntax = "proto3";
|
|||||||
package arbiter.client;
|
package arbiter.client;
|
||||||
|
|
||||||
import "evm.proto";
|
import "evm.proto";
|
||||||
|
import "google/protobuf/empty.proto";
|
||||||
|
|
||||||
message AuthChallengeRequest {
|
message AuthChallengeRequest {
|
||||||
bytes pubkey = 1;
|
bytes pubkey = 1;
|
||||||
@@ -17,30 +18,40 @@ message AuthChallengeSolution {
|
|||||||
bytes signature = 1;
|
bytes signature = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
message AuthOk {}
|
enum AuthResult {
|
||||||
|
AUTH_RESULT_UNSPECIFIED = 0;
|
||||||
|
AUTH_RESULT_SUCCESS = 1;
|
||||||
|
AUTH_RESULT_INVALID_KEY = 2;
|
||||||
|
AUTH_RESULT_INVALID_SIGNATURE = 3;
|
||||||
|
AUTH_RESULT_APPROVAL_DENIED = 4;
|
||||||
|
AUTH_RESULT_NO_USER_AGENTS_ONLINE = 5;
|
||||||
|
AUTH_RESULT_INTERNAL = 6;
|
||||||
|
}
|
||||||
|
|
||||||
|
enum VaultState {
|
||||||
|
VAULT_STATE_UNSPECIFIED = 0;
|
||||||
|
VAULT_STATE_UNBOOTSTRAPPED = 1;
|
||||||
|
VAULT_STATE_SEALED = 2;
|
||||||
|
VAULT_STATE_UNSEALED = 3;
|
||||||
|
VAULT_STATE_ERROR = 4;
|
||||||
|
}
|
||||||
|
|
||||||
message ClientRequest {
|
message ClientRequest {
|
||||||
|
int32 request_id = 4;
|
||||||
oneof payload {
|
oneof payload {
|
||||||
AuthChallengeRequest auth_challenge_request = 1;
|
AuthChallengeRequest auth_challenge_request = 1;
|
||||||
AuthChallengeSolution auth_challenge_solution = 2;
|
AuthChallengeSolution auth_challenge_solution = 2;
|
||||||
|
google.protobuf.Empty query_vault_state = 3;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
message ClientConnectError {
|
|
||||||
enum Code {
|
|
||||||
UNKNOWN = 0;
|
|
||||||
APPROVAL_DENIED = 1;
|
|
||||||
NO_USER_AGENTS_ONLINE = 2;
|
|
||||||
}
|
|
||||||
Code code = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
message ClientResponse {
|
message ClientResponse {
|
||||||
|
optional int32 request_id = 7;
|
||||||
oneof payload {
|
oneof payload {
|
||||||
AuthChallenge auth_challenge = 1;
|
AuthChallenge auth_challenge = 1;
|
||||||
AuthOk auth_ok = 2;
|
AuthResult auth_result = 2;
|
||||||
ClientConnectError client_connect_error = 5;
|
|
||||||
arbiter.evm.EvmSignTransactionResponse evm_sign_transaction = 3;
|
arbiter.evm.EvmSignTransactionResponse evm_sign_transaction = 3;
|
||||||
arbiter.evm.EvmAnalyzeTransactionResponse evm_analyze_transaction = 4;
|
arbiter.evm.EvmAnalyzeTransactionResponse evm_analyze_transaction = 4;
|
||||||
|
VaultState vault_state = 6;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ syntax = "proto3";
|
|||||||
|
|
||||||
package arbiter.user_agent;
|
package arbiter.user_agent;
|
||||||
|
|
||||||
import "google/protobuf/empty.proto";
|
|
||||||
import "evm.proto";
|
import "evm.proto";
|
||||||
|
import "google/protobuf/empty.proto";
|
||||||
|
|
||||||
enum KeyType {
|
enum KeyType {
|
||||||
KEY_TYPE_UNSPECIFIED = 0;
|
KEY_TYPE_UNSPECIFIED = 0;
|
||||||
@@ -19,15 +19,23 @@ message AuthChallengeRequest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
message AuthChallenge {
|
message AuthChallenge {
|
||||||
bytes pubkey = 1;
|
|
||||||
int32 nonce = 2;
|
int32 nonce = 2;
|
||||||
|
reserved 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
message AuthChallengeSolution {
|
message AuthChallengeSolution {
|
||||||
bytes signature = 1;
|
bytes signature = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
message AuthOk {}
|
enum AuthResult {
|
||||||
|
AUTH_RESULT_UNSPECIFIED = 0;
|
||||||
|
AUTH_RESULT_SUCCESS = 1;
|
||||||
|
AUTH_RESULT_INVALID_KEY = 2;
|
||||||
|
AUTH_RESULT_INVALID_SIGNATURE = 3;
|
||||||
|
AUTH_RESULT_BOOTSTRAP_REQUIRED = 4;
|
||||||
|
AUTH_RESULT_TOKEN_INVALID = 5;
|
||||||
|
AUTH_RESULT_INTERNAL = 6;
|
||||||
|
}
|
||||||
|
|
||||||
message UnsealStart {
|
message UnsealStart {
|
||||||
bytes client_pubkey = 1;
|
bytes client_pubkey = 1;
|
||||||
@@ -81,6 +89,7 @@ message ClientConnectionResponse {
|
|||||||
message ClientConnectionCancel {}
|
message ClientConnectionCancel {}
|
||||||
|
|
||||||
message UserAgentRequest {
|
message UserAgentRequest {
|
||||||
|
int32 id = 14;
|
||||||
oneof payload {
|
oneof payload {
|
||||||
AuthChallengeRequest auth_challenge_request = 1;
|
AuthChallengeRequest auth_challenge_request = 1;
|
||||||
AuthChallengeSolution auth_challenge_solution = 2;
|
AuthChallengeSolution auth_challenge_solution = 2;
|
||||||
@@ -97,9 +106,10 @@ message UserAgentRequest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
message UserAgentResponse {
|
message UserAgentResponse {
|
||||||
|
optional int32 id = 14;
|
||||||
oneof payload {
|
oneof payload {
|
||||||
AuthChallenge auth_challenge = 1;
|
AuthChallenge auth_challenge = 1;
|
||||||
AuthOk auth_ok = 2;
|
AuthResult auth_result = 2;
|
||||||
UnsealStartResponse unseal_start_response = 3;
|
UnsealStartResponse unseal_start_response = 3;
|
||||||
UnsealResult unseal_result = 4;
|
UnsealResult unseal_result = 4;
|
||||||
VaultState vault_state = 5;
|
VaultState vault_state = 5;
|
||||||
|
|||||||
1
server/Cargo.lock
generated
1
server/Cargo.lock
generated
@@ -697,6 +697,7 @@ dependencies = [
|
|||||||
"rustls-pki-types",
|
"rustls-pki-types",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
"tokio",
|
"tokio",
|
||||||
|
"tokio-stream",
|
||||||
"tonic",
|
"tonic",
|
||||||
"tonic-prost",
|
"tonic-prost",
|
||||||
"tonic-prost-build",
|
"tonic-prost-build",
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ base64 = "0.22.1"
|
|||||||
prost-types.workspace = true
|
prost-types.workspace = true
|
||||||
tracing.workspace = true
|
tracing.workspace = true
|
||||||
async-trait.workspace = true
|
async-trait.workspace = true
|
||||||
|
tokio-stream.workspace = true
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
tonic-prost-build = "0.14.3"
|
tonic-prost-build = "0.14.3"
|
||||||
|
|||||||
@@ -1,29 +1,49 @@
|
|||||||
//! Transport-facing abstractions shared by protocol/session code.
|
//! Transport-facing abstractions shared by protocol/session code.
|
||||||
//!
|
//!
|
||||||
//! This module defines a small duplex interface, [`Bi`], that actors and other
|
//! This module defines a small set of transport traits that actors and other
|
||||||
//! protocol code can depend on without knowing anything about the concrete
|
//! protocol code can depend on without knowing anything about the concrete
|
||||||
//! transport underneath.
|
//! transport underneath.
|
||||||
//!
|
//!
|
||||||
//! [`Bi`] is intentionally minimal and transport-agnostic:
|
//! The abstraction is split into:
|
||||||
//! - [`Bi::recv`] yields inbound messages
|
//! - [`Sender`] for outbound delivery
|
||||||
//! - [`Bi::send`] accepts outbound messages
|
//! - [`Receiver`] for inbound delivery
|
||||||
|
//! - [`Bi`] as the combined duplex form (`Sender + Receiver`)
|
||||||
|
//!
|
||||||
|
//! This split lets code depend only on the half it actually needs. For
|
||||||
|
//! example, some actor/session code only sends out-of-band messages, while
|
||||||
|
//! auth/state-machine code may need full duplex access.
|
||||||
|
//!
|
||||||
|
//! [`Bi`] remains intentionally minimal and transport-agnostic:
|
||||||
|
//! - [`Receiver::recv`] yields inbound messages
|
||||||
|
//! - [`Sender::send`] accepts outbound messages
|
||||||
//!
|
//!
|
||||||
//! Transport-specific adapters, including protobuf or gRPC bridges, live in the
|
//! Transport-specific adapters, including protobuf or gRPC bridges, live in the
|
||||||
//! crates that own those boundaries rather than in `arbiter-proto`.
|
//! crates that own those boundaries rather than in `arbiter-proto`.
|
||||||
//!
|
//!
|
||||||
|
//! [`Bi`] deliberately does not model request/response correlation. Some
|
||||||
|
//! transports may carry multiplexed request/response traffic, some may emit
|
||||||
|
//! out-of-band messages, and some may be one-message-at-a-time state machines.
|
||||||
|
//! Correlation concerns such as request IDs, pending response maps, and
|
||||||
|
//! out-of-band routing belong in the adapter or connection layer built on top
|
||||||
|
//! of [`Bi`], not in this abstraction itself.
|
||||||
|
//!
|
||||||
//! # Generic Ordering Rule
|
//! # Generic Ordering Rule
|
||||||
//!
|
//!
|
||||||
//! This module consistently uses `Inbound` first and `Outbound` second in
|
//! This module consistently uses `Inbound` first and `Outbound` second in
|
||||||
//! generic parameter lists.
|
//! generic parameter lists.
|
||||||
//!
|
//!
|
||||||
//! For [`Bi`], that means `Bi<Inbound, Outbound>`:
|
//! For [`Receiver`], [`Sender`], and [`Bi`], this means:
|
||||||
|
//! - `Receiver<Inbound>`
|
||||||
|
//! - `Sender<Outbound>`
|
||||||
|
//! - `Bi<Inbound, Outbound>`
|
||||||
|
//!
|
||||||
|
//! Concretely, for [`Bi`]:
|
||||||
//! - `recv() -> Option<Inbound>`
|
//! - `recv() -> Option<Inbound>`
|
||||||
//! - `send(Outbound)`
|
//! - `send(Outbound)`
|
||||||
//!
|
//!
|
||||||
//! [`expect_message`] is a small helper for request/response style flows: it
|
//! [`expect_message`] is a small helper for linear protocol steps: it reads one
|
||||||
//! reads one inbound message from a transport and extracts a typed value from
|
//! inbound message from a transport and extracts a typed value from it, failing
|
||||||
//! it, failing if the channel closes or the message shape is not what the
|
//! if the channel closes or the message shape is not what the caller expected.
|
||||||
//! caller expected.
|
|
||||||
//!
|
//!
|
||||||
//! [`DummyTransport`] is a no-op implementation useful for tests and local
|
//! [`DummyTransport`] is a no-op implementation useful for tests and local
|
||||||
//! actor execution where no real stream exists.
|
//! actor execution where no real stream exists.
|
||||||
@@ -63,16 +83,35 @@ where
|
|||||||
extractor(msg).ok_or(Error::UnexpectedMessage)
|
extractor(msg).ok_or(Error::UnexpectedMessage)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
pub trait Sender<Outbound>: Send + Sync {
|
||||||
|
async fn send(&mut self, item: Outbound) -> Result<(), Error>;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
pub trait Receiver<Inbound>: Send + Sync {
|
||||||
|
async fn recv(&mut self) -> Option<Inbound>;
|
||||||
|
}
|
||||||
|
|
||||||
/// Minimal bidirectional transport abstraction used by protocol code.
|
/// Minimal bidirectional transport abstraction used by protocol code.
|
||||||
///
|
///
|
||||||
/// `Bi<Inbound, Outbound>` models a duplex channel with:
|
/// `Bi<Inbound, Outbound>` is the combined duplex form of [`Sender`] and
|
||||||
|
/// [`Receiver`].
|
||||||
|
///
|
||||||
|
/// It models a channel with:
|
||||||
/// - inbound items of type `Inbound` read via [`Bi::recv`]
|
/// - inbound items of type `Inbound` read via [`Bi::recv`]
|
||||||
/// - outbound items of type `Outbound` written via [`Bi::send`]
|
/// - outbound items of type `Outbound` written via [`Bi::send`]
|
||||||
#[async_trait]
|
///
|
||||||
pub trait Bi<Inbound, Outbound>: Send + Sync + 'static {
|
/// It does not imply request/response sequencing, one-at-a-time exchange, or
|
||||||
async fn send(&mut self, item: Outbound) -> Result<(), Error>;
|
/// any built-in correlation mechanism between inbound and outbound items.
|
||||||
|
pub trait Bi<Inbound, Outbound>: Sender<Outbound> + Receiver<Inbound> + Send + Sync {}
|
||||||
|
|
||||||
async fn recv(&mut self) -> Option<Inbound>;
|
pub trait SplittableBi<Inbound, Outbound>: Bi<Inbound, Outbound> {
|
||||||
|
type Sender: Sender<Outbound>;
|
||||||
|
type Receiver: Receiver<Inbound>;
|
||||||
|
|
||||||
|
fn split(self) -> (Self::Sender, Self::Receiver);
|
||||||
|
fn from_parts(sender: Self::Sender, receiver: Self::Receiver) -> Self;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// No-op [`Bi`] transport for tests and manual actor usage.
|
/// No-op [`Bi`] transport for tests and manual actor usage.
|
||||||
@@ -83,22 +122,16 @@ pub struct DummyTransport<Inbound, Outbound> {
|
|||||||
_marker: PhantomData<(Inbound, Outbound)>,
|
_marker: PhantomData<(Inbound, Outbound)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<Inbound, Outbound> DummyTransport<Inbound, Outbound> {
|
impl<Inbound, Outbound> Default for DummyTransport<Inbound, Outbound> {
|
||||||
pub fn new() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
_marker: PhantomData,
|
_marker: PhantomData,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<Inbound, Outbound> Default for DummyTransport<Inbound, Outbound> {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self::new()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl<Inbound, Outbound> Bi<Inbound, Outbound> for DummyTransport<Inbound, Outbound>
|
impl<Inbound, Outbound> Sender<Outbound> for DummyTransport<Inbound, Outbound>
|
||||||
where
|
where
|
||||||
Inbound: Send + Sync + 'static,
|
Inbound: Send + Sync + 'static,
|
||||||
Outbound: Send + Sync + 'static,
|
Outbound: Send + Sync + 'static,
|
||||||
@@ -106,9 +139,25 @@ where
|
|||||||
async fn send(&mut self, _item: Outbound) -> Result<(), Error> {
|
async fn send(&mut self, _item: Outbound) -> Result<(), Error> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl<Inbound, Outbound> Receiver<Inbound> for DummyTransport<Inbound, Outbound>
|
||||||
|
where
|
||||||
|
Inbound: Send + Sync + 'static,
|
||||||
|
Outbound: Send + Sync + 'static,
|
||||||
|
{
|
||||||
async fn recv(&mut self) -> Option<Inbound> {
|
async fn recv(&mut self) -> Option<Inbound> {
|
||||||
std::future::pending::<()>().await;
|
std::future::pending::<()>().await;
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<Inbound, Outbound> Bi<Inbound, Outbound> for DummyTransport<Inbound, Outbound>
|
||||||
|
where
|
||||||
|
Inbound: Send + Sync + 'static,
|
||||||
|
Outbound: Send + Sync + 'static,
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
pub mod grpc;
|
||||||
|
|||||||
106
server/crates/arbiter-proto/src/transport/grpc.rs
Normal file
106
server/crates/arbiter-proto/src/transport/grpc.rs
Normal file
@@ -0,0 +1,106 @@
|
|||||||
|
use async_trait::async_trait;
|
||||||
|
use futures::StreamExt;
|
||||||
|
use tokio::sync::mpsc;
|
||||||
|
use tokio_stream::wrappers::ReceiverStream;
|
||||||
|
|
||||||
|
use super::{Bi, Receiver, Sender};
|
||||||
|
|
||||||
|
pub struct GrpcSender<Outbound> {
|
||||||
|
tx: mpsc::Sender<Result<Outbound, tonic::Status>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl<Outbound> Sender<Result<Outbound, tonic::Status>> for GrpcSender<Outbound>
|
||||||
|
where
|
||||||
|
Outbound: Send + Sync + 'static,
|
||||||
|
{
|
||||||
|
async fn send(&mut self, item: Result<Outbound, tonic::Status>) -> Result<(), super::Error> {
|
||||||
|
self.tx
|
||||||
|
.send(item)
|
||||||
|
.await
|
||||||
|
.map_err(|_| super::Error::ChannelClosed)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct GrpcReceiver<Inbound> {
|
||||||
|
rx: tonic::Streaming<Inbound>,
|
||||||
|
}
|
||||||
|
#[async_trait]
|
||||||
|
impl<Inbound> Receiver<Result<Inbound, tonic::Status>> for GrpcReceiver<Inbound>
|
||||||
|
where
|
||||||
|
Inbound: Send + Sync + 'static,
|
||||||
|
{
|
||||||
|
async fn recv(&mut self) -> Option<Result<Inbound, tonic::Status>> {
|
||||||
|
self.rx.next().await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct GrpcBi<Inbound, Outbound> {
|
||||||
|
sender: GrpcSender<Outbound>,
|
||||||
|
receiver: GrpcReceiver<Inbound>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Inbound, Outbound> GrpcBi<Inbound, Outbound>
|
||||||
|
where
|
||||||
|
Inbound: Send + Sync + 'static,
|
||||||
|
Outbound: Send + Sync + 'static,
|
||||||
|
{
|
||||||
|
pub fn from_bi_stream(
|
||||||
|
receiver: tonic::Streaming<Inbound>,
|
||||||
|
) -> (Self, ReceiverStream<Result<Outbound, tonic::Status>>) {
|
||||||
|
let (tx, rx) = mpsc::channel(10);
|
||||||
|
let sender = GrpcSender { tx };
|
||||||
|
let receiver = GrpcReceiver { rx: receiver };
|
||||||
|
let bi = GrpcBi { sender, receiver };
|
||||||
|
(bi, ReceiverStream::new(rx))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl<Inbound, Outbound> Sender<Result<Outbound, tonic::Status>> for GrpcBi<Inbound, Outbound>
|
||||||
|
where
|
||||||
|
Inbound: Send + Sync + 'static,
|
||||||
|
Outbound: Send + Sync + 'static,
|
||||||
|
{
|
||||||
|
async fn send(&mut self, item: Result<Outbound, tonic::Status>) -> Result<(), super::Error> {
|
||||||
|
self.sender.send(item).await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl<Inbound, Outbound> Receiver<Result<Inbound, tonic::Status>> for GrpcBi<Inbound, Outbound>
|
||||||
|
where
|
||||||
|
Inbound: Send + Sync + 'static,
|
||||||
|
Outbound: Send + Sync + 'static,
|
||||||
|
{
|
||||||
|
async fn recv(&mut self) -> Option<Result<Inbound, tonic::Status>> {
|
||||||
|
self.receiver.recv().await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Inbound, Outbound> Bi<Result<Inbound, tonic::Status>, Result<Outbound, tonic::Status>>
|
||||||
|
for GrpcBi<Inbound, Outbound>
|
||||||
|
where
|
||||||
|
Inbound: Send + Sync + 'static,
|
||||||
|
Outbound: Send + Sync + 'static,
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Inbound, Outbound>
|
||||||
|
super::SplittableBi<Result<Inbound, tonic::Status>, Result<Outbound, tonic::Status>>
|
||||||
|
for GrpcBi<Inbound, Outbound>
|
||||||
|
where
|
||||||
|
Inbound: Send + Sync + 'static,
|
||||||
|
Outbound: Send + Sync + 'static,
|
||||||
|
{
|
||||||
|
type Sender = GrpcSender<Outbound>;
|
||||||
|
type Receiver = GrpcReceiver<Inbound>;
|
||||||
|
|
||||||
|
fn split(self) -> (Self::Sender, Self::Receiver) {
|
||||||
|
(self.sender, self.receiver)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn from_parts(sender: Self::Sender, receiver: Self::Receiver) -> Self {
|
||||||
|
GrpcBi { sender, receiver }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,30 +1,25 @@
|
|||||||
use arbiter_proto::{format_challenge, transport::expect_message};
|
use arbiter_proto::{
|
||||||
|
format_challenge,
|
||||||
|
transport::{Bi, expect_message},
|
||||||
|
};
|
||||||
use diesel::{
|
use diesel::{
|
||||||
ExpressionMethods as _, OptionalExtension as _, QueryDsl as _, dsl::insert_into, update,
|
ExpressionMethods as _, OptionalExtension as _, QueryDsl as _, dsl::insert_into, update,
|
||||||
};
|
};
|
||||||
use diesel_async::RunQueryDsl as _;
|
use diesel_async::RunQueryDsl as _;
|
||||||
use ed25519_dalek::VerifyingKey;
|
use ed25519_dalek::{Signature, VerifyingKey};
|
||||||
use kameo::error::SendError;
|
use kameo::error::SendError;
|
||||||
use tracing::error;
|
use tracing::error;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
actors::{
|
actors::{
|
||||||
client::{ClientConnection, ConnectErrorCode, Request, Response},
|
client::ClientConnection,
|
||||||
router::{self, RequestClientApproval},
|
router::{self, RequestClientApproval},
|
||||||
},
|
},
|
||||||
db::{self, schema::program_client},
|
db::{self, schema::program_client},
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::session::ClientSession;
|
|
||||||
|
|
||||||
#[derive(thiserror::Error, Debug, Clone, PartialEq, Eq)]
|
#[derive(thiserror::Error, Debug, Clone, PartialEq, Eq)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
#[error("Unexpected message payload")]
|
|
||||||
UnexpectedMessagePayload,
|
|
||||||
#[error("Invalid client public key length")]
|
|
||||||
InvalidClientPubkeyLength,
|
|
||||||
#[error("Invalid client public key encoding")]
|
|
||||||
InvalidAuthPubkeyEncoding,
|
|
||||||
#[error("Database pool unavailable")]
|
#[error("Database pool unavailable")]
|
||||||
DatabasePoolUnavailable,
|
DatabasePoolUnavailable,
|
||||||
#[error("Database operation failed")]
|
#[error("Database operation failed")]
|
||||||
@@ -33,8 +28,6 @@ pub enum Error {
|
|||||||
InvalidChallengeSolution,
|
InvalidChallengeSolution,
|
||||||
#[error("Client approval request failed")]
|
#[error("Client approval request failed")]
|
||||||
ApproveError(#[from] ApproveError),
|
ApproveError(#[from] ApproveError),
|
||||||
#[error("Internal error")]
|
|
||||||
InternalError,
|
|
||||||
#[error("Transport error")]
|
#[error("Transport error")]
|
||||||
Transport,
|
Transport,
|
||||||
}
|
}
|
||||||
@@ -49,6 +42,18 @@ pub enum ApproveError {
|
|||||||
Upstream(router::ApprovalError),
|
Upstream(router::ApprovalError),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum Inbound {
|
||||||
|
AuthChallengeRequest { pubkey: VerifyingKey },
|
||||||
|
AuthChallengeSolution { signature: Signature },
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum Outbound {
|
||||||
|
AuthChallenge { pubkey: VerifyingKey, nonce: i32 },
|
||||||
|
AuthSuccess,
|
||||||
|
}
|
||||||
|
|
||||||
/// Atomically reads and increments the nonce for a known client.
|
/// Atomically reads and increments the nonce for a known client.
|
||||||
/// Returns `None` if the pubkey is not registered.
|
/// Returns `None` if the pubkey is not registered.
|
||||||
async fn get_nonce(db: &db::DatabasePool, pubkey: &VerifyingKey) -> Result<Option<i32>, Error> {
|
async fn get_nonce(db: &db::DatabasePool, pubkey: &VerifyingKey) -> Result<Option<i32>, Error> {
|
||||||
@@ -141,27 +146,24 @@ async fn insert_client(db: &db::DatabasePool, pubkey: &VerifyingKey) -> Result<(
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn challenge_client(
|
async fn challenge_client<T>(
|
||||||
props: &mut ClientConnection,
|
transport: &mut T,
|
||||||
pubkey: VerifyingKey,
|
pubkey: VerifyingKey,
|
||||||
nonce: i32,
|
nonce: i32,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error>
|
||||||
let challenge_pubkey = pubkey.as_bytes().to_vec();
|
where
|
||||||
|
T: Bi<Inbound, Result<Outbound, Error>> + ?Sized,
|
||||||
props
|
{
|
||||||
.transport
|
transport
|
||||||
.send(Ok(Response::AuthChallenge {
|
.send(Ok(Outbound::AuthChallenge { pubkey, nonce }))
|
||||||
pubkey: challenge_pubkey.clone(),
|
|
||||||
nonce,
|
|
||||||
}))
|
|
||||||
.await
|
.await
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
error!(error = ?e, "Failed to send auth challenge");
|
error!(error = ?e, "Failed to send auth challenge");
|
||||||
Error::Transport
|
Error::Transport
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
let signature = expect_message(&mut *props.transport, |req: Request| match req {
|
let signature = expect_message(transport, |req: Inbound| match req {
|
||||||
Request::AuthChallengeSolution { signature } => Some(signature),
|
Inbound::AuthChallengeSolution { signature } => Some(signature),
|
||||||
_ => None,
|
_ => None,
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
@@ -170,13 +172,9 @@ async fn challenge_client(
|
|||||||
Error::Transport
|
Error::Transport
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
let formatted = format_challenge(nonce, &challenge_pubkey);
|
let formatted = format_challenge(nonce, pubkey.as_bytes());
|
||||||
let sig = signature.as_slice().try_into().map_err(|_| {
|
|
||||||
error!("Invalid signature length");
|
|
||||||
Error::InvalidChallengeSolution
|
|
||||||
})?;
|
|
||||||
|
|
||||||
pubkey.verify_strict(&formatted, &sig).map_err(|_| {
|
pubkey.verify_strict(&formatted, &signature).map_err(|_| {
|
||||||
error!("Challenge solution verification failed");
|
error!("Challenge solution verification failed");
|
||||||
Error::InvalidChallengeSolution
|
Error::InvalidChallengeSolution
|
||||||
})?;
|
})?;
|
||||||
@@ -184,30 +182,17 @@ async fn challenge_client(
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn connect_error_code(err: &Error) -> ConnectErrorCode {
|
pub async fn authenticate<T>(
|
||||||
match err {
|
props: &mut ClientConnection,
|
||||||
Error::ApproveError(ApproveError::Denied) => ConnectErrorCode::ApprovalDenied,
|
transport: &mut T,
|
||||||
Error::ApproveError(ApproveError::Upstream(
|
) -> Result<VerifyingKey, Error>
|
||||||
router::ApprovalError::NoUserAgentsConnected,
|
where
|
||||||
)) => ConnectErrorCode::NoUserAgentsOnline,
|
T: Bi<Inbound, Result<Outbound, Error>> + Send + ?Sized,
|
||||||
_ => ConnectErrorCode::Unknown,
|
{
|
||||||
}
|
let Some(Inbound::AuthChallengeRequest { pubkey }) = transport.recv().await else {
|
||||||
}
|
|
||||||
|
|
||||||
async fn authenticate(props: &mut ClientConnection) -> Result<VerifyingKey, Error> {
|
|
||||||
let Some(Request::AuthChallengeRequest {
|
|
||||||
pubkey: challenge_pubkey,
|
|
||||||
}) = props.transport.recv().await
|
|
||||||
else {
|
|
||||||
return Err(Error::Transport);
|
return Err(Error::Transport);
|
||||||
};
|
};
|
||||||
|
|
||||||
let pubkey_bytes = challenge_pubkey
|
|
||||||
.as_array()
|
|
||||||
.ok_or(Error::InvalidClientPubkeyLength)?;
|
|
||||||
let pubkey =
|
|
||||||
VerifyingKey::from_bytes(pubkey_bytes).map_err(|_| Error::InvalidAuthPubkeyEncoding)?;
|
|
||||||
|
|
||||||
let nonce = match get_nonce(&props.db, &pubkey).await? {
|
let nonce = match get_nonce(&props.db, &pubkey).await? {
|
||||||
Some(nonce) => nonce,
|
Some(nonce) => nonce,
|
||||||
None => {
|
None => {
|
||||||
@@ -217,21 +202,14 @@ async fn authenticate(props: &mut ClientConnection) -> Result<VerifyingKey, Erro
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
challenge_client(props, pubkey, nonce).await?;
|
challenge_client(transport, pubkey, nonce).await?;
|
||||||
|
transport
|
||||||
|
.send(Ok(Outbound::AuthSuccess))
|
||||||
|
.await
|
||||||
|
.map_err(|e| {
|
||||||
|
error!(error = ?e, "Failed to send auth success");
|
||||||
|
Error::Transport
|
||||||
|
})?;
|
||||||
|
|
||||||
Ok(pubkey)
|
Ok(pubkey)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn authenticate_and_create(mut props: ClientConnection) -> Result<ClientSession, Error> {
|
|
||||||
match authenticate(&mut props).await {
|
|
||||||
Ok(_pubkey) => Ok(ClientSession::new(props)),
|
|
||||||
Err(err) => {
|
|
||||||
let code = connect_error_code(&err);
|
|
||||||
let _ = props
|
|
||||||
.transport
|
|
||||||
.send(Ok(Response::ClientConnectError { code }))
|
|
||||||
.await;
|
|
||||||
Err(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -7,68 +7,31 @@ use crate::{
|
|||||||
db,
|
db,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
|
|
||||||
pub enum ClientError {
|
|
||||||
#[error("Expected message with payload")]
|
|
||||||
MissingRequestPayload,
|
|
||||||
#[error("Unexpected request payload")]
|
|
||||||
UnexpectedRequestPayload,
|
|
||||||
#[error("State machine error")]
|
|
||||||
StateTransitionFailed,
|
|
||||||
#[error("Connection registration failed")]
|
|
||||||
ConnectionRegistrationFailed,
|
|
||||||
#[error(transparent)]
|
|
||||||
Auth(#[from] auth::Error),
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
||||||
pub enum ConnectErrorCode {
|
|
||||||
Unknown,
|
|
||||||
ApprovalDenied,
|
|
||||||
NoUserAgentsOnline,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
||||||
pub enum Request {
|
|
||||||
AuthChallengeRequest { pubkey: Vec<u8> },
|
|
||||||
AuthChallengeSolution { signature: Vec<u8> },
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
||||||
pub enum Response {
|
|
||||||
AuthChallenge { pubkey: Vec<u8>, nonce: i32 },
|
|
||||||
AuthOk,
|
|
||||||
ClientConnectError { code: ConnectErrorCode },
|
|
||||||
}
|
|
||||||
|
|
||||||
pub type Transport = Box<dyn Bi<Request, Result<Response, ClientError>> + Send>;
|
|
||||||
|
|
||||||
pub struct ClientConnection {
|
pub struct ClientConnection {
|
||||||
pub(crate) db: db::DatabasePool,
|
pub(crate) db: db::DatabasePool,
|
||||||
pub(crate) transport: Transport,
|
|
||||||
pub(crate) actors: GlobalActors,
|
pub(crate) actors: GlobalActors,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ClientConnection {
|
impl ClientConnection {
|
||||||
pub fn new(db: db::DatabasePool, transport: Transport, actors: GlobalActors) -> Self {
|
pub fn new(db: db::DatabasePool, actors: GlobalActors) -> Self {
|
||||||
Self {
|
Self { db, actors }
|
||||||
db,
|
|
||||||
transport,
|
|
||||||
actors,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub mod auth;
|
pub mod auth;
|
||||||
pub mod session;
|
pub mod session;
|
||||||
|
|
||||||
pub async fn connect_client(props: ClientConnection) {
|
pub async fn connect_client<T>(mut props: ClientConnection, transport: &mut T)
|
||||||
match auth::authenticate_and_create(props).await {
|
where
|
||||||
Ok(session) => {
|
T: Bi<auth::Inbound, Result<auth::Outbound, auth::Error>> + Send + ?Sized,
|
||||||
ClientSession::spawn(session);
|
{
|
||||||
|
match auth::authenticate(&mut props, transport).await {
|
||||||
|
Ok(_pubkey) => {
|
||||||
|
ClientSession::spawn(ClientSession::new(props));
|
||||||
info!("Client authenticated, session started");
|
info!("Client authenticated, session started");
|
||||||
}
|
}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
|
let _ = transport.send(Err(err.clone())).await;
|
||||||
error!(?err, "Authentication failed, closing connection");
|
error!(?err, "Authentication failed, closing connection");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,9 @@
|
|||||||
use kameo::Actor;
|
use kameo::{Actor, messages};
|
||||||
use tokio::select;
|
use tracing::error;
|
||||||
use tracing::{error, info};
|
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
actors::{
|
actors::{
|
||||||
GlobalActors,
|
GlobalActors, client::ClientConnection, keyholder::KeyHolderState, router::RegisterClient,
|
||||||
client::{ClientConnection, ClientError, Request, Response},
|
|
||||||
router::RegisterClient,
|
|
||||||
},
|
},
|
||||||
db,
|
db,
|
||||||
};
|
};
|
||||||
@@ -19,19 +16,30 @@ impl ClientSession {
|
|||||||
pub(crate) fn new(props: ClientConnection) -> Self {
|
pub(crate) fn new(props: ClientConnection) -> Self {
|
||||||
Self { props }
|
Self { props }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn process_transport_inbound(&mut self, req: Request) -> Output {
|
|
||||||
let _ = req;
|
|
||||||
Err(ClientError::UnexpectedRequestPayload)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type Output = Result<Response, ClientError>;
|
#[messages]
|
||||||
|
impl ClientSession {
|
||||||
|
#[message]
|
||||||
|
pub(crate) async fn handle_query_vault_state(&mut self) -> Result<KeyHolderState, Error> {
|
||||||
|
use crate::actors::keyholder::GetState;
|
||||||
|
|
||||||
|
let vault_state = match self.props.actors.key_holder.ask(GetState {}).await {
|
||||||
|
Ok(state) => state,
|
||||||
|
Err(err) => {
|
||||||
|
error!(?err, actor = "client", "keyholder.query.failed");
|
||||||
|
return Err(Error::Internal);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(vault_state)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Actor for ClientSession {
|
impl Actor for ClientSession {
|
||||||
type Args = Self;
|
type Args = Self;
|
||||||
|
|
||||||
type Error = ClientError;
|
type Error = Error;
|
||||||
|
|
||||||
async fn on_start(
|
async fn on_start(
|
||||||
args: Self::Args,
|
args: Self::Args,
|
||||||
@@ -42,52 +50,22 @@ impl Actor for ClientSession {
|
|||||||
.router
|
.router
|
||||||
.ask(RegisterClient { actor: this })
|
.ask(RegisterClient { actor: this })
|
||||||
.await
|
.await
|
||||||
.map_err(|_| ClientError::ConnectionRegistrationFailed)?;
|
.map_err(|_| Error::ConnectionRegistrationFailed)?;
|
||||||
Ok(args)
|
Ok(args)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn next(
|
|
||||||
&mut self,
|
|
||||||
_actor_ref: kameo::prelude::WeakActorRef<Self>,
|
|
||||||
mailbox_rx: &mut kameo::prelude::MailboxReceiver<Self>,
|
|
||||||
) -> Option<kameo::mailbox::Signal<Self>> {
|
|
||||||
loop {
|
|
||||||
select! {
|
|
||||||
signal = mailbox_rx.recv() => {
|
|
||||||
return signal;
|
|
||||||
}
|
|
||||||
msg = self.props.transport.recv() => {
|
|
||||||
match msg {
|
|
||||||
Some(request) => {
|
|
||||||
match self.process_transport_inbound(request).await {
|
|
||||||
Ok(resp) => {
|
|
||||||
if self.props.transport.send(Ok(resp)).await.is_err() {
|
|
||||||
error!(actor = "client", reason = "channel closed", "send.failed");
|
|
||||||
return Some(kameo::mailbox::Signal::Stop);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(err) => {
|
|
||||||
let _ = self.props.transport.send(Err(err)).await;
|
|
||||||
return Some(kameo::mailbox::Signal::Stop);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None => {
|
|
||||||
info!(actor = "client", "transport.closed");
|
|
||||||
return Some(kameo::mailbox::Signal::Stop);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ClientSession {
|
impl ClientSession {
|
||||||
pub fn new_test(db: db::DatabasePool, actors: GlobalActors) -> Self {
|
pub fn new_test(db: db::DatabasePool, actors: GlobalActors) -> Self {
|
||||||
use arbiter_proto::transport::DummyTransport;
|
let props = ClientConnection::new(db, actors);
|
||||||
let transport: super::Transport = Box::new(DummyTransport::new());
|
|
||||||
let props = ClientConnection::new(db, transport, actors);
|
|
||||||
Self { props }
|
Self { props }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, thiserror::Error)]
|
||||||
|
pub enum Error {
|
||||||
|
#[error("Connection registration failed")]
|
||||||
|
ConnectionRegistrationFailed,
|
||||||
|
#[error("Internal error")]
|
||||||
|
Internal,
|
||||||
|
}
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ use encryption::v1::{self, KeyCell, Nonce};
|
|||||||
pub mod encryption;
|
pub mod encryption;
|
||||||
|
|
||||||
#[derive(Default, EnumDiscriminants)]
|
#[derive(Default, EnumDiscriminants)]
|
||||||
#[strum_discriminants(derive(Reply), vis(pub))]
|
#[strum_discriminants(derive(Reply), vis(pub), name(KeyHolderState))]
|
||||||
enum State {
|
enum State {
|
||||||
#[default]
|
#[default]
|
||||||
Unbootstrapped,
|
Unbootstrapped,
|
||||||
@@ -325,7 +325,7 @@ impl KeyHolder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[message]
|
#[message]
|
||||||
pub fn get_state(&self) -> StateDiscriminants {
|
pub fn get_state(&self) -> KeyHolderState {
|
||||||
self.state.discriminant()
|
self.state.discriminant()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,74 +1,82 @@
|
|||||||
|
use arbiter_proto::transport::Bi;
|
||||||
use tracing::error;
|
use tracing::error;
|
||||||
|
|
||||||
use crate::actors::user_agent::{
|
use crate::actors::user_agent::{
|
||||||
Request, UserAgentConnection,
|
AuthPublicKey, UserAgentConnection,
|
||||||
auth::state::{AuthContext, AuthStateMachine},
|
auth::state::{AuthContext, AuthStateMachine},
|
||||||
AuthPublicKey,
|
|
||||||
session::UserAgentSession,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(thiserror::Error, Debug, PartialEq)]
|
|
||||||
pub enum Error {
|
|
||||||
#[error("Unexpected message payload")]
|
|
||||||
UnexpectedMessagePayload,
|
|
||||||
#[error("Invalid client public key length")]
|
|
||||||
InvalidClientPubkeyLength,
|
|
||||||
#[error("Invalid client public key encoding")]
|
|
||||||
InvalidAuthPubkeyEncoding,
|
|
||||||
#[error("Database pool unavailable")]
|
|
||||||
DatabasePoolUnavailable,
|
|
||||||
#[error("Database operation failed")]
|
|
||||||
DatabaseOperationFailed,
|
|
||||||
#[error("Public key not registered")]
|
|
||||||
PublicKeyNotRegistered,
|
|
||||||
#[error("Transport error")]
|
|
||||||
Transport,
|
|
||||||
#[error("Invalid bootstrap token")]
|
|
||||||
InvalidBootstrapToken,
|
|
||||||
#[error("Bootstrapper actor unreachable")]
|
|
||||||
BootstrapperActorUnreachable,
|
|
||||||
#[error("Invalid challenge solution")]
|
|
||||||
InvalidChallengeSolution,
|
|
||||||
}
|
|
||||||
|
|
||||||
mod state;
|
mod state;
|
||||||
use state::*;
|
use state::*;
|
||||||
|
|
||||||
fn parse_auth_event(payload: Request) -> Result<AuthEvents, Error> {
|
#[derive(Debug, Clone)]
|
||||||
match payload {
|
pub enum Inbound {
|
||||||
Request::AuthChallengeRequest {
|
AuthChallengeRequest {
|
||||||
pubkey,
|
pubkey: AuthPublicKey,
|
||||||
bootstrap_token: None,
|
bootstrap_token: Option<String>,
|
||||||
} => Ok(AuthEvents::AuthRequest(ChallengeRequest { pubkey })),
|
},
|
||||||
Request::AuthChallengeRequest {
|
AuthChallengeSolution {
|
||||||
pubkey,
|
signature: Vec<u8>,
|
||||||
bootstrap_token: Some(token),
|
},
|
||||||
} => Ok(AuthEvents::BootstrapAuthRequest(BootstrapAuthRequest {
|
}
|
||||||
pubkey,
|
|
||||||
token,
|
#[derive(Debug)]
|
||||||
})),
|
pub enum Error {
|
||||||
Request::AuthChallengeSolution { signature } => {
|
UnregisteredPublicKey,
|
||||||
Ok(AuthEvents::ReceivedSolution(ChallengeSolution {
|
InvalidChallengeSolution,
|
||||||
solution: signature,
|
InvalidBootstrapToken,
|
||||||
}))
|
Internal { details: String },
|
||||||
|
Transport,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Error {
|
||||||
|
fn internal(details: impl Into<String>) -> Self {
|
||||||
|
Self::Internal {
|
||||||
|
details: details.into(),
|
||||||
}
|
}
|
||||||
_ => Err(Error::UnexpectedMessagePayload),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn authenticate(props: &mut UserAgentConnection) -> Result<AuthPublicKey, Error> {
|
#[derive(Debug, Clone)]
|
||||||
let mut state = AuthStateMachine::new(AuthContext::new(props));
|
pub enum Outbound {
|
||||||
|
AuthChallenge { nonce: i32 },
|
||||||
|
AuthSuccess,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_auth_event(payload: Inbound) -> AuthEvents {
|
||||||
|
match payload {
|
||||||
|
Inbound::AuthChallengeRequest {
|
||||||
|
pubkey,
|
||||||
|
bootstrap_token: None,
|
||||||
|
} => AuthEvents::AuthRequest(ChallengeRequest { pubkey }),
|
||||||
|
Inbound::AuthChallengeRequest {
|
||||||
|
pubkey,
|
||||||
|
bootstrap_token: Some(token),
|
||||||
|
} => AuthEvents::BootstrapAuthRequest(BootstrapAuthRequest { pubkey, token }),
|
||||||
|
Inbound::AuthChallengeSolution { signature } => {
|
||||||
|
AuthEvents::ReceivedSolution(ChallengeSolution {
|
||||||
|
solution: signature,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn authenticate<T>(
|
||||||
|
props: &mut UserAgentConnection,
|
||||||
|
transport: T,
|
||||||
|
) -> Result<AuthPublicKey, Error>
|
||||||
|
where
|
||||||
|
T: Bi<Inbound, Result<Outbound, Error>> + Send,
|
||||||
|
{
|
||||||
|
let mut state = AuthStateMachine::new(AuthContext::new(props, transport));
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
// `state` holds a mutable reference to `props` so we can't access it directly here
|
// `state` holds a mutable reference to `props` so we can't access it directly here
|
||||||
let transport = state.context_mut().conn.transport.as_mut();
|
let Some(payload) = state.context_mut().transport.recv().await else {
|
||||||
let Some(payload) = transport.recv().await else {
|
|
||||||
return Err(Error::Transport);
|
return Err(Error::Transport);
|
||||||
};
|
};
|
||||||
|
|
||||||
let event = parse_auth_event(payload)?;
|
match state.process_event(parse_auth_event(payload)).await {
|
||||||
|
|
||||||
match state.process_event(event).await {
|
|
||||||
Ok(AuthStates::AuthOk(key)) => return Ok(key.clone()),
|
Ok(AuthStates::AuthOk(key)) => return Ok(key.clone()),
|
||||||
Err(AuthError::ActionFailed(err)) => {
|
Err(AuthError::ActionFailed(err)) => {
|
||||||
error!(?err, "State machine action failed");
|
error!(?err, "State machine action failed");
|
||||||
@@ -91,11 +99,3 @@ pub async fn authenticate(props: &mut UserAgentConnection) -> Result<AuthPublicK
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn authenticate_and_create(
|
|
||||||
mut props: UserAgentConnection,
|
|
||||||
) -> Result<UserAgentSession, Error> {
|
|
||||||
let _key = authenticate(&mut props).await?;
|
|
||||||
let session = UserAgentSession::new(props);
|
|
||||||
Ok(session)
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
use alloy::transports::Transport;
|
||||||
|
use arbiter_proto::transport::Bi;
|
||||||
use diesel::{ExpressionMethods as _, OptionalExtension as _, QueryDsl, update};
|
use diesel::{ExpressionMethods as _, OptionalExtension as _, QueryDsl, update};
|
||||||
use diesel_async::RunQueryDsl;
|
use diesel_async::RunQueryDsl;
|
||||||
use tracing::error;
|
use tracing::error;
|
||||||
@@ -6,7 +8,7 @@ use super::Error;
|
|||||||
use crate::{
|
use crate::{
|
||||||
actors::{
|
actors::{
|
||||||
bootstrap::ConsumeToken,
|
bootstrap::ConsumeToken,
|
||||||
user_agent::{AuthPublicKey, Response, UserAgentConnection},
|
user_agent::{AuthPublicKey, OutOfBand, UserAgentConnection, auth::Outbound},
|
||||||
},
|
},
|
||||||
db::schema,
|
db::schema,
|
||||||
};
|
};
|
||||||
@@ -42,7 +44,7 @@ smlang::statemachine!(
|
|||||||
async fn create_nonce(db: &crate::db::DatabasePool, pubkey_bytes: &[u8]) -> Result<i32, Error> {
|
async fn create_nonce(db: &crate::db::DatabasePool, pubkey_bytes: &[u8]) -> Result<i32, Error> {
|
||||||
let mut db_conn = db.get().await.map_err(|e| {
|
let mut db_conn = db.get().await.map_err(|e| {
|
||||||
error!(error = ?e, "Database pool error");
|
error!(error = ?e, "Database pool error");
|
||||||
Error::DatabasePoolUnavailable
|
Error::internal("Database unavailable")
|
||||||
})?;
|
})?;
|
||||||
db_conn
|
db_conn
|
||||||
.exclusive_transaction(|conn| {
|
.exclusive_transaction(|conn| {
|
||||||
@@ -66,11 +68,11 @@ async fn create_nonce(db: &crate::db::DatabasePool, pubkey_bytes: &[u8]) -> Resu
|
|||||||
.optional()
|
.optional()
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
error!(error = ?e, "Database error");
|
error!(error = ?e, "Database error");
|
||||||
Error::DatabaseOperationFailed
|
Error::internal("Database operation failed")
|
||||||
})?
|
})?
|
||||||
.ok_or_else(|| {
|
.ok_or_else(|| {
|
||||||
error!(?pubkey_bytes, "Public key not found in database");
|
error!(?pubkey_bytes, "Public key not found in database");
|
||||||
Error::PublicKeyNotRegistered
|
Error::UnregisteredPublicKey
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -79,7 +81,7 @@ async fn register_key(db: &crate::db::DatabasePool, pubkey: &AuthPublicKey) -> R
|
|||||||
let key_type = pubkey.key_type();
|
let key_type = pubkey.key_type();
|
||||||
let mut conn = db.get().await.map_err(|e| {
|
let mut conn = db.get().await.map_err(|e| {
|
||||||
error!(error = ?e, "Database pool error");
|
error!(error = ?e, "Database pool error");
|
||||||
Error::DatabasePoolUnavailable
|
Error::internal("Database unavailable")
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
diesel::insert_into(schema::useragent_client::table)
|
diesel::insert_into(schema::useragent_client::table)
|
||||||
@@ -92,23 +94,27 @@ async fn register_key(db: &crate::db::DatabasePool, pubkey: &AuthPublicKey) -> R
|
|||||||
.await
|
.await
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
error!(error = ?e, "Database error");
|
error!(error = ?e, "Database error");
|
||||||
Error::DatabaseOperationFailed
|
Error::internal("Database operation failed")
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct AuthContext<'a> {
|
pub struct AuthContext<'a, T> {
|
||||||
pub(super) conn: &'a mut UserAgentConnection,
|
pub(super) conn: &'a mut UserAgentConnection,
|
||||||
|
pub(super) transport: T,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> AuthContext<'a> {
|
impl<'a, T> AuthContext<'a, T> {
|
||||||
pub fn new(conn: &'a mut UserAgentConnection) -> Self {
|
pub fn new(conn: &'a mut UserAgentConnection, transport: T) -> Self {
|
||||||
Self { conn }
|
Self { conn, transport }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AuthStateMachineContext for AuthContext<'_> {
|
impl<T> AuthStateMachineContext for AuthContext<'_, T>
|
||||||
|
where
|
||||||
|
T: Bi<super::Inbound, Result<super::Outbound, Error>> + Send,
|
||||||
|
{
|
||||||
type Error = Error;
|
type Error = Error;
|
||||||
|
|
||||||
async fn prepare_challenge(
|
async fn prepare_challenge(
|
||||||
@@ -118,9 +124,8 @@ impl AuthStateMachineContext for AuthContext<'_> {
|
|||||||
let stored_bytes = pubkey.to_stored_bytes();
|
let stored_bytes = pubkey.to_stored_bytes();
|
||||||
let nonce = create_nonce(&self.conn.db, &stored_bytes).await?;
|
let nonce = create_nonce(&self.conn.db, &stored_bytes).await?;
|
||||||
|
|
||||||
self.conn
|
self.transport
|
||||||
.transport
|
.send(Ok(Outbound::AuthChallenge { nonce }))
|
||||||
.send(Ok(Response::AuthChallenge { nonce }))
|
|
||||||
.await
|
.await
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
error!(?e, "Failed to send auth challenge");
|
error!(?e, "Failed to send auth challenge");
|
||||||
@@ -149,7 +154,7 @@ impl AuthStateMachineContext for AuthContext<'_> {
|
|||||||
.await
|
.await
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
error!(?e, "Failed to consume bootstrap token");
|
error!(?e, "Failed to consume bootstrap token");
|
||||||
Error::BootstrapperActorUnreachable
|
Error::internal("Failed to consume bootstrap token")
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
if !token_ok {
|
if !token_ok {
|
||||||
@@ -159,11 +164,10 @@ impl AuthStateMachineContext for AuthContext<'_> {
|
|||||||
|
|
||||||
register_key(&self.conn.db, &pubkey).await?;
|
register_key(&self.conn.db, &pubkey).await?;
|
||||||
|
|
||||||
self.conn
|
self.transport
|
||||||
.transport
|
.send(Ok(Outbound::AuthSuccess))
|
||||||
.send(Ok(Response::AuthOk))
|
.await
|
||||||
.await
|
.map_err(|_| Error::Transport)?;
|
||||||
.map_err(|_| Error::Transport)?;
|
|
||||||
|
|
||||||
Ok(pubkey)
|
Ok(pubkey)
|
||||||
}
|
}
|
||||||
@@ -172,7 +176,10 @@ impl AuthStateMachineContext for AuthContext<'_> {
|
|||||||
#[allow(clippy::unused_unit)]
|
#[allow(clippy::unused_unit)]
|
||||||
async fn verify_solution(
|
async fn verify_solution(
|
||||||
&mut self,
|
&mut self,
|
||||||
ChallengeContext { challenge_nonce, key }: &ChallengeContext,
|
ChallengeContext {
|
||||||
|
challenge_nonce,
|
||||||
|
key,
|
||||||
|
}: &ChallengeContext,
|
||||||
ChallengeSolution { solution }: ChallengeSolution,
|
ChallengeSolution { solution }: ChallengeSolution,
|
||||||
) -> Result<AuthPublicKey, Self::Error> {
|
) -> Result<AuthPublicKey, Self::Error> {
|
||||||
let formatted = arbiter_proto::format_challenge(*challenge_nonce, &key.to_stored_bytes());
|
let formatted = arbiter_proto::format_challenge(*challenge_nonce, &key.to_stored_bytes());
|
||||||
@@ -205,9 +212,8 @@ impl AuthStateMachineContext for AuthContext<'_> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if valid {
|
if valid {
|
||||||
self.conn
|
self.transport
|
||||||
.transport
|
.send(Ok(Outbound::AuthSuccess))
|
||||||
.send(Ok(Response::AuthOk))
|
|
||||||
.await
|
.await
|
||||||
.map_err(|_| Error::Transport)?;
|
.map_err(|_| Error::Transport)?;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,33 +1,15 @@
|
|||||||
use alloy::primitives::Address;
|
use alloy::primitives::Address;
|
||||||
use arbiter_proto::transport::Bi;
|
use arbiter_proto::transport::{Bi, Sender};
|
||||||
use kameo::actor::Spawn as _;
|
use kameo::actor::Spawn as _;
|
||||||
use tracing::{error, info};
|
use tracing::{error, info};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
actors::{GlobalActors, evm, user_agent::session::UserAgentSession},
|
actors::{GlobalActors, evm},
|
||||||
db::{self, models::KeyType},
|
db::{self, models::KeyType},
|
||||||
evm::policies::SharedGrantSettings,
|
evm::policies::SharedGrantSettings,
|
||||||
evm::policies::{Grant, SpecificGrant},
|
evm::policies::{Grant, SpecificGrant},
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Debug, thiserror::Error, PartialEq)]
|
|
||||||
pub enum TransportResponseError {
|
|
||||||
#[error("Unexpected request payload")]
|
|
||||||
UnexpectedRequestPayload,
|
|
||||||
#[error("Invalid state for unseal encrypted key")]
|
|
||||||
InvalidStateForUnsealEncryptedKey,
|
|
||||||
#[error("client_pubkey must be 32 bytes")]
|
|
||||||
InvalidClientPubkeyLength,
|
|
||||||
#[error("State machine error")]
|
|
||||||
StateTransitionFailed,
|
|
||||||
#[error("Vault is not available")]
|
|
||||||
KeyHolderActorUnreachable,
|
|
||||||
#[error(transparent)]
|
|
||||||
Auth(#[from] auth::Error),
|
|
||||||
#[error("Failed registering connection")]
|
|
||||||
ConnectionRegistrationFailed,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Abstraction over Ed25519 / ECDSA-secp256k1 / RSA public keys used during the auth handshake.
|
/// Abstraction over Ed25519 / ECDSA-secp256k1 / RSA public keys used during the auth handshake.
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub enum AuthPublicKey {
|
pub enum AuthPublicKey {
|
||||||
@@ -65,119 +47,55 @@ impl AuthPublicKey {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
impl TryFrom<(KeyType, Vec<u8>)> for AuthPublicKey {
|
||||||
pub enum UnsealError {
|
type Error = &'static str;
|
||||||
InvalidKey,
|
|
||||||
Unbootstrapped,
|
fn try_from(value: (KeyType, Vec<u8>)) -> Result<Self, Self::Error> {
|
||||||
}
|
let (key_type, bytes) = value;
|
||||||
|
match key_type {
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
KeyType::Ed25519 => {
|
||||||
pub enum BootstrapError {
|
let bytes: [u8; 32] = bytes.try_into().map_err(|_| "invalid Ed25519 key length")?;
|
||||||
AlreadyBootstrapped,
|
let key = ed25519_dalek::VerifyingKey::from_bytes(&bytes)
|
||||||
InvalidKey,
|
.map_err(|e| "invalid Ed25519 key")?;
|
||||||
}
|
Ok(AuthPublicKey::Ed25519(key))
|
||||||
|
}
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
KeyType::EcdsaSecp256k1 => {
|
||||||
pub enum VaultState {
|
let point =
|
||||||
Unbootstrapped,
|
k256::EncodedPoint::from_bytes(&bytes).map_err(|e| "invalid ECDSA key")?;
|
||||||
Sealed,
|
let key = k256::ecdsa::VerifyingKey::from_encoded_point(&point)
|
||||||
Unsealed,
|
.map_err(|e| "invalid ECDSA key")?;
|
||||||
}
|
Ok(AuthPublicKey::EcdsaSecp256k1(key))
|
||||||
|
}
|
||||||
#[derive(Debug, Clone)]
|
KeyType::Rsa => {
|
||||||
pub enum Request {
|
use rsa::pkcs8::DecodePublicKey as _;
|
||||||
AuthChallengeRequest {
|
let key = rsa::RsaPublicKey::from_public_key_der(&bytes)
|
||||||
pubkey: AuthPublicKey,
|
.map_err(|e| "invalid RSA key")?;
|
||||||
bootstrap_token: Option<String>,
|
Ok(AuthPublicKey::Rsa(key))
|
||||||
},
|
}
|
||||||
AuthChallengeSolution {
|
}
|
||||||
signature: Vec<u8>,
|
}
|
||||||
},
|
|
||||||
UnsealStart {
|
|
||||||
client_pubkey: x25519_dalek::PublicKey,
|
|
||||||
},
|
|
||||||
UnsealEncryptedKey {
|
|
||||||
nonce: Vec<u8>,
|
|
||||||
ciphertext: Vec<u8>,
|
|
||||||
associated_data: Vec<u8>,
|
|
||||||
},
|
|
||||||
BootstrapEncryptedKey {
|
|
||||||
nonce: Vec<u8>,
|
|
||||||
ciphertext: Vec<u8>,
|
|
||||||
associated_data: Vec<u8>,
|
|
||||||
},
|
|
||||||
QueryVaultState,
|
|
||||||
EvmWalletCreate,
|
|
||||||
EvmWalletList,
|
|
||||||
ClientConnectionResponse {
|
|
||||||
approved: bool,
|
|
||||||
},
|
|
||||||
|
|
||||||
ListGrants,
|
|
||||||
EvmGrantCreate {
|
|
||||||
client_id: i32,
|
|
||||||
shared: SharedGrantSettings,
|
|
||||||
specific: SpecificGrant,
|
|
||||||
},
|
|
||||||
EvmGrantDelete {
|
|
||||||
grant_id: i32,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Messages, sent by user agent to connection client without having a request
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum Response {
|
pub enum OutOfBand {
|
||||||
AuthChallenge {
|
ClientConnectionRequest { pubkey: ed25519_dalek::VerifyingKey },
|
||||||
nonce: i32,
|
|
||||||
},
|
|
||||||
AuthOk,
|
|
||||||
UnsealStartResponse {
|
|
||||||
server_pubkey: x25519_dalek::PublicKey,
|
|
||||||
},
|
|
||||||
UnsealResult(Result<(), UnsealError>),
|
|
||||||
BootstrapResult(Result<(), BootstrapError>),
|
|
||||||
VaultState(VaultState),
|
|
||||||
ClientConnectionRequest {
|
|
||||||
pubkey: ed25519_dalek::VerifyingKey,
|
|
||||||
},
|
|
||||||
ClientConnectionCancel,
|
ClientConnectionCancel,
|
||||||
EvmWalletCreate(Result<(), evm::Error>),
|
|
||||||
EvmWalletList(Vec<Address>),
|
|
||||||
|
|
||||||
ListGrants(Vec<Grant<SpecificGrant>>),
|
|
||||||
EvmGrantCreate(Result<i32, evm::Error>),
|
|
||||||
EvmGrantDelete(Result<(), evm::Error>),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type Transport = Box<dyn Bi<Request, Result<Response, TransportResponseError>> + Send>;
|
|
||||||
|
|
||||||
pub struct UserAgentConnection {
|
pub struct UserAgentConnection {
|
||||||
db: db::DatabasePool,
|
pub(crate) db: db::DatabasePool,
|
||||||
actors: GlobalActors,
|
pub(crate) actors: GlobalActors,
|
||||||
transport: Transport,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl UserAgentConnection {
|
impl UserAgentConnection {
|
||||||
pub fn new(db: db::DatabasePool, actors: GlobalActors, transport: Transport) -> Self {
|
pub fn new(db: db::DatabasePool, actors: GlobalActors) -> Self {
|
||||||
Self {
|
Self { db, actors }
|
||||||
db,
|
|
||||||
actors,
|
|
||||||
transport,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub mod auth;
|
pub mod auth;
|
||||||
pub mod session;
|
pub mod session;
|
||||||
|
|
||||||
#[tracing::instrument(skip(props))]
|
pub use auth::authenticate;
|
||||||
pub async fn connect_user_agent(props: UserAgentConnection) {
|
pub use session::UserAgentSession;
|
||||||
match auth::authenticate_and_create(props).await {
|
|
||||||
Ok(session) => {
|
|
||||||
UserAgentSession::spawn(session);
|
|
||||||
info!("User authenticated, session started");
|
|
||||||
}
|
|
||||||
Err(err) => {
|
|
||||||
error!(?err, "Authentication failed, closing connection");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,93 +1,80 @@
|
|||||||
|
use std::{borrow::Cow, convert::Infallible};
|
||||||
|
|
||||||
|
use arbiter_proto::transport::Sender;
|
||||||
|
use async_trait::async_trait;
|
||||||
use ed25519_dalek::VerifyingKey;
|
use ed25519_dalek::VerifyingKey;
|
||||||
use kameo::{Actor, messages, prelude::Context};
|
use kameo::{Actor, messages, prelude::Context};
|
||||||
|
use thiserror::Error;
|
||||||
use tokio::{select, sync::watch};
|
use tokio::{select, sync::watch};
|
||||||
use tracing::{error, info};
|
use tracing::{error, info};
|
||||||
|
|
||||||
use crate::actors::{
|
use crate::actors::{
|
||||||
router::RegisterUserAgent,
|
router::RegisterUserAgent,
|
||||||
user_agent::{
|
user_agent::{OutOfBand, UserAgentConnection},
|
||||||
Request, Response, TransportResponseError,
|
|
||||||
UserAgentConnection,
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
mod state;
|
mod state;
|
||||||
use state::{DummyContext, UserAgentEvents, UserAgentStateMachine};
|
use state::{DummyContext, UserAgentEvents, UserAgentStateMachine};
|
||||||
|
|
||||||
// Error for consumption by other actors
|
#[derive(Debug, Error)]
|
||||||
#[derive(Debug, thiserror::Error, PartialEq)]
|
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
#[error("User agent session ended due to connection loss")]
|
#[error("State transition failed")]
|
||||||
ConnectionLost,
|
State,
|
||||||
|
|
||||||
#[error("User agent session ended due to unexpected message")]
|
#[error("Internal error: {message}")]
|
||||||
UnexpectedMessage,
|
Internal { message: Cow<'static, str> },
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Error {
|
||||||
|
pub fn internal(message: impl Into<Cow<'static, str>>) -> Self {
|
||||||
|
Self::Internal {
|
||||||
|
message: message.into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct UserAgentSession {
|
pub struct UserAgentSession {
|
||||||
props: UserAgentConnection,
|
props: UserAgentConnection,
|
||||||
state: UserAgentStateMachine<DummyContext>,
|
state: UserAgentStateMachine<DummyContext>,
|
||||||
|
sender: Box<dyn Sender<OutOfBand>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
mod connection;
|
mod connection;
|
||||||
|
pub(crate) use connection::{
|
||||||
|
BootstrapError, HandleBootstrapEncryptedKey, HandleEvmWalletCreate, HandleEvmWalletList,
|
||||||
|
HandleGrantCreate, HandleGrantDelete, HandleGrantList, HandleQueryVaultState,
|
||||||
|
};
|
||||||
|
pub use connection::{HandleUnsealEncryptedKey, HandleUnsealRequest, UnsealError};
|
||||||
|
|
||||||
impl UserAgentSession {
|
impl UserAgentSession {
|
||||||
pub(crate) fn new(props: UserAgentConnection) -> Self {
|
pub(crate) fn new(props: UserAgentConnection, sender: Box<dyn Sender<OutOfBand>>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
props,
|
props,
|
||||||
state: UserAgentStateMachine::new(DummyContext),
|
state: UserAgentStateMachine::new(DummyContext),
|
||||||
|
sender,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) async fn send_msg<Reply: kameo::Reply>(
|
pub fn new_test(db: crate::db::DatabasePool, actors: crate::actors::GlobalActors) -> Self {
|
||||||
&mut self,
|
struct DummySender;
|
||||||
msg: Response,
|
|
||||||
_ctx: &mut Context<Self, Reply>,
|
#[async_trait]
|
||||||
) -> Result<(), Error> {
|
impl Sender<OutOfBand> for DummySender {
|
||||||
self.props.transport.send(Ok(msg)).await.map_err(|_| {
|
async fn send(
|
||||||
error!(
|
&mut self,
|
||||||
actor = "useragent",
|
_item: OutOfBand,
|
||||||
reason = "channel closed",
|
) -> Result<(), arbiter_proto::transport::Error> {
|
||||||
"send.failed"
|
Ok(())
|
||||||
);
|
}
|
||||||
Error::ConnectionLost
|
}
|
||||||
})
|
|
||||||
|
Self::new(UserAgentConnection::new(db, actors), Box::new(DummySender))
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn expect_msg<Extractor, Msg, Reply>(
|
fn transition(&mut self, event: UserAgentEvents) -> Result<(), Error> {
|
||||||
&mut self,
|
|
||||||
extractor: Extractor,
|
|
||||||
ctx: &mut Context<Self, Reply>,
|
|
||||||
) -> Result<Msg, Error>
|
|
||||||
where
|
|
||||||
Extractor: FnOnce(Request) -> Option<Msg>,
|
|
||||||
Reply: kameo::Reply,
|
|
||||||
{
|
|
||||||
let msg = self.props.transport.recv().await.ok_or_else(|| {
|
|
||||||
error!(
|
|
||||||
actor = "useragent",
|
|
||||||
reason = "channel closed",
|
|
||||||
"recv.failed"
|
|
||||||
);
|
|
||||||
ctx.stop();
|
|
||||||
Error::ConnectionLost
|
|
||||||
})?;
|
|
||||||
|
|
||||||
extractor(msg).ok_or_else(|| {
|
|
||||||
error!(
|
|
||||||
actor = "useragent",
|
|
||||||
reason = "unexpected message",
|
|
||||||
"recv.failed"
|
|
||||||
);
|
|
||||||
ctx.stop();
|
|
||||||
Error::UnexpectedMessage
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn transition(&mut self, event: UserAgentEvents) -> Result<(), TransportResponseError> {
|
|
||||||
self.state.process_event(event).map_err(|e| {
|
self.state.process_event(event).map_err(|e| {
|
||||||
error!(?e, "State transition failed");
|
error!(?e, "State transition failed");
|
||||||
TransportResponseError::StateTransitionFailed
|
Error::State
|
||||||
})?;
|
})?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -95,52 +82,21 @@ impl UserAgentSession {
|
|||||||
|
|
||||||
#[messages]
|
#[messages]
|
||||||
impl UserAgentSession {
|
impl UserAgentSession {
|
||||||
// TODO: Think about refactoring it to state-machine based flow, as we already have one
|
|
||||||
#[message(ctx)]
|
#[message(ctx)]
|
||||||
pub async fn request_new_client_approval(
|
pub async fn request_new_client_approval(
|
||||||
&mut self,
|
&mut self,
|
||||||
client_pubkey: VerifyingKey,
|
client_pubkey: VerifyingKey,
|
||||||
mut cancel_flag: watch::Receiver<()>,
|
mut cancel_flag: watch::Receiver<()>,
|
||||||
ctx: &mut Context<Self, Result<bool, Error>>,
|
ctx: &mut Context<Self, Result<bool, ()>>,
|
||||||
) -> Result<bool, Error> {
|
) -> Result<bool, ()> {
|
||||||
self.send_msg(
|
todo!("Think about refactoring it to state-machine based flow, as we already have one")
|
||||||
Response::ClientConnectionRequest {
|
|
||||||
pubkey: client_pubkey,
|
|
||||||
},
|
|
||||||
ctx,
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
let extractor = |msg| {
|
|
||||||
if let Request::ClientConnectionResponse { approved } = msg {
|
|
||||||
Some(approved)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
tokio::select! {
|
|
||||||
_ = cancel_flag.changed() => {
|
|
||||||
info!(actor = "useragent", "client connection approval cancelled");
|
|
||||||
self.send_msg(
|
|
||||||
Response::ClientConnectionCancel,
|
|
||||||
ctx,
|
|
||||||
).await?;
|
|
||||||
Ok(false)
|
|
||||||
}
|
|
||||||
result = self.expect_msg(extractor, ctx) => {
|
|
||||||
let result = result?;
|
|
||||||
info!(actor = "useragent", "received client connection approval result: approved={}", result);
|
|
||||||
Ok(result)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Actor for UserAgentSession {
|
impl Actor for UserAgentSession {
|
||||||
type Args = Self;
|
type Args = Self;
|
||||||
|
|
||||||
type Error = TransportResponseError;
|
type Error = Error;
|
||||||
|
|
||||||
async fn on_start(
|
async fn on_start(
|
||||||
args: Self::Args,
|
args: Self::Args,
|
||||||
@@ -155,56 +111,8 @@ impl Actor for UserAgentSession {
|
|||||||
.await
|
.await
|
||||||
.map_err(|err| {
|
.map_err(|err| {
|
||||||
error!(?err, "Failed to register user agent connection with router");
|
error!(?err, "Failed to register user agent connection with router");
|
||||||
TransportResponseError::ConnectionRegistrationFailed
|
Error::internal("Failed to register user agent connection with router")
|
||||||
})?;
|
})?;
|
||||||
Ok(args)
|
Ok(args)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn next(
|
|
||||||
&mut self,
|
|
||||||
_actor_ref: kameo::prelude::WeakActorRef<Self>,
|
|
||||||
mailbox_rx: &mut kameo::prelude::MailboxReceiver<Self>,
|
|
||||||
) -> Option<kameo::mailbox::Signal<Self>> {
|
|
||||||
loop {
|
|
||||||
select! {
|
|
||||||
signal = mailbox_rx.recv() => {
|
|
||||||
return signal;
|
|
||||||
}
|
|
||||||
msg = self.props.transport.recv() => {
|
|
||||||
match msg {
|
|
||||||
Some(request) => {
|
|
||||||
match self.process_transport_inbound(request).await {
|
|
||||||
Ok(response) => {
|
|
||||||
if self.props.transport.send(Ok(response)).await.is_err() {
|
|
||||||
error!(actor = "useragent", reason = "channel closed", "send.failed");
|
|
||||||
return Some(kameo::mailbox::Signal::Stop);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(err) => {
|
|
||||||
let _ = self.props.transport.send(Err(err)).await;
|
|
||||||
return Some(kameo::mailbox::Signal::Stop);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None => {
|
|
||||||
info!(actor = "useragent", "transport.closed");
|
|
||||||
return Some(kameo::mailbox::Signal::Stop);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl UserAgentSession {
|
|
||||||
pub fn new_test(db: crate::db::DatabasePool, actors: crate::actors::GlobalActors) -> Self {
|
|
||||||
use arbiter_proto::transport::DummyTransport;
|
|
||||||
let transport: super::Transport = Box::new(DummyTransport::new());
|
|
||||||
let props = UserAgentConnection::new(db, actors, transport);
|
|
||||||
Self {
|
|
||||||
props,
|
|
||||||
state: UserAgentStateMachine::new(DummyContext),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,15 @@
|
|||||||
use std::sync::Mutex;
|
use std::sync::Mutex;
|
||||||
|
|
||||||
|
use alloy::primitives::Address;
|
||||||
use chacha20poly1305::{AeadInPlace, XChaCha20Poly1305, XNonce, aead::KeyInit};
|
use chacha20poly1305::{AeadInPlace, XChaCha20Poly1305, XNonce, aead::KeyInit};
|
||||||
use kameo::error::SendError;
|
use kameo::error::SendError;
|
||||||
|
use kameo::messages;
|
||||||
use tracing::{error, info};
|
use tracing::{error, info};
|
||||||
use x25519_dalek::{EphemeralSecret, PublicKey};
|
use x25519_dalek::{EphemeralSecret, PublicKey};
|
||||||
|
|
||||||
|
use crate::actors::keyholder::KeyHolderState;
|
||||||
|
use crate::actors::user_agent::session::Error;
|
||||||
|
use crate::evm::policies::{Grant, SpecificGrant};
|
||||||
use crate::safe_cell::SafeCell;
|
use crate::safe_cell::SafeCell;
|
||||||
use crate::{
|
use crate::{
|
||||||
actors::{
|
actors::{
|
||||||
@@ -13,7 +18,7 @@ use crate::{
|
|||||||
},
|
},
|
||||||
keyholder::{self, Bootstrap, TryUnseal},
|
keyholder::{self, Bootstrap, TryUnseal},
|
||||||
user_agent::{
|
user_agent::{
|
||||||
BootstrapError, Request, Response, TransportResponseError, UnsealError, VaultState,
|
OutOfBand,
|
||||||
session::{
|
session::{
|
||||||
UserAgentSession,
|
UserAgentSession,
|
||||||
state::{UnsealContext, UserAgentEvents, UserAgentStates},
|
state::{UnsealContext, UserAgentEvents, UserAgentStates},
|
||||||
@@ -24,55 +29,10 @@ use crate::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
impl UserAgentSession {
|
impl UserAgentSession {
|
||||||
pub async fn process_transport_inbound(&mut self, req: Request) -> Output {
|
fn take_unseal_secret(&mut self) -> Result<(EphemeralSecret, PublicKey), Error> {
|
||||||
match req {
|
|
||||||
Request::UnsealStart { client_pubkey } => {
|
|
||||||
self.handle_unseal_request(client_pubkey).await
|
|
||||||
}
|
|
||||||
Request::UnsealEncryptedKey {
|
|
||||||
nonce,
|
|
||||||
ciphertext,
|
|
||||||
associated_data,
|
|
||||||
} => {
|
|
||||||
self.handle_unseal_encrypted_key(nonce, ciphertext, associated_data)
|
|
||||||
.await
|
|
||||||
}
|
|
||||||
Request::BootstrapEncryptedKey {
|
|
||||||
nonce,
|
|
||||||
ciphertext,
|
|
||||||
associated_data,
|
|
||||||
} => {
|
|
||||||
self.handle_bootstrap_encrypted_key(nonce, ciphertext, associated_data)
|
|
||||||
.await
|
|
||||||
}
|
|
||||||
Request::ListGrants => self.handle_grant_list().await,
|
|
||||||
Request::QueryVaultState => self.handle_query_vault_state().await,
|
|
||||||
Request::EvmWalletCreate => self.handle_evm_wallet_create().await,
|
|
||||||
Request::EvmWalletList => self.handle_evm_wallet_list().await,
|
|
||||||
Request::AuthChallengeRequest { .. }
|
|
||||||
| Request::AuthChallengeSolution { .. }
|
|
||||||
| Request::ClientConnectionResponse { .. } => {
|
|
||||||
Err(TransportResponseError::UnexpectedRequestPayload)
|
|
||||||
}
|
|
||||||
Request::EvmGrantCreate {
|
|
||||||
client_id,
|
|
||||||
shared,
|
|
||||||
specific,
|
|
||||||
} => self.handle_grant_create(client_id, shared, specific).await,
|
|
||||||
Request::EvmGrantDelete { grant_id } => self.handle_grant_delete(grant_id).await,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type Output = Result<Response, TransportResponseError>;
|
|
||||||
|
|
||||||
impl UserAgentSession {
|
|
||||||
fn take_unseal_secret(
|
|
||||||
&mut self,
|
|
||||||
) -> Result<(EphemeralSecret, PublicKey), TransportResponseError> {
|
|
||||||
let UserAgentStates::WaitingForUnsealKey(unseal_context) = self.state.state() else {
|
let UserAgentStates::WaitingForUnsealKey(unseal_context) = self.state.state() else {
|
||||||
error!("Received encrypted key in invalid state");
|
error!("Received encrypted key in invalid state");
|
||||||
return Err(TransportResponseError::InvalidStateForUnsealEncryptedKey);
|
return Err(Error::internal("Invalid state for unseal encrypted key"));
|
||||||
};
|
};
|
||||||
|
|
||||||
let ephemeral_secret = {
|
let ephemeral_secret = {
|
||||||
@@ -87,7 +47,7 @@ impl UserAgentSession {
|
|||||||
None => {
|
None => {
|
||||||
drop(secret_lock);
|
drop(secret_lock);
|
||||||
error!("Ephemeral secret already taken");
|
error!("Ephemeral secret already taken");
|
||||||
return Err(TransportResponseError::StateTransitionFailed);
|
return Err(Error::internal("Ephemeral secret already taken"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -121,8 +81,38 @@ impl UserAgentSession {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async fn handle_unseal_request(&mut self, client_pubkey: x25519_dalek::PublicKey) -> Output {
|
pub struct UnsealStartResponse {
|
||||||
|
pub server_pubkey: PublicKey,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Error)]
|
||||||
|
pub enum UnsealError {
|
||||||
|
#[error("Invalid key provided for unsealing")]
|
||||||
|
InvalidKey,
|
||||||
|
#[error("Internal error during unsealing process")]
|
||||||
|
General(#[from] super::Error),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Error)]
|
||||||
|
pub enum BootstrapError {
|
||||||
|
#[error("Invalid key provided for bootstrapping")]
|
||||||
|
InvalidKey,
|
||||||
|
#[error("Vault is already bootstrapped")]
|
||||||
|
AlreadyBootstrapped,
|
||||||
|
|
||||||
|
#[error("Internal error during bootstrapping process")]
|
||||||
|
General(#[from] super::Error),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[messages]
|
||||||
|
impl UserAgentSession {
|
||||||
|
#[message]
|
||||||
|
pub async fn handle_unseal_request(
|
||||||
|
&mut self,
|
||||||
|
client_pubkey: x25519_dalek::PublicKey,
|
||||||
|
) -> Result<UnsealStartResponse, Error> {
|
||||||
let secret = EphemeralSecret::random();
|
let secret = EphemeralSecret::random();
|
||||||
let public_key = PublicKey::from(&secret);
|
let public_key = PublicKey::from(&secret);
|
||||||
|
|
||||||
@@ -131,24 +121,27 @@ impl UserAgentSession {
|
|||||||
client_public_key: client_pubkey,
|
client_public_key: client_pubkey,
|
||||||
}))?;
|
}))?;
|
||||||
|
|
||||||
Ok(Response::UnsealStartResponse {
|
Ok(UnsealStartResponse {
|
||||||
server_pubkey: public_key,
|
server_pubkey: public_key,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn handle_unseal_encrypted_key(
|
#[message]
|
||||||
|
pub async fn handle_unseal_encrypted_key(
|
||||||
&mut self,
|
&mut self,
|
||||||
nonce: Vec<u8>,
|
nonce: Vec<u8>,
|
||||||
ciphertext: Vec<u8>,
|
ciphertext: Vec<u8>,
|
||||||
associated_data: Vec<u8>,
|
associated_data: Vec<u8>,
|
||||||
) -> Output {
|
) -> Result<(), UnsealError> {
|
||||||
let (ephemeral_secret, client_public_key) = match self.take_unseal_secret() {
|
let (ephemeral_secret, client_public_key) = match self.take_unseal_secret() {
|
||||||
Ok(values) => values,
|
Ok(values) => values,
|
||||||
Err(TransportResponseError::StateTransitionFailed) => {
|
Err(Error::State) => {
|
||||||
self.transition(UserAgentEvents::ReceivedInvalidKey)?;
|
self.transition(UserAgentEvents::ReceivedInvalidKey)?;
|
||||||
return Ok(Response::UnsealResult(Err(UnsealError::InvalidKey)));
|
return Err(UnsealError::InvalidKey);
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
return Err(Error::internal("Failed to take unseal secret").into());
|
||||||
}
|
}
|
||||||
Err(err) => return Err(err),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let seal_key_buffer = match Self::decrypt_client_key_material(
|
let seal_key_buffer = match Self::decrypt_client_key_material(
|
||||||
@@ -161,7 +154,7 @@ impl UserAgentSession {
|
|||||||
Ok(buffer) => buffer,
|
Ok(buffer) => buffer,
|
||||||
Err(()) => {
|
Err(()) => {
|
||||||
self.transition(UserAgentEvents::ReceivedInvalidKey)?;
|
self.transition(UserAgentEvents::ReceivedInvalidKey)?;
|
||||||
return Ok(Response::UnsealResult(Err(UnsealError::InvalidKey)));
|
return Err(UnsealError::InvalidKey);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -177,38 +170,39 @@ impl UserAgentSession {
|
|||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
info!("Successfully unsealed key with client-provided key");
|
info!("Successfully unsealed key with client-provided key");
|
||||||
self.transition(UserAgentEvents::ReceivedValidKey)?;
|
self.transition(UserAgentEvents::ReceivedValidKey)?;
|
||||||
Ok(Response::UnsealResult(Ok(())))
|
Ok(())
|
||||||
}
|
}
|
||||||
Err(SendError::HandlerError(keyholder::Error::InvalidKey)) => {
|
Err(SendError::HandlerError(keyholder::Error::InvalidKey)) => {
|
||||||
self.transition(UserAgentEvents::ReceivedInvalidKey)?;
|
self.transition(UserAgentEvents::ReceivedInvalidKey)?;
|
||||||
Ok(Response::UnsealResult(Err(UnsealError::InvalidKey)))
|
Err(UnsealError::InvalidKey)
|
||||||
}
|
}
|
||||||
Err(SendError::HandlerError(err)) => {
|
Err(SendError::HandlerError(err)) => {
|
||||||
error!(?err, "Keyholder failed to unseal key");
|
error!(?err, "Keyholder failed to unseal key");
|
||||||
self.transition(UserAgentEvents::ReceivedInvalidKey)?;
|
self.transition(UserAgentEvents::ReceivedInvalidKey)?;
|
||||||
Ok(Response::UnsealResult(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 keyholder");
|
||||||
self.transition(UserAgentEvents::ReceivedInvalidKey)?;
|
self.transition(UserAgentEvents::ReceivedInvalidKey)?;
|
||||||
Err(TransportResponseError::KeyHolderActorUnreachable)
|
Err(Error::internal("Vault actor error").into())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn handle_bootstrap_encrypted_key(
|
#[message]
|
||||||
|
pub(crate) async fn handle_bootstrap_encrypted_key(
|
||||||
&mut self,
|
&mut self,
|
||||||
nonce: Vec<u8>,
|
nonce: Vec<u8>,
|
||||||
ciphertext: Vec<u8>,
|
ciphertext: Vec<u8>,
|
||||||
associated_data: Vec<u8>,
|
associated_data: Vec<u8>,
|
||||||
) -> Output {
|
) -> Result<(), BootstrapError> {
|
||||||
let (ephemeral_secret, client_public_key) = match self.take_unseal_secret() {
|
let (ephemeral_secret, client_public_key) = match self.take_unseal_secret() {
|
||||||
Ok(values) => values,
|
Ok(values) => values,
|
||||||
Err(TransportResponseError::StateTransitionFailed) => {
|
Err(Error::State) => {
|
||||||
self.transition(UserAgentEvents::ReceivedInvalidKey)?;
|
self.transition(UserAgentEvents::ReceivedInvalidKey)?;
|
||||||
return Ok(Response::BootstrapResult(Err(BootstrapError::InvalidKey)));
|
return Err(BootstrapError::InvalidKey);
|
||||||
}
|
}
|
||||||
Err(err) => return Err(err),
|
Err(err) => return Err(err.into()),
|
||||||
};
|
};
|
||||||
|
|
||||||
let seal_key_buffer = match Self::decrypt_client_key_material(
|
let seal_key_buffer = match Self::decrypt_client_key_material(
|
||||||
@@ -221,7 +215,7 @@ impl UserAgentSession {
|
|||||||
Ok(buffer) => buffer,
|
Ok(buffer) => buffer,
|
||||||
Err(()) => {
|
Err(()) => {
|
||||||
self.transition(UserAgentEvents::ReceivedInvalidKey)?;
|
self.transition(UserAgentEvents::ReceivedInvalidKey)?;
|
||||||
return Ok(Response::BootstrapResult(Err(BootstrapError::InvalidKey)));
|
return Err(BootstrapError::InvalidKey);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -237,87 +231,94 @@ impl UserAgentSession {
|
|||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
info!("Successfully bootstrapped vault with client-provided key");
|
info!("Successfully bootstrapped vault with client-provided key");
|
||||||
self.transition(UserAgentEvents::ReceivedValidKey)?;
|
self.transition(UserAgentEvents::ReceivedValidKey)?;
|
||||||
Ok(Response::BootstrapResult(Ok(())))
|
Ok(())
|
||||||
}
|
}
|
||||||
Err(SendError::HandlerError(keyholder::Error::AlreadyBootstrapped)) => {
|
Err(SendError::HandlerError(keyholder::Error::AlreadyBootstrapped)) => {
|
||||||
self.transition(UserAgentEvents::ReceivedInvalidKey)?;
|
self.transition(UserAgentEvents::ReceivedInvalidKey)?;
|
||||||
Ok(Response::BootstrapResult(Err(
|
Err(BootstrapError::AlreadyBootstrapped)
|
||||||
BootstrapError::AlreadyBootstrapped,
|
|
||||||
)))
|
|
||||||
}
|
}
|
||||||
Err(SendError::HandlerError(err)) => {
|
Err(SendError::HandlerError(err)) => {
|
||||||
error!(?err, "Keyholder failed to bootstrap vault");
|
error!(?err, "Keyholder failed to bootstrap vault");
|
||||||
self.transition(UserAgentEvents::ReceivedInvalidKey)?;
|
self.transition(UserAgentEvents::ReceivedInvalidKey)?;
|
||||||
Ok(Response::BootstrapResult(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 keyholder");
|
||||||
self.transition(UserAgentEvents::ReceivedInvalidKey)?;
|
self.transition(UserAgentEvents::ReceivedInvalidKey)?;
|
||||||
Err(TransportResponseError::KeyHolderActorUnreachable)
|
Err(BootstrapError::General(Error::internal(
|
||||||
|
"Vault actor error",
|
||||||
|
)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[messages]
|
||||||
impl UserAgentSession {
|
impl UserAgentSession {
|
||||||
async fn handle_query_vault_state(&mut self) -> Output {
|
#[message]
|
||||||
use crate::actors::keyholder::{GetState, StateDiscriminants};
|
pub(crate) async fn handle_query_vault_state(&mut self) -> Result<KeyHolderState, Error> {
|
||||||
|
use crate::actors::keyholder::GetState;
|
||||||
|
|
||||||
let vault_state = match self.props.actors.key_holder.ask(GetState {}).await {
|
let vault_state = match self.props.actors.key_holder.ask(GetState {}).await {
|
||||||
Ok(StateDiscriminants::Unbootstrapped) => VaultState::Unbootstrapped,
|
Ok(state) => state,
|
||||||
Ok(StateDiscriminants::Sealed) => VaultState::Sealed,
|
|
||||||
Ok(StateDiscriminants::Unsealed) => VaultState::Unsealed,
|
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
error!(?err, actor = "useragent", "keyholder.query.failed");
|
error!(?err, actor = "useragent", "keyholder.query.failed");
|
||||||
return Err(TransportResponseError::KeyHolderActorUnreachable);
|
return Err(Error::internal("Vault is in broken state").into());
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(Response::VaultState(vault_state))
|
Ok(vault_state)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[messages]
|
||||||
impl UserAgentSession {
|
impl UserAgentSession {
|
||||||
async fn handle_evm_wallet_create(&mut self) -> Output {
|
#[message]
|
||||||
let result = match self.props.actors.evm.ask(Generate {}).await {
|
pub(crate) async fn handle_evm_wallet_create(&mut self) -> Result<Address, Error> {
|
||||||
Ok(_address) => return Ok(Response::EvmWalletCreate(Ok(()))),
|
match self.props.actors.evm.ask(Generate {}).await {
|
||||||
Err(SendError::HandlerError(err)) => Err(err),
|
Ok(address) => return Ok(address),
|
||||||
|
Err(SendError::HandlerError(err)) => Err(Error::internal(format!(
|
||||||
|
"EVM wallet generation failed: {err}"
|
||||||
|
))),
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
error!(?err, "EVM actor unreachable during wallet create");
|
error!(?err, "EVM actor unreachable during wallet create");
|
||||||
return Err(TransportResponseError::KeyHolderActorUnreachable);
|
return Err(Error::internal("EVM actor unreachable"));
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
Ok(Response::EvmWalletCreate(result))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn handle_evm_wallet_list(&mut self) -> Output {
|
#[message]
|
||||||
|
pub(crate) async fn handle_evm_wallet_list(&mut self) -> Result<Vec<Address>, Error> {
|
||||||
match self.props.actors.evm.ask(ListWallets {}).await {
|
match self.props.actors.evm.ask(ListWallets {}).await {
|
||||||
Ok(wallets) => Ok(Response::EvmWalletList(wallets)),
|
Ok(wallets) => Ok(wallets),
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
error!(?err, "EVM wallet list failed");
|
error!(?err, "EVM wallet list failed");
|
||||||
Err(TransportResponseError::KeyHolderActorUnreachable)
|
Err(Error::internal("Failed to list EVM wallets"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[messages]
|
||||||
impl UserAgentSession {
|
impl UserAgentSession {
|
||||||
async fn handle_grant_list(&mut self) -> Output {
|
#[message]
|
||||||
|
pub(crate) async fn handle_grant_list(&mut self) -> Result<Vec<Grant<SpecificGrant>>, Error> {
|
||||||
match self.props.actors.evm.ask(UseragentListGrants {}).await {
|
match self.props.actors.evm.ask(UseragentListGrants {}).await {
|
||||||
Ok(grants) => Ok(Response::ListGrants(grants)),
|
Ok(grants) => Ok(grants),
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
error!(?err, "EVM grant list failed");
|
error!(?err, "EVM grant list failed");
|
||||||
Err(TransportResponseError::KeyHolderActorUnreachable)
|
Err(Error::internal("Failed to list EVM grants"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn handle_grant_create(
|
#[message]
|
||||||
|
pub(crate) async fn handle_grant_create(
|
||||||
&mut self,
|
&mut self,
|
||||||
client_id: i32,
|
client_id: i32,
|
||||||
basic: crate::evm::policies::SharedGrantSettings,
|
basic: crate::evm::policies::SharedGrantSettings,
|
||||||
grant: crate::evm::policies::SpecificGrant,
|
grant: crate::evm::policies::SpecificGrant,
|
||||||
) -> Output {
|
) -> Result<i32, Error> {
|
||||||
match self
|
match self
|
||||||
.props
|
.props
|
||||||
.actors
|
.actors
|
||||||
@@ -329,15 +330,16 @@ impl UserAgentSession {
|
|||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
Ok(grant_id) => Ok(Response::EvmGrantCreate(Ok(grant_id))),
|
Ok(grant_id) => Ok(grant_id),
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
error!(?err, "EVM grant create failed");
|
error!(?err, "EVM grant create failed");
|
||||||
Err(TransportResponseError::KeyHolderActorUnreachable)
|
Err(Error::internal("Failed to create EVM grant"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn handle_grant_delete(&mut self, grant_id: i32) -> Output {
|
#[message]
|
||||||
|
pub(crate) async fn handle_grant_delete(&mut self, grant_id: i32) -> Result<(), Error> {
|
||||||
match self
|
match self
|
||||||
.props
|
.props
|
||||||
.actors
|
.actors
|
||||||
@@ -345,10 +347,10 @@ impl UserAgentSession {
|
|||||||
.ask(UseragentDeleteGrant { grant_id })
|
.ask(UseragentDeleteGrant { grant_id })
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
Ok(()) => Ok(Response::EvmGrantDelete(Ok(()))),
|
Ok(()) => Ok(()),
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
error!(?err, "EVM grant delete failed");
|
error!(?err, "EVM grant delete failed");
|
||||||
Err(TransportResponseError::KeyHolderActorUnreachable)
|
Err(Error::internal("Failed to delete EVM grant"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,6 +44,14 @@ pub enum DatabaseSetupError {
|
|||||||
Pool(#[from] PoolInitError),
|
Pool(#[from] PoolInitError),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Error, Debug)]
|
||||||
|
pub enum DatabaseError {
|
||||||
|
#[error("Database connection error")]
|
||||||
|
Pool(#[from] PoolError),
|
||||||
|
#[error("Database query error")]
|
||||||
|
Connection(#[from] diesel::result::Error),
|
||||||
|
}
|
||||||
|
|
||||||
#[tracing::instrument(level = "info")]
|
#[tracing::instrument(level = "info")]
|
||||||
fn database_path() -> Result<std::path::PathBuf, DatabaseSetupError> {
|
fn database_path() -> Result<std::path::PathBuf, DatabaseSetupError> {
|
||||||
let arbiter_home = arbiter_proto::home_path().map_err(DatabaseSetupError::HomeDir)?;
|
let arbiter_home = arbiter_proto::home_path().map_err(DatabaseSetupError::HomeDir)?;
|
||||||
|
|||||||
@@ -1,137 +1,138 @@
|
|||||||
use arbiter_proto::{
|
use arbiter_proto::{
|
||||||
proto::client::{
|
proto::client::{
|
||||||
AuthChallenge as ProtoAuthChallenge,
|
ClientRequest, ClientResponse, VaultState as ProtoVaultState,
|
||||||
AuthChallengeRequest as ProtoAuthChallengeRequest,
|
|
||||||
AuthChallengeSolution as ProtoAuthChallengeSolution, AuthOk as ProtoAuthOk,
|
|
||||||
ClientConnectError, ClientRequest, ClientResponse,
|
|
||||||
client_connect_error::Code as ProtoClientConnectErrorCode,
|
|
||||||
client_request::Payload as ClientRequestPayload,
|
client_request::Payload as ClientRequestPayload,
|
||||||
client_response::Payload as ClientResponsePayload,
|
client_response::Payload as ClientResponsePayload,
|
||||||
},
|
},
|
||||||
transport::{Bi, Error as TransportError},
|
transport::{Receiver, Sender, grpc::GrpcBi},
|
||||||
};
|
};
|
||||||
use async_trait::async_trait;
|
use kameo::{
|
||||||
use futures::StreamExt as _;
|
actor::{ActorRef, Spawn as _},
|
||||||
use tokio::sync::mpsc;
|
error::SendError,
|
||||||
use tonic::{Status, Streaming};
|
};
|
||||||
|
use tonic::Status;
|
||||||
|
use tracing::{info, warn};
|
||||||
|
|
||||||
use crate::actors::client::{
|
use crate::{
|
||||||
self, ClientError, ConnectErrorCode, Request as DomainRequest, Response as DomainResponse,
|
actors::{
|
||||||
|
client::{
|
||||||
|
self, ClientConnection,
|
||||||
|
session::{ClientSession, Error, HandleQueryVaultState},
|
||||||
|
},
|
||||||
|
keyholder::KeyHolderState,
|
||||||
|
},
|
||||||
|
grpc::request_tracker::RequestTracker,
|
||||||
|
utils::defer,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct GrpcTransport {
|
mod auth;
|
||||||
sender: mpsc::Sender<Result<ClientResponse, Status>>,
|
|
||||||
receiver: Streaming<ClientRequest>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl GrpcTransport {
|
async fn dispatch_loop(
|
||||||
pub fn new(
|
mut bi: GrpcBi<ClientRequest, ClientResponse>,
|
||||||
sender: mpsc::Sender<Result<ClientResponse, Status>>,
|
actor: ActorRef<ClientSession>,
|
||||||
receiver: Streaming<ClientRequest>,
|
mut request_tracker: RequestTracker,
|
||||||
) -> Self {
|
) {
|
||||||
Self { sender, receiver }
|
loop {
|
||||||
}
|
let Some(conn) = bi.recv().await else {
|
||||||
|
return;
|
||||||
fn request_to_domain(request: ClientRequest) -> Result<DomainRequest, Status> {
|
|
||||||
match request.payload {
|
|
||||||
Some(ClientRequestPayload::AuthChallengeRequest(ProtoAuthChallengeRequest {
|
|
||||||
pubkey,
|
|
||||||
})) => Ok(DomainRequest::AuthChallengeRequest { pubkey }),
|
|
||||||
Some(ClientRequestPayload::AuthChallengeSolution(
|
|
||||||
ProtoAuthChallengeSolution { signature },
|
|
||||||
)) => Ok(DomainRequest::AuthChallengeSolution { signature }),
|
|
||||||
None => Err(Status::invalid_argument("Missing client request payload")),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn response_to_proto(response: DomainResponse) -> ClientResponse {
|
|
||||||
let payload = match response {
|
|
||||||
DomainResponse::AuthChallenge { pubkey, nonce } => {
|
|
||||||
ClientResponsePayload::AuthChallenge(ProtoAuthChallenge { pubkey, nonce })
|
|
||||||
}
|
|
||||||
DomainResponse::AuthOk => ClientResponsePayload::AuthOk(ProtoAuthOk {}),
|
|
||||||
DomainResponse::ClientConnectError { code } => {
|
|
||||||
ClientResponsePayload::ClientConnectError(ClientConnectError {
|
|
||||||
code: match code {
|
|
||||||
ConnectErrorCode::Unknown => ProtoClientConnectErrorCode::Unknown,
|
|
||||||
ConnectErrorCode::ApprovalDenied => {
|
|
||||||
ProtoClientConnectErrorCode::ApprovalDenied
|
|
||||||
}
|
|
||||||
ConnectErrorCode::NoUserAgentsOnline => {
|
|
||||||
ProtoClientConnectErrorCode::NoUserAgentsOnline
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.into(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
ClientResponse {
|
if dispatch_conn_message(&mut bi, &actor, &mut request_tracker, conn)
|
||||||
payload: Some(payload),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn error_to_status(value: ClientError) -> Status {
|
|
||||||
match value {
|
|
||||||
ClientError::MissingRequestPayload | ClientError::UnexpectedRequestPayload => {
|
|
||||||
Status::invalid_argument("Expected message with payload")
|
|
||||||
}
|
|
||||||
ClientError::StateTransitionFailed => Status::internal("State machine error"),
|
|
||||||
ClientError::Auth(ref err) => auth_error_status(err),
|
|
||||||
ClientError::ConnectionRegistrationFailed => {
|
|
||||||
Status::internal("Connection registration failed")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[async_trait]
|
|
||||||
impl Bi<DomainRequest, Result<DomainResponse, ClientError>> for GrpcTransport {
|
|
||||||
async fn send(&mut self, item: Result<DomainResponse, ClientError>) -> Result<(), TransportError> {
|
|
||||||
let outbound = match item {
|
|
||||||
Ok(message) => Ok(Self::response_to_proto(message)),
|
|
||||||
Err(err) => Err(Self::error_to_status(err)),
|
|
||||||
};
|
|
||||||
|
|
||||||
self.sender
|
|
||||||
.send(outbound)
|
|
||||||
.await
|
.await
|
||||||
.map_err(|_| TransportError::ChannelClosed)
|
.is_err()
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async fn recv(&mut self) -> Option<DomainRequest> {
|
async fn dispatch_conn_message(
|
||||||
match self.receiver.next().await {
|
bi: &mut GrpcBi<ClientRequest, ClientResponse>,
|
||||||
Some(Ok(item)) => match Self::request_to_domain(item) {
|
actor: &ActorRef<ClientSession>,
|
||||||
Ok(request) => Some(request),
|
request_tracker: &mut RequestTracker,
|
||||||
Err(status) => {
|
conn: Result<ClientRequest, Status>,
|
||||||
let _ = self.sender.send(Err(status)).await;
|
) -> Result<(), ()> {
|
||||||
None
|
let conn = match conn {
|
||||||
|
Ok(conn) => conn,
|
||||||
|
Err(err) => {
|
||||||
|
warn!(error = ?err, "Failed to receive client request");
|
||||||
|
return Err(());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let request_id = match request_tracker.request(conn.request_id) {
|
||||||
|
Ok(request_id) => request_id,
|
||||||
|
Err(err) => {
|
||||||
|
let _ = bi.send(Err(err)).await;
|
||||||
|
return Err(());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let Some(payload) = conn.payload else {
|
||||||
|
let _ = bi
|
||||||
|
.send(Err(Status::invalid_argument(
|
||||||
|
"Missing client request payload",
|
||||||
|
)))
|
||||||
|
.await;
|
||||||
|
return Err(());
|
||||||
|
};
|
||||||
|
|
||||||
|
let payload = match payload {
|
||||||
|
ClientRequestPayload::QueryVaultState(_) => ClientResponsePayload::VaultState(
|
||||||
|
match actor.ask(HandleQueryVaultState {}).await {
|
||||||
|
Ok(KeyHolderState::Unbootstrapped) => ProtoVaultState::Unbootstrapped,
|
||||||
|
Ok(KeyHolderState::Sealed) => ProtoVaultState::Sealed,
|
||||||
|
Ok(KeyHolderState::Unsealed) => ProtoVaultState::Unsealed,
|
||||||
|
Err(SendError::HandlerError(Error::Internal)) => ProtoVaultState::Error,
|
||||||
|
Err(err) => {
|
||||||
|
warn!(error = ?err, "Failed to query vault state");
|
||||||
|
ProtoVaultState::Error
|
||||||
}
|
}
|
||||||
},
|
|
||||||
Some(Err(error)) => {
|
|
||||||
tracing::error!(error = ?error, "grpc client recv failed; closing stream");
|
|
||||||
None
|
|
||||||
}
|
}
|
||||||
None => None,
|
.into(),
|
||||||
|
),
|
||||||
|
payload => {
|
||||||
|
warn!(?payload, "Unsupported post-auth client request");
|
||||||
|
let _ = bi
|
||||||
|
.send(Err(Status::invalid_argument("Unsupported client request")))
|
||||||
|
.await;
|
||||||
|
return Err(());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
bi.send(Ok(ClientResponse {
|
||||||
|
request_id: Some(request_id),
|
||||||
|
payload: Some(payload),
|
||||||
|
}))
|
||||||
|
.await
|
||||||
|
.map_err(|_| ())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn start(conn: ClientConnection, mut bi: GrpcBi<ClientRequest, ClientResponse>) {
|
||||||
|
let mut conn = conn;
|
||||||
|
let mut request_tracker = RequestTracker::default();
|
||||||
|
let mut response_id = None;
|
||||||
|
|
||||||
|
match auth::start(&mut conn, &mut bi, &mut request_tracker, &mut response_id).await {
|
||||||
|
Ok(_) => {
|
||||||
|
let actor =
|
||||||
|
client::session::ClientSession::spawn(client::session::ClientSession::new(conn));
|
||||||
|
let actor_for_cleanup = actor.clone();
|
||||||
|
let _ = defer(move || {
|
||||||
|
actor_for_cleanup.kill();
|
||||||
|
});
|
||||||
|
|
||||||
|
info!("Client authenticated successfully");
|
||||||
|
dispatch_loop(bi, actor, request_tracker).await;
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
let mut transport = auth::AuthTransportAdapter::new(
|
||||||
|
&mut bi,
|
||||||
|
&mut request_tracker,
|
||||||
|
&mut response_id,
|
||||||
|
);
|
||||||
|
let _ = transport.send(Err(e.clone())).await;
|
||||||
|
warn!(error = ?e, "Authentication failed");
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn auth_error_status(value: &client::auth::Error) -> Status {
|
|
||||||
use client::auth::Error;
|
|
||||||
|
|
||||||
match value {
|
|
||||||
Error::UnexpectedMessagePayload | Error::InvalidClientPubkeyLength => {
|
|
||||||
Status::invalid_argument(value.to_string())
|
|
||||||
}
|
|
||||||
Error::InvalidAuthPubkeyEncoding => {
|
|
||||||
Status::invalid_argument("Failed to convert pubkey to VerifyingKey")
|
|
||||||
}
|
|
||||||
Error::InvalidChallengeSolution => Status::unauthenticated(value.to_string()),
|
|
||||||
Error::ApproveError(_) => Status::permission_denied(value.to_string()),
|
|
||||||
Error::Transport => Status::internal("Transport error"),
|
|
||||||
Error::DatabasePoolUnavailable => Status::internal("Database pool error"),
|
|
||||||
Error::DatabaseOperationFailed => Status::internal("Database error"),
|
|
||||||
Error::InternalError => Status::internal("Internal error"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
173
server/crates/arbiter-server/src/grpc/client/auth.rs
Normal file
173
server/crates/arbiter-server/src/grpc/client/auth.rs
Normal file
@@ -0,0 +1,173 @@
|
|||||||
|
use arbiter_proto::{
|
||||||
|
proto::client::{
|
||||||
|
AuthChallenge as ProtoAuthChallenge, AuthChallengeRequest as ProtoAuthChallengeRequest,
|
||||||
|
AuthChallengeSolution as ProtoAuthChallengeSolution, AuthResult as ProtoAuthResult,
|
||||||
|
ClientRequest, ClientResponse, client_request::Payload as ClientRequestPayload,
|
||||||
|
client_response::Payload as ClientResponsePayload,
|
||||||
|
},
|
||||||
|
transport::{Bi, Error as TransportError, Receiver, Sender, grpc::GrpcBi},
|
||||||
|
};
|
||||||
|
use async_trait::async_trait;
|
||||||
|
use tonic::Status;
|
||||||
|
use tracing::warn;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
actors::client::{self, ClientConnection, auth},
|
||||||
|
grpc::request_tracker::RequestTracker,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub struct AuthTransportAdapter<'a> {
|
||||||
|
bi: &'a mut GrpcBi<ClientRequest, ClientResponse>,
|
||||||
|
request_tracker: &'a mut RequestTracker,
|
||||||
|
response_id: &'a mut Option<i32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> AuthTransportAdapter<'a> {
|
||||||
|
pub fn new(
|
||||||
|
bi: &'a mut GrpcBi<ClientRequest, ClientResponse>,
|
||||||
|
request_tracker: &'a mut RequestTracker,
|
||||||
|
response_id: &'a mut Option<i32>,
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
bi,
|
||||||
|
request_tracker,
|
||||||
|
response_id,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn response_to_proto(response: auth::Outbound) -> ClientResponsePayload {
|
||||||
|
match response {
|
||||||
|
auth::Outbound::AuthChallenge { pubkey, nonce } => {
|
||||||
|
ClientResponsePayload::AuthChallenge(ProtoAuthChallenge {
|
||||||
|
pubkey: pubkey.to_bytes().to_vec(),
|
||||||
|
nonce,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
auth::Outbound::AuthSuccess => {
|
||||||
|
ClientResponsePayload::AuthResult(ProtoAuthResult::Success.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn error_to_proto(error: auth::Error) -> ClientResponsePayload {
|
||||||
|
ClientResponsePayload::AuthResult(
|
||||||
|
match error {
|
||||||
|
auth::Error::InvalidChallengeSolution => ProtoAuthResult::InvalidSignature,
|
||||||
|
auth::Error::ApproveError(auth::ApproveError::Denied) => {
|
||||||
|
ProtoAuthResult::ApprovalDenied
|
||||||
|
}
|
||||||
|
auth::Error::ApproveError(auth::ApproveError::Upstream(
|
||||||
|
crate::actors::router::ApprovalError::NoUserAgentsConnected,
|
||||||
|
)) => ProtoAuthResult::NoUserAgentsOnline,
|
||||||
|
auth::Error::ApproveError(auth::ApproveError::Internal)
|
||||||
|
| auth::Error::DatabasePoolUnavailable
|
||||||
|
| auth::Error::DatabaseOperationFailed
|
||||||
|
| auth::Error::Transport => ProtoAuthResult::Internal,
|
||||||
|
}
|
||||||
|
.into(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn send_client_response(
|
||||||
|
&mut self,
|
||||||
|
payload: ClientResponsePayload,
|
||||||
|
) -> Result<(), TransportError> {
|
||||||
|
let request_id = self.response_id.take();
|
||||||
|
|
||||||
|
self.bi
|
||||||
|
.send(Ok(ClientResponse {
|
||||||
|
request_id,
|
||||||
|
payload: Some(payload),
|
||||||
|
}))
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn send_auth_result(&mut self, result: ProtoAuthResult) -> Result<(), TransportError> {
|
||||||
|
self.send_client_response(ClientResponsePayload::AuthResult(result.into()))
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl Sender<Result<auth::Outbound, auth::Error>> for AuthTransportAdapter<'_> {
|
||||||
|
async fn send(
|
||||||
|
&mut self,
|
||||||
|
item: Result<auth::Outbound, auth::Error>,
|
||||||
|
) -> Result<(), TransportError> {
|
||||||
|
let payload = match item {
|
||||||
|
Ok(message) => AuthTransportAdapter::response_to_proto(message),
|
||||||
|
Err(err) => AuthTransportAdapter::error_to_proto(err),
|
||||||
|
};
|
||||||
|
|
||||||
|
self.send_client_response(payload).await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl Receiver<auth::Inbound> for AuthTransportAdapter<'_> {
|
||||||
|
async fn recv(&mut self) -> Option<auth::Inbound> {
|
||||||
|
let request = match self.bi.recv().await? {
|
||||||
|
Ok(request) => request,
|
||||||
|
Err(error) => {
|
||||||
|
warn!(error = ?error, "grpc client recv failed; closing stream");
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let request_id = match self.request_tracker.request(request.request_id) {
|
||||||
|
Ok(request_id) => request_id,
|
||||||
|
Err(error) => {
|
||||||
|
let _ = self.bi.send(Err(error)).await;
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
*self.response_id = Some(request_id);
|
||||||
|
|
||||||
|
let payload = request.payload?;
|
||||||
|
|
||||||
|
match payload {
|
||||||
|
ClientRequestPayload::AuthChallengeRequest(ProtoAuthChallengeRequest { pubkey }) => {
|
||||||
|
let Ok(pubkey) = <[u8; 32]>::try_from(pubkey) else {
|
||||||
|
let _ = self.send_auth_result(ProtoAuthResult::InvalidKey).await;
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
let Ok(pubkey) = ed25519_dalek::VerifyingKey::from_bytes(&pubkey) else {
|
||||||
|
let _ = self.send_auth_result(ProtoAuthResult::InvalidKey).await;
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
Some(auth::Inbound::AuthChallengeRequest { pubkey })
|
||||||
|
}
|
||||||
|
ClientRequestPayload::AuthChallengeSolution(ProtoAuthChallengeSolution {
|
||||||
|
signature,
|
||||||
|
}) => {
|
||||||
|
let Ok(signature) = ed25519_dalek::Signature::try_from(signature.as_slice()) else {
|
||||||
|
let _ = self
|
||||||
|
.send_auth_result(ProtoAuthResult::InvalidSignature)
|
||||||
|
.await;
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
Some(auth::Inbound::AuthChallengeSolution { signature })
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
let _ = self
|
||||||
|
.bi
|
||||||
|
.send(Err(Status::invalid_argument("Unsupported client auth request")))
|
||||||
|
.await;
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Bi<auth::Inbound, Result<auth::Outbound, auth::Error>> for AuthTransportAdapter<'_> {}
|
||||||
|
|
||||||
|
pub async fn start(
|
||||||
|
conn: &mut ClientConnection,
|
||||||
|
bi: &mut GrpcBi<ClientRequest, ClientResponse>,
|
||||||
|
request_tracker: &mut RequestTracker,
|
||||||
|
response_id: &mut Option<i32>,
|
||||||
|
) -> Result<(), auth::Error> {
|
||||||
|
let mut transport = AuthTransportAdapter::new(bi, request_tracker, response_id);
|
||||||
|
client::auth::authenticate(conn, &mut transport).await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
@@ -1,7 +1,9 @@
|
|||||||
|
use arbiter_proto::{
|
||||||
use arbiter_proto::proto::{
|
proto::{
|
||||||
client::{ClientRequest, ClientResponse},
|
client::{ClientRequest, ClientResponse},
|
||||||
user_agent::{UserAgentRequest, UserAgentResponse},
|
user_agent::{UserAgentRequest, UserAgentResponse},
|
||||||
|
},
|
||||||
|
transport::grpc::GrpcBi,
|
||||||
};
|
};
|
||||||
use tokio::sync::mpsc;
|
use tokio::sync::mpsc;
|
||||||
use tokio_stream::wrappers::ReceiverStream;
|
use tokio_stream::wrappers::ReceiverStream;
|
||||||
@@ -10,10 +12,12 @@ use tracing::info;
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
DEFAULT_CHANNEL_SIZE,
|
DEFAULT_CHANNEL_SIZE,
|
||||||
actors::{client::{ClientConnection, connect_client}, user_agent::{UserAgentConnection, connect_user_agent}},
|
actors::{client::ClientConnection, user_agent::UserAgentConnection},
|
||||||
|
grpc::{self, user_agent::start},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub mod client;
|
pub mod client;
|
||||||
|
mod request_tracker;
|
||||||
pub mod user_agent;
|
pub mod user_agent;
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
@@ -27,19 +31,13 @@ impl arbiter_proto::proto::arbiter_service_server::ArbiterService for super::Ser
|
|||||||
request: Request<tonic::Streaming<ClientRequest>>,
|
request: Request<tonic::Streaming<ClientRequest>>,
|
||||||
) -> Result<Response<Self::ClientStream>, Status> {
|
) -> Result<Response<Self::ClientStream>, Status> {
|
||||||
let req_stream = request.into_inner();
|
let req_stream = request.into_inner();
|
||||||
let (tx, rx) = mpsc::channel(DEFAULT_CHANNEL_SIZE);
|
let (bi, rx) = GrpcBi::from_bi_stream(req_stream);
|
||||||
|
let props = ClientConnection::new(self.context.db.clone(), self.context.actors.clone());
|
||||||
let transport = client::GrpcTransport::new(tx, req_stream);
|
tokio::spawn(client::start(props, bi));
|
||||||
let props = ClientConnection::new(
|
|
||||||
self.context.db.clone(),
|
|
||||||
Box::new(transport),
|
|
||||||
self.context.actors.clone(),
|
|
||||||
);
|
|
||||||
tokio::spawn(connect_client(props));
|
|
||||||
|
|
||||||
info!(event = "connection established", "grpc.client");
|
info!(event = "connection established", "grpc.client");
|
||||||
|
|
||||||
Ok(Response::new(ReceiverStream::new(rx)))
|
Ok(Response::new(rx))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(level = "debug", skip(self))]
|
#[tracing::instrument(level = "debug", skip(self))]
|
||||||
@@ -48,18 +46,19 @@ impl arbiter_proto::proto::arbiter_service_server::ArbiterService for super::Ser
|
|||||||
request: Request<tonic::Streaming<UserAgentRequest>>,
|
request: Request<tonic::Streaming<UserAgentRequest>>,
|
||||||
) -> Result<Response<Self::UserAgentStream>, Status> {
|
) -> Result<Response<Self::UserAgentStream>, Status> {
|
||||||
let req_stream = request.into_inner();
|
let req_stream = request.into_inner();
|
||||||
let (tx, rx) = mpsc::channel(DEFAULT_CHANNEL_SIZE);
|
|
||||||
|
|
||||||
let transport = user_agent::GrpcTransport::new(tx, req_stream);
|
let (bi, rx) = GrpcBi::from_bi_stream(req_stream);
|
||||||
let props = UserAgentConnection::new(
|
|
||||||
self.context.db.clone(),
|
tokio::spawn(start(
|
||||||
self.context.actors.clone(),
|
UserAgentConnection {
|
||||||
Box::new(transport),
|
db: self.context.db.clone(),
|
||||||
);
|
actors: self.context.actors.clone(),
|
||||||
tokio::spawn(connect_user_agent(props));
|
},
|
||||||
|
bi,
|
||||||
|
));
|
||||||
|
|
||||||
info!(event = "connection established", "grpc.user_agent");
|
info!(event = "connection established", "grpc.user_agent");
|
||||||
|
|
||||||
Ok(Response::new(ReceiverStream::new(rx)))
|
Ok(Response::new(rx))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
20
server/crates/arbiter-server/src/grpc/request_tracker.rs
Normal file
20
server/crates/arbiter-server/src/grpc/request_tracker.rs
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
use tonic::Status;
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct RequestTracker {
|
||||||
|
next_request_id: i32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RequestTracker {
|
||||||
|
pub fn request(&mut self, id: i32) -> Result<i32, Status> {
|
||||||
|
if id < self.next_request_id {
|
||||||
|
return Err(Status::invalid_argument("Duplicate request id"));
|
||||||
|
}
|
||||||
|
|
||||||
|
self.next_request_id = id
|
||||||
|
.checked_add(1)
|
||||||
|
.ok_or_else(|| Status::invalid_argument("Invalid request id"))?;
|
||||||
|
|
||||||
|
Ok(id)
|
||||||
|
}
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
180
server/crates/arbiter-server/src/grpc/user_agent/auth.rs
Normal file
180
server/crates/arbiter-server/src/grpc/user_agent/auth.rs
Normal file
@@ -0,0 +1,180 @@
|
|||||||
|
use arbiter_proto::{
|
||||||
|
proto::user_agent::{
|
||||||
|
AuthChallenge as ProtoAuthChallenge, AuthChallengeRequest as ProtoAuthChallengeRequest,
|
||||||
|
AuthChallengeSolution as ProtoAuthChallengeSolution, AuthResult as ProtoAuthResult,
|
||||||
|
KeyType as ProtoKeyType, UserAgentRequest, UserAgentResponse,
|
||||||
|
user_agent_request::Payload as UserAgentRequestPayload,
|
||||||
|
user_agent_response::Payload as UserAgentResponsePayload,
|
||||||
|
},
|
||||||
|
transport::{Bi, Error as TransportError, Receiver, Sender, grpc::GrpcBi},
|
||||||
|
};
|
||||||
|
use async_trait::async_trait;
|
||||||
|
use tonic::Status;
|
||||||
|
use tracing::warn;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
actors::user_agent::{AuthPublicKey, UserAgentConnection, auth},
|
||||||
|
db::models::KeyType,
|
||||||
|
grpc::request_tracker::RequestTracker,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub struct AuthTransportAdapter<'a> {
|
||||||
|
bi: &'a mut GrpcBi<UserAgentRequest, UserAgentResponse>,
|
||||||
|
request_tracker: &'a mut RequestTracker,
|
||||||
|
response_id: &'a mut Option<i32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> AuthTransportAdapter<'a> {
|
||||||
|
pub fn new(
|
||||||
|
bi: &'a mut GrpcBi<UserAgentRequest, UserAgentResponse>,
|
||||||
|
request_tracker: &'a mut RequestTracker,
|
||||||
|
response_id: &'a mut Option<i32>,
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
bi,
|
||||||
|
request_tracker,
|
||||||
|
response_id,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn send_user_agent_response(
|
||||||
|
&mut self,
|
||||||
|
payload: UserAgentResponsePayload,
|
||||||
|
) -> Result<(), TransportError> {
|
||||||
|
let id = self.response_id.take();
|
||||||
|
|
||||||
|
self.bi
|
||||||
|
.send(Ok(UserAgentResponse {
|
||||||
|
id,
|
||||||
|
payload: Some(payload),
|
||||||
|
}))
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl Sender<Result<auth::Outbound, auth::Error>> for AuthTransportAdapter<'_> {
|
||||||
|
async fn send(
|
||||||
|
&mut self,
|
||||||
|
item: Result<auth::Outbound, auth::Error>,
|
||||||
|
) -> Result<(), TransportError> {
|
||||||
|
use auth::{Error, Outbound};
|
||||||
|
let payload = match item {
|
||||||
|
Ok(Outbound::AuthChallenge { nonce }) => {
|
||||||
|
UserAgentResponsePayload::AuthChallenge(ProtoAuthChallenge { nonce })
|
||||||
|
}
|
||||||
|
Ok(Outbound::AuthSuccess) => {
|
||||||
|
UserAgentResponsePayload::AuthResult(ProtoAuthResult::Success.into())
|
||||||
|
}
|
||||||
|
Err(Error::UnregisteredPublicKey) => {
|
||||||
|
UserAgentResponsePayload::AuthResult(ProtoAuthResult::InvalidKey.into())
|
||||||
|
}
|
||||||
|
Err(Error::InvalidChallengeSolution) => {
|
||||||
|
UserAgentResponsePayload::AuthResult(ProtoAuthResult::InvalidSignature.into())
|
||||||
|
}
|
||||||
|
Err(Error::InvalidBootstrapToken) => {
|
||||||
|
UserAgentResponsePayload::AuthResult(ProtoAuthResult::TokenInvalid.into())
|
||||||
|
}
|
||||||
|
Err(Error::Internal { details }) => return self.bi.send(Err(Status::internal(details))).await,
|
||||||
|
Err(Error::Transport) => {
|
||||||
|
return self.bi.send(Err(Status::unavailable("transport error"))).await;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
self.send_user_agent_response(payload).await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl Receiver<auth::Inbound> for AuthTransportAdapter<'_> {
|
||||||
|
async fn recv(&mut self) -> Option<auth::Inbound> {
|
||||||
|
let request = match self.bi.recv().await? {
|
||||||
|
Ok(request) => request,
|
||||||
|
Err(error) => {
|
||||||
|
warn!(error = ?error, "Failed to receive user agent auth request");
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let request_id = match self.request_tracker.request(request.id) {
|
||||||
|
Ok(request_id) => request_id,
|
||||||
|
Err(error) => {
|
||||||
|
let _ = self.bi.send(Err(error)).await;
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
*self.response_id = Some(request_id);
|
||||||
|
|
||||||
|
let Some(payload) = request.payload else {
|
||||||
|
warn!(
|
||||||
|
event = "received request with empty payload",
|
||||||
|
"grpc.useragent.auth_adapter"
|
||||||
|
);
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
|
||||||
|
match payload {
|
||||||
|
UserAgentRequestPayload::AuthChallengeRequest(ProtoAuthChallengeRequest {
|
||||||
|
pubkey,
|
||||||
|
bootstrap_token,
|
||||||
|
key_type,
|
||||||
|
}) => {
|
||||||
|
let Ok(key_type) = ProtoKeyType::try_from(key_type) else {
|
||||||
|
warn!(
|
||||||
|
event = "received request with invalid key type",
|
||||||
|
"grpc.useragent.auth_adapter"
|
||||||
|
);
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
let key_type = match key_type {
|
||||||
|
ProtoKeyType::Ed25519 => KeyType::Ed25519,
|
||||||
|
ProtoKeyType::EcdsaSecp256k1 => KeyType::EcdsaSecp256k1,
|
||||||
|
ProtoKeyType::Rsa => KeyType::Rsa,
|
||||||
|
ProtoKeyType::Unspecified => {
|
||||||
|
warn!(
|
||||||
|
event = "received request with unspecified key type",
|
||||||
|
"grpc.useragent.auth_adapter"
|
||||||
|
);
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let Ok(pubkey) = AuthPublicKey::try_from((key_type, pubkey)) else {
|
||||||
|
warn!(
|
||||||
|
event = "received request with invalid public key",
|
||||||
|
"grpc.useragent.auth_adapter"
|
||||||
|
);
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
|
||||||
|
Some(auth::Inbound::AuthChallengeRequest {
|
||||||
|
pubkey,
|
||||||
|
bootstrap_token,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
UserAgentRequestPayload::AuthChallengeSolution(ProtoAuthChallengeSolution {
|
||||||
|
signature,
|
||||||
|
}) => Some(auth::Inbound::AuthChallengeSolution { signature }),
|
||||||
|
_ => {
|
||||||
|
let _ = self
|
||||||
|
.bi
|
||||||
|
.send(Err(Status::invalid_argument(
|
||||||
|
"Unsupported user-agent auth request",
|
||||||
|
)))
|
||||||
|
.await;
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Bi<auth::Inbound, Result<auth::Outbound, auth::Error>> for AuthTransportAdapter<'_> {}
|
||||||
|
|
||||||
|
pub async fn start(
|
||||||
|
conn: &mut UserAgentConnection,
|
||||||
|
bi: &mut GrpcBi<UserAgentRequest, UserAgentResponse>,
|
||||||
|
request_tracker: &mut RequestTracker,
|
||||||
|
response_id: &mut Option<i32>,
|
||||||
|
) -> Result<AuthPublicKey, auth::Error> {
|
||||||
|
let transport = AuthTransportAdapter::new(bi, request_tracker, response_id);
|
||||||
|
auth::authenticate(conn, transport).await
|
||||||
|
}
|
||||||
@@ -1,9 +1,5 @@
|
|||||||
#![forbid(unsafe_code)]
|
#![forbid(unsafe_code)]
|
||||||
#![deny(
|
#![deny(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
|
||||||
clippy::unwrap_used,
|
|
||||||
clippy::expect_used,
|
|
||||||
clippy::panic
|
|
||||||
)]
|
|
||||||
|
|
||||||
use crate::context::ServerContext;
|
use crate::context::ServerContext;
|
||||||
|
|
||||||
@@ -13,6 +9,7 @@ pub mod db;
|
|||||||
pub mod evm;
|
pub mod evm;
|
||||||
pub mod grpc;
|
pub mod grpc;
|
||||||
pub mod safe_cell;
|
pub mod safe_cell;
|
||||||
|
pub mod utils;
|
||||||
|
|
||||||
const DEFAULT_CHANNEL_SIZE: usize = 1000;
|
const DEFAULT_CHANNEL_SIZE: usize = 1000;
|
||||||
|
|
||||||
@@ -25,4 +22,3 @@ impl Server {
|
|||||||
Self { context }
|
Self { context }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
16
server/crates/arbiter-server/src/utils.rs
Normal file
16
server/crates/arbiter-server/src/utils.rs
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
struct DeferClosure<F: FnOnce()> {
|
||||||
|
f: Option<F>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<F: FnOnce()> Drop for DeferClosure<F> {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
if let Some(f) = self.f.take() {
|
||||||
|
f();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run some code when a scope is exited, similar to Go's defer statement
|
||||||
|
pub fn defer<F: FnOnce()>(f: F) -> impl Drop + Sized {
|
||||||
|
DeferClosure { f: Some(f) }
|
||||||
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
use arbiter_proto::transport::Bi;
|
use arbiter_proto::transport::{Receiver, Sender};
|
||||||
use arbiter_server::actors::GlobalActors;
|
use arbiter_server::actors::GlobalActors;
|
||||||
use arbiter_server::{
|
use arbiter_server::{
|
||||||
actors::client::{ClientConnection, Request, Response, connect_client},
|
actors::client::{ClientConnection, auth, connect_client},
|
||||||
db::{self, schema},
|
db::{self, schema},
|
||||||
};
|
};
|
||||||
use diesel::{ExpressionMethods as _, insert_into};
|
use diesel::{ExpressionMethods as _, insert_into};
|
||||||
@@ -17,15 +17,17 @@ pub async fn test_unregistered_pubkey_rejected() {
|
|||||||
|
|
||||||
let (server_transport, mut test_transport) = ChannelTransport::new();
|
let (server_transport, mut test_transport) = ChannelTransport::new();
|
||||||
let actors = GlobalActors::spawn(db.clone()).await.unwrap();
|
let actors = GlobalActors::spawn(db.clone()).await.unwrap();
|
||||||
let props = ClientConnection::new(db.clone(), Box::new(server_transport), actors);
|
let props = ClientConnection::new(db.clone(), actors);
|
||||||
let task = tokio::spawn(connect_client(props));
|
let task = tokio::spawn(async move {
|
||||||
|
let mut server_transport = server_transport;
|
||||||
|
connect_client(props, &mut server_transport).await;
|
||||||
|
});
|
||||||
|
|
||||||
let new_key = ed25519_dalek::SigningKey::generate(&mut rand::rng());
|
let new_key = ed25519_dalek::SigningKey::generate(&mut rand::rng());
|
||||||
let pubkey_bytes = new_key.verifying_key().to_bytes().to_vec();
|
|
||||||
|
|
||||||
test_transport
|
test_transport
|
||||||
.send(Request::AuthChallengeRequest {
|
.send(auth::Inbound::AuthChallengeRequest {
|
||||||
pubkey: pubkey_bytes,
|
pubkey: new_key.verifying_key(),
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
@@ -54,13 +56,16 @@ pub async fn test_challenge_auth() {
|
|||||||
let (server_transport, mut test_transport) = ChannelTransport::new();
|
let (server_transport, mut test_transport) = ChannelTransport::new();
|
||||||
let actors = GlobalActors::spawn(db.clone()).await.unwrap();
|
let actors = GlobalActors::spawn(db.clone()).await.unwrap();
|
||||||
|
|
||||||
let props = ClientConnection::new(db.clone(), Box::new(server_transport), actors);
|
let props = ClientConnection::new(db.clone(), actors);
|
||||||
let task = tokio::spawn(connect_client(props));
|
let task = tokio::spawn(async move {
|
||||||
|
let mut server_transport = server_transport;
|
||||||
|
connect_client(props, &mut server_transport).await;
|
||||||
|
});
|
||||||
|
|
||||||
// Send challenge request
|
// Send challenge request
|
||||||
test_transport
|
test_transport
|
||||||
.send(Request::AuthChallengeRequest {
|
.send(auth::Inbound::AuthChallengeRequest {
|
||||||
pubkey: pubkey_bytes,
|
pubkey: new_key.verifying_key(),
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
@@ -72,23 +77,31 @@ pub async fn test_challenge_auth() {
|
|||||||
.expect("should receive challenge");
|
.expect("should receive challenge");
|
||||||
let challenge = match response {
|
let challenge = match response {
|
||||||
Ok(resp) => match resp {
|
Ok(resp) => match resp {
|
||||||
Response::AuthChallenge { pubkey, nonce } => (pubkey, nonce),
|
auth::Outbound::AuthChallenge { pubkey, nonce } => (pubkey, nonce),
|
||||||
other => panic!("Expected AuthChallenge, got {other:?}"),
|
other => panic!("Expected AuthChallenge, got {other:?}"),
|
||||||
},
|
},
|
||||||
Err(err) => panic!("Expected Ok response, got Err({err:?})"),
|
Err(err) => panic!("Expected Ok response, got Err({err:?})"),
|
||||||
};
|
};
|
||||||
|
|
||||||
// Sign the challenge and send solution
|
// Sign the challenge and send solution
|
||||||
let formatted_challenge = arbiter_proto::format_challenge(challenge.1, &challenge.0);
|
let formatted_challenge = arbiter_proto::format_challenge(challenge.1, challenge.0.as_bytes());
|
||||||
let signature = new_key.sign(&formatted_challenge);
|
let signature = new_key.sign(&formatted_challenge);
|
||||||
|
|
||||||
test_transport
|
test_transport
|
||||||
.send(Request::AuthChallengeSolution {
|
.send(auth::Inbound::AuthChallengeSolution { signature })
|
||||||
signature: signature.to_bytes().to_vec(),
|
|
||||||
})
|
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
|
let response = test_transport
|
||||||
|
.recv()
|
||||||
|
.await
|
||||||
|
.expect("should receive auth success");
|
||||||
|
match response {
|
||||||
|
Ok(auth::Outbound::AuthSuccess) => {}
|
||||||
|
Ok(other) => panic!("Expected AuthSuccess, got {other:?}"),
|
||||||
|
Err(err) => panic!("Expected Ok response, got Err({err:?})"),
|
||||||
|
}
|
||||||
|
|
||||||
// Auth completes, session spawned
|
// Auth completes, session spawned
|
||||||
task.await.unwrap();
|
task.await.unwrap();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
use arbiter_proto::transport::{Bi, Error};
|
use arbiter_proto::transport::{Bi, Error, Receiver, Sender};
|
||||||
use arbiter_server::{
|
use arbiter_server::{
|
||||||
actors::keyholder::KeyHolder,
|
actors::keyholder::KeyHolder,
|
||||||
db::{self, schema}, safe_cell::{SafeCell, SafeCellHandle as _},
|
db::{self, schema},
|
||||||
|
safe_cell::{SafeCell, SafeCellHandle as _},
|
||||||
};
|
};
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use diesel::QueryDsl;
|
use diesel::QueryDsl;
|
||||||
@@ -54,10 +55,10 @@ impl<T, Y> ChannelTransport<T, Y> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl<T, Y> Bi<T, Y> for ChannelTransport<T, Y>
|
impl<T, Y> Sender<Y> for ChannelTransport<T, Y>
|
||||||
where
|
where
|
||||||
T: Send + 'static,
|
T: Send + Sync + 'static,
|
||||||
Y: Send + 'static,
|
Y: Send + Sync + 'static,
|
||||||
{
|
{
|
||||||
async fn send(&mut self, item: Y) -> Result<(), Error> {
|
async fn send(&mut self, item: Y) -> Result<(), Error> {
|
||||||
self.sender
|
self.sender
|
||||||
@@ -65,8 +66,22 @@ where
|
|||||||
.await
|
.await
|
||||||
.map_err(|_| Error::ChannelClosed)
|
.map_err(|_| Error::ChannelClosed)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl<T, Y> Receiver<T> for ChannelTransport<T, Y>
|
||||||
|
where
|
||||||
|
T: Send + Sync + 'static,
|
||||||
|
Y: Send + Sync + 'static,
|
||||||
|
{
|
||||||
async fn recv(&mut self) -> Option<T> {
|
async fn recv(&mut self) -> Option<T> {
|
||||||
self.receiver.recv().await
|
self.receiver.recv().await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<T, Y> Bi<T, Y> for ChannelTransport<T, Y>
|
||||||
|
where
|
||||||
|
T: Send + Sync + 'static,
|
||||||
|
Y: Send + Sync + 'static,
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
use arbiter_proto::transport::Bi;
|
use arbiter_proto::transport::{Receiver, Sender};
|
||||||
use arbiter_server::{
|
use arbiter_server::{
|
||||||
actors::{
|
actors::{
|
||||||
GlobalActors,
|
GlobalActors,
|
||||||
bootstrap::GetToken,
|
bootstrap::GetToken,
|
||||||
user_agent::{AuthPublicKey, Request, Response, UserAgentConnection, connect_user_agent},
|
user_agent::{AuthPublicKey, UserAgentConnection, auth},
|
||||||
},
|
},
|
||||||
db::{self, schema},
|
db::{self, schema},
|
||||||
};
|
};
|
||||||
@@ -21,19 +21,31 @@ pub async fn test_bootstrap_token_auth() {
|
|||||||
let token = actors.bootstrapper.ask(GetToken).await.unwrap().unwrap();
|
let token = actors.bootstrapper.ask(GetToken).await.unwrap().unwrap();
|
||||||
|
|
||||||
let (server_transport, mut test_transport) = ChannelTransport::new();
|
let (server_transport, mut test_transport) = ChannelTransport::new();
|
||||||
let props = UserAgentConnection::new(db.clone(), actors, Box::new(server_transport));
|
let db_for_task = db.clone();
|
||||||
let task = tokio::spawn(connect_user_agent(props));
|
let task = tokio::spawn(async move {
|
||||||
|
let mut props = UserAgentConnection::new(db_for_task, actors);
|
||||||
|
auth::authenticate(&mut props, server_transport).await
|
||||||
|
});
|
||||||
|
|
||||||
let new_key = ed25519_dalek::SigningKey::generate(&mut rand::rng());
|
let new_key = ed25519_dalek::SigningKey::generate(&mut rand::rng());
|
||||||
test_transport
|
test_transport
|
||||||
.send(Request::AuthChallengeRequest {
|
.send(auth::Inbound::AuthChallengeRequest {
|
||||||
pubkey: AuthPublicKey::Ed25519(new_key.verifying_key()),
|
pubkey: AuthPublicKey::Ed25519(new_key.verifying_key()),
|
||||||
bootstrap_token: Some(token),
|
bootstrap_token: Some(token),
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
task.await.unwrap();
|
let response = test_transport
|
||||||
|
.recv()
|
||||||
|
.await
|
||||||
|
.expect("should receive auth result");
|
||||||
|
match response {
|
||||||
|
Ok(auth::Outbound::AuthSuccess) => {}
|
||||||
|
other => panic!("Expected AuthSuccess, got {other:?}"),
|
||||||
|
}
|
||||||
|
|
||||||
|
task.await.unwrap().unwrap();
|
||||||
|
|
||||||
let mut conn = db.get().await.unwrap();
|
let mut conn = db.get().await.unwrap();
|
||||||
let stored_pubkey: Vec<u8> = schema::useragent_client::table
|
let stored_pubkey: Vec<u8> = schema::useragent_client::table
|
||||||
@@ -51,20 +63,25 @@ pub async fn test_bootstrap_invalid_token_auth() {
|
|||||||
let actors = GlobalActors::spawn(db.clone()).await.unwrap();
|
let actors = GlobalActors::spawn(db.clone()).await.unwrap();
|
||||||
|
|
||||||
let (server_transport, mut test_transport) = ChannelTransport::new();
|
let (server_transport, mut test_transport) = ChannelTransport::new();
|
||||||
let props = UserAgentConnection::new(db.clone(), actors, Box::new(server_transport));
|
let db_for_task = db.clone();
|
||||||
let task = tokio::spawn(connect_user_agent(props));
|
let task = tokio::spawn(async move {
|
||||||
|
let mut props = UserAgentConnection::new(db_for_task, actors);
|
||||||
|
auth::authenticate(&mut props, server_transport).await
|
||||||
|
});
|
||||||
|
|
||||||
let new_key = ed25519_dalek::SigningKey::generate(&mut rand::rng());
|
let new_key = ed25519_dalek::SigningKey::generate(&mut rand::rng());
|
||||||
test_transport
|
test_transport
|
||||||
.send(Request::AuthChallengeRequest {
|
.send(auth::Inbound::AuthChallengeRequest {
|
||||||
pubkey: AuthPublicKey::Ed25519(new_key.verifying_key()),
|
pubkey: AuthPublicKey::Ed25519(new_key.verifying_key()),
|
||||||
bootstrap_token: Some("invalid_token".to_string()),
|
bootstrap_token: Some("invalid_token".to_string()),
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
// Auth fails, connect_user_agent returns, transport drops
|
assert!(matches!(
|
||||||
task.await.unwrap();
|
task.await.unwrap(),
|
||||||
|
Err(auth::Error::InvalidBootstrapToken)
|
||||||
|
));
|
||||||
|
|
||||||
// Verify no key was registered
|
// Verify no key was registered
|
||||||
let mut conn = db.get().await.unwrap();
|
let mut conn = db.get().await.unwrap();
|
||||||
@@ -99,12 +116,15 @@ pub async fn test_challenge_auth() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let (server_transport, mut test_transport) = ChannelTransport::new();
|
let (server_transport, mut test_transport) = ChannelTransport::new();
|
||||||
let props = UserAgentConnection::new(db.clone(), actors, Box::new(server_transport));
|
let db_for_task = db.clone();
|
||||||
let task = tokio::spawn(connect_user_agent(props));
|
let task = tokio::spawn(async move {
|
||||||
|
let mut props = UserAgentConnection::new(db_for_task, actors);
|
||||||
|
auth::authenticate(&mut props, server_transport).await
|
||||||
|
});
|
||||||
|
|
||||||
// Send challenge request
|
// Send challenge request
|
||||||
test_transport
|
test_transport
|
||||||
.send(Request::AuthChallengeRequest {
|
.send(auth::Inbound::AuthChallengeRequest {
|
||||||
pubkey: AuthPublicKey::Ed25519(new_key.verifying_key()),
|
pubkey: AuthPublicKey::Ed25519(new_key.verifying_key()),
|
||||||
bootstrap_token: None,
|
bootstrap_token: None,
|
||||||
})
|
})
|
||||||
@@ -118,7 +138,7 @@ pub async fn test_challenge_auth() {
|
|||||||
.expect("should receive challenge");
|
.expect("should receive challenge");
|
||||||
let challenge = match response {
|
let challenge = match response {
|
||||||
Ok(resp) => match resp {
|
Ok(resp) => match resp {
|
||||||
Response::AuthChallenge { nonce } => nonce,
|
auth::Outbound::AuthChallenge { nonce } => nonce,
|
||||||
other => panic!("Expected AuthChallenge, got {other:?}"),
|
other => panic!("Expected AuthChallenge, got {other:?}"),
|
||||||
},
|
},
|
||||||
Err(err) => panic!("Expected Ok response, got Err({err:?})"),
|
Err(err) => panic!("Expected Ok response, got Err({err:?})"),
|
||||||
@@ -128,12 +148,20 @@ pub async fn test_challenge_auth() {
|
|||||||
let signature = new_key.sign(&formatted_challenge);
|
let signature = new_key.sign(&formatted_challenge);
|
||||||
|
|
||||||
test_transport
|
test_transport
|
||||||
.send(Request::AuthChallengeSolution {
|
.send(auth::Inbound::AuthChallengeSolution {
|
||||||
signature: signature.to_bytes().to_vec(),
|
signature: signature.to_bytes().to_vec(),
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
// Auth completes, session spawned
|
let response = test_transport
|
||||||
task.await.unwrap();
|
.recv()
|
||||||
|
.await
|
||||||
|
.expect("should receive auth result");
|
||||||
|
match response {
|
||||||
|
Ok(auth::Outbound::AuthSuccess) => {}
|
||||||
|
other => panic!("Expected AuthSuccess, got {other:?}"),
|
||||||
|
}
|
||||||
|
|
||||||
|
task.await.unwrap().unwrap();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,15 +2,20 @@ use arbiter_server::{
|
|||||||
actors::{
|
actors::{
|
||||||
GlobalActors,
|
GlobalActors,
|
||||||
keyholder::{Bootstrap, Seal},
|
keyholder::{Bootstrap, Seal},
|
||||||
user_agent::{Request, Response, UnsealError, session::UserAgentSession},
|
user_agent::session::{
|
||||||
|
HandleUnsealEncryptedKey, HandleUnsealRequest, UnsealError, UserAgentSession,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
db,
|
db,
|
||||||
safe_cell::{SafeCell, SafeCellHandle as _},
|
safe_cell::{SafeCell, SafeCellHandle as _},
|
||||||
};
|
};
|
||||||
use chacha20poly1305::{AeadInPlace, XChaCha20Poly1305, XNonce, aead::KeyInit};
|
use chacha20poly1305::{AeadInPlace, XChaCha20Poly1305, XNonce, aead::KeyInit};
|
||||||
|
use kameo::actor::Spawn as _;
|
||||||
use x25519_dalek::{EphemeralSecret, PublicKey};
|
use x25519_dalek::{EphemeralSecret, PublicKey};
|
||||||
|
|
||||||
async fn setup_sealed_user_agent(seal_key: &[u8]) -> (db::DatabasePool, UserAgentSession) {
|
async fn setup_sealed_user_agent(
|
||||||
|
seal_key: &[u8],
|
||||||
|
) -> (db::DatabasePool, kameo::actor::ActorRef<UserAgentSession>) {
|
||||||
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();
|
||||||
|
|
||||||
@@ -23,26 +28,26 @@ async fn setup_sealed_user_agent(seal_key: &[u8]) -> (db::DatabasePool, UserAgen
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
actors.key_holder.ask(Seal).await.unwrap();
|
actors.key_holder.ask(Seal).await.unwrap();
|
||||||
|
|
||||||
let session = UserAgentSession::new_test(db.clone(), actors);
|
let session = UserAgentSession::spawn(UserAgentSession::new_test(db.clone(), actors));
|
||||||
|
|
||||||
(db, session)
|
(db, session)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn client_dh_encrypt(user_agent: &mut UserAgentSession, key_to_send: &[u8]) -> Request {
|
async fn client_dh_encrypt(
|
||||||
|
user_agent: &kameo::actor::ActorRef<UserAgentSession>,
|
||||||
|
key_to_send: &[u8],
|
||||||
|
) -> HandleUnsealEncryptedKey {
|
||||||
let client_secret = EphemeralSecret::random();
|
let client_secret = EphemeralSecret::random();
|
||||||
let client_public = PublicKey::from(&client_secret);
|
let client_public = PublicKey::from(&client_secret);
|
||||||
|
|
||||||
let response = user_agent
|
let response = user_agent
|
||||||
.process_transport_inbound(Request::UnsealStart {
|
.ask(HandleUnsealRequest {
|
||||||
client_pubkey: client_public,
|
client_pubkey: client_public,
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let server_pubkey = match response {
|
let server_pubkey = response.server_pubkey;
|
||||||
Response::UnsealStartResponse { server_pubkey } => server_pubkey,
|
|
||||||
other => panic!("Expected UnsealStartResponse, got {other:?}"),
|
|
||||||
};
|
|
||||||
|
|
||||||
let shared_secret = client_secret.diffie_hellman(&server_pubkey);
|
let shared_secret = client_secret.diffie_hellman(&server_pubkey);
|
||||||
let cipher = XChaCha20Poly1305::new(shared_secret.as_bytes().into());
|
let cipher = XChaCha20Poly1305::new(shared_secret.as_bytes().into());
|
||||||
@@ -53,7 +58,7 @@ async fn client_dh_encrypt(user_agent: &mut UserAgentSession, key_to_send: &[u8]
|
|||||||
.encrypt_in_place(&nonce, associated_data, &mut ciphertext)
|
.encrypt_in_place(&nonce, associated_data, &mut ciphertext)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
Request::UnsealEncryptedKey {
|
HandleUnsealEncryptedKey {
|
||||||
nonce: nonce.to_vec(),
|
nonce: nonce.to_vec(),
|
||||||
ciphertext,
|
ciphertext,
|
||||||
associated_data: associated_data.to_vec(),
|
associated_data: associated_data.to_vec(),
|
||||||
@@ -64,63 +69,58 @@ async fn client_dh_encrypt(user_agent: &mut UserAgentSession, key_to_send: &[u8]
|
|||||||
#[test_log::test]
|
#[test_log::test]
|
||||||
pub async fn test_unseal_success() {
|
pub async fn test_unseal_success() {
|
||||||
let seal_key = b"test-seal-key";
|
let seal_key = b"test-seal-key";
|
||||||
let (_db, mut user_agent) = setup_sealed_user_agent(seal_key).await;
|
let (_db, user_agent) = setup_sealed_user_agent(seal_key).await;
|
||||||
|
|
||||||
let encrypted_key = client_dh_encrypt(&mut user_agent, seal_key).await;
|
let encrypted_key = client_dh_encrypt(&user_agent, seal_key).await;
|
||||||
|
|
||||||
let response = user_agent
|
let response = user_agent.ask(encrypted_key).await;
|
||||||
.process_transport_inbound(encrypted_key)
|
assert!(matches!(response, Ok(())));
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
assert!(matches!(response, Response::UnsealResult(Ok(()))));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
#[test_log::test]
|
#[test_log::test]
|
||||||
pub async fn test_unseal_wrong_seal_key() {
|
pub async fn test_unseal_wrong_seal_key() {
|
||||||
let (_db, mut user_agent) = setup_sealed_user_agent(b"correct-key").await;
|
let (_db, user_agent) = setup_sealed_user_agent(b"correct-key").await;
|
||||||
|
|
||||||
let encrypted_key = client_dh_encrypt(&mut user_agent, b"wrong-key").await;
|
let encrypted_key = client_dh_encrypt(&user_agent, b"wrong-key").await;
|
||||||
|
|
||||||
let response = user_agent
|
|
||||||
.process_transport_inbound(encrypted_key)
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
|
let response = user_agent.ask(encrypted_key).await;
|
||||||
assert!(matches!(
|
assert!(matches!(
|
||||||
response,
|
response,
|
||||||
Response::UnsealResult(Err(UnsealError::InvalidKey))
|
Err(kameo::error::SendError::HandlerError(
|
||||||
|
UnsealError::InvalidKey
|
||||||
|
))
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
#[test_log::test]
|
#[test_log::test]
|
||||||
pub async fn test_unseal_corrupted_ciphertext() {
|
pub async fn test_unseal_corrupted_ciphertext() {
|
||||||
let (_db, mut user_agent) = setup_sealed_user_agent(b"test-key").await;
|
let (_db, user_agent) = setup_sealed_user_agent(b"test-key").await;
|
||||||
|
|
||||||
let client_secret = EphemeralSecret::random();
|
let client_secret = EphemeralSecret::random();
|
||||||
let client_public = PublicKey::from(&client_secret);
|
let client_public = PublicKey::from(&client_secret);
|
||||||
|
|
||||||
user_agent
|
user_agent
|
||||||
.process_transport_inbound(Request::UnsealStart {
|
.ask(HandleUnsealRequest {
|
||||||
client_pubkey: client_public,
|
client_pubkey: client_public,
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let response = user_agent
|
let response = user_agent
|
||||||
.process_transport_inbound(Request::UnsealEncryptedKey {
|
.ask(HandleUnsealEncryptedKey {
|
||||||
nonce: vec![0u8; 24],
|
nonce: vec![0u8; 24],
|
||||||
ciphertext: vec![0u8; 32],
|
ciphertext: vec![0u8; 32],
|
||||||
associated_data: vec![],
|
associated_data: vec![],
|
||||||
})
|
})
|
||||||
.await
|
.await;
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
assert!(matches!(
|
assert!(matches!(
|
||||||
response,
|
response,
|
||||||
Response::UnsealResult(Err(UnsealError::InvalidKey))
|
Err(kameo::error::SendError::HandlerError(
|
||||||
|
UnsealError::InvalidKey
|
||||||
|
))
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -128,30 +128,24 @@ pub async fn test_unseal_corrupted_ciphertext() {
|
|||||||
#[test_log::test]
|
#[test_log::test]
|
||||||
pub async fn test_unseal_retry_after_invalid_key() {
|
pub async fn test_unseal_retry_after_invalid_key() {
|
||||||
let seal_key = b"real-seal-key";
|
let seal_key = b"real-seal-key";
|
||||||
let (_db, mut user_agent) = setup_sealed_user_agent(seal_key).await;
|
let (_db, user_agent) = setup_sealed_user_agent(seal_key).await;
|
||||||
|
|
||||||
{
|
{
|
||||||
let encrypted_key = client_dh_encrypt(&mut user_agent, b"wrong-key").await;
|
let encrypted_key = client_dh_encrypt(&user_agent, b"wrong-key").await;
|
||||||
|
|
||||||
let response = user_agent
|
|
||||||
.process_transport_inbound(encrypted_key)
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
|
let response = user_agent.ask(encrypted_key).await;
|
||||||
assert!(matches!(
|
assert!(matches!(
|
||||||
response,
|
response,
|
||||||
Response::UnsealResult(Err(UnsealError::InvalidKey))
|
Err(kameo::error::SendError::HandlerError(
|
||||||
|
UnsealError::InvalidKey
|
||||||
|
))
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
let encrypted_key = client_dh_encrypt(&mut user_agent, seal_key).await;
|
let encrypted_key = client_dh_encrypt(&user_agent, seal_key).await;
|
||||||
|
|
||||||
let response = user_agent
|
let response = user_agent.ask(encrypted_key).await;
|
||||||
.process_transport_inbound(encrypted_key)
|
assert!(matches!(response, Ok(())));
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
assert!(matches!(response, Response::UnsealResult(Ok(()))));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,13 +9,49 @@ import 'package:arbiter/proto/user_agent.pb.dart';
|
|||||||
import 'package:grpc/grpc.dart';
|
import 'package:grpc/grpc.dart';
|
||||||
import 'package:mtcore/markettakers.dart';
|
import 'package:mtcore/markettakers.dart';
|
||||||
|
|
||||||
|
class AuthorizationException implements Exception {
|
||||||
|
const AuthorizationException(this.result);
|
||||||
|
|
||||||
|
final AuthResult result;
|
||||||
|
|
||||||
|
String get message => switch (result) {
|
||||||
|
AuthResult.AUTH_RESULT_INVALID_KEY =>
|
||||||
|
'Authentication failed: this device key is not registered on the server.',
|
||||||
|
AuthResult.AUTH_RESULT_INVALID_SIGNATURE =>
|
||||||
|
'Authentication failed: the server rejected the signature for this device key.',
|
||||||
|
AuthResult.AUTH_RESULT_BOOTSTRAP_REQUIRED =>
|
||||||
|
'Authentication failed: the server requires bootstrap before this device can connect.',
|
||||||
|
AuthResult.AUTH_RESULT_TOKEN_INVALID =>
|
||||||
|
'Authentication failed: the bootstrap token is invalid.',
|
||||||
|
AuthResult.AUTH_RESULT_INTERNAL =>
|
||||||
|
'Authentication failed: the server hit an internal error.',
|
||||||
|
AuthResult.AUTH_RESULT_UNSPECIFIED =>
|
||||||
|
'Authentication failed: the server returned an unspecified auth error.',
|
||||||
|
AuthResult.AUTH_RESULT_SUCCESS => 'Authentication succeeded.',
|
||||||
|
_ => 'Authentication failed: ${result.name}.',
|
||||||
|
};
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => message;
|
||||||
|
}
|
||||||
|
|
||||||
|
class ConnectionException implements Exception {
|
||||||
|
const ConnectionException(this.message);
|
||||||
|
|
||||||
|
final String message;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => message;
|
||||||
|
}
|
||||||
|
|
||||||
Future<Connection> connectAndAuthorize(
|
Future<Connection> connectAndAuthorize(
|
||||||
StoredServerInfo serverInfo,
|
StoredServerInfo serverInfo,
|
||||||
KeyHandle key, {
|
KeyHandle key, {
|
||||||
String? bootstrapToken,
|
String? bootstrapToken,
|
||||||
}) async {
|
}) async {
|
||||||
|
Connection? connection;
|
||||||
try {
|
try {
|
||||||
final connection = await _connect(serverInfo);
|
connection = await _connect(serverInfo);
|
||||||
talker.info(
|
talker.info(
|
||||||
'Connected to server at ${serverInfo.address}:${serverInfo.port}',
|
'Connected to server at ${serverInfo.address}:${serverInfo.port}',
|
||||||
);
|
);
|
||||||
@@ -30,21 +66,24 @@ Future<Connection> connectAndAuthorize(
|
|||||||
KeyAlgorithm.ed25519 => KeyType.KEY_TYPE_ED25519,
|
KeyAlgorithm.ed25519 => KeyType.KEY_TYPE_ED25519,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
await connection.send(UserAgentRequest(authChallengeRequest: req));
|
final response = await connection.request(
|
||||||
|
UserAgentRequest(authChallengeRequest: req),
|
||||||
|
);
|
||||||
talker.info(
|
talker.info(
|
||||||
"Sent auth challenge request with pubkey ${base64Encode(pubkey)}",
|
"Sent auth challenge request with pubkey ${base64Encode(pubkey)}",
|
||||||
);
|
);
|
||||||
|
|
||||||
final response = await connection.receive();
|
|
||||||
talker.info('Received response from server, checking auth flow...');
|
talker.info('Received response from server, checking auth flow...');
|
||||||
|
|
||||||
if (response.hasAuthOk()) {
|
if (response.hasAuthResult()) {
|
||||||
|
if (response.authResult != AuthResult.AUTH_RESULT_SUCCESS) {
|
||||||
|
throw AuthorizationException(response.authResult);
|
||||||
|
}
|
||||||
talker.info('Authentication successful, connection established');
|
talker.info('Authentication successful, connection established');
|
||||||
return connection;
|
return connection;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!response.hasAuthChallenge()) {
|
if (!response.hasAuthChallenge()) {
|
||||||
throw Exception(
|
throw ConnectionException(
|
||||||
'Expected AuthChallengeResponse, got ${response.whichPayload()}',
|
'Expected AuthChallengeResponse, got ${response.whichPayload()}',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -55,23 +94,35 @@ Future<Connection> connectAndAuthorize(
|
|||||||
);
|
);
|
||||||
|
|
||||||
final signature = await key.sign(challenge);
|
final signature = await key.sign(challenge);
|
||||||
await connection.send(
|
final solutionResponse = await connection.request(
|
||||||
UserAgentRequest(authChallengeSolution: AuthChallengeSolution(signature: signature)),
|
UserAgentRequest(authChallengeSolution: AuthChallengeSolution(signature: signature)),
|
||||||
);
|
);
|
||||||
|
|
||||||
talker.info('Sent auth challenge solution, waiting for server response...');
|
talker.info('Sent auth challenge solution, waiting for server response...');
|
||||||
|
|
||||||
final solutionResponse = await connection.receive();
|
if (!solutionResponse.hasAuthResult()) {
|
||||||
if (!solutionResponse.hasAuthOk()) {
|
throw ConnectionException(
|
||||||
throw Exception(
|
|
||||||
'Expected AuthChallengeSolutionResponse, got ${solutionResponse.whichPayload()}',
|
'Expected AuthChallengeSolutionResponse, got ${solutionResponse.whichPayload()}',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
if (solutionResponse.authResult != AuthResult.AUTH_RESULT_SUCCESS) {
|
||||||
|
throw AuthorizationException(solutionResponse.authResult);
|
||||||
|
}
|
||||||
|
|
||||||
talker.info('Authentication successful, connection established');
|
talker.info('Authentication successful, connection established');
|
||||||
return connection;
|
return connection;
|
||||||
|
} on AuthorizationException {
|
||||||
|
await connection?.close();
|
||||||
|
rethrow;
|
||||||
|
} on GrpcError catch (error) {
|
||||||
|
await connection?.close();
|
||||||
|
throw ConnectionException('Failed to connect to server: ${error.message}');
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
throw Exception('Failed to connect to server: $e');
|
await connection?.close();
|
||||||
|
if (e is ConnectionException) {
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
|
throw ConnectionException('Failed to connect to server: $e');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,33 +5,113 @@ import 'package:grpc/grpc.dart';
|
|||||||
import 'package:mtcore/markettakers.dart';
|
import 'package:mtcore/markettakers.dart';
|
||||||
|
|
||||||
class Connection {
|
class Connection {
|
||||||
final ClientChannel channel;
|
|
||||||
final StreamController<UserAgentRequest> _tx;
|
|
||||||
final StreamIterator<UserAgentResponse> _rx;
|
|
||||||
|
|
||||||
Connection({
|
Connection({
|
||||||
required this.channel,
|
required this.channel,
|
||||||
required StreamController<UserAgentRequest> tx,
|
required StreamController<UserAgentRequest> tx,
|
||||||
required ResponseStream<UserAgentResponse> rx,
|
required ResponseStream<UserAgentResponse> rx,
|
||||||
}) : _tx = tx,
|
}) : _tx = tx {
|
||||||
_rx = StreamIterator(rx);
|
_rxSubscription = rx.listen(
|
||||||
|
_handleResponse,
|
||||||
Future<void> send(UserAgentRequest request) async {
|
onError: _handleError,
|
||||||
talker.debug('Sending request: ${request.toDebugString()}');
|
onDone: _handleDone,
|
||||||
_tx.add(request);
|
cancelOnError: true,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<UserAgentResponse> receive() async {
|
final ClientChannel channel;
|
||||||
final hasValue = await _rx.moveNext();
|
final StreamController<UserAgentRequest> _tx;
|
||||||
if (!hasValue) {
|
final Map<int, Completer<UserAgentResponse>> _pendingRequests = {};
|
||||||
throw Exception('Connection closed while waiting for server response.');
|
final StreamController<UserAgentResponse> _outOfBandMessages =
|
||||||
|
StreamController<UserAgentResponse>.broadcast();
|
||||||
|
|
||||||
|
StreamSubscription<UserAgentResponse>? _rxSubscription;
|
||||||
|
int _nextRequestId = 0;
|
||||||
|
|
||||||
|
Stream<UserAgentResponse> get outOfBandMessages => _outOfBandMessages.stream;
|
||||||
|
|
||||||
|
Future<UserAgentResponse> request(UserAgentRequest message) async {
|
||||||
|
_ensureOpen();
|
||||||
|
|
||||||
|
final requestId = _nextRequestId++;
|
||||||
|
final completer = Completer<UserAgentResponse>();
|
||||||
|
_pendingRequests[requestId] = completer;
|
||||||
|
|
||||||
|
message.id = requestId;
|
||||||
|
talker.debug('Sending request: ${message.toDebugString()}');
|
||||||
|
|
||||||
|
try {
|
||||||
|
_tx.add(message);
|
||||||
|
} catch (error, stackTrace) {
|
||||||
|
_pendingRequests.remove(requestId);
|
||||||
|
completer.completeError(error, stackTrace);
|
||||||
}
|
}
|
||||||
talker.debug('Received response: ${_rx.current.toDebugString()}');
|
|
||||||
return _rx.current;
|
return completer.future;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> close() async {
|
Future<void> close() async {
|
||||||
|
final rxSubscription = _rxSubscription;
|
||||||
|
if (rxSubscription == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_rxSubscription = null;
|
||||||
|
await rxSubscription.cancel();
|
||||||
|
_failPendingRequests(Exception('Connection closed.'));
|
||||||
|
await _outOfBandMessages.close();
|
||||||
await _tx.close();
|
await _tx.close();
|
||||||
await channel.shutdown();
|
await channel.shutdown();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _handleResponse(UserAgentResponse response) {
|
||||||
|
talker.debug('Received response: ${response.toDebugString()}');
|
||||||
|
|
||||||
|
if (response.hasId()) {
|
||||||
|
final completer = _pendingRequests.remove(response.id);
|
||||||
|
if (completer == null) {
|
||||||
|
talker.warning('Received response for unknown request id ${response.id}');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
completer.complete(response);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_outOfBandMessages.add(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _handleError(Object error, StackTrace stackTrace) {
|
||||||
|
_rxSubscription = null;
|
||||||
|
_failPendingRequests(error, stackTrace);
|
||||||
|
_outOfBandMessages.addError(error, stackTrace);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _handleDone() {
|
||||||
|
if (_rxSubscription == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_rxSubscription = null;
|
||||||
|
final error = Exception(
|
||||||
|
'Connection closed while waiting for server response.',
|
||||||
|
);
|
||||||
|
_failPendingRequests(error);
|
||||||
|
_outOfBandMessages.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
void _failPendingRequests(Object error, [StackTrace? stackTrace]) {
|
||||||
|
final pendingRequests = _pendingRequests.values.toList(growable: false);
|
||||||
|
_pendingRequests.clear();
|
||||||
|
|
||||||
|
for (final completer in pendingRequests) {
|
||||||
|
if (!completer.isCompleted) {
|
||||||
|
completer.completeError(error, stackTrace);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _ensureOpen() {
|
||||||
|
if (_rxSubscription == null) {
|
||||||
|
throw StateError('Connection is closed.');
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,9 +4,9 @@ import 'package:arbiter/proto/user_agent.pb.dart';
|
|||||||
import 'package:protobuf/well_known_types/google/protobuf/empty.pb.dart';
|
import 'package:protobuf/well_known_types/google/protobuf/empty.pb.dart';
|
||||||
|
|
||||||
Future<List<WalletEntry>> listEvmWallets(Connection connection) async {
|
Future<List<WalletEntry>> listEvmWallets(Connection connection) async {
|
||||||
await connection.send(UserAgentRequest(evmWalletList: Empty()));
|
final response = await connection.request(
|
||||||
|
UserAgentRequest(evmWalletList: Empty()),
|
||||||
final response = await connection.receive();
|
);
|
||||||
if (!response.hasEvmWalletList()) {
|
if (!response.hasEvmWalletList()) {
|
||||||
throw Exception(
|
throw Exception(
|
||||||
'Expected EVM wallet list response, got ${response.whichPayload()}',
|
'Expected EVM wallet list response, got ${response.whichPayload()}',
|
||||||
@@ -25,9 +25,9 @@ Future<List<WalletEntry>> listEvmWallets(Connection connection) async {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> createEvmWallet(Connection connection) async {
|
Future<void> createEvmWallet(Connection connection) async {
|
||||||
await connection.send(UserAgentRequest(evmWalletCreate: Empty()));
|
final response = await connection.request(
|
||||||
|
UserAgentRequest(evmWalletCreate: Empty()),
|
||||||
final response = await connection.receive();
|
);
|
||||||
if (!response.hasEvmWalletCreate()) {
|
if (!response.hasEvmWalletCreate()) {
|
||||||
throw Exception(
|
throw Exception(
|
||||||
'Expected EVM wallet create response, got ${response.whichPayload()}',
|
'Expected EVM wallet create response, got ${response.whichPayload()}',
|
||||||
|
|||||||
@@ -13,9 +13,9 @@ Future<List<GrantEntry>> listEvmGrants(
|
|||||||
request.walletId = walletId;
|
request.walletId = walletId;
|
||||||
}
|
}
|
||||||
|
|
||||||
await connection.send(UserAgentRequest(evmGrantList: request));
|
final response = await connection.request(
|
||||||
|
UserAgentRequest(evmGrantList: request),
|
||||||
final response = await connection.receive();
|
);
|
||||||
if (!response.hasEvmGrantList()) {
|
if (!response.hasEvmGrantList()) {
|
||||||
throw Exception(
|
throw Exception(
|
||||||
'Expected EVM grant list response, got ${response.whichPayload()}',
|
'Expected EVM grant list response, got ${response.whichPayload()}',
|
||||||
@@ -45,7 +45,7 @@ Future<int> createEvmGrant(
|
|||||||
TransactionRateLimit? rateLimit,
|
TransactionRateLimit? rateLimit,
|
||||||
required SpecificGrant specific,
|
required SpecificGrant specific,
|
||||||
}) async {
|
}) async {
|
||||||
await connection.send(
|
final response = await connection.request(
|
||||||
UserAgentRequest(
|
UserAgentRequest(
|
||||||
evmGrantCreate: EvmGrantCreateRequest(
|
evmGrantCreate: EvmGrantCreateRequest(
|
||||||
clientId: clientId,
|
clientId: clientId,
|
||||||
@@ -62,8 +62,6 @@ Future<int> createEvmGrant(
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
final response = await connection.receive();
|
|
||||||
if (!response.hasEvmGrantCreate()) {
|
if (!response.hasEvmGrantCreate()) {
|
||||||
throw Exception(
|
throw Exception(
|
||||||
'Expected EVM grant create response, got ${response.whichPayload()}',
|
'Expected EVM grant create response, got ${response.whichPayload()}',
|
||||||
@@ -82,11 +80,9 @@ Future<int> createEvmGrant(
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> deleteEvmGrant(Connection connection, int grantId) async {
|
Future<void> deleteEvmGrant(Connection connection, int grantId) async {
|
||||||
await connection.send(
|
final response = await connection.request(
|
||||||
UserAgentRequest(evmGrantDelete: EvmGrantDeleteRequest(grantId: grantId)),
|
UserAgentRequest(evmGrantDelete: EvmGrantDeleteRequest(grantId: grantId)),
|
||||||
);
|
);
|
||||||
|
|
||||||
final response = await connection.receive();
|
|
||||||
if (!response.hasEvmGrantDelete()) {
|
if (!response.hasEvmGrantDelete()) {
|
||||||
throw Exception(
|
throw Exception(
|
||||||
'Expected EVM grant delete response, got ${response.whichPayload()}',
|
'Expected EVM grant delete response, got ${response.whichPayload()}',
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ Future<BootstrapResult> bootstrapVault(
|
|||||||
) async {
|
) async {
|
||||||
final encryptedKey = await _encryptVaultKeyMaterial(connection, password);
|
final encryptedKey = await _encryptVaultKeyMaterial(connection, password);
|
||||||
|
|
||||||
await connection.send(
|
final response = await connection.request(
|
||||||
UserAgentRequest(
|
UserAgentRequest(
|
||||||
bootstrapEncryptedKey: BootstrapEncryptedKey(
|
bootstrapEncryptedKey: BootstrapEncryptedKey(
|
||||||
nonce: encryptedKey.nonce,
|
nonce: encryptedKey.nonce,
|
||||||
@@ -19,8 +19,6 @@ Future<BootstrapResult> bootstrapVault(
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
final response = await connection.receive();
|
|
||||||
if (!response.hasBootstrapResult()) {
|
if (!response.hasBootstrapResult()) {
|
||||||
throw Exception(
|
throw Exception(
|
||||||
'Expected bootstrap result, got ${response.whichPayload()}',
|
'Expected bootstrap result, got ${response.whichPayload()}',
|
||||||
@@ -33,7 +31,7 @@ Future<BootstrapResult> bootstrapVault(
|
|||||||
Future<UnsealResult> unsealVault(Connection connection, String password) async {
|
Future<UnsealResult> unsealVault(Connection connection, String password) async {
|
||||||
final encryptedKey = await _encryptVaultKeyMaterial(connection, password);
|
final encryptedKey = await _encryptVaultKeyMaterial(connection, password);
|
||||||
|
|
||||||
await connection.send(
|
final response = await connection.request(
|
||||||
UserAgentRequest(
|
UserAgentRequest(
|
||||||
unsealEncryptedKey: UnsealEncryptedKey(
|
unsealEncryptedKey: UnsealEncryptedKey(
|
||||||
nonce: encryptedKey.nonce,
|
nonce: encryptedKey.nonce,
|
||||||
@@ -42,8 +40,6 @@ Future<UnsealResult> unsealVault(Connection connection, String password) async {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
final response = await connection.receive();
|
|
||||||
if (!response.hasUnsealResult()) {
|
if (!response.hasUnsealResult()) {
|
||||||
throw Exception('Expected unseal result, got ${response.whichPayload()}');
|
throw Exception('Expected unseal result, got ${response.whichPayload()}');
|
||||||
}
|
}
|
||||||
@@ -60,11 +56,9 @@ Future<_EncryptedVaultKey> _encryptVaultKeyMaterial(
|
|||||||
final clientKeyPair = await keyExchange.newKeyPair();
|
final clientKeyPair = await keyExchange.newKeyPair();
|
||||||
final clientPublicKey = await clientKeyPair.extractPublicKey();
|
final clientPublicKey = await clientKeyPair.extractPublicKey();
|
||||||
|
|
||||||
await connection.send(
|
final handshakeResponse = await connection.request(
|
||||||
UserAgentRequest(unsealStart: UnsealStart(clientPubkey: clientPublicKey.bytes)),
|
UserAgentRequest(unsealStart: UnsealStart(clientPubkey: clientPublicKey.bytes)),
|
||||||
);
|
);
|
||||||
|
|
||||||
final handshakeResponse = await connection.receive();
|
|
||||||
if (!handshakeResponse.hasUnsealStartResponse()) {
|
if (!handshakeResponse.hasUnsealStartResponse()) {
|
||||||
throw Exception(
|
throw Exception(
|
||||||
'Expected unseal handshake response, got ${handshakeResponse.whichPayload()}',
|
'Expected unseal handshake response, got ${handshakeResponse.whichPayload()}',
|
||||||
|
|||||||
@@ -13,9 +13,10 @@
|
|||||||
import 'dart:core' as $core;
|
import 'dart:core' as $core;
|
||||||
|
|
||||||
import 'package:protobuf/protobuf.dart' as $pb;
|
import 'package:protobuf/protobuf.dart' as $pb;
|
||||||
|
import 'package:protobuf/well_known_types/google/protobuf/empty.pb.dart' as $0;
|
||||||
|
|
||||||
import 'client.pbenum.dart';
|
import 'client.pbenum.dart';
|
||||||
import 'evm.pb.dart' as $0;
|
import 'evm.pb.dart' as $1;
|
||||||
|
|
||||||
export 'package:protobuf/protobuf.dart' show GeneratedMessageGenericExtensions;
|
export 'package:protobuf/protobuf.dart' show GeneratedMessageGenericExtensions;
|
||||||
|
|
||||||
@@ -199,46 +200,10 @@ class AuthChallengeSolution extends $pb.GeneratedMessage {
|
|||||||
void clearSignature() => $_clearField(1);
|
void clearSignature() => $_clearField(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
class AuthOk extends $pb.GeneratedMessage {
|
|
||||||
factory AuthOk() => create();
|
|
||||||
|
|
||||||
AuthOk._();
|
|
||||||
|
|
||||||
factory AuthOk.fromBuffer($core.List<$core.int> data,
|
|
||||||
[$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) =>
|
|
||||||
create()..mergeFromBuffer(data, registry);
|
|
||||||
factory AuthOk.fromJson($core.String json,
|
|
||||||
[$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) =>
|
|
||||||
create()..mergeFromJson(json, registry);
|
|
||||||
|
|
||||||
static final $pb.BuilderInfo _i = $pb.BuilderInfo(
|
|
||||||
_omitMessageNames ? '' : 'AuthOk',
|
|
||||||
package: const $pb.PackageName(_omitMessageNames ? '' : 'arbiter.client'),
|
|
||||||
createEmptyInstance: create)
|
|
||||||
..hasRequiredFields = false;
|
|
||||||
|
|
||||||
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
|
|
||||||
AuthOk clone() => deepCopy();
|
|
||||||
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
|
|
||||||
AuthOk copyWith(void Function(AuthOk) updates) =>
|
|
||||||
super.copyWith((message) => updates(message as AuthOk)) as AuthOk;
|
|
||||||
|
|
||||||
@$core.override
|
|
||||||
$pb.BuilderInfo get info_ => _i;
|
|
||||||
|
|
||||||
@$core.pragma('dart2js:noInline')
|
|
||||||
static AuthOk create() => AuthOk._();
|
|
||||||
@$core.override
|
|
||||||
AuthOk createEmptyInstance() => create();
|
|
||||||
@$core.pragma('dart2js:noInline')
|
|
||||||
static AuthOk getDefault() =>
|
|
||||||
_defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<AuthOk>(create);
|
|
||||||
static AuthOk? _defaultInstance;
|
|
||||||
}
|
|
||||||
|
|
||||||
enum ClientRequest_Payload {
|
enum ClientRequest_Payload {
|
||||||
authChallengeRequest,
|
authChallengeRequest,
|
||||||
authChallengeSolution,
|
authChallengeSolution,
|
||||||
|
queryVaultState,
|
||||||
notSet
|
notSet
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -246,12 +211,16 @@ class ClientRequest extends $pb.GeneratedMessage {
|
|||||||
factory ClientRequest({
|
factory ClientRequest({
|
||||||
AuthChallengeRequest? authChallengeRequest,
|
AuthChallengeRequest? authChallengeRequest,
|
||||||
AuthChallengeSolution? authChallengeSolution,
|
AuthChallengeSolution? authChallengeSolution,
|
||||||
|
$0.Empty? queryVaultState,
|
||||||
|
$core.int? requestId,
|
||||||
}) {
|
}) {
|
||||||
final result = create();
|
final result = create();
|
||||||
if (authChallengeRequest != null)
|
if (authChallengeRequest != null)
|
||||||
result.authChallengeRequest = authChallengeRequest;
|
result.authChallengeRequest = authChallengeRequest;
|
||||||
if (authChallengeSolution != null)
|
if (authChallengeSolution != null)
|
||||||
result.authChallengeSolution = authChallengeSolution;
|
result.authChallengeSolution = authChallengeSolution;
|
||||||
|
if (queryVaultState != null) result.queryVaultState = queryVaultState;
|
||||||
|
if (requestId != null) result.requestId = requestId;
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -268,19 +237,23 @@ class ClientRequest extends $pb.GeneratedMessage {
|
|||||||
_ClientRequest_PayloadByTag = {
|
_ClientRequest_PayloadByTag = {
|
||||||
1: ClientRequest_Payload.authChallengeRequest,
|
1: ClientRequest_Payload.authChallengeRequest,
|
||||||
2: ClientRequest_Payload.authChallengeSolution,
|
2: ClientRequest_Payload.authChallengeSolution,
|
||||||
|
3: ClientRequest_Payload.queryVaultState,
|
||||||
0: ClientRequest_Payload.notSet
|
0: ClientRequest_Payload.notSet
|
||||||
};
|
};
|
||||||
static final $pb.BuilderInfo _i = $pb.BuilderInfo(
|
static final $pb.BuilderInfo _i = $pb.BuilderInfo(
|
||||||
_omitMessageNames ? '' : 'ClientRequest',
|
_omitMessageNames ? '' : 'ClientRequest',
|
||||||
package: const $pb.PackageName(_omitMessageNames ? '' : 'arbiter.client'),
|
package: const $pb.PackageName(_omitMessageNames ? '' : 'arbiter.client'),
|
||||||
createEmptyInstance: create)
|
createEmptyInstance: create)
|
||||||
..oo(0, [1, 2])
|
..oo(0, [1, 2, 3])
|
||||||
..aOM<AuthChallengeRequest>(
|
..aOM<AuthChallengeRequest>(
|
||||||
1, _omitFieldNames ? '' : 'authChallengeRequest',
|
1, _omitFieldNames ? '' : 'authChallengeRequest',
|
||||||
subBuilder: AuthChallengeRequest.create)
|
subBuilder: AuthChallengeRequest.create)
|
||||||
..aOM<AuthChallengeSolution>(
|
..aOM<AuthChallengeSolution>(
|
||||||
2, _omitFieldNames ? '' : 'authChallengeSolution',
|
2, _omitFieldNames ? '' : 'authChallengeSolution',
|
||||||
subBuilder: AuthChallengeSolution.create)
|
subBuilder: AuthChallengeSolution.create)
|
||||||
|
..aOM<$0.Empty>(3, _omitFieldNames ? '' : 'queryVaultState',
|
||||||
|
subBuilder: $0.Empty.create)
|
||||||
|
..aI(4, _omitFieldNames ? '' : 'requestId')
|
||||||
..hasRequiredFields = false;
|
..hasRequiredFields = false;
|
||||||
|
|
||||||
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
|
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
|
||||||
@@ -304,10 +277,12 @@ class ClientRequest extends $pb.GeneratedMessage {
|
|||||||
|
|
||||||
@$pb.TagNumber(1)
|
@$pb.TagNumber(1)
|
||||||
@$pb.TagNumber(2)
|
@$pb.TagNumber(2)
|
||||||
|
@$pb.TagNumber(3)
|
||||||
ClientRequest_Payload whichPayload() =>
|
ClientRequest_Payload whichPayload() =>
|
||||||
_ClientRequest_PayloadByTag[$_whichOneof(0)]!;
|
_ClientRequest_PayloadByTag[$_whichOneof(0)]!;
|
||||||
@$pb.TagNumber(1)
|
@$pb.TagNumber(1)
|
||||||
@$pb.TagNumber(2)
|
@$pb.TagNumber(2)
|
||||||
|
@$pb.TagNumber(3)
|
||||||
void clearPayload() => $_clearField($_whichOneof(0));
|
void clearPayload() => $_clearField($_whichOneof(0));
|
||||||
|
|
||||||
@$pb.TagNumber(1)
|
@$pb.TagNumber(1)
|
||||||
@@ -332,89 +307,55 @@ class ClientRequest extends $pb.GeneratedMessage {
|
|||||||
void clearAuthChallengeSolution() => $_clearField(2);
|
void clearAuthChallengeSolution() => $_clearField(2);
|
||||||
@$pb.TagNumber(2)
|
@$pb.TagNumber(2)
|
||||||
AuthChallengeSolution ensureAuthChallengeSolution() => $_ensure(1);
|
AuthChallengeSolution ensureAuthChallengeSolution() => $_ensure(1);
|
||||||
}
|
|
||||||
|
|
||||||
class ClientConnectError extends $pb.GeneratedMessage {
|
@$pb.TagNumber(3)
|
||||||
factory ClientConnectError({
|
$0.Empty get queryVaultState => $_getN(2);
|
||||||
ClientConnectError_Code? code,
|
@$pb.TagNumber(3)
|
||||||
}) {
|
set queryVaultState($0.Empty value) => $_setField(3, value);
|
||||||
final result = create();
|
@$pb.TagNumber(3)
|
||||||
if (code != null) result.code = code;
|
$core.bool hasQueryVaultState() => $_has(2);
|
||||||
return result;
|
@$pb.TagNumber(3)
|
||||||
}
|
void clearQueryVaultState() => $_clearField(3);
|
||||||
|
@$pb.TagNumber(3)
|
||||||
|
$0.Empty ensureQueryVaultState() => $_ensure(2);
|
||||||
|
|
||||||
ClientConnectError._();
|
@$pb.TagNumber(4)
|
||||||
|
$core.int get requestId => $_getIZ(3);
|
||||||
factory ClientConnectError.fromBuffer($core.List<$core.int> data,
|
@$pb.TagNumber(4)
|
||||||
[$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) =>
|
set requestId($core.int value) => $_setSignedInt32(3, value);
|
||||||
create()..mergeFromBuffer(data, registry);
|
@$pb.TagNumber(4)
|
||||||
factory ClientConnectError.fromJson($core.String json,
|
$core.bool hasRequestId() => $_has(3);
|
||||||
[$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) =>
|
@$pb.TagNumber(4)
|
||||||
create()..mergeFromJson(json, registry);
|
void clearRequestId() => $_clearField(4);
|
||||||
|
|
||||||
static final $pb.BuilderInfo _i = $pb.BuilderInfo(
|
|
||||||
_omitMessageNames ? '' : 'ClientConnectError',
|
|
||||||
package: const $pb.PackageName(_omitMessageNames ? '' : 'arbiter.client'),
|
|
||||||
createEmptyInstance: create)
|
|
||||||
..aE<ClientConnectError_Code>(1, _omitFieldNames ? '' : 'code',
|
|
||||||
enumValues: ClientConnectError_Code.values)
|
|
||||||
..hasRequiredFields = false;
|
|
||||||
|
|
||||||
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
|
|
||||||
ClientConnectError clone() => deepCopy();
|
|
||||||
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
|
|
||||||
ClientConnectError copyWith(void Function(ClientConnectError) updates) =>
|
|
||||||
super.copyWith((message) => updates(message as ClientConnectError))
|
|
||||||
as ClientConnectError;
|
|
||||||
|
|
||||||
@$core.override
|
|
||||||
$pb.BuilderInfo get info_ => _i;
|
|
||||||
|
|
||||||
@$core.pragma('dart2js:noInline')
|
|
||||||
static ClientConnectError create() => ClientConnectError._();
|
|
||||||
@$core.override
|
|
||||||
ClientConnectError createEmptyInstance() => create();
|
|
||||||
@$core.pragma('dart2js:noInline')
|
|
||||||
static ClientConnectError getDefault() => _defaultInstance ??=
|
|
||||||
$pb.GeneratedMessage.$_defaultFor<ClientConnectError>(create);
|
|
||||||
static ClientConnectError? _defaultInstance;
|
|
||||||
|
|
||||||
@$pb.TagNumber(1)
|
|
||||||
ClientConnectError_Code get code => $_getN(0);
|
|
||||||
@$pb.TagNumber(1)
|
|
||||||
set code(ClientConnectError_Code value) => $_setField(1, value);
|
|
||||||
@$pb.TagNumber(1)
|
|
||||||
$core.bool hasCode() => $_has(0);
|
|
||||||
@$pb.TagNumber(1)
|
|
||||||
void clearCode() => $_clearField(1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
enum ClientResponse_Payload {
|
enum ClientResponse_Payload {
|
||||||
authChallenge,
|
authChallenge,
|
||||||
authOk,
|
authResult,
|
||||||
evmSignTransaction,
|
evmSignTransaction,
|
||||||
evmAnalyzeTransaction,
|
evmAnalyzeTransaction,
|
||||||
clientConnectError,
|
vaultState,
|
||||||
notSet
|
notSet
|
||||||
}
|
}
|
||||||
|
|
||||||
class ClientResponse extends $pb.GeneratedMessage {
|
class ClientResponse extends $pb.GeneratedMessage {
|
||||||
factory ClientResponse({
|
factory ClientResponse({
|
||||||
AuthChallenge? authChallenge,
|
AuthChallenge? authChallenge,
|
||||||
AuthOk? authOk,
|
AuthResult? authResult,
|
||||||
$0.EvmSignTransactionResponse? evmSignTransaction,
|
$1.EvmSignTransactionResponse? evmSignTransaction,
|
||||||
$0.EvmAnalyzeTransactionResponse? evmAnalyzeTransaction,
|
$1.EvmAnalyzeTransactionResponse? evmAnalyzeTransaction,
|
||||||
ClientConnectError? clientConnectError,
|
VaultState? vaultState,
|
||||||
|
$core.int? requestId,
|
||||||
}) {
|
}) {
|
||||||
final result = create();
|
final result = create();
|
||||||
if (authChallenge != null) result.authChallenge = authChallenge;
|
if (authChallenge != null) result.authChallenge = authChallenge;
|
||||||
if (authOk != null) result.authOk = authOk;
|
if (authResult != null) result.authResult = authResult;
|
||||||
if (evmSignTransaction != null)
|
if (evmSignTransaction != null)
|
||||||
result.evmSignTransaction = evmSignTransaction;
|
result.evmSignTransaction = evmSignTransaction;
|
||||||
if (evmAnalyzeTransaction != null)
|
if (evmAnalyzeTransaction != null)
|
||||||
result.evmAnalyzeTransaction = evmAnalyzeTransaction;
|
result.evmAnalyzeTransaction = evmAnalyzeTransaction;
|
||||||
if (clientConnectError != null)
|
if (vaultState != null) result.vaultState = vaultState;
|
||||||
result.clientConnectError = clientConnectError;
|
if (requestId != null) result.requestId = requestId;
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -430,28 +371,30 @@ class ClientResponse extends $pb.GeneratedMessage {
|
|||||||
static const $core.Map<$core.int, ClientResponse_Payload>
|
static const $core.Map<$core.int, ClientResponse_Payload>
|
||||||
_ClientResponse_PayloadByTag = {
|
_ClientResponse_PayloadByTag = {
|
||||||
1: ClientResponse_Payload.authChallenge,
|
1: ClientResponse_Payload.authChallenge,
|
||||||
2: ClientResponse_Payload.authOk,
|
2: ClientResponse_Payload.authResult,
|
||||||
3: ClientResponse_Payload.evmSignTransaction,
|
3: ClientResponse_Payload.evmSignTransaction,
|
||||||
4: ClientResponse_Payload.evmAnalyzeTransaction,
|
4: ClientResponse_Payload.evmAnalyzeTransaction,
|
||||||
5: ClientResponse_Payload.clientConnectError,
|
6: ClientResponse_Payload.vaultState,
|
||||||
0: ClientResponse_Payload.notSet
|
0: ClientResponse_Payload.notSet
|
||||||
};
|
};
|
||||||
static final $pb.BuilderInfo _i = $pb.BuilderInfo(
|
static final $pb.BuilderInfo _i = $pb.BuilderInfo(
|
||||||
_omitMessageNames ? '' : 'ClientResponse',
|
_omitMessageNames ? '' : 'ClientResponse',
|
||||||
package: const $pb.PackageName(_omitMessageNames ? '' : 'arbiter.client'),
|
package: const $pb.PackageName(_omitMessageNames ? '' : 'arbiter.client'),
|
||||||
createEmptyInstance: create)
|
createEmptyInstance: create)
|
||||||
..oo(0, [1, 2, 3, 4, 5])
|
..oo(0, [1, 2, 3, 4, 6])
|
||||||
..aOM<AuthChallenge>(1, _omitFieldNames ? '' : 'authChallenge',
|
..aOM<AuthChallenge>(1, _omitFieldNames ? '' : 'authChallenge',
|
||||||
subBuilder: AuthChallenge.create)
|
subBuilder: AuthChallenge.create)
|
||||||
..aOM<AuthOk>(2, _omitFieldNames ? '' : 'authOk', subBuilder: AuthOk.create)
|
..aE<AuthResult>(2, _omitFieldNames ? '' : 'authResult',
|
||||||
..aOM<$0.EvmSignTransactionResponse>(
|
enumValues: AuthResult.values)
|
||||||
|
..aOM<$1.EvmSignTransactionResponse>(
|
||||||
3, _omitFieldNames ? '' : 'evmSignTransaction',
|
3, _omitFieldNames ? '' : 'evmSignTransaction',
|
||||||
subBuilder: $0.EvmSignTransactionResponse.create)
|
subBuilder: $1.EvmSignTransactionResponse.create)
|
||||||
..aOM<$0.EvmAnalyzeTransactionResponse>(
|
..aOM<$1.EvmAnalyzeTransactionResponse>(
|
||||||
4, _omitFieldNames ? '' : 'evmAnalyzeTransaction',
|
4, _omitFieldNames ? '' : 'evmAnalyzeTransaction',
|
||||||
subBuilder: $0.EvmAnalyzeTransactionResponse.create)
|
subBuilder: $1.EvmAnalyzeTransactionResponse.create)
|
||||||
..aOM<ClientConnectError>(5, _omitFieldNames ? '' : 'clientConnectError',
|
..aE<VaultState>(6, _omitFieldNames ? '' : 'vaultState',
|
||||||
subBuilder: ClientConnectError.create)
|
enumValues: VaultState.values)
|
||||||
|
..aI(7, _omitFieldNames ? '' : 'requestId')
|
||||||
..hasRequiredFields = false;
|
..hasRequiredFields = false;
|
||||||
|
|
||||||
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
|
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
|
||||||
@@ -477,14 +420,14 @@ class ClientResponse extends $pb.GeneratedMessage {
|
|||||||
@$pb.TagNumber(2)
|
@$pb.TagNumber(2)
|
||||||
@$pb.TagNumber(3)
|
@$pb.TagNumber(3)
|
||||||
@$pb.TagNumber(4)
|
@$pb.TagNumber(4)
|
||||||
@$pb.TagNumber(5)
|
@$pb.TagNumber(6)
|
||||||
ClientResponse_Payload whichPayload() =>
|
ClientResponse_Payload whichPayload() =>
|
||||||
_ClientResponse_PayloadByTag[$_whichOneof(0)]!;
|
_ClientResponse_PayloadByTag[$_whichOneof(0)]!;
|
||||||
@$pb.TagNumber(1)
|
@$pb.TagNumber(1)
|
||||||
@$pb.TagNumber(2)
|
@$pb.TagNumber(2)
|
||||||
@$pb.TagNumber(3)
|
@$pb.TagNumber(3)
|
||||||
@$pb.TagNumber(4)
|
@$pb.TagNumber(4)
|
||||||
@$pb.TagNumber(5)
|
@$pb.TagNumber(6)
|
||||||
void clearPayload() => $_clearField($_whichOneof(0));
|
void clearPayload() => $_clearField($_whichOneof(0));
|
||||||
|
|
||||||
@$pb.TagNumber(1)
|
@$pb.TagNumber(1)
|
||||||
@@ -499,50 +442,55 @@ class ClientResponse extends $pb.GeneratedMessage {
|
|||||||
AuthChallenge ensureAuthChallenge() => $_ensure(0);
|
AuthChallenge ensureAuthChallenge() => $_ensure(0);
|
||||||
|
|
||||||
@$pb.TagNumber(2)
|
@$pb.TagNumber(2)
|
||||||
AuthOk get authOk => $_getN(1);
|
AuthResult get authResult => $_getN(1);
|
||||||
@$pb.TagNumber(2)
|
@$pb.TagNumber(2)
|
||||||
set authOk(AuthOk value) => $_setField(2, value);
|
set authResult(AuthResult value) => $_setField(2, value);
|
||||||
@$pb.TagNumber(2)
|
@$pb.TagNumber(2)
|
||||||
$core.bool hasAuthOk() => $_has(1);
|
$core.bool hasAuthResult() => $_has(1);
|
||||||
@$pb.TagNumber(2)
|
@$pb.TagNumber(2)
|
||||||
void clearAuthOk() => $_clearField(2);
|
void clearAuthResult() => $_clearField(2);
|
||||||
@$pb.TagNumber(2)
|
|
||||||
AuthOk ensureAuthOk() => $_ensure(1);
|
|
||||||
|
|
||||||
@$pb.TagNumber(3)
|
@$pb.TagNumber(3)
|
||||||
$0.EvmSignTransactionResponse get evmSignTransaction => $_getN(2);
|
$1.EvmSignTransactionResponse get evmSignTransaction => $_getN(2);
|
||||||
@$pb.TagNumber(3)
|
@$pb.TagNumber(3)
|
||||||
set evmSignTransaction($0.EvmSignTransactionResponse value) =>
|
set evmSignTransaction($1.EvmSignTransactionResponse value) =>
|
||||||
$_setField(3, value);
|
$_setField(3, value);
|
||||||
@$pb.TagNumber(3)
|
@$pb.TagNumber(3)
|
||||||
$core.bool hasEvmSignTransaction() => $_has(2);
|
$core.bool hasEvmSignTransaction() => $_has(2);
|
||||||
@$pb.TagNumber(3)
|
@$pb.TagNumber(3)
|
||||||
void clearEvmSignTransaction() => $_clearField(3);
|
void clearEvmSignTransaction() => $_clearField(3);
|
||||||
@$pb.TagNumber(3)
|
@$pb.TagNumber(3)
|
||||||
$0.EvmSignTransactionResponse ensureEvmSignTransaction() => $_ensure(2);
|
$1.EvmSignTransactionResponse ensureEvmSignTransaction() => $_ensure(2);
|
||||||
|
|
||||||
@$pb.TagNumber(4)
|
@$pb.TagNumber(4)
|
||||||
$0.EvmAnalyzeTransactionResponse get evmAnalyzeTransaction => $_getN(3);
|
$1.EvmAnalyzeTransactionResponse get evmAnalyzeTransaction => $_getN(3);
|
||||||
@$pb.TagNumber(4)
|
@$pb.TagNumber(4)
|
||||||
set evmAnalyzeTransaction($0.EvmAnalyzeTransactionResponse value) =>
|
set evmAnalyzeTransaction($1.EvmAnalyzeTransactionResponse value) =>
|
||||||
$_setField(4, value);
|
$_setField(4, value);
|
||||||
@$pb.TagNumber(4)
|
@$pb.TagNumber(4)
|
||||||
$core.bool hasEvmAnalyzeTransaction() => $_has(3);
|
$core.bool hasEvmAnalyzeTransaction() => $_has(3);
|
||||||
@$pb.TagNumber(4)
|
@$pb.TagNumber(4)
|
||||||
void clearEvmAnalyzeTransaction() => $_clearField(4);
|
void clearEvmAnalyzeTransaction() => $_clearField(4);
|
||||||
@$pb.TagNumber(4)
|
@$pb.TagNumber(4)
|
||||||
$0.EvmAnalyzeTransactionResponse ensureEvmAnalyzeTransaction() => $_ensure(3);
|
$1.EvmAnalyzeTransactionResponse ensureEvmAnalyzeTransaction() => $_ensure(3);
|
||||||
|
|
||||||
@$pb.TagNumber(5)
|
@$pb.TagNumber(6)
|
||||||
ClientConnectError get clientConnectError => $_getN(4);
|
VaultState get vaultState => $_getN(4);
|
||||||
@$pb.TagNumber(5)
|
@$pb.TagNumber(6)
|
||||||
set clientConnectError(ClientConnectError value) => $_setField(5, value);
|
set vaultState(VaultState value) => $_setField(6, value);
|
||||||
@$pb.TagNumber(5)
|
@$pb.TagNumber(6)
|
||||||
$core.bool hasClientConnectError() => $_has(4);
|
$core.bool hasVaultState() => $_has(4);
|
||||||
@$pb.TagNumber(5)
|
@$pb.TagNumber(6)
|
||||||
void clearClientConnectError() => $_clearField(5);
|
void clearVaultState() => $_clearField(6);
|
||||||
@$pb.TagNumber(5)
|
|
||||||
ClientConnectError ensureClientConnectError() => $_ensure(4);
|
@$pb.TagNumber(7)
|
||||||
|
$core.int get requestId => $_getIZ(5);
|
||||||
|
@$pb.TagNumber(7)
|
||||||
|
set requestId($core.int value) => $_setSignedInt32(5, value);
|
||||||
|
@$pb.TagNumber(7)
|
||||||
|
$core.bool hasRequestId() => $_has(5);
|
||||||
|
@$pb.TagNumber(7)
|
||||||
|
void clearRequestId() => $_clearField(7);
|
||||||
}
|
}
|
||||||
|
|
||||||
const $core.bool _omitFieldNames =
|
const $core.bool _omitFieldNames =
|
||||||
|
|||||||
@@ -14,28 +14,66 @@ import 'dart:core' as $core;
|
|||||||
|
|
||||||
import 'package:protobuf/protobuf.dart' as $pb;
|
import 'package:protobuf/protobuf.dart' as $pb;
|
||||||
|
|
||||||
class ClientConnectError_Code extends $pb.ProtobufEnum {
|
class AuthResult extends $pb.ProtobufEnum {
|
||||||
static const ClientConnectError_Code UNKNOWN =
|
static const AuthResult AUTH_RESULT_UNSPECIFIED =
|
||||||
ClientConnectError_Code._(0, _omitEnumNames ? '' : 'UNKNOWN');
|
AuthResult._(0, _omitEnumNames ? '' : 'AUTH_RESULT_UNSPECIFIED');
|
||||||
static const ClientConnectError_Code APPROVAL_DENIED =
|
static const AuthResult AUTH_RESULT_SUCCESS =
|
||||||
ClientConnectError_Code._(1, _omitEnumNames ? '' : 'APPROVAL_DENIED');
|
AuthResult._(1, _omitEnumNames ? '' : 'AUTH_RESULT_SUCCESS');
|
||||||
static const ClientConnectError_Code NO_USER_AGENTS_ONLINE =
|
static const AuthResult AUTH_RESULT_INVALID_KEY =
|
||||||
ClientConnectError_Code._(
|
AuthResult._(2, _omitEnumNames ? '' : 'AUTH_RESULT_INVALID_KEY');
|
||||||
2, _omitEnumNames ? '' : 'NO_USER_AGENTS_ONLINE');
|
static const AuthResult AUTH_RESULT_INVALID_SIGNATURE =
|
||||||
|
AuthResult._(3, _omitEnumNames ? '' : 'AUTH_RESULT_INVALID_SIGNATURE');
|
||||||
|
static const AuthResult AUTH_RESULT_APPROVAL_DENIED =
|
||||||
|
AuthResult._(4, _omitEnumNames ? '' : 'AUTH_RESULT_APPROVAL_DENIED');
|
||||||
|
static const AuthResult AUTH_RESULT_NO_USER_AGENTS_ONLINE = AuthResult._(
|
||||||
|
5, _omitEnumNames ? '' : 'AUTH_RESULT_NO_USER_AGENTS_ONLINE');
|
||||||
|
static const AuthResult AUTH_RESULT_INTERNAL =
|
||||||
|
AuthResult._(6, _omitEnumNames ? '' : 'AUTH_RESULT_INTERNAL');
|
||||||
|
|
||||||
static const $core.List<ClientConnectError_Code> values =
|
static const $core.List<AuthResult> values = <AuthResult>[
|
||||||
<ClientConnectError_Code>[
|
AUTH_RESULT_UNSPECIFIED,
|
||||||
UNKNOWN,
|
AUTH_RESULT_SUCCESS,
|
||||||
APPROVAL_DENIED,
|
AUTH_RESULT_INVALID_KEY,
|
||||||
NO_USER_AGENTS_ONLINE,
|
AUTH_RESULT_INVALID_SIGNATURE,
|
||||||
|
AUTH_RESULT_APPROVAL_DENIED,
|
||||||
|
AUTH_RESULT_NO_USER_AGENTS_ONLINE,
|
||||||
|
AUTH_RESULT_INTERNAL,
|
||||||
];
|
];
|
||||||
|
|
||||||
static final $core.List<ClientConnectError_Code?> _byValue =
|
static final $core.List<AuthResult?> _byValue =
|
||||||
$pb.ProtobufEnum.$_initByValueList(values, 2);
|
$pb.ProtobufEnum.$_initByValueList(values, 6);
|
||||||
static ClientConnectError_Code? valueOf($core.int value) =>
|
static AuthResult? valueOf($core.int value) =>
|
||||||
value < 0 || value >= _byValue.length ? null : _byValue[value];
|
value < 0 || value >= _byValue.length ? null : _byValue[value];
|
||||||
|
|
||||||
const ClientConnectError_Code._(super.value, super.name);
|
const AuthResult._(super.value, super.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
class VaultState extends $pb.ProtobufEnum {
|
||||||
|
static const VaultState VAULT_STATE_UNSPECIFIED =
|
||||||
|
VaultState._(0, _omitEnumNames ? '' : 'VAULT_STATE_UNSPECIFIED');
|
||||||
|
static const VaultState VAULT_STATE_UNBOOTSTRAPPED =
|
||||||
|
VaultState._(1, _omitEnumNames ? '' : 'VAULT_STATE_UNBOOTSTRAPPED');
|
||||||
|
static const VaultState VAULT_STATE_SEALED =
|
||||||
|
VaultState._(2, _omitEnumNames ? '' : 'VAULT_STATE_SEALED');
|
||||||
|
static const VaultState VAULT_STATE_UNSEALED =
|
||||||
|
VaultState._(3, _omitEnumNames ? '' : 'VAULT_STATE_UNSEALED');
|
||||||
|
static const VaultState VAULT_STATE_ERROR =
|
||||||
|
VaultState._(4, _omitEnumNames ? '' : 'VAULT_STATE_ERROR');
|
||||||
|
|
||||||
|
static const $core.List<VaultState> values = <VaultState>[
|
||||||
|
VAULT_STATE_UNSPECIFIED,
|
||||||
|
VAULT_STATE_UNBOOTSTRAPPED,
|
||||||
|
VAULT_STATE_SEALED,
|
||||||
|
VAULT_STATE_UNSEALED,
|
||||||
|
VAULT_STATE_ERROR,
|
||||||
|
];
|
||||||
|
|
||||||
|
static final $core.List<VaultState?> _byValue =
|
||||||
|
$pb.ProtobufEnum.$_initByValueList(values, 4);
|
||||||
|
static VaultState? valueOf($core.int value) =>
|
||||||
|
value < 0 || value >= _byValue.length ? null : _byValue[value];
|
||||||
|
|
||||||
|
const VaultState._(super.value, super.name);
|
||||||
}
|
}
|
||||||
|
|
||||||
const $core.bool _omitEnumNames =
|
const $core.bool _omitEnumNames =
|
||||||
|
|||||||
@@ -15,6 +15,46 @@ import 'dart:convert' as $convert;
|
|||||||
import 'dart:core' as $core;
|
import 'dart:core' as $core;
|
||||||
import 'dart:typed_data' as $typed_data;
|
import 'dart:typed_data' as $typed_data;
|
||||||
|
|
||||||
|
@$core.Deprecated('Use authResultDescriptor instead')
|
||||||
|
const AuthResult$json = {
|
||||||
|
'1': 'AuthResult',
|
||||||
|
'2': [
|
||||||
|
{'1': 'AUTH_RESULT_UNSPECIFIED', '2': 0},
|
||||||
|
{'1': 'AUTH_RESULT_SUCCESS', '2': 1},
|
||||||
|
{'1': 'AUTH_RESULT_INVALID_KEY', '2': 2},
|
||||||
|
{'1': 'AUTH_RESULT_INVALID_SIGNATURE', '2': 3},
|
||||||
|
{'1': 'AUTH_RESULT_APPROVAL_DENIED', '2': 4},
|
||||||
|
{'1': 'AUTH_RESULT_NO_USER_AGENTS_ONLINE', '2': 5},
|
||||||
|
{'1': 'AUTH_RESULT_INTERNAL', '2': 6},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Descriptor for `AuthResult`. Decode as a `google.protobuf.EnumDescriptorProto`.
|
||||||
|
final $typed_data.Uint8List authResultDescriptor = $convert.base64Decode(
|
||||||
|
'CgpBdXRoUmVzdWx0EhsKF0FVVEhfUkVTVUxUX1VOU1BFQ0lGSUVEEAASFwoTQVVUSF9SRVNVTF'
|
||||||
|
'RfU1VDQ0VTUxABEhsKF0FVVEhfUkVTVUxUX0lOVkFMSURfS0VZEAISIQodQVVUSF9SRVNVTFRf'
|
||||||
|
'SU5WQUxJRF9TSUdOQVRVUkUQAxIfChtBVVRIX1JFU1VMVF9BUFBST1ZBTF9ERU5JRUQQBBIlCi'
|
||||||
|
'FBVVRIX1JFU1VMVF9OT19VU0VSX0FHRU5UU19PTkxJTkUQBRIYChRBVVRIX1JFU1VMVF9JTlRF'
|
||||||
|
'Uk5BTBAG');
|
||||||
|
|
||||||
|
@$core.Deprecated('Use vaultStateDescriptor instead')
|
||||||
|
const VaultState$json = {
|
||||||
|
'1': 'VaultState',
|
||||||
|
'2': [
|
||||||
|
{'1': 'VAULT_STATE_UNSPECIFIED', '2': 0},
|
||||||
|
{'1': 'VAULT_STATE_UNBOOTSTRAPPED', '2': 1},
|
||||||
|
{'1': 'VAULT_STATE_SEALED', '2': 2},
|
||||||
|
{'1': 'VAULT_STATE_UNSEALED', '2': 3},
|
||||||
|
{'1': 'VAULT_STATE_ERROR', '2': 4},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Descriptor for `VaultState`. Decode as a `google.protobuf.EnumDescriptorProto`.
|
||||||
|
final $typed_data.Uint8List vaultStateDescriptor = $convert.base64Decode(
|
||||||
|
'CgpWYXVsdFN0YXRlEhsKF1ZBVUxUX1NUQVRFX1VOU1BFQ0lGSUVEEAASHgoaVkFVTFRfU1RBVE'
|
||||||
|
'VfVU5CT09UU1RSQVBQRUQQARIWChJWQVVMVF9TVEFURV9TRUFMRUQQAhIYChRWQVVMVF9TVEFU'
|
||||||
|
'RV9VTlNFQUxFRBADEhUKEVZBVUxUX1NUQVRFX0VSUk9SEAQ=');
|
||||||
|
|
||||||
@$core.Deprecated('Use authChallengeRequestDescriptor instead')
|
@$core.Deprecated('Use authChallengeRequestDescriptor instead')
|
||||||
const AuthChallengeRequest$json = {
|
const AuthChallengeRequest$json = {
|
||||||
'1': 'AuthChallengeRequest',
|
'1': 'AuthChallengeRequest',
|
||||||
@@ -54,19 +94,11 @@ const AuthChallengeSolution$json = {
|
|||||||
final $typed_data.Uint8List authChallengeSolutionDescriptor = $convert.base64Decode(
|
final $typed_data.Uint8List authChallengeSolutionDescriptor = $convert.base64Decode(
|
||||||
'ChVBdXRoQ2hhbGxlbmdlU29sdXRpb24SHAoJc2lnbmF0dXJlGAEgASgMUglzaWduYXR1cmU=');
|
'ChVBdXRoQ2hhbGxlbmdlU29sdXRpb24SHAoJc2lnbmF0dXJlGAEgASgMUglzaWduYXR1cmU=');
|
||||||
|
|
||||||
@$core.Deprecated('Use authOkDescriptor instead')
|
|
||||||
const AuthOk$json = {
|
|
||||||
'1': 'AuthOk',
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Descriptor for `AuthOk`. Decode as a `google.protobuf.DescriptorProto`.
|
|
||||||
final $typed_data.Uint8List authOkDescriptor =
|
|
||||||
$convert.base64Decode('CgZBdXRoT2s=');
|
|
||||||
|
|
||||||
@$core.Deprecated('Use clientRequestDescriptor instead')
|
@$core.Deprecated('Use clientRequestDescriptor instead')
|
||||||
const ClientRequest$json = {
|
const ClientRequest$json = {
|
||||||
'1': 'ClientRequest',
|
'1': 'ClientRequest',
|
||||||
'2': [
|
'2': [
|
||||||
|
{'1': 'request_id', '3': 4, '4': 1, '5': 5, '10': 'requestId'},
|
||||||
{
|
{
|
||||||
'1': 'auth_challenge_request',
|
'1': 'auth_challenge_request',
|
||||||
'3': 1,
|
'3': 1,
|
||||||
@@ -85,6 +117,15 @@ const ClientRequest$json = {
|
|||||||
'9': 0,
|
'9': 0,
|
||||||
'10': 'authChallengeSolution'
|
'10': 'authChallengeSolution'
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
'1': 'query_vault_state',
|
||||||
|
'3': 3,
|
||||||
|
'4': 1,
|
||||||
|
'5': 11,
|
||||||
|
'6': '.google.protobuf.Empty',
|
||||||
|
'9': 0,
|
||||||
|
'10': 'queryVaultState'
|
||||||
|
},
|
||||||
],
|
],
|
||||||
'8': [
|
'8': [
|
||||||
{'1': 'payload'},
|
{'1': 'payload'},
|
||||||
@@ -93,47 +134,26 @@ const ClientRequest$json = {
|
|||||||
|
|
||||||
/// Descriptor for `ClientRequest`. Decode as a `google.protobuf.DescriptorProto`.
|
/// Descriptor for `ClientRequest`. Decode as a `google.protobuf.DescriptorProto`.
|
||||||
final $typed_data.Uint8List clientRequestDescriptor = $convert.base64Decode(
|
final $typed_data.Uint8List clientRequestDescriptor = $convert.base64Decode(
|
||||||
'Cg1DbGllbnRSZXF1ZXN0ElwKFmF1dGhfY2hhbGxlbmdlX3JlcXVlc3QYASABKAsyJC5hcmJpdG'
|
'Cg1DbGllbnRSZXF1ZXN0Eh0KCnJlcXVlc3RfaWQYBCABKAVSCXJlcXVlc3RJZBJcChZhdXRoX2'
|
||||||
'VyLmNsaWVudC5BdXRoQ2hhbGxlbmdlUmVxdWVzdEgAUhRhdXRoQ2hhbGxlbmdlUmVxdWVzdBJf'
|
'NoYWxsZW5nZV9yZXF1ZXN0GAEgASgLMiQuYXJiaXRlci5jbGllbnQuQXV0aENoYWxsZW5nZVJl'
|
||||||
'ChdhdXRoX2NoYWxsZW5nZV9zb2x1dGlvbhgCIAEoCzIlLmFyYml0ZXIuY2xpZW50LkF1dGhDaG'
|
'cXVlc3RIAFIUYXV0aENoYWxsZW5nZVJlcXVlc3QSXwoXYXV0aF9jaGFsbGVuZ2Vfc29sdXRpb2'
|
||||||
'FsbGVuZ2VTb2x1dGlvbkgAUhVhdXRoQ2hhbGxlbmdlU29sdXRpb25CCQoHcGF5bG9hZA==');
|
'4YAiABKAsyJS5hcmJpdGVyLmNsaWVudC5BdXRoQ2hhbGxlbmdlU29sdXRpb25IAFIVYXV0aENo'
|
||||||
|
'YWxsZW5nZVNvbHV0aW9uEkQKEXF1ZXJ5X3ZhdWx0X3N0YXRlGAMgASgLMhYuZ29vZ2xlLnByb3'
|
||||||
@$core.Deprecated('Use clientConnectErrorDescriptor instead')
|
'RvYnVmLkVtcHR5SABSD3F1ZXJ5VmF1bHRTdGF0ZUIJCgdwYXlsb2Fk');
|
||||||
const ClientConnectError$json = {
|
|
||||||
'1': 'ClientConnectError',
|
|
||||||
'2': [
|
|
||||||
{
|
|
||||||
'1': 'code',
|
|
||||||
'3': 1,
|
|
||||||
'4': 1,
|
|
||||||
'5': 14,
|
|
||||||
'6': '.arbiter.client.ClientConnectError.Code',
|
|
||||||
'10': 'code'
|
|
||||||
},
|
|
||||||
],
|
|
||||||
'4': [ClientConnectError_Code$json],
|
|
||||||
};
|
|
||||||
|
|
||||||
@$core.Deprecated('Use clientConnectErrorDescriptor instead')
|
|
||||||
const ClientConnectError_Code$json = {
|
|
||||||
'1': 'Code',
|
|
||||||
'2': [
|
|
||||||
{'1': 'UNKNOWN', '2': 0},
|
|
||||||
{'1': 'APPROVAL_DENIED', '2': 1},
|
|
||||||
{'1': 'NO_USER_AGENTS_ONLINE', '2': 2},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Descriptor for `ClientConnectError`. Decode as a `google.protobuf.DescriptorProto`.
|
|
||||||
final $typed_data.Uint8List clientConnectErrorDescriptor = $convert.base64Decode(
|
|
||||||
'ChJDbGllbnRDb25uZWN0RXJyb3ISOwoEY29kZRgBIAEoDjInLmFyYml0ZXIuY2xpZW50LkNsaW'
|
|
||||||
'VudENvbm5lY3RFcnJvci5Db2RlUgRjb2RlIkMKBENvZGUSCwoHVU5LTk9XThAAEhMKD0FQUFJP'
|
|
||||||
'VkFMX0RFTklFRBABEhkKFU5PX1VTRVJfQUdFTlRTX09OTElORRAC');
|
|
||||||
|
|
||||||
@$core.Deprecated('Use clientResponseDescriptor instead')
|
@$core.Deprecated('Use clientResponseDescriptor instead')
|
||||||
const ClientResponse$json = {
|
const ClientResponse$json = {
|
||||||
'1': 'ClientResponse',
|
'1': 'ClientResponse',
|
||||||
'2': [
|
'2': [
|
||||||
|
{
|
||||||
|
'1': 'request_id',
|
||||||
|
'3': 7,
|
||||||
|
'4': 1,
|
||||||
|
'5': 5,
|
||||||
|
'9': 1,
|
||||||
|
'10': 'requestId',
|
||||||
|
'17': true
|
||||||
|
},
|
||||||
{
|
{
|
||||||
'1': 'auth_challenge',
|
'1': 'auth_challenge',
|
||||||
'3': 1,
|
'3': 1,
|
||||||
@@ -144,22 +164,13 @@ const ClientResponse$json = {
|
|||||||
'10': 'authChallenge'
|
'10': 'authChallenge'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'1': 'auth_ok',
|
'1': 'auth_result',
|
||||||
'3': 2,
|
'3': 2,
|
||||||
'4': 1,
|
'4': 1,
|
||||||
'5': 11,
|
'5': 14,
|
||||||
'6': '.arbiter.client.AuthOk',
|
'6': '.arbiter.client.AuthResult',
|
||||||
'9': 0,
|
'9': 0,
|
||||||
'10': 'authOk'
|
'10': 'authResult'
|
||||||
},
|
|
||||||
{
|
|
||||||
'1': 'client_connect_error',
|
|
||||||
'3': 5,
|
|
||||||
'4': 1,
|
|
||||||
'5': 11,
|
|
||||||
'6': '.arbiter.client.ClientConnectError',
|
|
||||||
'9': 0,
|
|
||||||
'10': 'clientConnectError'
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'1': 'evm_sign_transaction',
|
'1': 'evm_sign_transaction',
|
||||||
@@ -179,19 +190,30 @@ const ClientResponse$json = {
|
|||||||
'9': 0,
|
'9': 0,
|
||||||
'10': 'evmAnalyzeTransaction'
|
'10': 'evmAnalyzeTransaction'
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
'1': 'vault_state',
|
||||||
|
'3': 6,
|
||||||
|
'4': 1,
|
||||||
|
'5': 14,
|
||||||
|
'6': '.arbiter.client.VaultState',
|
||||||
|
'9': 0,
|
||||||
|
'10': 'vaultState'
|
||||||
|
},
|
||||||
],
|
],
|
||||||
'8': [
|
'8': [
|
||||||
{'1': 'payload'},
|
{'1': 'payload'},
|
||||||
|
{'1': '_request_id'},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Descriptor for `ClientResponse`. Decode as a `google.protobuf.DescriptorProto`.
|
/// Descriptor for `ClientResponse`. Decode as a `google.protobuf.DescriptorProto`.
|
||||||
final $typed_data.Uint8List clientResponseDescriptor = $convert.base64Decode(
|
final $typed_data.Uint8List clientResponseDescriptor = $convert.base64Decode(
|
||||||
'Cg5DbGllbnRSZXNwb25zZRJGCg5hdXRoX2NoYWxsZW5nZRgBIAEoCzIdLmFyYml0ZXIuY2xpZW'
|
'Cg5DbGllbnRSZXNwb25zZRIiCgpyZXF1ZXN0X2lkGAcgASgFSAFSCXJlcXVlc3RJZIgBARJGCg'
|
||||||
'50LkF1dGhDaGFsbGVuZ2VIAFINYXV0aENoYWxsZW5nZRIxCgdhdXRoX29rGAIgASgLMhYuYXJi'
|
'5hdXRoX2NoYWxsZW5nZRgBIAEoCzIdLmFyYml0ZXIuY2xpZW50LkF1dGhDaGFsbGVuZ2VIAFIN'
|
||||||
'aXRlci5jbGllbnQuQXV0aE9rSABSBmF1dGhPaxJWChRjbGllbnRfY29ubmVjdF9lcnJvchgFIA'
|
'YXV0aENoYWxsZW5nZRI9CgthdXRoX3Jlc3VsdBgCIAEoDjIaLmFyYml0ZXIuY2xpZW50LkF1dG'
|
||||||
'EoCzIiLmFyYml0ZXIuY2xpZW50LkNsaWVudENvbm5lY3RFcnJvckgAUhJjbGllbnRDb25uZWN0'
|
'hSZXN1bHRIAFIKYXV0aFJlc3VsdBJbChRldm1fc2lnbl90cmFuc2FjdGlvbhgDIAEoCzInLmFy'
|
||||||
'RXJyb3ISWwoUZXZtX3NpZ25fdHJhbnNhY3Rpb24YAyABKAsyJy5hcmJpdGVyLmV2bS5Fdm1TaW'
|
'Yml0ZXIuZXZtLkV2bVNpZ25UcmFuc2FjdGlvblJlc3BvbnNlSABSEmV2bVNpZ25UcmFuc2FjdG'
|
||||||
'duVHJhbnNhY3Rpb25SZXNwb25zZUgAUhJldm1TaWduVHJhbnNhY3Rpb24SZAoXZXZtX2FuYWx5'
|
'lvbhJkChdldm1fYW5hbHl6ZV90cmFuc2FjdGlvbhgEIAEoCzIqLmFyYml0ZXIuZXZtLkV2bUFu'
|
||||||
'emVfdHJhbnNhY3Rpb24YBCABKAsyKi5hcmJpdGVyLmV2bS5Fdm1BbmFseXplVHJhbnNhY3Rpb2'
|
'YWx5emVUcmFuc2FjdGlvblJlc3BvbnNlSABSFWV2bUFuYWx5emVUcmFuc2FjdGlvbhI9Cgt2YX'
|
||||||
'5SZXNwb25zZUgAUhVldm1BbmFseXplVHJhbnNhY3Rpb25CCQoHcGF5bG9hZA==');
|
'VsdF9zdGF0ZRgGIAEoDjIaLmFyYml0ZXIuY2xpZW50LlZhdWx0U3RhdGVIAFIKdmF1bHRTdGF0'
|
||||||
|
'ZUIJCgdwYXlsb2FkQg0KC19yZXF1ZXN0X2lk');
|
||||||
|
|||||||
@@ -105,11 +105,9 @@ class AuthChallengeRequest extends $pb.GeneratedMessage {
|
|||||||
|
|
||||||
class AuthChallenge extends $pb.GeneratedMessage {
|
class AuthChallenge extends $pb.GeneratedMessage {
|
||||||
factory AuthChallenge({
|
factory AuthChallenge({
|
||||||
$core.List<$core.int>? pubkey,
|
|
||||||
$core.int? nonce,
|
$core.int? nonce,
|
||||||
}) {
|
}) {
|
||||||
final result = create();
|
final result = create();
|
||||||
if (pubkey != null) result.pubkey = pubkey;
|
|
||||||
if (nonce != null) result.nonce = nonce;
|
if (nonce != null) result.nonce = nonce;
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
@@ -128,8 +126,6 @@ class AuthChallenge extends $pb.GeneratedMessage {
|
|||||||
package:
|
package:
|
||||||
const $pb.PackageName(_omitMessageNames ? '' : 'arbiter.user_agent'),
|
const $pb.PackageName(_omitMessageNames ? '' : 'arbiter.user_agent'),
|
||||||
createEmptyInstance: create)
|
createEmptyInstance: create)
|
||||||
..a<$core.List<$core.int>>(
|
|
||||||
1, _omitFieldNames ? '' : 'pubkey', $pb.PbFieldType.OY)
|
|
||||||
..aI(2, _omitFieldNames ? '' : 'nonce')
|
..aI(2, _omitFieldNames ? '' : 'nonce')
|
||||||
..hasRequiredFields = false;
|
..hasRequiredFields = false;
|
||||||
|
|
||||||
@@ -152,21 +148,12 @@ class AuthChallenge extends $pb.GeneratedMessage {
|
|||||||
$pb.GeneratedMessage.$_defaultFor<AuthChallenge>(create);
|
$pb.GeneratedMessage.$_defaultFor<AuthChallenge>(create);
|
||||||
static AuthChallenge? _defaultInstance;
|
static AuthChallenge? _defaultInstance;
|
||||||
|
|
||||||
@$pb.TagNumber(1)
|
|
||||||
$core.List<$core.int> get pubkey => $_getN(0);
|
|
||||||
@$pb.TagNumber(1)
|
|
||||||
set pubkey($core.List<$core.int> value) => $_setBytes(0, value);
|
|
||||||
@$pb.TagNumber(1)
|
|
||||||
$core.bool hasPubkey() => $_has(0);
|
|
||||||
@$pb.TagNumber(1)
|
|
||||||
void clearPubkey() => $_clearField(1);
|
|
||||||
|
|
||||||
@$pb.TagNumber(2)
|
@$pb.TagNumber(2)
|
||||||
$core.int get nonce => $_getIZ(1);
|
$core.int get nonce => $_getIZ(0);
|
||||||
@$pb.TagNumber(2)
|
@$pb.TagNumber(2)
|
||||||
set nonce($core.int value) => $_setSignedInt32(1, value);
|
set nonce($core.int value) => $_setSignedInt32(0, value);
|
||||||
@$pb.TagNumber(2)
|
@$pb.TagNumber(2)
|
||||||
$core.bool hasNonce() => $_has(1);
|
$core.bool hasNonce() => $_has(0);
|
||||||
@$pb.TagNumber(2)
|
@$pb.TagNumber(2)
|
||||||
void clearNonce() => $_clearField(2);
|
void clearNonce() => $_clearField(2);
|
||||||
}
|
}
|
||||||
@@ -228,44 +215,6 @@ class AuthChallengeSolution extends $pb.GeneratedMessage {
|
|||||||
void clearSignature() => $_clearField(1);
|
void clearSignature() => $_clearField(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
class AuthOk extends $pb.GeneratedMessage {
|
|
||||||
factory AuthOk() => create();
|
|
||||||
|
|
||||||
AuthOk._();
|
|
||||||
|
|
||||||
factory AuthOk.fromBuffer($core.List<$core.int> data,
|
|
||||||
[$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) =>
|
|
||||||
create()..mergeFromBuffer(data, registry);
|
|
||||||
factory AuthOk.fromJson($core.String json,
|
|
||||||
[$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) =>
|
|
||||||
create()..mergeFromJson(json, registry);
|
|
||||||
|
|
||||||
static final $pb.BuilderInfo _i = $pb.BuilderInfo(
|
|
||||||
_omitMessageNames ? '' : 'AuthOk',
|
|
||||||
package:
|
|
||||||
const $pb.PackageName(_omitMessageNames ? '' : 'arbiter.user_agent'),
|
|
||||||
createEmptyInstance: create)
|
|
||||||
..hasRequiredFields = false;
|
|
||||||
|
|
||||||
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
|
|
||||||
AuthOk clone() => deepCopy();
|
|
||||||
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
|
|
||||||
AuthOk copyWith(void Function(AuthOk) updates) =>
|
|
||||||
super.copyWith((message) => updates(message as AuthOk)) as AuthOk;
|
|
||||||
|
|
||||||
@$core.override
|
|
||||||
$pb.BuilderInfo get info_ => _i;
|
|
||||||
|
|
||||||
@$core.pragma('dart2js:noInline')
|
|
||||||
static AuthOk create() => AuthOk._();
|
|
||||||
@$core.override
|
|
||||||
AuthOk createEmptyInstance() => create();
|
|
||||||
@$core.pragma('dart2js:noInline')
|
|
||||||
static AuthOk getDefault() =>
|
|
||||||
_defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<AuthOk>(create);
|
|
||||||
static AuthOk? _defaultInstance;
|
|
||||||
}
|
|
||||||
|
|
||||||
class UnsealStart extends $pb.GeneratedMessage {
|
class UnsealStart extends $pb.GeneratedMessage {
|
||||||
factory UnsealStart({
|
factory UnsealStart({
|
||||||
$core.List<$core.int>? clientPubkey,
|
$core.List<$core.int>? clientPubkey,
|
||||||
@@ -726,6 +675,7 @@ class UserAgentRequest extends $pb.GeneratedMessage {
|
|||||||
$1.EvmGrantListRequest? evmGrantList,
|
$1.EvmGrantListRequest? evmGrantList,
|
||||||
ClientConnectionResponse? clientConnectionResponse,
|
ClientConnectionResponse? clientConnectionResponse,
|
||||||
BootstrapEncryptedKey? bootstrapEncryptedKey,
|
BootstrapEncryptedKey? bootstrapEncryptedKey,
|
||||||
|
$core.int? id,
|
||||||
}) {
|
}) {
|
||||||
final result = create();
|
final result = create();
|
||||||
if (authChallengeRequest != null)
|
if (authChallengeRequest != null)
|
||||||
@@ -745,6 +695,7 @@ class UserAgentRequest extends $pb.GeneratedMessage {
|
|||||||
result.clientConnectionResponse = clientConnectionResponse;
|
result.clientConnectionResponse = clientConnectionResponse;
|
||||||
if (bootstrapEncryptedKey != null)
|
if (bootstrapEncryptedKey != null)
|
||||||
result.bootstrapEncryptedKey = bootstrapEncryptedKey;
|
result.bootstrapEncryptedKey = bootstrapEncryptedKey;
|
||||||
|
if (id != null) result.id = id;
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -807,6 +758,7 @@ class UserAgentRequest extends $pb.GeneratedMessage {
|
|||||||
..aOM<BootstrapEncryptedKey>(
|
..aOM<BootstrapEncryptedKey>(
|
||||||
12, _omitFieldNames ? '' : 'bootstrapEncryptedKey',
|
12, _omitFieldNames ? '' : 'bootstrapEncryptedKey',
|
||||||
subBuilder: BootstrapEncryptedKey.create)
|
subBuilder: BootstrapEncryptedKey.create)
|
||||||
|
..aI(14, _omitFieldNames ? '' : 'id')
|
||||||
..hasRequiredFields = false;
|
..hasRequiredFields = false;
|
||||||
|
|
||||||
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
|
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
|
||||||
@@ -990,11 +942,20 @@ class UserAgentRequest extends $pb.GeneratedMessage {
|
|||||||
void clearBootstrapEncryptedKey() => $_clearField(12);
|
void clearBootstrapEncryptedKey() => $_clearField(12);
|
||||||
@$pb.TagNumber(12)
|
@$pb.TagNumber(12)
|
||||||
BootstrapEncryptedKey ensureBootstrapEncryptedKey() => $_ensure(11);
|
BootstrapEncryptedKey ensureBootstrapEncryptedKey() => $_ensure(11);
|
||||||
|
|
||||||
|
@$pb.TagNumber(14)
|
||||||
|
$core.int get id => $_getIZ(12);
|
||||||
|
@$pb.TagNumber(14)
|
||||||
|
set id($core.int value) => $_setSignedInt32(12, value);
|
||||||
|
@$pb.TagNumber(14)
|
||||||
|
$core.bool hasId() => $_has(12);
|
||||||
|
@$pb.TagNumber(14)
|
||||||
|
void clearId() => $_clearField(14);
|
||||||
}
|
}
|
||||||
|
|
||||||
enum UserAgentResponse_Payload {
|
enum UserAgentResponse_Payload {
|
||||||
authChallenge,
|
authChallenge,
|
||||||
authOk,
|
authResult,
|
||||||
unsealStartResponse,
|
unsealStartResponse,
|
||||||
unsealResult,
|
unsealResult,
|
||||||
vaultState,
|
vaultState,
|
||||||
@@ -1012,7 +973,7 @@ enum UserAgentResponse_Payload {
|
|||||||
class UserAgentResponse extends $pb.GeneratedMessage {
|
class UserAgentResponse extends $pb.GeneratedMessage {
|
||||||
factory UserAgentResponse({
|
factory UserAgentResponse({
|
||||||
AuthChallenge? authChallenge,
|
AuthChallenge? authChallenge,
|
||||||
AuthOk? authOk,
|
AuthResult? authResult,
|
||||||
UnsealStartResponse? unsealStartResponse,
|
UnsealStartResponse? unsealStartResponse,
|
||||||
UnsealResult? unsealResult,
|
UnsealResult? unsealResult,
|
||||||
VaultState? vaultState,
|
VaultState? vaultState,
|
||||||
@@ -1024,10 +985,11 @@ class UserAgentResponse extends $pb.GeneratedMessage {
|
|||||||
ClientConnectionRequest? clientConnectionRequest,
|
ClientConnectionRequest? clientConnectionRequest,
|
||||||
ClientConnectionCancel? clientConnectionCancel,
|
ClientConnectionCancel? clientConnectionCancel,
|
||||||
BootstrapResult? bootstrapResult,
|
BootstrapResult? bootstrapResult,
|
||||||
|
$core.int? id,
|
||||||
}) {
|
}) {
|
||||||
final result = create();
|
final result = create();
|
||||||
if (authChallenge != null) result.authChallenge = authChallenge;
|
if (authChallenge != null) result.authChallenge = authChallenge;
|
||||||
if (authOk != null) result.authOk = authOk;
|
if (authResult != null) result.authResult = authResult;
|
||||||
if (unsealStartResponse != null)
|
if (unsealStartResponse != null)
|
||||||
result.unsealStartResponse = unsealStartResponse;
|
result.unsealStartResponse = unsealStartResponse;
|
||||||
if (unsealResult != null) result.unsealResult = unsealResult;
|
if (unsealResult != null) result.unsealResult = unsealResult;
|
||||||
@@ -1042,6 +1004,7 @@ class UserAgentResponse extends $pb.GeneratedMessage {
|
|||||||
if (clientConnectionCancel != null)
|
if (clientConnectionCancel != null)
|
||||||
result.clientConnectionCancel = clientConnectionCancel;
|
result.clientConnectionCancel = clientConnectionCancel;
|
||||||
if (bootstrapResult != null) result.bootstrapResult = bootstrapResult;
|
if (bootstrapResult != null) result.bootstrapResult = bootstrapResult;
|
||||||
|
if (id != null) result.id = id;
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1057,7 +1020,7 @@ class UserAgentResponse extends $pb.GeneratedMessage {
|
|||||||
static const $core.Map<$core.int, UserAgentResponse_Payload>
|
static const $core.Map<$core.int, UserAgentResponse_Payload>
|
||||||
_UserAgentResponse_PayloadByTag = {
|
_UserAgentResponse_PayloadByTag = {
|
||||||
1: UserAgentResponse_Payload.authChallenge,
|
1: UserAgentResponse_Payload.authChallenge,
|
||||||
2: UserAgentResponse_Payload.authOk,
|
2: UserAgentResponse_Payload.authResult,
|
||||||
3: UserAgentResponse_Payload.unsealStartResponse,
|
3: UserAgentResponse_Payload.unsealStartResponse,
|
||||||
4: UserAgentResponse_Payload.unsealResult,
|
4: UserAgentResponse_Payload.unsealResult,
|
||||||
5: UserAgentResponse_Payload.vaultState,
|
5: UserAgentResponse_Payload.vaultState,
|
||||||
@@ -1079,7 +1042,8 @@ class UserAgentResponse extends $pb.GeneratedMessage {
|
|||||||
..oo(0, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13])
|
..oo(0, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13])
|
||||||
..aOM<AuthChallenge>(1, _omitFieldNames ? '' : 'authChallenge',
|
..aOM<AuthChallenge>(1, _omitFieldNames ? '' : 'authChallenge',
|
||||||
subBuilder: AuthChallenge.create)
|
subBuilder: AuthChallenge.create)
|
||||||
..aOM<AuthOk>(2, _omitFieldNames ? '' : 'authOk', subBuilder: AuthOk.create)
|
..aE<AuthResult>(2, _omitFieldNames ? '' : 'authResult',
|
||||||
|
enumValues: AuthResult.values)
|
||||||
..aOM<UnsealStartResponse>(3, _omitFieldNames ? '' : 'unsealStartResponse',
|
..aOM<UnsealStartResponse>(3, _omitFieldNames ? '' : 'unsealStartResponse',
|
||||||
subBuilder: UnsealStartResponse.create)
|
subBuilder: UnsealStartResponse.create)
|
||||||
..aE<UnsealResult>(4, _omitFieldNames ? '' : 'unsealResult',
|
..aE<UnsealResult>(4, _omitFieldNames ? '' : 'unsealResult',
|
||||||
@@ -1104,6 +1068,7 @@ class UserAgentResponse extends $pb.GeneratedMessage {
|
|||||||
subBuilder: ClientConnectionCancel.create)
|
subBuilder: ClientConnectionCancel.create)
|
||||||
..aE<BootstrapResult>(13, _omitFieldNames ? '' : 'bootstrapResult',
|
..aE<BootstrapResult>(13, _omitFieldNames ? '' : 'bootstrapResult',
|
||||||
enumValues: BootstrapResult.values)
|
enumValues: BootstrapResult.values)
|
||||||
|
..aI(14, _omitFieldNames ? '' : 'id')
|
||||||
..hasRequiredFields = false;
|
..hasRequiredFields = false;
|
||||||
|
|
||||||
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
|
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
|
||||||
@@ -1167,15 +1132,13 @@ class UserAgentResponse extends $pb.GeneratedMessage {
|
|||||||
AuthChallenge ensureAuthChallenge() => $_ensure(0);
|
AuthChallenge ensureAuthChallenge() => $_ensure(0);
|
||||||
|
|
||||||
@$pb.TagNumber(2)
|
@$pb.TagNumber(2)
|
||||||
AuthOk get authOk => $_getN(1);
|
AuthResult get authResult => $_getN(1);
|
||||||
@$pb.TagNumber(2)
|
@$pb.TagNumber(2)
|
||||||
set authOk(AuthOk value) => $_setField(2, value);
|
set authResult(AuthResult value) => $_setField(2, value);
|
||||||
@$pb.TagNumber(2)
|
@$pb.TagNumber(2)
|
||||||
$core.bool hasAuthOk() => $_has(1);
|
$core.bool hasAuthResult() => $_has(1);
|
||||||
@$pb.TagNumber(2)
|
@$pb.TagNumber(2)
|
||||||
void clearAuthOk() => $_clearField(2);
|
void clearAuthResult() => $_clearField(2);
|
||||||
@$pb.TagNumber(2)
|
|
||||||
AuthOk ensureAuthOk() => $_ensure(1);
|
|
||||||
|
|
||||||
@$pb.TagNumber(3)
|
@$pb.TagNumber(3)
|
||||||
UnsealStartResponse get unsealStartResponse => $_getN(2);
|
UnsealStartResponse get unsealStartResponse => $_getN(2);
|
||||||
@@ -1293,6 +1256,15 @@ class UserAgentResponse extends $pb.GeneratedMessage {
|
|||||||
$core.bool hasBootstrapResult() => $_has(12);
|
$core.bool hasBootstrapResult() => $_has(12);
|
||||||
@$pb.TagNumber(13)
|
@$pb.TagNumber(13)
|
||||||
void clearBootstrapResult() => $_clearField(13);
|
void clearBootstrapResult() => $_clearField(13);
|
||||||
|
|
||||||
|
@$pb.TagNumber(14)
|
||||||
|
$core.int get id => $_getIZ(13);
|
||||||
|
@$pb.TagNumber(14)
|
||||||
|
set id($core.int value) => $_setSignedInt32(13, value);
|
||||||
|
@$pb.TagNumber(14)
|
||||||
|
$core.bool hasId() => $_has(13);
|
||||||
|
@$pb.TagNumber(14)
|
||||||
|
void clearId() => $_clearField(14);
|
||||||
}
|
}
|
||||||
|
|
||||||
const $core.bool _omitFieldNames =
|
const $core.bool _omitFieldNames =
|
||||||
|
|||||||
@@ -39,6 +39,40 @@ class KeyType extends $pb.ProtobufEnum {
|
|||||||
const KeyType._(super.value, super.name);
|
const KeyType._(super.value, super.name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class AuthResult extends $pb.ProtobufEnum {
|
||||||
|
static const AuthResult AUTH_RESULT_UNSPECIFIED =
|
||||||
|
AuthResult._(0, _omitEnumNames ? '' : 'AUTH_RESULT_UNSPECIFIED');
|
||||||
|
static const AuthResult AUTH_RESULT_SUCCESS =
|
||||||
|
AuthResult._(1, _omitEnumNames ? '' : 'AUTH_RESULT_SUCCESS');
|
||||||
|
static const AuthResult AUTH_RESULT_INVALID_KEY =
|
||||||
|
AuthResult._(2, _omitEnumNames ? '' : 'AUTH_RESULT_INVALID_KEY');
|
||||||
|
static const AuthResult AUTH_RESULT_INVALID_SIGNATURE =
|
||||||
|
AuthResult._(3, _omitEnumNames ? '' : 'AUTH_RESULT_INVALID_SIGNATURE');
|
||||||
|
static const AuthResult AUTH_RESULT_BOOTSTRAP_REQUIRED =
|
||||||
|
AuthResult._(4, _omitEnumNames ? '' : 'AUTH_RESULT_BOOTSTRAP_REQUIRED');
|
||||||
|
static const AuthResult AUTH_RESULT_TOKEN_INVALID =
|
||||||
|
AuthResult._(5, _omitEnumNames ? '' : 'AUTH_RESULT_TOKEN_INVALID');
|
||||||
|
static const AuthResult AUTH_RESULT_INTERNAL =
|
||||||
|
AuthResult._(6, _omitEnumNames ? '' : 'AUTH_RESULT_INTERNAL');
|
||||||
|
|
||||||
|
static const $core.List<AuthResult> values = <AuthResult>[
|
||||||
|
AUTH_RESULT_UNSPECIFIED,
|
||||||
|
AUTH_RESULT_SUCCESS,
|
||||||
|
AUTH_RESULT_INVALID_KEY,
|
||||||
|
AUTH_RESULT_INVALID_SIGNATURE,
|
||||||
|
AUTH_RESULT_BOOTSTRAP_REQUIRED,
|
||||||
|
AUTH_RESULT_TOKEN_INVALID,
|
||||||
|
AUTH_RESULT_INTERNAL,
|
||||||
|
];
|
||||||
|
|
||||||
|
static final $core.List<AuthResult?> _byValue =
|
||||||
|
$pb.ProtobufEnum.$_initByValueList(values, 6);
|
||||||
|
static AuthResult? valueOf($core.int value) =>
|
||||||
|
value < 0 || value >= _byValue.length ? null : _byValue[value];
|
||||||
|
|
||||||
|
const AuthResult._(super.value, super.name);
|
||||||
|
}
|
||||||
|
|
||||||
class UnsealResult extends $pb.ProtobufEnum {
|
class UnsealResult extends $pb.ProtobufEnum {
|
||||||
static const UnsealResult UNSEAL_RESULT_UNSPECIFIED =
|
static const UnsealResult UNSEAL_RESULT_UNSPECIFIED =
|
||||||
UnsealResult._(0, _omitEnumNames ? '' : 'UNSEAL_RESULT_UNSPECIFIED');
|
UnsealResult._(0, _omitEnumNames ? '' : 'UNSEAL_RESULT_UNSPECIFIED');
|
||||||
@@ -65,14 +99,15 @@ class UnsealResult extends $pb.ProtobufEnum {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class BootstrapResult extends $pb.ProtobufEnum {
|
class BootstrapResult extends $pb.ProtobufEnum {
|
||||||
static const BootstrapResult BOOTSTRAP_RESULT_UNSPECIFIED =
|
static const BootstrapResult BOOTSTRAP_RESULT_UNSPECIFIED = BootstrapResult._(
|
||||||
BootstrapResult._(0, _omitEnumNames ? '' : 'BOOTSTRAP_RESULT_UNSPECIFIED');
|
0, _omitEnumNames ? '' : 'BOOTSTRAP_RESULT_UNSPECIFIED');
|
||||||
static const BootstrapResult BOOTSTRAP_RESULT_SUCCESS =
|
static const BootstrapResult BOOTSTRAP_RESULT_SUCCESS =
|
||||||
BootstrapResult._(1, _omitEnumNames ? '' : 'BOOTSTRAP_RESULT_SUCCESS');
|
BootstrapResult._(1, _omitEnumNames ? '' : 'BOOTSTRAP_RESULT_SUCCESS');
|
||||||
static const BootstrapResult BOOTSTRAP_RESULT_ALREADY_BOOTSTRAPPED =
|
static const BootstrapResult BOOTSTRAP_RESULT_ALREADY_BOOTSTRAPPED =
|
||||||
BootstrapResult._(2, _omitEnumNames ? '' : 'BOOTSTRAP_RESULT_ALREADY_BOOTSTRAPPED');
|
BootstrapResult._(
|
||||||
static const BootstrapResult BOOTSTRAP_RESULT_INVALID_KEY =
|
2, _omitEnumNames ? '' : 'BOOTSTRAP_RESULT_ALREADY_BOOTSTRAPPED');
|
||||||
BootstrapResult._(3, _omitEnumNames ? '' : 'BOOTSTRAP_RESULT_INVALID_KEY');
|
static const BootstrapResult BOOTSTRAP_RESULT_INVALID_KEY = BootstrapResult._(
|
||||||
|
3, _omitEnumNames ? '' : 'BOOTSTRAP_RESULT_INVALID_KEY');
|
||||||
|
|
||||||
static const $core.List<BootstrapResult> values = <BootstrapResult>[
|
static const $core.List<BootstrapResult> values = <BootstrapResult>[
|
||||||
BOOTSTRAP_RESULT_UNSPECIFIED,
|
BOOTSTRAP_RESULT_UNSPECIFIED,
|
||||||
|
|||||||
@@ -31,6 +31,28 @@ final $typed_data.Uint8List keyTypeDescriptor = $convert.base64Decode(
|
|||||||
'CgdLZXlUeXBlEhgKFEtFWV9UWVBFX1VOU1BFQ0lGSUVEEAASFAoQS0VZX1RZUEVfRUQyNTUxOR'
|
'CgdLZXlUeXBlEhgKFEtFWV9UWVBFX1VOU1BFQ0lGSUVEEAASFAoQS0VZX1RZUEVfRUQyNTUxOR'
|
||||||
'ABEhwKGEtFWV9UWVBFX0VDRFNBX1NFQ1AyNTZLMRACEhAKDEtFWV9UWVBFX1JTQRAD');
|
'ABEhwKGEtFWV9UWVBFX0VDRFNBX1NFQ1AyNTZLMRACEhAKDEtFWV9UWVBFX1JTQRAD');
|
||||||
|
|
||||||
|
@$core.Deprecated('Use authResultDescriptor instead')
|
||||||
|
const AuthResult$json = {
|
||||||
|
'1': 'AuthResult',
|
||||||
|
'2': [
|
||||||
|
{'1': 'AUTH_RESULT_UNSPECIFIED', '2': 0},
|
||||||
|
{'1': 'AUTH_RESULT_SUCCESS', '2': 1},
|
||||||
|
{'1': 'AUTH_RESULT_INVALID_KEY', '2': 2},
|
||||||
|
{'1': 'AUTH_RESULT_INVALID_SIGNATURE', '2': 3},
|
||||||
|
{'1': 'AUTH_RESULT_BOOTSTRAP_REQUIRED', '2': 4},
|
||||||
|
{'1': 'AUTH_RESULT_TOKEN_INVALID', '2': 5},
|
||||||
|
{'1': 'AUTH_RESULT_INTERNAL', '2': 6},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Descriptor for `AuthResult`. Decode as a `google.protobuf.EnumDescriptorProto`.
|
||||||
|
final $typed_data.Uint8List authResultDescriptor = $convert.base64Decode(
|
||||||
|
'CgpBdXRoUmVzdWx0EhsKF0FVVEhfUkVTVUxUX1VOU1BFQ0lGSUVEEAASFwoTQVVUSF9SRVNVTF'
|
||||||
|
'RfU1VDQ0VTUxABEhsKF0FVVEhfUkVTVUxUX0lOVkFMSURfS0VZEAISIQodQVVUSF9SRVNVTFRf'
|
||||||
|
'SU5WQUxJRF9TSUdOQVRVUkUQAxIiCh5BVVRIX1JFU1VMVF9CT09UU1RSQVBfUkVRVUlSRUQQBB'
|
||||||
|
'IdChlBVVRIX1JFU1VMVF9UT0tFTl9JTlZBTElEEAUSGAoUQVVUSF9SRVNVTFRfSU5URVJOQUwQ'
|
||||||
|
'Bg==');
|
||||||
|
|
||||||
@$core.Deprecated('Use unsealResultDescriptor instead')
|
@$core.Deprecated('Use unsealResultDescriptor instead')
|
||||||
const UnsealResult$json = {
|
const UnsealResult$json = {
|
||||||
'1': 'UnsealResult',
|
'1': 'UnsealResult',
|
||||||
@@ -48,6 +70,23 @@ final $typed_data.Uint8List unsealResultDescriptor = $convert.base64Decode(
|
|||||||
'9SRVNVTFRfU1VDQ0VTUxABEh0KGVVOU0VBTF9SRVNVTFRfSU5WQUxJRF9LRVkQAhIgChxVTlNF'
|
'9SRVNVTFRfU1VDQ0VTUxABEh0KGVVOU0VBTF9SRVNVTFRfSU5WQUxJRF9LRVkQAhIgChxVTlNF'
|
||||||
'QUxfUkVTVUxUX1VOQk9PVFNUUkFQUEVEEAM=');
|
'QUxfUkVTVUxUX1VOQk9PVFNUUkFQUEVEEAM=');
|
||||||
|
|
||||||
|
@$core.Deprecated('Use bootstrapResultDescriptor instead')
|
||||||
|
const BootstrapResult$json = {
|
||||||
|
'1': 'BootstrapResult',
|
||||||
|
'2': [
|
||||||
|
{'1': 'BOOTSTRAP_RESULT_UNSPECIFIED', '2': 0},
|
||||||
|
{'1': 'BOOTSTRAP_RESULT_SUCCESS', '2': 1},
|
||||||
|
{'1': 'BOOTSTRAP_RESULT_ALREADY_BOOTSTRAPPED', '2': 2},
|
||||||
|
{'1': 'BOOTSTRAP_RESULT_INVALID_KEY', '2': 3},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Descriptor for `BootstrapResult`. Decode as a `google.protobuf.EnumDescriptorProto`.
|
||||||
|
final $typed_data.Uint8List bootstrapResultDescriptor = $convert.base64Decode(
|
||||||
|
'Cg9Cb290c3RyYXBSZXN1bHQSIAocQk9PVFNUUkFQX1JFU1VMVF9VTlNQRUNJRklFRBAAEhwKGE'
|
||||||
|
'JPT1RTVFJBUF9SRVNVTFRfU1VDQ0VTUxABEikKJUJPT1RTVFJBUF9SRVNVTFRfQUxSRUFEWV9C'
|
||||||
|
'T09UU1RSQVBQRUQQAhIgChxCT09UU1RSQVBfUkVTVUxUX0lOVkFMSURfS0VZEAM=');
|
||||||
|
|
||||||
@$core.Deprecated('Use vaultStateDescriptor instead')
|
@$core.Deprecated('Use vaultStateDescriptor instead')
|
||||||
const VaultState$json = {
|
const VaultState$json = {
|
||||||
'1': 'VaultState',
|
'1': 'VaultState',
|
||||||
@@ -105,15 +144,16 @@ final $typed_data.Uint8List authChallengeRequestDescriptor = $convert.base64Deco
|
|||||||
const AuthChallenge$json = {
|
const AuthChallenge$json = {
|
||||||
'1': 'AuthChallenge',
|
'1': 'AuthChallenge',
|
||||||
'2': [
|
'2': [
|
||||||
{'1': 'pubkey', '3': 1, '4': 1, '5': 12, '10': 'pubkey'},
|
|
||||||
{'1': 'nonce', '3': 2, '4': 1, '5': 5, '10': 'nonce'},
|
{'1': 'nonce', '3': 2, '4': 1, '5': 5, '10': 'nonce'},
|
||||||
],
|
],
|
||||||
|
'9': [
|
||||||
|
{'1': 1, '2': 2},
|
||||||
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Descriptor for `AuthChallenge`. Decode as a `google.protobuf.DescriptorProto`.
|
/// Descriptor for `AuthChallenge`. Decode as a `google.protobuf.DescriptorProto`.
|
||||||
final $typed_data.Uint8List authChallengeDescriptor = $convert.base64Decode(
|
final $typed_data.Uint8List authChallengeDescriptor = $convert.base64Decode(
|
||||||
'Cg1BdXRoQ2hhbGxlbmdlEhYKBnB1YmtleRgBIAEoDFIGcHVia2V5EhQKBW5vbmNlGAIgASgFUg'
|
'Cg1BdXRoQ2hhbGxlbmdlEhQKBW5vbmNlGAIgASgFUgVub25jZUoECAEQAg==');
|
||||||
'Vub25jZQ==');
|
|
||||||
|
|
||||||
@$core.Deprecated('Use authChallengeSolutionDescriptor instead')
|
@$core.Deprecated('Use authChallengeSolutionDescriptor instead')
|
||||||
const AuthChallengeSolution$json = {
|
const AuthChallengeSolution$json = {
|
||||||
@@ -127,15 +167,6 @@ const AuthChallengeSolution$json = {
|
|||||||
final $typed_data.Uint8List authChallengeSolutionDescriptor = $convert.base64Decode(
|
final $typed_data.Uint8List authChallengeSolutionDescriptor = $convert.base64Decode(
|
||||||
'ChVBdXRoQ2hhbGxlbmdlU29sdXRpb24SHAoJc2lnbmF0dXJlGAEgASgMUglzaWduYXR1cmU=');
|
'ChVBdXRoQ2hhbGxlbmdlU29sdXRpb24SHAoJc2lnbmF0dXJlGAEgASgMUglzaWduYXR1cmU=');
|
||||||
|
|
||||||
@$core.Deprecated('Use authOkDescriptor instead')
|
|
||||||
const AuthOk$json = {
|
|
||||||
'1': 'AuthOk',
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Descriptor for `AuthOk`. Decode as a `google.protobuf.DescriptorProto`.
|
|
||||||
final $typed_data.Uint8List authOkDescriptor =
|
|
||||||
$convert.base64Decode('CgZBdXRoT2s=');
|
|
||||||
|
|
||||||
@$core.Deprecated('Use unsealStartDescriptor instead')
|
@$core.Deprecated('Use unsealStartDescriptor instead')
|
||||||
const UnsealStart$json = {
|
const UnsealStart$json = {
|
||||||
'1': 'UnsealStart',
|
'1': 'UnsealStart',
|
||||||
@@ -177,6 +208,22 @@ final $typed_data.Uint8List unsealEncryptedKeyDescriptor = $convert.base64Decode
|
|||||||
'QYAiABKAxSCmNpcGhlcnRleHQSJwoPYXNzb2NpYXRlZF9kYXRhGAMgASgMUg5hc3NvY2lhdGVk'
|
'QYAiABKAxSCmNpcGhlcnRleHQSJwoPYXNzb2NpYXRlZF9kYXRhGAMgASgMUg5hc3NvY2lhdGVk'
|
||||||
'RGF0YQ==');
|
'RGF0YQ==');
|
||||||
|
|
||||||
|
@$core.Deprecated('Use bootstrapEncryptedKeyDescriptor instead')
|
||||||
|
const BootstrapEncryptedKey$json = {
|
||||||
|
'1': 'BootstrapEncryptedKey',
|
||||||
|
'2': [
|
||||||
|
{'1': 'nonce', '3': 1, '4': 1, '5': 12, '10': 'nonce'},
|
||||||
|
{'1': 'ciphertext', '3': 2, '4': 1, '5': 12, '10': 'ciphertext'},
|
||||||
|
{'1': 'associated_data', '3': 3, '4': 1, '5': 12, '10': 'associatedData'},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Descriptor for `BootstrapEncryptedKey`. Decode as a `google.protobuf.DescriptorProto`.
|
||||||
|
final $typed_data.Uint8List bootstrapEncryptedKeyDescriptor = $convert.base64Decode(
|
||||||
|
'ChVCb290c3RyYXBFbmNyeXB0ZWRLZXkSFAoFbm9uY2UYASABKAxSBW5vbmNlEh4KCmNpcGhlcn'
|
||||||
|
'RleHQYAiABKAxSCmNpcGhlcnRleHQSJwoPYXNzb2NpYXRlZF9kYXRhGAMgASgMUg5hc3NvY2lh'
|
||||||
|
'dGVkRGF0YQ==');
|
||||||
|
|
||||||
@$core.Deprecated('Use clientConnectionRequestDescriptor instead')
|
@$core.Deprecated('Use clientConnectionRequestDescriptor instead')
|
||||||
const ClientConnectionRequest$json = {
|
const ClientConnectionRequest$json = {
|
||||||
'1': 'ClientConnectionRequest',
|
'1': 'ClientConnectionRequest',
|
||||||
@@ -216,6 +263,7 @@ final $typed_data.Uint8List clientConnectionCancelDescriptor =
|
|||||||
const UserAgentRequest$json = {
|
const UserAgentRequest$json = {
|
||||||
'1': 'UserAgentRequest',
|
'1': 'UserAgentRequest',
|
||||||
'2': [
|
'2': [
|
||||||
|
{'1': 'id', '3': 14, '4': 1, '5': 5, '10': 'id'},
|
||||||
{
|
{
|
||||||
'1': 'auth_challenge_request',
|
'1': 'auth_challenge_request',
|
||||||
'3': 1,
|
'3': 1,
|
||||||
@@ -315,6 +363,15 @@ const UserAgentRequest$json = {
|
|||||||
'9': 0,
|
'9': 0,
|
||||||
'10': 'clientConnectionResponse'
|
'10': 'clientConnectionResponse'
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
'1': 'bootstrap_encrypted_key',
|
||||||
|
'3': 12,
|
||||||
|
'4': 1,
|
||||||
|
'5': 11,
|
||||||
|
'6': '.arbiter.user_agent.BootstrapEncryptedKey',
|
||||||
|
'9': 0,
|
||||||
|
'10': 'bootstrapEncryptedKey'
|
||||||
|
},
|
||||||
],
|
],
|
||||||
'8': [
|
'8': [
|
||||||
{'1': 'payload'},
|
{'1': 'payload'},
|
||||||
@@ -323,28 +380,32 @@ const UserAgentRequest$json = {
|
|||||||
|
|
||||||
/// Descriptor for `UserAgentRequest`. Decode as a `google.protobuf.DescriptorProto`.
|
/// Descriptor for `UserAgentRequest`. Decode as a `google.protobuf.DescriptorProto`.
|
||||||
final $typed_data.Uint8List userAgentRequestDescriptor = $convert.base64Decode(
|
final $typed_data.Uint8List userAgentRequestDescriptor = $convert.base64Decode(
|
||||||
'ChBVc2VyQWdlbnRSZXF1ZXN0EmAKFmF1dGhfY2hhbGxlbmdlX3JlcXVlc3QYASABKAsyKC5hcm'
|
'ChBVc2VyQWdlbnRSZXF1ZXN0Eg4KAmlkGA4gASgFUgJpZBJgChZhdXRoX2NoYWxsZW5nZV9yZX'
|
||||||
'JpdGVyLnVzZXJfYWdlbnQuQXV0aENoYWxsZW5nZVJlcXVlc3RIAFIUYXV0aENoYWxsZW5nZVJl'
|
'F1ZXN0GAEgASgLMiguYXJiaXRlci51c2VyX2FnZW50LkF1dGhDaGFsbGVuZ2VSZXF1ZXN0SABS'
|
||||||
'cXVlc3QSYwoXYXV0aF9jaGFsbGVuZ2Vfc29sdXRpb24YAiABKAsyKS5hcmJpdGVyLnVzZXJfYW'
|
'FGF1dGhDaGFsbGVuZ2VSZXF1ZXN0EmMKF2F1dGhfY2hhbGxlbmdlX3NvbHV0aW9uGAIgASgLMi'
|
||||||
'dlbnQuQXV0aENoYWxsZW5nZVNvbHV0aW9uSABSFWF1dGhDaGFsbGVuZ2VTb2x1dGlvbhJECgx1'
|
'kuYXJiaXRlci51c2VyX2FnZW50LkF1dGhDaGFsbGVuZ2VTb2x1dGlvbkgAUhVhdXRoQ2hhbGxl'
|
||||||
'bnNlYWxfc3RhcnQYAyABKAsyHy5hcmJpdGVyLnVzZXJfYWdlbnQuVW5zZWFsU3RhcnRIAFILdW'
|
'bmdlU29sdXRpb24SRAoMdW5zZWFsX3N0YXJ0GAMgASgLMh8uYXJiaXRlci51c2VyX2FnZW50Ll'
|
||||||
'5zZWFsU3RhcnQSWgoUdW5zZWFsX2VuY3J5cHRlZF9rZXkYBCABKAsyJi5hcmJpdGVyLnVzZXJf'
|
'Vuc2VhbFN0YXJ0SABSC3Vuc2VhbFN0YXJ0EloKFHVuc2VhbF9lbmNyeXB0ZWRfa2V5GAQgASgL'
|
||||||
'YWdlbnQuVW5zZWFsRW5jcnlwdGVkS2V5SABSEnVuc2VhbEVuY3J5cHRlZEtleRJEChFxdWVyeV'
|
'MiYuYXJiaXRlci51c2VyX2FnZW50LlVuc2VhbEVuY3J5cHRlZEtleUgAUhJ1bnNlYWxFbmNyeX'
|
||||||
'92YXVsdF9zdGF0ZRgFIAEoCzIWLmdvb2dsZS5wcm90b2J1Zi5FbXB0eUgAUg9xdWVyeVZhdWx0'
|
'B0ZWRLZXkSRAoRcXVlcnlfdmF1bHRfc3RhdGUYBSABKAsyFi5nb29nbGUucHJvdG9idWYuRW1w'
|
||||||
'U3RhdGUSRAoRZXZtX3dhbGxldF9jcmVhdGUYBiABKAsyFi5nb29nbGUucHJvdG9idWYuRW1wdH'
|
'dHlIAFIPcXVlcnlWYXVsdFN0YXRlEkQKEWV2bV93YWxsZXRfY3JlYXRlGAYgASgLMhYuZ29vZ2'
|
||||||
'lIAFIPZXZtV2FsbGV0Q3JlYXRlEkAKD2V2bV93YWxsZXRfbGlzdBgHIAEoCzIWLmdvb2dsZS5w'
|
'xlLnByb3RvYnVmLkVtcHR5SABSD2V2bVdhbGxldENyZWF0ZRJACg9ldm1fd2FsbGV0X2xpc3QY'
|
||||||
'cm90b2J1Zi5FbXB0eUgAUg1ldm1XYWxsZXRMaXN0Ek4KEGV2bV9ncmFudF9jcmVhdGUYCCABKA'
|
'ByABKAsyFi5nb29nbGUucHJvdG9idWYuRW1wdHlIAFINZXZtV2FsbGV0TGlzdBJOChBldm1fZ3'
|
||||||
'syIi5hcmJpdGVyLmV2bS5Fdm1HcmFudENyZWF0ZVJlcXVlc3RIAFIOZXZtR3JhbnRDcmVhdGUS'
|
'JhbnRfY3JlYXRlGAggASgLMiIuYXJiaXRlci5ldm0uRXZtR3JhbnRDcmVhdGVSZXF1ZXN0SABS'
|
||||||
'TgoQZXZtX2dyYW50X2RlbGV0ZRgJIAEoCzIiLmFyYml0ZXIuZXZtLkV2bUdyYW50RGVsZXRlUm'
|
'DmV2bUdyYW50Q3JlYXRlEk4KEGV2bV9ncmFudF9kZWxldGUYCSABKAsyIi5hcmJpdGVyLmV2bS'
|
||||||
'VxdWVzdEgAUg5ldm1HcmFudERlbGV0ZRJICg5ldm1fZ3JhbnRfbGlzdBgKIAEoCzIgLmFyYml0'
|
'5Fdm1HcmFudERlbGV0ZVJlcXVlc3RIAFIOZXZtR3JhbnREZWxldGUSSAoOZXZtX2dyYW50X2xp'
|
||||||
'ZXIuZXZtLkV2bUdyYW50TGlzdFJlcXVlc3RIAFIMZXZtR3JhbnRMaXN0EmwKGmNsaWVudF9jb2'
|
'c3QYCiABKAsyIC5hcmJpdGVyLmV2bS5Fdm1HcmFudExpc3RSZXF1ZXN0SABSDGV2bUdyYW50TG'
|
||||||
'5uZWN0aW9uX3Jlc3BvbnNlGAsgASgLMiwuYXJiaXRlci51c2VyX2FnZW50LkNsaWVudENvbm5l'
|
'lzdBJsChpjbGllbnRfY29ubmVjdGlvbl9yZXNwb25zZRgLIAEoCzIsLmFyYml0ZXIudXNlcl9h'
|
||||||
'Y3Rpb25SZXNwb25zZUgAUhhjbGllbnRDb25uZWN0aW9uUmVzcG9uc2VCCQoHcGF5bG9hZA==');
|
'Z2VudC5DbGllbnRDb25uZWN0aW9uUmVzcG9uc2VIAFIYY2xpZW50Q29ubmVjdGlvblJlc3Bvbn'
|
||||||
|
'NlEmMKF2Jvb3RzdHJhcF9lbmNyeXB0ZWRfa2V5GAwgASgLMikuYXJiaXRlci51c2VyX2FnZW50'
|
||||||
|
'LkJvb3RzdHJhcEVuY3J5cHRlZEtleUgAUhVib290c3RyYXBFbmNyeXB0ZWRLZXlCCQoHcGF5bG'
|
||||||
|
'9hZA==');
|
||||||
|
|
||||||
@$core.Deprecated('Use userAgentResponseDescriptor instead')
|
@$core.Deprecated('Use userAgentResponseDescriptor instead')
|
||||||
const UserAgentResponse$json = {
|
const UserAgentResponse$json = {
|
||||||
'1': 'UserAgentResponse',
|
'1': 'UserAgentResponse',
|
||||||
'2': [
|
'2': [
|
||||||
|
{'1': 'id', '3': 14, '4': 1, '5': 5, '9': 1, '10': 'id', '17': true},
|
||||||
{
|
{
|
||||||
'1': 'auth_challenge',
|
'1': 'auth_challenge',
|
||||||
'3': 1,
|
'3': 1,
|
||||||
@@ -355,13 +416,13 @@ const UserAgentResponse$json = {
|
|||||||
'10': 'authChallenge'
|
'10': 'authChallenge'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'1': 'auth_ok',
|
'1': 'auth_result',
|
||||||
'3': 2,
|
'3': 2,
|
||||||
'4': 1,
|
'4': 1,
|
||||||
'5': 11,
|
'5': 14,
|
||||||
'6': '.arbiter.user_agent.AuthOk',
|
'6': '.arbiter.user_agent.AuthResult',
|
||||||
'9': 0,
|
'9': 0,
|
||||||
'10': 'authOk'
|
'10': 'authResult'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'1': 'unseal_start_response',
|
'1': 'unseal_start_response',
|
||||||
@@ -453,30 +514,42 @@ const UserAgentResponse$json = {
|
|||||||
'9': 0,
|
'9': 0,
|
||||||
'10': 'clientConnectionCancel'
|
'10': 'clientConnectionCancel'
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
'1': 'bootstrap_result',
|
||||||
|
'3': 13,
|
||||||
|
'4': 1,
|
||||||
|
'5': 14,
|
||||||
|
'6': '.arbiter.user_agent.BootstrapResult',
|
||||||
|
'9': 0,
|
||||||
|
'10': 'bootstrapResult'
|
||||||
|
},
|
||||||
],
|
],
|
||||||
'8': [
|
'8': [
|
||||||
{'1': 'payload'},
|
{'1': 'payload'},
|
||||||
|
{'1': '_id'},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Descriptor for `UserAgentResponse`. Decode as a `google.protobuf.DescriptorProto`.
|
/// Descriptor for `UserAgentResponse`. Decode as a `google.protobuf.DescriptorProto`.
|
||||||
final $typed_data.Uint8List userAgentResponseDescriptor = $convert.base64Decode(
|
final $typed_data.Uint8List userAgentResponseDescriptor = $convert.base64Decode(
|
||||||
'ChFVc2VyQWdlbnRSZXNwb25zZRJKCg5hdXRoX2NoYWxsZW5nZRgBIAEoCzIhLmFyYml0ZXIudX'
|
'ChFVc2VyQWdlbnRSZXNwb25zZRITCgJpZBgOIAEoBUgBUgJpZIgBARJKCg5hdXRoX2NoYWxsZW'
|
||||||
'Nlcl9hZ2VudC5BdXRoQ2hhbGxlbmdlSABSDWF1dGhDaGFsbGVuZ2USNQoHYXV0aF9vaxgCIAEo'
|
'5nZRgBIAEoCzIhLmFyYml0ZXIudXNlcl9hZ2VudC5BdXRoQ2hhbGxlbmdlSABSDWF1dGhDaGFs'
|
||||||
'CzIaLmFyYml0ZXIudXNlcl9hZ2VudC5BdXRoT2tIAFIGYXV0aE9rEl0KFXVuc2VhbF9zdGFydF'
|
'bGVuZ2USQQoLYXV0aF9yZXN1bHQYAiABKA4yHi5hcmJpdGVyLnVzZXJfYWdlbnQuQXV0aFJlc3'
|
||||||
'9yZXNwb25zZRgDIAEoCzInLmFyYml0ZXIudXNlcl9hZ2VudC5VbnNlYWxTdGFydFJlc3BvbnNl'
|
'VsdEgAUgphdXRoUmVzdWx0El0KFXVuc2VhbF9zdGFydF9yZXNwb25zZRgDIAEoCzInLmFyYml0'
|
||||||
'SABSE3Vuc2VhbFN0YXJ0UmVzcG9uc2USRwoNdW5zZWFsX3Jlc3VsdBgEIAEoDjIgLmFyYml0ZX'
|
'ZXIudXNlcl9hZ2VudC5VbnNlYWxTdGFydFJlc3BvbnNlSABSE3Vuc2VhbFN0YXJ0UmVzcG9uc2'
|
||||||
'IudXNlcl9hZ2VudC5VbnNlYWxSZXN1bHRIAFIMdW5zZWFsUmVzdWx0EkEKC3ZhdWx0X3N0YXRl'
|
'USRwoNdW5zZWFsX3Jlc3VsdBgEIAEoDjIgLmFyYml0ZXIudXNlcl9hZ2VudC5VbnNlYWxSZXN1'
|
||||||
'GAUgASgOMh4uYXJiaXRlci51c2VyX2FnZW50LlZhdWx0U3RhdGVIAFIKdmF1bHRTdGF0ZRJPCh'
|
'bHRIAFIMdW5zZWFsUmVzdWx0EkEKC3ZhdWx0X3N0YXRlGAUgASgOMh4uYXJiaXRlci51c2VyX2'
|
||||||
'Fldm1fd2FsbGV0X2NyZWF0ZRgGIAEoCzIhLmFyYml0ZXIuZXZtLldhbGxldENyZWF0ZVJlc3Bv'
|
'FnZW50LlZhdWx0U3RhdGVIAFIKdmF1bHRTdGF0ZRJPChFldm1fd2FsbGV0X2NyZWF0ZRgGIAEo'
|
||||||
'bnNlSABSD2V2bVdhbGxldENyZWF0ZRJJCg9ldm1fd2FsbGV0X2xpc3QYByABKAsyHy5hcmJpdG'
|
'CzIhLmFyYml0ZXIuZXZtLldhbGxldENyZWF0ZVJlc3BvbnNlSABSD2V2bVdhbGxldENyZWF0ZR'
|
||||||
'VyLmV2bS5XYWxsZXRMaXN0UmVzcG9uc2VIAFINZXZtV2FsbGV0TGlzdBJPChBldm1fZ3JhbnRf'
|
'JJCg9ldm1fd2FsbGV0X2xpc3QYByABKAsyHy5hcmJpdGVyLmV2bS5XYWxsZXRMaXN0UmVzcG9u'
|
||||||
'Y3JlYXRlGAggASgLMiMuYXJiaXRlci5ldm0uRXZtR3JhbnRDcmVhdGVSZXNwb25zZUgAUg5ldm'
|
'c2VIAFINZXZtV2FsbGV0TGlzdBJPChBldm1fZ3JhbnRfY3JlYXRlGAggASgLMiMuYXJiaXRlci'
|
||||||
'1HcmFudENyZWF0ZRJPChBldm1fZ3JhbnRfZGVsZXRlGAkgASgLMiMuYXJiaXRlci5ldm0uRXZt'
|
'5ldm0uRXZtR3JhbnRDcmVhdGVSZXNwb25zZUgAUg5ldm1HcmFudENyZWF0ZRJPChBldm1fZ3Jh'
|
||||||
'R3JhbnREZWxldGVSZXNwb25zZUgAUg5ldm1HcmFudERlbGV0ZRJJCg5ldm1fZ3JhbnRfbGlzdB'
|
'bnRfZGVsZXRlGAkgASgLMiMuYXJiaXRlci5ldm0uRXZtR3JhbnREZWxldGVSZXNwb25zZUgAUg'
|
||||||
'gKIAEoCzIhLmFyYml0ZXIuZXZtLkV2bUdyYW50TGlzdFJlc3BvbnNlSABSDGV2bUdyYW50TGlz'
|
'5ldm1HcmFudERlbGV0ZRJJCg5ldm1fZ3JhbnRfbGlzdBgKIAEoCzIhLmFyYml0ZXIuZXZtLkV2'
|
||||||
'dBJpChljbGllbnRfY29ubmVjdGlvbl9yZXF1ZXN0GAsgASgLMisuYXJiaXRlci51c2VyX2FnZW'
|
'bUdyYW50TGlzdFJlc3BvbnNlSABSDGV2bUdyYW50TGlzdBJpChljbGllbnRfY29ubmVjdGlvbl'
|
||||||
'50LkNsaWVudENvbm5lY3Rpb25SZXF1ZXN0SABSF2NsaWVudENvbm5lY3Rpb25SZXF1ZXN0EmYK'
|
'9yZXF1ZXN0GAsgASgLMisuYXJiaXRlci51c2VyX2FnZW50LkNsaWVudENvbm5lY3Rpb25SZXF1'
|
||||||
'GGNsaWVudF9jb25uZWN0aW9uX2NhbmNlbBgMIAEoCzIqLmFyYml0ZXIudXNlcl9hZ2VudC5DbG'
|
'ZXN0SABSF2NsaWVudENvbm5lY3Rpb25SZXF1ZXN0EmYKGGNsaWVudF9jb25uZWN0aW9uX2Nhbm'
|
||||||
'llbnRDb25uZWN0aW9uQ2FuY2VsSABSFmNsaWVudENvbm5lY3Rpb25DYW5jZWxCCQoHcGF5bG9h'
|
'NlbBgMIAEoCzIqLmFyYml0ZXIudXNlcl9hZ2VudC5DbGllbnRDb25uZWN0aW9uQ2FuY2VsSABS'
|
||||||
'ZA==');
|
'FmNsaWVudENvbm5lY3Rpb25DYW5jZWwSUAoQYm9vdHN0cmFwX3Jlc3VsdBgNIAEoDjIjLmFyYm'
|
||||||
|
'l0ZXIudXNlcl9hZ2VudC5Cb290c3RyYXBSZXN1bHRIAFIPYm9vdHN0cmFwUmVzdWx0QgkKB3Bh'
|
||||||
|
'eWxvYWRCBQoDX2lk');
|
||||||
|
|||||||
@@ -13,9 +13,7 @@ Future<VaultState?> vaultState(Ref ref) async {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
await conn.send(UserAgentRequest(queryVaultState: Empty()));
|
final resp = await conn.request(UserAgentRequest(queryVaultState: Empty()));
|
||||||
|
|
||||||
final resp = await conn.receive();
|
|
||||||
if (resp.whichPayload() != UserAgentResponse_Payload.vaultState) {
|
if (resp.whichPayload() != UserAgentResponse_Payload.vaultState) {
|
||||||
talker.warning('Expected vault state response, got ${resp.whichPayload()}');
|
talker.warning('Expected vault state response, got ${resp.whichPayload()}');
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
@@ -46,4 +46,4 @@ final class VaultStateProvider
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
String _$vaultStateHash() => r'1fd975a9661de1f62beef9eb1c7c439f377a8b88';
|
String _$vaultStateHash() => r'97085e49bc3a296e36fa6c04a8f4c9abafac0835';
|
||||||
|
|||||||
Reference in New Issue
Block a user