diff --git a/common/authenticator-requests/src/error.rs b/common/authenticator-requests/src/error.rs index d07ad7c1e7..fc9f8700d5 100644 --- a/common/authenticator-requests/src/error.rs +++ b/common/authenticator-requests/src/error.rs @@ -19,4 +19,7 @@ pub enum Error { #[source] source: hmac::digest::MacError, }, + + #[error("conversion: {0}")] + Conversion(String), } diff --git a/common/authenticator-requests/src/lib.rs b/common/authenticator-requests/src/lib.rs index 80112f061c..dd98cc9f7f 100644 --- a/common/authenticator-requests/src/lib.rs +++ b/common/authenticator-requests/src/lib.rs @@ -3,13 +3,14 @@ pub mod v1; pub mod v2; +pub mod v3; mod error; pub use error::Error; -pub use v2 as latest; +pub use v3 as latest; -pub const CURRENT_VERSION: u8 = 2; +pub const CURRENT_VERSION: u8 = 3; fn make_bincode_serializer() -> impl bincode::Options { use bincode::Options; diff --git a/common/authenticator-requests/src/v3/conversion.rs b/common/authenticator-requests/src/v3/conversion.rs new file mode 100644 index 0000000000..15659271b2 --- /dev/null +++ b/common/authenticator-requests/src/v3/conversion.rs @@ -0,0 +1,188 @@ +// Copyright 2024 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use nym_service_provider_requests_common::{Protocol, ServiceProviderType}; + +use crate::{v2, v3}; + +impl From for v3::request::AuthenticatorRequest { + fn from(authenticator_request: v2::request::AuthenticatorRequest) -> Self { + Self { + protocol: Protocol { + version: 2, + service_provider_type: ServiceProviderType::Authenticator, + }, + data: authenticator_request.data.into(), + reply_to: authenticator_request.reply_to, + request_id: authenticator_request.request_id, + } + } +} + +impl From for v3::request::AuthenticatorRequestData { + fn from(authenticator_request_data: v2::request::AuthenticatorRequestData) -> Self { + match authenticator_request_data { + v2::request::AuthenticatorRequestData::Initial(init_msg) => { + v3::request::AuthenticatorRequestData::Initial(init_msg.into()) + } + v2::request::AuthenticatorRequestData::Final(gw_client) => { + v3::request::AuthenticatorRequestData::Final(gw_client.into()) + } + v2::request::AuthenticatorRequestData::QueryBandwidth(pub_key) => { + v3::request::AuthenticatorRequestData::QueryBandwidth(pub_key) + } + } + } +} + +impl From for v3::registration::InitMessage { + fn from(init_msg: v2::registration::InitMessage) -> Self { + Self { + pub_key: init_msg.pub_key, + } + } +} + +impl From> for Box { + fn from(gw_client: Box) -> Self { + Box::new(v3::registration::FinalMessage { + gateway_client: gw_client.gateway_client.into(), + credential: gw_client.credential, + }) + } +} + +impl From for v3::registration::GatewayClient { + fn from(gw_client: v2::registration::GatewayClient) -> Self { + Self { + pub_key: gw_client.pub_key, + private_ip: gw_client.private_ip, + mac: gw_client.mac.into(), + } + } +} + +impl From for v2::registration::GatewayClient { + fn from(gw_client: v3::registration::GatewayClient) -> Self { + Self { + pub_key: gw_client.pub_key, + private_ip: gw_client.private_ip, + mac: gw_client.mac.into(), + } + } +} + +impl From for v3::registration::ClientMac { + fn from(mac: v2::registration::ClientMac) -> Self { + Self::new(mac.to_vec()) + } +} + +impl From for v2::registration::ClientMac { + fn from(mac: v3::registration::ClientMac) -> Self { + Self::new(mac.to_vec()) + } +} + +impl TryFrom for v2::response::AuthenticatorResponse { + type Error = crate::Error; + + fn try_from( + authenticator_response: v3::response::AuthenticatorResponse, + ) -> Result { + Ok(Self { + data: authenticator_response.data.try_into()?, + reply_to: authenticator_response.reply_to, + protocol: authenticator_response.protocol, + }) + } +} + +impl TryFrom for v2::response::AuthenticatorResponseData { + type Error = crate::Error; + + fn try_from( + authenticator_response_data: v3::response::AuthenticatorResponseData, + ) -> Result { + match authenticator_response_data { + v3::response::AuthenticatorResponseData::PendingRegistration( + pending_registration_response, + ) => Ok( + v2::response::AuthenticatorResponseData::PendingRegistration( + pending_registration_response.into(), + ), + ), + v3::response::AuthenticatorResponseData::Registered(registered_response) => Ok( + v2::response::AuthenticatorResponseData::Registered(registered_response.into()), + ), + v3::response::AuthenticatorResponseData::RemainingBandwidth( + remaining_bandwidth_response, + ) => Ok(v2::response::AuthenticatorResponseData::RemainingBandwidth( + remaining_bandwidth_response.into(), + )), + v3::response::AuthenticatorResponseData::TopUpBandwidth(_) => { + Err(Self::Error::Conversion( + "a v2 request couldn't produce a v3 only type of response".to_string(), + )) + } + } + } +} + +impl From for v2::response::PendingRegistrationResponse { + fn from(value: v3::response::PendingRegistrationResponse) -> Self { + Self { + request_id: value.request_id, + reply_to: value.reply_to, + reply: value.reply.into(), + } + } +} + +impl From for v2::response::RegisteredResponse { + fn from(value: v3::response::RegisteredResponse) -> Self { + Self { + request_id: value.request_id, + reply_to: value.reply_to, + reply: value.reply.into(), + } + } +} + +impl From for v2::response::RemainingBandwidthResponse { + fn from(value: v3::response::RemainingBandwidthResponse) -> Self { + Self { + request_id: value.request_id, + reply_to: value.reply_to, + reply: value.reply.map(Into::into), + } + } +} + +impl From for v2::registration::RegistrationData { + fn from(value: v3::registration::RegistrationData) -> Self { + Self { + nonce: value.nonce, + gateway_data: value.gateway_data.into(), + wg_port: value.wg_port, + } + } +} + +impl From for v2::registration::RegistredData { + fn from(value: v3::registration::RegistredData) -> Self { + Self { + pub_key: value.pub_key, + private_ip: value.private_ip, + wg_port: value.wg_port, + } + } +} + +impl From for v2::registration::RemainingBandwidthData { + fn from(value: v3::registration::RemainingBandwidthData) -> Self { + Self { + available_bandwidth: value.available_bandwidth, + } + } +} diff --git a/common/authenticator-requests/src/v3/mod.rs b/common/authenticator-requests/src/v3/mod.rs new file mode 100644 index 0000000000..1a060367d7 --- /dev/null +++ b/common/authenticator-requests/src/v3/mod.rs @@ -0,0 +1,10 @@ +// Copyright 2024 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +pub mod conversion; +pub mod registration; +pub mod request; +pub mod response; +pub mod topup; + +pub const VERSION: u8 = 3; diff --git a/common/authenticator-requests/src/v3/registration.rs b/common/authenticator-requests/src/v3/registration.rs new file mode 100644 index 0000000000..70dceb7690 --- /dev/null +++ b/common/authenticator-requests/src/v3/registration.rs @@ -0,0 +1,227 @@ +// -2024 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::error::Error; +use base64::{engine::general_purpose, Engine}; +use nym_credentials_interface::CredentialSpendingData; +use nym_wireguard_types::PeerPublicKey; +use serde::{Deserialize, Serialize}; +use std::collections::HashMap; +use std::net::IpAddr; +use std::time::SystemTime; +use std::{fmt, ops::Deref, str::FromStr}; + +#[cfg(feature = "verify")] +use hmac::{Hmac, Mac}; +#[cfg(feature = "verify")] +use nym_crypto::asymmetric::encryption::PrivateKey; +#[cfg(feature = "verify")] +use sha2::Sha256; + +pub type PendingRegistrations = HashMap; +pub type PrivateIPs = HashMap; + +#[cfg(feature = "verify")] +pub type HmacSha256 = Hmac; + +pub type Nonce = u64; +pub type Taken = Option; + +pub const BANDWIDTH_CAP_PER_DAY: u64 = 1024 * 1024 * 1024; // 1 GB + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct InitMessage { + /// Base64 encoded x25519 public key + pub pub_key: PeerPublicKey, +} + +impl InitMessage { + pub fn new(pub_key: PeerPublicKey) -> Self { + InitMessage { pub_key } + } +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct FinalMessage { + /// Gateway client data + pub gateway_client: GatewayClient, + + /// Ecash credential + pub credential: Option, +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct RegistrationData { + pub nonce: u64, + pub gateway_data: GatewayClient, + pub wg_port: u16, +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct RegistredData { + pub pub_key: PeerPublicKey, + pub private_ip: IpAddr, + pub wg_port: u16, +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct RemainingBandwidthData { + pub available_bandwidth: i64, +} + +/// Client that wants to register sends its PublicKey bytes mac digest encrypted with a DH shared secret. +/// Gateway/Nym node can then verify pub_key payload using the same process +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct GatewayClient { + /// Base64 encoded x25519 public key + pub pub_key: PeerPublicKey, + + /// Assigned private IP + pub private_ip: IpAddr, + + /// Sha256 hmac on the data (alongside the prior nonce) + pub mac: ClientMac, +} + +impl GatewayClient { + #[cfg(feature = "verify")] + pub fn new( + local_secret: &PrivateKey, + remote_public: x25519_dalek::PublicKey, + private_ip: IpAddr, + nonce: u64, + ) -> Self { + // convert from 1.0 x25519-dalek private key into 2.0 x25519-dalek + #[allow(clippy::expect_used)] + let static_secret = x25519_dalek::StaticSecret::from(local_secret.to_bytes()); + let local_public: x25519_dalek::PublicKey = (&static_secret).into(); + + let dh = static_secret.diffie_hellman(&remote_public); + + // TODO: change that to use our nym_crypto::hmac module instead + #[allow(clippy::expect_used)] + let mut mac = HmacSha256::new_from_slice(dh.as_bytes()) + .expect("x25519 shared secret is always 32 bytes long"); + + mac.update(local_public.as_bytes()); + mac.update(private_ip.to_string().as_bytes()); + mac.update(&nonce.to_le_bytes()); + + GatewayClient { + pub_key: PeerPublicKey::new(local_public), + private_ip, + mac: ClientMac(mac.finalize().into_bytes().to_vec()), + } + } + + // Reusable secret should be gateways Wireguard PK + // Client should perform this step when generating its payload, using its own WG PK + #[cfg(feature = "verify")] + pub fn verify(&self, gateway_key: &PrivateKey, nonce: u64) -> Result<(), Error> { + // convert from 1.0 x25519-dalek private key into 2.0 x25519-dalek + #[allow(clippy::expect_used)] + let static_secret = x25519_dalek::StaticSecret::from(gateway_key.to_bytes()); + + let dh = static_secret.diffie_hellman(&self.pub_key); + + // TODO: change that to use our nym_crypto::hmac module instead + #[allow(clippy::expect_used)] + let mut mac = HmacSha256::new_from_slice(dh.as_bytes()) + .expect("x25519 shared secret is always 32 bytes long"); + + mac.update(self.pub_key.as_bytes()); + mac.update(self.private_ip.to_string().as_bytes()); + mac.update(&nonce.to_le_bytes()); + + mac.verify_slice(&self.mac) + .map_err(|source| Error::FailedClientMacVerification { + client: self.pub_key.to_string(), + source, + }) + } + + pub fn pub_key(&self) -> PeerPublicKey { + self.pub_key + } +} + +// TODO: change the inner type into generic array of size HmacSha256::OutputSize +// TODO2: rely on our internal crypto/hmac +#[derive(Debug, Clone)] +pub struct ClientMac(Vec); + +impl fmt::Display for ClientMac { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", general_purpose::STANDARD.encode(&self.0)) + } +} + +impl ClientMac { + #[allow(dead_code)] + pub fn new(mac: Vec) -> Self { + ClientMac(mac) + } +} + +impl Deref for ClientMac { + type Target = Vec; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl FromStr for ClientMac { + type Err = Error; + + fn from_str(s: &str) -> Result { + let mac_bytes: Vec = + general_purpose::STANDARD + .decode(s) + .map_err(|source| Error::MalformedClientMac { + mac: s.to_string(), + source, + })?; + + Ok(ClientMac(mac_bytes)) + } +} + +impl Serialize for ClientMac { + fn serialize(&self, serializer: S) -> Result { + let encoded_key = general_purpose::STANDARD.encode(self.0.clone()); + serializer.serialize_str(&encoded_key) + } +} + +impl<'de> Deserialize<'de> for ClientMac { + fn deserialize>(deserializer: D) -> Result { + let encoded_key = String::deserialize(deserializer)?; + ClientMac::from_str(&encoded_key).map_err(serde::de::Error::custom) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use nym_crypto::asymmetric::encryption; + + #[test] + #[cfg(feature = "verify")] + fn client_request_roundtrip() { + let mut rng = rand::thread_rng(); + + let gateway_key_pair = encryption::KeyPair::new(&mut rng); + let client_key_pair = encryption::KeyPair::new(&mut rng); + + let nonce = 1234567890; + + let client = GatewayClient::new( + client_key_pair.private_key(), + x25519_dalek::PublicKey::from(gateway_key_pair.public_key().to_bytes()), + "10.0.0.42".parse().unwrap(), + nonce, + ); + assert!(client.verify(gateway_key_pair.private_key(), nonce).is_ok()) + } +} diff --git a/common/authenticator-requests/src/v3/request.rs b/common/authenticator-requests/src/v3/request.rs new file mode 100644 index 0000000000..61e0914c1d --- /dev/null +++ b/common/authenticator-requests/src/v3/request.rs @@ -0,0 +1,120 @@ +// Copyright 2024 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use super::{ + registration::{FinalMessage, InitMessage}, + topup::TopUpMessage, +}; +use nym_service_provider_requests_common::{Protocol, ServiceProviderType}; +use nym_sphinx::addressing::Recipient; +use nym_wireguard_types::PeerPublicKey; +use serde::{Deserialize, Serialize}; + +use crate::make_bincode_serializer; + +use super::VERSION; + +fn generate_random() -> u64 { + use rand::RngCore; + let mut rng = rand::rngs::OsRng; + rng.next_u64() +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct AuthenticatorRequest { + pub protocol: Protocol, + pub data: AuthenticatorRequestData, + pub reply_to: Recipient, + pub request_id: u64, +} + +impl AuthenticatorRequest { + pub fn from_reconstructed_message( + message: &nym_sphinx::receiver::ReconstructedMessage, + ) -> Result { + use bincode::Options; + make_bincode_serializer().deserialize(&message.message) + } + + pub fn new_initial_request(init_message: InitMessage, reply_to: Recipient) -> (Self, u64) { + let request_id = generate_random(); + ( + Self { + protocol: Protocol { + service_provider_type: ServiceProviderType::Authenticator, + version: VERSION, + }, + data: AuthenticatorRequestData::Initial(init_message), + reply_to, + request_id, + }, + request_id, + ) + } + + pub fn new_final_request(final_message: FinalMessage, reply_to: Recipient) -> (Self, u64) { + let request_id = generate_random(); + ( + Self { + protocol: Protocol { + service_provider_type: ServiceProviderType::Authenticator, + version: VERSION, + }, + data: AuthenticatorRequestData::Final(Box::new(final_message)), + reply_to, + request_id, + }, + request_id, + ) + } + + pub fn new_query_request(peer_public_key: PeerPublicKey, reply_to: Recipient) -> (Self, u64) { + let request_id = generate_random(); + ( + Self { + protocol: Protocol { + service_provider_type: ServiceProviderType::Authenticator, + version: VERSION, + }, + data: AuthenticatorRequestData::QueryBandwidth(peer_public_key), + reply_to, + request_id, + }, + request_id, + ) + } + + pub fn to_bytes(&self) -> Result, bincode::Error> { + use bincode::Options; + make_bincode_serializer().serialize(self) + } +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub enum AuthenticatorRequestData { + Initial(InitMessage), + Final(Box), + QueryBandwidth(PeerPublicKey), + TopUpBandwidth(Box), +} + +#[cfg(test)] +mod tests { + use super::*; + use std::str::FromStr; + + #[test] + fn check_first_bytes_protocol() { + let version = 2; + let data = AuthenticatorRequest { + protocol: Protocol { version, service_provider_type: ServiceProviderType::Authenticator }, + data: AuthenticatorRequestData::Initial(InitMessage::new( + PeerPublicKey::from_str("yvNUDpT5l7W/xDhiu6HkqTHDQwbs/B3J5UrLmORl1EQ=").unwrap(), + )), + reply_to: Recipient::try_from_base58_string("D1rrpsysCGCYXy9saP8y3kmNpGtJZUXN9SvFoUcqAsM9.9Ssso1ea5NfkbMASdiseDSjTN1fSWda5SgEVjdSN4CvV@GJqd3ZxpXWSNxTfx7B1pPtswpetH4LnJdFeLeuY5KUuN").unwrap(), + request_id: 1, + }; + let bytes = *data.to_bytes().unwrap().first_chunk::<2>().unwrap(); + assert_eq!(bytes, [version, ServiceProviderType::Authenticator as u8]); + } +} diff --git a/common/authenticator-requests/src/v3/response.rs b/common/authenticator-requests/src/v3/response.rs new file mode 100644 index 0000000000..370fc64671 --- /dev/null +++ b/common/authenticator-requests/src/v3/response.rs @@ -0,0 +1,157 @@ +// Copyright 2024 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use super::registration::{RegistrationData, RegistredData, RemainingBandwidthData}; +use nym_service_provider_requests_common::{Protocol, ServiceProviderType}; +use nym_sphinx::addressing::Recipient; +use serde::{Deserialize, Serialize}; + +use crate::make_bincode_serializer; + +use super::VERSION; + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct AuthenticatorResponse { + pub protocol: Protocol, + pub data: AuthenticatorResponseData, + pub reply_to: Recipient, +} + +impl AuthenticatorResponse { + pub fn new_pending_registration_success( + registration_data: RegistrationData, + request_id: u64, + reply_to: Recipient, + ) -> Self { + Self { + protocol: Protocol { + service_provider_type: ServiceProviderType::Authenticator, + version: VERSION, + }, + data: AuthenticatorResponseData::PendingRegistration(PendingRegistrationResponse { + reply: registration_data, + reply_to, + request_id, + }), + reply_to, + } + } + + pub fn new_registered( + registred_data: RegistredData, + reply_to: Recipient, + request_id: u64, + ) -> Self { + Self { + protocol: Protocol { + service_provider_type: ServiceProviderType::Authenticator, + version: VERSION, + }, + data: AuthenticatorResponseData::Registered(RegisteredResponse { + reply: registred_data, + reply_to, + request_id, + }), + reply_to, + } + } + + pub fn new_remaining_bandwidth( + remaining_bandwidth_data: Option, + reply_to: Recipient, + request_id: u64, + ) -> Self { + Self { + protocol: Protocol { + service_provider_type: ServiceProviderType::Authenticator, + version: VERSION, + }, + data: AuthenticatorResponseData::RemainingBandwidth(RemainingBandwidthResponse { + reply: remaining_bandwidth_data, + reply_to, + request_id, + }), + reply_to, + } + } + + pub fn new_topup_bandwidth( + remaining_bandwidth_data: RemainingBandwidthData, + reply_to: Recipient, + request_id: u64, + ) -> Self { + Self { + protocol: Protocol { + service_provider_type: ServiceProviderType::Authenticator, + version: VERSION, + }, + data: AuthenticatorResponseData::TopUpBandwidth(TopUpBandwidthResponse { + reply: remaining_bandwidth_data, + reply_to, + request_id, + }), + reply_to, + } + } + + pub fn recipient(&self) -> Recipient { + self.reply_to + } + + pub fn to_bytes(&self) -> Result, bincode::Error> { + use bincode::Options; + make_bincode_serializer().serialize(self) + } + + pub fn from_reconstructed_message( + message: &nym_sphinx::receiver::ReconstructedMessage, + ) -> Result { + use bincode::Options; + make_bincode_serializer().deserialize(&message.message) + } + + pub fn id(&self) -> Option { + match &self.data { + AuthenticatorResponseData::PendingRegistration(response) => Some(response.request_id), + AuthenticatorResponseData::Registered(response) => Some(response.request_id), + AuthenticatorResponseData::RemainingBandwidth(response) => Some(response.request_id), + AuthenticatorResponseData::TopUpBandwidth(response) => Some(response.request_id), + } + } +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub enum AuthenticatorResponseData { + PendingRegistration(PendingRegistrationResponse), + Registered(RegisteredResponse), + RemainingBandwidth(RemainingBandwidthResponse), + TopUpBandwidth(TopUpBandwidthResponse), +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct PendingRegistrationResponse { + pub request_id: u64, + pub reply_to: Recipient, + pub reply: RegistrationData, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct RegisteredResponse { + pub request_id: u64, + pub reply_to: Recipient, + pub reply: RegistredData, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct RemainingBandwidthResponse { + pub request_id: u64, + pub reply_to: Recipient, + pub reply: Option, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct TopUpBandwidthResponse { + pub request_id: u64, + pub reply_to: Recipient, + pub reply: RemainingBandwidthData, +} diff --git a/common/authenticator-requests/src/v3/topup.rs b/common/authenticator-requests/src/v3/topup.rs new file mode 100644 index 0000000000..31a61a0659 --- /dev/null +++ b/common/authenticator-requests/src/v3/topup.rs @@ -0,0 +1,15 @@ +// Copyright 2024 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use nym_credentials_interface::CredentialSpendingData; +use nym_wireguard_types::PeerPublicKey; +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct TopUpMessage { + /// Base64 encoded x25519 public key + pub pub_key: PeerPublicKey, + + /// Ecash credential + pub credential: CredentialSpendingData, +} diff --git a/common/commands/src/ecash/import_ticket_book.rs b/common/commands/src/ecash/import_ticket_book.rs index 2c5d597659..14ebd4b686 100644 --- a/common/commands/src/ecash/import_ticket_book.rs +++ b/common/commands/src/ecash/import_ticket_book.rs @@ -1,8 +1,6 @@ // Copyright 2024 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use crate::utils::CommonConfigsWrapper; -use anyhow::bail; use clap::ArgGroup; use clap::Parser; use nym_credential_storage::initialise_persistent_storage; @@ -31,7 +29,7 @@ impl FromStr for CredentialDataWrapper { pub struct Args { /// Config file of the client that is supposed to use the credential. #[clap(long)] - pub(crate) client_config: PathBuf, + pub(crate) credentials_store: PathBuf, /// Explicitly provide the encoded credential data (as base58) #[clap(long, group = "cred_data")] @@ -70,21 +68,7 @@ impl Args { } pub async fn execute(args: Args) -> anyhow::Result<()> { - let loaded = CommonConfigsWrapper::try_load(&args.client_config)?; - - if let Ok(id) = loaded.try_get_id() { - println!("loaded config file for client '{id}'"); - } - - let Ok(credentials_store) = loaded.try_get_credentials_store() else { - bail!("the loaded config does not have a credentials store information") - }; - - println!( - "using credentials store at '{}'", - credentials_store.display() - ); - let credentials_store = initialise_persistent_storage(credentials_store).await; + let credentials_store = initialise_persistent_storage(args.credentials_store.clone()).await; let version = args.version; let standalone = args.standalone; diff --git a/common/wireguard/src/peer_controller.rs b/common/wireguard/src/peer_controller.rs index 9bc49b883b..c6316a720e 100644 --- a/common/wireguard/src/peer_controller.rs +++ b/common/wireguard/src/peer_controller.rs @@ -8,7 +8,7 @@ use defguard_wireguard_rs::{ }; use futures::channel::oneshot; use nym_authenticator_requests::{ - v1::registration::BANDWIDTH_CAP_PER_DAY, v2::registration::RemainingBandwidthData, + latest::registration::RemainingBandwidthData, v1::registration::BANDWIDTH_CAP_PER_DAY, }; use nym_credential_verification::{ bandwidth_storage_manager::BandwidthStorageManager, BandwidthFlushingBehaviourConfig, diff --git a/service-providers/authenticator/src/error.rs b/service-providers/authenticator/src/error.rs index 32ee7fb439..bfc04c3fbc 100644 --- a/service-providers/authenticator/src/error.rs +++ b/service-providers/authenticator/src/error.rs @@ -79,6 +79,15 @@ pub enum AuthenticatorError { #[error("peers can't be interacted with anymore")] PeerInteractionStopped, + + #[error("operation is not supported")] + UnsupportedOperation, + + #[error("operation unavailable for older client")] + OldClient, + + #[error("storage should have the requested bandwidht entry")] + MissingClientBandwidthEntry, } pub type Result = std::result::Result; diff --git a/service-providers/authenticator/src/mixnet_listener.rs b/service-providers/authenticator/src/mixnet_listener.rs index 05c80cd496..a68b86ff7e 100644 --- a/service-providers/authenticator/src/mixnet_listener.rs +++ b/service-providers/authenticator/src/mixnet_listener.rs @@ -9,25 +9,24 @@ use std::{ use crate::{error::AuthenticatorError, peer_manager::PeerManager}; use futures::StreamExt; use log::warn; -use nym_authenticator_requests::v2::{ - self, - registration::{ - FinalMessage, GatewayClient, InitMessage, PendingRegistrations, PrivateIPs, - RegistrationData, RegistredData, - }, -}; use nym_authenticator_requests::{ - v1, - v2::{ + v1, v2, + v3::{ + self, + registration::{ + FinalMessage, GatewayClient, InitMessage, PendingRegistrations, PrivateIPs, + RegistrationData, RegistredData, RemainingBandwidthData, + }, request::{AuthenticatorRequest, AuthenticatorRequestData}, response::AuthenticatorResponse, }, + CURRENT_VERSION, }; use nym_credential_verification::{ bandwidth_storage_manager::BandwidthStorageManager, ecash::EcashManager, BandwidthFlushingBehaviourConfig, ClientBandwidth, CredentialVerifier, }; -use nym_credentials_interface::CredentialSpendingData; +use nym_credentials_interface::{CredentialSpendingData, TicketType}; use nym_crypto::asymmetric::x25519::KeyPair; use nym_gateway_requests::models::CredentialSpendingRequest; use nym_gateway_storage::Storage; @@ -329,6 +328,68 @@ impl MixnetListener { )) } + async fn on_topup_bandwidth_request( + &mut self, + peer_public_key: PeerPublicKey, + credential: CredentialSpendingData, + request_id: u64, + reply_to: Recipient, + ) -> AuthenticatorHandleResult { + let Some(ecash_verifier) = self.ecash_verifier.clone() else { + return Err(AuthenticatorError::UnsupportedOperation); + }; + let client_id = ecash_verifier + .storage() + .get_wireguard_peer(&peer_public_key.to_string()) + .await? + .ok_or(AuthenticatorError::MissingClientBandwidthEntry)? + .client_id + .ok_or(AuthenticatorError::OldClient)?; + let bandwidth = ecash_verifier + .storage() + .get_available_bandwidth(client_id) + .await? + .ok_or(AuthenticatorError::InternalError( + "bandwidth entry should have just been created".to_string(), + ))?; + + let t_type = credential.payment.t_type; + let client_bandwidth = ClientBandwidth::new(bandwidth.into()); + let mut verifier = CredentialVerifier::new( + CredentialSpendingRequest::new(credential), + ecash_verifier.clone(), + BandwidthStorageManager::new( + ecash_verifier.storage().clone(), + client_bandwidth, + client_id, + BandwidthFlushingBehaviourConfig::default(), + true, + ), + ); + verifier.verify().await?; + + let amount = TicketType::try_from_encoded(t_type) + .map_err(|e| { + AuthenticatorError::CredentialVerificationError( + nym_credential_verification::Error::UnknownTicketType(e), + ) + })? + .to_repr() + .bandwidth_value() as i64; + let available_bandwidth = ecash_verifier + .storage() + .increase_bandwidth(client_id, amount) + .await?; + + Ok(AuthenticatorResponse::new_topup_bandwidth( + RemainingBandwidthData { + available_bandwidth, + }, + reply_to, + request_id, + )) + } + async fn on_reconstructed_message( &mut self, reconstructed: ReconstructedMessage, @@ -362,6 +423,15 @@ impl MixnetListener { ) .await } + AuthenticatorRequestData::TopUpBandwidth(topup_message) => { + self.on_topup_bandwidth_request( + topup_message.pub_key, + topup_message.credential, + request.request_id, + request.reply_to, + ) + .await + } } } @@ -392,6 +462,7 @@ impl MixnetListener { } pub(crate) async fn run(mut self) -> Result<()> { + log::info!("Using authenticator version {}", CURRENT_VERSION); let mut task_client = self.task_handle.fork("main_loop"); while !task_client.is_shutdown() { @@ -440,6 +511,7 @@ fn deserialize_request(reconstructed: &ReconstructedMessage) -> Result v1::request::AuthenticatorRequest::from_reconstructed_message(reconstructed) .map_err(|err| AuthenticatorError::FailedToDeserializeTaggedPacket { source: err }) + .map(Into::::into) .map(Into::into), [2, request_type] => { if request_type == ServiceProviderType::Authenticator as u8 { @@ -452,6 +524,16 @@ fn deserialize_request(reconstructed: &ReconstructedMessage) -> Result { + if request_type == ServiceProviderType::Authenticator as u8 { + v3::request::AuthenticatorRequest::from_reconstructed_message(reconstructed) + .map_err(|err| AuthenticatorError::FailedToDeserializeTaggedPacket { + source: err, + }) + } else { + Err(AuthenticatorError::InvalidPacketType(request_type)) + } + } [version, _] => { log::info!("Received packet with invalid version: v{version}"); Err(AuthenticatorError::InvalidPacketVersion(version)) diff --git a/service-providers/authenticator/src/peer_manager.rs b/service-providers/authenticator/src/peer_manager.rs index fbbdf6a06f..9d70036c9c 100644 --- a/service-providers/authenticator/src/peer_manager.rs +++ b/service-providers/authenticator/src/peer_manager.rs @@ -4,7 +4,7 @@ use crate::error::*; use defguard_wireguard_rs::{host::Peer, key::Key, net::IpAddrMask}; use futures::channel::oneshot; -use nym_authenticator_requests::v2::registration::{GatewayClient, RemainingBandwidthData}; +use nym_authenticator_requests::latest::registration::{GatewayClient, RemainingBandwidthData}; use nym_wireguard::{ peer_controller::{ AddPeerControlResponse, PeerControlRequest, QueryBandwidthControlResponse,