brought in wg routes into nym-node router
This commit is contained in:
Generated
+128
-14
@@ -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
@@ -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 +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 +0,0 @@
|
||||
pub(crate) mod client_registry;
|
||||
@@ -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
@@ -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;
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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> {
|
||||
|
||||
+126
@@ -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<()>;
|
||||
+106
-77
@@ -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(®istration_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(®istration_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({
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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,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,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 {
|
||||
|
||||
@@ -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
@@ -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 {}
|
||||
|
||||
@@ -8,3 +8,4 @@
|
||||
|
||||
pub mod error;
|
||||
pub mod http;
|
||||
pub mod wireguard;
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
}
|
||||
@@ -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;
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user