-
Notifications
You must be signed in to change notification settings - Fork 8
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Feature/reg over mix #633
Feature/reg over mix #633
Changes from 20 commits
973d93c
ce14558
88a585a
5615d61
467e848
b87ec62
11c21b0
6b888bf
44b4618
0e10597
e7c0ae8
7fa254a
40f3581
023d7c5
da8d537
12090a2
f9578e2
0d9e9ab
f3f2379
fa2e6fa
21852bb
25b26a9
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
[package] | ||
name = "nym-authenticator-client" | ||
version = "0.1.0" | ||
authors.workspace = true | ||
repository.workspace = true | ||
homepage.workspace = true | ||
documentation.workspace = true | ||
edition.workspace = true | ||
license.workspace = true | ||
|
||
[dependencies] | ||
nym-authenticator-requests.workspace = true | ||
nym-sdk.workspace = true | ||
nym-wireguard-types.workspace = true | ||
tracing.workspace = true | ||
thiserror.workspace = true | ||
tokio.workspace = true | ||
tracing-subscriber.workspace = true | ||
futures.workspace = true |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
#[derive(thiserror::Error, Debug)] | ||
pub enum Error { | ||
#[error("got reply for connect request, but it appears intended for the wrong address?")] | ||
GotReplyIntendedForWrongAddress, | ||
|
||
#[error("mixnet client stopped returning responses")] | ||
NoMixnetMessagesReceived, | ||
|
||
#[error("failed to get version from message")] | ||
NoVersionInMessage, | ||
|
||
#[error("received response with version v{received}, the client is too new and can only understand v{expected}")] | ||
ReceivedResponseWithOldVersion { expected: u8, received: u8 }, | ||
|
||
#[error("received response with version v{received}, the client is too old and can only understand v{expected}")] | ||
ReceivedResponseWithNewVersion { expected: u8, received: u8 }, | ||
|
||
#[error(transparent)] | ||
SdkError(#[from] nym_sdk::Error), | ||
|
||
#[error("timeout waiting for connect response from exit gateway (authenticator)")] | ||
TimeoutWaitingForConnectResponse, | ||
} | ||
|
||
// Result type based on our error type | ||
pub type Result<T> = std::result::Result<T, Error>; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,186 @@ | ||
use std::{cmp::Ordering, sync::Arc, time::Duration}; | ||
|
||
use nym_authenticator_requests::v1::{ | ||
request::AuthenticatorRequest, response::AuthenticatorResponse, | ||
}; | ||
use nym_sdk::mixnet::{ | ||
MixnetClient, MixnetClientSender, MixnetMessageSender, Recipient, ReconstructedMessage, | ||
TransmissionLane, | ||
}; | ||
use nym_wireguard_types::ClientMessage; | ||
use tracing::{debug, error}; | ||
|
||
mod error; | ||
|
||
pub use crate::error::{Error, Result}; | ||
|
||
#[derive(Clone)] | ||
pub struct SharedMixnetClient(Arc<tokio::sync::Mutex<Option<MixnetClient>>>); | ||
|
||
impl SharedMixnetClient { | ||
pub fn new(mixnet_client: MixnetClient) -> Self { | ||
Self(Arc::new(tokio::sync::Mutex::new(Some(mixnet_client)))) | ||
} | ||
|
||
pub async fn lock(&self) -> tokio::sync::MutexGuard<'_, Option<MixnetClient>> { | ||
self.0.lock().await | ||
} | ||
|
||
pub async fn nym_address(&self) -> Recipient { | ||
*self.lock().await.as_ref().unwrap().nym_address() | ||
} | ||
|
||
pub async fn send(&self, msg: nym_sdk::mixnet::InputMessage) -> Result<()> { | ||
self.lock().await.as_mut().unwrap().send(msg).await?; | ||
Ok(()) | ||
} | ||
|
||
pub fn inner(&self) -> Arc<tokio::sync::Mutex<Option<MixnetClient>>> { | ||
self.0.clone() | ||
} | ||
} | ||
|
||
pub struct AuthClient { | ||
mixnet_client: SharedMixnetClient, | ||
mixnet_sender: MixnetClientSender, | ||
nym_address: Recipient, | ||
} | ||
|
||
impl AuthClient { | ||
pub async fn new(mixnet_client: SharedMixnetClient) -> Self { | ||
let mixnet_sender = mixnet_client.lock().await.as_ref().unwrap().split_sender(); | ||
let nym_address = *mixnet_client | ||
.inner() | ||
.lock() | ||
.await | ||
.as_ref() | ||
.unwrap() | ||
.nym_address(); | ||
Self { | ||
mixnet_client, | ||
mixnet_sender, | ||
nym_address, | ||
} | ||
} | ||
|
||
// A workaround until we can extract SharedMixnetClient to a common crate | ||
pub async fn new_from_inner( | ||
mixnet_client: Arc<tokio::sync::Mutex<Option<MixnetClient>>>, | ||
) -> Self { | ||
let mixnet_client = SharedMixnetClient(mixnet_client); | ||
Self::new(mixnet_client).await | ||
} | ||
|
||
pub async fn send( | ||
&mut self, | ||
message: ClientMessage, | ||
authenticator_address: Recipient, | ||
) -> Result<AuthenticatorResponse> { | ||
self.send_inner(message, authenticator_address).await | ||
} | ||
|
||
async fn send_inner( | ||
&mut self, | ||
message: ClientMessage, | ||
authenticator_address: Recipient, | ||
) -> Result<AuthenticatorResponse> { | ||
let request_id = self | ||
.send_connect_request(message, authenticator_address) | ||
.await?; | ||
|
||
debug!("Waiting for reply..."); | ||
self.listen_for_connect_response(request_id).await | ||
} | ||
|
||
async fn send_connect_request( | ||
&self, | ||
message: ClientMessage, | ||
authenticator_address: Recipient, | ||
) -> Result<u64> { | ||
let (request, request_id) = match message { | ||
ClientMessage::Initial(init_message) => { | ||
AuthenticatorRequest::new_initial_request(init_message, self.nym_address) | ||
} | ||
ClientMessage::Final(gateway_client) => { | ||
AuthenticatorRequest::new_final_request(gateway_client, self.nym_address) | ||
} | ||
}; | ||
debug!("Sent connect request with version v{}", request.version); | ||
|
||
self.mixnet_sender | ||
.send(nym_sdk::mixnet::InputMessage::new_regular( | ||
authenticator_address, | ||
request.to_bytes().unwrap(), | ||
TransmissionLane::General, | ||
None, | ||
)) | ||
.await?; | ||
|
||
Ok(request_id) | ||
} | ||
|
||
async fn listen_for_connect_response(&self, request_id: u64) -> Result<AuthenticatorResponse> { | ||
// Connecting is basically synchronous from the perspective of the mixnet client, so it's safe | ||
// to just grab ahold of the mutex and keep it until we get the response. | ||
let mut mixnet_client_handle = self.mixnet_client.lock().await; | ||
let mixnet_client = mixnet_client_handle.as_mut().unwrap(); | ||
|
||
let timeout = tokio::time::sleep(Duration::from_secs(5)); | ||
tokio::pin!(timeout); | ||
|
||
loop { | ||
tokio::select! { | ||
_ = &mut timeout => { | ||
error!("Timed out waiting for reply to connect request"); | ||
return Err(Error::TimeoutWaitingForConnectResponse); | ||
} | ||
msgs = mixnet_client.wait_for_messages() => match msgs { | ||
None => { | ||
return Err(Error::NoMixnetMessagesReceived); | ||
} | ||
Some(msgs) => { | ||
for msg in msgs { | ||
// Confirm that the version is correct | ||
check_auth_message_version(&msg)?; | ||
|
||
// Then we deserialize the message | ||
debug!("AuthClient: got message while waiting for connect response"); | ||
let Ok(response) = AuthenticatorResponse::from_reconstructed_message(&msg) else { | ||
// This is ok, it's likely just one of our self-pings | ||
debug!("Failed to deserialize reconstructed message"); | ||
continue; | ||
}; | ||
|
||
if response.id() == Some(request_id) { | ||
debug!("Got response with matching id"); | ||
return Ok(response); | ||
} | ||
} | ||
} | ||
} | ||
} | ||
} | ||
} | ||
} | ||
|
||
fn check_auth_message_version(message: &ReconstructedMessage) -> Result<()> { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd rather have this a proper struct (the whole message), but we can leave it for a future iteration |
||
// Assuing it's an Authenticator message, it will have a version as its first byte | ||
if let Some(version) = message.message.first() { | ||
match version.cmp(&nym_authenticator_requests::CURRENT_VERSION) { | ||
Ordering::Greater => Err(Error::ReceivedResponseWithNewVersion { | ||
expected: nym_authenticator_requests::CURRENT_VERSION, | ||
received: *version, | ||
}), | ||
Ordering::Less => Err(Error::ReceivedResponseWithOldVersion { | ||
expected: nym_authenticator_requests::CURRENT_VERSION, | ||
received: *version, | ||
}), | ||
Ordering::Equal => { | ||
// We're good | ||
Ok(()) | ||
} | ||
} | ||
} else { | ||
Err(Error::NoVersionInMessage) | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net> | ||
// SPDX-License-Identifier: GPL-3.0-only | ||
|
||
use std::{fmt::Display, str::FromStr}; | ||
|
||
use crate::{error::Result, DescribedGatewayWithLocation, Error}; | ||
use nym_sdk::mixnet::Recipient; | ||
|
||
// optional, until we remove the wireguard feature flag | ||
#[derive(Debug, Copy, Clone)] | ||
pub struct AuthAddress(pub Option<Recipient>); | ||
|
||
#[derive(Debug, Copy, Clone)] | ||
pub struct AuthAddresses { | ||
entry_addr: AuthAddress, | ||
exit_addr: AuthAddress, | ||
} | ||
|
||
impl AuthAddresses { | ||
pub fn new(entry_addr: AuthAddress, exit_addr: AuthAddress) -> Self { | ||
AuthAddresses { | ||
entry_addr, | ||
exit_addr, | ||
} | ||
} | ||
|
||
pub fn entry(&self) -> AuthAddress { | ||
self.entry_addr | ||
} | ||
|
||
pub fn exit(&self) -> AuthAddress { | ||
self.exit_addr | ||
} | ||
} | ||
|
||
impl Display for AuthAddresses { | ||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { | ||
write!( | ||
f, | ||
"entry: {:?} exit: {:?}", | ||
self.entry_addr.0, self.exit_addr.0 | ||
) | ||
} | ||
} | ||
|
||
pub fn extract_authenticator( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is great, love it |
||
gateways: &[DescribedGatewayWithLocation], | ||
identity: String, | ||
) -> Result<AuthAddress> { | ||
let auth_addr = gateways | ||
.iter() | ||
.find(|gw| *gw.gateway.bond.identity() == identity) | ||
.ok_or(Error::RequestedGatewayIdNotFound(identity.clone()))? | ||
.gateway | ||
.self_described | ||
.clone() | ||
.ok_or(Error::NoGatewayDescriptionAvailable(identity))? | ||
.authenticator | ||
.map(|auth| Recipient::from_str(&auth.address)) | ||
.transpose() | ||
.map_err(|_| Error::RecipientFormattingError)?; | ||
Ok(AuthAddress(auth_addr)) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,5 @@ | ||
pub(crate) mod auth_addresses; | ||
pub(crate) mod described_gateway; | ||
pub(crate) mod entry_point; | ||
pub(crate) mod exit_point; | ||
pub(crate) mod ipr_address; | ||
pub(crate) mod ipr_addresses; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we want to make this configurable? Something to keep in mind for the future