brought in wg routes into nym-node router

This commit is contained in:
Jędrzej Stuczyński
2023-10-11 15:26:08 +01:00
parent 89cddc62cc
commit 3260ba84fe
32 changed files with 1029 additions and 455 deletions
Generated
+128 -14
View File
@@ -2717,7 +2717,7 @@ dependencies = [
"ed25519 2.2.2",
"rand_core 0.6.4",
"serde",
"sha2 0.10.7",
"sha2 0.10.8",
"zeroize",
]
@@ -3871,6 +3871,9 @@ dependencies = [
"serde",
"serde_json",
"thiserror",
"tracing",
"url",
"wasmtimer",
]
[[package]]
@@ -3884,6 +3887,12 @@ dependencies = [
"pin-project-lite 0.2.12",
]
[[package]]
name = "http-range-header"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "add0ab9360ddbd88cfeb3bd9574a1d85cfdfa14db10b3e21d3700dbc4328758f"
[[package]]
name = "httparse"
version = "1.8.0"
@@ -4135,6 +4144,7 @@ checksum = "d5477fe2230a79769d8dc68e0eabf5437907c0457a5614a9e8dddb67f65eb65d"
dependencies = [
"equivalent",
"hashbrown 0.14.0",
"serde",
]
[[package]]
@@ -4270,7 +4280,7 @@ dependencies = [
"socket2 0.4.9",
"widestring",
"winapi",
"winreg",
"winreg 0.10.1",
]
[[package]]
@@ -5995,6 +6005,7 @@ dependencies = [
"nym-mixnet-contract-common",
"nym-multisig-contract-common",
"nym-name-service-common",
"nym-node-requests",
"nym-node-tester-utils",
"nym-pemstore",
"nym-service-provider-directory-common",
@@ -6005,7 +6016,6 @@ dependencies = [
"nym-vesting-contract-common",
"okapi",
"pin-project",
"pretty_env_logger",
"rand 0.7.3",
"rand 0.8.5",
"reqwest",
@@ -6039,6 +6049,7 @@ dependencies = [
"getset",
"nym-coconut-interface",
"nym-mixnet-contract-common",
"nym-node-requests",
"schemars",
"serde",
"ts-rs",
@@ -6079,6 +6090,7 @@ dependencies = [
"tracing-opentelemetry",
"tracing-subscriber",
"tracing-tree",
"utoipa",
"vergen",
]
@@ -6535,6 +6547,7 @@ dependencies = [
"nym-mixnode-common",
"nym-network-defaults",
"nym-network-requester",
"nym-node",
"nym-pemstore",
"nym-sphinx",
"nym-statistics-common",
@@ -6850,6 +6863,52 @@ dependencies = [
"tokio",
]
[[package]]
name = "nym-node"
version = "0.1.0"
dependencies = [
"anyhow",
"axum",
"base64 0.21.4",
"bytes",
"colored",
"fastrand 2.0.0",
"hmac 0.12.1",
"hyper",
"mime",
"nym-crypto",
"nym-node-requests",
"nym-task",
"rand 0.7.3",
"serde",
"serde_json",
"serde_yaml",
"sha2 0.10.8",
"thiserror",
"tokio",
"tower",
"tower-http",
"tracing",
"utoipa",
"utoipa-swagger-ui",
"x25519-dalek 2.0.0",
]
[[package]]
name = "nym-node-requests"
version = "0.1.0"
dependencies = [
"async-trait",
"http-api-client",
"nym-bin-common",
"nym-crypto",
"schemars",
"serde",
"serde_json",
"thiserror",
"utoipa",
]
[[package]]
name = "nym-node-tester-utils"
version = "0.1.0"
@@ -8815,9 +8874,9 @@ checksum = "e5ea92a5b6195c6ef2a0295ea818b312502c6fc94dde986c5553242e18fd4ce2"
[[package]]
name = "reqwest"
version = "0.11.18"
version = "0.11.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cde824a14b7c14f85caff81225f411faacc04a2013f41670f41443742b1c1c55"
checksum = "046cd98826c46c2ac8ddecae268eb5c2e58628688a5fc7a2643704a73faba95b"
dependencies = [
"base64 0.21.4",
"bytes",
@@ -8840,6 +8899,7 @@ dependencies = [
"serde",
"serde_json",
"serde_urlencoded",
"system-configuration",
"tokio",
"tokio-native-tls",
"tokio-socks",
@@ -8848,7 +8908,7 @@ dependencies = [
"wasm-bindgen",
"wasm-bindgen-futures",
"web-sys",
"winreg",
"winreg 0.50.0",
]
[[package]]
@@ -9654,6 +9714,19 @@ dependencies = [
"serde",
]
[[package]]
name = "serde_yaml"
version = "0.9.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a49e178e4452f45cb61d0cd8cebc1b0fafd3e41929e996cef79aa3aca91f574"
dependencies = [
"indexmap 2.0.0",
"itoa",
"ryu",
"serde",
"unsafe-libyaml",
]
[[package]]
name = "sha-1"
version = "0.9.8"
@@ -10900,6 +10973,31 @@ dependencies = [
"tracing",
]
[[package]]
name = "tower-http"
version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "61c5bb1d698276a2443e5ecfabc1008bf15a36c12e6a7176e7bf089ea9131140"
dependencies = [
"bitflags 2.4.0",
"bytes",
"futures-core",
"futures-util",
"http",
"http-body",
"http-range-header",
"httpdate",
"mime",
"mime_guess",
"percent-encoding",
"pin-project-lite 0.2.12",
"tokio",
"tokio-util",
"tower-layer",
"tower-service",
"tracing",
]
[[package]]
name = "tower-layer"
version = "0.3.2"
@@ -11331,6 +11429,12 @@ dependencies = [
"subtle 2.4.1",
]
[[package]]
name = "unsafe-libyaml"
version = "0.2.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f28467d3e1d3c6586d8f25fa243f544f5800fec42d97032474e17222c2b75cfa"
[[package]]
name = "unsigned-varint"
version = "0.7.1"
@@ -11410,11 +11514,11 @@ checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a"
[[package]]
name = "utoipa"
version = "3.3.0"
version = "3.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68ae74ef183fae36d650f063ae7bde1cacbe1cd7e72b617cbe1e985551878b98"
checksum = "d82b1bc5417102a73e8464c686eef947bdfb99fcdfc0a4f228e81afa9526470a"
dependencies = [
"indexmap 1.9.3",
"indexmap 2.0.0",
"serde",
"serde_json",
"utoipa-gen",
@@ -11422,11 +11526,10 @@ dependencies = [
[[package]]
name = "utoipa-gen"
version = "3.3.0"
version = "3.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7ea8ac818da7e746a63285594cce8a96f5e00ee31994e655bd827569cb8b137b"
checksum = "05d96dcd6fc96f3df9b3280ef480770af1b7c5d14bc55192baa9b067976d920c"
dependencies = [
"lazy_static",
"proc-macro-error",
"proc-macro2",
"quote",
@@ -11436,11 +11539,12 @@ dependencies = [
[[package]]
name = "utoipa-swagger-ui"
version = "3.1.3"
version = "3.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "062bba5a3568e126ac72049a63254f4cb1da2eb713db0c1ab2a4c76be191db8c"
checksum = "84614caa239fb25b2bb373a52859ffd94605ceb256eeb1d63436325cf81e3653"
dependencies = [
"actix-web",
"axum",
"mime_guess",
"regex",
"rust-embed",
@@ -12251,6 +12355,16 @@ dependencies = [
"winapi",
]
[[package]]
name = "winreg"
version = "0.50.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1"
dependencies = [
"cfg-if",
"windows-sys 0.48.0",
]
[[package]]
name = "with_builtin_macros"
version = "0.0.3"
+24 -17
View File
@@ -3,15 +3,18 @@
use crate::config::Config;
use crate::error::GatewayError;
use crate::node::helpers::{load_identity_keys, load_public_key};
use crate::node::helpers::load_public_key;
use nym_bin_common::bin_info_owned;
use nym_crypto::asymmetric::{encryption, identity};
use nym_node::error::NymNodeError;
use nym_node::http::api::api_requests;
use nym_node::http::api::api_requests::SignedHostInformation;
use nym_node::http::router::WireguardAppState;
use nym_node::wireguard::types::ClientRegistry;
use nym_sphinx::addressing::clients::Recipient;
use nym_task::TaskClient;
use zeroize::Zeroize;
use std::sync::Arc;
use tokio::sync::RwLock;
fn load_gateway_details(
config: &Config,
@@ -29,27 +32,21 @@ fn load_gateway_details(
fn load_host_details(
config: &Config,
sphinx_key: &encryption::PublicKey,
identity_keypair: &identity::KeyPair,
) -> Result<api_requests::v1::node::models::SignedHostInformation, GatewayError> {
let mut identity_keypair = load_identity_keys(config)?;
let sphinx_public_key: encryption::PublicKey = load_public_key(
&config.storage_paths.keys.public_sphinx_key_file,
"gateway sphinx",
)?;
let host_info = api_requests::v1::node::models::HostInformation {
// TODO: this should be extracted differently, i.e. it's the issue of the public/private address
ip_address: vec![config.gateway.listening_address.to_string()],
hostname: None,
keys: api_requests::v1::node::models::HostKeys {
ed25519: identity_keypair.public_key().to_base58_string(),
x25519: sphinx_public_key.to_base58_string(),
x25519: sphinx_key.to_base58_string(),
},
};
let signed_info = SignedHostInformation::new(host_info, identity_keypair.private_key())
.map_err(NymNodeError::from)?;
identity_keypair.zeroize();
Ok(signed_info)
}
@@ -97,6 +94,11 @@ fn load_network_requester_details(
pub(crate) fn start_http_api(
gateway_config: &Config,
network_requester_config: Option<&nym_network_requester::Config>,
client_registry: Arc<RwLock<ClientRegistry>>,
identity_keypair: &identity::KeyPair,
// TODO: this should be a wg specific key and not re-used sphinx
sphinx_keypair: Arc<encryption::KeyPair>,
task_client: TaskClient,
) -> Result<(), GatewayError> {
// is it suboptimal to load all the keys, etc for the second time after they've already been
@@ -105,18 +107,23 @@ pub(crate) fn start_http_api(
// but in the grand scheme of things performance penalty is negligible since it's only happening on startup
// and makes the code a bit nicer to manage. on top of it, all of it will refactored anyway at some point
// (famous last words, eh? - 22.09.23)
// TODO: load private key, set zeroizing wrapper and sign whatever responses we need to sign
let mut config =
nym_node::http::Config::new(bin_info_owned!(), load_host_details(gateway_config)?)
.with_gateway(load_gateway_details(gateway_config)?);
let mut config = nym_node::http::Config::new(
bin_info_owned!(),
load_host_details(
gateway_config,
sphinx_keypair.public_key(),
identity_keypair,
)?,
)
.with_gateway(load_gateway_details(gateway_config)?);
if let Some(nr_config) = network_requester_config {
config = config
.with_network_requester(load_network_requester_details(gateway_config, nr_config)?)
}
let router = nym_node::http::NymNodeRouter::new(config);
let wg_state = WireguardAppState::new(sphinx_keypair, client_registry, Default::default());
let router = nym_node::http::NymNodeRouter::new(config, Some(wg_state));
let server = router
.build_server(&gateway_config.http.bind_address)?
@@ -1,181 +1,182 @@
use std::{
collections::HashMap,
fmt,
hash::{Hash, Hasher},
net::SocketAddr,
ops::Deref,
str::FromStr,
};
use base64::{engine::general_purpose, Engine};
use hmac::{Hmac, Mac};
use nym_crypto::asymmetric::encryption::PrivateKey;
use serde::{Deserialize, Serialize};
use sha2::Sha256;
use x25519_dalek::StaticSecret;
use crate::error::GatewayError;
#[derive(Debug, Clone, Deserialize, Serialize)]
pub(crate) enum ClientMessage {
Init(InitMessage),
Final(Client),
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub(crate) struct InitMessage {
pub_key: ClientPublicKey,
}
impl InitMessage {
pub fn pub_key(&self) -> &ClientPublicKey {
&self.pub_key
}
#[allow(dead_code)]
pub fn new(pub_key: ClientPublicKey) -> Self {
InitMessage { pub_key }
}
}
// Client that wants to register sends its PublicKey and SocketAddr bytes mac digest encrypted with a DH shared secret.
// Gateway can then verify pub_key payload using the sme process
#[derive(Debug, Clone, Deserialize, Serialize)]
pub(crate) struct Client {
// base64 encoded public key, using x25519-dalek for impl
pub(crate) pub_key: ClientPublicKey,
pub(crate) socket: SocketAddr,
pub(crate) mac: ClientMac,
}
pub type HmacSha256 = Hmac<Sha256>;
impl Client {
// Reusable secret should be gateways Wireguard PK
// Client should perform this step when generating its payload, using its own WG PK
pub fn verify(&self, gateway_key: &PrivateKey, nonce: u64) -> Result<(), GatewayError> {
#[allow(clippy::expect_used)]
let static_secret =
StaticSecret::try_from(gateway_key.to_bytes()).expect("This is infalliable");
let dh = static_secret.diffie_hellman(&self.pub_key);
let mut mac = HmacSha256::new_from_slice(dh.as_bytes())?;
mac.update(self.pub_key.as_bytes());
mac.update(self.socket.ip().to_string().as_bytes());
mac.update(self.socket.port().to_string().as_bytes());
mac.update(&nonce.to_le_bytes());
Ok(mac.verify_slice(&self.mac)?)
}
pub fn pub_key(&self) -> &ClientPublicKey {
&self.pub_key
}
pub fn socket(&self) -> SocketAddr {
self.socket
}
}
// This should go into nym-wireguard crate
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct ClientPublicKey(x25519_dalek::PublicKey);
#[derive(Debug, Clone)]
pub(crate) struct ClientMac(Vec<u8>);
impl ClientMac {
#[allow(dead_code)]
pub fn new(mac: Vec<u8>) -> Self {
ClientMac(mac)
}
}
impl Deref for ClientMac {
type Target = Vec<u8>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl fmt::Display for ClientPublicKey {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", general_purpose::STANDARD.encode(self.0.as_bytes()))
}
}
impl Hash for ClientPublicKey {
fn hash<H: Hasher>(&self, state: &mut H) {
self.0.as_bytes().hash(state)
}
}
impl FromStr for ClientMac {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let mac_bytes: Vec<u8> = general_purpose::STANDARD
.decode(s)
.map_err(|_| "Could not base64 decode public key mac representation".to_string())?;
Ok(ClientMac(mac_bytes))
}
}
impl Serialize for ClientMac {
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
let encoded_key = general_purpose::STANDARD.encode(self.0.clone());
serializer.serialize_str(&encoded_key)
}
}
impl<'de> Deserialize<'de> for ClientMac {
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
let encoded_key = String::deserialize(deserializer)?;
ClientMac::from_str(&encoded_key).map_err(serde::de::Error::custom)
}
}
impl ClientPublicKey {
#[allow(dead_code)]
pub fn new(key: x25519_dalek::PublicKey) -> Self {
ClientPublicKey(key)
}
pub fn as_bytes(&self) -> &[u8] {
self.0.as_bytes()
}
}
impl Deref for ClientPublicKey {
type Target = x25519_dalek::PublicKey;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl FromStr for ClientPublicKey {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let key_bytes: [u8; 32] = general_purpose::STANDARD
.decode(s)
.map_err(|_| "Could not base64 decode public key representation".to_string())?
.try_into()
.map_err(|_| "Invalid key length".to_string())?;
Ok(ClientPublicKey(x25519_dalek::PublicKey::from(key_bytes)))
}
}
impl Serialize for ClientPublicKey {
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
let encoded_key = general_purpose::STANDARD.encode(self.0.as_bytes());
serializer.serialize_str(&encoded_key)
}
}
impl<'de> Deserialize<'de> for ClientPublicKey {
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
let encoded_key = String::deserialize(deserializer)?;
Ok(ClientPublicKey::from_str(&encoded_key).map_err(serde::de::Error::custom))?
}
}
pub(crate) type ClientRegistry = HashMap<SocketAddr, Client>;
// use std::{
// collections::HashMap,
// fmt,
// hash::{Hash, Hasher},
// net::SocketAddr,
// ops::Deref,
// str::FromStr,
// };
//
// use base64::{engine::general_purpose, Engine};
// use hmac::{Hmac, Mac};
// use nym_crypto::asymmetric::encryption::PrivateKey;
// use serde::{Deserialize, Serialize};
// use sha2::Sha256;
// use x25519_dalek::StaticSecret;
//
// use crate::error::GatewayError;
//
// #[derive(Debug, Clone, Deserialize, Serialize)]
// pub(crate) enum ClientMessage {
// Init(InitMessage),
// Final(Client),
// }
//
// #[derive(Debug, Clone, Deserialize, Serialize)]
// pub(crate) struct InitMessage {
// pub_key: ClientPublicKey,
// }
//
// impl InitMessage {
// pub fn pub_key(&self) -> &ClientPublicKey {
// &self.pub_key
// }
// #[allow(dead_code)]
// pub fn new(pub_key: ClientPublicKey) -> Self {
// InitMessage { pub_key }
// }
// }
//
// // Client that wants to register sends its PublicKey and SocketAddr bytes mac digest encrypted with a DH shared secret.
// // Gateway can then verify pub_key payload using the sme process
// #[derive(Debug, Clone, Deserialize, Serialize)]
// pub(crate) struct Client {
// // base64 encoded public key, using x25519-dalek for impl
// pub(crate) pub_key: ClientPublicKey,
// pub(crate) socket: SocketAddr,
// pub(crate) mac: ClientMac,
// }
//
// pub type HmacSha256 = Hmac<Sha256>;
//
// impl Client {
// // Reusable secret should be gateways Wireguard PK
// // Client should perform this step when generating its payload, using its own WG PK
// pub fn verify(&self, gateway_key: &PrivateKey, nonce: u64) -> Result<(), GatewayError> {
// #[allow(clippy::expect_used)]
// let static_secret =
// StaticSecret::try_from(gateway_key.to_bytes()).expect("This is infalliable");
// let dh = static_secret.diffie_hellman(&self.pub_key);
// let mut mac = HmacSha256::new_from_slice(dh.as_bytes())?;
// mac.update(self.pub_key.as_bytes());
// mac.update(self.socket.ip().to_string().as_bytes());
// mac.update(self.socket.port().to_string().as_bytes());
// mac.update(&nonce.to_le_bytes());
// Ok(mac.verify_slice(&self.mac)?)
// }
//
// pub fn pub_key(&self) -> &ClientPublicKey {
// &self.pub_key
// }
//
// pub fn socket(&self) -> SocketAddr {
// self.socket
// }
// }
//
// // This should go into nym-wireguard crate
// #[derive(Debug, Clone, Eq, PartialEq)]
// pub struct ClientPublicKey(x25519_dalek::PublicKey);
//
// #[derive(Debug, Clone)]
// pub(crate) struct ClientMac(Vec<u8>);
//
// impl ClientMac {
// #[allow(dead_code)]
// pub fn new(mac: Vec<u8>) -> Self {
// ClientMac(mac)
// }
// }
//
// impl Deref for ClientMac {
// type Target = Vec<u8>;
//
// fn deref(&self) -> &Self::Target {
// &self.0
// }
// }
//
// impl fmt::Display for ClientPublicKey {
// fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
// write!(f, "{}", general_purpose::STANDARD.encode(self.0.as_bytes()))
// }
// }
//
// impl Hash for ClientPublicKey {
// fn hash<H: Hasher>(&self, state: &mut H) {
// self.0.as_bytes().hash(state)
// }
// }
//
// impl FromStr for ClientMac {
// type Err = String;
//
// fn from_str(s: &str) -> Result<Self, Self::Err> {
// let mac_bytes: Vec<u8> = general_purpose::STANDARD
// .decode(s)
// .map_err(|_| "Could not base64 decode public key mac representation".to_string())?;
// Ok(ClientMac(mac_bytes))
// }
// }
//
// impl Serialize for ClientMac {
// fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
// let encoded_key = general_purpose::STANDARD.encode(self.0.clone());
// serializer.serialize_str(&encoded_key)
// }
// }
//
// impl<'de> Deserialize<'de> for ClientMac {
// fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
// let encoded_key = String::deserialize(deserializer)?;
// ClientMac::from_str(&encoded_key).map_err(serde::de::Error::custom)
// }
// }
//
// impl ClientPublicKey {
// #[allow(dead_code)]
// pub fn new(key: x25519_dalek::PublicKey) -> Self {
// ClientPublicKey(key)
// }
//
// pub fn as_bytes(&self) -> &[u8] {
// self.0.as_bytes()
// }
// }
//
// impl Deref for ClientPublicKey {
// type Target = x25519_dalek::PublicKey;
//
// fn deref(&self) -> &Self::Target {
// &self.0
// }
// }
//
// impl FromStr for ClientPublicKey {
// type Err = String;
//
// fn from_str(s: &str) -> Result<Self, Self::Err> {
// let key_bytes: [u8; 32] = general_purpose::STANDARD
// .decode(s)
// .map_err(|_| "Could not base64 decode public key representation".to_string())?
// .try_into()
// .map_err(|_| "Invalid key length".to_string())?;
// Ok(ClientPublicKey(x25519_dalek::PublicKey::from(key_bytes)))
// }
// }
//
// impl Serialize for ClientPublicKey {
// fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
// let encoded_key = general_purpose::STANDARD.encode(self.0.as_bytes());
// serializer.serialize_str(&encoded_key)
// }
// }
//
// impl<'de> Deserialize<'de> for ClientPublicKey {
// fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
// let encoded_key = String::deserialize(deserializer)?;
// Ok(ClientPublicKey::from_str(&encoded_key).map_err(serde::de::Error::custom))?
// }
// }
//
// pub(crate) type ClientRegistry = HashMap<SocketAddr, Client>;
-1
View File
@@ -1 +0,0 @@
pub(crate) mod v1;
@@ -1,105 +0,0 @@
use axum::extract::Path;
use std::sync::Arc;
use axum::http::StatusCode;
use axum::{extract::State, Json};
use std::str::FromStr;
// use axum_macros::debug_handler;
use crate::node::client_handling::client_registration::{
Client, ClientMessage, ClientPublicKey, InitMessage,
};
use crate::node::http::ApiState;
async fn process_final_message(client: Client, state: Arc<ApiState>) -> StatusCode {
let preshared_nonce = {
let in_progress_ro = state.registration_in_progress.read().await;
if let Some(nonce) = in_progress_ro.get(client.pub_key()) {
*nonce
} else {
return StatusCode::BAD_REQUEST;
}
};
if client
.verify(state.sphinx_key_pair.private_key(), preshared_nonce)
.is_ok()
{
{
let mut in_progress_rw = state.registration_in_progress.write().await;
in_progress_rw.remove(client.pub_key());
}
{
let mut registry_rw = state.client_registry.write().await;
registry_rw.insert(client.socket(), client);
}
return StatusCode::OK;
}
StatusCode::BAD_REQUEST
}
async fn process_init_message(init_message: InitMessage, state: Arc<ApiState>) -> u64 {
let nonce: u64 = fastrand::u64(..);
let mut registry_rw = state.registration_in_progress.write().await;
registry_rw.insert(init_message.pub_key().clone(), nonce);
nonce
}
// #[debug_handler]
pub(crate) async fn register_client(
State(state): State<Arc<ApiState>>,
Json(payload): Json<ClientMessage>,
) -> (StatusCode, Json<Option<u64>>) {
match payload {
ClientMessage::Init(i) => (
StatusCode::OK,
Json(Some(process_init_message(i, Arc::clone(&state)).await)),
),
ClientMessage::Final(client) => (
process_final_message(client, Arc::clone(&state)).await,
Json(None),
),
}
}
pub(crate) async fn get_all_clients(
State(state): State<Arc<ApiState>>,
) -> (StatusCode, Json<Vec<ClientPublicKey>>) {
let registry_ro = state.client_registry.read().await;
(
StatusCode::OK,
Json(
registry_ro
.values()
.map(|c| c.pub_key().clone())
.collect::<Vec<ClientPublicKey>>(),
),
)
}
pub(crate) async fn get_client(
Path(pub_key): Path<String>,
State(state): State<Arc<ApiState>>,
) -> (StatusCode, Json<Vec<Client>>) {
let pub_key = match ClientPublicKey::from_str(&pub_key) {
Ok(pub_key) => pub_key,
Err(_) => return (StatusCode::BAD_REQUEST, Json(vec![])),
};
let registry_ro = state.client_registry.read().await;
let clients = registry_ro
.iter()
.filter_map(|(_, c)| {
if c.pub_key() == &pub_key {
Some(c.clone())
} else {
None
}
})
.collect::<Vec<Client>>();
if clients.is_empty() {
return (StatusCode::NOT_FOUND, Json(clients));
}
(StatusCode::OK, Json(clients))
}
-1
View File
@@ -1 +0,0 @@
pub(crate) mod client_registry;
+4 -9
View File
@@ -1,7 +1,6 @@
// Copyright 2020-2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use self::client_handling::client_registration::ClientRegistry;
use self::storage::PersistentStorage;
use crate::commands::helpers::{override_network_requester_config, OverrideNetworkRequesterConfig};
use crate::config::Config;
@@ -14,7 +13,6 @@ use crate::node::client_handling::embedded_network_requester::{
use crate::node::client_handling::websocket;
use crate::node::client_handling::websocket::connection_handler::coconut::CoconutVerifier;
use crate::node::helpers::{initialise_main_storage, load_network_requester_config};
use crate::node::http::start_http_api;
use crate::node::mixnet_handling::receiver::connection_handler::ConnectionHandler;
use crate::node::statistics::collector::GatewayStatisticsCollector;
use crate::node::storage::Storage;
@@ -25,6 +23,7 @@ use nym_crypto::asymmetric::{encryption, identity};
use nym_mixnet_client::forwarder::{MixForwardingSender, PacketForwarder};
use nym_network_defaults::NymNetworkDetails;
use nym_network_requester::{LocalGateway, NRServiceProviderBuilder};
use nym_node::wireguard::types::ClientRegistry;
use nym_statistics_common::collector::StatisticsSender;
use nym_task::{TaskClient, TaskManager};
use nym_validator_client::{nyxd, DirectSigningHttpRpcNyxdClient};
@@ -39,7 +38,6 @@ use tokio::sync::RwLock;
pub(crate) mod client_handling;
pub(crate) mod helpers;
pub(crate) mod http;
pub(crate) mod mixnet_handling;
pub(crate) mod statistics;
pub(crate) mod storage;
@@ -337,6 +335,9 @@ impl<St> Gateway<St> {
start_http_api(
&self.config,
self.network_requester_opts.as_ref().map(|o| &o.config),
self.client_registry.clone(),
self.identity_keypair.as_ref(),
self.sphinx_keypair.clone(),
shutdown.subscribe().named("http-api"),
)?;
@@ -391,12 +392,6 @@ impl<St> Gateway<St> {
bail!("{err}")
}
// This should likely be wireguard feature gated, but its easier to test if it hangs in here
tokio::spawn(start_http_api(
Arc::clone(&self.client_registry),
Arc::clone(&self.sphinx_keypair),
));
info!("Finished nym gateway startup procedure - it should now be able to receive mix and client traffic!");
if let Err(err) = Self::wait_for_interrupt(shutdown).await {
+15 -2
View File
@@ -19,7 +19,15 @@ serde_yaml = "0.9.25"
serde_json = { workspace = true }
thiserror = { workspace = true }
tracing = { workspace = true }
tokio = { workspace = true, features = ["macros"] }
tokio = { workspace = true, features = ["macros", "sync"] }
# Wireguard:
base64 = "0.21.4"
sha2 = "0.10.8"
hmac = "0.12.1"
fastrand = "2"
x25519-dalek = { version = "2.0.0", features = ["static_secrets"] }
# HTTP API:
axum = { workspace = true }
@@ -41,6 +49,11 @@ utoipa-swagger-ui = { workspace = true, features = ["axum"] }
#utoipa-redoc = { version = "0.1.0", features = ["axum"] }
#utoipa-rapidoc = { version = "0.1.0", features = ["axum"] }
nym-crypto = { path = "../common/crypto", features = ["asymmetric" ]}
nym-node-requests = { path = "nym-node-requests", default-features = false, features = ["openapi"]}
nym-task = { path = "../common/task" }
[dev-dependencies]
rand = "0.7.3"
nym-crypto = { path = "../common/crypto", features = ["rand" ]}
@@ -0,0 +1,4 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
pub mod wireguard;
@@ -0,0 +1,4 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
pub mod models;
@@ -0,0 +1,36 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, Debug, Clone, JsonSchema)]
#[serde(tag = "type", rename_all = "camelCase")]
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
pub enum ClientMessage {
Initial(InitMessage),
Final(Client),
}
#[derive(Serialize, Deserialize, Debug, Clone, JsonSchema)]
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
pub struct InitMessage {
/// Base64 encoded x25519 public key
pub pub_key: String,
}
/// Client that wants to register sends its PublicKey and SocketAddr 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, JsonSchema)]
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
pub struct Client {
/// Base64 encoded x25519 public key
pub pub_key: String,
/// Client's socket address
#[cfg_attr(feature = "openapi", schema(example = "1.2.3.4:51820"))]
pub socket: String,
/// Sha256 hmac on the data (alongside the prior nonce)
pub mac: String,
}
@@ -2,3 +2,4 @@
// SPDX-License-Identifier: Apache-2.0
pub mod models;
pub mod client_interefaces;
+10
View File
@@ -63,6 +63,16 @@ pub mod routes {
client_interfaces_absolute(),
WEBSOCKETS
);
pub mod wireguard {
use super::*;
pub const CLIENT: &str = "/client";
pub const CLIENTS: &str = "/clients";
absolute_route!(client_absolute, wireguard_absolute(), CLIENT);
absolute_route!(clients_absolute, wireguard_absolute(), CLIENTS);
}
}
}
+7
View File
@@ -1,6 +1,7 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::wireguard::error::WireguardError;
use std::net::SocketAddr;
use thiserror::Error;
@@ -18,6 +19,12 @@ pub enum NymNodeError {
source: nym_node_requests::error::Error,
},
#[error(transparent)]
WireguardError {
#[from]
source: WireguardError,
},
#[error("unimplemented")]
Unimplemented,
}
+1 -1
View File
@@ -31,7 +31,7 @@ pub async fn logger<B>(req: Request<B>, next: Next<B>) -> impl IntoResponse {
status.to_string().yellow()
};
info!("[{host}] {method} '{uri}': {print_status} / agent: {agent}");
info!(target: "incoming request", "[{host}] {method} '{uri}': {print_status} / agent: {agent}");
res
}
+9 -3
View File
@@ -1,7 +1,6 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::http::state::AppState;
use axum::http::{header, HeaderValue, StatusCode};
use axum::response::{IntoResponse, Response};
use axum::{Json, Router};
@@ -11,6 +10,7 @@ use utoipa::{IntoParams, ToSchema};
pub mod v1;
use crate::http::api::v1::gateway::client_interfaces::wireguard::WireguardAppState;
pub use nym_node_requests::api as api_requests;
use nym_node_requests::routes;
@@ -19,8 +19,14 @@ pub struct Config {
pub v1_config: v1::Config,
}
pub(super) fn routes(config: Config) -> Router<AppState> {
Router::new().nest(routes::api::V1, v1::routes(config.v1_config))
pub(super) fn routes<S: Send + Sync + 'static + Clone>(
config: Config,
initial_wg_state: WireguardAppState,
) -> Router<S> {
Router::new().nest(
routes::api::V1,
v1::routes(config.v1_config, initial_wg_state),
)
}
#[derive(Debug, Clone, ToSchema)]
@@ -1,8 +1,8 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::http::api::v1::gateway::client_interfaces::wireguard::WireguardAppState;
use crate::http::api::{FormattedResponse, OutputParams};
use crate::http::state::AppState;
use axum::extract::Query;
use axum::http::StatusCode;
use axum::routing::get;
@@ -10,7 +10,12 @@ use axum::Router;
use nym_node_requests::api::v1::gateway::models::{ClientInterfaces, WebSockets, Wireguard};
use nym_node_requests::routes::api::v1::gateway::client_interfaces;
pub(crate) fn routes(interfaces: Option<ClientInterfaces>) -> Router<AppState> {
pub(crate) mod wireguard;
pub(crate) fn routes<S: Send + Sync + 'static + Clone>(
interfaces: Option<ClientInterfaces>,
initial_wg_state: WireguardAppState,
) -> Router<S> {
Router::new()
.route(
"/",
@@ -26,11 +31,15 @@ pub(crate) fn routes(interfaces: Option<ClientInterfaces>) -> Router<AppState> {
move |query| mixnet_websockets(websockets, query)
}),
)
.nest(
client_interfaces::WIREGUARD,
wireguard::routes(initial_wg_state),
)
.route(
client_interfaces::WIREGUARD,
get({
let wireguard_info = interfaces.and_then(|i| i.wireguard);
move |query| wireguard(wireguard_info, query)
let wireguard_cfg_info = interfaces.and_then(|i| i.wireguard);
move |query| wireguard_info(wireguard_cfg_info, query)
}),
)
}
@@ -76,7 +85,7 @@ pub type ClientInterfacesResponse = FormattedResponse<ClientInterfaces>;
),
params(OutputParams)
)]
pub(crate) async fn wireguard(
pub(crate) async fn wireguard_info(
wireguard: Option<Wireguard>,
Query(output): Query<OutputParams>,
) -> Result<WireguardResponse, StatusCode> {
@@ -0,0 +1,126 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::http::api::v1::gateway::client_interfaces::wireguard::{
WireguardAppState, WireguardAppStateInner,
};
use crate::wireguard::types::{
Client, ClientMessage, ClientMessageRequest, ClientPublicKey, InitMessage,
};
use axum::extract::{Path, State};
use axum::http::StatusCode;
use axum::Json;
use std::str::FromStr;
use tracing::warn;
async fn process_final_message(client: Client, state: &WireguardAppStateInner) -> StatusCode {
let preshared_nonce = {
let in_progress_ro = state.registration_in_progress.read().await;
if let Some(nonce) = in_progress_ro.get(&client.pub_key()) {
*nonce
} else {
return StatusCode::BAD_REQUEST;
}
};
if client
.verify(state.dh_keypair.private_key(), preshared_nonce)
.is_ok()
{
{
let mut in_progress_rw = state.registration_in_progress.write().await;
in_progress_rw.remove(&client.pub_key());
}
{
let mut registry_rw = state.client_registry.write().await;
registry_rw.insert(client.socket(), client);
}
return StatusCode::OK;
}
StatusCode::BAD_REQUEST
}
async fn process_init_message(init_message: InitMessage, state: &WireguardAppStateInner) -> u64 {
let nonce: u64 = fastrand::u64(..);
let mut registry_rw = state.registration_in_progress.write().await;
registry_rw.insert(*init_message.pub_key(), nonce);
nonce
}
pub(crate) async fn register_client(
State(state): State<WireguardAppState>,
Json(payload): Json<ClientMessageRequest>,
) -> (StatusCode, Json<Option<u64>>) {
let Some(state) = state.inner() else {
return (StatusCode::NOT_IMPLEMENTED, Json(None));
};
match ClientMessage::try_from(payload) {
Ok(payload) => match payload {
ClientMessage::Init(i) => (
StatusCode::OK,
Json(Some(process_init_message(i, state).await)),
),
ClientMessage::Final(client) => {
(process_final_message(client, state).await, Json(None))
}
},
Err(err) => {
warn!("failed to deserialize received request: {err}");
(StatusCode::BAD_REQUEST, Json(None))
}
}
}
// pub type RegisterClientResponse = FormattedResponse<()>;
pub(crate) async fn get_all_clients(
State(state): State<WireguardAppState>,
) -> (StatusCode, Json<Vec<ClientPublicKey>>) {
let Some(state) = state.inner() else {
return (StatusCode::NOT_IMPLEMENTED, Json(Vec::new()));
};
let registry_ro = state.client_registry.read().await;
(
StatusCode::OK,
Json(
registry_ro
.values()
.map(|c| c.pub_key())
.collect::<Vec<ClientPublicKey>>(),
),
)
}
// pub type AllClientsResponse = FormattedResponse<()>;
pub(crate) async fn get_client(
Path(pub_key): Path<String>,
State(state): State<WireguardAppState>,
) -> (StatusCode, Json<Vec<Client>>) {
let Some(state) = state.inner() else {
return (StatusCode::NOT_IMPLEMENTED, Json(Vec::new()));
};
let pub_key = match ClientPublicKey::from_str(&pub_key) {
Ok(pub_key) => pub_key,
Err(_) => return (StatusCode::BAD_REQUEST, Json(vec![])),
};
let registry_ro = state.client_registry.read().await;
let clients = registry_ro
.iter()
.filter_map(|(_, c)| {
if c.pub_key() == pub_key {
Some(c.clone())
} else {
None
}
})
.collect::<Vec<Client>>();
if clients.is_empty() {
return (StatusCode::NOT_FOUND, Json(clients));
}
(StatusCode::OK, Json(clients))
}
// pub type ClientResponse = FormattedResponse<()>;
@@ -1,86 +1,112 @@
use std::{collections::HashMap, sync::Arc};
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use axum::{
routing::{get, post},
Router,
use crate::http::api::v1::gateway::client_interfaces::wireguard::client_registry::{
get_all_clients, get_client, register_client,
};
use log::info;
use crate::wireguard::types::{ClientRegistry, PendingRegistrations};
use axum::routing::{get, post};
use axum::Router;
use nym_crypto::asymmetric::encryption;
use nym_node_requests::routes::api::v1::gateway::client_interfaces::wireguard;
use std::sync::Arc;
use tokio::sync::RwLock;
mod api;
use api::v1::client_registry::*;
pub(crate) mod client_registry;
use super::client_handling::client_registration::{ClientPublicKey, ClientRegistry};
const ROUTE_PREFIX: &str = "/api/v1/gateway/client-interfaces/wireguard";
pub struct ApiState {
client_registry: Arc<RwLock<ClientRegistry>>,
sphinx_key_pair: Arc<encryption::KeyPair>,
registration_in_progress: Arc<RwLock<HashMap<ClientPublicKey, u64>>>,
// I don't see any reason why this state should be accessible to any routes outside /wireguard
// if anyone finds compelling reason, it could be moved to the `AppState` struct instead
#[derive(Clone, Default)]
pub struct WireguardAppState {
inner: Option<WireguardAppStateInner>,
}
fn router_with_state(state: Arc<ApiState>) -> Router {
impl WireguardAppState {
pub fn new(
dh_keypair: Arc<encryption::KeyPair>,
client_registry: Arc<RwLock<ClientRegistry>>,
registration_in_progress: Arc<RwLock<PendingRegistrations>>,
) -> Self {
WireguardAppState {
inner: Some(WireguardAppStateInner {
dh_keypair,
client_registry,
registration_in_progress,
}),
}
}
#[allow(dead_code)]
pub(crate) fn dh_keypair(&self) -> Option<&encryption::KeyPair> {
self.inner.as_ref().map(|s| s.dh_keypair.as_ref())
}
#[allow(dead_code)]
pub(crate) fn client_registry(&self) -> Option<&RwLock<ClientRegistry>> {
self.inner.as_ref().map(|s| s.client_registry.as_ref())
}
#[allow(dead_code)]
pub(crate) fn registration_in_progress(&self) -> Option<&RwLock<PendingRegistrations>> {
self.inner
.as_ref()
.map(|s| s.registration_in_progress.as_ref())
}
// not sure what to feel about exposing this method
pub(crate) fn inner(&self) -> Option<&WireguardAppStateInner> {
self.inner.as_ref()
}
}
// helper macro to deal with missing wg state (if not being exposed by the node)
#[macro_export]
macro_rules! get_state {
( $state: ident, $field: ident ) => {{
let Some(ref inner) = $state.inner else {
return ::axum::http::StatusCode::NOT_IMPLEMENTED;
};
inner.$field.as_ref()
}};
}
#[derive(Clone)]
pub(crate) struct WireguardAppStateInner {
dh_keypair: Arc<encryption::KeyPair>,
client_registry: Arc<RwLock<ClientRegistry>>,
registration_in_progress: Arc<RwLock<PendingRegistrations>>,
}
pub(crate) fn routes<S>(initial_state: WireguardAppState) -> Router<S> {
Router::new()
.route(&format!("{}/clients", ROUTE_PREFIX), get(get_all_clients))
.route(&format!("{}/client", ROUTE_PREFIX), post(register_client))
.route(
&format!("{}/client/:pub_key", ROUTE_PREFIX),
get(get_client),
)
.with_state(state)
}
pub(crate) async fn start_http_api(
client_registry: Arc<RwLock<ClientRegistry>>,
sphinx_key_pair: Arc<encryption::KeyPair>,
) {
// Port should be 80 post smoosh
let port = 8000;
info!("Started HTTP API on port {}", port);
let client_registry = Arc::clone(&client_registry);
let state = Arc::new(ApiState {
client_registry,
sphinx_key_pair,
registration_in_progress: Arc::new(RwLock::new(HashMap::new())),
});
let routes = router_with_state(state);
#[allow(clippy::unwrap_used)]
axum::Server::bind(&format!("0.0.0.0:{}", port).parse().unwrap())
.serve(routes.into_make_service())
.await
.unwrap();
// .route("/", get())
.route(wireguard::CLIENTS, get(get_all_clients))
.route(wireguard::CLIENT, post(register_client))
.route(&format!("{}/:pub_key", wireguard::CLIENT), get(get_client))
.with_state(initial_state)
}
#[cfg(test)]
mod test {
use std::net::SocketAddr;
use std::str::FromStr;
use std::{collections::HashMap, sync::Arc};
use crate::http::api::v1::gateway::client_interfaces::wireguard::{
routes, WireguardAppState, WireguardAppStateInner,
};
use crate::wireguard::types::{
ClientMac, ClientMessageRequest, ClientPublicKey, ClientRequest, HmacSha256,
InitMessageRequest,
};
use axum::body::Body;
use axum::http::Request;
use axum::http::StatusCode;
use hmac::Mac;
use nym_crypto::asymmetric::encryption;
use nym_node_requests::routes::api::v1::gateway::client_interfaces::wireguard;
use std::{collections::HashMap, sync::Arc};
use tokio::sync::RwLock;
use tower::Service;
use tower::ServiceExt;
use nym_crypto::asymmetric::encryption;
use tokio::sync::RwLock;
use x25519_dalek::{PublicKey, StaticSecret};
use crate::node::client_handling::client_registration::{
Client, ClientMac, ClientMessage, InitMessage,
};
use crate::node::client_handling::client_registration::{ClientPublicKey, HmacSha256};
use crate::node::http::{router_with_state, ApiState, ROUTE_PREFIX};
#[tokio::test]
async fn registration() {
// 1. Provision random keys for gateway and client
@@ -108,22 +134,25 @@ mod test {
let registration_in_progress = Arc::new(RwLock::new(HashMap::new()));
let client_registry = Arc::new(RwLock::new(HashMap::new()));
let state = Arc::new(ApiState {
client_registry: Arc::clone(&client_registry),
sphinx_key_pair: Arc::new(gateway_key_pair),
registration_in_progress: Arc::clone(&registration_in_progress),
});
let state = WireguardAppState {
inner: Some(WireguardAppStateInner {
client_registry: Arc::clone(&client_registry),
dh_keypair: Arc::new(gateway_key_pair),
registration_in_progress: Arc::clone(&registration_in_progress),
}),
};
// `Router` implements `tower::Service<Request<Body>>` so we can
// call it like any tower service, no need to run an HTTP server.
let mut app = router_with_state(state);
let mut app = routes(state);
let init_message =
ClientMessage::Init(InitMessage::new(ClientPublicKey::new(client_static_public)));
let init_message = ClientMessageRequest::Initial(InitMessageRequest {
pub_key: ClientPublicKey::new(client_static_public).to_string(),
});
let init_request = Request::builder()
.method("POST")
.uri(format!("{}/client", ROUTE_PREFIX))
.uri(wireguard::CLIENT)
.header("Content-type", "application/json")
.body(Body::from(serde_json::to_vec(&init_message).unwrap()))
.unwrap();
@@ -150,15 +179,15 @@ mod test {
mac.update(&nonce.unwrap().to_le_bytes());
let mac = mac.finalize().into_bytes();
let finalized_message = ClientMessage::Final(Client {
pub_key: ClientPublicKey::new(client_static_public),
socket: SocketAddr::from_str("127.0.0.1:8080").unwrap(),
mac: ClientMac::new(mac.as_slice().to_vec()),
let finalized_message = ClientMessageRequest::Final(ClientRequest {
pub_key: ClientPublicKey::new(client_static_public).to_string(),
socket: "127.0.0.1:8080".to_string(),
mac: ClientMac::new(mac.as_slice().to_vec()).to_string(),
});
let final_request = Request::builder()
.method("POST")
.uri(format!("{}/client", ROUTE_PREFIX))
.uri(wireguard::CLIENT)
.header("Content-type", "application/json")
.body(Body::from(serde_json::to_vec(&finalized_message).unwrap()))
.unwrap();
@@ -175,7 +204,7 @@ mod test {
let clients_request = Request::builder()
.method("GET")
.uri(format!("{}/clients", ROUTE_PREFIX))
.uri(wireguard::CLIENTS)
.body(Body::empty())
.unwrap();
@@ -1,7 +1,7 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::http::state::AppState;
use crate::http::api::v1::gateway::client_interfaces::wireguard::WireguardAppState;
use axum::routing::get;
use axum::Router;
use nym_node_requests::api::v1::gateway::models;
@@ -15,7 +15,10 @@ pub struct Config {
pub details: Option<models::Gateway>,
}
pub(crate) fn routes(config: Config) -> Router<AppState> {
pub(crate) fn routes<S: Send + Sync + 'static + Clone>(
config: Config,
initial_wg_state: WireguardAppState,
) -> Router<S> {
Router::new()
.route(
"/",
@@ -26,6 +29,9 @@ pub(crate) fn routes(config: Config) -> Router<AppState> {
)
.nest(
gateway::CLIENT_INTERFACES,
client_interfaces::routes(config.details.map(|g| g.client_interfaces)),
client_interfaces::routes(
config.details.map(|g| g.client_interfaces),
initial_wg_state,
),
)
}
@@ -1,7 +1,6 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::http::state::AppState;
use axum::routing::get;
use axum::Router;
use nym_node_requests::api::v1::mixnode::models;
@@ -13,7 +12,7 @@ pub struct Config {
pub details: Option<models::Mixnode>,
}
pub(crate) fn routes(config: Config) -> Router<AppState> {
pub(crate) fn routes<S: Send + Sync + 'static + Clone>(config: Config) -> Router<S> {
Router::new().route(
"/",
get({
+9 -3
View File
@@ -1,7 +1,7 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::http::state::AppState;
use crate::http::api::v1::gateway::client_interfaces::wireguard::WireguardAppState;
use axum::Router;
use nym_node_requests::routes::api::v1;
@@ -19,9 +19,15 @@ pub struct Config {
pub network_requester: network_requester::Config,
}
pub(super) fn routes(config: Config) -> Router<AppState> {
pub(super) fn routes<S: Send + Sync + 'static + Clone>(
config: Config,
initial_wg_state: WireguardAppState,
) -> Router<S> {
Router::new()
.nest(v1::GATEWAY, gateway::routes(config.gateway))
.nest(
v1::GATEWAY,
gateway::routes(config.gateway, initial_wg_state),
)
.nest(v1::MIXNODE, mixnode::routes(config.mixnode))
.nest(
v1::NETWORK_REQUESTER,
@@ -1,7 +1,6 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::http::state::AppState;
use axum::routing::get;
use axum::Router;
use nym_node_requests::api::v1::network_requester::models;
@@ -13,7 +12,7 @@ pub struct Config {
pub details: Option<models::NetworkRequester>,
}
pub(crate) fn routes(config: Config) -> Router<AppState> {
pub(crate) fn routes<S: Send + Sync + 'static + Clone>(config: Config) -> Router<S> {
Router::new().route(
"/",
get({
+1 -2
View File
@@ -4,7 +4,6 @@
use crate::http::api::v1::node::build_information::build_information;
use crate::http::api::v1::node::host_information::host_information;
use crate::http::api::v1::node::roles::roles;
use crate::http::state::AppState;
use axum::routing::get;
use axum::Router;
use nym_node_requests::api::v1::node::models;
@@ -21,7 +20,7 @@ pub struct Config {
pub roles: models::NodeRoles,
}
pub(super) fn routes(config: Config) -> Router<AppState> {
pub(super) fn routes<S: Send + Sync + 'static + Clone>(config: Config) -> Router<S> {
Router::new()
.route(
v1::BUILD_INFO,
+2 -3
View File
@@ -2,7 +2,6 @@
// SPDX-License-Identifier: Apache-2.0
use crate::http::router::api;
use crate::http::state::AppState;
use axum::Router;
use nym_node_requests::api as api_requests;
use nym_node_requests::routes::api::v1;
@@ -18,7 +17,7 @@ use utoipa_swagger_ui::SwaggerUi;
api::v1::node::roles::roles,
api::v1::gateway::root::root_gateway,
api::v1::gateway::client_interfaces::client_interfaces,
api::v1::gateway::client_interfaces::wireguard,
api::v1::gateway::client_interfaces::wireguard_info,
api::v1::gateway::client_interfaces::mixnet_websockets,
api::v1::mixnode::root::root_mixnode,
api::v1::network_requester::root::root_network_requester,
@@ -41,7 +40,7 @@ use utoipa_swagger_ui::SwaggerUi;
)]
pub(crate) struct ApiDoc;
pub(crate) fn route() -> Router<AppState> {
pub(crate) fn route<S: Send + Sync + 'static + Clone>() -> Router<S> {
// provide absolute path to the openapi.json
let config = utoipa_swagger_ui::Config::from("/api/v1/api-docs/openapi.json");
SwaggerUi::new(v1::SWAGGER)
+1 -2
View File
@@ -1,7 +1,6 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::http::state::AppState;
use axum::routing::get;
use axum::Router;
use std::path::PathBuf;
@@ -12,7 +11,7 @@ pub struct Config {
pub assets_path: Option<PathBuf>,
}
pub(super) fn routes(config: Config) -> Router<AppState> {
pub(super) fn routes<S: Send + Sync + 'static + Clone>(config: Config) -> Router<S> {
if let Some(assets) = config.assets_path {
Router::new().nest_service("/", ServeDir::new(assets))
} else {
+7 -2
View File
@@ -2,6 +2,7 @@
// SPDX-License-Identifier: Apache-2.0
use crate::error::NymNodeError;
pub use crate::http::api::v1::gateway::client_interfaces::wireguard::WireguardAppState;
use crate::http::middleware::logging;
use crate::http::state::AppState;
use crate::http::NymNodeHTTPServer;
@@ -72,13 +73,17 @@ pub struct NymNodeRouter {
}
impl NymNodeRouter {
pub fn new(config: Config) -> NymNodeRouter {
// TODO: move the wg state to a builder
pub fn new(config: Config, initial_wg_state: Option<WireguardAppState>) -> NymNodeRouter {
let state = AppState::new();
NymNodeRouter {
inner: Router::new()
.nest(routes::LANDING_PAGE, landing_page::routes(config.landing))
.nest(routes::API, api::routes(config.api))
.nest(
routes::API,
api::routes(config.api, initial_wg_state.unwrap_or_default()),
)
.layer(axum::middleware::from_fn(logging::logger))
.with_state(state),
}
+17 -20
View File
@@ -1,32 +1,29 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use std::ops::Deref;
use std::sync::Arc;
#[derive(Debug, Clone)]
pub(crate) struct AppState {
inner: Arc<AppStateInner>,
// wireguard: WireguardAppState,
}
// TODO: https://docs.rs/axum/latest/axum/extract/struct.State.html#substates
// #[derive(Debug, Clone)]
// pub struct WireguardAppState {
// // inner: Option<WireguardAppStateInner>,
// }
//
// #[derive(Debug)]
// pub(crate) struct WireguardAppStateInner {
// //
// }
//
// impl FromRef<AppState> for WireguardAppState {
// fn from_ref(app_state: &AppState) -> Self {
// app_state.wireguard.clone()
// }
// }
impl AppState {
pub fn new() -> Self {
AppState {
inner: Arc::new(AppStateInner {}),
}
AppState {}
}
}
// TODO: that's kinda abusing it...
impl Deref for AppState {
type Target = AppStateInner;
fn deref(&self) -> &Self::Target {
&self.inner
}
}
#[derive(Debug)]
pub(crate) struct AppStateInner {}
+1
View File
@@ -8,3 +8,4 @@
pub mod error;
pub mod http;
pub mod wireguard;
+43
View File
@@ -0,0 +1,43 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use hmac::digest::MacError;
use std::net::AddrParseError;
use thiserror::Error;
#[derive(Debug, Error)]
pub enum WireguardError {
#[error("the provided base64-encoded client MAC ('{mac}') was malformed: {source}")]
MalformedClientMac {
mac: String,
#[source]
source: base64::DecodeError,
},
#[error("the provided base64-encoded client x25519 public key ('{pub_key}') was malformed: {source}")]
MalformedClientPublicKeyEncoding {
pub_key: String,
#[source]
source: base64::DecodeError,
},
#[error("the provided base64-encoded client x25519 public key ('{pub_key}') has invalid length: {decoded_length}. expected 32 bytes")]
InvalidClientPublicKeyLength {
pub_key: String,
decoded_length: usize,
},
#[error("failed to verify mac provided by '{client}': {source}")]
FailedClientMacVerification {
client: String,
#[source]
source: MacError,
},
#[error("the provided client socket address ('{raw}') was malformed: {source}")]
MalformedClientSocketAddress {
raw: String,
#[source]
source: AddrParseError,
},
}
+8
View File
@@ -0,0 +1,8 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
// obviously most of the features currently reside in the gateway,
// but let's start putting everything in here
pub mod error;
pub mod types;
+258
View File
@@ -0,0 +1,258 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::wireguard::error::WireguardError;
use base64::{engine::general_purpose, Engine};
use hmac::{Hmac, Mac};
use nym_crypto::asymmetric::encryption::PrivateKey;
pub(crate) use nym_node_requests::api::v1::gateway::client_interefaces::wireguard::models::{
Client as ClientRequest, ClientMessage as ClientMessageRequest,
InitMessage as InitMessageRequest,
};
use serde::{Deserialize, Serialize};
use sha2::Sha256;
use std::collections::HashMap;
use std::fmt;
use std::hash::{Hash, Hasher};
use std::net::SocketAddr;
use std::ops::Deref;
use std::str::FromStr;
use x25519_dalek::StaticSecret;
pub type HmacSha256 = Hmac<Sha256>;
pub type ClientRegistry = HashMap<SocketAddr, Client>;
pub type Nonce = u64;
pub type PendingRegistrations = HashMap<ClientPublicKey, Nonce>;
#[derive(Debug, Clone)]
pub(crate) enum ClientMessage {
Init(InitMessage),
Final(Client),
}
impl TryFrom<ClientMessageRequest> for ClientMessage {
type Error = WireguardError;
fn try_from(value: ClientMessageRequest) -> Result<Self, Self::Error> {
match value {
ClientMessageRequest::Initial(init) => init.try_into().map(ClientMessage::Init),
ClientMessageRequest::Final(final_msg) => {
final_msg.try_into().map(ClientMessage::Final)
}
}
}
}
#[derive(Debug, Clone)]
pub(crate) struct InitMessage {
pub pub_key: ClientPublicKey,
}
impl TryFrom<InitMessageRequest> for InitMessage {
type Error = WireguardError;
fn try_from(value: InitMessageRequest) -> Result<Self, Self::Error> {
Ok(InitMessage {
pub_key: value.pub_key.parse()?,
})
}
}
impl InitMessage {
pub fn pub_key(&self) -> &ClientPublicKey {
&self.pub_key
}
#[allow(dead_code)]
pub fn new(pub_key: ClientPublicKey) -> Self {
InitMessage { pub_key }
}
}
// Client that wants to register sends its PublicKey and SocketAddr bytes mac digest encrypted with a DH shared secret.
// Gateway can then verify pub_key payload using the sme process
#[derive(Debug, Clone)]
// TEMP:
#[derive(Serialize)]
pub struct Client {
// base64 encoded public key, using x25519-dalek for impl
pub pub_key: ClientPublicKey,
pub socket: SocketAddr,
pub mac: ClientMac,
}
impl TryFrom<ClientRequest> for Client {
type Error = WireguardError;
fn try_from(value: ClientRequest) -> Result<Self, Self::Error> {
Ok(Client {
pub_key: value.pub_key.parse()?,
socket: value.socket.parse().map_err(|source| {
WireguardError::MalformedClientSocketAddress {
raw: value.socket,
source,
}
})?,
mac: value.mac.parse()?,
})
}
}
impl Client {
// Reusable secret should be gateways Wireguard PK
// Client should perform this step when generating its payload, using its own WG PK
pub fn verify(&self, gateway_key: &PrivateKey, nonce: u64) -> Result<(), WireguardError> {
// convert from 1.0 x25519-dalek private key into 2.0 x25519-dalek
#[allow(clippy::expect_used)]
let static_secret = StaticSecret::try_from(gateway_key.to_bytes())
.expect("conversion between x25519 private keys is infallible");
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.socket.ip().to_string().as_bytes());
mac.update(self.socket.port().to_string().as_bytes());
mac.update(&nonce.to_le_bytes());
mac.verify_slice(&self.mac)
.map_err(|source| WireguardError::FailedClientMacVerification {
client: self.pub_key.to_string(),
source,
})
}
pub fn pub_key(&self) -> ClientPublicKey {
self.pub_key
}
pub fn socket(&self) -> SocketAddr {
self.socket
}
}
// This should go into nym-wireguard crate
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub struct ClientPublicKey(x25519_dalek::PublicKey);
// TODO: change the inner type into generic array of size HmacSha256::OutputSize
#[derive(Debug, Clone)]
pub struct ClientMac(Vec<u8>);
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<u8>) -> Self {
ClientMac(mac)
}
}
impl Deref for ClientMac {
type Target = Vec<u8>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl fmt::Display for ClientPublicKey {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", general_purpose::STANDARD.encode(self.0.as_bytes()))
}
}
impl Hash for ClientPublicKey {
fn hash<H: Hasher>(&self, state: &mut H) {
self.0.as_bytes().hash(state)
}
}
impl FromStr for ClientMac {
type Err = WireguardError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let mac_bytes: Vec<u8> = general_purpose::STANDARD.decode(s).map_err(|source| {
WireguardError::MalformedClientMac {
mac: s.to_string(),
source,
}
})?;
Ok(ClientMac(mac_bytes))
}
}
impl ClientPublicKey {
#[allow(dead_code)]
pub fn new(key: x25519_dalek::PublicKey) -> Self {
ClientPublicKey(key)
}
pub fn as_bytes(&self) -> &[u8] {
self.0.as_bytes()
}
}
impl Deref for ClientPublicKey {
type Target = x25519_dalek::PublicKey;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl FromStr for ClientPublicKey {
type Err = WireguardError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let key_bytes: Vec<u8> = general_purpose::STANDARD.decode(s).map_err(|source| {
WireguardError::MalformedClientPublicKeyEncoding {
pub_key: s.to_string(),
source,
}
})?;
let decoded_length = key_bytes.len();
let Ok(key_arr): Result<[u8; 32], _> = key_bytes.try_into() else {
return Err(WireguardError::InvalidClientPublicKeyLength {
pub_key: s.to_string(),
decoded_length,
})?;
};
Ok(ClientPublicKey(x25519_dalek::PublicKey::from(key_arr)))
}
}
// TEMPORARY:
impl Serialize for ClientPublicKey {
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
let encoded_key = general_purpose::STANDARD.encode(self.0.as_bytes());
serializer.serialize_str(&encoded_key)
}
}
impl<'de> Deserialize<'de> for ClientPublicKey {
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
let encoded_key = String::deserialize(deserializer)?;
Ok(ClientPublicKey::from_str(&encoded_key).map_err(serde::de::Error::custom))?
}
}
impl Serialize for ClientMac {
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
let encoded_key = general_purpose::STANDARD.encode(&self.0);
serializer.serialize_str(&encoded_key)
}
}