fix(tls, client): added proper errors to client & schema to connect url; added localhost wildcard for self-signed setup
This commit is contained in:
@@ -14,19 +14,7 @@ use crate::{
|
||||
};
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum ConnectError {
|
||||
#[error("Could not establish connection")]
|
||||
Connection(#[from] tonic::transport::Error),
|
||||
|
||||
#[error("Invalid server URI")]
|
||||
InvalidUri(#[from] http::uri::InvalidUri),
|
||||
|
||||
#[error("Invalid CA certificate")]
|
||||
InvalidCaCert(#[from] webpki::Error),
|
||||
|
||||
#[error("gRPC error")]
|
||||
Grpc(#[from] tonic::Status),
|
||||
|
||||
pub enum AuthError {
|
||||
#[error("Auth challenge was not returned by server")]
|
||||
MissingAuthChallenge,
|
||||
|
||||
@@ -43,15 +31,15 @@ pub enum ConnectError {
|
||||
Storage(#[from] StorageError),
|
||||
}
|
||||
|
||||
fn map_auth_result(code: i32) -> ConnectError {
|
||||
fn map_auth_result(code: i32) -> AuthError {
|
||||
match AuthResult::try_from(code).unwrap_or(AuthResult::Unspecified) {
|
||||
AuthResult::ApprovalDenied => ConnectError::ApprovalDenied,
|
||||
AuthResult::NoUserAgentsOnline => ConnectError::NoUserAgentsOnline,
|
||||
AuthResult::ApprovalDenied => AuthError::ApprovalDenied,
|
||||
AuthResult::NoUserAgentsOnline => AuthError::NoUserAgentsOnline,
|
||||
AuthResult::Unspecified
|
||||
| AuthResult::Success
|
||||
| AuthResult::InvalidKey
|
||||
| AuthResult::InvalidSignature
|
||||
| AuthResult::Internal => ConnectError::UnexpectedAuthResponse,
|
||||
| AuthResult::Internal => AuthError::UnexpectedAuthResponse,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,7 +47,7 @@ async fn send_auth_challenge_request(
|
||||
transport: &mut ClientTransport,
|
||||
metadata: ClientMetadata,
|
||||
key: &ed25519_dalek::SigningKey,
|
||||
) -> std::result::Result<(), ConnectError> {
|
||||
) -> std::result::Result<(), AuthError> {
|
||||
transport
|
||||
.send(ClientRequest {
|
||||
request_id: next_request_id(),
|
||||
@@ -75,22 +63,22 @@ async fn send_auth_challenge_request(
|
||||
)),
|
||||
})
|
||||
.await
|
||||
.map_err(|_| ConnectError::UnexpectedAuthResponse)
|
||||
.map_err(|_| AuthError::UnexpectedAuthResponse)
|
||||
}
|
||||
|
||||
async fn receive_auth_challenge(
|
||||
transport: &mut ClientTransport,
|
||||
) -> std::result::Result<arbiter_proto::proto::client::AuthChallenge, ConnectError> {
|
||||
) -> std::result::Result<arbiter_proto::proto::client::AuthChallenge, AuthError> {
|
||||
let response = transport
|
||||
.recv()
|
||||
.await
|
||||
.map_err(|_| ConnectError::MissingAuthChallenge)?;
|
||||
.map_err(|_| AuthError::MissingAuthChallenge)?;
|
||||
|
||||
let payload = response.payload.ok_or(ConnectError::MissingAuthChallenge)?;
|
||||
let payload = response.payload.ok_or(AuthError::MissingAuthChallenge)?;
|
||||
match payload {
|
||||
ClientResponsePayload::AuthChallenge(challenge) => Ok(challenge),
|
||||
ClientResponsePayload::AuthResult(result) => Err(map_auth_result(result)),
|
||||
_ => Err(ConnectError::UnexpectedAuthResponse),
|
||||
_ => Err(AuthError::UnexpectedAuthResponse),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -98,7 +86,7 @@ async fn send_auth_challenge_solution(
|
||||
transport: &mut ClientTransport,
|
||||
key: &ed25519_dalek::SigningKey,
|
||||
challenge: arbiter_proto::proto::client::AuthChallenge,
|
||||
) -> std::result::Result<(), ConnectError> {
|
||||
) -> std::result::Result<(), AuthError> {
|
||||
let challenge_payload = format_challenge(challenge.nonce, &challenge.pubkey);
|
||||
let signature = key.sign(&challenge_payload).to_bytes().to_vec();
|
||||
|
||||
@@ -110,20 +98,20 @@ async fn send_auth_challenge_solution(
|
||||
)),
|
||||
})
|
||||
.await
|
||||
.map_err(|_| ConnectError::UnexpectedAuthResponse)
|
||||
.map_err(|_| AuthError::UnexpectedAuthResponse)
|
||||
}
|
||||
|
||||
async fn receive_auth_confirmation(
|
||||
transport: &mut ClientTransport,
|
||||
) -> std::result::Result<(), ConnectError> {
|
||||
) -> std::result::Result<(), AuthError> {
|
||||
let response = transport
|
||||
.recv()
|
||||
.await
|
||||
.map_err(|_| ConnectError::UnexpectedAuthResponse)?;
|
||||
.map_err(|_| AuthError::UnexpectedAuthResponse)?;
|
||||
|
||||
let payload = response
|
||||
.payload
|
||||
.ok_or(ConnectError::UnexpectedAuthResponse)?;
|
||||
.ok_or(AuthError::UnexpectedAuthResponse)?;
|
||||
match payload {
|
||||
ClientResponsePayload::AuthResult(result)
|
||||
if AuthResult::try_from(result).ok() == Some(AuthResult::Success) =>
|
||||
@@ -131,7 +119,7 @@ async fn receive_auth_confirmation(
|
||||
Ok(())
|
||||
}
|
||||
ClientResponsePayload::AuthResult(result) => Err(map_auth_result(result)),
|
||||
_ => Err(ConnectError::UnexpectedAuthResponse),
|
||||
_ => Err(AuthError::UnexpectedAuthResponse),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -139,7 +127,7 @@ pub(crate) async fn authenticate(
|
||||
transport: &mut ClientTransport,
|
||||
metadata: ClientMetadata,
|
||||
key: &ed25519_dalek::SigningKey,
|
||||
) -> std::result::Result<(), ConnectError> {
|
||||
) -> std::result::Result<(), AuthError> {
|
||||
send_auth_challenge_request(transport, metadata, key).await?;
|
||||
let challenge = receive_auth_challenge(transport).await?;
|
||||
send_auth_challenge_solution(transport, key, challenge).await?;
|
||||
|
||||
@@ -3,6 +3,7 @@ use std::io::{self, Write};
|
||||
|
||||
use arbiter_client::ArbiterClient;
|
||||
use arbiter_proto::{ClientMetadata, url::ArbiterUrl};
|
||||
use tonic::ConnectError;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
@@ -22,6 +23,8 @@ async fn main() {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
|
||||
let url = match ArbiterUrl::try_from(input) {
|
||||
Ok(url) => url,
|
||||
Err(err) => {
|
||||
@@ -30,6 +33,8 @@ async fn main() {
|
||||
}
|
||||
};
|
||||
|
||||
println!("{:#?}", url);
|
||||
|
||||
let metadata = ClientMetadata {
|
||||
name: "arbiter-client test_connect".to_string(),
|
||||
description: Some("Manual connection smoke test".to_string()),
|
||||
@@ -38,6 +43,6 @@ async fn main() {
|
||||
|
||||
match ArbiterClient::connect(url, metadata).await {
|
||||
Ok(_) => println!("Connected and authenticated successfully."),
|
||||
Err(err) => eprintln!("Failed to connect: {err}"),
|
||||
Err(err) => eprintln!("Failed to connect: {:#?}", err),
|
||||
}
|
||||
}
|
||||
@@ -5,21 +5,32 @@ use tokio_stream::wrappers::ReceiverStream;
|
||||
use tonic::transport::ClientTlsConfig;
|
||||
|
||||
use crate::{
|
||||
auth::{ConnectError, authenticate},
|
||||
storage::{FileSigningKeyStorage, SigningKeyStorage},
|
||||
transport::{BUFFER_LENGTH, ClientTransport},
|
||||
StorageError, auth::{AuthError, authenticate}, storage::{FileSigningKeyStorage, SigningKeyStorage}, transport::{BUFFER_LENGTH, ClientTransport}
|
||||
};
|
||||
|
||||
#[cfg(feature = "evm")]
|
||||
use crate::wallets::evm::ArbiterEvmWallet;
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum ClientError {
|
||||
pub enum Error {
|
||||
#[error("gRPC error")]
|
||||
Grpc(#[from] tonic::Status),
|
||||
|
||||
#[error("Connection closed by server")]
|
||||
ConnectionClosed,
|
||||
#[error("Could not establish connection")]
|
||||
Connection(#[from] tonic::transport::Error),
|
||||
|
||||
#[error("Invalid server URI")]
|
||||
InvalidUri(#[from] http::uri::InvalidUri),
|
||||
|
||||
#[error("Invalid CA certificate")]
|
||||
InvalidCaCert(#[from] webpki::Error),
|
||||
|
||||
#[error("Authentication error")]
|
||||
Authentication(#[from] AuthError),
|
||||
|
||||
#[error("Storage error")]
|
||||
Storage(#[from] StorageError),
|
||||
|
||||
}
|
||||
|
||||
pub struct ArbiterClient {
|
||||
@@ -28,7 +39,7 @@ pub struct ArbiterClient {
|
||||
}
|
||||
|
||||
impl ArbiterClient {
|
||||
pub async fn connect(url: ArbiterUrl, metadata: ClientMetadata) -> Result<Self, ConnectError> {
|
||||
pub async fn connect(url: ArbiterUrl, metadata: ClientMetadata) -> Result<Self, Error> {
|
||||
let storage = FileSigningKeyStorage::from_default_location()?;
|
||||
Self::connect_with_storage(url, metadata, &storage).await
|
||||
}
|
||||
@@ -37,7 +48,7 @@ impl ArbiterClient {
|
||||
url: ArbiterUrl,
|
||||
metadata: ClientMetadata,
|
||||
storage: &S,
|
||||
) -> Result<Self, ConnectError> {
|
||||
) -> Result<Self, Error> {
|
||||
let key = storage.load_or_create()?;
|
||||
Self::connect_with_key(url, metadata, key).await
|
||||
}
|
||||
@@ -46,11 +57,11 @@ impl ArbiterClient {
|
||||
url: ArbiterUrl,
|
||||
metadata: ClientMetadata,
|
||||
key: ed25519_dalek::SigningKey,
|
||||
) -> Result<Self, ConnectError> {
|
||||
) -> Result<Self, Error> {
|
||||
let anchor = webpki::anchor_from_trusted_cert(&url.ca_cert)?.to_owned();
|
||||
let tls = ClientTlsConfig::new().trust_anchor(anchor);
|
||||
|
||||
let channel = tonic::transport::Channel::from_shared(format!("{}:{}", url.host, url.port))?
|
||||
let channel = tonic::transport::Channel::from_shared(format!("https://{}:{}", url.host, url.port))?
|
||||
.tls_config(tls)?
|
||||
.connect()
|
||||
.await?;
|
||||
@@ -72,7 +83,7 @@ impl ArbiterClient {
|
||||
}
|
||||
|
||||
#[cfg(feature = "evm")]
|
||||
pub async fn evm_wallets(&self) -> Result<Vec<ArbiterEvmWallet>, ClientError> {
|
||||
pub async fn evm_wallets(&self) -> Result<Vec<ArbiterEvmWallet>, Error> {
|
||||
todo!("fetch EVM wallet list from server")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,8 +4,8 @@ mod storage;
|
||||
mod transport;
|
||||
pub mod wallets;
|
||||
|
||||
pub use auth::ConnectError;
|
||||
pub use client::{ArbiterClient, ClientError};
|
||||
pub use auth::AuthError;
|
||||
pub use client::{ArbiterClient, Error};
|
||||
pub use storage::{FileSigningKeyStorage, SigningKeyStorage, StorageError};
|
||||
|
||||
#[cfg(feature = "evm")]
|
||||
|
||||
@@ -7,6 +7,8 @@ const ARBITER_URL_SCHEME: &str = "arbiter";
|
||||
const CERT_QUERY_KEY: &str = "cert";
|
||||
const BOOTSTRAP_TOKEN_QUERY_KEY: &str = "bootstrap_token";
|
||||
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ArbiterUrl {
|
||||
pub host: String,
|
||||
pub port: u16,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use std::string::FromUtf8Error;
|
||||
use std::{net::IpAddr, string::FromUtf8Error};
|
||||
|
||||
use diesel::{ExpressionMethods as _, QueryDsl, SelectableHelper as _};
|
||||
use diesel_async::{AsyncConnection, RunQueryDsl};
|
||||
@@ -6,7 +6,7 @@ use miette::Diagnostic;
|
||||
use pem::Pem;
|
||||
use rcgen::{
|
||||
BasicConstraints, Certificate, CertificateParams, CertifiedIssuer, DistinguishedName, DnType,
|
||||
IsCa, Issuer, KeyPair, KeyUsagePurpose,
|
||||
IsCa, Issuer, KeyPair, KeyUsagePurpose, SanType,
|
||||
};
|
||||
use rustls::pki_types::pem::PemObject;
|
||||
use thiserror::Error;
|
||||
@@ -114,6 +114,11 @@ impl TlsCa {
|
||||
KeyUsagePurpose::DigitalSignature,
|
||||
KeyUsagePurpose::KeyEncipherment,
|
||||
];
|
||||
params
|
||||
.subject_alt_names
|
||||
.push(SanType::IpAddress(IpAddr::from([
|
||||
127, 0, 0, 1,
|
||||
])));
|
||||
|
||||
let mut dn = DistinguishedName::new();
|
||||
dn.push(DnType::CommonName, "Arbiter Instance Leaf");
|
||||
|
||||
Reference in New Issue
Block a user