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