Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 012a81857b | |||
| 8c752b3028 |
@@ -23,7 +23,7 @@ jobs:
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
|
||||
|
||||
- name: Setup yarn
|
||||
run: npm install -g yarn
|
||||
|
||||
@@ -54,6 +54,6 @@ jobs:
|
||||
|
||||
- name: Lint
|
||||
run: yarn lint
|
||||
|
||||
|
||||
- name: Typecheck with tsc
|
||||
run: yarn tsc
|
||||
|
||||
@@ -4,38 +4,6 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [2025.20-leerdammer] (2025-11-12)
|
||||
|
||||
- Max/tweak ts sdk actions ([#6185])
|
||||
- chore: resolve clippy 1.91 warnings ([#6168])
|
||||
- [chore] Remove unused dependencies ([#6151])
|
||||
- Use typed-builder for registration client builder config ([#6150])
|
||||
- tommy is too quick ([#6149])
|
||||
- configurable mixnet client startup timeout ([#6148])
|
||||
- [Feature/operators]: QUIC bridge deployment script v2 ([#6145])
|
||||
- Bugfix: Add circuit breaker ([#6143])
|
||||
- bugfix: update internal owner address in transferred share ([#6139])
|
||||
- Update quic_bridge_deployment.sh for IPv4 and .deb package ([#6138])
|
||||
- feat: expose more explicit new_with_fronted_urls builder for http API client ([#6136])
|
||||
- bugfix: update stored epoch share when changing ownership ([#6135])
|
||||
- Domain fronting ([#6134])
|
||||
- bugfix: update stored epoch share when changing announce address ([#6131])
|
||||
|
||||
[#6185]: https://github.com/nymtech/nym/pull/6185
|
||||
[#6168]: https://github.com/nymtech/nym/pull/6168
|
||||
[#6151]: https://github.com/nymtech/nym/pull/6151
|
||||
[#6150]: https://github.com/nymtech/nym/pull/6150
|
||||
[#6149]: https://github.com/nymtech/nym/pull/6149
|
||||
[#6148]: https://github.com/nymtech/nym/pull/6148
|
||||
[#6145]: https://github.com/nymtech/nym/pull/6145
|
||||
[#6143]: https://github.com/nymtech/nym/pull/6143
|
||||
[#6139]: https://github.com/nymtech/nym/pull/6139
|
||||
[#6138]: https://github.com/nymtech/nym/pull/6138
|
||||
[#6136]: https://github.com/nymtech/nym/pull/6136
|
||||
[#6135]: https://github.com/nymtech/nym/pull/6135
|
||||
[#6134]: https://github.com/nymtech/nym/pull/6134
|
||||
[#6131]: https://github.com/nymtech/nym/pull/6131
|
||||
|
||||
## [2025.19-kase] (2025-10-30)
|
||||
|
||||
- update ns agent workflow ([#6154])
|
||||
|
||||
Generated
+43
-24
@@ -4825,7 +4825,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-api"
|
||||
version = "1.1.69"
|
||||
version = "1.1.68"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@@ -4988,7 +4988,6 @@ dependencies = [
|
||||
"nym-network-defaults",
|
||||
"nym-service-provider-requests-common",
|
||||
"nym-sphinx",
|
||||
"nym-test-utils",
|
||||
"nym-wireguard-types",
|
||||
"rand 0.8.5",
|
||||
"semver 1.0.26",
|
||||
@@ -4996,7 +4995,6 @@ dependencies = [
|
||||
"sha2 0.10.9",
|
||||
"strum_macros",
|
||||
"thiserror 2.0.12",
|
||||
"tracing",
|
||||
"x25519-dalek",
|
||||
]
|
||||
|
||||
@@ -5005,16 +5003,21 @@ name = "nym-bandwidth-controller"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"bip39",
|
||||
"log",
|
||||
"nym-credential-storage",
|
||||
"nym-credentials",
|
||||
"nym-credentials-interface",
|
||||
"nym-crypto",
|
||||
"nym-ecash-contract-common",
|
||||
"nym-ecash-time",
|
||||
"nym-network-defaults",
|
||||
"nym-task",
|
||||
"nym-validator-client",
|
||||
"rand 0.8.5",
|
||||
"thiserror 2.0.12",
|
||||
"url",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -5048,7 +5051,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-cli"
|
||||
version = "1.1.66"
|
||||
version = "1.1.65"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"base64 0.22.1",
|
||||
@@ -5131,7 +5134,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-client"
|
||||
version = "1.1.66"
|
||||
version = "1.1.65"
|
||||
dependencies = [
|
||||
"bs58",
|
||||
"clap",
|
||||
@@ -5567,7 +5570,6 @@ dependencies = [
|
||||
"sqlx",
|
||||
"sqlx-pool-guard",
|
||||
"thiserror 2.0.12",
|
||||
"time",
|
||||
"tokio",
|
||||
"zeroize",
|
||||
]
|
||||
@@ -5603,13 +5605,12 @@ dependencies = [
|
||||
"nym-api-requests",
|
||||
"nym-credentials",
|
||||
"nym-credentials-interface",
|
||||
"nym-crypto",
|
||||
"nym-ecash-contract-common",
|
||||
"nym-gateway-requests",
|
||||
"nym-gateway-storage",
|
||||
"nym-task",
|
||||
"nym-upgrade-mode-check",
|
||||
"nym-validator-client",
|
||||
"rand 0.8.5",
|
||||
"si-scale",
|
||||
"thiserror 2.0.12",
|
||||
"time",
|
||||
@@ -5649,7 +5650,6 @@ dependencies = [
|
||||
"nym-compact-ecash",
|
||||
"nym-ecash-time",
|
||||
"nym-network-defaults",
|
||||
"nym-upgrade-mode-check",
|
||||
"rand 0.8.5",
|
||||
"serde",
|
||||
"strum",
|
||||
@@ -5800,6 +5800,7 @@ dependencies = [
|
||||
name = "nym-gateway"
|
||||
version = "1.1.36"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
"bincode",
|
||||
"bip39",
|
||||
@@ -5810,6 +5811,7 @@ dependencies = [
|
||||
"futures",
|
||||
"ipnetwork",
|
||||
"mock_instant",
|
||||
"nym-api-requests",
|
||||
"nym-authenticator-requests",
|
||||
"nym-client-core",
|
||||
"nym-credential-verification",
|
||||
@@ -5822,6 +5824,7 @@ dependencies = [
|
||||
"nym-id",
|
||||
"nym-ip-packet-router",
|
||||
"nym-mixnet-client",
|
||||
"nym-mixnode-common",
|
||||
"nym-network-defaults",
|
||||
"nym-network-requester",
|
||||
"nym-node-metrics",
|
||||
@@ -5831,18 +5834,20 @@ dependencies = [
|
||||
"nym-statistics-common",
|
||||
"nym-task",
|
||||
"nym-topology",
|
||||
"nym-upgrade-mode-check",
|
||||
"nym-types",
|
||||
"nym-validator-client",
|
||||
"nym-wireguard",
|
||||
"nym-wireguard-private-metadata-server",
|
||||
"nym-wireguard-types",
|
||||
"rand 0.8.5",
|
||||
"serde",
|
||||
"sha2 0.10.9",
|
||||
"thiserror 2.0.12",
|
||||
"time",
|
||||
"tokio",
|
||||
"tokio-stream",
|
||||
"tokio-tungstenite",
|
||||
"tokio-util",
|
||||
"tracing",
|
||||
"url",
|
||||
"zeroize",
|
||||
@@ -6053,7 +6058,6 @@ dependencies = [
|
||||
"thiserror 2.0.12",
|
||||
"tokio",
|
||||
"tracing",
|
||||
"tracing-subscriber",
|
||||
"url",
|
||||
"wasmtimer",
|
||||
]
|
||||
@@ -6358,7 +6362,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-network-requester"
|
||||
version = "1.1.67"
|
||||
version = "1.1.66"
|
||||
dependencies = [
|
||||
"addr",
|
||||
"anyhow",
|
||||
@@ -6408,7 +6412,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-node"
|
||||
version = "1.21.0"
|
||||
version = "1.20.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"arc-swap",
|
||||
@@ -6432,7 +6436,6 @@ dependencies = [
|
||||
"futures",
|
||||
"hkdf",
|
||||
"human-repr",
|
||||
"humantime",
|
||||
"humantime-serde",
|
||||
"indicatif",
|
||||
"ipnetwork",
|
||||
@@ -6440,7 +6443,6 @@ dependencies = [
|
||||
"nym-bin-common",
|
||||
"nym-client-core-config-types",
|
||||
"nym-config",
|
||||
"nym-credential-verification",
|
||||
"nym-crypto",
|
||||
"nym-gateway",
|
||||
"nym-gateway-stats-storage",
|
||||
@@ -6513,13 +6515,13 @@ version = "0.1.0"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"celes",
|
||||
"humantime",
|
||||
"humantime-serde",
|
||||
"nym-bin-common",
|
||||
"nym-crypto",
|
||||
"nym-exit-policy",
|
||||
"nym-http-api-client",
|
||||
"nym-noise-keys",
|
||||
"nym-upgrade-mode-check",
|
||||
"nym-wireguard-types",
|
||||
"rand_chacha 0.3.1",
|
||||
"schemars 0.8.22",
|
||||
@@ -6530,7 +6532,6 @@ dependencies = [
|
||||
"thiserror 2.0.12",
|
||||
"time",
|
||||
"tokio",
|
||||
"url",
|
||||
"utoipa",
|
||||
]
|
||||
|
||||
@@ -6938,7 +6939,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-socks5-client"
|
||||
version = "1.1.66"
|
||||
version = "1.1.65"
|
||||
dependencies = [
|
||||
"bs58",
|
||||
"clap",
|
||||
@@ -7572,10 +7573,17 @@ dependencies = [
|
||||
name = "nym-wireguard"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"base64 0.22.1",
|
||||
"bincode",
|
||||
"chrono",
|
||||
"dashmap",
|
||||
"defguard_wireguard_rs",
|
||||
"dyn-clone",
|
||||
"futures",
|
||||
"ip_network",
|
||||
"log",
|
||||
"nym-authenticator-requests",
|
||||
"nym-credential-verification",
|
||||
"nym-credentials-interface",
|
||||
"nym-crypto",
|
||||
@@ -7586,9 +7594,11 @@ dependencies = [
|
||||
"nym-task",
|
||||
"nym-wireguard-types",
|
||||
"thiserror 2.0.12",
|
||||
"time",
|
||||
"tokio",
|
||||
"tokio-stream",
|
||||
"tracing",
|
||||
"x25519-dalek",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -7640,20 +7650,15 @@ version = "1.0.0"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"axum",
|
||||
"futures",
|
||||
"nym-credential-verification",
|
||||
"nym-credentials-interface",
|
||||
"nym-crypto",
|
||||
"nym-http-api-client",
|
||||
"nym-http-api-common",
|
||||
"nym-upgrade-mode-check",
|
||||
"nym-wireguard",
|
||||
"nym-wireguard-private-metadata-client",
|
||||
"nym-wireguard-private-metadata-server",
|
||||
"nym-wireguard-private-metadata-shared",
|
||||
"time",
|
||||
"tokio",
|
||||
"tower 0.5.2",
|
||||
"tower-http 0.5.2",
|
||||
"utoipa",
|
||||
]
|
||||
@@ -7663,7 +7668,10 @@ name = "nym-wireguard-types"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"base64 0.22.1",
|
||||
"log",
|
||||
"nym-config",
|
||||
"nym-crypto",
|
||||
"nym-network-defaults",
|
||||
"rand 0.8.5",
|
||||
"serde",
|
||||
"thiserror 2.0.12",
|
||||
@@ -7672,7 +7680,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nymvisor"
|
||||
version = "0.1.31"
|
||||
version = "0.1.30"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bytes",
|
||||
@@ -8593,6 +8601,17 @@ dependencies = [
|
||||
"psl-types",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "query-tester"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"nym-network-defaults",
|
||||
"nym-validator-client",
|
||||
"serde",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quick-error"
|
||||
version = "2.0.1"
|
||||
|
||||
+1
-2
@@ -166,12 +166,11 @@ members = [
|
||||
"wasm/node-tester",
|
||||
"wasm/zknym-lib",
|
||||
"nym-gateway-probe"
|
||||
]
|
||||
, "query-tester"]
|
||||
|
||||
default-members = [
|
||||
"clients/native",
|
||||
"clients/socks5",
|
||||
"nym-authenticator-client",
|
||||
"nym-api",
|
||||
"nym-credential-proxy/nym-credential-proxy",
|
||||
"nym-node",
|
||||
|
||||
@@ -140,7 +140,8 @@ clippy: sdk-wasm-lint
|
||||
|
||||
WASM_CONTRACT_DIR := contracts/target/wasm32-unknown-unknown/release
|
||||
# Find every direct contract folder that contains a Cargo.toml
|
||||
CONTRACT_DIRS := $(shell find contracts -type f -name Cargo.toml \( ! -path "contracts/Cargo.toml" \) | grep -v integration-tests | xargs -n1 dirname | sort -u)
|
||||
#CONTRACT_DIRS := $(shell find contracts -type f -name Cargo.toml \( ! -path "contracts/Cargo.toml" \) | grep -v integration-tests | xargs -n1 dirname | sort -u)
|
||||
CONTRACT_DIRS := contracts/example-contract
|
||||
|
||||
CONTRACTS_OUT_DIR = contracts/artifacts
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "nym-client"
|
||||
version = "1.1.66"
|
||||
version = "1.1.65"
|
||||
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>", "Jędrzej Stuczyński <andrew@nymtech.net>"]
|
||||
description = "Implementation of the Nym Client"
|
||||
edition = "2021"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "nym-socks5-client"
|
||||
version = "1.1.66"
|
||||
version = "1.1.65"
|
||||
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>"]
|
||||
description = "A SOCKS5 localhost proxy that converts incoming messages to Sphinx and sends them to a Nym address"
|
||||
edition = "2021"
|
||||
|
||||
@@ -16,7 +16,6 @@ serde = { workspace = true, features = ["derive"] }
|
||||
semver = { workspace = true }
|
||||
strum_macros = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
tracing = { workspace = true }
|
||||
|
||||
nym-credentials-interface = { path = "../credentials-interface" }
|
||||
nym-crypto = { path = "../crypto", features = ["asymmetric"] }
|
||||
@@ -30,13 +29,7 @@ hmac = { workspace = true, optional = true }
|
||||
sha2 = { workspace = true, optional = true }
|
||||
x25519-dalek = { workspace = true, features = ["static_secrets"] }
|
||||
|
||||
[dev-dependencies]
|
||||
nym-test-utils = { path = "../test-utils" }
|
||||
|
||||
[features]
|
||||
default = ["verify"]
|
||||
# this is moved to a separate feature as we really need clients to import it (especially, *cough*, wasm)
|
||||
verify = ["hmac", "sha2"]
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
@@ -6,11 +6,9 @@ use nym_wireguard_types::PeerPublicKey;
|
||||
|
||||
use crate::{
|
||||
AuthenticatorVersion, Error,
|
||||
traits::{
|
||||
FinalMessage, InitMessage, QueryBandwidthMessage, TopUpMessage, UpgradeModeMessage,
|
||||
Versionable,
|
||||
},
|
||||
v2, v3, v4, v5, v6,
|
||||
latest::registration::IpPair,
|
||||
traits::{FinalMessage, InitMessage, QueryBandwidthMessage, TopUpMessage, Versionable},
|
||||
v2, v3, v4, v5,
|
||||
};
|
||||
|
||||
// This is very redundant with AuthenticatorRequest and I reckon they could be smooshed.
|
||||
@@ -21,293 +19,6 @@ pub enum ClientMessage {
|
||||
Final(Box<dyn FinalMessage + Send + Sync + 'static>),
|
||||
Query(Box<dyn QueryBandwidthMessage + Send + Sync + 'static>),
|
||||
TopUp(Box<dyn TopUpMessage + Send + Sync + 'static>),
|
||||
UpgradeModeCheck(Box<dyn UpgradeModeMessage + Send + Sync + 'static>),
|
||||
}
|
||||
|
||||
pub struct SerialisedRequest {
|
||||
pub bytes: Vec<u8>,
|
||||
pub request_id: u64,
|
||||
}
|
||||
|
||||
impl SerialisedRequest {
|
||||
pub fn new(bytes: Vec<u8>, request_id: u64) -> Self {
|
||||
Self { bytes, request_id }
|
||||
}
|
||||
}
|
||||
|
||||
impl ClientMessage {
|
||||
fn serialise_v1(&self) -> Result<SerialisedRequest, Error> {
|
||||
Err(Error::UnsupportedVersion)
|
||||
}
|
||||
|
||||
fn serialise_v2(&self, reply_to: Recipient) -> Result<SerialisedRequest, Error> {
|
||||
use v2::{
|
||||
registration::{ClientMac, FinalMessage, GatewayClient, InitMessage},
|
||||
request::AuthenticatorRequest,
|
||||
};
|
||||
match self {
|
||||
ClientMessage::Initial(init_message) => {
|
||||
let (req, id) = AuthenticatorRequest::new_initial_request(
|
||||
InitMessage {
|
||||
pub_key: init_message.pub_key(),
|
||||
},
|
||||
reply_to,
|
||||
);
|
||||
Ok(SerialisedRequest::new(req.to_bytes()?, id))
|
||||
}
|
||||
ClientMessage::Final(final_message) => {
|
||||
let (req, id) = AuthenticatorRequest::new_final_request(
|
||||
FinalMessage {
|
||||
gateway_client: GatewayClient {
|
||||
pub_key: final_message.gateway_client_pub_key(),
|
||||
private_ip: final_message
|
||||
.gateway_client_ipv4()
|
||||
.ok_or(Error::UnsupportedMessage)?
|
||||
.into(),
|
||||
mac: ClientMac::new(final_message.gateway_client_mac()),
|
||||
},
|
||||
credential: final_message
|
||||
.credential()
|
||||
.and_then(|c| c.credential.into_zk_nym())
|
||||
.map(|c| *c),
|
||||
},
|
||||
reply_to,
|
||||
);
|
||||
Ok(SerialisedRequest::new(req.to_bytes()?, id))
|
||||
}
|
||||
ClientMessage::Query(query_message) => {
|
||||
let (req, id) =
|
||||
AuthenticatorRequest::new_query_request(query_message.pub_key(), reply_to);
|
||||
Ok(SerialisedRequest::new(req.to_bytes()?, id))
|
||||
}
|
||||
_ => Err(Error::UnsupportedMessage),
|
||||
}
|
||||
}
|
||||
|
||||
fn serialise_v3(&self, reply_to: Recipient) -> Result<SerialisedRequest, Error> {
|
||||
use v3::{
|
||||
registration::{ClientMac, FinalMessage, GatewayClient, InitMessage},
|
||||
request::AuthenticatorRequest,
|
||||
topup::TopUpMessage,
|
||||
};
|
||||
match self {
|
||||
ClientMessage::Initial(init_message) => {
|
||||
let (req, id) = AuthenticatorRequest::new_initial_request(
|
||||
InitMessage {
|
||||
pub_key: init_message.pub_key(),
|
||||
},
|
||||
reply_to,
|
||||
);
|
||||
Ok(SerialisedRequest::new(req.to_bytes()?, id))
|
||||
}
|
||||
ClientMessage::Final(final_message) => {
|
||||
let (req, id) = AuthenticatorRequest::new_final_request(
|
||||
FinalMessage {
|
||||
gateway_client: GatewayClient {
|
||||
pub_key: final_message.gateway_client_pub_key(),
|
||||
private_ip: final_message
|
||||
.gateway_client_ipv4()
|
||||
.ok_or(Error::UnsupportedMessage)?
|
||||
.into(),
|
||||
mac: ClientMac::new(final_message.gateway_client_mac()),
|
||||
},
|
||||
credential: final_message
|
||||
.credential()
|
||||
.and_then(|c| c.credential.into_zk_nym())
|
||||
.map(|c| *c),
|
||||
},
|
||||
reply_to,
|
||||
);
|
||||
Ok(SerialisedRequest::new(req.to_bytes()?, id))
|
||||
}
|
||||
ClientMessage::Query(query_message) => {
|
||||
let (req, id) =
|
||||
AuthenticatorRequest::new_query_request(query_message.pub_key(), reply_to);
|
||||
Ok(SerialisedRequest::new(req.to_bytes()?, id))
|
||||
}
|
||||
ClientMessage::TopUp(top_up_message) => {
|
||||
let (req, id) = AuthenticatorRequest::new_topup_request(
|
||||
TopUpMessage {
|
||||
pub_key: top_up_message.pub_key(),
|
||||
credential: top_up_message.credential(),
|
||||
},
|
||||
reply_to,
|
||||
);
|
||||
Ok(SerialisedRequest::new(req.to_bytes()?, id))
|
||||
}
|
||||
_ => Err(Error::UnsupportedMessage),
|
||||
}
|
||||
}
|
||||
|
||||
fn serialise_v4(&self, reply_to: Recipient) -> Result<SerialisedRequest, Error> {
|
||||
use v4::{
|
||||
registration::{ClientMac, FinalMessage, GatewayClient, InitMessage, IpPair},
|
||||
request::AuthenticatorRequest,
|
||||
topup::TopUpMessage,
|
||||
};
|
||||
match self {
|
||||
ClientMessage::Initial(init_message) => {
|
||||
let (req, id) = AuthenticatorRequest::new_initial_request(
|
||||
InitMessage {
|
||||
pub_key: init_message.pub_key(),
|
||||
},
|
||||
reply_to,
|
||||
);
|
||||
Ok(SerialisedRequest::new(req.to_bytes()?, id))
|
||||
}
|
||||
ClientMessage::Final(final_message) => {
|
||||
let (req, id) = AuthenticatorRequest::new_final_request(
|
||||
FinalMessage {
|
||||
gateway_client: GatewayClient {
|
||||
pub_key: final_message.gateway_client_pub_key(),
|
||||
private_ips: IpPair {
|
||||
ipv4: final_message
|
||||
.gateway_client_ipv4()
|
||||
.ok_or(Error::UnsupportedMessage)?,
|
||||
ipv6: final_message
|
||||
.gateway_client_ipv6()
|
||||
.ok_or(Error::UnsupportedMessage)?,
|
||||
},
|
||||
mac: ClientMac::new(final_message.gateway_client_mac()),
|
||||
},
|
||||
credential: final_message
|
||||
.credential()
|
||||
.and_then(|c| c.credential.into_zk_nym())
|
||||
.map(|c| *c),
|
||||
},
|
||||
reply_to,
|
||||
);
|
||||
Ok(SerialisedRequest::new(req.to_bytes()?, id))
|
||||
}
|
||||
ClientMessage::Query(query_message) => {
|
||||
let (req, id) =
|
||||
AuthenticatorRequest::new_query_request(query_message.pub_key(), reply_to);
|
||||
Ok(SerialisedRequest::new(req.to_bytes()?, id))
|
||||
}
|
||||
ClientMessage::TopUp(top_up_message) => {
|
||||
let (req, id) = AuthenticatorRequest::new_topup_request(
|
||||
TopUpMessage {
|
||||
pub_key: top_up_message.pub_key(),
|
||||
credential: top_up_message.credential(),
|
||||
},
|
||||
reply_to,
|
||||
);
|
||||
Ok(SerialisedRequest::new(req.to_bytes()?, id))
|
||||
}
|
||||
_ => Err(Error::UnsupportedMessage),
|
||||
}
|
||||
}
|
||||
|
||||
fn serialise_v5(&self) -> Result<SerialisedRequest, Error> {
|
||||
use v5::{
|
||||
registration::{ClientMac, FinalMessage, GatewayClient, InitMessage, IpPair},
|
||||
request::AuthenticatorRequest,
|
||||
topup::TopUpMessage,
|
||||
};
|
||||
match self {
|
||||
ClientMessage::Initial(init_message) => {
|
||||
let (req, id) = AuthenticatorRequest::new_initial_request(InitMessage {
|
||||
pub_key: init_message.pub_key(),
|
||||
});
|
||||
Ok(SerialisedRequest::new(req.to_bytes()?, id))
|
||||
}
|
||||
ClientMessage::Final(final_message) => {
|
||||
let (req, id) = AuthenticatorRequest::new_final_request(FinalMessage {
|
||||
gateway_client: GatewayClient {
|
||||
pub_key: final_message.gateway_client_pub_key(),
|
||||
private_ips: IpPair {
|
||||
ipv4: final_message
|
||||
.gateway_client_ipv4()
|
||||
.ok_or(Error::UnsupportedMessage)?,
|
||||
ipv6: final_message
|
||||
.gateway_client_ipv6()
|
||||
.ok_or(Error::UnsupportedMessage)?,
|
||||
},
|
||||
mac: ClientMac::new(final_message.gateway_client_mac()),
|
||||
},
|
||||
credential: final_message
|
||||
.credential()
|
||||
.and_then(|c| c.credential.into_zk_nym())
|
||||
.map(|c| *c),
|
||||
});
|
||||
Ok(SerialisedRequest::new(req.to_bytes()?, id))
|
||||
}
|
||||
ClientMessage::Query(query_message) => {
|
||||
let (req, id) = AuthenticatorRequest::new_query_request(query_message.pub_key());
|
||||
Ok(SerialisedRequest::new(req.to_bytes()?, id))
|
||||
}
|
||||
ClientMessage::TopUp(top_up_message) => {
|
||||
let (req, id) = AuthenticatorRequest::new_topup_request(TopUpMessage {
|
||||
pub_key: top_up_message.pub_key(),
|
||||
credential: top_up_message.credential(),
|
||||
});
|
||||
Ok(SerialisedRequest::new(req.to_bytes()?, id))
|
||||
}
|
||||
_ => Err(Error::UnsupportedMessage),
|
||||
}
|
||||
}
|
||||
|
||||
fn serialise_v6(&self) -> Result<SerialisedRequest, Error> {
|
||||
use v6::{
|
||||
registration::{ClientMac, FinalMessage, GatewayClient, InitMessage, IpPair},
|
||||
request::AuthenticatorRequest,
|
||||
topup::TopUpMessage,
|
||||
upgrade_mode_check::UpgradeModeCheckRequest,
|
||||
};
|
||||
match self {
|
||||
ClientMessage::Initial(init_message) => {
|
||||
let (req, id) = AuthenticatorRequest::new_initial_request(InitMessage {
|
||||
pub_key: init_message.pub_key(),
|
||||
});
|
||||
Ok(SerialisedRequest::new(req.to_bytes()?, id))
|
||||
}
|
||||
ClientMessage::Final(final_message) => {
|
||||
let (req, id) = AuthenticatorRequest::new_final_request(FinalMessage {
|
||||
gateway_client: GatewayClient {
|
||||
pub_key: final_message.gateway_client_pub_key(),
|
||||
private_ips: IpPair {
|
||||
ipv4: final_message
|
||||
.gateway_client_ipv4()
|
||||
.ok_or(Error::UnsupportedMessage)?,
|
||||
ipv6: final_message
|
||||
.gateway_client_ipv6()
|
||||
.ok_or(Error::UnsupportedMessage)?,
|
||||
},
|
||||
mac: ClientMac::new(final_message.gateway_client_mac()),
|
||||
},
|
||||
credential: final_message.credential(),
|
||||
});
|
||||
Ok(SerialisedRequest::new(req.to_bytes()?, id))
|
||||
}
|
||||
ClientMessage::Query(query_message) => {
|
||||
let (req, id) = AuthenticatorRequest::new_query_request(query_message.pub_key());
|
||||
Ok(SerialisedRequest::new(req.to_bytes()?, id))
|
||||
}
|
||||
ClientMessage::TopUp(top_up_message) => {
|
||||
let (req, id) = AuthenticatorRequest::new_topup_request(TopUpMessage {
|
||||
pub_key: top_up_message.pub_key(),
|
||||
credential: top_up_message.credential(),
|
||||
});
|
||||
Ok(SerialisedRequest::new(req.to_bytes()?, id))
|
||||
}
|
||||
ClientMessage::UpgradeModeCheck(upgrade_mode_check) => {
|
||||
// currently JWT is the only emergency credential option
|
||||
let Some(upgrade_mode_jwt) =
|
||||
upgrade_mode_check.upgrade_mode_global_attestation_jwt()
|
||||
else {
|
||||
return Err(Error::conversion(
|
||||
"no valid known upgrade mode check variants",
|
||||
));
|
||||
};
|
||||
let msg = UpgradeModeCheckRequest::UpgradeModeJwt {
|
||||
token: upgrade_mode_jwt,
|
||||
};
|
||||
|
||||
let (req, id) = AuthenticatorRequest::new_upgrade_mode_check_request(msg);
|
||||
Ok(SerialisedRequest::new(req.to_bytes()?, id))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ClientMessage {
|
||||
@@ -316,7 +27,7 @@ impl ClientMessage {
|
||||
match self {
|
||||
Self::Final(msg) => msg.credential().is_some(),
|
||||
Self::TopUp(_) => true,
|
||||
Self::Initial(_) | Self::Query(_) | Self::UpgradeModeCheck(_) => false,
|
||||
Self::Initial(_) | Self::Query(_) => false,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -326,18 +37,208 @@ impl ClientMessage {
|
||||
ClientMessage::Final(msg) => msg.version(),
|
||||
ClientMessage::Query(msg) => msg.version(),
|
||||
ClientMessage::TopUp(msg) => msg.version(),
|
||||
ClientMessage::UpgradeModeCheck(msg) => msg.version(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn bytes(&self, reply_to: Recipient) -> Result<SerialisedRequest, Error> {
|
||||
pub fn bytes(&self, reply_to: Recipient) -> Result<(Vec<u8>, u64), Error> {
|
||||
match self.version() {
|
||||
AuthenticatorVersion::V1 => self.serialise_v1(),
|
||||
AuthenticatorVersion::V2 => self.serialise_v2(reply_to),
|
||||
AuthenticatorVersion::V3 => self.serialise_v3(reply_to),
|
||||
AuthenticatorVersion::V4 => self.serialise_v4(reply_to),
|
||||
AuthenticatorVersion::V5 => self.serialise_v5(),
|
||||
AuthenticatorVersion::V6 => self.serialise_v6(),
|
||||
AuthenticatorVersion::V1 => Err(Error::UnsupportedVersion),
|
||||
AuthenticatorVersion::V2 => {
|
||||
use v2::{
|
||||
registration::{ClientMac, FinalMessage, GatewayClient, InitMessage},
|
||||
request::AuthenticatorRequest,
|
||||
};
|
||||
match self {
|
||||
ClientMessage::Initial(init_message) => {
|
||||
let (req, id) = AuthenticatorRequest::new_initial_request(
|
||||
InitMessage {
|
||||
pub_key: init_message.pub_key(),
|
||||
},
|
||||
reply_to,
|
||||
);
|
||||
Ok((req.to_bytes()?, id))
|
||||
}
|
||||
ClientMessage::Final(final_message) => {
|
||||
let (req, id) = AuthenticatorRequest::new_final_request(
|
||||
FinalMessage {
|
||||
gateway_client: GatewayClient {
|
||||
pub_key: final_message.gateway_client_pub_key(),
|
||||
private_ip: final_message
|
||||
.gateway_client_ipv4()
|
||||
.ok_or(Error::UnsupportedMessage)?
|
||||
.into(),
|
||||
mac: ClientMac::new(final_message.gateway_client_mac()),
|
||||
},
|
||||
credential: final_message.credential(),
|
||||
},
|
||||
reply_to,
|
||||
);
|
||||
Ok((req.to_bytes()?, id))
|
||||
}
|
||||
ClientMessage::Query(query_message) => {
|
||||
let (req, id) = AuthenticatorRequest::new_query_request(
|
||||
query_message.pub_key(),
|
||||
reply_to,
|
||||
);
|
||||
Ok((req.to_bytes()?, id))
|
||||
}
|
||||
_ => Err(Error::UnsupportedMessage),
|
||||
}
|
||||
}
|
||||
AuthenticatorVersion::V3 => {
|
||||
use v3::{
|
||||
registration::{ClientMac, FinalMessage, GatewayClient, InitMessage},
|
||||
request::AuthenticatorRequest,
|
||||
topup::TopUpMessage,
|
||||
};
|
||||
match self {
|
||||
ClientMessage::Initial(init_message) => {
|
||||
let (req, id) = AuthenticatorRequest::new_initial_request(
|
||||
InitMessage {
|
||||
pub_key: init_message.pub_key(),
|
||||
},
|
||||
reply_to,
|
||||
);
|
||||
Ok((req.to_bytes()?, id))
|
||||
}
|
||||
ClientMessage::Final(final_message) => {
|
||||
let (req, id) = AuthenticatorRequest::new_final_request(
|
||||
FinalMessage {
|
||||
gateway_client: GatewayClient {
|
||||
pub_key: final_message.gateway_client_pub_key(),
|
||||
private_ip: final_message
|
||||
.gateway_client_ipv4()
|
||||
.ok_or(Error::UnsupportedMessage)?
|
||||
.into(),
|
||||
mac: ClientMac::new(final_message.gateway_client_mac()),
|
||||
},
|
||||
credential: final_message.credential(),
|
||||
},
|
||||
reply_to,
|
||||
);
|
||||
Ok((req.to_bytes()?, id))
|
||||
}
|
||||
ClientMessage::Query(query_message) => {
|
||||
let (req, id) = AuthenticatorRequest::new_query_request(
|
||||
query_message.pub_key(),
|
||||
reply_to,
|
||||
);
|
||||
Ok((req.to_bytes()?, id))
|
||||
}
|
||||
ClientMessage::TopUp(top_up_message) => {
|
||||
let (req, id) = AuthenticatorRequest::new_topup_request(
|
||||
TopUpMessage {
|
||||
pub_key: top_up_message.pub_key(),
|
||||
credential: top_up_message.credential(),
|
||||
},
|
||||
reply_to,
|
||||
);
|
||||
Ok((req.to_bytes()?, id))
|
||||
}
|
||||
}
|
||||
}
|
||||
AuthenticatorVersion::V4 => {
|
||||
use v4::{
|
||||
registration::{ClientMac, FinalMessage, GatewayClient, InitMessage},
|
||||
request::AuthenticatorRequest,
|
||||
topup::TopUpMessage,
|
||||
};
|
||||
match self {
|
||||
ClientMessage::Initial(init_message) => {
|
||||
let (req, id) = AuthenticatorRequest::new_initial_request(
|
||||
InitMessage {
|
||||
pub_key: init_message.pub_key(),
|
||||
},
|
||||
reply_to,
|
||||
);
|
||||
Ok((req.to_bytes()?, id))
|
||||
}
|
||||
ClientMessage::Final(final_message) => {
|
||||
let (req, id) = AuthenticatorRequest::new_final_request(
|
||||
FinalMessage {
|
||||
gateway_client: GatewayClient {
|
||||
pub_key: final_message.gateway_client_pub_key(),
|
||||
private_ips: IpPair {
|
||||
ipv4: final_message
|
||||
.gateway_client_ipv4()
|
||||
.ok_or(Error::UnsupportedMessage)?,
|
||||
ipv6: final_message
|
||||
.gateway_client_ipv6()
|
||||
.ok_or(Error::UnsupportedMessage)?,
|
||||
}
|
||||
.into(),
|
||||
mac: ClientMac::new(final_message.gateway_client_mac()),
|
||||
},
|
||||
credential: final_message.credential(),
|
||||
},
|
||||
reply_to,
|
||||
);
|
||||
Ok((req.to_bytes()?, id))
|
||||
}
|
||||
ClientMessage::Query(query_message) => {
|
||||
let (req, id) = AuthenticatorRequest::new_query_request(
|
||||
query_message.pub_key(),
|
||||
reply_to,
|
||||
);
|
||||
Ok((req.to_bytes()?, id))
|
||||
}
|
||||
ClientMessage::TopUp(top_up_message) => {
|
||||
let (req, id) = AuthenticatorRequest::new_topup_request(
|
||||
TopUpMessage {
|
||||
pub_key: top_up_message.pub_key(),
|
||||
credential: top_up_message.credential(),
|
||||
},
|
||||
reply_to,
|
||||
);
|
||||
Ok((req.to_bytes()?, id))
|
||||
}
|
||||
}
|
||||
}
|
||||
AuthenticatorVersion::V5 => {
|
||||
use v5::{
|
||||
registration::{ClientMac, FinalMessage, GatewayClient, InitMessage},
|
||||
request::AuthenticatorRequest,
|
||||
topup::TopUpMessage,
|
||||
};
|
||||
match self {
|
||||
ClientMessage::Initial(init_message) => {
|
||||
let (req, id) = AuthenticatorRequest::new_initial_request(InitMessage {
|
||||
pub_key: init_message.pub_key(),
|
||||
});
|
||||
Ok((req.to_bytes()?, id))
|
||||
}
|
||||
ClientMessage::Final(final_message) => {
|
||||
let (req, id) = AuthenticatorRequest::new_final_request(FinalMessage {
|
||||
gateway_client: GatewayClient {
|
||||
pub_key: final_message.gateway_client_pub_key(),
|
||||
private_ips: IpPair {
|
||||
ipv4: final_message
|
||||
.gateway_client_ipv4()
|
||||
.ok_or(Error::UnsupportedMessage)?,
|
||||
ipv6: final_message
|
||||
.gateway_client_ipv6()
|
||||
.ok_or(Error::UnsupportedMessage)?,
|
||||
},
|
||||
mac: ClientMac::new(final_message.gateway_client_mac()),
|
||||
},
|
||||
credential: final_message.credential(),
|
||||
});
|
||||
Ok((req.to_bytes()?, id))
|
||||
}
|
||||
ClientMessage::Query(query_message) => {
|
||||
let (req, id) =
|
||||
AuthenticatorRequest::new_query_request(query_message.pub_key());
|
||||
Ok((req.to_bytes()?, id))
|
||||
}
|
||||
ClientMessage::TopUp(top_up_message) => {
|
||||
let (req, id) = AuthenticatorRequest::new_topup_request(TopUpMessage {
|
||||
pub_key: top_up_message.pub_key(),
|
||||
credential: top_up_message.credential(),
|
||||
});
|
||||
Ok((req.to_bytes()?, id))
|
||||
}
|
||||
}
|
||||
}
|
||||
AuthenticatorVersion::UNKNOWN => Err(Error::UnknownVersion),
|
||||
}
|
||||
}
|
||||
@@ -346,7 +247,7 @@ impl ClientMessage {
|
||||
use AuthenticatorVersion::*;
|
||||
match self.version() {
|
||||
V1 | V2 | V3 | V4 => false,
|
||||
V5 | V6 => true,
|
||||
V5 => true,
|
||||
UNKNOWN => true,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use std::fmt::Display;
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
@@ -38,13 +37,3 @@ pub enum Error {
|
||||
#[error(transparent)]
|
||||
Bincode(#[from] bincode::Error),
|
||||
}
|
||||
|
||||
impl Error {
|
||||
pub fn conversion(msg: impl Into<String>) -> Self {
|
||||
Error::Conversion(msg.into())
|
||||
}
|
||||
|
||||
pub fn conversion_display(msg: impl Display) -> Self {
|
||||
Error::Conversion(msg.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
pub mod client_message;
|
||||
pub mod models;
|
||||
pub mod request;
|
||||
pub mod response;
|
||||
pub mod traits;
|
||||
@@ -11,14 +10,13 @@ pub mod v2;
|
||||
pub mod v3;
|
||||
pub mod v4;
|
||||
pub mod v5;
|
||||
pub mod v6;
|
||||
|
||||
mod error;
|
||||
mod util;
|
||||
mod version;
|
||||
|
||||
pub use error::Error;
|
||||
pub use v6 as latest;
|
||||
pub use v5 as latest;
|
||||
pub use version::AuthenticatorVersion;
|
||||
|
||||
pub const CURRENT_VERSION: u8 = latest::VERSION;
|
||||
|
||||
@@ -1,58 +0,0 @@
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use nym_credentials_interface::{
|
||||
BandwidthCredential, CredentialSpendingData, TicketType, UnknownTicketType,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq)]
|
||||
pub enum CurrentUpgradeModeStatus {
|
||||
Enabled,
|
||||
Disabled,
|
||||
// everything pre-v6
|
||||
Unknown,
|
||||
}
|
||||
|
||||
impl CurrentUpgradeModeStatus {
|
||||
pub fn is_enabled(&self) -> bool {
|
||||
matches!(self, CurrentUpgradeModeStatus::Enabled)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<bool> for CurrentUpgradeModeStatus {
|
||||
fn from(value: bool) -> Self {
|
||||
if value {
|
||||
CurrentUpgradeModeStatus::Enabled
|
||||
} else {
|
||||
CurrentUpgradeModeStatus::Disabled
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<CurrentUpgradeModeStatus> for Option<bool> {
|
||||
fn from(value: CurrentUpgradeModeStatus) -> Self {
|
||||
match value {
|
||||
CurrentUpgradeModeStatus::Enabled => Some(true),
|
||||
CurrentUpgradeModeStatus::Disabled => Some(false),
|
||||
CurrentUpgradeModeStatus::Unknown => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
|
||||
pub struct BandwidthClaim {
|
||||
pub credential: BandwidthCredential,
|
||||
pub kind: TicketType,
|
||||
}
|
||||
|
||||
impl TryFrom<CredentialSpendingData> for BandwidthClaim {
|
||||
type Error = UnknownTicketType;
|
||||
|
||||
fn try_from(credential: CredentialSpendingData) -> Result<Self, Self::Error> {
|
||||
Ok(BandwidthClaim {
|
||||
kind: TicketType::try_from_encoded(credential.payment.t_type)?,
|
||||
credential: BandwidthCredential::from(credential),
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -4,10 +4,8 @@
|
||||
use nym_service_provider_requests_common::{Protocol, ServiceProviderType};
|
||||
use nym_sphinx::addressing::Recipient;
|
||||
|
||||
use crate::traits::{
|
||||
FinalMessage, InitMessage, QueryBandwidthMessage, TopUpMessage, UpgradeModeMessage,
|
||||
};
|
||||
use crate::{v1, v2, v3, v4, v5, v6};
|
||||
use crate::traits::{FinalMessage, InitMessage, QueryBandwidthMessage, TopUpMessage};
|
||||
use crate::{v1, v2, v3, v4, v5};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum AuthenticatorRequest {
|
||||
@@ -35,11 +33,6 @@ pub enum AuthenticatorRequest {
|
||||
reply_to: Option<Recipient>,
|
||||
request_id: u64,
|
||||
},
|
||||
CheckUpgradeMode {
|
||||
msg: Box<dyn UpgradeModeMessage + Send + Sync + 'static>,
|
||||
protocol: Protocol,
|
||||
request_id: u64,
|
||||
},
|
||||
}
|
||||
|
||||
impl From<v1::request::AuthenticatorRequest> for AuthenticatorRequest {
|
||||
@@ -209,45 +202,3 @@ impl From<v5::request::AuthenticatorRequest> for AuthenticatorRequest {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<v6::request::AuthenticatorRequest> for AuthenticatorRequest {
|
||||
fn from(value: v6::request::AuthenticatorRequest) -> Self {
|
||||
match value.data {
|
||||
v6::request::AuthenticatorRequestData::Initial(init_message) => Self::Initial {
|
||||
msg: Box::new(init_message),
|
||||
protocol: value.protocol,
|
||||
reply_to: None,
|
||||
request_id: value.request_id,
|
||||
},
|
||||
v6::request::AuthenticatorRequestData::Final(final_message) => Self::Final {
|
||||
msg: final_message,
|
||||
protocol: value.protocol,
|
||||
reply_to: None,
|
||||
request_id: value.request_id,
|
||||
},
|
||||
v6::request::AuthenticatorRequestData::QueryBandwidth(peer_public_key) => {
|
||||
Self::QueryBandwidth {
|
||||
msg: Box::new(peer_public_key),
|
||||
protocol: value.protocol,
|
||||
reply_to: None,
|
||||
request_id: value.request_id,
|
||||
}
|
||||
}
|
||||
v6::request::AuthenticatorRequestData::TopUpBandwidth(top_up_message) => {
|
||||
Self::TopUpBandwidth {
|
||||
msg: top_up_message,
|
||||
protocol: value.protocol,
|
||||
reply_to: None,
|
||||
request_id: value.request_id,
|
||||
}
|
||||
}
|
||||
v6::request::AuthenticatorRequestData::CheckUpgradeMode(upgrade_mode_check_msg) => {
|
||||
Self::CheckUpgradeMode {
|
||||
msg: Box::new(upgrade_mode_check_msg),
|
||||
protocol: value.protocol,
|
||||
request_id: value.request_id,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::models::CurrentUpgradeModeStatus;
|
||||
use crate::traits::{
|
||||
Id, PendingRegistrationResponse, RegisteredResponse, RemainingBandwidthResponse,
|
||||
TopUpBandwidthResponse, UpgradeModeStatus,
|
||||
TopUpBandwidthResponse,
|
||||
};
|
||||
use crate::{v2, v3, v4, v5, v6};
|
||||
use crate::{v2, v3, v4, v5};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum AuthenticatorResponse {
|
||||
@@ -14,29 +13,6 @@ pub enum AuthenticatorResponse {
|
||||
Registered(Box<dyn RegisteredResponse + Send + Sync + 'static>),
|
||||
RemainingBandwidth(Box<dyn RemainingBandwidthResponse + Send + Sync + 'static>),
|
||||
TopUpBandwidth(Box<dyn TopUpBandwidthResponse + Send + Sync + 'static>),
|
||||
UpgradeMode(Box<dyn UpgradeModeStatus + Send + Sync + 'static>),
|
||||
}
|
||||
|
||||
impl UpgradeModeStatus for AuthenticatorResponse {
|
||||
fn upgrade_mode_status(&self) -> CurrentUpgradeModeStatus {
|
||||
match self {
|
||||
AuthenticatorResponse::PendingRegistration(pending_registration_response) => {
|
||||
pending_registration_response.upgrade_mode_status()
|
||||
}
|
||||
AuthenticatorResponse::Registered(registered_response) => {
|
||||
registered_response.upgrade_mode_status()
|
||||
}
|
||||
AuthenticatorResponse::RemainingBandwidth(remaining_bandwidth_response) => {
|
||||
remaining_bandwidth_response.upgrade_mode_status()
|
||||
}
|
||||
AuthenticatorResponse::TopUpBandwidth(top_up_bandwidth_response) => {
|
||||
top_up_bandwidth_response.upgrade_mode_status()
|
||||
}
|
||||
AuthenticatorResponse::UpgradeMode(upgrade_mode_response) => {
|
||||
upgrade_mode_response.upgrade_mode_status()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Id for AuthenticatorResponse {
|
||||
@@ -52,7 +28,6 @@ impl Id for AuthenticatorResponse {
|
||||
AuthenticatorResponse::TopUpBandwidth(top_up_bandwidth_response) => {
|
||||
top_up_bandwidth_response.id()
|
||||
}
|
||||
AuthenticatorResponse::UpgradeMode(upgrade_mode_response) => upgrade_mode_response.id(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -129,25 +104,3 @@ impl From<v5::response::AuthenticatorResponse> for AuthenticatorResponse {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<v6::response::AuthenticatorResponse> for AuthenticatorResponse {
|
||||
fn from(value: v6::response::AuthenticatorResponse) -> Self {
|
||||
match value.data {
|
||||
v6::response::AuthenticatorResponseData::PendingRegistration(
|
||||
pending_registration_response,
|
||||
) => Self::PendingRegistration(Box::new(pending_registration_response)),
|
||||
v6::response::AuthenticatorResponseData::Registered(registered_response) => {
|
||||
Self::Registered(Box::new(registered_response))
|
||||
}
|
||||
v6::response::AuthenticatorResponseData::RemainingBandwidth(
|
||||
remaining_bandwidth_response,
|
||||
) => Self::RemainingBandwidth(Box::new(remaining_bandwidth_response)),
|
||||
v6::response::AuthenticatorResponseData::TopUpBandwidth(top_up_bandwidth_response) => {
|
||||
Self::TopUpBandwidth(Box::new(top_up_bandwidth_response))
|
||||
}
|
||||
v6::response::AuthenticatorResponseData::UpgradeMode(upgrade_mode_check_response) => {
|
||||
Self::UpgradeMode(Box::new(upgrade_mode_check_response))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::latest::registration::IpPair;
|
||||
use crate::models::{BandwidthClaim, CurrentUpgradeModeStatus};
|
||||
use crate::{AuthenticatorVersion, Error, v1, v2, v3, v4, v5, v6};
|
||||
use nym_credentials_interface::CredentialSpendingData;
|
||||
use nym_crypto::asymmetric::x25519;
|
||||
use nym_wireguard_types::PeerPublicKey;
|
||||
use std::fmt;
|
||||
use std::net::{Ipv4Addr, Ipv6Addr};
|
||||
use tracing::error;
|
||||
|
||||
use nym_credentials_interface::CredentialSpendingData;
|
||||
use nym_crypto::asymmetric::x25519::PrivateKey;
|
||||
use nym_wireguard_types::PeerPublicKey;
|
||||
|
||||
use crate::latest::registration::IpPair;
|
||||
use crate::{AuthenticatorVersion, Error, v1, v2, v3, v4, v5};
|
||||
|
||||
pub trait Versionable {
|
||||
fn version(&self) -> AuthenticatorVersion;
|
||||
@@ -51,12 +51,6 @@ impl Versionable for v5::registration::InitMessage {
|
||||
}
|
||||
}
|
||||
|
||||
impl Versionable for v6::registration::InitMessage {
|
||||
fn version(&self) -> AuthenticatorVersion {
|
||||
AuthenticatorVersion::V6
|
||||
}
|
||||
}
|
||||
|
||||
impl Versionable for v2::registration::FinalMessage {
|
||||
fn version(&self) -> AuthenticatorVersion {
|
||||
AuthenticatorVersion::V2
|
||||
@@ -81,12 +75,6 @@ impl Versionable for v5::registration::FinalMessage {
|
||||
}
|
||||
}
|
||||
|
||||
impl Versionable for v6::registration::FinalMessage {
|
||||
fn version(&self) -> AuthenticatorVersion {
|
||||
AuthenticatorVersion::V6
|
||||
}
|
||||
}
|
||||
|
||||
impl Versionable for PeerPublicKey {
|
||||
fn version(&self) -> AuthenticatorVersion {
|
||||
AuthenticatorVersion::V3
|
||||
@@ -110,158 +98,6 @@ impl Versionable for v5::topup::TopUpMessage {
|
||||
AuthenticatorVersion::V5
|
||||
}
|
||||
}
|
||||
impl Versionable for v6::topup::TopUpMessage {
|
||||
fn version(&self) -> AuthenticatorVersion {
|
||||
AuthenticatorVersion::V6
|
||||
}
|
||||
}
|
||||
|
||||
impl Versionable for v6::upgrade_mode_check::UpgradeModeCheckRequest {
|
||||
fn version(&self) -> AuthenticatorVersion {
|
||||
AuthenticatorVersion::V6
|
||||
}
|
||||
}
|
||||
|
||||
pub trait UpgradeModeStatus: Id + fmt::Debug {
|
||||
fn upgrade_mode_status(&self) -> CurrentUpgradeModeStatus;
|
||||
}
|
||||
|
||||
impl UpgradeModeStatus for v1::response::PendingRegistrationResponse {
|
||||
fn upgrade_mode_status(&self) -> CurrentUpgradeModeStatus {
|
||||
CurrentUpgradeModeStatus::Unknown
|
||||
}
|
||||
}
|
||||
|
||||
impl UpgradeModeStatus for v1::response::RegisteredResponse {
|
||||
fn upgrade_mode_status(&self) -> CurrentUpgradeModeStatus {
|
||||
CurrentUpgradeModeStatus::Unknown
|
||||
}
|
||||
}
|
||||
|
||||
impl UpgradeModeStatus for v1::response::RemainingBandwidthResponse {
|
||||
fn upgrade_mode_status(&self) -> CurrentUpgradeModeStatus {
|
||||
CurrentUpgradeModeStatus::Unknown
|
||||
}
|
||||
}
|
||||
|
||||
impl UpgradeModeStatus for v2::response::PendingRegistrationResponse {
|
||||
fn upgrade_mode_status(&self) -> CurrentUpgradeModeStatus {
|
||||
CurrentUpgradeModeStatus::Unknown
|
||||
}
|
||||
}
|
||||
|
||||
impl UpgradeModeStatus for v2::response::RegisteredResponse {
|
||||
fn upgrade_mode_status(&self) -> CurrentUpgradeModeStatus {
|
||||
CurrentUpgradeModeStatus::Unknown
|
||||
}
|
||||
}
|
||||
|
||||
impl UpgradeModeStatus for v2::response::RemainingBandwidthResponse {
|
||||
fn upgrade_mode_status(&self) -> CurrentUpgradeModeStatus {
|
||||
CurrentUpgradeModeStatus::Unknown
|
||||
}
|
||||
}
|
||||
impl UpgradeModeStatus for v3::response::PendingRegistrationResponse {
|
||||
fn upgrade_mode_status(&self) -> CurrentUpgradeModeStatus {
|
||||
CurrentUpgradeModeStatus::Unknown
|
||||
}
|
||||
}
|
||||
|
||||
impl UpgradeModeStatus for v3::response::RegisteredResponse {
|
||||
fn upgrade_mode_status(&self) -> CurrentUpgradeModeStatus {
|
||||
CurrentUpgradeModeStatus::Unknown
|
||||
}
|
||||
}
|
||||
|
||||
impl UpgradeModeStatus for v3::response::RemainingBandwidthResponse {
|
||||
fn upgrade_mode_status(&self) -> CurrentUpgradeModeStatus {
|
||||
CurrentUpgradeModeStatus::Unknown
|
||||
}
|
||||
}
|
||||
|
||||
impl UpgradeModeStatus for v3::response::TopUpBandwidthResponse {
|
||||
fn upgrade_mode_status(&self) -> CurrentUpgradeModeStatus {
|
||||
CurrentUpgradeModeStatus::Unknown
|
||||
}
|
||||
}
|
||||
|
||||
impl UpgradeModeStatus for v4::response::PendingRegistrationResponse {
|
||||
fn upgrade_mode_status(&self) -> CurrentUpgradeModeStatus {
|
||||
CurrentUpgradeModeStatus::Unknown
|
||||
}
|
||||
}
|
||||
|
||||
impl UpgradeModeStatus for v4::response::RegisteredResponse {
|
||||
fn upgrade_mode_status(&self) -> CurrentUpgradeModeStatus {
|
||||
CurrentUpgradeModeStatus::Unknown
|
||||
}
|
||||
}
|
||||
|
||||
impl UpgradeModeStatus for v4::response::RemainingBandwidthResponse {
|
||||
fn upgrade_mode_status(&self) -> CurrentUpgradeModeStatus {
|
||||
CurrentUpgradeModeStatus::Unknown
|
||||
}
|
||||
}
|
||||
|
||||
impl UpgradeModeStatus for v4::response::TopUpBandwidthResponse {
|
||||
fn upgrade_mode_status(&self) -> CurrentUpgradeModeStatus {
|
||||
CurrentUpgradeModeStatus::Unknown
|
||||
}
|
||||
}
|
||||
|
||||
impl UpgradeModeStatus for v5::response::PendingRegistrationResponse {
|
||||
fn upgrade_mode_status(&self) -> CurrentUpgradeModeStatus {
|
||||
CurrentUpgradeModeStatus::Unknown
|
||||
}
|
||||
}
|
||||
|
||||
impl UpgradeModeStatus for v5::response::RegisteredResponse {
|
||||
fn upgrade_mode_status(&self) -> CurrentUpgradeModeStatus {
|
||||
CurrentUpgradeModeStatus::Unknown
|
||||
}
|
||||
}
|
||||
|
||||
impl UpgradeModeStatus for v5::response::RemainingBandwidthResponse {
|
||||
fn upgrade_mode_status(&self) -> CurrentUpgradeModeStatus {
|
||||
CurrentUpgradeModeStatus::Unknown
|
||||
}
|
||||
}
|
||||
|
||||
impl UpgradeModeStatus for v5::response::TopUpBandwidthResponse {
|
||||
fn upgrade_mode_status(&self) -> CurrentUpgradeModeStatus {
|
||||
CurrentUpgradeModeStatus::Unknown
|
||||
}
|
||||
}
|
||||
|
||||
impl UpgradeModeStatus for v6::response::PendingRegistrationResponse {
|
||||
fn upgrade_mode_status(&self) -> CurrentUpgradeModeStatus {
|
||||
self.upgrade_mode_enabled.into()
|
||||
}
|
||||
}
|
||||
|
||||
impl UpgradeModeStatus for v6::response::RegisteredResponse {
|
||||
fn upgrade_mode_status(&self) -> CurrentUpgradeModeStatus {
|
||||
self.upgrade_mode_enabled.into()
|
||||
}
|
||||
}
|
||||
|
||||
impl UpgradeModeStatus for v6::response::RemainingBandwidthResponse {
|
||||
fn upgrade_mode_status(&self) -> CurrentUpgradeModeStatus {
|
||||
self.upgrade_mode_enabled.into()
|
||||
}
|
||||
}
|
||||
|
||||
impl UpgradeModeStatus for v6::response::TopUpBandwidthResponse {
|
||||
fn upgrade_mode_status(&self) -> CurrentUpgradeModeStatus {
|
||||
self.upgrade_mode_enabled.into()
|
||||
}
|
||||
}
|
||||
|
||||
impl UpgradeModeStatus for v6::response::UpgradeModeResponse {
|
||||
fn upgrade_mode_status(&self) -> CurrentUpgradeModeStatus {
|
||||
self.upgrade_mode_enabled.into()
|
||||
}
|
||||
}
|
||||
|
||||
pub trait InitMessage: Versionable + fmt::Debug {
|
||||
fn pub_key(&self) -> PeerPublicKey;
|
||||
@@ -297,20 +133,14 @@ impl InitMessage for v5::registration::InitMessage {
|
||||
}
|
||||
}
|
||||
|
||||
impl InitMessage for v6::registration::InitMessage {
|
||||
fn pub_key(&self) -> PeerPublicKey {
|
||||
self.pub_key
|
||||
}
|
||||
}
|
||||
|
||||
pub trait FinalMessage: Versionable + fmt::Debug {
|
||||
fn gateway_client_pub_key(&self) -> PeerPublicKey;
|
||||
fn verify(&self, private_key: &x25519::PrivateKey, nonce: u64) -> Result<(), Error>;
|
||||
fn verify(&self, private_key: &PrivateKey, nonce: u64) -> Result<(), Error>;
|
||||
fn private_ips(&self) -> IpPair;
|
||||
fn gateway_client_ipv4(&self) -> Option<Ipv4Addr>;
|
||||
fn gateway_client_ipv6(&self) -> Option<Ipv6Addr>;
|
||||
fn gateway_client_mac(&self) -> Vec<u8>;
|
||||
fn credential(&self) -> Option<BandwidthClaim>;
|
||||
fn credential(&self) -> Option<CredentialSpendingData>;
|
||||
}
|
||||
|
||||
impl FinalMessage for v1::GatewayClient {
|
||||
@@ -318,7 +148,7 @@ impl FinalMessage for v1::GatewayClient {
|
||||
self.pub_key
|
||||
}
|
||||
|
||||
fn verify(&self, private_key: &x25519::PrivateKey, nonce: u64) -> Result<(), Error> {
|
||||
fn verify(&self, private_key: &PrivateKey, nonce: u64) -> Result<(), Error> {
|
||||
self.verify(private_key, nonce)
|
||||
}
|
||||
|
||||
@@ -341,7 +171,7 @@ impl FinalMessage for v1::GatewayClient {
|
||||
self.mac.to_vec()
|
||||
}
|
||||
|
||||
fn credential(&self) -> Option<BandwidthClaim> {
|
||||
fn credential(&self) -> Option<CredentialSpendingData> {
|
||||
None
|
||||
}
|
||||
}
|
||||
@@ -351,7 +181,7 @@ impl FinalMessage for v2::registration::FinalMessage {
|
||||
self.gateway_client.pub_key
|
||||
}
|
||||
|
||||
fn verify(&self, private_key: &x25519::PrivateKey, nonce: u64) -> Result<(), Error> {
|
||||
fn verify(&self, private_key: &PrivateKey, nonce: u64) -> Result<(), Error> {
|
||||
self.gateway_client.verify(private_key, nonce)
|
||||
}
|
||||
|
||||
@@ -374,12 +204,8 @@ impl FinalMessage for v2::registration::FinalMessage {
|
||||
self.gateway_client.mac.to_vec()
|
||||
}
|
||||
|
||||
fn credential(&self) -> Option<BandwidthClaim> {
|
||||
self.credential.clone().and_then(|c| {
|
||||
c.try_into()
|
||||
.inspect_err(|err| error!("credential conversion error: {err}"))
|
||||
.ok()
|
||||
})
|
||||
fn credential(&self) -> Option<CredentialSpendingData> {
|
||||
self.credential.clone()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -388,7 +214,7 @@ impl FinalMessage for v3::registration::FinalMessage {
|
||||
self.gateway_client.pub_key
|
||||
}
|
||||
|
||||
fn verify(&self, private_key: &x25519::PrivateKey, nonce: u64) -> Result<(), Error> {
|
||||
fn verify(&self, private_key: &PrivateKey, nonce: u64) -> Result<(), Error> {
|
||||
self.gateway_client.verify(private_key, nonce)
|
||||
}
|
||||
|
||||
@@ -411,12 +237,8 @@ impl FinalMessage for v3::registration::FinalMessage {
|
||||
self.gateway_client.mac.to_vec()
|
||||
}
|
||||
|
||||
fn credential(&self) -> Option<BandwidthClaim> {
|
||||
self.credential.clone().and_then(|c| {
|
||||
c.try_into()
|
||||
.inspect_err(|err| error!("credential conversion error: {err}"))
|
||||
.ok()
|
||||
})
|
||||
fn credential(&self) -> Option<CredentialSpendingData> {
|
||||
self.credential.clone()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -425,42 +247,7 @@ impl FinalMessage for v4::registration::FinalMessage {
|
||||
self.gateway_client.pub_key
|
||||
}
|
||||
|
||||
fn verify(&self, private_key: &x25519::PrivateKey, nonce: u64) -> Result<(), Error> {
|
||||
self.gateway_client.verify(private_key, nonce)
|
||||
}
|
||||
|
||||
fn private_ips(&self) -> IpPair {
|
||||
// v4 -> v5 -> v6
|
||||
v5::registration::IpPair::from(self.gateway_client.private_ips).into()
|
||||
}
|
||||
|
||||
fn gateway_client_ipv4(&self) -> Option<Ipv4Addr> {
|
||||
Some(self.gateway_client.private_ips.ipv4)
|
||||
}
|
||||
|
||||
fn gateway_client_ipv6(&self) -> Option<Ipv6Addr> {
|
||||
Some(self.gateway_client.private_ips.ipv6)
|
||||
}
|
||||
|
||||
fn gateway_client_mac(&self) -> Vec<u8> {
|
||||
self.gateway_client.mac.to_vec()
|
||||
}
|
||||
|
||||
fn credential(&self) -> Option<BandwidthClaim> {
|
||||
self.credential.clone().and_then(|c| {
|
||||
c.try_into()
|
||||
.inspect_err(|err| error!("credential conversion error: {err}"))
|
||||
.ok()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl FinalMessage for v5::registration::FinalMessage {
|
||||
fn gateway_client_pub_key(&self) -> PeerPublicKey {
|
||||
self.gateway_client.pub_key
|
||||
}
|
||||
|
||||
fn verify(&self, private_key: &x25519::PrivateKey, nonce: u64) -> Result<(), Error> {
|
||||
fn verify(&self, private_key: &PrivateKey, nonce: u64) -> Result<(), Error> {
|
||||
self.gateway_client.verify(private_key, nonce)
|
||||
}
|
||||
|
||||
@@ -480,21 +267,17 @@ impl FinalMessage for v5::registration::FinalMessage {
|
||||
self.gateway_client.mac.to_vec()
|
||||
}
|
||||
|
||||
fn credential(&self) -> Option<BandwidthClaim> {
|
||||
self.credential.clone().and_then(|c| {
|
||||
c.try_into()
|
||||
.inspect_err(|err| error!("credential conversion error: {err}"))
|
||||
.ok()
|
||||
})
|
||||
fn credential(&self) -> Option<CredentialSpendingData> {
|
||||
self.credential.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl FinalMessage for v6::registration::FinalMessage {
|
||||
impl FinalMessage for v5::registration::FinalMessage {
|
||||
fn gateway_client_pub_key(&self) -> PeerPublicKey {
|
||||
self.gateway_client.pub_key
|
||||
}
|
||||
|
||||
fn verify(&self, private_key: &x25519::PrivateKey, nonce: u64) -> Result<(), Error> {
|
||||
fn verify(&self, private_key: &PrivateKey, nonce: u64) -> Result<(), Error> {
|
||||
self.gateway_client.verify(private_key, nonce)
|
||||
}
|
||||
|
||||
@@ -514,7 +297,7 @@ impl FinalMessage for v6::registration::FinalMessage {
|
||||
self.gateway_client.mac.to_vec()
|
||||
}
|
||||
|
||||
fn credential(&self) -> Option<BandwidthClaim> {
|
||||
fn credential(&self) -> Option<CredentialSpendingData> {
|
||||
self.credential.clone()
|
||||
}
|
||||
}
|
||||
@@ -564,42 +347,10 @@ impl TopUpMessage for v5::topup::TopUpMessage {
|
||||
}
|
||||
}
|
||||
|
||||
impl TopUpMessage for v6::topup::TopUpMessage {
|
||||
fn pub_key(&self) -> PeerPublicKey {
|
||||
self.pub_key
|
||||
}
|
||||
|
||||
fn credential(&self) -> CredentialSpendingData {
|
||||
self.credential.clone()
|
||||
}
|
||||
}
|
||||
|
||||
pub trait UpgradeModeMessage: Versionable + fmt::Debug {
|
||||
// the idea is to expose different types of emergency credentials here,
|
||||
// like upgrade mode JWT, emergency threshold credential issued by signers, etc.
|
||||
fn upgrade_mode_global_attestation_jwt(&self) -> Option<String>;
|
||||
}
|
||||
|
||||
impl UpgradeModeMessage for v6::upgrade_mode_check::UpgradeModeCheckRequest {
|
||||
fn upgrade_mode_global_attestation_jwt(&self) -> Option<String> {
|
||||
use v6::upgrade_mode_check::UpgradeModeCheckRequest;
|
||||
|
||||
match self {
|
||||
UpgradeModeCheckRequest::UpgradeModeJwt { token } => Some(token.clone()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait Id {
|
||||
fn id(&self) -> u64;
|
||||
}
|
||||
|
||||
impl Id for v1::response::PendingRegistrationResponse {
|
||||
fn id(&self) -> u64 {
|
||||
self.request_id
|
||||
}
|
||||
}
|
||||
|
||||
impl Id for v2::response::PendingRegistrationResponse {
|
||||
fn id(&self) -> u64 {
|
||||
self.request_id
|
||||
@@ -624,18 +375,6 @@ impl Id for v5::response::PendingRegistrationResponse {
|
||||
}
|
||||
}
|
||||
|
||||
impl Id for v6::response::PendingRegistrationResponse {
|
||||
fn id(&self) -> u64 {
|
||||
self.request_id
|
||||
}
|
||||
}
|
||||
|
||||
impl Id for v1::response::RegisteredResponse {
|
||||
fn id(&self) -> u64 {
|
||||
self.request_id
|
||||
}
|
||||
}
|
||||
|
||||
impl Id for v2::response::RegisteredResponse {
|
||||
fn id(&self) -> u64 {
|
||||
self.request_id
|
||||
@@ -660,18 +399,6 @@ impl Id for v5::response::RegisteredResponse {
|
||||
}
|
||||
}
|
||||
|
||||
impl Id for v6::response::RegisteredResponse {
|
||||
fn id(&self) -> u64 {
|
||||
self.request_id
|
||||
}
|
||||
}
|
||||
|
||||
impl Id for v1::response::RemainingBandwidthResponse {
|
||||
fn id(&self) -> u64 {
|
||||
self.request_id
|
||||
}
|
||||
}
|
||||
|
||||
impl Id for v2::response::RemainingBandwidthResponse {
|
||||
fn id(&self) -> u64 {
|
||||
self.request_id
|
||||
@@ -696,12 +423,6 @@ impl Id for v5::response::RemainingBandwidthResponse {
|
||||
}
|
||||
}
|
||||
|
||||
impl Id for v6::response::RemainingBandwidthResponse {
|
||||
fn id(&self) -> u64 {
|
||||
self.request_id
|
||||
}
|
||||
}
|
||||
|
||||
impl Id for v3::response::TopUpBandwidthResponse {
|
||||
fn id(&self) -> u64 {
|
||||
self.request_id
|
||||
@@ -720,28 +441,11 @@ impl Id for v5::response::TopUpBandwidthResponse {
|
||||
}
|
||||
}
|
||||
|
||||
impl Id for v6::response::TopUpBandwidthResponse {
|
||||
fn id(&self) -> u64 {
|
||||
self.request_id
|
||||
}
|
||||
}
|
||||
|
||||
impl Id for v6::response::UpgradeModeResponse {
|
||||
fn id(&self) -> u64 {
|
||||
self.request_id
|
||||
}
|
||||
}
|
||||
|
||||
pub trait PendingRegistrationResponse: Id + UpgradeModeStatus + fmt::Debug {
|
||||
pub trait PendingRegistrationResponse: Id + fmt::Debug {
|
||||
fn nonce(&self) -> u64;
|
||||
fn verify(&self, gateway_key: &x25519::PrivateKey) -> Result<(), Error>;
|
||||
fn verify(&self, gateway_key: &PrivateKey) -> std::result::Result<(), Error>;
|
||||
fn pub_key(&self) -> PeerPublicKey;
|
||||
fn private_ips(&self) -> IpPair;
|
||||
fn finalise_registration(
|
||||
&self,
|
||||
private_key: &x25519::PrivateKey,
|
||||
credential: Option<BandwidthClaim>,
|
||||
) -> Box<dyn FinalMessage + Send + Sync>;
|
||||
}
|
||||
|
||||
impl PendingRegistrationResponse for v2::response::PendingRegistrationResponse {
|
||||
@@ -749,7 +453,7 @@ impl PendingRegistrationResponse for v2::response::PendingRegistrationResponse {
|
||||
self.reply.nonce
|
||||
}
|
||||
|
||||
fn verify(&self, gateway_key: &x25519::PrivateKey) -> Result<(), Error> {
|
||||
fn verify(&self, gateway_key: &PrivateKey) -> std::result::Result<(), Error> {
|
||||
self.reply.gateway_data.verify(gateway_key, self.nonce())
|
||||
}
|
||||
|
||||
@@ -760,22 +464,6 @@ impl PendingRegistrationResponse for v2::response::PendingRegistrationResponse {
|
||||
fn private_ips(&self) -> IpPair {
|
||||
self.reply.gateway_data.private_ip.into()
|
||||
}
|
||||
|
||||
fn finalise_registration(
|
||||
&self,
|
||||
private_key: &x25519::PrivateKey,
|
||||
credential: Option<BandwidthClaim>,
|
||||
) -> Box<dyn FinalMessage + Send + Sync> {
|
||||
Box::new(v2::registration::FinalMessage {
|
||||
gateway_client: v2::registration::GatewayClient::new(
|
||||
private_key,
|
||||
self.pub_key().inner(),
|
||||
self.private_ips().ipv4.into(),
|
||||
self.nonce(),
|
||||
),
|
||||
credential: credential.and_then(|b| b.credential.into_zk_nym().map(|c| *c)),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl PendingRegistrationResponse for v3::response::PendingRegistrationResponse {
|
||||
@@ -783,7 +471,7 @@ impl PendingRegistrationResponse for v3::response::PendingRegistrationResponse {
|
||||
self.reply.nonce
|
||||
}
|
||||
|
||||
fn verify(&self, gateway_key: &x25519::PrivateKey) -> Result<(), Error> {
|
||||
fn verify(&self, gateway_key: &PrivateKey) -> std::result::Result<(), Error> {
|
||||
self.reply.gateway_data.verify(gateway_key, self.nonce())
|
||||
}
|
||||
|
||||
@@ -794,22 +482,6 @@ impl PendingRegistrationResponse for v3::response::PendingRegistrationResponse {
|
||||
fn private_ips(&self) -> IpPair {
|
||||
self.reply.gateway_data.private_ip.into()
|
||||
}
|
||||
|
||||
fn finalise_registration(
|
||||
&self,
|
||||
private_key: &x25519::PrivateKey,
|
||||
credential: Option<BandwidthClaim>,
|
||||
) -> Box<dyn FinalMessage + Send + Sync> {
|
||||
Box::new(v3::registration::FinalMessage {
|
||||
gateway_client: v3::registration::GatewayClient::new(
|
||||
private_key,
|
||||
self.pub_key().inner(),
|
||||
self.private_ips().ipv4.into(),
|
||||
self.nonce(),
|
||||
),
|
||||
credential: credential.and_then(|b| b.credential.into_zk_nym().map(|c| *c)),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl PendingRegistrationResponse for v4::response::PendingRegistrationResponse {
|
||||
@@ -817,42 +489,7 @@ impl PendingRegistrationResponse for v4::response::PendingRegistrationResponse {
|
||||
self.reply.nonce
|
||||
}
|
||||
|
||||
fn verify(&self, gateway_key: &x25519::PrivateKey) -> Result<(), Error> {
|
||||
self.reply.gateway_data.verify(gateway_key, self.nonce())
|
||||
}
|
||||
|
||||
fn pub_key(&self) -> PeerPublicKey {
|
||||
self.reply.gateway_data.pub_key
|
||||
}
|
||||
|
||||
fn private_ips(&self) -> IpPair {
|
||||
// v4 -> v5 -> v6
|
||||
v5::registration::IpPair::from(self.reply.gateway_data.private_ips).into()
|
||||
}
|
||||
|
||||
fn finalise_registration(
|
||||
&self,
|
||||
private_key: &x25519::PrivateKey,
|
||||
credential: Option<BandwidthClaim>,
|
||||
) -> Box<dyn FinalMessage + Send + Sync> {
|
||||
Box::new(v4::registration::FinalMessage {
|
||||
gateway_client: v4::registration::GatewayClient::new(
|
||||
private_key,
|
||||
self.pub_key().inner(),
|
||||
self.reply.gateway_data.private_ips,
|
||||
self.nonce(),
|
||||
),
|
||||
credential: credential.and_then(|b| b.credential.into_zk_nym().map(|c| *c)),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl PendingRegistrationResponse for v5::response::PendingRegistrationResponse {
|
||||
fn nonce(&self) -> u64 {
|
||||
self.reply.nonce
|
||||
}
|
||||
|
||||
fn verify(&self, gateway_key: &x25519::PrivateKey) -> Result<(), Error> {
|
||||
fn verify(&self, gateway_key: &PrivateKey) -> std::result::Result<(), Error> {
|
||||
self.reply.gateway_data.verify(gateway_key, self.nonce())
|
||||
}
|
||||
|
||||
@@ -863,30 +500,14 @@ impl PendingRegistrationResponse for v5::response::PendingRegistrationResponse {
|
||||
fn private_ips(&self) -> IpPair {
|
||||
self.reply.gateway_data.private_ips.into()
|
||||
}
|
||||
|
||||
fn finalise_registration(
|
||||
&self,
|
||||
private_key: &x25519::PrivateKey,
|
||||
credential: Option<BandwidthClaim>,
|
||||
) -> Box<dyn FinalMessage + Send + Sync> {
|
||||
Box::new(v5::registration::FinalMessage {
|
||||
gateway_client: v5::registration::GatewayClient::new(
|
||||
private_key,
|
||||
self.pub_key().inner(),
|
||||
self.reply.gateway_data.private_ips,
|
||||
self.nonce(),
|
||||
),
|
||||
credential: credential.and_then(|b| b.credential.into_zk_nym().map(|c| *c)),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl PendingRegistrationResponse for v6::response::PendingRegistrationResponse {
|
||||
impl PendingRegistrationResponse for v5::response::PendingRegistrationResponse {
|
||||
fn nonce(&self) -> u64 {
|
||||
self.reply.nonce
|
||||
}
|
||||
|
||||
fn verify(&self, gateway_key: &x25519::PrivateKey) -> Result<(), Error> {
|
||||
fn verify(&self, gateway_key: &PrivateKey) -> std::result::Result<(), Error> {
|
||||
self.reply.gateway_data.verify(gateway_key, self.nonce())
|
||||
}
|
||||
|
||||
@@ -897,25 +518,9 @@ impl PendingRegistrationResponse for v6::response::PendingRegistrationResponse {
|
||||
fn private_ips(&self) -> IpPair {
|
||||
self.reply.gateway_data.private_ips
|
||||
}
|
||||
|
||||
fn finalise_registration(
|
||||
&self,
|
||||
private_key: &x25519::PrivateKey,
|
||||
credential: Option<BandwidthClaim>,
|
||||
) -> Box<dyn FinalMessage + Send + Sync> {
|
||||
Box::new(v6::registration::FinalMessage {
|
||||
gateway_client: v6::registration::GatewayClient::new(
|
||||
private_key,
|
||||
self.pub_key().inner(),
|
||||
self.reply.gateway_data.private_ips,
|
||||
self.nonce(),
|
||||
),
|
||||
credential,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub trait RegisteredResponse: Id + UpgradeModeStatus + fmt::Debug {
|
||||
pub trait RegisteredResponse: Id + fmt::Debug {
|
||||
fn private_ips(&self) -> IpPair;
|
||||
fn pub_key(&self) -> PeerPublicKey;
|
||||
fn wg_port(&self) -> u16;
|
||||
@@ -950,8 +555,7 @@ impl RegisteredResponse for v3::response::RegisteredResponse {
|
||||
}
|
||||
impl RegisteredResponse for v4::response::RegisteredResponse {
|
||||
fn private_ips(&self) -> IpPair {
|
||||
// v4 -> v5 -> v6
|
||||
v5::registration::IpPair::from(self.reply.private_ips).into()
|
||||
self.reply.private_ips.into()
|
||||
}
|
||||
|
||||
fn pub_key(&self) -> PeerPublicKey {
|
||||
@@ -964,20 +568,6 @@ impl RegisteredResponse for v4::response::RegisteredResponse {
|
||||
}
|
||||
|
||||
impl RegisteredResponse for v5::response::RegisteredResponse {
|
||||
fn private_ips(&self) -> IpPair {
|
||||
self.reply.private_ips.into()
|
||||
}
|
||||
|
||||
fn pub_key(&self) -> PeerPublicKey {
|
||||
self.reply.pub_key
|
||||
}
|
||||
|
||||
fn wg_port(&self) -> u16 {
|
||||
self.reply.wg_port
|
||||
}
|
||||
}
|
||||
|
||||
impl RegisteredResponse for v6::response::RegisteredResponse {
|
||||
fn private_ips(&self) -> IpPair {
|
||||
self.reply.private_ips
|
||||
}
|
||||
@@ -991,7 +581,7 @@ impl RegisteredResponse for v6::response::RegisteredResponse {
|
||||
}
|
||||
}
|
||||
|
||||
pub trait RemainingBandwidthResponse: Id + UpgradeModeStatus + fmt::Debug {
|
||||
pub trait RemainingBandwidthResponse: Id + fmt::Debug {
|
||||
fn available_bandwidth(&self) -> Option<i64>;
|
||||
}
|
||||
|
||||
@@ -1019,13 +609,7 @@ impl RemainingBandwidthResponse for v5::response::RemainingBandwidthResponse {
|
||||
}
|
||||
}
|
||||
|
||||
impl RemainingBandwidthResponse for v6::response::RemainingBandwidthResponse {
|
||||
fn available_bandwidth(&self) -> Option<i64> {
|
||||
self.reply.as_ref().map(|r| r.available_bandwidth)
|
||||
}
|
||||
}
|
||||
|
||||
pub trait TopUpBandwidthResponse: Id + UpgradeModeStatus + fmt::Debug {
|
||||
pub trait TopUpBandwidthResponse: Id + fmt::Debug {
|
||||
fn available_bandwidth(&self) -> i64;
|
||||
}
|
||||
|
||||
@@ -1046,9 +630,3 @@ impl TopUpBandwidthResponse for v5::response::TopUpBandwidthResponse {
|
||||
self.reply.available_bandwidth
|
||||
}
|
||||
}
|
||||
|
||||
impl TopUpBandwidthResponse for v6::response::TopUpBandwidthResponse {
|
||||
fn available_bandwidth(&self) -> i64 {
|
||||
self.reply.available_bandwidth
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,7 +48,7 @@ pub struct RegistrationData {
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct RegisteredData {
|
||||
pub struct RegistredData {
|
||||
pub pub_key: PeerPublicKey,
|
||||
pub private_ip: IpAddr,
|
||||
pub wg_port: u16,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use super::registration::{RegisteredData, RegistrationData, RemainingBandwidthData};
|
||||
use super::registration::{RegistrationData, RegistredData, RemainingBandwidthData};
|
||||
use nym_sphinx::addressing::Recipient;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
@@ -34,7 +34,7 @@ impl AuthenticatorResponse {
|
||||
}
|
||||
|
||||
pub fn new_registered(
|
||||
registred_data: RegisteredData,
|
||||
registred_data: RegistredData,
|
||||
reply_to: Recipient,
|
||||
request_id: u64,
|
||||
) -> Self {
|
||||
@@ -108,7 +108,7 @@ pub struct PendingRegistrationResponse {
|
||||
pub struct RegisteredResponse {
|
||||
pub request_id: u64,
|
||||
pub reply_to: Recipient,
|
||||
pub reply: RegisteredData,
|
||||
pub reply: RegistredData,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
|
||||
@@ -154,8 +154,8 @@ impl From<v2::registration::RegistrationData> for v1::registration::Registration
|
||||
}
|
||||
}
|
||||
|
||||
impl From<v2::registration::RegisteredData> for v1::registration::RegisteredData {
|
||||
fn from(value: v2::registration::RegisteredData) -> Self {
|
||||
impl From<v2::registration::RegistredData> for v1::registration::RegistredData {
|
||||
fn from(value: v2::registration::RegistredData) -> Self {
|
||||
Self {
|
||||
pub_key: value.pub_key,
|
||||
private_ip: value.private_ip,
|
||||
|
||||
@@ -58,7 +58,7 @@ pub struct RegistrationData {
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
|
||||
pub struct RegisteredData {
|
||||
pub struct RegistredData {
|
||||
pub pub_key: PeerPublicKey,
|
||||
pub private_ip: IpAddr,
|
||||
pub wg_port: u16,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use super::registration::{RegisteredData, RegistrationData, RemainingBandwidthData};
|
||||
use super::registration::{RegistrationData, RegistredData, RemainingBandwidthData};
|
||||
use nym_service_provider_requests_common::{Protocol, ServiceProviderType};
|
||||
use nym_sphinx::addressing::Recipient;
|
||||
use serde::{Deserialize, Serialize};
|
||||
@@ -38,7 +38,7 @@ impl AuthenticatorResponse {
|
||||
}
|
||||
|
||||
pub fn new_registered(
|
||||
registred_data: RegisteredData,
|
||||
registred_data: RegistredData,
|
||||
reply_to: Recipient,
|
||||
request_id: u64,
|
||||
) -> Self {
|
||||
@@ -118,7 +118,7 @@ pub struct PendingRegistrationResponse {
|
||||
pub struct RegisteredResponse {
|
||||
pub request_id: u64,
|
||||
pub reply_to: Recipient,
|
||||
pub reply: RegisteredData,
|
||||
pub reply: RegistredData,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
|
||||
@@ -299,8 +299,8 @@ impl From<v2::registration::RegistrationData> for v3::registration::Registration
|
||||
}
|
||||
}
|
||||
|
||||
impl From<v3::registration::RegisteredData> for v2::registration::RegisteredData {
|
||||
fn from(value: v3::registration::RegisteredData) -> Self {
|
||||
impl From<v3::registration::RegistredData> for v2::registration::RegistredData {
|
||||
fn from(value: v3::registration::RegistredData) -> Self {
|
||||
Self {
|
||||
pub_key: value.pub_key,
|
||||
private_ip: value.private_ip,
|
||||
@@ -309,8 +309,8 @@ impl From<v3::registration::RegisteredData> for v2::registration::RegisteredData
|
||||
}
|
||||
}
|
||||
|
||||
impl From<v2::registration::RegisteredData> for v3::registration::RegisteredData {
|
||||
fn from(value: v2::registration::RegisteredData) -> Self {
|
||||
impl From<v2::registration::RegistredData> for v3::registration::RegistredData {
|
||||
fn from(value: v2::registration::RegistredData) -> Self {
|
||||
Self {
|
||||
pub_key: value.pub_key,
|
||||
private_ip: value.private_ip,
|
||||
@@ -674,7 +674,7 @@ mod tests {
|
||||
let pub_key = PeerPublicKey::new(PublicKey::from([0; 32]));
|
||||
let private_ip = IpAddr::from_str("10.10.10.10").unwrap();
|
||||
let wg_port = 51822;
|
||||
let registred_data = v2::registration::RegisteredData {
|
||||
let registred_data = v2::registration::RegistredData {
|
||||
pub_key,
|
||||
private_ip,
|
||||
wg_port,
|
||||
@@ -701,7 +701,7 @@ mod tests {
|
||||
v3::response::AuthenticatorResponseData::Registered(v3::response::RegisteredResponse {
|
||||
request_id,
|
||||
reply_to,
|
||||
reply: v3::registration::RegisteredData {
|
||||
reply: v3::registration::RegistredData {
|
||||
wg_port,
|
||||
pub_key,
|
||||
private_ip
|
||||
@@ -715,7 +715,7 @@ mod tests {
|
||||
let pub_key = PeerPublicKey::new(PublicKey::from([0; 32]));
|
||||
let private_ip = IpAddr::from_str("10.10.10.10").unwrap();
|
||||
let wg_port = 51822;
|
||||
let registred_data = v3::registration::RegisteredData {
|
||||
let registred_data = v3::registration::RegistredData {
|
||||
pub_key,
|
||||
private_ip,
|
||||
wg_port,
|
||||
@@ -742,7 +742,7 @@ mod tests {
|
||||
v2::response::AuthenticatorResponseData::Registered(v2::response::RegisteredResponse {
|
||||
request_id,
|
||||
reply_to,
|
||||
reply: v2::registration::RegisteredData {
|
||||
reply: v2::registration::RegistredData {
|
||||
wg_port,
|
||||
pub_key,
|
||||
private_ip
|
||||
|
||||
@@ -58,7 +58,7 @@ pub struct RegistrationData {
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
|
||||
pub struct RegisteredData {
|
||||
pub struct RegistredData {
|
||||
pub pub_key: PeerPublicKey,
|
||||
pub private_ip: IpAddr,
|
||||
pub wg_port: u16,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use super::registration::{RegisteredData, RegistrationData, RemainingBandwidthData};
|
||||
use super::registration::{RegistrationData, RegistredData, RemainingBandwidthData};
|
||||
use nym_service_provider_requests_common::{Protocol, ServiceProviderType};
|
||||
use nym_sphinx::addressing::Recipient;
|
||||
use serde::{Deserialize, Serialize};
|
||||
@@ -38,7 +38,7 @@ impl AuthenticatorResponse {
|
||||
}
|
||||
|
||||
pub fn new_registered(
|
||||
registred_data: RegisteredData,
|
||||
registred_data: RegistredData,
|
||||
reply_to: Recipient,
|
||||
request_id: u64,
|
||||
) -> Self {
|
||||
@@ -139,7 +139,7 @@ pub struct PendingRegistrationResponse {
|
||||
pub struct RegisteredResponse {
|
||||
pub request_id: u64,
|
||||
pub reply_to: Recipient,
|
||||
pub reply: RegisteredData,
|
||||
pub reply: RegistredData,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
|
||||
@@ -262,8 +262,8 @@ impl From<v4::response::TopUpBandwidthResponse> for v3::response::TopUpBandwidth
|
||||
}
|
||||
}
|
||||
|
||||
impl From<v3::registration::RegisteredData> for v4::registration::RegisteredData {
|
||||
fn from(value: v3::registration::RegisteredData) -> Self {
|
||||
impl From<v3::registration::RegistredData> for v4::registration::RegistredData {
|
||||
fn from(value: v3::registration::RegistredData) -> Self {
|
||||
Self {
|
||||
pub_key: value.pub_key,
|
||||
private_ips: value.private_ip.into(),
|
||||
@@ -272,8 +272,8 @@ impl From<v3::registration::RegisteredData> for v4::registration::RegisteredData
|
||||
}
|
||||
}
|
||||
|
||||
impl From<v4::registration::RegisteredData> for v3::registration::RegisteredData {
|
||||
fn from(value: v4::registration::RegisteredData) -> Self {
|
||||
impl From<v4::registration::RegistredData> for v3::registration::RegistredData {
|
||||
fn from(value: v4::registration::RegistredData) -> Self {
|
||||
Self {
|
||||
pub_key: value.pub_key,
|
||||
private_ip: value.private_ips.ipv4.into(),
|
||||
@@ -565,7 +565,7 @@ mod tests {
|
||||
let private_ips =
|
||||
v4::registration::IpPair::new(ipv4, Ipv6Addr::from_str("fc01::a0a").unwrap());
|
||||
let wg_port = 51822;
|
||||
let registred_data = v3::registration::RegisteredData {
|
||||
let registred_data = v3::registration::RegistredData {
|
||||
pub_key,
|
||||
private_ip: ipv4.into(),
|
||||
wg_port,
|
||||
@@ -592,7 +592,7 @@ mod tests {
|
||||
v4::response::AuthenticatorResponseData::Registered(v4::response::RegisteredResponse {
|
||||
request_id,
|
||||
reply_to,
|
||||
reply: v4::registration::RegisteredData {
|
||||
reply: v4::registration::RegistredData {
|
||||
wg_port,
|
||||
pub_key,
|
||||
private_ips
|
||||
@@ -608,7 +608,7 @@ mod tests {
|
||||
let private_ips =
|
||||
v4::registration::IpPair::new(ipv4, Ipv6Addr::from_str("fc01::10").unwrap());
|
||||
let wg_port = 51822;
|
||||
let registred_data = v4::registration::RegisteredData {
|
||||
let registred_data = v4::registration::RegistredData {
|
||||
pub_key,
|
||||
private_ips,
|
||||
wg_port,
|
||||
@@ -635,7 +635,7 @@ mod tests {
|
||||
v3::response::AuthenticatorResponseData::Registered(v3::response::RegisteredResponse {
|
||||
request_id,
|
||||
reply_to,
|
||||
reply: v3::registration::RegisteredData {
|
||||
reply: v3::registration::RegistredData {
|
||||
wg_port,
|
||||
pub_key,
|
||||
private_ip: ipv4.into()
|
||||
|
||||
@@ -110,7 +110,7 @@ pub struct RegistrationData {
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
|
||||
pub struct RegisteredData {
|
||||
pub struct RegistredData {
|
||||
pub pub_key: PeerPublicKey,
|
||||
pub private_ips: IpPair,
|
||||
pub wg_port: u16,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use super::registration::{RegisteredData, RegistrationData, RemainingBandwidthData};
|
||||
use super::registration::{RegistrationData, RegistredData, RemainingBandwidthData};
|
||||
use nym_service_provider_requests_common::{Protocol, ServiceProviderType};
|
||||
use nym_sphinx::addressing::Recipient;
|
||||
use serde::{Deserialize, Serialize};
|
||||
@@ -38,7 +38,7 @@ impl AuthenticatorResponse {
|
||||
}
|
||||
|
||||
pub fn new_registered(
|
||||
registred_data: RegisteredData,
|
||||
registred_data: RegistredData,
|
||||
reply_to: Recipient,
|
||||
request_id: u64,
|
||||
) -> Self {
|
||||
@@ -139,7 +139,7 @@ pub struct PendingRegistrationResponse {
|
||||
pub struct RegisteredResponse {
|
||||
pub request_id: u64,
|
||||
pub reply_to: Recipient,
|
||||
pub reply: RegisteredData,
|
||||
pub reply: RegistredData,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
|
||||
@@ -186,8 +186,8 @@ impl From<v4::response::TopUpBandwidthResponse> for v5::response::TopUpBandwidth
|
||||
}
|
||||
}
|
||||
|
||||
impl From<v4::registration::RegisteredData> for v5::registration::RegisteredData {
|
||||
fn from(value: v4::registration::RegisteredData) -> Self {
|
||||
impl From<v4::registration::RegistredData> for v5::registration::RegistredData {
|
||||
fn from(value: v4::registration::RegistredData) -> Self {
|
||||
Self {
|
||||
pub_key: value.pub_key,
|
||||
private_ips: value.private_ips.into(),
|
||||
@@ -405,7 +405,7 @@ mod tests {
|
||||
let ipv6 = Ipv6Addr::from_str("fc01::a0a").unwrap();
|
||||
let private_ips = v4::registration::IpPair::new(ipv4, ipv6);
|
||||
let wg_port = 51822;
|
||||
let registred_data = v4::registration::RegisteredData {
|
||||
let registred_data = v4::registration::RegistredData {
|
||||
pub_key,
|
||||
private_ips,
|
||||
wg_port,
|
||||
@@ -431,7 +431,7 @@ mod tests {
|
||||
upgraded_msg.data,
|
||||
v5::response::AuthenticatorResponseData::Registered(v5::response::RegisteredResponse {
|
||||
request_id,
|
||||
reply: v5::registration::RegisteredData {
|
||||
reply: v5::registration::RegistredData {
|
||||
wg_port,
|
||||
pub_key,
|
||||
private_ips: v5::registration::IpPair::new(ipv4, ipv6)
|
||||
|
||||
@@ -108,7 +108,7 @@ pub struct RegistrationData {
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
|
||||
pub struct RegisteredData {
|
||||
pub struct RegistredData {
|
||||
pub pub_key: PeerPublicKey,
|
||||
pub private_ips: IpPair,
|
||||
pub wg_port: u16,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use super::registration::{RegisteredData, RegistrationData, RemainingBandwidthData};
|
||||
use super::registration::{RegistrationData, RegistredData, RemainingBandwidthData};
|
||||
use nym_service_provider_requests_common::{Protocol, ServiceProviderType};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
@@ -32,7 +32,7 @@ impl AuthenticatorResponse {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_registered(registred_data: RegisteredData, request_id: u64) -> Self {
|
||||
pub fn new_registered(registred_data: RegistredData, request_id: u64) -> Self {
|
||||
Self {
|
||||
protocol: Protocol {
|
||||
service_provider_type: ServiceProviderType::Authenticator,
|
||||
@@ -116,7 +116,7 @@ pub struct PendingRegistrationResponse {
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
pub struct RegisteredResponse {
|
||||
pub request_id: u64,
|
||||
pub reply: RegisteredData,
|
||||
pub reply: RegistredData,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
|
||||
@@ -1,441 +0,0 @@
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::{v5, v6};
|
||||
|
||||
impl TryFrom<v5::request::AuthenticatorRequest> for v6::request::AuthenticatorRequest {
|
||||
type Error = crate::Error;
|
||||
|
||||
fn try_from(
|
||||
authenticator_request: v5::request::AuthenticatorRequest,
|
||||
) -> Result<Self, Self::Error> {
|
||||
Ok(Self {
|
||||
protocol: v6::PROTOCOL,
|
||||
data: authenticator_request.data.try_into()?,
|
||||
request_id: authenticator_request.request_id,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<v5::request::AuthenticatorRequestData> for v6::request::AuthenticatorRequestData {
|
||||
type Error = crate::Error;
|
||||
|
||||
fn try_from(
|
||||
authenticator_request_data: v5::request::AuthenticatorRequestData,
|
||||
) -> Result<Self, Self::Error> {
|
||||
match authenticator_request_data {
|
||||
v5::request::AuthenticatorRequestData::Initial(init_msg) => Ok(
|
||||
v6::request::AuthenticatorRequestData::Initial(init_msg.into()),
|
||||
),
|
||||
v5::request::AuthenticatorRequestData::Final(final_msg) => Ok(
|
||||
v6::request::AuthenticatorRequestData::Final(Box::new((*final_msg).try_into()?)),
|
||||
),
|
||||
v5::request::AuthenticatorRequestData::QueryBandwidth(pub_key) => Ok(
|
||||
v6::request::AuthenticatorRequestData::QueryBandwidth(pub_key),
|
||||
),
|
||||
v5::request::AuthenticatorRequestData::TopUpBandwidth(top_up_message) => Ok(
|
||||
v6::request::AuthenticatorRequestData::TopUpBandwidth(top_up_message.into()),
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<v5::registration::InitMessage> for v6::registration::InitMessage {
|
||||
fn from(init_msg: v5::registration::InitMessage) -> Self {
|
||||
Self {
|
||||
pub_key: init_msg.pub_key,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<v5::registration::FinalMessage> for v6::registration::FinalMessage {
|
||||
type Error = crate::Error;
|
||||
|
||||
fn try_from(final_msg: v5::registration::FinalMessage) -> Result<Self, Self::Error> {
|
||||
Ok(Self {
|
||||
gateway_client: final_msg.gateway_client.into(),
|
||||
credential: final_msg
|
||||
.credential
|
||||
.map(TryInto::try_into)
|
||||
.transpose()
|
||||
.map_err(Self::Error::conversion_display)?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl From<v5::registration::GatewayClient> for v6::registration::GatewayClient {
|
||||
fn from(gateway_client: v5::registration::GatewayClient) -> Self {
|
||||
Self {
|
||||
pub_key: gateway_client.pub_key,
|
||||
private_ips: gateway_client.private_ips.into(),
|
||||
mac: gateway_client.mac.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<v6::registration::GatewayClient> for v5::registration::GatewayClient {
|
||||
fn from(gateway_client: v6::registration::GatewayClient) -> Self {
|
||||
Self {
|
||||
pub_key: gateway_client.pub_key,
|
||||
private_ips: gateway_client.private_ips.into(),
|
||||
mac: gateway_client.mac.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<v5::registration::ClientMac> for v6::registration::ClientMac {
|
||||
fn from(client_mac: v5::registration::ClientMac) -> Self {
|
||||
Self::new((*client_mac).clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<v6::registration::ClientMac> for v5::registration::ClientMac {
|
||||
fn from(client_mac: v6::registration::ClientMac) -> Self {
|
||||
Self::new((*client_mac).clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Box<v5::topup::TopUpMessage>> for Box<v6::topup::TopUpMessage> {
|
||||
fn from(top_up_message: Box<v5::topup::TopUpMessage>) -> Self {
|
||||
Box::new(v6::topup::TopUpMessage {
|
||||
pub_key: top_up_message.pub_key,
|
||||
credential: top_up_message.credential,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl From<v5::response::AuthenticatorResponse> for v6::response::AuthenticatorResponse {
|
||||
fn from(value: v5::response::AuthenticatorResponse) -> Self {
|
||||
Self {
|
||||
protocol: v6::PROTOCOL,
|
||||
data: value.data.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<v5::response::AuthenticatorResponseData> for v6::response::AuthenticatorResponseData {
|
||||
fn from(authenticator_response_data: v5::response::AuthenticatorResponseData) -> Self {
|
||||
match authenticator_response_data {
|
||||
v5::response::AuthenticatorResponseData::PendingRegistration(pending_response) => {
|
||||
v6::response::AuthenticatorResponseData::PendingRegistration(
|
||||
pending_response.into(),
|
||||
)
|
||||
}
|
||||
v5::response::AuthenticatorResponseData::Registered(registered_response) => {
|
||||
v6::response::AuthenticatorResponseData::Registered(registered_response.into())
|
||||
}
|
||||
v5::response::AuthenticatorResponseData::RemainingBandwidth(
|
||||
remaining_bandwidth_response,
|
||||
) => v6::response::AuthenticatorResponseData::RemainingBandwidth(
|
||||
remaining_bandwidth_response.into(),
|
||||
),
|
||||
v5::response::AuthenticatorResponseData::TopUpBandwidth(top_up_response) => {
|
||||
v6::response::AuthenticatorResponseData::TopUpBandwidth(top_up_response.into())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<v5::response::RegisteredResponse> for v6::response::RegisteredResponse {
|
||||
fn from(value: v5::response::RegisteredResponse) -> Self {
|
||||
Self {
|
||||
request_id: value.request_id,
|
||||
reply: value.reply.into(),
|
||||
upgrade_mode_enabled: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<v5::response::PendingRegistrationResponse> for v6::response::PendingRegistrationResponse {
|
||||
fn from(value: v5::response::PendingRegistrationResponse) -> Self {
|
||||
Self {
|
||||
request_id: value.request_id,
|
||||
reply: value.reply.into(),
|
||||
upgrade_mode_enabled: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<v5::registration::RegistrationData> for v6::registration::RegistrationData {
|
||||
fn from(value: v5::registration::RegistrationData) -> Self {
|
||||
Self {
|
||||
nonce: value.nonce,
|
||||
gateway_data: value.gateway_data.into(),
|
||||
wg_port: value.wg_port,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<v6::registration::RegistrationData> for v5::registration::RegistrationData {
|
||||
fn from(value: v6::registration::RegistrationData) -> Self {
|
||||
Self {
|
||||
nonce: value.nonce,
|
||||
gateway_data: value.gateway_data.into(),
|
||||
wg_port: value.wg_port,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<v5::response::RemainingBandwidthResponse> for v6::response::RemainingBandwidthResponse {
|
||||
fn from(value: v5::response::RemainingBandwidthResponse) -> Self {
|
||||
Self {
|
||||
request_id: value.request_id,
|
||||
reply: value.reply.map(Into::into),
|
||||
upgrade_mode_enabled: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<v5::response::TopUpBandwidthResponse> for v6::response::TopUpBandwidthResponse {
|
||||
fn from(value: v5::response::TopUpBandwidthResponse) -> Self {
|
||||
Self {
|
||||
request_id: value.request_id,
|
||||
reply: value.reply.into(),
|
||||
upgrade_mode_enabled: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<v5::registration::RegisteredData> for v6::registration::RegisteredData {
|
||||
fn from(value: v5::registration::RegisteredData) -> Self {
|
||||
Self {
|
||||
pub_key: value.pub_key,
|
||||
private_ips: value.private_ips.into(),
|
||||
wg_port: value.wg_port,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<v5::registration::RemainingBandwidthData> for v6::registration::RemainingBandwidthData {
|
||||
fn from(value: v5::registration::RemainingBandwidthData) -> Self {
|
||||
Self {
|
||||
available_bandwidth: value.available_bandwidth,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<v5::registration::IpPair> for v6::registration::IpPair {
|
||||
fn from(value: v5::registration::IpPair) -> Self {
|
||||
Self {
|
||||
ipv4: value.ipv4,
|
||||
ipv6: value.ipv6,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<v6::registration::IpPair> for v5::registration::IpPair {
|
||||
fn from(value: v6::registration::IpPair) -> Self {
|
||||
Self {
|
||||
ipv4: value.ipv4,
|
||||
ipv6: value.ipv6,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::{
|
||||
net::{Ipv4Addr, Ipv6Addr},
|
||||
str::FromStr,
|
||||
};
|
||||
|
||||
use nym_credentials_interface::{BandwidthCredential, CredentialSpendingData, TicketType};
|
||||
use nym_crypto::asymmetric::x25519::PrivateKey;
|
||||
use nym_wireguard_types::PeerPublicKey;
|
||||
use x25519_dalek::PublicKey;
|
||||
|
||||
use super::*;
|
||||
use crate::models::BandwidthClaim;
|
||||
use crate::{util::tests::CREDENTIAL_BYTES, v5};
|
||||
|
||||
#[test]
|
||||
fn upgrade_initial_req() {
|
||||
let pub_key = PeerPublicKey::new(PublicKey::from([0; 32]));
|
||||
|
||||
let (msg, _) = v5::request::AuthenticatorRequest::new_initial_request(
|
||||
v5::registration::InitMessage::new(pub_key),
|
||||
);
|
||||
let upgraded_msg = v6::request::AuthenticatorRequest::try_from(msg).unwrap();
|
||||
|
||||
assert_eq!(upgraded_msg.protocol, v6::PROTOCOL);
|
||||
assert_eq!(
|
||||
upgraded_msg.data,
|
||||
v6::request::AuthenticatorRequestData::Initial(v6::registration::InitMessage {
|
||||
pub_key
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn upgrade_final_req() {
|
||||
let mut rng = rand::thread_rng();
|
||||
|
||||
let local_secret = PrivateKey::new(&mut rng);
|
||||
let remote_secret = x25519_dalek::StaticSecret::random_from_rng(&mut rng);
|
||||
let ipv4 = Ipv4Addr::from_str("10.10.10.10").unwrap();
|
||||
let ipv6 = Ipv6Addr::from_str("fc01::a0a").unwrap();
|
||||
let ips = v5::registration::IpPair::new(ipv4, ipv6);
|
||||
let nonce = 42;
|
||||
let gateway_client = v5::registration::GatewayClient::new(
|
||||
&local_secret,
|
||||
(&remote_secret).into(),
|
||||
ips,
|
||||
nonce,
|
||||
);
|
||||
let credential = CredentialSpendingData::try_from_bytes(&CREDENTIAL_BYTES).unwrap();
|
||||
let final_message = v5::registration::FinalMessage {
|
||||
gateway_client: gateway_client.clone(),
|
||||
credential: Some(credential.clone()),
|
||||
};
|
||||
|
||||
let (msg, _) = v5::request::AuthenticatorRequest::new_final_request(final_message);
|
||||
let upgraded_msg = v6::request::AuthenticatorRequest::try_from(msg).unwrap();
|
||||
|
||||
assert_eq!(upgraded_msg.protocol, v6::PROTOCOL);
|
||||
assert_eq!(
|
||||
upgraded_msg.data,
|
||||
v6::request::AuthenticatorRequestData::Final(Box::new(
|
||||
v6::registration::FinalMessage {
|
||||
gateway_client: v6::registration::GatewayClient::new(
|
||||
&local_secret,
|
||||
(&remote_secret).into(),
|
||||
v6::registration::IpPair::new(ipv4, ipv6),
|
||||
nonce
|
||||
),
|
||||
credential: Some(BandwidthClaim {
|
||||
credential: BandwidthCredential::ZkNym(Box::new(credential)),
|
||||
kind: TicketType::V1MixnetEntry,
|
||||
})
|
||||
}
|
||||
))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn upgrade_query_req() {
|
||||
let pub_key = PeerPublicKey::new(PublicKey::from([0; 32]));
|
||||
|
||||
let (msg, _) = v5::request::AuthenticatorRequest::new_query_request(pub_key);
|
||||
let upgraded_msg = v6::request::AuthenticatorRequest::try_from(msg).unwrap();
|
||||
|
||||
assert_eq!(upgraded_msg.protocol, v6::PROTOCOL);
|
||||
assert_eq!(
|
||||
upgraded_msg.data,
|
||||
v6::request::AuthenticatorRequestData::QueryBandwidth(pub_key)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn upgrade_pending_reg_resp() {
|
||||
let mut rng = rand::thread_rng();
|
||||
|
||||
let local_secret = PrivateKey::new(&mut rng);
|
||||
let remote_secret = x25519_dalek::StaticSecret::random_from_rng(&mut rng);
|
||||
let ipv4 = Ipv4Addr::from_str("10.10.10.10").unwrap();
|
||||
let ipv6 = Ipv6Addr::from_str("fc01::a0a").unwrap();
|
||||
let ips = v5::registration::IpPair::new(ipv4, ipv6);
|
||||
let nonce = 42;
|
||||
let wg_port = 51822;
|
||||
let gateway_data = v5::registration::GatewayClient::new(
|
||||
&local_secret,
|
||||
(&remote_secret).into(),
|
||||
ips,
|
||||
nonce,
|
||||
);
|
||||
let registration_data = v5::registration::RegistrationData {
|
||||
nonce,
|
||||
gateway_data,
|
||||
wg_port,
|
||||
};
|
||||
let request_id = 123;
|
||||
|
||||
let msg = v5::response::AuthenticatorResponse::new_pending_registration_success(
|
||||
registration_data,
|
||||
request_id,
|
||||
);
|
||||
let upgraded_msg = v6::response::AuthenticatorResponse::from(msg);
|
||||
|
||||
assert_eq!(upgraded_msg.protocol, v6::PROTOCOL);
|
||||
|
||||
assert_eq!(
|
||||
upgraded_msg.data,
|
||||
v6::response::AuthenticatorResponseData::PendingRegistration(
|
||||
v6::response::PendingRegistrationResponse {
|
||||
request_id,
|
||||
reply: v6::registration::RegistrationData {
|
||||
nonce,
|
||||
gateway_data: v6::registration::GatewayClient::new(
|
||||
&local_secret,
|
||||
(&remote_secret).into(),
|
||||
v6::registration::IpPair::new(ipv4, ipv6),
|
||||
nonce
|
||||
),
|
||||
wg_port
|
||||
},
|
||||
upgrade_mode_enabled: false,
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn upgrade_registered_resp() {
|
||||
let pub_key = PeerPublicKey::new(PublicKey::from([0; 32]));
|
||||
let ipv4 = Ipv4Addr::from_str("10.1.10.10").unwrap();
|
||||
let ipv6 = Ipv6Addr::from_str("fc01::a0a").unwrap();
|
||||
let private_ips = v5::registration::IpPair::new(ipv4, ipv6);
|
||||
let wg_port = 51822;
|
||||
let registered_data = v5::registration::RegisteredData {
|
||||
pub_key,
|
||||
private_ips,
|
||||
wg_port,
|
||||
};
|
||||
let request_id = 123;
|
||||
|
||||
let msg = v5::response::AuthenticatorResponse::new_registered(registered_data, request_id);
|
||||
let upgraded_msg = v6::response::AuthenticatorResponse::from(msg);
|
||||
|
||||
assert_eq!(upgraded_msg.protocol, v6::PROTOCOL);
|
||||
assert_eq!(
|
||||
upgraded_msg.data,
|
||||
v6::response::AuthenticatorResponseData::Registered(v6::response::RegisteredResponse {
|
||||
request_id,
|
||||
reply: v6::registration::RegisteredData {
|
||||
wg_port,
|
||||
pub_key,
|
||||
private_ips: v6::registration::IpPair::new(ipv4, ipv6)
|
||||
},
|
||||
upgrade_mode_enabled: false,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn upgrade_remaining_bandwidth_resp() {
|
||||
let available_bandwidth = 42;
|
||||
let remaining_bandwidth_data = Some(v5::registration::RemainingBandwidthData {
|
||||
available_bandwidth,
|
||||
});
|
||||
let request_id = 123;
|
||||
|
||||
let msg = v5::response::AuthenticatorResponse::new_remaining_bandwidth(
|
||||
remaining_bandwidth_data,
|
||||
request_id,
|
||||
);
|
||||
let upgraded_msg = v6::response::AuthenticatorResponse::from(msg);
|
||||
|
||||
assert_eq!(upgraded_msg.protocol, v6::PROTOCOL);
|
||||
assert_eq!(
|
||||
upgraded_msg.data,
|
||||
v6::response::AuthenticatorResponseData::RemainingBandwidth(
|
||||
v6::response::RemainingBandwidthResponse {
|
||||
request_id,
|
||||
reply: Some(v6::registration::RemainingBandwidthData {
|
||||
available_bandwidth,
|
||||
}),
|
||||
upgrade_mode_enabled: false,
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use nym_service_provider_requests_common::{Protocol, ServiceProviderType};
|
||||
|
||||
pub mod conversion;
|
||||
pub mod registration;
|
||||
pub mod request;
|
||||
pub mod response;
|
||||
pub mod topup;
|
||||
pub mod upgrade_mode_check;
|
||||
|
||||
pub const VERSION: u8 = 6;
|
||||
|
||||
pub const PROTOCOL: Protocol = Protocol::new(VERSION, ServiceProviderType::Authenticator);
|
||||
@@ -1,287 +0,0 @@
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::error::Error;
|
||||
use crate::models::BandwidthClaim;
|
||||
use base64::{Engine, engine::general_purpose};
|
||||
use nym_network_defaults::constants::{WG_TUN_DEVICE_IP_ADDRESS_V4, WG_TUN_DEVICE_IP_ADDRESS_V6};
|
||||
use nym_wireguard_types::PeerPublicKey;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
|
||||
use std::time::SystemTime;
|
||||
use std::{fmt, ops::Deref, str::FromStr};
|
||||
|
||||
#[cfg(feature = "verify")]
|
||||
use hmac::{Hmac, Mac};
|
||||
#[cfg(feature = "verify")]
|
||||
use nym_crypto::asymmetric::x25519::{PrivateKey, PublicKey};
|
||||
#[cfg(feature = "verify")]
|
||||
use sha2::Sha256;
|
||||
|
||||
pub type PendingRegistrations = HashMap<PeerPublicKey, RegistrationData>;
|
||||
pub type PrivateIPs = HashMap<IpPair, Taken>;
|
||||
|
||||
#[cfg(feature = "verify")]
|
||||
pub type HmacSha256 = Hmac<Sha256>;
|
||||
|
||||
pub type Nonce = u64;
|
||||
pub type Taken = Option<SystemTime>;
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||
pub struct IpPair {
|
||||
pub ipv4: Ipv4Addr,
|
||||
pub ipv6: Ipv6Addr,
|
||||
}
|
||||
|
||||
impl IpPair {
|
||||
pub fn new(ipv4: Ipv4Addr, ipv6: Ipv6Addr) -> Self {
|
||||
IpPair { ipv4, ipv6 }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<(Ipv4Addr, Ipv6Addr)> for IpPair {
|
||||
fn from((ipv4, ipv6): (Ipv4Addr, Ipv6Addr)) -> Self {
|
||||
IpPair { ipv4, ipv6 }
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for IpPair {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "({}, {})", self.ipv4, self.ipv6)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<IpAddr> for IpPair {
|
||||
fn from(value: IpAddr) -> Self {
|
||||
let (before_last_byte, last_byte) = match value {
|
||||
IpAddr::V4(ipv4_addr) => (ipv4_addr.octets()[2], ipv4_addr.octets()[3]),
|
||||
IpAddr::V6(ipv6_addr) => (ipv6_addr.octets()[14], ipv6_addr.octets()[15]),
|
||||
};
|
||||
let last_bytes = ((before_last_byte as u16) << 8) | last_byte as u16;
|
||||
let ipv4 = Ipv4Addr::new(
|
||||
WG_TUN_DEVICE_IP_ADDRESS_V4.octets()[0],
|
||||
WG_TUN_DEVICE_IP_ADDRESS_V4.octets()[1],
|
||||
before_last_byte,
|
||||
last_byte,
|
||||
);
|
||||
let ipv6 = Ipv6Addr::new(
|
||||
WG_TUN_DEVICE_IP_ADDRESS_V6.segments()[0],
|
||||
WG_TUN_DEVICE_IP_ADDRESS_V6.segments()[1],
|
||||
WG_TUN_DEVICE_IP_ADDRESS_V6.segments()[2],
|
||||
WG_TUN_DEVICE_IP_ADDRESS_V6.segments()[3],
|
||||
WG_TUN_DEVICE_IP_ADDRESS_V6.segments()[4],
|
||||
WG_TUN_DEVICE_IP_ADDRESS_V6.segments()[5],
|
||||
WG_TUN_DEVICE_IP_ADDRESS_V6.segments()[6],
|
||||
last_bytes,
|
||||
);
|
||||
IpPair::new(ipv4, ipv6)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
|
||||
pub struct InitMessage {
|
||||
/// Base64 encoded x25519 public key
|
||||
pub pub_key: PeerPublicKey,
|
||||
}
|
||||
|
||||
impl InitMessage {
|
||||
pub fn new(pub_key: PeerPublicKey) -> Self {
|
||||
InitMessage { pub_key }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
|
||||
pub struct FinalMessage {
|
||||
/// Gateway client data
|
||||
pub gateway_client: GatewayClient,
|
||||
|
||||
/// Ecash credential
|
||||
pub credential: Option<BandwidthClaim>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
|
||||
pub struct RegistrationData {
|
||||
pub nonce: u64,
|
||||
pub gateway_data: GatewayClient,
|
||||
pub wg_port: u16,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
|
||||
pub struct RegisteredData {
|
||||
pub pub_key: PeerPublicKey,
|
||||
pub private_ips: IpPair,
|
||||
pub wg_port: u16,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
|
||||
pub struct RemainingBandwidthData {
|
||||
pub available_bandwidth: i64,
|
||||
}
|
||||
|
||||
/// Client that wants to register sends its PublicKey bytes mac digest encrypted with a DH shared secret.
|
||||
/// Gateway/Nym node can then verify pub_key payload using the same process
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
|
||||
pub struct GatewayClient {
|
||||
/// Base64 encoded x25519 public key
|
||||
pub pub_key: PeerPublicKey,
|
||||
|
||||
/// Assigned private IPs (v4 and v6)
|
||||
pub private_ips: IpPair,
|
||||
|
||||
/// Sha256 hmac on the data (alongside the prior nonce)
|
||||
pub mac: ClientMac,
|
||||
}
|
||||
|
||||
impl GatewayClient {
|
||||
#[cfg(feature = "verify")]
|
||||
pub fn new(
|
||||
local_secret: &PrivateKey,
|
||||
remote_public: x25519_dalek::PublicKey,
|
||||
private_ips: IpPair,
|
||||
nonce: u64,
|
||||
) -> Self {
|
||||
let local_public = PublicKey::from(local_secret);
|
||||
let remote_public = PublicKey::from(remote_public);
|
||||
|
||||
let dh = local_secret.diffie_hellman(&remote_public);
|
||||
|
||||
// TODO: change that to use our nym_crypto::hmac module instead
|
||||
#[allow(clippy::expect_used)]
|
||||
let mut mac = HmacSha256::new_from_slice(&dh[..])
|
||||
.expect("x25519 shared secret is always 32 bytes long");
|
||||
|
||||
mac.update(local_public.as_bytes());
|
||||
mac.update(private_ips.to_string().as_bytes());
|
||||
mac.update(&nonce.to_le_bytes());
|
||||
|
||||
GatewayClient {
|
||||
pub_key: PeerPublicKey::new(local_public.into()),
|
||||
private_ips,
|
||||
mac: ClientMac(mac.finalize().into_bytes().to_vec()),
|
||||
}
|
||||
}
|
||||
|
||||
// Reusable secret should be gateways Wireguard PK
|
||||
// Client should perform this step when generating its payload, using its own WG PK
|
||||
#[cfg(feature = "verify")]
|
||||
pub fn verify(&self, gateway_key: &PrivateKey, nonce: u64) -> Result<(), Error> {
|
||||
// use gateways key as a ref to an x25519_dalek key
|
||||
let dh = gateway_key.inner().diffie_hellman(&self.pub_key);
|
||||
|
||||
// TODO: change that to use our nym_crypto::hmac module instead
|
||||
#[allow(clippy::expect_used)]
|
||||
let mut mac = HmacSha256::new_from_slice(dh.as_bytes())
|
||||
.expect("x25519 shared secret is always 32 bytes long");
|
||||
|
||||
mac.update(self.pub_key.as_bytes());
|
||||
mac.update(self.private_ips.to_string().as_bytes());
|
||||
mac.update(&nonce.to_le_bytes());
|
||||
|
||||
mac.verify_slice(&self.mac)
|
||||
.map_err(|source| Error::FailedClientMacVerification {
|
||||
client: self.pub_key.to_string(),
|
||||
source,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn pub_key(&self) -> PeerPublicKey {
|
||||
self.pub_key
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: change the inner type into generic array of size HmacSha256::OutputSize
|
||||
// TODO2: rely on our internal crypto/hmac
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
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 From<Vec<u8>> for ClientMac {
|
||||
fn from(v: Vec<u8>) -> Self {
|
||||
ClientMac(v)
|
||||
}
|
||||
}
|
||||
|
||||
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 FromStr for ClientMac {
|
||||
type Err = Error;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
let mac_bytes: Vec<u8> =
|
||||
general_purpose::STANDARD
|
||||
.decode(s)
|
||||
.map_err(|source| Error::MalformedClientMac {
|
||||
mac: s.to_string(),
|
||||
source,
|
||||
})?;
|
||||
|
||||
Ok(ClientMac(mac_bytes))
|
||||
}
|
||||
}
|
||||
|
||||
impl Serialize for ClientMac {
|
||||
fn serialize<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)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use nym_crypto::asymmetric::x25519;
|
||||
use nym_test_utils::helpers::deterministic_rng;
|
||||
|
||||
#[test]
|
||||
fn create_ip_pair() {
|
||||
let ipv4: IpAddr = Ipv4Addr::from_str("10.1.10.50").unwrap().into();
|
||||
let ipv6: IpAddr = Ipv6Addr::from_str("fc01::0a32").unwrap().into();
|
||||
|
||||
assert_eq!(IpPair::from(ipv4), IpPair::from(ipv6));
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "verify")]
|
||||
fn client_request_roundtrip() {
|
||||
let mut rng = deterministic_rng();
|
||||
|
||||
let gateway_key_pair = x25519::KeyPair::new(&mut rng);
|
||||
let client_key_pair = x25519::KeyPair::new(&mut rng);
|
||||
|
||||
let nonce = 1234567890;
|
||||
|
||||
let client = GatewayClient::new(
|
||||
client_key_pair.private_key(),
|
||||
x25519_dalek::PublicKey::from(gateway_key_pair.public_key().to_bytes()),
|
||||
IpPair::new("10.0.0.42".parse().unwrap(), "fc00::42".parse().unwrap()),
|
||||
nonce,
|
||||
);
|
||||
assert!(client.verify(gateway_key_pair.private_key(), nonce).is_ok())
|
||||
}
|
||||
}
|
||||
@@ -1,135 +0,0 @@
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use super::{
|
||||
PROTOCOL,
|
||||
registration::{FinalMessage, InitMessage},
|
||||
topup::TopUpMessage,
|
||||
upgrade_mode_check::UpgradeModeCheckRequest,
|
||||
};
|
||||
use nym_service_provider_requests_common::Protocol;
|
||||
use nym_wireguard_types::PeerPublicKey;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::make_bincode_serializer;
|
||||
|
||||
fn generate_random() -> u64 {
|
||||
use rand::RngCore;
|
||||
let mut rng = rand::rngs::OsRng;
|
||||
rng.next_u64()
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
pub struct AuthenticatorRequest {
|
||||
pub protocol: Protocol,
|
||||
pub data: AuthenticatorRequestData,
|
||||
pub request_id: u64,
|
||||
}
|
||||
|
||||
impl AuthenticatorRequest {
|
||||
pub fn from_reconstructed_message(
|
||||
message: &nym_sphinx::receiver::ReconstructedMessage,
|
||||
) -> Result<Self, bincode::Error> {
|
||||
use bincode::Options;
|
||||
make_bincode_serializer().deserialize(&message.message)
|
||||
}
|
||||
|
||||
pub fn new_initial_request(init_message: InitMessage) -> (Self, u64) {
|
||||
let request_id = generate_random();
|
||||
(
|
||||
Self {
|
||||
protocol: PROTOCOL,
|
||||
data: AuthenticatorRequestData::Initial(init_message),
|
||||
request_id,
|
||||
},
|
||||
request_id,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn new_final_request(final_message: FinalMessage) -> (Self, u64) {
|
||||
let request_id = generate_random();
|
||||
(
|
||||
Self {
|
||||
protocol: PROTOCOL,
|
||||
data: AuthenticatorRequestData::Final(Box::new(final_message)),
|
||||
request_id,
|
||||
},
|
||||
request_id,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn new_query_request(peer_public_key: PeerPublicKey) -> (Self, u64) {
|
||||
let request_id = generate_random();
|
||||
(
|
||||
Self {
|
||||
protocol: PROTOCOL,
|
||||
data: AuthenticatorRequestData::QueryBandwidth(peer_public_key),
|
||||
request_id,
|
||||
},
|
||||
request_id,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn new_topup_request(top_up_message: TopUpMessage) -> (Self, u64) {
|
||||
let request_id = generate_random();
|
||||
(
|
||||
Self {
|
||||
protocol: PROTOCOL,
|
||||
data: AuthenticatorRequestData::TopUpBandwidth(Box::new(top_up_message)),
|
||||
request_id,
|
||||
},
|
||||
request_id,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn new_upgrade_mode_check_request(message: UpgradeModeCheckRequest) -> (Self, u64) {
|
||||
let request_id = generate_random();
|
||||
(
|
||||
Self {
|
||||
protocol: PROTOCOL,
|
||||
data: AuthenticatorRequestData::CheckUpgradeMode(message),
|
||||
request_id,
|
||||
},
|
||||
request_id,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn to_bytes(&self) -> Result<Vec<u8>, bincode::Error> {
|
||||
use bincode::Options;
|
||||
make_bincode_serializer().serialize(self)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
pub enum AuthenticatorRequestData {
|
||||
Initial(InitMessage),
|
||||
Final(Box<FinalMessage>),
|
||||
QueryBandwidth(PeerPublicKey),
|
||||
TopUpBandwidth(Box<TopUpMessage>),
|
||||
CheckUpgradeMode(UpgradeModeCheckRequest),
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::super::VERSION;
|
||||
use super::*;
|
||||
use nym_service_provider_requests_common::ServiceProviderType;
|
||||
use std::str::FromStr;
|
||||
|
||||
#[test]
|
||||
fn check_first_bytes_protocol() {
|
||||
let version = VERSION;
|
||||
let data = AuthenticatorRequest {
|
||||
protocol: Protocol {
|
||||
version,
|
||||
service_provider_type: ServiceProviderType::Authenticator,
|
||||
},
|
||||
data: AuthenticatorRequestData::Initial(InitMessage::new(
|
||||
PeerPublicKey::from_str("yvNUDpT5l7W/xDhiu6HkqTHDQwbs/B3J5UrLmORl1EQ=").unwrap(),
|
||||
)),
|
||||
request_id: 1,
|
||||
};
|
||||
let bytes = *data.to_bytes().unwrap().first_chunk::<2>().unwrap();
|
||||
assert_eq!(bytes, [version, ServiceProviderType::Authenticator as u8]);
|
||||
}
|
||||
}
|
||||
@@ -1,153 +0,0 @@
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use super::registration::{RegisteredData, RegistrationData, RemainingBandwidthData};
|
||||
use nym_service_provider_requests_common::Protocol;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::make_bincode_serializer;
|
||||
|
||||
use super::PROTOCOL;
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
pub struct AuthenticatorResponse {
|
||||
pub protocol: Protocol,
|
||||
pub data: AuthenticatorResponseData,
|
||||
}
|
||||
|
||||
impl AuthenticatorResponse {
|
||||
pub fn new_pending_registration_success(
|
||||
registration_data: RegistrationData,
|
||||
request_id: u64,
|
||||
upgrade_mode_enabled: bool,
|
||||
) -> Self {
|
||||
Self {
|
||||
protocol: PROTOCOL,
|
||||
data: AuthenticatorResponseData::PendingRegistration(PendingRegistrationResponse {
|
||||
reply: registration_data,
|
||||
request_id,
|
||||
upgrade_mode_enabled,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_registered(
|
||||
registered_data: RegisteredData,
|
||||
request_id: u64,
|
||||
upgrade_mode_enabled: bool,
|
||||
) -> Self {
|
||||
Self {
|
||||
protocol: PROTOCOL,
|
||||
data: AuthenticatorResponseData::Registered(RegisteredResponse {
|
||||
reply: registered_data,
|
||||
request_id,
|
||||
upgrade_mode_enabled,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_remaining_bandwidth(
|
||||
remaining_bandwidth_data: Option<RemainingBandwidthData>,
|
||||
request_id: u64,
|
||||
upgrade_mode_enabled: bool,
|
||||
) -> Self {
|
||||
Self {
|
||||
protocol: PROTOCOL,
|
||||
data: AuthenticatorResponseData::RemainingBandwidth(RemainingBandwidthResponse {
|
||||
reply: remaining_bandwidth_data,
|
||||
request_id,
|
||||
upgrade_mode_enabled,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_topup_bandwidth(
|
||||
remaining_bandwidth_data: RemainingBandwidthData,
|
||||
request_id: u64,
|
||||
upgrade_mode_enabled: bool,
|
||||
) -> Self {
|
||||
Self {
|
||||
protocol: PROTOCOL,
|
||||
data: AuthenticatorResponseData::TopUpBandwidth(TopUpBandwidthResponse {
|
||||
reply: remaining_bandwidth_data,
|
||||
request_id,
|
||||
upgrade_mode_enabled,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_upgrade_mode_check(request_id: u64, upgrade_mode_enabled: bool) -> Self {
|
||||
Self {
|
||||
protocol: PROTOCOL,
|
||||
data: AuthenticatorResponseData::UpgradeMode(UpgradeModeResponse {
|
||||
request_id,
|
||||
upgrade_mode_enabled,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_bytes(&self) -> Result<Vec<u8>, bincode::Error> {
|
||||
use bincode::Options;
|
||||
make_bincode_serializer().serialize(self)
|
||||
}
|
||||
|
||||
pub fn from_reconstructed_message(
|
||||
message: &nym_sphinx::receiver::ReconstructedMessage,
|
||||
) -> Result<Self, bincode::Error> {
|
||||
use bincode::Options;
|
||||
make_bincode_serializer().deserialize(&message.message)
|
||||
}
|
||||
|
||||
pub fn id(&self) -> Option<u64> {
|
||||
match &self.data {
|
||||
AuthenticatorResponseData::PendingRegistration(response) => Some(response.request_id),
|
||||
AuthenticatorResponseData::Registered(response) => Some(response.request_id),
|
||||
AuthenticatorResponseData::RemainingBandwidth(response) => Some(response.request_id),
|
||||
AuthenticatorResponseData::TopUpBandwidth(response) => Some(response.request_id),
|
||||
AuthenticatorResponseData::UpgradeMode(response) => Some(response.request_id),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
pub enum AuthenticatorResponseData {
|
||||
PendingRegistration(PendingRegistrationResponse),
|
||||
Registered(RegisteredResponse),
|
||||
RemainingBandwidth(RemainingBandwidthResponse),
|
||||
TopUpBandwidth(TopUpBandwidthResponse),
|
||||
UpgradeMode(UpgradeModeResponse),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
pub struct PendingRegistrationResponse {
|
||||
pub request_id: u64,
|
||||
pub reply: RegistrationData,
|
||||
pub upgrade_mode_enabled: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
pub struct RegisteredResponse {
|
||||
pub request_id: u64,
|
||||
pub reply: RegisteredData,
|
||||
pub upgrade_mode_enabled: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
pub struct RemainingBandwidthResponse {
|
||||
pub request_id: u64,
|
||||
pub reply: Option<RemainingBandwidthData>,
|
||||
pub upgrade_mode_enabled: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
pub struct TopUpBandwidthResponse {
|
||||
pub request_id: u64,
|
||||
pub reply: RemainingBandwidthData,
|
||||
pub upgrade_mode_enabled: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
pub struct UpgradeModeResponse {
|
||||
pub request_id: u64,
|
||||
pub upgrade_mode_enabled: bool,
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use nym_credentials_interface::CredentialSpendingData;
|
||||
use nym_wireguard_types::PeerPublicKey;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
|
||||
pub struct TopUpMessage {
|
||||
/// Base64 encoded x25519 public key
|
||||
pub pub_key: PeerPublicKey,
|
||||
|
||||
/// Ecash credential
|
||||
pub credential: CredentialSpendingData,
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
|
||||
#[non_exhaustive]
|
||||
pub enum UpgradeModeCheckRequest {
|
||||
/// Attempt to request upgrade mode recheck via the JWT issued as the result of
|
||||
/// global attestation.json being published
|
||||
UpgradeModeJwt { token: String },
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use super::{v1, v2, v3, v4, v5, v6};
|
||||
use super::{v1, v2, v3, v4, v5};
|
||||
use nym_service_provider_requests_common::{Protocol, ServiceProviderType};
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, strum_macros::Display)]
|
||||
@@ -22,15 +22,11 @@ pub enum AuthenticatorVersion {
|
||||
/// introduced in dorina-patched release (1.6.1)
|
||||
V5,
|
||||
|
||||
/// introduced in niolo release (1.23.0)
|
||||
V6,
|
||||
|
||||
/// an unknown, future, variant that can be present if running outdated software
|
||||
UNKNOWN,
|
||||
}
|
||||
|
||||
impl AuthenticatorVersion {
|
||||
pub const LATEST: Self = Self::V6;
|
||||
pub const LATEST: Self = Self::V5;
|
||||
|
||||
pub const fn release_version(&self) -> semver::Version {
|
||||
match self {
|
||||
@@ -39,7 +35,6 @@ impl AuthenticatorVersion {
|
||||
AuthenticatorVersion::V3 => semver::Version::new(1, 1, 10),
|
||||
AuthenticatorVersion::V4 => semver::Version::new(1, 2, 0),
|
||||
AuthenticatorVersion::V5 => semver::Version::new(1, 6, 1),
|
||||
AuthenticatorVersion::V6 => semver::Version::new(1, 23, 0),
|
||||
AuthenticatorVersion::UNKNOWN => semver::Version::new(0, 0, 0),
|
||||
}
|
||||
}
|
||||
@@ -59,8 +54,6 @@ impl From<Protocol> for AuthenticatorVersion {
|
||||
AuthenticatorVersion::V4
|
||||
} else if value.version == v5::VERSION {
|
||||
AuthenticatorVersion::V5
|
||||
} else if value.version == v6::VERSION {
|
||||
AuthenticatorVersion::V6
|
||||
} else {
|
||||
AuthenticatorVersion::UNKNOWN
|
||||
}
|
||||
@@ -79,8 +72,6 @@ impl From<u8> for AuthenticatorVersion {
|
||||
AuthenticatorVersion::V4
|
||||
} else if value == v5::VERSION {
|
||||
AuthenticatorVersion::V5
|
||||
} else if value == v6::VERSION {
|
||||
AuthenticatorVersion::V6
|
||||
} else {
|
||||
AuthenticatorVersion::UNKNOWN
|
||||
}
|
||||
@@ -135,14 +126,11 @@ impl From<semver::Version> for AuthenticatorVersion {
|
||||
if semver < AuthenticatorVersion::V5.release_version() {
|
||||
return Self::V4;
|
||||
}
|
||||
if semver < AuthenticatorVersion::V6.release_version() {
|
||||
return Self::V5;
|
||||
}
|
||||
// if provided version is higher (or equal) to release version of V6,
|
||||
// we return the latest (i.e. v6)
|
||||
// if provided version is higher (or equal) to release version of V5,
|
||||
// we return the latest (i.e. v5)
|
||||
|
||||
debug_assert_eq!(
|
||||
Self::V6,
|
||||
Self::V5,
|
||||
Self::LATEST,
|
||||
"a new AuthenticatorVersion variant has been introduced without adjusting the `From<semver::Version>` trait"
|
||||
);
|
||||
@@ -203,9 +191,5 @@ mod tests {
|
||||
assert_eq!(AuthenticatorVersion::V5, "1.7.0".into());
|
||||
assert_eq!(AuthenticatorVersion::V5, "1.16.11".into());
|
||||
assert_eq!(AuthenticatorVersion::V5, "1.17.0".into());
|
||||
assert_eq!(AuthenticatorVersion::V5, "1.22.0".into());
|
||||
assert_eq!(AuthenticatorVersion::V6, "1.23.0".into());
|
||||
assert_eq!(AuthenticatorVersion::V6, "1.23.1".into());
|
||||
assert_eq!(AuthenticatorVersion::V6, "1.24.0".into());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,16 +7,21 @@ license.workspace = true
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
async-trait = { workspace = true }
|
||||
async-trait = { workspace = true }
|
||||
bip39 = { workspace = true }
|
||||
log = { workspace = true }
|
||||
rand = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
url = { workspace = true }
|
||||
zeroize = { workspace = true }
|
||||
|
||||
nym-credential-storage = { path = "../credential-storage" }
|
||||
nym-credentials = { path = "../credentials" }
|
||||
nym-credentials-interface = { path = "../credentials-interface" }
|
||||
nym-crypto = { path = "../crypto", features = ["rand", "asymmetric", "stream_cipher", "aes", "hashing"] }
|
||||
nym-ecash-contract-common = { path = "../cosmwasm-smart-contracts/ecash-contract" }
|
||||
nym-ecash-time = { path = "../ecash-time" }
|
||||
nym-network-defaults = { path = "../network-defaults" }
|
||||
nym-task = { path = "../task" }
|
||||
nym-validator-client = { path = "../client-libs/validator-client", default-features = false }
|
||||
|
||||
|
||||
@@ -21,9 +21,6 @@ pub enum BandwidthControllerError {
|
||||
#[error("There was a credential storage error - {0}")]
|
||||
CredentialStorageError(Box<dyn std::error::Error + Send + Sync>),
|
||||
|
||||
#[error("retrieved upgrade mode token is not a valid String")]
|
||||
MalformedUpgradeModeToken,
|
||||
|
||||
#[error("the credential storage does not contain any usable credentials")]
|
||||
NoCredentialsAvailable,
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ use crate::utils::{
|
||||
ApiClientsWrapper,
|
||||
};
|
||||
use log::error;
|
||||
use nym_credential_storage::models::{EmergencyCredential, RetrievedTicketbook};
|
||||
use nym_credential_storage::models::RetrievedTicketbook;
|
||||
use nym_credential_storage::storage::Storage;
|
||||
use nym_credentials::ecash::bandwidth::CredentialSpendingData;
|
||||
use nym_credentials_interface::{
|
||||
@@ -220,19 +220,6 @@ impl<C, St: Storage> BandwidthController<C, St> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn get_emergency_credential(
|
||||
&self,
|
||||
typ: &str,
|
||||
) -> Result<Option<EmergencyCredential>, BandwidthControllerError>
|
||||
where
|
||||
<St as Storage>::StorageError: Send + Sync + 'static,
|
||||
{
|
||||
self.storage
|
||||
.get_emergency_credential(typ)
|
||||
.await
|
||||
.map_err(BandwidthControllerError::credential_storage_error)
|
||||
}
|
||||
}
|
||||
|
||||
impl<C, St> Clone for BandwidthController<C, St>
|
||||
|
||||
@@ -11,9 +11,6 @@ use crate::{error::BandwidthControllerError, BandwidthController, PreparedCreden
|
||||
|
||||
pub const DEFAULT_TICKETS_TO_SPEND: u32 = 1;
|
||||
|
||||
// TODO: this does not really belong here
|
||||
pub const UPGRADE_MODE_JWT_TYPE: &str = "UPGRADE_MODE_JWT";
|
||||
|
||||
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
|
||||
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
|
||||
pub trait BandwidthTicketProvider: Send + Sync {
|
||||
@@ -23,8 +20,6 @@ pub trait BandwidthTicketProvider: Send + Sync {
|
||||
gateway_id: ed25519::PublicKey,
|
||||
tickets_to_spend: u32,
|
||||
) -> Result<PreparedCredential, BandwidthControllerError>;
|
||||
|
||||
async fn get_upgrade_mode_token(&self) -> Result<Option<String>, BandwidthControllerError>;
|
||||
}
|
||||
|
||||
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
|
||||
@@ -44,16 +39,4 @@ where
|
||||
self.prepare_ecash_ticket(ticket_type, gateway_id.to_bytes(), tickets_to_spend)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn get_upgrade_mode_token(&self) -> Result<Option<String>, BandwidthControllerError> {
|
||||
let Some(emergency_credential) =
|
||||
self.get_emergency_credential(UPGRADE_MODE_JWT_TYPE).await?
|
||||
else {
|
||||
return Ok(None);
|
||||
};
|
||||
// upgrade mode credential is just a simple stringified JWT
|
||||
let token = String::from_utf8(emergency_credential.data.content)
|
||||
.map_err(|_| BandwidthControllerError::MalformedUpgradeModeToken)?;
|
||||
Ok(Some(token))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,10 +81,6 @@ pub struct CommonClientInitArgs {
|
||||
#[cfg_attr(feature = "cli", clap(long, hide = true))]
|
||||
pub enabled_credentials_mode: Option<bool>,
|
||||
|
||||
/// Change the default minimum node performance used during initial node selection filtering.
|
||||
#[cfg_attr(feature = "cli", clap(long, hide = true))]
|
||||
pub minimum_gateway_performance: Option<u8>,
|
||||
|
||||
/// Mostly debug-related option to increase default traffic rate so that you would not need to
|
||||
/// modify config post init
|
||||
#[cfg_attr(feature = "cli", clap(long, hide = true))]
|
||||
@@ -177,14 +173,10 @@ where
|
||||
})?;
|
||||
hardcoded_topology.entry_capable_nodes().cloned().collect()
|
||||
} else {
|
||||
let minimum_performance = common_args
|
||||
.minimum_gateway_performance
|
||||
.unwrap_or(core.debug.topology.minimum_gateway_performance);
|
||||
|
||||
crate::init::helpers::gateways_for_init(
|
||||
&core.client.nym_api_urls,
|
||||
user_agent,
|
||||
minimum_performance,
|
||||
core.debug.topology.minimum_gateway_performance,
|
||||
core.debug.topology.ignore_ingress_epoch_role,
|
||||
None,
|
||||
)
|
||||
|
||||
@@ -45,7 +45,7 @@ pub enum ClientCoreError {
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
#[error("resolution failed: {0}")]
|
||||
ResolutionFailed(#[from] nym_http_api_client::ResolveError),
|
||||
ResolutionFailed(#[from] nym_http_api_client::HickoryDnsError),
|
||||
|
||||
#[error("no gateways on network")]
|
||||
NoGatewaysOnNetwork,
|
||||
|
||||
@@ -30,6 +30,7 @@ pub(crate) async fn connect_async(
|
||||
resolver
|
||||
.resolve_str(domain)
|
||||
.await?
|
||||
.into_iter()
|
||||
.map(|a| SocketAddr::new(a, port))
|
||||
.collect()
|
||||
}
|
||||
|
||||
@@ -88,6 +88,3 @@ features = ["js"]
|
||||
|
||||
[features]
|
||||
wasm = []
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
@@ -2,7 +2,6 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use si_scale::helpers::bibytes2;
|
||||
use std::fmt::{Display, Formatter};
|
||||
use std::sync::atomic::{AtomicBool, AtomicI64, Ordering};
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
@@ -27,39 +26,6 @@ pub struct ClientBandwidth {
|
||||
inner: Arc<ClientBandwidthInner>,
|
||||
}
|
||||
|
||||
// simple helper for logging purposes to accommodate 'unknown' case
|
||||
pub(crate) enum UpgradeModeEnabledWrapper {
|
||||
True,
|
||||
False,
|
||||
Unknown,
|
||||
}
|
||||
|
||||
impl From<Option<bool>> for UpgradeModeEnabledWrapper {
|
||||
fn from(value: Option<bool>) -> Self {
|
||||
match value {
|
||||
Some(true) => UpgradeModeEnabledWrapper::True,
|
||||
Some(false) => UpgradeModeEnabledWrapper::False,
|
||||
None => UpgradeModeEnabledWrapper::Unknown,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<bool> for UpgradeModeEnabledWrapper {
|
||||
fn from(value: bool) -> Self {
|
||||
Some(value).into()
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for UpgradeModeEnabledWrapper {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
UpgradeModeEnabledWrapper::True => write!(f, "true"),
|
||||
UpgradeModeEnabledWrapper::False => write!(f, "false"),
|
||||
UpgradeModeEnabledWrapper::Unknown => write!(f, "unknown"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct ClientBandwidthInner {
|
||||
/// the actual bandwidth amount (in bytes) available
|
||||
available: AtomicI64,
|
||||
@@ -105,41 +71,26 @@ impl ClientBandwidth {
|
||||
self.inner.available.load(Ordering::Acquire)
|
||||
}
|
||||
|
||||
pub(crate) fn maybe_log_bandwidth(
|
||||
&self,
|
||||
now: Option<OffsetDateTime>,
|
||||
upgrade_mode: impl Into<UpgradeModeEnabledWrapper>,
|
||||
) {
|
||||
pub(crate) fn maybe_log_bandwidth(&self, now: Option<OffsetDateTime>) {
|
||||
let last = self.last_logged();
|
||||
let now = now.unwrap_or_else(OffsetDateTime::now_utc);
|
||||
if last + Duration::from_secs(10) < now {
|
||||
self.log_bandwidth(Some(now), upgrade_mode)
|
||||
self.log_bandwidth(Some(now))
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn log_bandwidth(
|
||||
&self,
|
||||
now: Option<OffsetDateTime>,
|
||||
upgrade_mode: impl Into<UpgradeModeEnabledWrapper>,
|
||||
) {
|
||||
pub(crate) fn log_bandwidth(&self, now: Option<OffsetDateTime>) {
|
||||
let now = now.unwrap_or_else(OffsetDateTime::now_utc);
|
||||
let upgrade_mode = upgrade_mode.into();
|
||||
|
||||
let remaining = self.remaining();
|
||||
let remaining_bi2 = bibytes2(remaining as f64);
|
||||
|
||||
if remaining < 0 {
|
||||
tracing::warn!(
|
||||
"OUT OF BANDWIDTH. remaining: {remaining_bi2}. in 'upgrade mode': {upgrade_mode}"
|
||||
);
|
||||
tracing::warn!("OUT OF BANDWIDTH. remaining: {remaining_bi2}");
|
||||
} else if remaining < 1_000_000 {
|
||||
tracing::info!(
|
||||
"remaining bandwidth: {remaining_bi2}. in 'upgrade mode': {upgrade_mode}"
|
||||
);
|
||||
tracing::info!("remaining bandwidth: {remaining_bi2}");
|
||||
} else {
|
||||
tracing::trace!(
|
||||
"remaining bandwidth: {remaining_bi2}. in 'upgrade mode': {upgrade_mode}"
|
||||
);
|
||||
tracing::debug!("remaining bandwidth: {remaining_bi2}");
|
||||
}
|
||||
|
||||
self.inner
|
||||
@@ -147,35 +98,26 @@ impl ClientBandwidth {
|
||||
.store(now.unix_timestamp(), Ordering::Relaxed)
|
||||
}
|
||||
|
||||
pub(crate) fn update_and_maybe_log(
|
||||
&self,
|
||||
remaining: i64,
|
||||
upgrade_mode: impl Into<UpgradeModeEnabledWrapper>,
|
||||
) {
|
||||
pub(crate) fn update_and_maybe_log(&self, remaining: i64) {
|
||||
let now = OffsetDateTime::now_utc();
|
||||
self.inner.available.store(remaining, Ordering::Release);
|
||||
self.inner
|
||||
.last_updated_ts
|
||||
.store(now.unix_timestamp(), Ordering::Relaxed);
|
||||
self.maybe_log_bandwidth(Some(now), upgrade_mode)
|
||||
self.maybe_log_bandwidth(Some(now))
|
||||
}
|
||||
|
||||
pub(crate) fn update_and_log(
|
||||
&self,
|
||||
remaining: i64,
|
||||
upgrade_mode: impl Into<UpgradeModeEnabledWrapper>,
|
||||
) {
|
||||
pub(crate) fn update_and_log(&self, remaining: i64) {
|
||||
let now = OffsetDateTime::now_utc();
|
||||
self.inner.available.store(remaining, Ordering::Release);
|
||||
self.inner
|
||||
.last_updated_ts
|
||||
.store(now.unix_timestamp(), Ordering::Relaxed);
|
||||
self.log_bandwidth(Some(now), upgrade_mode)
|
||||
self.log_bandwidth(Some(now))
|
||||
}
|
||||
|
||||
fn last_logged(&self) -> OffsetDateTime {
|
||||
// SAFETY: this value is always populated with valid timestamps
|
||||
#[allow(clippy::unwrap_used)]
|
||||
OffsetDateTime::from_unix_timestamp(self.inner.last_logged_ts.load(Ordering::Relaxed))
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::error::GatewayClientError;
|
||||
use nym_credentials_interface::DEFAULT_MIXNET_REQUEST_BANDWIDTH_THRESHOLD;
|
||||
use nym_network_defaults::TicketTypeRepr::V1MixnetEntry;
|
||||
use si_scale::helpers::bibytes2;
|
||||
use std::time::Duration;
|
||||
|
||||
@@ -103,7 +103,7 @@ impl BandwidthTickets {
|
||||
|
||||
// 20% of entry ticket value
|
||||
pub const DEFAULT_REMAINING_BANDWIDTH_THRESHOLD: i64 =
|
||||
DEFAULT_MIXNET_REQUEST_BANDWIDTH_THRESHOLD;
|
||||
(V1MixnetEntry.bandwidth_value() / 5) as i64;
|
||||
|
||||
pub const DEFAULT_CUTOFF_REMAINING_BANDWIDTH_THRESHOLD: Option<i64> = None;
|
||||
|
||||
|
||||
@@ -20,9 +20,9 @@ use nym_credentials_interface::TicketType;
|
||||
use nym_crypto::asymmetric::ed25519;
|
||||
use nym_gateway_requests::registration::handshake::client_handshake;
|
||||
use nym_gateway_requests::{
|
||||
BandwidthResponse, BinaryRequest, ClientControlRequest, ClientRequest, GatewayProtocolVersion,
|
||||
GatewayProtocolVersionExt, GatewayRequestsError, SensitiveServerResponse, ServerResponse,
|
||||
SharedGatewayKey, SharedSymmetricKey, CREDENTIAL_UPDATE_V2_PROTOCOL_VERSION,
|
||||
BinaryRequest, ClientControlRequest, ClientRequest, GatewayProtocolVersionExt,
|
||||
GatewayRequestsError, SensitiveServerResponse, ServerResponse, SharedGatewayKey,
|
||||
SharedSymmetricKey, CREDENTIAL_UPDATE_V2_PROTOCOL_VERSION, CURRENT_PROTOCOL_VERSION,
|
||||
};
|
||||
use nym_sphinx::forwarding::packet::MixPacket;
|
||||
use nym_statistics_common::clients::connection::ConnectionStatsEvent;
|
||||
@@ -101,7 +101,8 @@ pub struct GatewayClient<C, St = EphemeralCredentialStorage> {
|
||||
bandwidth_controller: Option<BandwidthController<C, St>>,
|
||||
stats_reporter: ClientStatsSender,
|
||||
|
||||
negotiated_protocol: Option<GatewayProtocolVersion>,
|
||||
// currently unused (but populated)
|
||||
negotiated_protocol: Option<u8>,
|
||||
|
||||
// Callback on the fd as soon as the connection has been established
|
||||
#[cfg(unix)]
|
||||
@@ -165,12 +166,10 @@ impl<C, St> GatewayClient<C, St> {
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
#[allow(clippy::unreachable)]
|
||||
async fn _close_connection(&mut self) -> Result<(), GatewayClientError> {
|
||||
match std::mem::replace(&mut self.connection, SocketState::NotConnected) {
|
||||
SocketState::Available(mut socket) => Ok((*socket).close(None).await?),
|
||||
SocketState::PartiallyDelegated(_) => {
|
||||
// SAFETY: this is only called after the caller has already recovered the connection
|
||||
unreachable!("this branch should have never been reached!")
|
||||
}
|
||||
_ => Ok(()), // no need to do anything in those cases
|
||||
@@ -178,7 +177,6 @@ impl<C, St> GatewayClient<C, St> {
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
#[allow(clippy::unreachable)]
|
||||
async fn _close_connection(&mut self) -> Result<(), GatewayClientError> {
|
||||
match std::mem::replace(&mut self.connection, SocketState::NotConnected) {
|
||||
SocketState::Available(socket) => {
|
||||
@@ -186,7 +184,6 @@ impl<C, St> GatewayClient<C, St> {
|
||||
Ok(())
|
||||
}
|
||||
SocketState::PartiallyDelegated(_) => {
|
||||
// SAFETY: this is only called after the caller has already recovered the connection
|
||||
unreachable!("this branch should have never been reached!")
|
||||
}
|
||||
_ => Ok(()), // no need to do anything in those cases
|
||||
@@ -461,16 +458,43 @@ impl<C, St> GatewayClient<C, St> {
|
||||
}
|
||||
}
|
||||
|
||||
fn check_gateway_protocol(
|
||||
&self,
|
||||
gateway_protocol: Option<u8>,
|
||||
) -> Result<(), GatewayClientError> {
|
||||
debug!("gateway protocol: {gateway_protocol:?}, ours: {CURRENT_PROTOCOL_VERSION}");
|
||||
|
||||
// right now there are no failure cases here, but this might change in the future
|
||||
match gateway_protocol {
|
||||
None => {
|
||||
warn!("the gateway we're connected to has not specified its protocol version. It's probably running version < 1.1.X, but that's still fine for now. It will become a hard error in 1.2.0");
|
||||
// note: in +1.2.0 we will have to return a hard error here
|
||||
Ok(())
|
||||
}
|
||||
Some(v) if v > CURRENT_PROTOCOL_VERSION => {
|
||||
let err = GatewayClientError::IncompatibleProtocol {
|
||||
gateway: Some(v),
|
||||
current: CURRENT_PROTOCOL_VERSION,
|
||||
};
|
||||
error!("{err}");
|
||||
Err(err)
|
||||
}
|
||||
|
||||
Some(_) => {
|
||||
debug!("the gateway is using exactly the same (or older) protocol version as we are. We're good to continue!");
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn register(
|
||||
&mut self,
|
||||
supported_gateway_protocol: Option<GatewayProtocolVersion>,
|
||||
derive_aes256_gcm_siv_key: bool,
|
||||
) -> Result<(), GatewayClientError> {
|
||||
if !self.connection.is_established() {
|
||||
return Err(GatewayClientError::ConnectionNotEstablished);
|
||||
}
|
||||
|
||||
let derive_aes256_gcm_siv_key = supported_gateway_protocol.supports_aes256_gcm_siv();
|
||||
|
||||
debug_assert!(self.connection.is_available());
|
||||
log::debug!(
|
||||
"registering with gateway. using legacy key derivation: {}",
|
||||
@@ -481,13 +505,14 @@ impl<C, St> GatewayClient<C, St> {
|
||||
// and putting it into the GatewayClient struct would be a hassle
|
||||
let mut rng = OsRng;
|
||||
|
||||
let handshake_result = match &mut self.connection {
|
||||
let shared_key = match &mut self.connection {
|
||||
SocketState::Available(ws_stream) => client_handshake(
|
||||
&mut rng,
|
||||
ws_stream,
|
||||
self.local_identity.as_ref(),
|
||||
self.gateway_identity,
|
||||
supported_gateway_protocol,
|
||||
self.cfg.bandwidth.require_tickets,
|
||||
derive_aes256_gcm_siv_key,
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
self.shutdown_token.clone(),
|
||||
)
|
||||
@@ -496,31 +521,26 @@ impl<C, St> GatewayClient<C, St> {
|
||||
_ => return Err(GatewayClientError::ConnectionInInvalidState),
|
||||
}?;
|
||||
|
||||
let authentication_status = match self.read_control_response().await? {
|
||||
let (authentication_status, gateway_protocol) = match self.read_control_response().await? {
|
||||
ServerResponse::Register {
|
||||
protocol_version,
|
||||
status,
|
||||
upgrade_mode,
|
||||
..
|
||||
} => {
|
||||
if upgrade_mode {
|
||||
warn!("the system is currently undergoing an upgrade. some of its functionalities might be unstable")
|
||||
}
|
||||
status
|
||||
}
|
||||
} => (status, protocol_version),
|
||||
ServerResponse::Error { message } => {
|
||||
return Err(GatewayClientError::GatewayError(message))
|
||||
}
|
||||
other => return Err(GatewayClientError::UnexpectedResponse { name: other.name() }),
|
||||
};
|
||||
|
||||
self.check_gateway_protocol(gateway_protocol)?;
|
||||
self.authenticated = authentication_status;
|
||||
|
||||
if self.authenticated {
|
||||
self.shared_key = Some(Arc::new(handshake_result.derived_key));
|
||||
self.shared_key = Some(Arc::new(shared_key));
|
||||
}
|
||||
|
||||
// populate the negotiated protocol for future uses
|
||||
self.negotiated_protocol = Some(handshake_result.negotiated_protocol);
|
||||
self.negotiated_protocol = gateway_protocol;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -603,24 +623,13 @@ impl<C, St> GatewayClient<C, St> {
|
||||
protocol_version,
|
||||
status,
|
||||
bandwidth_remaining,
|
||||
upgrade_mode,
|
||||
} => {
|
||||
if protocol_version.is_future_version() {
|
||||
// SAFETY: future version is always defined
|
||||
#[allow(clippy::unwrap_used)]
|
||||
let version = protocol_version.unwrap();
|
||||
error!("the gateway insists on using v{version} protocol which is not supported by this client");
|
||||
return Err(GatewayClientError::AuthenticationFailure);
|
||||
}
|
||||
self.check_gateway_protocol(protocol_version)?;
|
||||
self.authenticated = status;
|
||||
self.bandwidth
|
||||
.update_and_maybe_log(bandwidth_remaining, upgrade_mode);
|
||||
self.bandwidth.update_and_maybe_log(bandwidth_remaining);
|
||||
|
||||
self.negotiated_protocol = protocol_version;
|
||||
log::debug!("authenticated: {status}, bandwidth remaining: {bandwidth_remaining}");
|
||||
if upgrade_mode {
|
||||
warn!("the system is currently undergoing an upgrade. some of its functionalities might be unstable")
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -641,7 +650,7 @@ impl<C, St> GatewayClient<C, St> {
|
||||
.public_key()
|
||||
.derive_destination_address();
|
||||
|
||||
let msg = ClientControlRequest::new_legacy_authenticate(
|
||||
let msg = ClientControlRequest::new_authenticate(
|
||||
self_address,
|
||||
shared_key,
|
||||
self.cfg.bandwidth.require_tickets,
|
||||
@@ -650,40 +659,25 @@ impl<C, St> GatewayClient<C, St> {
|
||||
.await
|
||||
}
|
||||
|
||||
async fn authenticate_v2(
|
||||
&mut self,
|
||||
requested_protocol_version: GatewayProtocolVersion,
|
||||
) -> Result<(), GatewayClientError> {
|
||||
async fn authenticate_v2(&mut self) -> Result<(), GatewayClientError> {
|
||||
debug!("using v2 authentication");
|
||||
let Some(shared_key) = self.shared_key.as_ref() else {
|
||||
return Err(GatewayClientError::NoSharedKeyAvailable);
|
||||
};
|
||||
|
||||
let msg = ClientControlRequest::new_authenticate_v2(
|
||||
shared_key,
|
||||
&self.local_identity,
|
||||
requested_protocol_version,
|
||||
)?;
|
||||
let msg = ClientControlRequest::new_authenticate_v2(shared_key, &self.local_identity)?;
|
||||
self.send_authenticate_request_and_handle_response(msg)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn authenticate(
|
||||
&mut self,
|
||||
supported_gateway_protocol: Option<GatewayProtocolVersion>,
|
||||
) -> Result<(), GatewayClientError> {
|
||||
async fn authenticate(&mut self, use_v2: bool) -> Result<(), GatewayClientError> {
|
||||
if !self.connection.is_established() {
|
||||
return Err(GatewayClientError::ConnectionNotEstablished);
|
||||
}
|
||||
debug!("authenticating with gateway");
|
||||
|
||||
if supported_gateway_protocol.supports_authenticate_v2() {
|
||||
// use the highest possible protocol version the gateway has announced support for
|
||||
|
||||
// SAFETY: if announced protocol supports auth v2, it means it's properly set
|
||||
#[allow(clippy::unwrap_used)]
|
||||
self.authenticate_v2(supported_gateway_protocol.unwrap())
|
||||
.await
|
||||
if use_v2 {
|
||||
self.authenticate_v2().await
|
||||
} else {
|
||||
self.authenticate_v1().await
|
||||
}
|
||||
@@ -714,12 +708,9 @@ impl<C, St> GatewayClient<C, St> {
|
||||
}
|
||||
};
|
||||
|
||||
debug!("supported gateway protocol: {gw_protocol:?}");
|
||||
|
||||
let supports_aes_gcm_siv = gw_protocol.supports_aes256_gcm_siv();
|
||||
let supports_auth_v2 = gw_protocol.supports_authenticate_v2();
|
||||
let supports_key_rotation_info = gw_protocol.supports_key_rotation_packet();
|
||||
let supports_upgrade_mode = gw_protocol.supports_upgrade_mode();
|
||||
|
||||
if !supports_aes_gcm_siv {
|
||||
warn!("this gateway is on an old version that doesn't support AES256-GCM-SIV");
|
||||
@@ -730,16 +721,6 @@ impl<C, St> GatewayClient<C, St> {
|
||||
if !supports_key_rotation_info {
|
||||
warn!("this gateway is on an old version that doesn't support key rotation packets")
|
||||
}
|
||||
if !supports_upgrade_mode {
|
||||
warn!("this gateway is on an old version that doesn't support upgrade mode")
|
||||
}
|
||||
|
||||
let gw_protocol = if gw_protocol.is_future_version() {
|
||||
warn!("we're running outdated software as gateway is announcing protocol {gw_protocol:?} whilst we're using {}. we're going to attempt to downgrade", GatewayProtocolVersion::CURRENT);
|
||||
Some(GatewayProtocolVersion::CURRENT)
|
||||
} else {
|
||||
gw_protocol
|
||||
};
|
||||
|
||||
if self.authenticated {
|
||||
debug!("Already authenticated");
|
||||
@@ -754,11 +735,10 @@ impl<C, St> GatewayClient<C, St> {
|
||||
}
|
||||
|
||||
if self.shared_key.is_some() {
|
||||
self.authenticate(gw_protocol).await?;
|
||||
self.authenticate(supports_auth_v2).await?;
|
||||
|
||||
if self.authenticated {
|
||||
// if we are authenticated it means we MUST have an associated shared_key
|
||||
#[allow(clippy::unwrap_used)]
|
||||
let shared_key = self.shared_key.as_ref().unwrap();
|
||||
|
||||
let requires_key_upgrade = shared_key.is_legacy() && supports_aes_gcm_siv;
|
||||
@@ -771,10 +751,9 @@ impl<C, St> GatewayClient<C, St> {
|
||||
Err(GatewayClientError::AuthenticationFailure)
|
||||
}
|
||||
} else {
|
||||
self.register(gw_protocol).await?;
|
||||
self.register(supports_aes_gcm_siv).await?;
|
||||
|
||||
// if registration didn't return an error, we MUST have an associated shared key
|
||||
#[allow(clippy::unwrap_used)]
|
||||
let shared_key = self.shared_key.as_ref().unwrap();
|
||||
|
||||
// we're always registering with the highest supported protocol,
|
||||
@@ -804,81 +783,51 @@ impl<C, St> GatewayClient<C, St> {
|
||||
}
|
||||
}
|
||||
|
||||
async fn wait_for_bandwidth_response(
|
||||
async fn claim_ecash_bandwidth(
|
||||
&mut self,
|
||||
msg: ClientControlRequest,
|
||||
) -> Result<BandwidthResponse, GatewayClientError> {
|
||||
let response = match self
|
||||
credential: CredentialSpendingData,
|
||||
) -> Result<(), GatewayClientError> {
|
||||
let msg = ClientControlRequest::new_enc_ecash_credential(
|
||||
credential,
|
||||
self.shared_key.as_ref().unwrap(),
|
||||
)?;
|
||||
let bandwidth_remaining = match self
|
||||
.send_websocket_message_with_non_send_response(msg)
|
||||
.await?
|
||||
{
|
||||
ServerResponse::Bandwidth(response) => {
|
||||
if response.upgrade_mode {
|
||||
info!("the system is currently undergoing an upgrade. our bandwidth shouldn't have been metered")
|
||||
}
|
||||
Ok(response)
|
||||
}
|
||||
ServerResponse::Bandwidth { available_total } => Ok(available_total),
|
||||
ServerResponse::Error { message } => Err(GatewayClientError::GatewayError(message)),
|
||||
ServerResponse::TypedError { error } => {
|
||||
Err(GatewayClientError::TypedGatewayError(error))
|
||||
}
|
||||
other => Err(GatewayClientError::UnexpectedResponse { name: other.name() }),
|
||||
}?;
|
||||
Ok(response)
|
||||
}
|
||||
|
||||
async fn claim_ecash_bandwidth(
|
||||
&mut self,
|
||||
credential: CredentialSpendingData,
|
||||
) -> Result<(), GatewayClientError> {
|
||||
// SAFETY: claiming ecash bandwidth is called as part of `claim_bandwidth` which
|
||||
// ensures the shared key is defined
|
||||
#[allow(clippy::unwrap_used)]
|
||||
let msg = ClientControlRequest::new_enc_ecash_credential(
|
||||
credential,
|
||||
self.shared_key.as_ref().unwrap(),
|
||||
)?;
|
||||
let response = self.wait_for_bandwidth_response(msg).await?;
|
||||
|
||||
// TODO: create tracing span
|
||||
info!("managed to claim ecash bandwidth");
|
||||
self.bandwidth
|
||||
.update_and_log(response.available_total, response.upgrade_mode);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn send_upgrade_mode_jwt(&mut self, token: String) -> Result<(), GatewayClientError> {
|
||||
let msg = ClientControlRequest::new_upgrade_mode_jwt(token);
|
||||
let response = self.wait_for_bandwidth_response(msg).await?;
|
||||
|
||||
// if gateway rejected our jwt, we would have returned an error
|
||||
info!("gateway has accepted our jwt");
|
||||
if !response.upgrade_mode {
|
||||
error!("but we're not in upgrade mode - something is wrong!");
|
||||
return Err(GatewayClientError::UnexpectedUpgradeModeState);
|
||||
}
|
||||
|
||||
self.bandwidth
|
||||
.update_and_log(response.available_total, response.upgrade_mode);
|
||||
self.bandwidth.update_and_log(bandwidth_remaining);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn try_claim_testnet_bandwidth(&mut self) -> Result<(), GatewayClientError> {
|
||||
let msg = ClientControlRequest::ClaimFreeTestnetBandwidth;
|
||||
let response = self.wait_for_bandwidth_response(msg).await?;
|
||||
let bandwidth_remaining = match self
|
||||
.send_websocket_message_with_non_send_response(msg)
|
||||
.await?
|
||||
{
|
||||
ServerResponse::Bandwidth { available_total } => Ok(available_total),
|
||||
ServerResponse::Error { message } => Err(GatewayClientError::GatewayError(message)),
|
||||
other => Err(GatewayClientError::UnexpectedResponse { name: other.name() }),
|
||||
}?;
|
||||
|
||||
info!("managed to claim testnet bandwidth");
|
||||
self.bandwidth
|
||||
.update_and_log(response.available_total, response.upgrade_mode);
|
||||
self.bandwidth.update_and_log(bandwidth_remaining);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn unchecked_bandwidth_controller(&self) -> &BandwidthController<C, St> {
|
||||
// this is an unchecked method
|
||||
#[allow(clippy::unwrap_used)]
|
||||
self.bandwidth_controller.as_ref().unwrap()
|
||||
}
|
||||
|
||||
@@ -970,7 +919,6 @@ impl<C, St> GatewayClient<C, St> {
|
||||
BinaryRequest::ForwardSphinx { packet }
|
||||
};
|
||||
|
||||
#[allow(clippy::expect_used)]
|
||||
req.into_ws_message(
|
||||
self.shared_key
|
||||
.as_ref()
|
||||
@@ -1077,8 +1025,6 @@ impl<C, St> GatewayClient<C, St> {
|
||||
self.send_with_reconnection_on_failure(msg).await
|
||||
}
|
||||
|
||||
// SAFETY: this method is only called when the connection is in `PartiallyDelegated` state
|
||||
#[allow(clippy::unreachable)]
|
||||
async fn recover_socket_connection(&mut self) -> Result<(), GatewayClientError> {
|
||||
if self.connection.is_available() {
|
||||
return Ok(());
|
||||
@@ -1108,7 +1054,6 @@ impl<C, St> GatewayClient<C, St> {
|
||||
return Err(GatewayClientError::ConnectionInInvalidState);
|
||||
}
|
||||
|
||||
#[allow(clippy::expect_used)]
|
||||
let partially_delegated =
|
||||
match std::mem::replace(&mut self.connection, SocketState::Invalid) {
|
||||
SocketState::Available(conn) => {
|
||||
@@ -1124,13 +1069,7 @@ impl<C, St> GatewayClient<C, St> {
|
||||
self.shutdown_token.clone(),
|
||||
)
|
||||
}
|
||||
other => {
|
||||
error!(
|
||||
"attempted to start mixnet listener whilst the connection is in {} state!",
|
||||
other.name()
|
||||
);
|
||||
return Err(GatewayClientError::ConnectionInInvalidState);
|
||||
}
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
self.connection = SocketState::PartiallyDelegated(partially_delegated);
|
||||
@@ -1143,7 +1082,8 @@ impl<C, St> GatewayClient<C, St> {
|
||||
}
|
||||
|
||||
// if we're reconnecting, because we lost connection, we need to re-authenticate the connection
|
||||
self.authenticate(self.negotiated_protocol).await?;
|
||||
self.authenticate(self.negotiated_protocol.supports_authenticate_v2())
|
||||
.await?;
|
||||
|
||||
// this call is NON-blocking
|
||||
self.start_listening_for_mixnet_messages()?;
|
||||
|
||||
@@ -39,6 +39,7 @@ pub(crate) async fn connect_async(
|
||||
resolver
|
||||
.resolve_str(domain)
|
||||
.await?
|
||||
.into_iter()
|
||||
.map(|a| SocketAddr::new(a, port))
|
||||
.collect()
|
||||
}
|
||||
|
||||
@@ -54,7 +54,7 @@ pub enum GatewayClientError {
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
#[error("resolution failed: {0}")]
|
||||
ResolutionFailed(#[from] nym_http_api_client::ResolveError),
|
||||
ResolutionFailed(#[from] nym_http_api_client::HickoryDnsError),
|
||||
|
||||
#[error("No shared key was provided or obtained")]
|
||||
NoSharedKeyAvailable,
|
||||
@@ -128,9 +128,6 @@ pub enum GatewayClientError {
|
||||
"this operation couldn't be completed as the program is in the process of shutting down"
|
||||
)]
|
||||
ShutdownInProgress,
|
||||
|
||||
#[error("the system is an unexpected upgrade mode state")]
|
||||
UnexpectedUpgradeModeState,
|
||||
}
|
||||
|
||||
impl From<WsError> for GatewayClientError {
|
||||
|
||||
@@ -35,7 +35,6 @@ impl PacketRouter {
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::panic)]
|
||||
pub fn route_mixnet_messages(
|
||||
&self,
|
||||
received_messages: Vec<Vec<u8>>,
|
||||
@@ -55,7 +54,6 @@ impl PacketRouter {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[allow(clippy::panic)]
|
||||
pub fn route_acks(&self, received_acks: Vec<Vec<u8>>) -> Result<(), GatewayClientError> {
|
||||
if let Err(err) = self.ack_sender.unbounded_send(received_acks) {
|
||||
// check if the failure is due to the shutdown being in progress and thus the receiver channel
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::bandwidth::ClientBandwidth;
|
||||
use crate::client::config::BandwidthTickets;
|
||||
use crate::error::GatewayClientError;
|
||||
use crate::packet_router::PacketRouter;
|
||||
use crate::traits::GatewayPacketRouter;
|
||||
@@ -11,9 +10,7 @@ use futures::channel::oneshot;
|
||||
use futures::stream::{SplitSink, SplitStream};
|
||||
use futures::{SinkExt, StreamExt};
|
||||
use nym_gateway_requests::shared_key::SharedGatewayKey;
|
||||
use nym_gateway_requests::{
|
||||
SendResponse, SensitiveServerResponse, ServerResponse, SimpleGatewayRequestsError,
|
||||
};
|
||||
use nym_gateway_requests::{SensitiveServerResponse, ServerResponse, SimpleGatewayRequestsError};
|
||||
use nym_task::ShutdownToken;
|
||||
use si_scale::helpers::bibytes2;
|
||||
use std::os::raw::c_int as RawFd;
|
||||
@@ -157,12 +154,11 @@ impl PartiallyDelegatedRouter {
|
||||
fn handle_text_message(&self, text: String) -> Result<(), GatewayClientError> {
|
||||
// if we fail to deserialise the response, return a hard error. we can't handle garbage
|
||||
match ServerResponse::try_from(text).map_err(|_| GatewayClientError::MalformedResponse)? {
|
||||
ServerResponse::Send(SendResponse {
|
||||
ServerResponse::Send {
|
||||
remaining_bandwidth,
|
||||
upgrade_mode,
|
||||
}) => {
|
||||
} => {
|
||||
self.client_bandwidth
|
||||
.update_and_maybe_log(remaining_bandwidth, upgrade_mode);
|
||||
.update_and_maybe_log(remaining_bandwidth);
|
||||
Ok(())
|
||||
}
|
||||
ServerResponse::Error { message } => {
|
||||
@@ -178,20 +174,7 @@ impl PartiallyDelegatedRouter {
|
||||
let available_bi2 = bibytes2(available as f64);
|
||||
let required_bi2 = bibytes2(required as f64);
|
||||
warn!("run out of bandwidth when attempting to send the message! we got {available_bi2} available, but needed at least {required_bi2} to send the previous message");
|
||||
// if we run out of bandwidth (and tried to send reasonable amount of data),
|
||||
// the upgrade mode is implicitly disabled, as otherwise we would have been
|
||||
// to proceed
|
||||
let upgrade_mode = if available
|
||||
< BandwidthTickets::DEFAULT_REMAINING_BANDWIDTH_THRESHOLD
|
||||
{
|
||||
Some(false)
|
||||
} else {
|
||||
// we were attempting to send a lot of data at once
|
||||
// - we have no certainty about upgrade mode at this point
|
||||
None
|
||||
};
|
||||
self.client_bandwidth
|
||||
.update_and_log(available, upgrade_mode);
|
||||
self.client_bandwidth.update_and_log(available);
|
||||
// UNIMPLEMENTED: we should stop sending messages until we recover bandwidth
|
||||
Ok(())
|
||||
}
|
||||
@@ -344,7 +327,6 @@ impl PartiallyDelegatedHandle {
|
||||
Ok(self.sink_half.send_all(&mut send_stream).await?)
|
||||
}
|
||||
|
||||
#[allow(clippy::panic)]
|
||||
pub(crate) async fn merge(self) -> Result<WsConn, GatewayClientError> {
|
||||
let (mut stream_receiver, notify) = self.delegated_stream;
|
||||
|
||||
@@ -373,10 +355,8 @@ impl PartiallyDelegatedHandle {
|
||||
// in receive_res
|
||||
.map_err(|_| GatewayClientError::ConnectionAbruptlyClosed)?;
|
||||
let stream = stream_results?;
|
||||
|
||||
// the error is thrown when trying to reunite sink and stream that did not originate
|
||||
// from the same split which is impossible to happen here
|
||||
#[allow(clippy::unwrap_used)]
|
||||
Ok(self.sink_half.reunite(stream).unwrap())
|
||||
}
|
||||
}
|
||||
@@ -407,13 +387,4 @@ impl SocketState {
|
||||
SocketState::Available(_) | SocketState::PartiallyDelegated(_)
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) fn name(&self) -> &'static str {
|
||||
match self {
|
||||
SocketState::Available(_) => "available",
|
||||
SocketState::PartiallyDelegated(_) => "partially delegated",
|
||||
SocketState::NotConnected => "not connected",
|
||||
SocketState::Invalid => "invalid",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
// Copyright 2025 Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use nym_crypto::asymmetric::ed25519;
|
||||
use nym_ecash_signer_check::SignerCheckError;
|
||||
use nym_validator_client::coconut::EcashApiError;
|
||||
use nym_validator_client::nym_api::{EpochId, error::NymAPIError};
|
||||
@@ -169,24 +168,6 @@ pub enum CredentialProxyError {
|
||||
device_id: String,
|
||||
credential_id: String,
|
||||
},
|
||||
|
||||
#[error(
|
||||
"the attestation check url has not been provided through either the CLI nor the default .env config"
|
||||
)]
|
||||
AttestationCheckUrlNotSet,
|
||||
|
||||
#[error("the provided attester public key is malformed: {source}")]
|
||||
MalformedAttestationCheckUrl { source: url::ParseError },
|
||||
|
||||
#[error(
|
||||
"the attester public key has not been provided through either the CLI nor the default .env config"
|
||||
)]
|
||||
AttesterPublicKeyNotSet,
|
||||
|
||||
#[error("the provided attester public key is malformed: {source}")]
|
||||
MalformedAttesterPublicKey {
|
||||
source: ed25519::Ed25519RecoveryError,
|
||||
},
|
||||
}
|
||||
|
||||
impl From<NymAPIError> for CredentialProxyError {
|
||||
|
||||
@@ -14,7 +14,6 @@ bincode = { workspace = true, optional = true }
|
||||
log = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
serde = { workspace = true, features = ["derive"], optional = true }
|
||||
time = { workspace = true }
|
||||
tokio = { workspace = true, features = ["sync"] }
|
||||
zeroize = { workspace = true, features = ["zeroize_derive"] }
|
||||
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
/*
|
||||
* Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
CREATE TABLE emergency_credential
|
||||
(
|
||||
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||
type TEXT NOT NULL,
|
||||
-- don't define any strict schema on the content as it might be implementation-dependant
|
||||
content BLOB NOT NULL,
|
||||
expiration TIMESTAMP WITHOUT TIME ZONE
|
||||
);
|
||||
|
||||
-- no point in allowing duplicate data
|
||||
CREATE UNIQUE INDEX emergency_credential_unique_type_content
|
||||
ON emergency_credential (type, content);
|
||||
@@ -1,10 +1,7 @@
|
||||
// Copyright 2023-2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::models::{
|
||||
BasicTicketbookInformation, EmergencyCredential, EmergencyCredentialContent,
|
||||
RetrievedPendingTicketbook, RetrievedTicketbook,
|
||||
};
|
||||
use crate::models::{BasicTicketbookInformation, RetrievedPendingTicketbook, RetrievedTicketbook};
|
||||
use nym_compact_ecash::scheme::coin_indices_signatures::AnnotatedCoinIndexSignature;
|
||||
use nym_compact_ecash::scheme::expiration_date_signatures::AnnotatedExpirationDateSignature;
|
||||
use nym_compact_ecash::VerificationKeyAuth;
|
||||
@@ -25,12 +22,6 @@ pub struct MemoryEcachTicketbookManager {
|
||||
inner: Arc<RwLock<EcashCredentialManagerInner>>,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct InternalIdCounters {
|
||||
next_ticketbook_id: i64,
|
||||
next_emergency_credential_id: i64,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct EcashCredentialManagerInner {
|
||||
ticketbooks: HashMap<i64, RetrievedTicketbook>,
|
||||
@@ -38,22 +29,13 @@ struct EcashCredentialManagerInner {
|
||||
master_vk: HashMap<u64, VerificationKeyAuth>,
|
||||
coin_indices_sigs: HashMap<u64, Vec<AnnotatedCoinIndexSignature>>,
|
||||
expiration_date_sigs: HashMap<(u64, Date), Vec<AnnotatedExpirationDateSignature>>,
|
||||
emergency_credentials: HashMap<String, Vec<EmergencyCredential>>,
|
||||
|
||||
// internal counters emulating assignment of an increasing id to new inserted database entries
|
||||
internal_counters: InternalIdCounters,
|
||||
_next_id: i64,
|
||||
}
|
||||
|
||||
impl EcashCredentialManagerInner {
|
||||
fn next_ticketbook_id(&mut self) -> i64 {
|
||||
let next = self.internal_counters.next_ticketbook_id;
|
||||
self.internal_counters.next_ticketbook_id += 1;
|
||||
next
|
||||
}
|
||||
|
||||
fn next_emergency_credential_id(&mut self) -> i64 {
|
||||
let next = self.internal_counters.next_emergency_credential_id;
|
||||
self.internal_counters.next_emergency_credential_id += 1;
|
||||
fn next_id(&mut self) -> i64 {
|
||||
let next = self._next_id;
|
||||
self._next_id += 1;
|
||||
next
|
||||
}
|
||||
}
|
||||
@@ -188,7 +170,7 @@ impl MemoryEcachTicketbookManager {
|
||||
used_tickets: u32,
|
||||
) {
|
||||
let mut guard = self.inner.write().await;
|
||||
let id = guard.next_ticketbook_id();
|
||||
let id = guard.next_id();
|
||||
|
||||
#[allow(clippy::unwrap_used)]
|
||||
let mut nasty_clone = hack_clone_ticketbook(ticketbook);
|
||||
@@ -295,41 +277,4 @@ impl MemoryEcachTicketbookManager {
|
||||
sigs.signatures.clone(),
|
||||
);
|
||||
}
|
||||
|
||||
pub(crate) async fn get_emergency_credential(&self, typ: &str) -> Option<EmergencyCredential> {
|
||||
let guard = self.inner.read().await;
|
||||
|
||||
guard.emergency_credentials.get(typ)?.first().cloned()
|
||||
}
|
||||
|
||||
pub(crate) async fn insert_emergency_credential(
|
||||
&self,
|
||||
credential: &EmergencyCredentialContent,
|
||||
) {
|
||||
let mut guard = self.inner.write().await;
|
||||
let id = guard.next_emergency_credential_id();
|
||||
|
||||
guard
|
||||
.emergency_credentials
|
||||
.entry(credential.typ.clone())
|
||||
.or_default()
|
||||
.push(EmergencyCredential {
|
||||
id,
|
||||
data: credential.clone(),
|
||||
});
|
||||
}
|
||||
|
||||
pub(crate) async fn remove_emergency_credential(&self, id: i64) {
|
||||
let mut guard = self.inner.write().await;
|
||||
|
||||
guard.emergency_credentials.retain(|_, credentials| {
|
||||
credentials.retain(|c| c.id != id);
|
||||
!credentials.is_empty()
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) async fn remove_emergency_credentials_of_type(&self, typ: &str) {
|
||||
let mut guard = self.inner.write().await;
|
||||
guard.emergency_credentials.remove(typ);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,9 +2,8 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::models::{
|
||||
BasicTicketbookInformation, EmergencyCredential, EmergencyCredentialContent,
|
||||
RawCoinIndexSignatures, RawExpirationDateSignatures, RawVerificationKey,
|
||||
StoredIssuedTicketbook, StoredPendingTicketbook,
|
||||
BasicTicketbookInformation, RawCoinIndexSignatures, RawExpirationDateSignatures,
|
||||
RawVerificationKey, StoredIssuedTicketbook, StoredPendingTicketbook,
|
||||
};
|
||||
use nym_ecash_time::Date;
|
||||
use sqlx::{Executor, Sqlite, Transaction};
|
||||
@@ -306,74 +305,6 @@ impl SqliteEcashTicketbookManager {
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) async fn get_emergency_credential(
|
||||
&self,
|
||||
typ: &str,
|
||||
) -> Result<Option<EmergencyCredential>, sqlx::Error> {
|
||||
sqlx::query_as(
|
||||
r#"
|
||||
SELECT *
|
||||
FROM emergency_credential
|
||||
WHERE type = ?
|
||||
AND (expiration IS NULL OR expiration > CURRENT_TIMESTAMP)
|
||||
ORDER BY expiration DESC NULLS LAST
|
||||
LIMIT 1
|
||||
"#,
|
||||
)
|
||||
.bind(typ)
|
||||
.fetch_optional(&*self.connection_pool)
|
||||
.await
|
||||
}
|
||||
|
||||
pub(crate) async fn insert_emergency_credential(
|
||||
&self,
|
||||
credential: &EmergencyCredentialContent,
|
||||
) -> Result<(), sqlx::Error> {
|
||||
sqlx::query!(
|
||||
r#"
|
||||
INSERT INTO emergency_credential
|
||||
(type, content, expiration)
|
||||
VALUES (?, ?, ?)
|
||||
ON CONFLICT(type, content) DO NOTHING;
|
||||
"#,
|
||||
credential.typ,
|
||||
credential.content,
|
||||
credential.expiration,
|
||||
)
|
||||
.execute(&*self.connection_pool)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) async fn remove_emergency_credential(&self, id: i64) -> Result<(), sqlx::Error> {
|
||||
sqlx::query!(
|
||||
r#"
|
||||
DELETE FROM emergency_credential
|
||||
WHERE id = ?
|
||||
"#,
|
||||
id
|
||||
)
|
||||
.execute(&*self.connection_pool)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) async fn remove_emergency_credentials_of_type(
|
||||
&self,
|
||||
typ: &str,
|
||||
) -> Result<(), sqlx::Error> {
|
||||
sqlx::query!(
|
||||
r#"
|
||||
DELETE FROM emergency_credential
|
||||
WHERE type = ?
|
||||
"#,
|
||||
typ
|
||||
)
|
||||
.execute(&*self.connection_pool)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn get_next_unspent_ticketbook<'a, E>(
|
||||
|
||||
@@ -3,10 +3,7 @@
|
||||
|
||||
use crate::backends::memory::MemoryEcachTicketbookManager;
|
||||
use crate::error::StorageError;
|
||||
use crate::models::{
|
||||
BasicTicketbookInformation, EmergencyCredential, EmergencyCredentialContent,
|
||||
RetrievedPendingTicketbook, RetrievedTicketbook,
|
||||
};
|
||||
use crate::models::{BasicTicketbookInformation, RetrievedPendingTicketbook, RetrievedTicketbook};
|
||||
use crate::storage::Storage;
|
||||
use async_trait::async_trait;
|
||||
use nym_compact_ecash::scheme::coin_indices_signatures::AnnotatedCoinIndexSignature;
|
||||
@@ -221,38 +218,6 @@ impl Storage for EphemeralStorage {
|
||||
.await;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn get_emergency_credential(
|
||||
&self,
|
||||
typ: &str,
|
||||
) -> Result<Option<EmergencyCredential>, Self::StorageError> {
|
||||
Ok(self.storage_manager.get_emergency_credential(typ).await)
|
||||
}
|
||||
|
||||
async fn insert_emergency_credential(
|
||||
&self,
|
||||
credential: &EmergencyCredentialContent,
|
||||
) -> Result<(), Self::StorageError> {
|
||||
self.storage_manager
|
||||
.insert_emergency_credential(credential)
|
||||
.await;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn remove_emergency_credential(&self, id: i64) -> Result<(), Self::StorageError> {
|
||||
self.storage_manager.remove_emergency_credential(id).await;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn remove_emergency_credentials_of_type(
|
||||
&self,
|
||||
typ: &str,
|
||||
) -> Result<(), Self::StorageError> {
|
||||
self.storage_manager
|
||||
.remove_emergency_credentials_of_type(typ)
|
||||
.await;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
|
||||
use nym_credentials::{IssuanceTicketBook, IssuedTicketBook};
|
||||
use nym_ecash_time::Date;
|
||||
use time::OffsetDateTime;
|
||||
use zeroize::{Zeroize, ZeroizeOnDrop};
|
||||
|
||||
pub struct RetrievedTicketbook {
|
||||
@@ -79,20 +78,3 @@ pub struct RawVerificationKey {
|
||||
pub serialised_key: Vec<u8>,
|
||||
pub serialization_revision: u8,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
#[cfg_attr(not(target_arch = "wasm32"), derive(sqlx::FromRow))]
|
||||
pub struct EmergencyCredential {
|
||||
pub id: i64,
|
||||
#[cfg_attr(not(target_arch = "wasm32"), sqlx(flatten))]
|
||||
pub data: EmergencyCredentialContent,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
#[cfg_attr(not(target_arch = "wasm32"), derive(sqlx::FromRow))]
|
||||
pub struct EmergencyCredentialContent {
|
||||
#[cfg_attr(not(target_arch = "wasm32"), sqlx(rename = "type"))]
|
||||
pub typ: String,
|
||||
pub content: Vec<u8>,
|
||||
pub expiration: Option<OffsetDateTime>,
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
|
||||
mod legacy_helpers;
|
||||
|
||||
use crate::models::{EmergencyCredential, EmergencyCredentialContent};
|
||||
use crate::{
|
||||
backends::sqlite::{
|
||||
get_next_unspent_ticketbook, increase_used_ticketbook_tickets, SqliteEcashTicketbookManager,
|
||||
@@ -402,36 +401,4 @@ impl Storage for PersistentStorage {
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn get_emergency_credential(
|
||||
&self,
|
||||
typ: &str,
|
||||
) -> Result<Option<EmergencyCredential>, Self::StorageError> {
|
||||
Ok(self.storage_manager.get_emergency_credential(typ).await?)
|
||||
}
|
||||
|
||||
async fn insert_emergency_credential(
|
||||
&self,
|
||||
credential: &EmergencyCredentialContent,
|
||||
) -> Result<(), Self::StorageError> {
|
||||
self.storage_manager
|
||||
.insert_emergency_credential(credential)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn remove_emergency_credential(&self, id: i64) -> Result<(), Self::StorageError> {
|
||||
self.storage_manager.remove_emergency_credential(id).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn remove_emergency_credentials_of_type(
|
||||
&self,
|
||||
typ: &str,
|
||||
) -> Result<(), Self::StorageError> {
|
||||
self.storage_manager
|
||||
.remove_emergency_credentials_of_type(typ)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
// Copyright 2022-2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::models::{
|
||||
BasicTicketbookInformation, EmergencyCredential, EmergencyCredentialContent,
|
||||
RetrievedPendingTicketbook, RetrievedTicketbook,
|
||||
};
|
||||
use crate::models::{BasicTicketbookInformation, RetrievedPendingTicketbook, RetrievedTicketbook};
|
||||
use async_trait::async_trait;
|
||||
use nym_compact_ecash::VerificationKeyAuth;
|
||||
use nym_credentials::ecash::bandwidth::serialiser::keys::EpochVerificationKey;
|
||||
@@ -111,21 +108,4 @@ pub trait Storage: Clone + Send + Sync {
|
||||
&self,
|
||||
signatures: &AggregatedExpirationDateSignatures,
|
||||
) -> Result<(), Self::StorageError>;
|
||||
|
||||
async fn get_emergency_credential(
|
||||
&self,
|
||||
typ: &str,
|
||||
) -> Result<Option<EmergencyCredential>, Self::StorageError>;
|
||||
|
||||
async fn insert_emergency_credential(
|
||||
&self,
|
||||
credential: &EmergencyCredentialContent,
|
||||
) -> Result<(), Self::StorageError>;
|
||||
|
||||
async fn remove_emergency_credential(&self, id: i64) -> Result<(), Self::StorageError>;
|
||||
|
||||
async fn remove_emergency_credentials_of_type(
|
||||
&self,
|
||||
typ: &str,
|
||||
) -> Result<(), Self::StorageError>;
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ cosmwasm-std = { workspace = true }
|
||||
cw-utils = { workspace = true }
|
||||
dyn-clone = { workspace = true }
|
||||
futures = { workspace = true }
|
||||
rand = { workspace = true }
|
||||
si-scale = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
tokio = { workspace = true, features = ["rt-multi-thread", "macros"] }
|
||||
@@ -26,10 +27,8 @@ tracing = { workspace = true }
|
||||
nym-api-requests = { path = "../../nym-api/nym-api-requests" }
|
||||
nym-credentials = { path = "../credentials" }
|
||||
nym-credentials-interface = { path = "../credentials-interface" }
|
||||
nym-crypto = { path = "../crypto", features = ["asymmetric"] }
|
||||
nym-ecash-contract-common = { path = "../cosmwasm-smart-contracts/ecash-contract" }
|
||||
nym-gateway-requests = { path = "../gateway-requests" }
|
||||
nym-gateway-storage = { path = "../gateway-storage" }
|
||||
nym-task = { path = "../task" }
|
||||
nym-validator-client = { path = "../client-libs/validator-client" }
|
||||
nym-upgrade-mode-check = { path = "../upgrade-mode-check" }
|
||||
|
||||
@@ -6,6 +6,7 @@ use crate::ClientBandwidth;
|
||||
use crate::error::*;
|
||||
use nym_credentials::ecash::utils::ecash_today;
|
||||
use nym_credentials_interface::Bandwidth;
|
||||
use nym_gateway_requests::ServerResponse;
|
||||
use nym_gateway_storage::traits::BandwidthGatewayStorage;
|
||||
use si_scale::helpers::bibytes2;
|
||||
use time::OffsetDateTime;
|
||||
@@ -65,7 +66,7 @@ impl BandwidthStorageManager {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn handle_claim_testnet_bandwidth(&mut self) -> Result<i64> {
|
||||
pub async fn handle_claim_testnet_bandwidth(&mut self) -> Result<ServerResponse> {
|
||||
debug!("handling testnet bandwidth request");
|
||||
|
||||
if self.only_coconut_credentials {
|
||||
@@ -75,7 +76,8 @@ impl BandwidthStorageManager {
|
||||
self.increase_bandwidth(FREE_TESTNET_BANDWIDTH_VALUE, ecash_today())
|
||||
.await?;
|
||||
let available_total = self.client_bandwidth.available().await;
|
||||
Ok(available_total)
|
||||
|
||||
Ok(ServerResponse::Bandwidth { available_total })
|
||||
}
|
||||
|
||||
#[instrument(skip_all)]
|
||||
@@ -94,7 +96,7 @@ impl BandwidthStorageManager {
|
||||
|
||||
let available_bi2 = bibytes2(available_bandwidth as f64);
|
||||
let required_bi2 = bibytes2(required_bandwidth as f64);
|
||||
trace!(available = available_bi2, required = required_bi2);
|
||||
debug!(available = available_bi2, required = required_bi2);
|
||||
|
||||
self.consume_bandwidth(required_bandwidth).await?;
|
||||
let remaining_bandwidth = self.client_bandwidth.available().await;
|
||||
|
||||
@@ -47,25 +47,6 @@ pub enum Error {
|
||||
UnknownTicketType(#[from] nym_credentials_interface::UnknownTicketType),
|
||||
}
|
||||
|
||||
impl Error {
|
||||
pub fn is_out_of_bandwidth(&self) -> bool {
|
||||
matches!(self, Error::OutOfBandwidth { .. })
|
||||
}
|
||||
}
|
||||
|
||||
pub trait OutOfBandwidthResultExt {
|
||||
fn is_out_of_bandwidth(&self) -> bool;
|
||||
}
|
||||
|
||||
impl<T> OutOfBandwidthResultExt for Result<T> {
|
||||
fn is_out_of_bandwidth(&self) -> bool {
|
||||
match &self {
|
||||
Ok(_) => false,
|
||||
Err(err) => err.is_out_of_bandwidth(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<EcashTicketError> for Error {
|
||||
fn from(err: EcashTicketError) -> Self {
|
||||
// don't expose storage issue details to the user
|
||||
|
||||
@@ -13,13 +13,11 @@ use tracing::*;
|
||||
|
||||
pub use client_bandwidth::*;
|
||||
pub use error::*;
|
||||
pub use upgrade_mode::UpgradeModeState;
|
||||
|
||||
pub mod bandwidth_storage_manager;
|
||||
mod client_bandwidth;
|
||||
pub mod ecash;
|
||||
pub mod error;
|
||||
pub mod upgrade_mode;
|
||||
|
||||
pub struct CredentialVerifier {
|
||||
credential: CredentialSpendingRequest,
|
||||
|
||||
@@ -1,293 +0,0 @@
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use futures::channel::mpsc::{UnboundedReceiver, UnboundedSender};
|
||||
use nym_crypto::asymmetric::ed25519;
|
||||
use nym_upgrade_mode_check::{
|
||||
CREDENTIAL_PROXY_JWT_ISSUER, UpgradeModeAttestation, validate_upgrade_mode_jwt,
|
||||
};
|
||||
use std::sync::Arc;
|
||||
use std::sync::atomic::{AtomicBool, AtomicI64, Ordering};
|
||||
use std::time::Duration;
|
||||
use thiserror::Error;
|
||||
use time::OffsetDateTime;
|
||||
use tokio::sync::{Notify, RwLock};
|
||||
use tracing::{debug, error, info};
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum UpgradeModeEnableError {
|
||||
#[error("too soon to perform another upgrade mode attestation check")]
|
||||
TooManyRecheckRequests,
|
||||
|
||||
#[error("provided upgrade mode JWT is invalid: {0}")]
|
||||
InvalidUpgradeModeJWT(#[from] nym_upgrade_mode_check::UpgradeModeCheckError),
|
||||
|
||||
#[error("the upgrade mode attestation does not appear to have been published")]
|
||||
AttestationNotPublished,
|
||||
|
||||
#[error("the provided upgrade mode attestation is different from the published one")]
|
||||
MismatchedUpgradeModeAttestation,
|
||||
}
|
||||
|
||||
// the idea behind this is as follows:
|
||||
// it's been relatively a long time since the watcher last performed its checks (since it's in 'regular' mode)
|
||||
// and some client has just sent a JWT. we have to retrieve most recent information in case upgrade mode
|
||||
// has just been enabled, and we haven't learned about it yet
|
||||
#[derive(Clone)]
|
||||
pub struct UpgradeModeCheckRequestSender(Option<UnboundedSender<CheckRequest>>);
|
||||
|
||||
impl UpgradeModeCheckRequestSender {
|
||||
pub fn new(sender: UnboundedSender<CheckRequest>) -> Self {
|
||||
UpgradeModeCheckRequestSender(Some(sender))
|
||||
}
|
||||
|
||||
pub fn new_empty() -> Self {
|
||||
Self(None)
|
||||
}
|
||||
|
||||
pub(crate) fn send_request(&self, on_done: Arc<Notify>) {
|
||||
let Some(ref inner) = self.0 else {
|
||||
// make sure the caller gets notified so it doesn't wait forever
|
||||
on_done.notify_waiters();
|
||||
return;
|
||||
};
|
||||
|
||||
if let Err(not_sent) = inner.unbounded_send(CheckRequest { on_done }) {
|
||||
debug!("failed to send upgrade mode check request - {not_sent}");
|
||||
// make sure the caller gets notified so it doesn't wait forever
|
||||
not_sent.into_inner().on_done.notify_waiters();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub type UpgradeModeCheckRequestReceiver = UnboundedReceiver<CheckRequest>;
|
||||
|
||||
pub struct CheckRequest {
|
||||
on_done: Arc<Notify>,
|
||||
}
|
||||
|
||||
impl CheckRequest {
|
||||
pub fn finalize(self) {
|
||||
self.on_done.notify_waiters();
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct UpgradeModeCheckConfig {
|
||||
/// The minimum duration since the last explicit check to allow creation of separate request.
|
||||
pub min_staleness_recheck: Duration,
|
||||
}
|
||||
|
||||
/// Full upgrade mode information, that apart from boolean flag indicating the state
|
||||
/// and the attestation information, includes channel connection to relevant
|
||||
/// attestation watcher to request state rechecks
|
||||
#[derive(Clone)]
|
||||
pub struct UpgradeModeDetails {
|
||||
pub(crate) config: UpgradeModeCheckConfig,
|
||||
pub(crate) request_checker: UpgradeModeCheckRequestSender,
|
||||
pub(crate) state: UpgradeModeState,
|
||||
}
|
||||
|
||||
impl UpgradeModeDetails {
|
||||
pub fn new(
|
||||
config: UpgradeModeCheckConfig,
|
||||
request_checker: UpgradeModeCheckRequestSender,
|
||||
state: UpgradeModeState,
|
||||
) -> Self {
|
||||
UpgradeModeDetails {
|
||||
config,
|
||||
request_checker,
|
||||
state,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn state(&self) -> &UpgradeModeState {
|
||||
&self.state
|
||||
}
|
||||
|
||||
pub fn enabled(&self) -> bool {
|
||||
self.state.upgrade_mode_enabled()
|
||||
}
|
||||
|
||||
fn since_last_query(&self) -> Duration {
|
||||
self.state.since_last_query()
|
||||
}
|
||||
|
||||
pub fn can_request_recheck(&self) -> bool {
|
||||
self.since_last_query() > self.config.min_staleness_recheck
|
||||
}
|
||||
|
||||
// explicitly request state update. this is only called when upgrade mode is NOT enabled,
|
||||
// and client has sent a JWT instead of ticket
|
||||
async fn request_recheck(&self) -> bool {
|
||||
// send request
|
||||
let on_done = Arc::new(Notify::new());
|
||||
self.request_checker.send_request(on_done.clone());
|
||||
|
||||
// wait for response - note, if we fail to send, notification will be sent regardless,
|
||||
// so that we wouldn't get stuck in here
|
||||
on_done.notified().await;
|
||||
|
||||
// check the state again
|
||||
self.enabled()
|
||||
}
|
||||
|
||||
pub async fn try_enable_via_received_jwt(
|
||||
&self,
|
||||
token: String,
|
||||
) -> Result<(), UpgradeModeEnableError> {
|
||||
// see if it's viable to perform another expedited check
|
||||
if !self.can_request_recheck() {
|
||||
return Err(UpgradeModeEnableError::TooManyRecheckRequests);
|
||||
}
|
||||
|
||||
// first validate whether the received JWT is even valid
|
||||
let attestation = validate_upgrade_mode_jwt(&token, Some(CREDENTIAL_PROXY_JWT_ISSUER))?;
|
||||
|
||||
// send request to revalidate internal state
|
||||
// this will, among other things, pull fresh attestation from the configured endpoint
|
||||
// and also verify required signatures (and pubkeys)
|
||||
self.request_recheck().await;
|
||||
|
||||
// not strictly necessary, but check if provided attestation actually matches the one retrieved
|
||||
// (if any)
|
||||
let Some(retrieved_attestation) = self.state.attestation().await else {
|
||||
return Err(UpgradeModeEnableError::AttestationNotPublished);
|
||||
};
|
||||
if retrieved_attestation != attestation {
|
||||
return Err(UpgradeModeEnableError::MismatchedUpgradeModeAttestation);
|
||||
}
|
||||
|
||||
// note: if attestation has been returned, it means we're definitely in upgrade mode
|
||||
// (otherwise it wouldn't have existed in the state)
|
||||
info!("managed to initialise upgrade mode through received JWT");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Detailed upgrade mode information, that apart from boolean flag,
|
||||
/// also includes, if applicable, the associated attestation
|
||||
#[derive(Clone)]
|
||||
pub struct UpgradeModeState {
|
||||
inner: Arc<UpgradeModeStateInner>,
|
||||
}
|
||||
|
||||
/// Just a shareable flag to indicate whether upgrade mode is enabled or disabled
|
||||
#[derive(Clone, Default)]
|
||||
pub struct UpgradeModeStatus(Arc<AtomicBool>);
|
||||
|
||||
impl UpgradeModeStatus {
|
||||
pub fn enabled(&self) -> bool {
|
||||
self.0.load(Ordering::Acquire)
|
||||
}
|
||||
|
||||
pub fn enable(&self) {
|
||||
self.0.store(true, Ordering::Relaxed);
|
||||
}
|
||||
|
||||
pub fn disable(&self) {
|
||||
self.0.store(false, Ordering::Release);
|
||||
}
|
||||
}
|
||||
|
||||
impl UpgradeModeState {
|
||||
pub fn new(attester_public_key: ed25519::PublicKey) -> UpgradeModeState {
|
||||
UpgradeModeState {
|
||||
inner: Arc::new(UpgradeModeStateInner {
|
||||
expected_attester_public_key: attester_public_key,
|
||||
expected_attestation: RwLock::new(None),
|
||||
last_queried_ts: AtomicI64::new(OffsetDateTime::UNIX_EPOCH.unix_timestamp()),
|
||||
status: UpgradeModeStatus(Arc::new(AtomicBool::new(false))),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn attester_pubkey(&self) -> ed25519::PublicKey {
|
||||
self.inner.expected_attester_public_key
|
||||
}
|
||||
|
||||
pub async fn attestation(&self) -> Option<UpgradeModeAttestation> {
|
||||
self.inner.expected_attestation.read().await.clone()
|
||||
}
|
||||
|
||||
pub async fn try_set_expected_attestation(
|
||||
&self,
|
||||
expected_attestation: Option<UpgradeModeAttestation>,
|
||||
) {
|
||||
// make sure to only enable upgrade mode flag AFTER we have written the expected value
|
||||
// (or still hold the exclusive lock as in this instance)
|
||||
let mut guard = self.inner.expected_attestation.write().await;
|
||||
|
||||
// ensure that the attestation had been signed with the expected key
|
||||
if let Some(attestation) = expected_attestation.as_ref() {
|
||||
if attestation.content.attester_public_key != self.inner.expected_attester_public_key {
|
||||
self.update_last_queried(OffsetDateTime::now_utc());
|
||||
return;
|
||||
}
|
||||
|
||||
self.enable_upgrade_mode()
|
||||
} else {
|
||||
self.disable_upgrade_mode()
|
||||
}
|
||||
|
||||
self.update_last_queried(OffsetDateTime::now_utc());
|
||||
*guard = expected_attestation;
|
||||
}
|
||||
|
||||
pub fn upgrade_mode_status(&self) -> UpgradeModeStatus {
|
||||
self.inner.status.clone()
|
||||
}
|
||||
|
||||
pub fn upgrade_mode_enabled(&self) -> bool {
|
||||
self.inner.status.enabled()
|
||||
}
|
||||
|
||||
pub fn enable_upgrade_mode(&self) {
|
||||
self.inner.status.enable()
|
||||
}
|
||||
|
||||
pub fn disable_upgrade_mode(&self) {
|
||||
self.inner.status.disable()
|
||||
}
|
||||
|
||||
pub fn last_queried(&self) -> OffsetDateTime {
|
||||
// SAFETY: the stored value here is always a valid unix timestamp
|
||||
#[allow(clippy::unwrap_used)]
|
||||
OffsetDateTime::from_unix_timestamp(self.inner.last_queried_ts.load(Ordering::Acquire))
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
pub fn update_last_queried(&self, queried_at: OffsetDateTime) {
|
||||
self.inner
|
||||
.last_queried_ts
|
||||
.store(queried_at.unix_timestamp(), Ordering::Release);
|
||||
}
|
||||
|
||||
pub fn since_last_query(&self) -> Duration {
|
||||
(OffsetDateTime::now_utc() - self.last_queried())
|
||||
.try_into()
|
||||
.unwrap_or_else(|_| {
|
||||
error!("somehow our last query for upgrade mode was in the future!");
|
||||
Duration::ZERO
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
struct UpgradeModeStateInner {
|
||||
/// Expected public key of the entity issuing upgrade mode attestations.
|
||||
expected_attester_public_key: ed25519::PublicKey,
|
||||
|
||||
/// Contents of the published upgrade mode attestation, as queried by this node
|
||||
expected_attestation: RwLock<Option<UpgradeModeAttestation>>,
|
||||
|
||||
/// timestamp indicating last time this node has queried for the current upgrade mode attestation
|
||||
/// it is used to determine if an additional expedited query should be made in case client sends a JWT
|
||||
/// whilst this node is not aware of the upgrade mode
|
||||
last_queried_ts: AtomicI64,
|
||||
|
||||
/// flag indicating whether upgrade mode is currently enabled. this is to perform cheap checks
|
||||
/// that avoid having to acquire the lock
|
||||
// (and dealing with the async consequences of that)
|
||||
status: UpgradeModeStatus,
|
||||
}
|
||||
@@ -23,5 +23,3 @@ rand = { workspace = true }
|
||||
nym-compact-ecash = { path = "../nym_offline_compact_ecash" }
|
||||
nym-ecash-time = { path = "../ecash-time" }
|
||||
nym-network-defaults = { path = "../network-defaults" }
|
||||
nym-upgrade-mode-check = { path = "../upgrade-mode-check" }
|
||||
|
||||
|
||||
@@ -30,35 +30,6 @@ pub use nym_compact_ecash::{
|
||||
};
|
||||
pub use nym_ecash_time::{EcashTime, ecash_today};
|
||||
pub use nym_network_defaults::TicketTypeRepr;
|
||||
use nym_network_defaults::TicketTypeRepr::V1MixnetEntry;
|
||||
|
||||
/// Default bandwidth amount under which [mixnet] clients will attempt to send additional zk-nyms
|
||||
/// to increase their allowance.
|
||||
// currently defined as 20% of entry ticket value
|
||||
// clients are, of course, free to override this value
|
||||
pub const DEFAULT_MIXNET_REQUEST_BANDWIDTH_THRESHOLD: i64 =
|
||||
(V1MixnetEntry.bandwidth_value() / 5) as i64;
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
|
||||
pub enum BandwidthCredential {
|
||||
ZkNym(Box<CredentialSpendingData>),
|
||||
UpgradeModeJWT { token: String },
|
||||
}
|
||||
|
||||
impl BandwidthCredential {
|
||||
pub fn into_zk_nym(self) -> Option<Box<CredentialSpendingData>> {
|
||||
match self {
|
||||
BandwidthCredential::ZkNym(credential) => Some(credential),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<CredentialSpendingData> for BandwidthCredential {
|
||||
fn from(credential: CredentialSpendingData) -> Self {
|
||||
Self::ZkNym(Box::new(credential))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct CredentialSigningData {
|
||||
|
||||
@@ -51,6 +51,3 @@ anyhow = { workspace = true }
|
||||
nym-compact-ecash = { path = "../nym_offline_compact_ecash" } # we need specific imports in tests
|
||||
nym-test-utils = { path = "../test-utils" }
|
||||
tokio = { workspace = true, features = ["full"] }
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
@@ -19,9 +19,7 @@ pub use shared_key::{
|
||||
SharedGatewayKey, SharedKeyConversionError, SharedKeyUsageError, SharedSymmetricKey,
|
||||
};
|
||||
|
||||
pub type GatewayProtocolVersion = u8;
|
||||
|
||||
pub const CURRENT_PROTOCOL_VERSION: GatewayProtocolVersion = UPGRADE_MODE_VERSION;
|
||||
pub const CURRENT_PROTOCOL_VERSION: u8 = EMBEDDED_KEY_ROTATION_INFO_VERSION;
|
||||
|
||||
/// Defines the current version of the communication protocol between gateway and clients.
|
||||
/// It has to be incremented for any breaking change.
|
||||
@@ -31,73 +29,35 @@ pub const CURRENT_PROTOCOL_VERSION: GatewayProtocolVersion = UPGRADE_MODE_VERSIO
|
||||
// 3 - change to AES-GCM-SIV and non-zero IVs
|
||||
// 4 - introduction of v2 authentication protocol to prevent reply attacks
|
||||
// 5 - add key rotation information to the serialised mix packet
|
||||
// 6 - support for 'upgrade mode'
|
||||
pub const INITIAL_PROTOCOL_VERSION: GatewayProtocolVersion = 1;
|
||||
pub const CREDENTIAL_UPDATE_V2_PROTOCOL_VERSION: GatewayProtocolVersion = 2;
|
||||
pub const AES_GCM_SIV_PROTOCOL_VERSION: GatewayProtocolVersion = 3;
|
||||
pub const AUTHENTICATE_V2_PROTOCOL_VERSION: GatewayProtocolVersion = 4;
|
||||
pub const EMBEDDED_KEY_ROTATION_INFO_VERSION: GatewayProtocolVersion = 5;
|
||||
pub const UPGRADE_MODE_VERSION: GatewayProtocolVersion = 6;
|
||||
pub const INITIAL_PROTOCOL_VERSION: u8 = 1;
|
||||
pub const CREDENTIAL_UPDATE_V2_PROTOCOL_VERSION: u8 = 2;
|
||||
pub const AES_GCM_SIV_PROTOCOL_VERSION: u8 = 3;
|
||||
pub const AUTHENTICATE_V2_PROTOCOL_VERSION: u8 = 4;
|
||||
pub const EMBEDDED_KEY_ROTATION_INFO_VERSION: u8 = 5;
|
||||
|
||||
// TODO: could using `Mac` trait here for OutputSize backfire?
|
||||
// Should hmac itself be exposed, imported and used instead?
|
||||
pub type LegacyGatewayMacSize = <GatewayIntegrityHmacAlgorithm as OutputSizeUser>::OutputSize;
|
||||
|
||||
pub trait GatewayProtocolVersionExt {
|
||||
const CURRENT: GatewayProtocolVersion = CURRENT_PROTOCOL_VERSION;
|
||||
|
||||
fn supports_aes256_gcm_siv(&self) -> bool;
|
||||
fn supports_authenticate_v2(&self) -> bool;
|
||||
fn supports_key_rotation_packet(&self) -> bool;
|
||||
fn supports_upgrade_mode(&self) -> bool;
|
||||
fn is_future_version(&self) -> bool;
|
||||
}
|
||||
|
||||
impl GatewayProtocolVersionExt for Option<GatewayProtocolVersion> {
|
||||
impl GatewayProtocolVersionExt for Option<u8> {
|
||||
fn supports_aes256_gcm_siv(&self) -> bool {
|
||||
let Some(protocol) = self else { return false };
|
||||
protocol.supports_aes256_gcm_siv()
|
||||
let Some(protocol) = *self else { return false };
|
||||
protocol >= AES_GCM_SIV_PROTOCOL_VERSION
|
||||
}
|
||||
|
||||
fn supports_authenticate_v2(&self) -> bool {
|
||||
let Some(protocol) = self else { return false };
|
||||
protocol.supports_authenticate_v2()
|
||||
let Some(protocol) = *self else { return false };
|
||||
protocol >= AUTHENTICATE_V2_PROTOCOL_VERSION
|
||||
}
|
||||
|
||||
fn supports_key_rotation_packet(&self) -> bool {
|
||||
let Some(protocol) = self else { return false };
|
||||
protocol.supports_key_rotation_packet()
|
||||
}
|
||||
|
||||
fn supports_upgrade_mode(&self) -> bool {
|
||||
let Some(protocol) = self else { return false };
|
||||
protocol.supports_upgrade_mode()
|
||||
}
|
||||
|
||||
fn is_future_version(&self) -> bool {
|
||||
let Some(protocol) = self else { return false };
|
||||
protocol.is_future_version()
|
||||
}
|
||||
}
|
||||
|
||||
impl GatewayProtocolVersionExt for GatewayProtocolVersion {
|
||||
fn supports_aes256_gcm_siv(&self) -> bool {
|
||||
*self >= AES_GCM_SIV_PROTOCOL_VERSION
|
||||
}
|
||||
|
||||
fn supports_authenticate_v2(&self) -> bool {
|
||||
*self >= AUTHENTICATE_V2_PROTOCOL_VERSION
|
||||
}
|
||||
|
||||
fn supports_key_rotation_packet(&self) -> bool {
|
||||
*self >= EMBEDDED_KEY_ROTATION_INFO_VERSION
|
||||
}
|
||||
|
||||
fn supports_upgrade_mode(&self) -> bool {
|
||||
*self >= UPGRADE_MODE_VERSION
|
||||
}
|
||||
|
||||
fn is_future_version(&self) -> bool {
|
||||
*self > CURRENT_PROTOCOL_VERSION
|
||||
let Some(protocol) = *self else { return false };
|
||||
protocol >= EMBEDDED_KEY_ROTATION_INFO_VERSION
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,12 +3,10 @@
|
||||
|
||||
use crate::registration::handshake::messages::{Finalization, GatewayMaterialExchange};
|
||||
use crate::registration::handshake::state::State;
|
||||
use crate::registration::handshake::HandshakeResult;
|
||||
use crate::registration::handshake::SharedGatewayKey;
|
||||
use crate::registration::handshake::{error::HandshakeError, WsItem};
|
||||
use crate::{GatewayProtocolVersionExt, INITIAL_PROTOCOL_VERSION};
|
||||
use futures::{Sink, Stream};
|
||||
use rand::{CryptoRng, RngCore};
|
||||
use tracing::info;
|
||||
use tungstenite::Message as WsMessage;
|
||||
|
||||
impl<S, R> State<'_, S, R> {
|
||||
@@ -27,26 +25,10 @@ impl<S, R> State<'_, S, R> {
|
||||
|
||||
// 2. wait for response with remote x25519 pubkey as well as encrypted signature
|
||||
// <- g^y || AES(k, sig(gate_priv, (g^y || g^x)) || MAYBE_NONCE
|
||||
let (mid_res, gateway_protocol) = self
|
||||
let mid_res = self
|
||||
.receive_handshake_message::<GatewayMaterialExchange>()
|
||||
.await?;
|
||||
|
||||
// NEGOTIATE PROTOCOL
|
||||
if gateway_protocol.is_future_version() {
|
||||
// SAFETY: future version means it's greater than CURRENT, which is always a `Some`
|
||||
#[allow(clippy::unwrap_used)]
|
||||
return Err(HandshakeError::UnsupportedProtocol {
|
||||
version: gateway_protocol.unwrap(),
|
||||
});
|
||||
}
|
||||
let gateway_protocol = gateway_protocol.unwrap_or(INITIAL_PROTOCOL_VERSION);
|
||||
|
||||
// that should never happen, but we're fine with that outcome
|
||||
if Some(gateway_protocol) != self.proposed_protocol_version() {
|
||||
info!("the gateway insists on protocol version different from the one we suggested. it wants {gateway_protocol} whilst we wanted {:?}, however, we can support it", self.proposed_protocol_version());
|
||||
self.set_protocol_version(gateway_protocol);
|
||||
}
|
||||
|
||||
// 3. derive shared keys locally
|
||||
// hkdf::<blake3>::(g^xy)
|
||||
self.derive_shared_key(&mid_res.ephemeral_dh, maybe_hkdf_salt.as_deref());
|
||||
@@ -60,14 +42,14 @@ impl<S, R> State<'_, S, R> {
|
||||
self.send_handshake_data(materials).await?;
|
||||
|
||||
// 6. wait for remote confirmation of finalizing the handshake
|
||||
let (finalization, _) = self.receive_handshake_message::<Finalization>().await?;
|
||||
let finalization = self.receive_handshake_message::<Finalization>().await?;
|
||||
finalization.ensure_success()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) async fn perform_client_handshake(
|
||||
mut self,
|
||||
) -> Result<HandshakeResult, HandshakeError>
|
||||
) -> Result<SharedGatewayKey, HandshakeError>
|
||||
where
|
||||
S: Stream<Item = WsItem> + Sink<WsMessage> + Unpin,
|
||||
R: CryptoRng + RngCore,
|
||||
|
||||
@@ -2,8 +2,6 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::shared_key::SharedKeyUsageError;
|
||||
use crate::GatewayProtocolVersion;
|
||||
use crate::GatewayProtocolVersionExt;
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
@@ -36,10 +34,4 @@ pub enum HandshakeError {
|
||||
|
||||
#[error("timed out waiting for a handshake message")]
|
||||
Timeout,
|
||||
|
||||
#[error("Connection is in an invalid state - please send a bug report")]
|
||||
ConnectionInInvalidState,
|
||||
|
||||
#[error("the gateway requests protocol version that's not supported by this client. it wants to use v{version} whilst we only understand up to v{}", GatewayProtocolVersion::CURRENT)]
|
||||
UnsupportedProtocol { version: GatewayProtocolVersion },
|
||||
}
|
||||
|
||||
@@ -5,11 +5,9 @@ use crate::registration::handshake::messages::{
|
||||
HandshakeMessage, Initialisation, MaterialExchange,
|
||||
};
|
||||
use crate::registration::handshake::state::State;
|
||||
use crate::registration::handshake::HandshakeResult;
|
||||
use crate::registration::handshake::SharedGatewayKey;
|
||||
use crate::registration::handshake::{error::HandshakeError, WsItem};
|
||||
use crate::{GatewayProtocolVersion, GatewayProtocolVersionExt};
|
||||
use futures::{Sink, Stream};
|
||||
use tracing::{debug, warn};
|
||||
use tungstenite::Message as WsMessage;
|
||||
|
||||
impl<S, R> State<'_, S, R> {
|
||||
@@ -20,39 +18,11 @@ impl<S, R> State<'_, S, R> {
|
||||
where
|
||||
S: Stream<Item = WsItem> + Sink<WsMessage> + Unpin,
|
||||
{
|
||||
// NEGOTIATE PROTOCOL
|
||||
// old clients were sending protocol version as defined by the following:
|
||||
/*
|
||||
fn request_protocol_version(&self) -> u8 {
|
||||
if self.derive_aes256_gcm_siv_key {
|
||||
AES_GCM_SIV_PROTOCOL_VERSION
|
||||
} else if self.expects_credential_usage {
|
||||
CREDENTIAL_UPDATE_V2_PROTOCOL_VERSION
|
||||
} else {
|
||||
INITIAL_PROTOCOL_VERSION
|
||||
}
|
||||
}
|
||||
*/
|
||||
// meaning the highest possible value they could have sent was `4` (AUTHENTICATE_V2_PROTOCOL_VERSION)
|
||||
// so if we received anything higher than that, it means they understand negotiation.
|
||||
// currently not strictly needed as we just blindly accept what they proposed,
|
||||
// but will be needed in the future.
|
||||
if self.proposed_protocol_version().is_future_version() {
|
||||
// this should never happen in a non-malicious client as it should use at most whatever version this gateway has announced
|
||||
self.set_protocol_version(GatewayProtocolVersion::CURRENT)
|
||||
} else {
|
||||
// currently we accept all protocols, i.e. legacy keys, aes128, etc. so we downgrade to whatever
|
||||
// the client has proposed. this will change in the future
|
||||
debug!(
|
||||
"using the protocol version proposed by the client: {:?}",
|
||||
self.proposed_protocol_version()
|
||||
)
|
||||
}
|
||||
|
||||
// 1. receive remote ed25519 pubkey alongside ephemeral x25519 pubkey and maybe a flag indicating non-legacy client
|
||||
// LOCAL_ID_PUBKEY || EPHEMERAL_KEY || MAYBE_NON_LEGACY
|
||||
let init_message = Initialisation::try_from_bytes(&raw_init_message)?;
|
||||
self.update_remote_identity(init_message.identity);
|
||||
self.set_aes256_gcm_siv_key_derivation(!init_message.is_legacy());
|
||||
|
||||
// 2. derive shared keys locally
|
||||
// hkdf::<blake3>::(g^xy)
|
||||
@@ -69,12 +39,7 @@ impl<S, R> State<'_, S, R> {
|
||||
self.send_handshake_data(material).await?;
|
||||
|
||||
// 4. wait for the remote response with their own encrypted signature
|
||||
let (materials, client_protocol) =
|
||||
self.receive_handshake_message::<MaterialExchange>().await?;
|
||||
if client_protocol != self.proposed_protocol_version() {
|
||||
warn!("the client hasn't accepted our proposed protocol version. we suggested {:?} while it returned {client_protocol:?}", self.proposed_protocol_version());
|
||||
// TBD what to do here
|
||||
}
|
||||
let materials = self.receive_handshake_message::<MaterialExchange>().await?;
|
||||
|
||||
// 5. verify the received signature using the locally derived keys
|
||||
self.verify_remote_key_material(&materials, &init_message.ephemeral_dh)?;
|
||||
@@ -89,7 +54,7 @@ impl<S, R> State<'_, S, R> {
|
||||
pub(crate) async fn perform_gateway_handshake(
|
||||
mut self,
|
||||
raw_init_message: Vec<u8>,
|
||||
) -> Result<HandshakeResult, HandshakeError>
|
||||
) -> Result<SharedGatewayKey, HandshakeError>
|
||||
where
|
||||
S: Stream<Item = WsItem> + Sink<WsMessage> + Unpin,
|
||||
{
|
||||
|
||||
@@ -24,6 +24,13 @@ pub struct Initialisation {
|
||||
pub initiator_salt: Option<Vec<u8>>,
|
||||
}
|
||||
|
||||
impl Initialisation {
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub fn is_legacy(&self) -> bool {
|
||||
self.initiator_salt.is_none()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct MaterialExchange {
|
||||
pub signature_ciphertext: Vec<u8>,
|
||||
@@ -92,9 +99,8 @@ impl HandshakeMessage for Initialisation {
|
||||
let identity = ed25519::PublicKey::from_bytes(&bytes[..ed25519::PUBLIC_KEY_LENGTH])
|
||||
.map_err(|_| HandshakeError::MalformedRequest)?;
|
||||
|
||||
// SAFETY: this can only fail if the provided bytes have len different from encryption::PUBLIC_KEY_SIZE
|
||||
// this can only fail if the provided bytes have len different from encryption::PUBLIC_KEY_SIZE
|
||||
// which is impossible
|
||||
#[allow(clippy::unwrap_used)]
|
||||
let ephemeral_dh =
|
||||
x25519::PublicKey::from_bytes(&bytes[ed25519::PUBLIC_KEY_LENGTH..legacy_len]).unwrap();
|
||||
|
||||
@@ -188,7 +194,6 @@ impl HandshakeMessage for GatewayMaterialExchange {
|
||||
|
||||
// this can only fail if the provided bytes have len different from PUBLIC_KEY_SIZE
|
||||
// which is impossible
|
||||
#[allow(clippy::unwrap_used)]
|
||||
let ephemeral_dh =
|
||||
x25519::PublicKey::from_bytes(&bytes[..x25519::PUBLIC_KEY_SIZE]).unwrap();
|
||||
let materials = MaterialExchange::try_from_bytes(&bytes[x25519::PUBLIC_KEY_SIZE..])?;
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
use self::error::HandshakeError;
|
||||
use crate::registration::handshake::state::State;
|
||||
use crate::{GatewayProtocolVersion, SharedGatewayKey};
|
||||
use crate::SharedGatewayKey;
|
||||
use futures::future::BoxFuture;
|
||||
use futures::{Sink, Stream};
|
||||
use nym_crypto::asymmetric::ed25519;
|
||||
@@ -34,29 +34,24 @@ pub const KDF_SALT_LENGTH: usize = 16;
|
||||
// we do not need to worry about that.
|
||||
|
||||
pub struct GatewayHandshake<'a> {
|
||||
handshake_future: BoxFuture<'a, Result<HandshakeResult, HandshakeError>>,
|
||||
handshake_future: BoxFuture<'a, Result<SharedGatewayKey, HandshakeError>>,
|
||||
}
|
||||
|
||||
impl Future for GatewayHandshake<'_> {
|
||||
type Output = Result<HandshakeResult, HandshakeError>;
|
||||
type Output = Result<SharedGatewayKey, HandshakeError>;
|
||||
|
||||
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
Pin::new(&mut self.handshake_future).poll(cx)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub struct HandshakeResult {
|
||||
pub negotiated_protocol: GatewayProtocolVersion,
|
||||
pub derived_key: SharedGatewayKey,
|
||||
}
|
||||
|
||||
pub fn client_handshake<'a, S, R>(
|
||||
rng: &'a mut R,
|
||||
ws_stream: &'a mut S,
|
||||
identity: &'a ed25519::KeyPair,
|
||||
gateway_pubkey: ed25519::PublicKey,
|
||||
gateway_protocol: Option<GatewayProtocolVersion>,
|
||||
expects_credential_usage: bool,
|
||||
derive_aes256_gcm_siv_key: bool,
|
||||
#[cfg(not(target_arch = "wasm32"))] shutdown_token: ShutdownToken,
|
||||
) -> GatewayHandshake<'a>
|
||||
where
|
||||
@@ -68,10 +63,11 @@ where
|
||||
ws_stream,
|
||||
identity,
|
||||
Some(gateway_pubkey),
|
||||
gateway_protocol,
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
shutdown_token,
|
||||
);
|
||||
)
|
||||
.with_credential_usage(expects_credential_usage)
|
||||
.with_aes256_gcm_siv_key(derive_aes256_gcm_siv_key);
|
||||
|
||||
GatewayHandshake {
|
||||
handshake_future: Box::pin(state.perform_client_handshake()),
|
||||
@@ -84,21 +80,13 @@ pub fn gateway_handshake<'a, S, R>(
|
||||
ws_stream: &'a mut S,
|
||||
identity: &'a ed25519::KeyPair,
|
||||
received_init_payload: Vec<u8>,
|
||||
requested_client_protocol: Option<GatewayProtocolVersion>,
|
||||
shutdown_token: ShutdownToken,
|
||||
) -> GatewayHandshake<'a>
|
||||
where
|
||||
S: Stream<Item = WsItem> + Sink<WsMessage> + Unpin + Send + 'a,
|
||||
R: CryptoRng + RngCore + Send,
|
||||
{
|
||||
let state = State::new(
|
||||
rng,
|
||||
ws_stream,
|
||||
identity,
|
||||
None,
|
||||
requested_client_protocol,
|
||||
shutdown_token,
|
||||
);
|
||||
let state = State::new(rng, ws_stream, identity, None, shutdown_token);
|
||||
GatewayHandshake {
|
||||
handshake_future: Box::pin(state.perform_gateway_handshake(received_init_payload)),
|
||||
}
|
||||
@@ -125,8 +113,7 @@ DONE(status)
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::{ClientControlRequest, CURRENT_PROTOCOL_VERSION, INITIAL_PROTOCOL_VERSION};
|
||||
use anyhow::{bail, Context};
|
||||
use crate::ClientControlRequest;
|
||||
use futures::StreamExt;
|
||||
use nym_test_utils::helpers::u64_seeded_rng;
|
||||
use nym_test_utils::mocks::stream_sink::mock_streams;
|
||||
@@ -134,53 +121,10 @@ mod tests {
|
||||
use tokio::join;
|
||||
use tungstenite::Message;
|
||||
|
||||
trait ClientControlRequestExt {
|
||||
async fn get_handshake_init_data(&mut self) -> anyhow::Result<Vec<u8>> {
|
||||
let ClientControlRequest::RegisterHandshakeInitRequest {
|
||||
protocol_version: _,
|
||||
data,
|
||||
} = self.get_control_request().await?
|
||||
else {
|
||||
bail!("unexpected ClientControlRequest")
|
||||
};
|
||||
Ok(data)
|
||||
}
|
||||
async fn get_control_request(&mut self) -> anyhow::Result<ClientControlRequest>;
|
||||
}
|
||||
#[tokio::test]
|
||||
async fn basic_handshake() -> anyhow::Result<()> {
|
||||
use anyhow::Context as _;
|
||||
|
||||
impl<T> ClientControlRequestExt for T
|
||||
where
|
||||
T: Stream<Item = WsItem> + Unpin,
|
||||
{
|
||||
async fn get_control_request(&mut self) -> anyhow::Result<ClientControlRequest> {
|
||||
let msg = self
|
||||
.next()
|
||||
.timeboxed()
|
||||
.await
|
||||
.context("timeout")?
|
||||
.context("no message!")??
|
||||
.into_text()?
|
||||
.parse::<ClientControlRequest>()?;
|
||||
Ok(msg)
|
||||
}
|
||||
}
|
||||
|
||||
struct Party<R: 'static, S: 'static> {
|
||||
rng: &'static mut R,
|
||||
keys: &'static mut ed25519::KeyPair,
|
||||
socket: &'static mut S,
|
||||
}
|
||||
|
||||
fn setup() -> (
|
||||
Party<
|
||||
impl CryptoRng + RngCore + Send,
|
||||
impl Stream<Item = WsItem> + Sink<WsMessage> + Unpin,
|
||||
>,
|
||||
Party<
|
||||
impl CryptoRng + RngCore + Send,
|
||||
impl Stream<Item = WsItem> + Sink<WsMessage> + Unpin,
|
||||
>,
|
||||
) {
|
||||
// solve the lifetime issue by just leaking the contents of the boxes
|
||||
// which is perfectly fine in test
|
||||
let client_rng = u64_seeded_rng(42).leak();
|
||||
@@ -198,139 +142,51 @@ mod tests {
|
||||
let client_ws = client_ws.leak();
|
||||
let gateway_ws = gateway_ws.leak();
|
||||
|
||||
(
|
||||
Party {
|
||||
rng: client_rng,
|
||||
keys: client_keys,
|
||||
socket: client_ws,
|
||||
},
|
||||
Party {
|
||||
rng: gateway_rng,
|
||||
keys: gateway_keys,
|
||||
socket: gateway_ws,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn basic_handshake() -> anyhow::Result<()> {
|
||||
let (client, gateway) = setup();
|
||||
|
||||
let handshake_client = client_handshake(
|
||||
client.rng,
|
||||
client.socket,
|
||||
client.keys,
|
||||
*gateway.keys.public_key(),
|
||||
Some(CURRENT_PROTOCOL_VERSION),
|
||||
client_rng,
|
||||
client_ws,
|
||||
client_keys,
|
||||
*gateway_keys.public_key(),
|
||||
false,
|
||||
true,
|
||||
ShutdownToken::default(),
|
||||
);
|
||||
|
||||
let client_fut = handshake_client.spawn_timeboxed();
|
||||
|
||||
// we need to receive the first message so that it could be propagated to the gateway side of the handshake
|
||||
let init_msg = gateway.socket.get_handshake_init_data().await?;
|
||||
let ClientControlRequest::RegisterHandshakeInitRequest {
|
||||
protocol_version: _,
|
||||
data,
|
||||
} = (gateway_ws.next())
|
||||
.timeboxed()
|
||||
.await
|
||||
.context("timeout")?
|
||||
.context("no message!")??
|
||||
.into_text()?
|
||||
.parse::<ClientControlRequest>()?
|
||||
else {
|
||||
panic!("bad message")
|
||||
};
|
||||
|
||||
let init_msg = data;
|
||||
|
||||
let handshake_gateway = gateway_handshake(
|
||||
gateway.rng,
|
||||
gateway.socket,
|
||||
gateway.keys,
|
||||
gateway_rng,
|
||||
gateway_ws,
|
||||
gateway_keys,
|
||||
init_msg,
|
||||
Some(CURRENT_PROTOCOL_VERSION),
|
||||
ShutdownToken::default(),
|
||||
);
|
||||
|
||||
let gateway_fut = handshake_gateway.spawn_timeboxed();
|
||||
let (client, gateway) = join!(client_fut, gateway_fut);
|
||||
|
||||
let client_res = client???;
|
||||
let gateway_res = gateway???;
|
||||
let client_key = client???;
|
||||
let gateway_key = gateway???;
|
||||
|
||||
// ensure the created keys are the same
|
||||
assert_eq!(client_res, gateway_res);
|
||||
assert_eq!(client_res.negotiated_protocol, CURRENT_PROTOCOL_VERSION);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn protocol_downgrade() -> anyhow::Result<()> {
|
||||
let (client, gateway) = setup();
|
||||
|
||||
let handshake_client = client_handshake(
|
||||
client.rng,
|
||||
client.socket,
|
||||
client.keys,
|
||||
*gateway.keys.public_key(),
|
||||
Some(CURRENT_PROTOCOL_VERSION + 42),
|
||||
ShutdownToken::default(),
|
||||
);
|
||||
|
||||
let client_fut = handshake_client.spawn_timeboxed();
|
||||
// we need to receive the first message so that it could be propagated to the gateway side of the handshake
|
||||
let init_msg = gateway.socket.get_handshake_init_data().await?;
|
||||
|
||||
let handshake_gateway = gateway_handshake(
|
||||
gateway.rng,
|
||||
gateway.socket,
|
||||
gateway.keys,
|
||||
init_msg,
|
||||
Some(CURRENT_PROTOCOL_VERSION + 42),
|
||||
ShutdownToken::default(),
|
||||
);
|
||||
|
||||
let gateway_fut = handshake_gateway.spawn_timeboxed();
|
||||
let (client, gateway) = join!(client_fut, gateway_fut);
|
||||
|
||||
let client_res = client???;
|
||||
let gateway_res = gateway???;
|
||||
|
||||
// ensure the created keys are the same
|
||||
assert_eq!(client_res, gateway_res);
|
||||
|
||||
// and the protocol got downgraded for both parties
|
||||
assert_eq!(client_res.negotiated_protocol, CURRENT_PROTOCOL_VERSION);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn protocol_upgrade() -> anyhow::Result<()> {
|
||||
let (client, gateway) = setup();
|
||||
|
||||
let handshake_client = client_handshake(
|
||||
client.rng,
|
||||
client.socket,
|
||||
client.keys,
|
||||
*gateway.keys.public_key(),
|
||||
None,
|
||||
ShutdownToken::default(),
|
||||
);
|
||||
|
||||
let client_fut = handshake_client.spawn_timeboxed();
|
||||
|
||||
// we need to receive the first message so that it could be propagated to the gateway side of the handshake
|
||||
let init_msg = gateway.socket.get_handshake_init_data().await?;
|
||||
|
||||
let handshake_gateway = gateway_handshake(
|
||||
gateway.rng,
|
||||
gateway.socket,
|
||||
gateway.keys,
|
||||
init_msg,
|
||||
None,
|
||||
ShutdownToken::default(),
|
||||
);
|
||||
|
||||
let gateway_fut = handshake_gateway.spawn_timeboxed();
|
||||
let (client, gateway) = join!(client_fut, gateway_fut);
|
||||
|
||||
let client_res = client???;
|
||||
let gateway_res = gateway???;
|
||||
|
||||
// ensure the created keys are the same
|
||||
assert_eq!(client_res, gateway_res);
|
||||
|
||||
// and the protocol got upgraded to the first known version
|
||||
assert_eq!(client_res.negotiated_protocol, INITIAL_PROTOCOL_VERSION);
|
||||
assert_eq!(client_key, gateway_key);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -5,11 +5,11 @@ use crate::registration::handshake::error::HandshakeError;
|
||||
use crate::registration::handshake::messages::{
|
||||
HandshakeMessage, Initialisation, MaterialExchange,
|
||||
};
|
||||
use crate::registration::handshake::{HandshakeResult, SharedGatewayKey, WsItem, KDF_SALT_LENGTH};
|
||||
use crate::registration::handshake::{SharedGatewayKey, WsItem, KDF_SALT_LENGTH};
|
||||
use crate::shared_key::SharedKeySize;
|
||||
use crate::{
|
||||
types, GatewayProtocolVersion, GatewayProtocolVersionExt, LegacySharedKeySize,
|
||||
LegacySharedKeys, SharedSymmetricKey, INITIAL_PROTOCOL_VERSION,
|
||||
types, LegacySharedKeySize, LegacySharedKeys, SharedSymmetricKey, AES_GCM_SIV_PROTOCOL_VERSION,
|
||||
CREDENTIAL_UPDATE_V2_PROTOCOL_VERSION, INITIAL_PROTOCOL_VERSION,
|
||||
};
|
||||
use futures::{Sink, SinkExt, Stream, StreamExt};
|
||||
use nym_crypto::asymmetric::{ed25519, x25519};
|
||||
@@ -54,11 +54,12 @@ pub(crate) struct State<'a, S, R> {
|
||||
/// Ideally it would always be known before the handshake was initiated.
|
||||
remote_pubkey: Option<ed25519::PublicKey>,
|
||||
|
||||
/// Version of the protocol to use during the handshake that also implicitly specifies
|
||||
/// additional features such as the type of derived shared keys, i.e.
|
||||
/// AES128Ctr + blake3 HMAC keys (legacy) or AES256-GCM-SIV (current)
|
||||
/// the above is decided by whether the specified protocol version supports the new variant or not.
|
||||
protocol_version: Option<GatewayProtocolVersion>,
|
||||
// this field is really out of place here, however, we need to propagate this information somehow
|
||||
// in order to establish correct protocol for backwards compatibility reasons
|
||||
expects_credential_usage: bool,
|
||||
|
||||
/// Specifies whether the end product should be an AES128Ctr + blake3 HMAC keys (legacy) or AES256-GCM-SIV (current)
|
||||
derive_aes256_gcm_siv_key: bool,
|
||||
|
||||
// channel to receive shutdown signal
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
@@ -71,7 +72,6 @@ impl<'a, S, R> State<'a, S, R> {
|
||||
ws_stream: &'a mut S,
|
||||
identity: &'a ed25519::KeyPair,
|
||||
remote_pubkey: Option<ed25519::PublicKey>,
|
||||
protocol_version: Option<GatewayProtocolVersion>,
|
||||
#[cfg(not(target_arch = "wasm32"))] shutdown_token: ShutdownToken,
|
||||
) -> Self
|
||||
where
|
||||
@@ -84,31 +84,40 @@ impl<'a, S, R> State<'a, S, R> {
|
||||
ephemeral_keypair,
|
||||
identity,
|
||||
remote_pubkey,
|
||||
protocol_version,
|
||||
derived_shared_keys: None,
|
||||
// later on this should become the default
|
||||
expects_credential_usage: false,
|
||||
derive_aes256_gcm_siv_key: false,
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
shutdown_token,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn with_credential_usage(mut self, expects_credential_usage: bool) -> Self {
|
||||
self.expects_credential_usage = expects_credential_usage;
|
||||
self
|
||||
}
|
||||
|
||||
pub(crate) fn with_aes256_gcm_siv_key(mut self, derive_aes256_gcm_siv_key: bool) -> Self {
|
||||
self.derive_aes256_gcm_siv_key = derive_aes256_gcm_siv_key;
|
||||
self
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub(crate) fn set_aes256_gcm_siv_key_derivation(&mut self, derive_aes256_gcm_siv_key: bool) {
|
||||
self.derive_aes256_gcm_siv_key = derive_aes256_gcm_siv_key;
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub(crate) fn local_ephemeral_key(&self) -> &x25519::PublicKey {
|
||||
self.ephemeral_keypair.public_key()
|
||||
}
|
||||
|
||||
pub(crate) fn proposed_protocol_version(&self) -> Option<GatewayProtocolVersion> {
|
||||
self.protocol_version
|
||||
}
|
||||
|
||||
pub(crate) fn set_protocol_version(&mut self, protocol_version: GatewayProtocolVersion) {
|
||||
self.protocol_version = Some(protocol_version);
|
||||
}
|
||||
|
||||
pub(crate) fn maybe_generate_initiator_salt(&mut self) -> Option<Vec<u8>>
|
||||
where
|
||||
R: CryptoRng + RngCore,
|
||||
{
|
||||
if self.protocol_version.supports_aes256_gcm_siv() {
|
||||
if self.derive_aes256_gcm_siv_key {
|
||||
let mut salt = vec![0u8; KDF_SALT_LENGTH];
|
||||
self.rng.fill_bytes(&mut salt);
|
||||
Some(salt)
|
||||
@@ -145,14 +154,13 @@ impl<'a, S, R> State<'a, S, R> {
|
||||
.private_key()
|
||||
.diffie_hellman(remote_ephemeral_key);
|
||||
|
||||
let key_size = if self.protocol_version.supports_aes256_gcm_siv() {
|
||||
let key_size = if self.derive_aes256_gcm_siv_key {
|
||||
SharedKeySize::to_usize()
|
||||
} else {
|
||||
LegacySharedKeySize::to_usize()
|
||||
};
|
||||
|
||||
// SAFETY: there is no reason for this to fail as our okm is expected to be only 16 bytes
|
||||
#[allow(clippy::expect_used)]
|
||||
// there is no reason for this to fail as our okm is expected to be only 16 bytes
|
||||
let okm = hkdf::extract_then_expand::<GatewaySharedKeyHkdfAlgorithm>(
|
||||
initiator_salt,
|
||||
&dh_result,
|
||||
@@ -161,14 +169,11 @@ impl<'a, S, R> State<'a, S, R> {
|
||||
)
|
||||
.expect("somehow too long okm was provided");
|
||||
|
||||
// SAFETY: the okm has been expanded to the length expected by the corresponding keys
|
||||
let shared_key = if self.protocol_version.supports_aes256_gcm_siv() {
|
||||
#[allow(clippy::expect_used)]
|
||||
let shared_key = if self.derive_aes256_gcm_siv_key {
|
||||
let current_key = SharedSymmetricKey::try_from_bytes(&okm)
|
||||
.expect("okm was expanded to incorrect length!");
|
||||
SharedGatewayKey::Current(current_key)
|
||||
} else {
|
||||
#[allow(clippy::expect_used)]
|
||||
let legacy_key = LegacySharedKeys::try_from_bytes(&okm)
|
||||
.expect("okm was expanded to incorrect length!");
|
||||
SharedGatewayKey::Legacy(legacy_key)
|
||||
@@ -191,7 +196,7 @@ impl<'a, S, R> State<'a, S, R> {
|
||||
.collect();
|
||||
let signature = self.identity.private_key().sign(plaintext);
|
||||
|
||||
let nonce = if self.protocol_version.supports_aes256_gcm_siv() {
|
||||
let nonce = if self.derive_aes256_gcm_siv_key {
|
||||
let mut rng = thread_rng();
|
||||
Some(random_nonce::<GatewayEncryptionAlgorithm, _>(&mut rng).to_vec())
|
||||
} else {
|
||||
@@ -199,7 +204,6 @@ impl<'a, S, R> State<'a, S, R> {
|
||||
};
|
||||
|
||||
// SAFETY: this function is only called after the local key has already been derived
|
||||
#[allow(clippy::expect_used)]
|
||||
let signature_ciphertext = self
|
||||
.derived_shared_keys
|
||||
.as_ref()
|
||||
@@ -218,14 +222,13 @@ impl<'a, S, R> State<'a, S, R> {
|
||||
remote_ephemeral_key: &x25519::PublicKey,
|
||||
) -> Result<(), HandshakeError> {
|
||||
// SAFETY: this function is only called after the local key has already been derived
|
||||
#[allow(clippy::expect_used)]
|
||||
let derived_shared_key = self
|
||||
.derived_shared_keys
|
||||
.as_ref()
|
||||
.expect("shared key was not derived!");
|
||||
|
||||
// if the [client] init message contained non-legacy flag, the associated nonce MUST be present
|
||||
if self.protocol_version.supports_aes256_gcm_siv() && remote_response.nonce.is_none() {
|
||||
if self.derive_aes256_gcm_siv_key && remote_response.nonce.is_none() {
|
||||
return Err(HandshakeError::MissingNonceForCurrentKey);
|
||||
}
|
||||
|
||||
@@ -246,7 +249,6 @@ impl<'a, S, R> State<'a, S, R> {
|
||||
.chain(self.ephemeral_keypair.public_key().to_bytes())
|
||||
.collect();
|
||||
|
||||
#[allow(clippy::unwrap_used)]
|
||||
self.remote_pubkey
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
@@ -259,10 +261,7 @@ impl<'a, S, R> State<'a, S, R> {
|
||||
self.remote_pubkey = Some(remote_pubkey)
|
||||
}
|
||||
|
||||
#[allow(clippy::complexity)]
|
||||
fn on_wg_msg(
|
||||
msg: Option<WsItem>,
|
||||
) -> Result<Option<(Vec<u8>, Option<GatewayProtocolVersion>)>, HandshakeError> {
|
||||
fn on_wg_msg(msg: Option<WsItem>) -> Result<Option<Vec<u8>>, HandshakeError> {
|
||||
let Some(msg) = msg else {
|
||||
return Err(HandshakeError::ClosedStream);
|
||||
};
|
||||
@@ -278,10 +277,9 @@ impl<'a, S, R> State<'a, S, R> {
|
||||
// hehe, that's a bit disgusting that the type system requires we explicitly ignore the
|
||||
// protocol_version field that we actually never attach at this point
|
||||
// yet another reason for the overdue refactor
|
||||
types::RegistrationHandshake::HandshakePayload {
|
||||
protocol_version,
|
||||
data,
|
||||
} => Ok(Some((data, protocol_version))),
|
||||
types::RegistrationHandshake::HandshakePayload { data, .. } => {
|
||||
Ok(Some(data))
|
||||
}
|
||||
types::RegistrationHandshake::HandshakeError { message } => {
|
||||
Err(HandshakeError::RemoteError(message))
|
||||
}
|
||||
@@ -301,9 +299,7 @@ impl<'a, S, R> State<'a, S, R> {
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
async fn _receive_handshake_message_bytes(
|
||||
&mut self,
|
||||
) -> Result<(Vec<u8>, Option<GatewayProtocolVersion>), HandshakeError>
|
||||
async fn _receive_handshake_message_bytes(&mut self) -> Result<Vec<u8>, HandshakeError>
|
||||
where
|
||||
S: Stream<Item = WsItem> + Unpin,
|
||||
{
|
||||
@@ -322,9 +318,7 @@ impl<'a, S, R> State<'a, S, R> {
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
async fn _receive_handshake_message_bytes(
|
||||
&mut self,
|
||||
) -> Result<(Vec<u8>, Option<GatewayProtocolVersion>), HandshakeError>
|
||||
async fn _receive_handshake_message_bytes(&mut self) -> Result<Vec<u8>, HandshakeError>
|
||||
where
|
||||
S: Stream<Item = WsItem> + Unpin,
|
||||
{
|
||||
@@ -337,22 +331,20 @@ impl<'a, S, R> State<'a, S, R> {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn receive_handshake_message<M>(
|
||||
&mut self,
|
||||
) -> Result<(M, Option<GatewayProtocolVersion>), HandshakeError>
|
||||
pub(crate) async fn receive_handshake_message<M>(&mut self) -> Result<M, HandshakeError>
|
||||
where
|
||||
S: Stream<Item = WsItem> + Unpin,
|
||||
M: HandshakeMessage,
|
||||
{
|
||||
// TODO: make timeout duration configurable
|
||||
let (bytes, protocol) = timeout(
|
||||
let bytes = timeout(
|
||||
Duration::from_secs(5),
|
||||
self._receive_handshake_message_bytes(),
|
||||
)
|
||||
.await
|
||||
.map_err(|_| HandshakeError::Timeout)??;
|
||||
|
||||
M::try_from_bytes(&bytes).map(|msg| (msg, protocol))
|
||||
M::try_from_bytes(&bytes)
|
||||
}
|
||||
|
||||
// upon receiving this, the receiver should terminate the handshake
|
||||
@@ -365,11 +357,21 @@ impl<'a, S, R> State<'a, S, R> {
|
||||
{
|
||||
let handshake_message = types::RegistrationHandshake::new_error(message);
|
||||
self.ws_stream
|
||||
.send(WsMessage::Text(handshake_message.into()))
|
||||
.send(WsMessage::Text(handshake_message.try_into().unwrap()))
|
||||
.await
|
||||
.map_err(|_| HandshakeError::ClosedStream)
|
||||
}
|
||||
|
||||
fn request_protocol_version(&self) -> u8 {
|
||||
if self.derive_aes256_gcm_siv_key {
|
||||
AES_GCM_SIV_PROTOCOL_VERSION
|
||||
} else if self.expects_credential_usage {
|
||||
CREDENTIAL_UPDATE_V2_PROTOCOL_VERSION
|
||||
} else {
|
||||
INITIAL_PROTOCOL_VERSION
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn send_handshake_data<M>(
|
||||
&mut self,
|
||||
inner_message: M,
|
||||
@@ -382,25 +384,18 @@ impl<'a, S, R> State<'a, S, R> {
|
||||
|
||||
let handshake_message = types::RegistrationHandshake::new_payload(
|
||||
inner_message.into_bytes(),
|
||||
self.protocol_version,
|
||||
self.request_protocol_version(),
|
||||
);
|
||||
self.ws_stream
|
||||
.send(WsMessage::Text(handshake_message.into()))
|
||||
.send(WsMessage::Text(handshake_message.try_into().unwrap()))
|
||||
.await
|
||||
.map_err(|_| HandshakeError::ClosedStream)
|
||||
}
|
||||
|
||||
/// Finish the handshake, yielding the derived shared key and implicitly dropping all borrowed
|
||||
/// values.
|
||||
pub(crate) fn finalize_handshake(self) -> HandshakeResult {
|
||||
// SAFETY: handshake can't be finalised without deriving the shared keys
|
||||
#[allow(clippy::unwrap_used)]
|
||||
HandshakeResult {
|
||||
negotiated_protocol: self
|
||||
.proposed_protocol_version()
|
||||
.unwrap_or(INITIAL_PROTOCOL_VERSION),
|
||||
derived_key: self.derived_shared_keys.unwrap(),
|
||||
}
|
||||
pub(crate) fn finalize_handshake(self) -> SharedGatewayKey {
|
||||
self.derived_shared_keys.unwrap()
|
||||
}
|
||||
|
||||
// If any step along the way failed (that are non-network related),
|
||||
|
||||
@@ -43,7 +43,6 @@ impl LegacySharedKeys {
|
||||
rng.fill_bytes(&mut salt);
|
||||
|
||||
let legacy_bytes = Zeroizing::new(self.to_bytes());
|
||||
#[allow(clippy::expect_used)]
|
||||
let okm = hkdf::extract_then_expand::<GatewaySharedKeyHkdfAlgorithm>(
|
||||
Some(&salt),
|
||||
&legacy_bytes,
|
||||
@@ -52,7 +51,6 @@ impl LegacySharedKeys {
|
||||
)
|
||||
.expect("somehow too long okm was provided");
|
||||
|
||||
#[allow(clippy::expect_used)]
|
||||
let key = SharedSymmetricKey::try_from_bytes(&okm)
|
||||
.expect("okm was expanded to incorrect length!");
|
||||
(key, salt)
|
||||
@@ -64,7 +62,6 @@ impl LegacySharedKeys {
|
||||
expected_digest: &[u8],
|
||||
) -> Option<SharedSymmetricKey> {
|
||||
let legacy_bytes = Zeroizing::new(self.to_bytes());
|
||||
#[allow(clippy::expect_used)]
|
||||
let okm = hkdf::extract_then_expand::<GatewaySharedKeyHkdfAlgorithm>(
|
||||
Some(salt),
|
||||
&legacy_bytes,
|
||||
@@ -72,8 +69,6 @@ impl LegacySharedKeys {
|
||||
SharedKeySize::to_usize(),
|
||||
)
|
||||
.expect("somehow too long okm was provided");
|
||||
|
||||
#[allow(clippy::expect_used)]
|
||||
let key = SharedSymmetricKey::try_from_bytes(&okm)
|
||||
.expect("okm was expanded to incorrect length!");
|
||||
if key.digest() != expected_digest {
|
||||
|
||||
@@ -47,8 +47,6 @@ impl SharedGatewayKey {
|
||||
}
|
||||
}
|
||||
|
||||
// it is responsibility of the caller to ensure the correct variant is present
|
||||
#[allow(clippy::panic)]
|
||||
pub fn unwrap_legacy(&self) -> &LegacySharedKeys {
|
||||
match self {
|
||||
SharedGatewayKey::Current(_) => panic!("expected legacy key"),
|
||||
|
||||
@@ -13,7 +13,7 @@ use thiserror::Error;
|
||||
use time::OffsetDateTime;
|
||||
|
||||
// specific errors (that should not be nested!!) for clients to match on
|
||||
#[derive(Debug, Copy, Clone, Error, Serialize, Deserialize, PartialEq)]
|
||||
#[derive(Debug, Copy, Clone, Error, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum SimpleGatewayRequestsError {
|
||||
#[error("insufficient bandwidth available to process the request. required: {required}B, available: {available}B")]
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::GatewayProtocolVersion;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::str::FromStr;
|
||||
|
||||
@@ -10,7 +9,7 @@ use std::str::FromStr;
|
||||
pub enum RegistrationHandshake {
|
||||
HandshakePayload {
|
||||
#[serde(default)]
|
||||
protocol_version: Option<GatewayProtocolVersion>,
|
||||
protocol_version: Option<u8>,
|
||||
data: Vec<u8>,
|
||||
},
|
||||
HandshakeError {
|
||||
@@ -19,9 +18,9 @@ pub enum RegistrationHandshake {
|
||||
}
|
||||
|
||||
impl RegistrationHandshake {
|
||||
pub fn new_payload(data: Vec<u8>, protocol_version: Option<GatewayProtocolVersion>) -> Self {
|
||||
pub fn new_payload(data: Vec<u8>, protocol_version: u8) -> Self {
|
||||
RegistrationHandshake::HandshakePayload {
|
||||
protocol_version,
|
||||
protocol_version: Some(protocol_version),
|
||||
data,
|
||||
}
|
||||
}
|
||||
@@ -49,11 +48,11 @@ impl TryFrom<String> for RegistrationHandshake {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<RegistrationHandshake> for String {
|
||||
fn from(value: RegistrationHandshake) -> Self {
|
||||
// SAFETY: we have infallible serde implementation
|
||||
#[allow(clippy::unwrap_used)]
|
||||
serde_json::to_string(&value).unwrap()
|
||||
impl TryInto<String> for RegistrationHandshake {
|
||||
type Error = serde_json::Error;
|
||||
|
||||
fn try_into(self) -> Result<String, serde_json::Error> {
|
||||
serde_json::to_string(&self)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,7 +79,7 @@ mod tests {
|
||||
assert_eq!(protocol_version, Some(42));
|
||||
assert_eq!(data, handshake_data)
|
||||
}
|
||||
_ => panic!("this branch shouldn't have been reached!"),
|
||||
_ => unreachable!("this branch shouldn't have been reached!"),
|
||||
}
|
||||
|
||||
let handshake_payload_without_protocol = RegistrationHandshake::HandshakePayload {
|
||||
@@ -98,7 +97,7 @@ mod tests {
|
||||
assert!(protocol_version.is_none());
|
||||
assert_eq!(data, handshake_data)
|
||||
}
|
||||
_ => panic!("this branch shouldn't have been reached!"),
|
||||
_ => unreachable!("this branch shouldn't have been reached!"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use crate::{
|
||||
AuthenticationFailure, GatewayProtocolVersion, GatewayRequestsError, SharedGatewayKey,
|
||||
};
|
||||
use crate::{AuthenticationFailure, GatewayRequestsError, SharedGatewayKey};
|
||||
use nym_crypto::asymmetric::ed25519;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::iter;
|
||||
@@ -22,7 +20,7 @@ pub struct AuthenticateRequest {
|
||||
|
||||
impl AuthenticateRequest {
|
||||
pub fn new(
|
||||
protocol_version: GatewayProtocolVersion,
|
||||
protocol_version: u8,
|
||||
shared_key: &SharedGatewayKey,
|
||||
identity_keys: &ed25519::KeyPair,
|
||||
) -> Result<AuthenticateRequest, GatewayRequestsError> {
|
||||
@@ -100,7 +98,7 @@ impl AuthenticateRequest {
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct AuthenticateRequestContent {
|
||||
pub protocol_version: GatewayProtocolVersion,
|
||||
pub protocol_version: u8,
|
||||
|
||||
// this is identical to the client's address
|
||||
pub client_identity: ed25519::PublicKey,
|
||||
|
||||
@@ -4,8 +4,9 @@
|
||||
use crate::models::CredentialSpendingRequest;
|
||||
use crate::text_request::authenticate::AuthenticateRequest;
|
||||
use crate::{
|
||||
GatewayProtocolVersion, GatewayRequestsError, SharedGatewayKey, SymmetricKey,
|
||||
AES_GCM_SIV_PROTOCOL_VERSION, CREDENTIAL_UPDATE_V2_PROTOCOL_VERSION, INITIAL_PROTOCOL_VERSION,
|
||||
GatewayRequestsError, SharedGatewayKey, SymmetricKey, AES_GCM_SIV_PROTOCOL_VERSION,
|
||||
AUTHENTICATE_V2_PROTOCOL_VERSION, CREDENTIAL_UPDATE_V2_PROTOCOL_VERSION,
|
||||
INITIAL_PROTOCOL_VERSION,
|
||||
};
|
||||
use nym_credentials_interface::CredentialSpendingData;
|
||||
use nym_crypto::asymmetric::ed25519;
|
||||
@@ -45,7 +46,6 @@ impl ClientRequest {
|
||||
// - the schema is self-describing which simplifies deserialisation
|
||||
|
||||
// SAFETY: the trait has been derived correctly with no weird variants
|
||||
#[allow(clippy::unwrap_used)]
|
||||
let plaintext = serde_json::to_vec(self).unwrap();
|
||||
let nonce = key.random_nonce_or_iv();
|
||||
let ciphertext = key.encrypt(&plaintext, Some(&nonce))?;
|
||||
@@ -72,7 +72,7 @@ pub enum ClientControlRequest {
|
||||
// have the shared key derived?
|
||||
Authenticate {
|
||||
#[serde(default)]
|
||||
protocol_version: Option<GatewayProtocolVersion>,
|
||||
protocol_version: Option<u8>,
|
||||
address: String,
|
||||
enc_address: String,
|
||||
iv: String,
|
||||
@@ -83,7 +83,7 @@ pub enum ClientControlRequest {
|
||||
#[serde(alias = "handshakePayload")]
|
||||
RegisterHandshakeInitRequest {
|
||||
#[serde(default)]
|
||||
protocol_version: Option<GatewayProtocolVersion>,
|
||||
protocol_version: Option<u8>,
|
||||
data: Vec<u8>,
|
||||
},
|
||||
BandwidthCredential {
|
||||
@@ -98,10 +98,6 @@ pub enum ClientControlRequest {
|
||||
enc_credential: Vec<u8>,
|
||||
iv: Vec<u8>,
|
||||
},
|
||||
UpgradeModeJWT {
|
||||
// no need to encrypt it as it's public anyway
|
||||
token: String,
|
||||
},
|
||||
ClaimFreeTestnetBandwidth,
|
||||
EncryptedRequest {
|
||||
ciphertext: Vec<u8>,
|
||||
@@ -112,14 +108,12 @@ pub enum ClientControlRequest {
|
||||
}
|
||||
|
||||
impl ClientControlRequest {
|
||||
pub fn new_legacy_authenticate(
|
||||
pub fn new_authenticate(
|
||||
address: DestinationAddressBytes,
|
||||
shared_key: &SharedGatewayKey,
|
||||
uses_credentials: bool,
|
||||
) -> Result<Self, GatewayRequestsError> {
|
||||
// if we're encrypting with non-legacy key, the remote must support AES256-GCM-SIV
|
||||
// since we are using legacy authentication, the gateway definitely doesn't understand the protocol downgrade,
|
||||
// so use the lowest possible version we can
|
||||
let protocol_version = if !shared_key.is_legacy() {
|
||||
Some(AES_GCM_SIV_PROTOCOL_VERSION)
|
||||
} else if uses_credentials {
|
||||
@@ -144,8 +138,10 @@ impl ClientControlRequest {
|
||||
pub fn new_authenticate_v2(
|
||||
shared_key: &SharedGatewayKey,
|
||||
identity_keys: &ed25519::KeyPair,
|
||||
protocol_version: GatewayProtocolVersion,
|
||||
) -> Result<Self, GatewayRequestsError> {
|
||||
// if we're using v2 authentication, we must announce at least that protocol version
|
||||
let protocol_version = AUTHENTICATE_V2_PROTOCOL_VERSION;
|
||||
|
||||
Ok(ClientControlRequest::AuthenticateV2(Box::new(
|
||||
AuthenticateRequest::new(protocol_version, shared_key, identity_keys)?,
|
||||
)))
|
||||
@@ -163,7 +159,6 @@ impl ClientControlRequest {
|
||||
"BandwidthCredentialV2".to_string()
|
||||
}
|
||||
ClientControlRequest::EcashCredential { .. } => "EcashCredential".to_string(),
|
||||
ClientControlRequest::UpgradeModeJWT { .. } => "UpgradeModeJWT".to_string(),
|
||||
ClientControlRequest::ClaimFreeTestnetBandwidth => {
|
||||
"ClaimFreeTestnetBandwidth".to_string()
|
||||
}
|
||||
@@ -197,16 +192,12 @@ impl ClientControlRequest {
|
||||
CredentialSpendingRequest::try_from_bytes(credential_bytes.as_slice())
|
||||
.map_err(|_| GatewayRequestsError::MalformedEncryption)
|
||||
}
|
||||
|
||||
pub fn new_upgrade_mode_jwt(token: String) -> Self {
|
||||
ClientControlRequest::UpgradeModeJWT { token }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ClientControlRequest> for Message {
|
||||
fn from(req: ClientControlRequest) -> Self {
|
||||
// SAFETY: all of the enum variants have valid (for json) serde impl
|
||||
#[allow(clippy::unwrap_used)]
|
||||
// it should be safe to call `unwrap` here as the message is generated by the server
|
||||
// so if it fails (and consequently panics) it's a bug that should be resolved
|
||||
let str_req = serde_json::to_string(&req).unwrap();
|
||||
Message::Text(str_req)
|
||||
}
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::{
|
||||
GatewayProtocolVersion, GatewayRequestsError, SimpleGatewayRequestsError, SymmetricKey,
|
||||
};
|
||||
use crate::{GatewayRequestsError, SimpleGatewayRequestsError, SymmetricKey};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tungstenite::Message;
|
||||
|
||||
@@ -28,7 +26,6 @@ impl SensitiveServerResponse {
|
||||
// - the schema is self-describing which simplifies deserialisation
|
||||
|
||||
// SAFETY: the trait has been derived correctly with no weird variants
|
||||
#[allow(clippy::unwrap_used)]
|
||||
let plaintext = serde_json::to_vec(self).unwrap();
|
||||
let nonce = key.random_nonce_or_iv();
|
||||
let ciphertext = key.encrypt(&plaintext, Some(&nonce))?;
|
||||
@@ -46,57 +43,31 @@ impl SensitiveServerResponse {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, PartialEq)]
|
||||
pub struct BandwidthResponse {
|
||||
pub available_total: i64,
|
||||
|
||||
/// Flag indicating whether the gateway has detected the system is undergoing the upgrade
|
||||
/// (thus it will not meter bandwidth)
|
||||
#[serde(default)]
|
||||
pub upgrade_mode: bool,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, PartialEq)]
|
||||
pub struct SendResponse {
|
||||
pub remaining_bandwidth: i64,
|
||||
|
||||
/// Flag indicating whether the gateway has detected the system is undergoing the upgrade
|
||||
/// (thus it will not meter bandwidth)
|
||||
#[serde(default)]
|
||||
pub upgrade_mode: bool,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, PartialEq)]
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
#[serde(tag = "type", rename_all = "camelCase")]
|
||||
#[non_exhaustive]
|
||||
pub enum ServerResponse {
|
||||
Authenticate {
|
||||
#[serde(default)]
|
||||
protocol_version: Option<GatewayProtocolVersion>,
|
||||
protocol_version: Option<u8>,
|
||||
status: bool,
|
||||
bandwidth_remaining: i64,
|
||||
|
||||
/// Flag indicating whether the gateway has detected the system is undergoing the upgrade
|
||||
/// (thus it will not meter bandwidth)
|
||||
#[serde(default)]
|
||||
upgrade_mode: bool,
|
||||
},
|
||||
Register {
|
||||
#[serde(default)]
|
||||
protocol_version: Option<GatewayProtocolVersion>,
|
||||
protocol_version: Option<u8>,
|
||||
status: bool,
|
||||
|
||||
/// Flag indicating whether the gateway has detected the system is undergoing the upgrade
|
||||
/// (thus it will not meter bandwidth)
|
||||
#[serde(default)]
|
||||
upgrade_mode: bool,
|
||||
},
|
||||
EncryptedResponse {
|
||||
ciphertext: Vec<u8>,
|
||||
nonce: Vec<u8>,
|
||||
},
|
||||
Bandwidth(BandwidthResponse),
|
||||
Send(SendResponse),
|
||||
Bandwidth {
|
||||
available_total: i64,
|
||||
},
|
||||
Send {
|
||||
remaining_bandwidth: i64,
|
||||
},
|
||||
SupportedProtocol {
|
||||
version: u8,
|
||||
},
|
||||
@@ -151,7 +122,6 @@ impl From<ServerResponse> for Message {
|
||||
fn from(res: ServerResponse) -> Self {
|
||||
// it should be safe to call `unwrap` here as the message is generated by the server
|
||||
// so if it fails (and consequently panics) it's a bug that should be resolved
|
||||
#[allow(clippy::unwrap_used)]
|
||||
let str_res = serde_json::to_string(&res).unwrap();
|
||||
Message::Text(str_res)
|
||||
}
|
||||
@@ -164,79 +134,3 @@ impl TryFrom<String> for ServerResponse {
|
||||
serde_json::from_str(&msg)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn server_response_serde_compat() {
|
||||
// make sure new serialisation is identical and compatible
|
||||
#[derive(Serialize, Deserialize, Debug, PartialEq)]
|
||||
#[serde(tag = "type", rename_all = "camelCase")]
|
||||
#[non_exhaustive]
|
||||
pub enum OldServerResponse {
|
||||
Bandwidth { available_total: i64 },
|
||||
Send { remaining_bandwidth: i64 },
|
||||
}
|
||||
|
||||
// OLD => NEW
|
||||
let old_bandwidth = OldServerResponse::Bandwidth {
|
||||
available_total: 100,
|
||||
};
|
||||
let old_send = OldServerResponse::Send {
|
||||
remaining_bandwidth: 100,
|
||||
};
|
||||
|
||||
let old_bandwidth_str = serde_json::to_string(&old_bandwidth).unwrap();
|
||||
let old_send_str = serde_json::to_string(&old_send).unwrap();
|
||||
|
||||
let recovered_bandwidth = ServerResponse::try_from(old_bandwidth_str).unwrap();
|
||||
assert_eq!(
|
||||
recovered_bandwidth,
|
||||
ServerResponse::Bandwidth(BandwidthResponse {
|
||||
available_total: 100,
|
||||
upgrade_mode: false
|
||||
})
|
||||
);
|
||||
|
||||
let recovered_send = ServerResponse::try_from(old_send_str).unwrap();
|
||||
assert_eq!(
|
||||
recovered_send,
|
||||
ServerResponse::Send(SendResponse {
|
||||
remaining_bandwidth: 100,
|
||||
upgrade_mode: false
|
||||
})
|
||||
);
|
||||
|
||||
// NEW => OLD
|
||||
let new_bandwidth = ServerResponse::Bandwidth(BandwidthResponse {
|
||||
available_total: 100,
|
||||
upgrade_mode: false,
|
||||
});
|
||||
let new_send = ServerResponse::Send(SendResponse {
|
||||
remaining_bandwidth: 100,
|
||||
upgrade_mode: false,
|
||||
});
|
||||
|
||||
let new_bandwidth_str = serde_json::to_string(&new_bandwidth).unwrap();
|
||||
let new_send_str = serde_json::to_string(&new_send).unwrap();
|
||||
|
||||
let recovered_bandwidth: OldServerResponse =
|
||||
serde_json::from_str(&new_bandwidth_str).unwrap();
|
||||
assert_eq!(
|
||||
recovered_bandwidth,
|
||||
OldServerResponse::Bandwidth {
|
||||
available_total: 100
|
||||
}
|
||||
);
|
||||
|
||||
let recovered_send: OldServerResponse = serde_json::from_str(&new_send_str).unwrap();
|
||||
assert_eq!(
|
||||
recovered_send,
|
||||
OldServerResponse::Send {
|
||||
remaining_bandwidth: 100
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@ thiserror = { workspace = true }
|
||||
tracing = { workspace = true }
|
||||
itertools = { workspace = true }
|
||||
inventory = { workspace = true }
|
||||
tokio = { workspace = true, features = ["rt", "macros", "time"] }
|
||||
|
||||
# used for decoding text responses (they were already implicitly included)
|
||||
bytes = { workspace = true }
|
||||
encoding_rs = { workspace = true }
|
||||
@@ -52,4 +52,5 @@ workspace = true
|
||||
features = ["tokio"]
|
||||
|
||||
[dev-dependencies]
|
||||
tracing-subscriber.workspace = true
|
||||
tokio = { workspace = true, features = ["rt", "macros"] }
|
||||
|
||||
|
||||
@@ -30,26 +30,19 @@
|
||||
use crate::ClientBuilder;
|
||||
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
net::{IpAddr, SocketAddr},
|
||||
str::FromStr,
|
||||
net::SocketAddr,
|
||||
sync::{Arc, LazyLock},
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
use hickory_resolver::{
|
||||
TokioResolver,
|
||||
config::{LookupIpStrategy, NameServerConfigGroup, ResolverConfig},
|
||||
lookup_ip::LookupIpIntoIter,
|
||||
ResolveError, TokioResolver,
|
||||
config::{LookupIpStrategy, NameServerConfigGroup, ResolverConfig, ServerOrderingStrategy},
|
||||
lookup_ip::{LookupIp, LookupIpIntoIter},
|
||||
name_server::TokioConnectionProvider,
|
||||
};
|
||||
use once_cell::sync::OnceCell;
|
||||
use reqwest::dns::{Addrs, Name, Resolve, Resolving};
|
||||
use tracing::*;
|
||||
|
||||
mod constants;
|
||||
mod static_resolver;
|
||||
pub use static_resolver::*;
|
||||
use tracing::warn;
|
||||
|
||||
impl ClientBuilder {
|
||||
/// Override the DNS resolver implementation used by the underlying http client.
|
||||
@@ -66,6 +59,10 @@ impl ClientBuilder {
|
||||
}
|
||||
}
|
||||
|
||||
struct SocketAddrs {
|
||||
iter: LookupIpIntoIter,
|
||||
}
|
||||
|
||||
// n.b. static items do not call [`Drop`] on program termination, so this won't be deallocated.
|
||||
// this is fine, as the OS can deallocate the terminated program faster than we can free memory
|
||||
// but tools like valgrind might report "memory leaks" as it isn't obvious this is intentional.
|
||||
@@ -75,24 +72,11 @@ static SHARED_RESOLVER: LazyLock<HickoryDnsResolver> = LazyLock::new(|| {
|
||||
});
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
#[allow(missing_docs)]
|
||||
#[error("hickory-dns resolver error: {hickory_error}")]
|
||||
/// Error occurring while resolving a hostname into an IP address.
|
||||
pub enum ResolveError {
|
||||
#[error("invalid name: {0}")]
|
||||
InvalidNameError(String),
|
||||
#[error("hickory-dns resolver error: {0}")]
|
||||
ResolveError(#[from] hickory_resolver::ResolveError),
|
||||
#[error("high level lookup timed out")]
|
||||
Timeout,
|
||||
#[error("hostname not found in static lookup table")]
|
||||
StaticLookupMiss,
|
||||
}
|
||||
|
||||
impl ResolveError {
|
||||
/// Returns true if the error is a timeout.
|
||||
pub fn is_timeout(&self) -> bool {
|
||||
matches!(self, ResolveError::Timeout)
|
||||
}
|
||||
pub struct HickoryDnsError {
|
||||
#[from]
|
||||
hickory_error: ResolveError,
|
||||
}
|
||||
|
||||
/// Wrapper around an `AsyncResolver`, which implements the `Resolve` trait.
|
||||
@@ -103,118 +87,71 @@ impl ResolveError {
|
||||
/// The default initialization uses a shared underlying `AsyncResolver`. If a thread local resolver
|
||||
/// is required use `thread_resolver()` to build a resolver with an independently instantiated
|
||||
/// internal `AsyncResolver`.
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Debug, Default, Clone)]
|
||||
pub struct HickoryDnsResolver {
|
||||
// Since we might not have been called in the context of a
|
||||
// Tokio Runtime in initialization, so we must delay the actual
|
||||
// construction of the resolver.
|
||||
state: Arc<OnceCell<TokioResolver>>,
|
||||
fallback: Option<Arc<OnceCell<TokioResolver>>>,
|
||||
static_base: Option<Arc<OnceCell<StaticResolver>>>,
|
||||
dont_use_shared: bool,
|
||||
/// Overall timeout for dns lookup associated with any individual host resolution. For example,
|
||||
/// use of retries, server_ordering_strategy, etc. ends absolutely if this timeout is reached.
|
||||
overall_dns_timeout: Duration,
|
||||
}
|
||||
|
||||
impl Default for HickoryDnsResolver {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
state: Default::default(),
|
||||
fallback: Default::default(),
|
||||
static_base: Default::default(),
|
||||
dont_use_shared: Default::default(),
|
||||
overall_dns_timeout: Duration::from_secs(10),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Resolve for HickoryDnsResolver {
|
||||
fn resolve(&self, name: Name) -> Resolving {
|
||||
let resolver = self.state.clone();
|
||||
let maybe_fallback = self.fallback.clone();
|
||||
let maybe_static = self.static_base.clone();
|
||||
let independent = self.dont_use_shared;
|
||||
let overall_dns_timeout = self.overall_dns_timeout;
|
||||
Box::pin(async move {
|
||||
resolve(
|
||||
name,
|
||||
resolver,
|
||||
maybe_fallback,
|
||||
maybe_static,
|
||||
independent,
|
||||
overall_dns_timeout,
|
||||
)
|
||||
.await
|
||||
.map_err(|e| Box::new(e) as Box<dyn std::error::Error + Send + Sync>)
|
||||
let resolver = resolver.get_or_try_init(|| {
|
||||
// using a closure here is slightly gross, but this makes sure that if the
|
||||
// lazy-init returns an error it can be handled by the client
|
||||
if independent {
|
||||
new_resolver()
|
||||
} else {
|
||||
Ok(SHARED_RESOLVER.state.get_or_try_init(new_resolver)?.clone())
|
||||
}
|
||||
})?;
|
||||
|
||||
// try the primary DNS resolver that we set up (DoH or DoT or whatever)
|
||||
let lookup = match resolver.lookup_ip(name.as_str()).await {
|
||||
Ok(res) => res,
|
||||
Err(e) => {
|
||||
if let Some(ref fallback) = maybe_fallback {
|
||||
// on failure use the fall back system configured DNS resolver
|
||||
if !e.is_no_records_found() {
|
||||
warn!("primary DNS failed w/ error {e}: using system fallback");
|
||||
}
|
||||
let resolver = fallback.get_or_try_init(|| {
|
||||
// using a closure here is slightly gross, but this makes sure that if the
|
||||
// lazy-init returns an error it can be handled by the client
|
||||
if independent {
|
||||
new_resolver_system()
|
||||
} else {
|
||||
Ok(SHARED_RESOLVER
|
||||
.fallback
|
||||
.as_ref()
|
||||
.ok_or(e)? // if the shared resolver has no fallback return the original error
|
||||
.get_or_try_init(new_resolver_system)?
|
||||
.clone())
|
||||
}
|
||||
})?;
|
||||
|
||||
resolver.lookup_ip(name.as_str()).await?
|
||||
} else {
|
||||
return Err(e.into());
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let addrs: Addrs = Box::new(SocketAddrs {
|
||||
iter: lookup.into_iter(),
|
||||
});
|
||||
Ok(addrs)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
async fn resolve(
|
||||
name: Name,
|
||||
resolver: Arc<OnceCell<TokioResolver>>,
|
||||
maybe_fallback: Option<Arc<OnceCell<TokioResolver>>>,
|
||||
maybe_static: Option<Arc<OnceCell<StaticResolver>>>,
|
||||
independent: bool,
|
||||
overall_dns_timeout: Duration,
|
||||
) -> Result<Addrs, ResolveError> {
|
||||
let resolver = resolver.get_or_try_init(|| HickoryDnsResolver::new_resolver(independent))?;
|
||||
|
||||
// Attempt a lookup using the primary resolver
|
||||
let resolve_fut = tokio::time::timeout(overall_dns_timeout, resolver.lookup_ip(name.as_str()));
|
||||
let primary_err = match resolve_fut.await {
|
||||
Err(_) => ResolveError::Timeout,
|
||||
Ok(Ok(lookup)) => {
|
||||
let addrs: Addrs = Box::new(SocketAddrs {
|
||||
iter: lookup.into_iter(),
|
||||
});
|
||||
return Ok(addrs);
|
||||
}
|
||||
Ok(Err(e)) => {
|
||||
// on failure use the fall back system configured DNS resolver
|
||||
if !e.is_no_records_found() {
|
||||
warn!("primary DNS failed w/ error: {e}");
|
||||
}
|
||||
e.into()
|
||||
}
|
||||
};
|
||||
|
||||
// If the primary resolver encountered an error, attempt a lookup using the fallback
|
||||
// resolver if one is configured.
|
||||
if let Some(ref fallback) = maybe_fallback {
|
||||
let resolver =
|
||||
fallback.get_or_try_init(|| HickoryDnsResolver::new_resolver_system(independent))?;
|
||||
|
||||
let resolve_fut =
|
||||
tokio::time::timeout(overall_dns_timeout, resolver.lookup_ip(name.as_str()));
|
||||
if let Ok(Ok(lookup)) = resolve_fut.await {
|
||||
let addrs: Addrs = Box::new(SocketAddrs {
|
||||
iter: lookup.into_iter(),
|
||||
});
|
||||
return Ok(addrs);
|
||||
}
|
||||
}
|
||||
|
||||
// If no record has been found and a static map of fallback addresses is configured
|
||||
// check the table for our entry
|
||||
if let Some(ref static_resolver) = maybe_static {
|
||||
debug!("checking static");
|
||||
let resolver =
|
||||
static_resolver.get_or_init(|| HickoryDnsResolver::new_static_fallback(independent));
|
||||
|
||||
if let Ok(addrs) = resolver.resolve(name).await {
|
||||
return Ok(addrs);
|
||||
}
|
||||
}
|
||||
|
||||
Err(primary_err)
|
||||
}
|
||||
|
||||
struct SocketAddrs {
|
||||
iter: LookupIpIntoIter,
|
||||
}
|
||||
|
||||
impl Iterator for SocketAddrs {
|
||||
type Item = SocketAddr;
|
||||
|
||||
@@ -225,22 +162,28 @@ impl Iterator for SocketAddrs {
|
||||
|
||||
impl HickoryDnsResolver {
|
||||
/// Attempt to resolve a domain name to a set of ['IpAddr']s
|
||||
pub async fn resolve_str(
|
||||
&self,
|
||||
name: &str,
|
||||
) -> Result<impl Iterator<Item = IpAddr> + use<>, ResolveError> {
|
||||
let n =
|
||||
Name::from_str(name).map_err(|_| ResolveError::InvalidNameError(name.to_string()))?;
|
||||
resolve(
|
||||
n,
|
||||
self.state.clone(),
|
||||
self.fallback.clone(),
|
||||
self.static_base.clone(),
|
||||
self.dont_use_shared,
|
||||
self.overall_dns_timeout,
|
||||
)
|
||||
.await
|
||||
.map(|addrs| addrs.map(|socket_addr| socket_addr.ip()))
|
||||
pub async fn resolve_str(&self, name: &str) -> Result<LookupIp, HickoryDnsError> {
|
||||
let resolver = self.state.get_or_try_init(|| self.new_resolver())?;
|
||||
|
||||
// try the primary DNS resolver that we set up (DoH or DoT or whatever)
|
||||
let lookup = match resolver.lookup_ip(name).await {
|
||||
Ok(res) => res,
|
||||
Err(e) => {
|
||||
if let Some(ref fallback) = self.fallback {
|
||||
// on failure use the fall back system configured DNS resolver
|
||||
if !e.is_no_records_found() {
|
||||
warn!("primary DNS failed w/ error {e}: using system fallback");
|
||||
}
|
||||
|
||||
let resolver = fallback.get_or_try_init(|| self.new_resolver_system())?;
|
||||
resolver.lookup_ip(name).await?
|
||||
} else {
|
||||
return Err(e.into());
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Ok(lookup)
|
||||
}
|
||||
|
||||
/// Create a (lazy-initialized) resolver that is not shared across threads.
|
||||
@@ -251,20 +194,16 @@ impl HickoryDnsResolver {
|
||||
}
|
||||
}
|
||||
|
||||
fn new_resolver(dont_use_shared: bool) -> Result<TokioResolver, ResolveError> {
|
||||
// using a closure here is slightly gross, but this makes sure that if the
|
||||
// lazy-init returns an error it can be handled by the client
|
||||
if dont_use_shared {
|
||||
fn new_resolver(&self) -> Result<TokioResolver, HickoryDnsError> {
|
||||
if self.dont_use_shared {
|
||||
new_resolver()
|
||||
} else {
|
||||
Ok(SHARED_RESOLVER.state.get_or_try_init(new_resolver)?.clone())
|
||||
}
|
||||
}
|
||||
|
||||
fn new_resolver_system(dont_use_shared: bool) -> Result<TokioResolver, ResolveError> {
|
||||
// using a closure here is slightly gross, but this makes sure that if the
|
||||
// lazy-init returns an error it can be handled by the client
|
||||
if dont_use_shared || SHARED_RESOLVER.fallback.is_none() {
|
||||
fn new_resolver_system(&self) -> Result<TokioResolver, HickoryDnsError> {
|
||||
if self.dont_use_shared || SHARED_RESOLVER.fallback.is_none() {
|
||||
new_resolver_system()
|
||||
} else {
|
||||
Ok(SHARED_RESOLVER
|
||||
@@ -276,18 +215,8 @@ impl HickoryDnsResolver {
|
||||
}
|
||||
}
|
||||
|
||||
fn new_static_fallback(dont_use_shared: bool) -> StaticResolver {
|
||||
if !dont_use_shared && let Some(ref shared_resolver) = SHARED_RESOLVER.static_base {
|
||||
shared_resolver
|
||||
.get_or_init(new_default_static_fallback)
|
||||
.clone()
|
||||
} else {
|
||||
new_default_static_fallback()
|
||||
}
|
||||
}
|
||||
|
||||
/// Enable fallback to the system default resolver if the primary (DoX) resolver fails
|
||||
pub fn enable_system_fallback(&mut self) -> Result<(), ResolveError> {
|
||||
pub fn enable_system_fallback(&mut self) -> Result<(), HickoryDnsError> {
|
||||
self.fallback = Some(Default::default());
|
||||
let _ = self
|
||||
.fallback
|
||||
@@ -302,51 +231,22 @@ impl HickoryDnsResolver {
|
||||
pub fn disable_system_fallback(&mut self) {
|
||||
self.fallback = None;
|
||||
}
|
||||
|
||||
/// Get the current map of hostname to address in use by the fallback static lookup if one
|
||||
/// exists.
|
||||
pub fn get_static_fallbacks(&self) -> Option<HashMap<String, Vec<IpAddr>>> {
|
||||
Some(self.static_base.as_ref()?.get()?.get_addrs())
|
||||
}
|
||||
|
||||
/// Set (or overwrite) the map of addresses used in the fallback static hostname lookup
|
||||
pub fn set_static_fallbacks(&mut self, addrs: HashMap<String, Vec<IpAddr>>) {
|
||||
let cell = OnceCell::new();
|
||||
cell.set(StaticResolver::new(addrs))
|
||||
.expect("infallible assign");
|
||||
self.static_base = Some(Arc::new(cell));
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a new resolver with a custom DoT based configuration. The options are overridden to look
|
||||
/// up for both IPv4 and IPv6 addresses to work with "happy eyeballs" algorithm.
|
||||
///
|
||||
/// Timeout Defaults to 5 seconds
|
||||
/// Number of retries after lookup failure before giving up Defaults to 2
|
||||
///
|
||||
/// Caches successfully resolved addresses for 30 minutes to prevent continual use of remote lookup.
|
||||
/// This resolver is intended to be used for OUR API endpoints that do not rapidly rotate IPs.
|
||||
fn new_resolver() -> Result<TokioResolver, ResolveError> {
|
||||
info!("building new configured resolver");
|
||||
|
||||
fn new_resolver() -> Result<TokioResolver, HickoryDnsError> {
|
||||
let mut name_servers = NameServerConfigGroup::quad9_tls();
|
||||
name_servers.merge(NameServerConfigGroup::quad9_https());
|
||||
name_servers.merge(NameServerConfigGroup::cloudflare_tls());
|
||||
name_servers.merge(NameServerConfigGroup::cloudflare_https());
|
||||
|
||||
configure_and_build_resolver(name_servers)
|
||||
}
|
||||
|
||||
fn configure_and_build_resolver(
|
||||
name_servers: NameServerConfigGroup,
|
||||
) -> Result<TokioResolver, ResolveError> {
|
||||
let config = ResolverConfig::from_parts(None, Vec::new(), name_servers);
|
||||
let mut resolver_builder =
|
||||
TokioResolver::builder_with_config(config, TokioConnectionProvider::default());
|
||||
|
||||
resolver_builder.options_mut().ip_strategy = LookupIpStrategy::Ipv4AndIpv6;
|
||||
// Cache successful responses for queries received by this resolver for 30 min minimum.
|
||||
resolver_builder.options_mut().positive_min_ttl = Some(Duration::from_secs(1800));
|
||||
resolver_builder.options_mut().server_ordering_strategy = ServerOrderingStrategy::RoundRobin;
|
||||
|
||||
Ok(resolver_builder.build())
|
||||
}
|
||||
@@ -354,27 +254,20 @@ fn configure_and_build_resolver(
|
||||
/// Create a new resolver with the default configuration, which reads from the system DNS config
|
||||
/// (i.e. `/etc/resolve.conf` in unix). The options are overridden to look up for both IPv4 and IPv6
|
||||
/// addresses to work with "happy eyeballs" algorithm.
|
||||
fn new_resolver_system() -> Result<TokioResolver, ResolveError> {
|
||||
fn new_resolver_system() -> Result<TokioResolver, HickoryDnsError> {
|
||||
let mut resolver_builder = TokioResolver::builder_tokio()?;
|
||||
resolver_builder.options_mut().ip_strategy = LookupIpStrategy::Ipv4AndIpv6;
|
||||
|
||||
Ok(resolver_builder.build())
|
||||
}
|
||||
|
||||
fn new_default_static_fallback() -> StaticResolver {
|
||||
StaticResolver::new(constants::default_static_addrs())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use itertools::Itertools;
|
||||
use std::collections::HashMap;
|
||||
|
||||
#[tokio::test]
|
||||
async fn reqwest_with_custom_dns() {
|
||||
let var_name = HickoryDnsResolver::default();
|
||||
let resolver = var_name;
|
||||
async fn reqwest_hickory_doh() {
|
||||
let resolver = HickoryDnsResolver::default();
|
||||
let client = reqwest::ClientBuilder::new()
|
||||
.dns_resolver(resolver.into())
|
||||
.build()
|
||||
@@ -393,7 +286,7 @@ mod test {
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn dns_lookup() -> Result<(), ResolveError> {
|
||||
async fn dns_lookup() -> Result<(), HickoryDnsError> {
|
||||
let resolver = HickoryDnsResolver::default();
|
||||
|
||||
let domain = "ifconfig.me";
|
||||
@@ -403,124 +296,4 @@ mod test {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn static_resolver_as_fallback() -> Result<(), ResolveError> {
|
||||
let example_domain = "non-existent.nymvpn.com";
|
||||
let mut resolver = HickoryDnsResolver {
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let result = resolver.resolve_str(example_domain).await;
|
||||
assert!(result.is_err()); // should be NXDomain
|
||||
|
||||
resolver.static_base = Some(Default::default());
|
||||
|
||||
let mut addr_map = HashMap::new();
|
||||
let example_ip4: IpAddr = "10.10.10.10".parse().unwrap();
|
||||
let example_ip6: IpAddr = "dead::beef".parse().unwrap();
|
||||
addr_map.insert(example_domain.to_string(), vec![example_ip4, example_ip6]);
|
||||
|
||||
resolver.set_static_fallbacks(addr_map);
|
||||
|
||||
let mut addrs = resolver.resolve_str(example_domain).await?;
|
||||
assert!(addrs.contains(&example_ip4));
|
||||
assert!(addrs.contains(&example_ip6));
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod failure_test {
|
||||
use super::*;
|
||||
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
|
||||
|
||||
/// IP addresses guaranteed to fail attempts to resolve
|
||||
///
|
||||
/// Addresses drawn from blocks set off by RFC5737 (ipv4) and RFC3849 (ipv6)
|
||||
const GUARANTEED_BROKEN_IPS_1: &[IpAddr] = &[
|
||||
IpAddr::V4(Ipv4Addr::new(192, 0, 2, 1)),
|
||||
IpAddr::V4(Ipv4Addr::new(198, 51, 100, 1)),
|
||||
IpAddr::V6(Ipv6Addr::new(0x2001, 0x0db8, 0, 0, 0, 0, 0, 0x1111)),
|
||||
IpAddr::V6(Ipv6Addr::new(0x2001, 0x0db8, 0, 0, 0, 0, 0, 0x1001)),
|
||||
];
|
||||
|
||||
// Create a resolver that behaves the same as the custom configured router, except for the fact
|
||||
// that it is guaranteed to fail.
|
||||
fn build_broken_resolver() -> Result<TokioResolver, ResolveError> {
|
||||
info!("building new faulty resolver");
|
||||
|
||||
let mut broken_ns_group = NameServerConfigGroup::from_ips_tls(
|
||||
GUARANTEED_BROKEN_IPS_1,
|
||||
853,
|
||||
"cloudflare-dns.com".to_string(),
|
||||
true,
|
||||
);
|
||||
let broken_ns_https = NameServerConfigGroup::from_ips_https(
|
||||
GUARANTEED_BROKEN_IPS_1,
|
||||
443,
|
||||
"cloudflare-dns.com".to_string(),
|
||||
true,
|
||||
);
|
||||
broken_ns_group.merge(broken_ns_https);
|
||||
|
||||
configure_and_build_resolver(broken_ns_group)
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn dns_lookup_failures() -> Result<(), ResolveError> {
|
||||
let time_start = std::time::Instant::now();
|
||||
|
||||
let r = OnceCell::new();
|
||||
r.set(build_broken_resolver().expect("failed to build resolver"))
|
||||
.expect("broken resolver init error");
|
||||
|
||||
// create a new resolver that won't mess with the shared resolver used by other tests
|
||||
let resolver = HickoryDnsResolver {
|
||||
dont_use_shared: true,
|
||||
state: Arc::new(r),
|
||||
overall_dns_timeout: Duration::from_secs(5),
|
||||
..Default::default()
|
||||
};
|
||||
build_broken_resolver()?;
|
||||
let domain = "ifconfig.me";
|
||||
let result = resolver.resolve_str(domain).await;
|
||||
assert!(result.is_err_and(|e| matches!(e, ResolveError::Timeout)));
|
||||
|
||||
let duration = time_start.elapsed();
|
||||
assert!(duration < resolver.overall_dns_timeout + Duration::from_secs(1));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn fallback_to_static() -> Result<(), ResolveError> {
|
||||
let r = OnceCell::new();
|
||||
r.set(build_broken_resolver().expect("failed to build resolver"))
|
||||
.expect("broken resolver init error");
|
||||
|
||||
// create a new resolver that won't mess with the shared resolver used by other tests
|
||||
let resolver = HickoryDnsResolver {
|
||||
dont_use_shared: true,
|
||||
state: Arc::new(r),
|
||||
static_base: Some(Default::default()),
|
||||
overall_dns_timeout: Duration::from_secs(5),
|
||||
..Default::default()
|
||||
};
|
||||
build_broken_resolver()?;
|
||||
|
||||
// successful lookup using fallback to static resolver
|
||||
let domain = "nymvpn.com";
|
||||
let _ = resolver
|
||||
.resolve_str(domain)
|
||||
.await
|
||||
.expect("failed to resolve address in static lookup");
|
||||
|
||||
// unsuccessful lookup - primary times out, and not in
|
||||
let domain = "non-existent.nymtech.net";
|
||||
let result = resolver.resolve_str(domain).await;
|
||||
assert!(result.is_err_and(|e| matches!(e, ResolveError::Timeout)));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,95 +0,0 @@
|
||||
#![allow(missing_docs)]
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
|
||||
|
||||
pub const NYM_API_DOMAIN: &str = "validator.nymtech.net";
|
||||
pub const NYM_API_IPS: &[IpAddr] = &[IpAddr::V4(Ipv4Addr::new(212, 71, 233, 232))];
|
||||
|
||||
pub const NYM_VPN_API_DOMAIN: &str = "nymvpn.com";
|
||||
pub const NYM_VPN_API_IPS: &[IpAddr] = &[IpAddr::V4(Ipv4Addr::new(76, 76, 21, 21))];
|
||||
|
||||
pub const NYM_FRONTDOOR_VERCEL_DOMAIN: &str = "nym-frontdoor.vercel.app";
|
||||
pub const NYM_FRONTDOOR_VERCEL_IPS: &[IpAddr] = &[
|
||||
IpAddr::V4(Ipv4Addr::new(64, 29, 17, 195)),
|
||||
IpAddr::V4(Ipv4Addr::new(216, 198, 79, 195)),
|
||||
];
|
||||
|
||||
pub const NYM_FRONTDOOR_FASTLY_DOMAIN: &str = "nym-frontdoor.global.ssl.fastly.net";
|
||||
pub const NYM_FRONTDOOR_FASTLY_IPS: &[IpAddr] = &[
|
||||
IpAddr::V4(Ipv4Addr::new(151, 101, 193, 194)),
|
||||
IpAddr::V4(Ipv4Addr::new(151, 101, 129, 194)),
|
||||
IpAddr::V4(Ipv4Addr::new(151, 101, 1, 194)),
|
||||
IpAddr::V4(Ipv4Addr::new(151, 101, 65, 194)),
|
||||
];
|
||||
|
||||
pub const NYMVPN_FRONTDOOR_FASTLY_DOMAIN: &str = "nymvpn-frontdoor.global.ssl.fastly.net";
|
||||
pub const NYMVPN_FRONTDOOR_FASTLY_IPS: &[IpAddr] = &[
|
||||
IpAddr::V4(Ipv4Addr::new(151, 101, 193, 194)),
|
||||
IpAddr::V4(Ipv4Addr::new(151, 101, 129, 194)),
|
||||
IpAddr::V4(Ipv4Addr::new(151, 101, 1, 194)),
|
||||
IpAddr::V4(Ipv4Addr::new(151, 101, 65, 194)),
|
||||
];
|
||||
|
||||
pub const YELP_FASTLY_DOMAIN: &str = "yelp.global.ssl.fastly.net";
|
||||
pub const YELP_FASTLY_IPS: &[IpAddr] = &[
|
||||
IpAddr::V4(Ipv4Addr::new(151, 101, 193, 194)),
|
||||
IpAddr::V4(Ipv4Addr::new(151, 101, 129, 194)),
|
||||
IpAddr::V4(Ipv4Addr::new(151, 101, 1, 194)),
|
||||
IpAddr::V4(Ipv4Addr::new(151, 101, 65, 194)),
|
||||
];
|
||||
|
||||
pub const VERCEL_APP_DOMAIN: &str = "vercel.app";
|
||||
pub const VERCEL_APP_IPS: &[IpAddr] = &[
|
||||
IpAddr::V4(Ipv4Addr::new(64, 29, 17, 195)),
|
||||
IpAddr::V4(Ipv4Addr::new(216, 198, 79, 195)),
|
||||
];
|
||||
|
||||
pub const VERCEL_COM_DOMAIN: &str = "vercel.com";
|
||||
pub const VERCEL_COM_IPS: &[IpAddr] = &[
|
||||
IpAddr::V4(Ipv4Addr::new(198, 169, 2, 129)),
|
||||
IpAddr::V4(Ipv4Addr::new(198, 169, 1, 193)),
|
||||
];
|
||||
|
||||
pub const NYM_COM_DOMAIN: &str = "nym.com";
|
||||
pub const NYM_COM_IPS: &[IpAddr] = &[IpAddr::V4(Ipv4Addr::new(76, 76, 21, 22))];
|
||||
|
||||
pub const NYM_STATS_API_DOMAIN: &str = "nym-statistics-api.nymtech.cc";
|
||||
pub const NYM_STATS_API_IPS: &[IpAddr] = &[IpAddr::V4(Ipv4Addr::new(91, 92, 153, 96))];
|
||||
|
||||
pub const NYM_RPC_DOMAIN: &str = "rpc.nymtech.net";
|
||||
pub const NYM_RPC_IPS: &[IpAddr] = &[
|
||||
IpAddr::V4(Ipv4Addr::new(194, 182, 169, 49)),
|
||||
IpAddr::V4(Ipv4Addr::new(91, 92, 200, 116)),
|
||||
IpAddr::V6(Ipv6Addr::new(
|
||||
0x2a04, 0xc43, 0xe00, 0x6f28, 0x400, 0xd8ff, 0xfe00, 0x1483,
|
||||
)),
|
||||
IpAddr::V6(Ipv6Addr::new(
|
||||
0x2a04, 0xc46, 0xe00, 0x6f28, 0x4b3, 0x68ff, 0xfe00, 0x460,
|
||||
)),
|
||||
];
|
||||
|
||||
pub fn default_static_addrs() -> HashMap<String, Vec<IpAddr>> {
|
||||
let mut m = HashMap::new();
|
||||
m.insert(NYM_API_DOMAIN.to_string(), NYM_API_IPS.to_vec());
|
||||
m.insert(NYM_VPN_API_DOMAIN.to_string(), NYM_VPN_API_IPS.to_vec());
|
||||
m.insert(
|
||||
NYM_FRONTDOOR_VERCEL_DOMAIN.to_string(),
|
||||
NYM_FRONTDOOR_VERCEL_IPS.to_vec(),
|
||||
);
|
||||
m.insert(
|
||||
NYM_FRONTDOOR_FASTLY_DOMAIN.to_string(),
|
||||
NYM_FRONTDOOR_FASTLY_IPS.to_vec(),
|
||||
);
|
||||
m.insert(
|
||||
NYMVPN_FRONTDOOR_FASTLY_DOMAIN.to_string(),
|
||||
NYMVPN_FRONTDOOR_FASTLY_IPS.to_vec(),
|
||||
);
|
||||
m.insert(YELP_FASTLY_DOMAIN.to_string(), YELP_FASTLY_IPS.to_vec());
|
||||
m.insert(VERCEL_APP_DOMAIN.to_string(), VERCEL_APP_IPS.to_vec());
|
||||
m.insert(VERCEL_COM_DOMAIN.to_string(), VERCEL_COM_IPS.to_vec());
|
||||
m.insert(NYM_COM_DOMAIN.to_string(), NYM_COM_IPS.to_vec());
|
||||
m.insert(NYM_STATS_API_DOMAIN.to_string(), NYM_STATS_API_IPS.to_vec());
|
||||
m.insert(NYM_RPC_DOMAIN.to_string(), NYM_RPC_IPS.to_vec());
|
||||
m
|
||||
}
|
||||
@@ -1,89 +0,0 @@
|
||||
use crate::dns::ResolveError;
|
||||
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
net::{IpAddr, SocketAddr},
|
||||
sync::{Arc, Mutex},
|
||||
};
|
||||
|
||||
use reqwest::dns::{Addrs, Name, Resolve, Resolving};
|
||||
use tracing::*;
|
||||
|
||||
#[derive(Debug, Default, Clone)]
|
||||
pub struct StaticResolver {
|
||||
static_addr_map: Arc<Mutex<HashMap<String, Vec<IpAddr>>>>,
|
||||
}
|
||||
|
||||
impl StaticResolver {
|
||||
pub fn new(static_entries: HashMap<String, Vec<IpAddr>>) -> StaticResolver {
|
||||
debug!("building static resolver");
|
||||
Self {
|
||||
static_addr_map: Arc::new(Mutex::new(static_entries)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_addrs(&self) -> HashMap<String, Vec<IpAddr>> {
|
||||
self.static_addr_map.lock().unwrap().clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl Resolve for StaticResolver {
|
||||
fn resolve(&self, name: Name) -> Resolving {
|
||||
debug!("looking up {name:?} in static resolver");
|
||||
let addr_map = self.static_addr_map.clone();
|
||||
Box::pin(async move {
|
||||
let addr_map = addr_map.lock().unwrap();
|
||||
let lookup = match addr_map.get(name.as_str()) {
|
||||
None => return Err(ResolveError::StaticLookupMiss.into()),
|
||||
Some(addrs) => addrs,
|
||||
};
|
||||
let addrs: Addrs = Box::new(
|
||||
lookup
|
||||
.clone()
|
||||
.into_iter()
|
||||
.map(|ip_addr| SocketAddr::new(ip_addr, 0)),
|
||||
);
|
||||
|
||||
Ok(addrs)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use itertools::Itertools;
|
||||
|
||||
use super::*;
|
||||
use std::error::Error as StdError;
|
||||
use std::str::FromStr;
|
||||
|
||||
#[tokio::test]
|
||||
async fn lookup_using_static_resolver() -> Result<(), Box<dyn StdError + Send + Sync>> {
|
||||
let example_domain = String::from("static.nymvpn.com");
|
||||
|
||||
// lookup for domain for which there is no entry
|
||||
let resolver = StaticResolver::new(HashMap::new());
|
||||
|
||||
let url = reqwest::dns::Name::from_str(&example_domain).unwrap();
|
||||
let result = resolver.resolve(url).await;
|
||||
assert!(result.is_err());
|
||||
match result {
|
||||
Ok(_) => panic!("lookup with empty map should fail"),
|
||||
Err(e) => assert_eq!(e.to_string(), ResolveError::StaticLookupMiss.to_string()),
|
||||
}
|
||||
|
||||
// Successful lookup
|
||||
let mut addr_map = HashMap::new();
|
||||
let example_ip4: IpAddr = "10.10.10.10".parse().unwrap();
|
||||
let example_ip6: IpAddr = "dead::beef".parse().unwrap();
|
||||
addr_map.insert(example_domain.clone(), vec![example_ip4, example_ip6]);
|
||||
|
||||
let url = reqwest::dns::Name::from_str(&example_domain).unwrap();
|
||||
let resolver = StaticResolver::new(addr_map);
|
||||
let mut addrs = resolver.resolve(url).await?;
|
||||
assert!(addrs.contains(&SocketAddr::new(example_ip4, 0)));
|
||||
assert!(addrs.contains(&SocketAddr::new(example_ip6, 0)));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -121,89 +121,4 @@ mod tests {
|
||||
// println!("{response:?}");
|
||||
assert_eq!(response.status(), 200);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn fallback_on_failure() {
|
||||
let url1 = Url::new(
|
||||
"https://fake-domain.nymtech.net",
|
||||
Some(vec![
|
||||
"https://fake-front-1.nymtech.net",
|
||||
"https://fake-front-2.nymtech.net",
|
||||
]),
|
||||
)
|
||||
.unwrap();
|
||||
let url2 = Url::new(
|
||||
"https://validator.global.ssl.fastly.net",
|
||||
Some(vec!["https://yelp.global.ssl.fastly.net"]),
|
||||
)
|
||||
.unwrap(); // fastly
|
||||
|
||||
let client = ClientBuilder::new_with_urls(vec![url1, url2])
|
||||
.expect("bad url")
|
||||
.with_fronting(FrontPolicy::Always)
|
||||
.build()
|
||||
.expect("failed to build client");
|
||||
|
||||
// Check that the initial configuration has the broken domain and front.
|
||||
assert_eq!(
|
||||
client.current_url().as_str(),
|
||||
"https://fake-domain.nymtech.net/",
|
||||
);
|
||||
assert_eq!(
|
||||
client.current_url().front_str(),
|
||||
Some("fake-front-1.nymtech.net"),
|
||||
);
|
||||
|
||||
let result = client
|
||||
.send_request::<_, (), &str, &str>(
|
||||
reqwest::Method::GET,
|
||||
&["api", "v1", "network", "details"],
|
||||
NO_PARAMS,
|
||||
None,
|
||||
)
|
||||
.await;
|
||||
assert!(result.is_err());
|
||||
|
||||
// Check that the host configuration updated the front on error.
|
||||
assert_eq!(
|
||||
client.current_url().as_str(),
|
||||
"https://fake-domain.nymtech.net/",
|
||||
);
|
||||
assert_eq!(
|
||||
client.current_url().front_str(),
|
||||
Some("fake-front-2.nymtech.net"),
|
||||
);
|
||||
|
||||
let result = client
|
||||
.send_request::<_, (), &str, &str>(
|
||||
reqwest::Method::GET,
|
||||
&["api", "v1", "network", "details"],
|
||||
NO_PARAMS,
|
||||
None,
|
||||
)
|
||||
.await;
|
||||
assert!(result.is_err());
|
||||
|
||||
// Check that the host configuration updated the domain and front on error.
|
||||
assert_eq!(
|
||||
client.current_url().as_str(),
|
||||
"https://validator.global.ssl.fastly.net/",
|
||||
);
|
||||
assert_eq!(
|
||||
client.current_url().front_str(),
|
||||
Some("yelp.global.ssl.fastly.net"),
|
||||
);
|
||||
|
||||
let response = client
|
||||
.send_request::<_, (), &str, &str>(
|
||||
reqwest::Method::GET,
|
||||
&["api", "v1", "network", "details"],
|
||||
NO_PARAMS,
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.expect("failed get request");
|
||||
|
||||
assert_eq!(response.status(), 200);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -179,7 +179,7 @@ mod dns;
|
||||
mod path;
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub use dns::{HickoryDnsResolver, ResolveError};
|
||||
pub use dns::{HickoryDnsError, HickoryDnsResolver};
|
||||
|
||||
// helper for generating user agent based on binary information
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
@@ -395,13 +395,6 @@ pub enum HttpClientError {
|
||||
#[error("failed to resolve request to {url} due to data inconsistency: {details}")]
|
||||
InternalResponseInconsistency { url: ::url::Url, details: String },
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
#[error("encountered dns failure: {inner}")]
|
||||
DnsLookupFailure {
|
||||
#[from]
|
||||
inner: ResolveError,
|
||||
},
|
||||
|
||||
#[error("Failed to encode bincode: {0}")]
|
||||
Bincode(#[from] bincode::Error),
|
||||
|
||||
@@ -428,8 +421,6 @@ impl HttpClientError {
|
||||
HttpClientError::ReqwestClientError { source } => source.is_timeout(),
|
||||
HttpClientError::RequestSendFailure { source, .. } => source.0.is_timeout(),
|
||||
HttpClientError::ResponseReadFailure { source, .. } => source.0.is_timeout(),
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
HttpClientError::DnsLookupFailure { inner } => inner.is_timeout(),
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
HttpClientError::RequestTimeout => true,
|
||||
_ => false,
|
||||
@@ -784,7 +775,6 @@ impl ClientBuilder {
|
||||
base_urls: self.urls,
|
||||
current_idx: Arc::new(AtomicUsize::new(0)),
|
||||
reqwest_client,
|
||||
using_secure_dns: self.use_secure_dns,
|
||||
|
||||
#[cfg(feature = "tunneling")]
|
||||
front: self.front,
|
||||
@@ -805,7 +795,6 @@ pub struct Client {
|
||||
base_urls: Vec<Url>,
|
||||
current_idx: Arc<AtomicUsize>,
|
||||
reqwest_client: reqwest::Client,
|
||||
using_secure_dns: bool,
|
||||
|
||||
#[cfg(feature = "tunneling")]
|
||||
front: Option<fronted::Front>,
|
||||
@@ -863,7 +852,6 @@ impl Client {
|
||||
base_urls: vec![new_url],
|
||||
current_idx: Arc::new(Default::default()),
|
||||
reqwest_client: self.reqwest_client.clone(),
|
||||
using_secure_dns: self.using_secure_dns,
|
||||
|
||||
#[cfg(feature = "tunneling")]
|
||||
front: self.front.clone(),
|
||||
@@ -895,34 +883,8 @@ impl Client {
|
||||
self.retry_limit = limit;
|
||||
}
|
||||
|
||||
fn matches_current_host(&self, url: &Url) -> bool {
|
||||
if cfg!(feature = "tunneling") {
|
||||
if let Some(ref front) = self.front
|
||||
&& front.is_enabled()
|
||||
{
|
||||
url.host_str() == self.current_url().front_str()
|
||||
} else {
|
||||
url.host_str() == self.current_url().host_str()
|
||||
}
|
||||
} else {
|
||||
url.host_str() == self.current_url().host_str()
|
||||
}
|
||||
}
|
||||
|
||||
/// If multiple base urls are available rotate to next (e.g. when the current one resulted in an error)
|
||||
///
|
||||
/// Takes an optional URL argument. If this is none, the current host will be updated automatically.
|
||||
/// If a url is provided first check that the CURRENT host matches the hostname in the URL before
|
||||
/// triggering a rotation. This is meant to prevent parallel requests that fail from rotating the host
|
||||
/// multiple times.
|
||||
fn update_host(&self, maybe_url: Option<Url>) {
|
||||
// If a causal url is provided and it doesn't match the hostname currently in use, skip update.
|
||||
if let Some(err_url) = maybe_url
|
||||
&& !self.matches_current_host(&err_url)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
fn update_host(&self) {
|
||||
#[cfg(feature = "tunneling")]
|
||||
if let Some(ref front) = self.front
|
||||
&& front.is_enabled()
|
||||
@@ -1086,28 +1048,8 @@ impl ApiClientCore for Client {
|
||||
.build()
|
||||
.map_err(HttpClientError::reqwest_client_build_error)?;
|
||||
self.apply_hosts_to_req(&mut req);
|
||||
let url: Url = req.url().clone().into();
|
||||
|
||||
// try an explicit DNS resolution - if successful then it will be in cache when reqwest
|
||||
// goes to execute the request. If failure then we get to handle the DNS lookup error.
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
if self.using_secure_dns
|
||||
&& let Some(hostname) = req.url().domain()
|
||||
// Default here will use a shared resolver instance
|
||||
&& let Err(err) = HickoryDnsResolver::default().resolve_str(hostname).await
|
||||
{
|
||||
// on failure update host, but don't trigger fronting enable.
|
||||
self.update_host(Some(url.clone()));
|
||||
|
||||
if attempts < self.retry_limit {
|
||||
attempts += 1;
|
||||
warn!(
|
||||
"Retrying request due to dns error on attempt ({attempts}/{}): {err}",
|
||||
self.retry_limit
|
||||
);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
let url = req.url().clone();
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
let response: Result<Response, HttpClientError> = {
|
||||
@@ -1125,39 +1067,25 @@ impl ApiClientCore for Client {
|
||||
match response {
|
||||
Ok(resp) => return Ok(resp),
|
||||
Err(err) => {
|
||||
// only if there was a network issue should we consider updating the host info
|
||||
//
|
||||
// note: for now this includes DNS resolution failure, I am not sure how I would go about
|
||||
// segregating that based on the interface provided by request for errors.
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
let is_network_err = err.is_timeout();
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
let is_network_err = err.is_timeout() || err.is_connect();
|
||||
// if we have multiple urls, update to the next
|
||||
self.update_host();
|
||||
|
||||
if is_network_err {
|
||||
// if we have multiple urls, update to the next
|
||||
self.update_host(Some(url.clone()));
|
||||
|
||||
#[cfg(feature = "tunneling")]
|
||||
if let Some(ref front) = self.front {
|
||||
// If fronting is set to be enabled on error, enable domain fronting as we
|
||||
// have encountered an error.
|
||||
let was_enabled = front.is_enabled();
|
||||
front.retry_enable();
|
||||
if !was_enabled && front.is_enabled() {
|
||||
tracing::info!(
|
||||
"Domain fronting activated after connection failure: {err}",
|
||||
);
|
||||
}
|
||||
#[cfg(feature = "tunneling")]
|
||||
if let Some(ref front) = self.front {
|
||||
// If fronting is set to be enabled on error, enable domain fronting as we
|
||||
// have encountered an error.
|
||||
let was_enabled = front.is_enabled();
|
||||
front.retry_enable();
|
||||
if !was_enabled && front.is_enabled() {
|
||||
tracing::info!(
|
||||
"Domain fronting activated after connection failure: {err}",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if attempts < self.retry_limit {
|
||||
warn!("Retrying request due to http error: {err}");
|
||||
attempts += 1;
|
||||
warn!(
|
||||
"Retrying request due to http error on attempt ({attempts}/{}): {err}",
|
||||
self.retry_limit
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -1166,7 +1094,7 @@ impl ApiClientCore for Client {
|
||||
if #[cfg(target_arch = "wasm32")] {
|
||||
return Err(err);
|
||||
} else {
|
||||
return Err(HttpClientError::request_send_error(url.into(), err));
|
||||
return Err(HttpClientError::request_send_error(url, err));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -91,15 +91,14 @@ fn sanitizing_urls() {
|
||||
#[tokio::test]
|
||||
async fn api_client_retry() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let client = ClientBuilder::new_with_urls(vec![
|
||||
"http://broken.nym.test".parse()?, // This will fail because of DNS (rotate)
|
||||
"http://127.0.0.1:9".parse()?, // This will fail because of TCP refused (rotate)
|
||||
"https://httpbin.org/status/200".parse()?, // This should succeed
|
||||
"http://broken.nym.test".parse()?, // This will fail
|
||||
"https://httpbin.org/status/200".parse()?, // This will succeed
|
||||
])?
|
||||
.with_retries(3)
|
||||
.build()?;
|
||||
|
||||
let req = client.create_get_request(&[], NO_PARAMS).unwrap();
|
||||
let _resp = client.send(req).await?;
|
||||
let resp = client.send(req).await?;
|
||||
|
||||
// The main test is that we successfully retried and switched to the working URL
|
||||
// We accept any response from the working endpoint since external services can be unreliable
|
||||
@@ -108,9 +107,7 @@ async fn api_client_retry() -> Result<(), Box<dyn std::error::Error>> {
|
||||
"https://httpbin.org/status/200"
|
||||
);
|
||||
|
||||
// // This assert can be unreliable due to factors beyond our control and beyond the scope of
|
||||
// // this test
|
||||
// assert_eq!(_resp.status(), StatusCode::OK);
|
||||
println!("Response status: {}", resp.status());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -126,7 +123,7 @@ fn host_updating() {
|
||||
assert_eq!(current_url.front_str(), None);
|
||||
|
||||
// update the url
|
||||
client.update_host(None);
|
||||
client.update_host();
|
||||
|
||||
// check that the url is still the same since there is one URL
|
||||
assert_eq!(client.current_url().as_str(), "http://nym-api1.test/");
|
||||
@@ -141,13 +138,13 @@ fn host_updating() {
|
||||
client.change_base_urls(new_urls);
|
||||
assert_eq!(client.current_url().as_str(), "http://nym-api1.test/");
|
||||
|
||||
client.update_host(None);
|
||||
client.update_host();
|
||||
|
||||
// check that the url got updated now that there are multiple URLs
|
||||
assert_eq!(client.current_url().as_str(), "http://nym-api2.test/");
|
||||
assert_eq!(client.current_url().front_str(), None);
|
||||
|
||||
client.update_host(None);
|
||||
client.update_host();
|
||||
assert_eq!(client.current_url().as_str(), "http://nym-api1.test/");
|
||||
|
||||
// =======================================
|
||||
@@ -165,33 +162,12 @@ fn host_updating() {
|
||||
|
||||
assert_eq!(client.current_url().as_str(), "http://nym-api1.test/");
|
||||
|
||||
client.update_host(None);
|
||||
client.update_host();
|
||||
|
||||
// check that the url got updated now that there are multiple URLs
|
||||
assert_eq!(client.current_url().as_str(), "http://nym-api2.test/");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn host_updating_url_conditioned() {
|
||||
let url1 = Url::new("http://nym-api1.test", None).unwrap();
|
||||
let url2 = Url::new("http://nym-api2.test", None).unwrap();
|
||||
let urls = vec![url1.clone(), url2.clone()];
|
||||
let client = ClientBuilder::new_with_urls(urls).unwrap().build().unwrap();
|
||||
|
||||
assert_eq!(client.current_url().as_str(), "http://nym-api1.test/");
|
||||
|
||||
// Try to update with a URL that does NOT match current - should result in no change
|
||||
client.update_host(Some(Url::parse("http://example.com").unwrap()));
|
||||
|
||||
// check that the url did NOT get updated
|
||||
assert_eq!(client.current_url().as_str(), "http://nym-api1.test/");
|
||||
assert_eq!(client.current_url().front_str(), None);
|
||||
|
||||
// Try to update with a URL that DOES match current - should result in no change
|
||||
client.update_host(Some(url1));
|
||||
assert_eq!(client.current_url().as_str(), "http://nym-api2.test/");
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "tunneling")]
|
||||
fn fronted_host_updating() {
|
||||
@@ -208,7 +184,7 @@ fn fronted_host_updating() {
|
||||
assert_eq!(current_url.front_str(), Some("cdn1.test"));
|
||||
|
||||
// update the url
|
||||
client.update_host(None);
|
||||
client.update_host();
|
||||
|
||||
// check that the url is still the same since there is one URL and one front
|
||||
let current_url = client.current_url();
|
||||
@@ -233,7 +209,7 @@ fn fronted_host_updating() {
|
||||
assert_eq!(current_url.front_str(), Some("cdn1.test"));
|
||||
|
||||
// update the url - this should keep the same host but change the front
|
||||
client.update_host(None);
|
||||
client.update_host();
|
||||
|
||||
let current_url = client.current_url();
|
||||
// check that the url is still the same since there is one URL
|
||||
@@ -241,7 +217,7 @@ fn fronted_host_updating() {
|
||||
assert_eq!(current_url.front_str(), Some("cdn2.test"));
|
||||
|
||||
// update the url - this should wrap around to the first front as the second url is not fronted
|
||||
client.update_host(None);
|
||||
client.update_host();
|
||||
|
||||
let current_url = client.current_url();
|
||||
assert_eq!(current_url.as_str(), "http://nym-api.test/");
|
||||
|
||||
@@ -54,12 +54,6 @@ pub const NYM_APIS: &[ApiUrlConst] = &[
|
||||
];
|
||||
|
||||
pub const NYM_VPN_API: &str = "https://nymvpn.com/api/";
|
||||
|
||||
pub const UPGRADE_MODE_ATTESTATION_URL: &str =
|
||||
"https://nymtech.net/.wellknown/upgrade-mode/attestation.json";
|
||||
pub const UPGRADE_MODE_ATTESTER_ED25519_BS58_PUBKEY: &str =
|
||||
"3bgffBYcfFkTTXc2npNNn9MkddFZ3H2LrPjXDmnJzrqd";
|
||||
|
||||
#[cfg(feature = "network")]
|
||||
pub const NYM_VPN_APIS: &[ApiUrlConst] = &[
|
||||
ApiUrlConst {
|
||||
@@ -165,14 +159,6 @@ pub fn export_to_env() {
|
||||
set_var_to_default(var_names::NYXD_WEBSOCKET, NYXD_WS);
|
||||
set_var_to_default(var_names::EXIT_POLICY_URL, EXIT_POLICY_URL);
|
||||
set_var_to_default(var_names::NYM_VPN_API, NYM_VPN_API);
|
||||
set_var_to_default(
|
||||
var_names::UPGRADE_MODE_ATTESTATION_URL,
|
||||
UPGRADE_MODE_ATTESTATION_URL,
|
||||
);
|
||||
set_var_to_default(
|
||||
var_names::UPGRADE_MODE_ATTESTER_ED25519_BS58_PUBKEY,
|
||||
UPGRADE_MODE_ATTESTER_ED25519_BS58_PUBKEY,
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "env", feature = "network"))]
|
||||
@@ -213,12 +199,4 @@ pub fn export_to_env_if_not_set() {
|
||||
set_var_conditionally_to_default(var_names::NYM_API, NYM_API);
|
||||
set_var_conditionally_to_default(var_names::NYXD_WEBSOCKET, NYXD_WS);
|
||||
set_var_conditionally_to_default(var_names::EXIT_POLICY_URL, EXIT_POLICY_URL);
|
||||
set_var_conditionally_to_default(
|
||||
var_names::UPGRADE_MODE_ATTESTATION_URL,
|
||||
UPGRADE_MODE_ATTESTATION_URL,
|
||||
);
|
||||
set_var_conditionally_to_default(
|
||||
var_names::UPGRADE_MODE_ATTESTER_ED25519_BS58_PUBKEY,
|
||||
UPGRADE_MODE_ATTESTER_ED25519_BS58_PUBKEY,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -25,8 +25,6 @@ pub const NYXD_WEBSOCKET: &str = "NYXD_WS";
|
||||
pub const EXIT_POLICY_URL: &str = "EXIT_POLICY";
|
||||
pub const NYM_VPN_API: &str = "NYM_VPN_API";
|
||||
pub const CLIENT_STATS_COLLECTION_PROVIDER: &str = "CLIENT_STATS_COLLECTION_PROVIDER";
|
||||
pub const UPGRADE_MODE_ATTESTATION_URL: &str = "UPGRADE_MODE_ATTESTATION_URL";
|
||||
pub const UPGRADE_MODE_ATTESTER_ED25519_BS58_PUBKEY: &str = "UPGRADE_MODE_ATTESTER_ED25519_PUBKEY";
|
||||
|
||||
pub const DKG_TIME_CONFIGURATION: &str = "DKG_TIME_CONFIGURATION";
|
||||
|
||||
|
||||
@@ -82,17 +82,6 @@ pub struct Protocol {
|
||||
pub service_provider_type: ServiceProviderType,
|
||||
}
|
||||
|
||||
impl Protocol {
|
||||
pub const fn new(version: u8, service_provider_type: ServiceProviderType) -> Self {
|
||||
Self {
|
||||
version,
|
||||
service_provider_type,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// NOTE: this only works under the assumption of using bincode for serialisation
|
||||
// with the current field layout
|
||||
impl TryFrom<&[u8; 2]> for Protocol {
|
||||
type Error = ProtocolError;
|
||||
|
||||
|
||||
@@ -6,12 +6,9 @@ use thiserror::Error;
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum UpgradeModeCheckError {
|
||||
#[error("failed to decode jwt metadata: {source}")]
|
||||
#[error("failed to decode jwt metadata")]
|
||||
TokenMetadataDecodeFailure { source: jwt_simple::Error },
|
||||
|
||||
#[error("the upgrade mode JWT is malformed")]
|
||||
MalformedToken,
|
||||
|
||||
#[error("the jwt metadata didn't contain explicit public key")]
|
||||
MissingTokenPublicKey,
|
||||
|
||||
@@ -24,6 +21,6 @@ pub enum UpgradeModeCheckError {
|
||||
#[error("failed to verify the jwt: {source}")]
|
||||
JwtVerificationFailure { source: jwt_simple::Error },
|
||||
|
||||
#[error("failed to retrieve attestation from {url}: {source}")]
|
||||
#[error("failed to retrieve attestation from {url}:{source}")]
|
||||
AttestationRetrievalFailure { url: String, source: reqwest::Error },
|
||||
}
|
||||
|
||||
@@ -4,17 +4,12 @@
|
||||
use crate::{UpgradeModeAttestation, UpgradeModeCheckError};
|
||||
use jwt_simple::claims::Claims;
|
||||
use jwt_simple::common::{KeyMetadata, VerificationOptions};
|
||||
use jwt_simple::prelude::{
|
||||
Base64UrlSafeNoPadding, EdDSAKeyPairLike, EdDSAPublicKeyLike, JWTClaims,
|
||||
};
|
||||
use jwt_simple::reexports::ct_codecs::Decoder;
|
||||
use jwt_simple::prelude::{EdDSAKeyPairLike, EdDSAPublicKeyLike};
|
||||
use jwt_simple::token::Token;
|
||||
use nym_crypto::asymmetric::ed25519;
|
||||
use std::collections::HashSet;
|
||||
use std::time::Duration;
|
||||
|
||||
pub const CREDENTIAL_PROXY_JWT_ISSUER: &str = "nym-credential-proxy";
|
||||
|
||||
// for now use static issuer such as "nym-credential-proxy"
|
||||
pub fn generate_jwt_for_upgrade_mode_attestation(
|
||||
attestation: UpgradeModeAttestation,
|
||||
@@ -79,26 +74,12 @@ pub fn validate_upgrade_mode_jwt(
|
||||
Ok(attestation)
|
||||
}
|
||||
|
||||
/// Attempt to extract the upgrade mode JWT payload from the provided token
|
||||
pub fn try_decode_upgrade_mode_jwt_claims(
|
||||
token: &str,
|
||||
) -> Result<JWTClaims<UpgradeModeAttestation>, UpgradeModeCheckError> {
|
||||
let mut parts = token.split('.');
|
||||
let _header = parts.next().ok_or(UpgradeModeCheckError::MalformedToken)?;
|
||||
let claims_b64 = parts.next().ok_or(UpgradeModeCheckError::MalformedToken)?;
|
||||
let claims_bytes = Base64UrlSafeNoPadding::decode_to_vec(claims_b64, None)
|
||||
.map_err(|_| UpgradeModeCheckError::MalformedToken)?;
|
||||
|
||||
serde_json::from_slice(&claims_bytes).map_err(|_| UpgradeModeCheckError::MalformedToken)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::generate_new_attestation;
|
||||
use nym_crypto::asymmetric::ed25519;
|
||||
use nym_test_utils::helpers::deterministic_rng;
|
||||
use time::OffsetDateTime;
|
||||
|
||||
#[test]
|
||||
fn generate_and_validate_jwt() {
|
||||
@@ -128,11 +109,11 @@ mod tests {
|
||||
attestation.clone(),
|
||||
Duration::from_secs(60 * 60),
|
||||
&unauthorised_jwt_keys,
|
||||
Some(CREDENTIAL_PROXY_JWT_ISSUER),
|
||||
Some("nym-credential-proxy"),
|
||||
);
|
||||
|
||||
// we expect 'nym-credential-proxy' issuer
|
||||
assert!(validate_upgrade_mode_jwt(&jwt_issuer, Some(CREDENTIAL_PROXY_JWT_ISSUER)).is_ok());
|
||||
assert!(validate_upgrade_mode_jwt(&jwt_issuer, Some("nym-credential-proxy")).is_ok());
|
||||
|
||||
// we don't care about issuer
|
||||
assert!(validate_upgrade_mode_jwt(&jwt_issuer, None).is_ok());
|
||||
@@ -152,65 +133,9 @@ mod tests {
|
||||
None,
|
||||
);
|
||||
// we expect 'nym-credential-proxy' issuer
|
||||
assert!(
|
||||
validate_upgrade_mode_jwt(&jwt_no_issuer, Some(CREDENTIAL_PROXY_JWT_ISSUER)).is_err()
|
||||
);
|
||||
assert!(validate_upgrade_mode_jwt(&jwt_no_issuer, Some("nym-credential-proxy")).is_err());
|
||||
|
||||
// we don't care about issuer
|
||||
assert!(validate_upgrade_mode_jwt(&jwt_no_issuer, None).is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn decode_upgrade_mode_claims() {
|
||||
let invalid_jwts = [
|
||||
"",
|
||||
"invalidSections",
|
||||
"also.invalid.sections",
|
||||
"eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCIsImp3ayI6IkZCdWsxS2lqS3ZwQ3VrU1Zhc0xoN1k1REZTZEdnVzU5WThQOUhWTDh2Mzk5In0.eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCIsImp3ayI6IkZCdWsxS2lqS3ZwQ3VrU1Zhc0xoN1k1REZTZEdnVzU5WThQOUhWTDh2Mzk5In0.eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCIsImp3ayI6IkZCdWsxS2lqS3ZwQ3VrU1Zhc0xoN1k1REZTZEdnVzU5WThQOUhWTDh2Mzk5In0",
|
||||
];
|
||||
|
||||
let attestation_key = ed25519::PrivateKey::from_bytes(&[
|
||||
108, 49, 193, 21, 126, 161, 249, 85, 242, 207, 74, 195, 238, 6, 64, 149, 201, 140, 248,
|
||||
163, 122, 170, 79, 198, 87, 85, 36, 29, 243, 92, 64, 161,
|
||||
])
|
||||
.unwrap();
|
||||
let jwt_key = ed25519::PrivateKey::from_bytes(&[
|
||||
152, 17, 144, 255, 213, 219, 246, 208, 109, 33, 100, 73, 1, 141, 32, 63, 141, 89, 167,
|
||||
2, 52, 215, 241, 219, 200, 18, 159, 241, 76, 111, 42, 32,
|
||||
])
|
||||
.unwrap();
|
||||
let jwt_keys = ed25519::KeyPair::from(jwt_key);
|
||||
|
||||
let validity = Duration::from_secs(60 * 60);
|
||||
let attestation = generate_new_attestation(&attestation_key, vec![*jwt_keys.public_key()]);
|
||||
let valid_jwt = generate_jwt_for_upgrade_mode_attestation(
|
||||
attestation.clone(),
|
||||
validity,
|
||||
&jwt_keys,
|
||||
Some("nym-credential-proxy"),
|
||||
);
|
||||
|
||||
for invalid in invalid_jwts {
|
||||
assert!(try_decode_upgrade_mode_jwt_claims(invalid).is_err())
|
||||
}
|
||||
|
||||
let decoded = try_decode_upgrade_mode_jwt_claims(&valid_jwt).unwrap();
|
||||
assert_eq!(decoded.issuer.unwrap(), "nym-credential-proxy");
|
||||
assert_eq!(decoded.custom, attestation);
|
||||
|
||||
// unfortunately we can't inject current time when constructing the JWT so the best we can do is ensure its within error margin
|
||||
let margin = Duration::from_secs(10);
|
||||
let now = OffsetDateTime::now_utc();
|
||||
let min = now - margin;
|
||||
let max = now + margin;
|
||||
let issued = decoded.issued_at.unwrap();
|
||||
let issued_time = OffsetDateTime::from_unix_timestamp(issued.as_secs() as i64).unwrap();
|
||||
assert!(issued_time >= min && issued_time <= max);
|
||||
|
||||
let min = now - margin + validity;
|
||||
let max = now + margin + validity;
|
||||
let expires = decoded.expires_at.unwrap();
|
||||
let expires_time = OffsetDateTime::from_unix_timestamp(expires.as_secs() as i64).unwrap();
|
||||
assert!(expires_time >= min && expires_time <= max);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,12 +9,7 @@ pub use attestation::{
|
||||
UpgradeModeAttestation, generate_new_attestation, generate_new_attestation_with_starting_time,
|
||||
};
|
||||
pub use error::UpgradeModeCheckError;
|
||||
pub use jwt::{
|
||||
CREDENTIAL_PROXY_JWT_ISSUER, generate_jwt_for_upgrade_mode_attestation,
|
||||
try_decode_upgrade_mode_jwt_claims, validate_upgrade_mode_jwt,
|
||||
};
|
||||
pub use jwt::{generate_jwt_for_upgrade_mode_attestation, validate_upgrade_mode_jwt};
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub use attestation::attempt_retrieve_attestation;
|
||||
|
||||
pub const UPGRADE_MODE_CREDENTIAL_TYPE: &str = "upgrade_mode_jwt";
|
||||
|
||||
@@ -46,23 +46,6 @@ pub trait WireguardMetadataApiClient: ApiClient {
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", skip(self, request_body))]
|
||||
async fn request_upgrade_mode_check(
|
||||
&self,
|
||||
request_body: &Request,
|
||||
) -> Result<Response, HttpClientError> {
|
||||
self.post_json(
|
||||
&[
|
||||
routes::V1_API_VERSION,
|
||||
routes::NETWORK,
|
||||
routes::UPGRADE_MODE_CHECK,
|
||||
],
|
||||
NO_PARAMS,
|
||||
request_body,
|
||||
)
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
|
||||
|
||||
@@ -15,7 +15,7 @@ use utoipa_swagger_ui::SwaggerUi;
|
||||
|
||||
use crate::http::openapi::ApiDoc;
|
||||
use crate::http::state::AppState;
|
||||
use crate::network::{bandwidth_routes, network_routes};
|
||||
use crate::network::bandwidth_routes;
|
||||
|
||||
/// Wrapper around `axum::Router` which ensures correct [order of layers][order].
|
||||
/// Add new routes as if you were working directly with `axum`.
|
||||
@@ -35,12 +35,7 @@ impl RouterBuilder {
|
||||
let default_routes = Router::new()
|
||||
.merge(SwaggerUi::new("/swagger").url("/api-docs/openapi.json", ApiDoc::openapi()))
|
||||
.route("/", get(|| async { Redirect::to("/swagger") }))
|
||||
.nest(
|
||||
"/v1",
|
||||
Router::new()
|
||||
.nest("/bandwidth", bandwidth_routes())
|
||||
.nest("/network", network_routes()),
|
||||
);
|
||||
.nest("/v1", Router::new().nest("/bandwidth", bandwidth_routes()));
|
||||
Self {
|
||||
unfinished_router: default_routes,
|
||||
}
|
||||
|
||||
@@ -1,137 +1,35 @@
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use nym_credential_verification::upgrade_mode::UpgradeModeDetails;
|
||||
use nym_credentials_interface::BandwidthCredential;
|
||||
use std::cmp::max;
|
||||
use std::net::IpAddr;
|
||||
|
||||
use nym_credentials_interface::CredentialSpendingData;
|
||||
|
||||
use crate::transceiver::PeerControllerTransceiver;
|
||||
use nym_wireguard_private_metadata_shared::error::MetadataError;
|
||||
use nym_wireguard_private_metadata_shared::interface::{ResponseData, UpgradeModeCheckRequestType};
|
||||
|
||||
// we need to be above MINIMUM_REMAINING_BANDWIDTH (500MB) plus we also have to trick the client
|
||||
// its depletion is low enough to not require sending new tickets
|
||||
const DEFAULT_WG_CLIENT_BANDWIDTH_THRESHOLD: i64 = 1024 * 1024 * 1024;
|
||||
|
||||
#[derive(Clone, axum::extract::FromRef)]
|
||||
pub struct AppState {
|
||||
transceiver: PeerControllerTransceiver,
|
||||
#[from_ref(skip)]
|
||||
upgrade_mode: UpgradeModeDetails,
|
||||
}
|
||||
|
||||
impl AppState {
|
||||
pub fn new(transceiver: PeerControllerTransceiver, upgrade_mode: UpgradeModeDetails) -> Self {
|
||||
Self {
|
||||
transceiver,
|
||||
upgrade_mode,
|
||||
}
|
||||
pub fn new(transceiver: PeerControllerTransceiver) -> Self {
|
||||
Self { transceiver }
|
||||
}
|
||||
|
||||
fn upgrade_mode_bandwidth(&self, true_bandwidth: i64) -> i64 {
|
||||
// if we're undergoing upgrade mode, we don't meter bandwidth,
|
||||
// we simply return MAX of clients current bandwidth and minimum bandwidth before default
|
||||
// client would have attempted to send new ticket (hopefully)
|
||||
// the latter is to support older clients that will ignore `upgrade_mode` field in the response
|
||||
// as they're not aware of its existence
|
||||
max(DEFAULT_WG_CLIENT_BANDWIDTH_THRESHOLD, true_bandwidth)
|
||||
}
|
||||
|
||||
pub async fn available_bandwidth(&self, ip: IpAddr) -> Result<ResponseData, MetadataError> {
|
||||
let upgrade_mode = self.upgrade_mode.enabled();
|
||||
|
||||
let true_bandwidth = self.transceiver.query_bandwidth(ip).await?;
|
||||
let available_bandwidth = if upgrade_mode {
|
||||
self.upgrade_mode_bandwidth(true_bandwidth)
|
||||
} else {
|
||||
true_bandwidth
|
||||
};
|
||||
|
||||
Ok(ResponseData::AvailableBandwidth {
|
||||
amount: available_bandwidth,
|
||||
upgrade_mode,
|
||||
})
|
||||
pub async fn available_bandwidth(&self, ip: IpAddr) -> Result<i64, MetadataError> {
|
||||
self.transceiver.query_bandwidth(ip).await
|
||||
}
|
||||
|
||||
// Top up with a credential and return the afterwards available bandwidth
|
||||
pub async fn topup_bandwidth(
|
||||
&self,
|
||||
ip: IpAddr,
|
||||
claim: Box<BandwidthCredential>,
|
||||
) -> Result<ResponseData, MetadataError> {
|
||||
match *claim {
|
||||
BandwidthCredential::ZkNym(zk_nym) => {
|
||||
// if we got zk-nym, we just try to verify it
|
||||
let available_bandwidth = self.transceiver.topup_bandwidth(ip, zk_nym).await?;
|
||||
|
||||
// however, we still follow the same upgrade-mode logic,
|
||||
// so that the client would not attempt to needlessly send more credentials
|
||||
let upgrade_mode = self.upgrade_mode.enabled();
|
||||
let available_bandwidth = if upgrade_mode {
|
||||
self.upgrade_mode_bandwidth(available_bandwidth)
|
||||
} else {
|
||||
available_bandwidth
|
||||
};
|
||||
|
||||
Ok(ResponseData::TopUpBandwidth {
|
||||
available_bandwidth,
|
||||
upgrade_mode,
|
||||
})
|
||||
}
|
||||
BandwidthCredential::UpgradeModeJWT { token } => {
|
||||
// if we're already in the upgrade mode, don't bother validating the token
|
||||
if self.upgrade_mode.enabled() {
|
||||
let true_bandwidth = self.transceiver.query_bandwidth(ip).await?;
|
||||
return Ok(ResponseData::TopUpBandwidth {
|
||||
available_bandwidth: self.upgrade_mode_bandwidth(true_bandwidth),
|
||||
upgrade_mode: true,
|
||||
});
|
||||
}
|
||||
|
||||
// if the token is valid, try to check if we're behind
|
||||
// and have to update our internal state
|
||||
self.upgrade_mode
|
||||
.try_enable_via_received_jwt(token)
|
||||
.await
|
||||
.map_err(|err| MetadataError::JWTVerification {
|
||||
message: err.to_string(),
|
||||
})?;
|
||||
|
||||
// if we didn't return an error, it means token got accepted
|
||||
// and we have transitioned into the upgrade mode
|
||||
let true_bandwidth = self.transceiver.query_bandwidth(ip).await?;
|
||||
|
||||
Ok(ResponseData::TopUpBandwidth {
|
||||
available_bandwidth: self.upgrade_mode_bandwidth(true_bandwidth),
|
||||
upgrade_mode: true,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn upgrade_mode_check(
|
||||
&self,
|
||||
request: UpgradeModeCheckRequestType,
|
||||
) -> Result<ResponseData, MetadataError> {
|
||||
// if we're already in the upgrade mode - no need to do anything
|
||||
if self.upgrade_mode.enabled() {
|
||||
return Ok(ResponseData::UpgradeMode { upgrade_mode: true });
|
||||
}
|
||||
|
||||
match request {
|
||||
UpgradeModeCheckRequestType::UpgradeModeJwt { token } => {
|
||||
self.upgrade_mode
|
||||
.try_enable_via_received_jwt(token)
|
||||
.await
|
||||
.map_err(|err| MetadataError::JWTVerification {
|
||||
message: err.to_string(),
|
||||
})?;
|
||||
}
|
||||
}
|
||||
|
||||
// if we didn't return an error, it means token got accepted
|
||||
// and we have transitioned into the upgrade mode
|
||||
Ok(ResponseData::UpgradeMode { upgrade_mode: true })
|
||||
credential: CredentialSpendingData,
|
||||
) -> Result<i64, MetadataError> {
|
||||
self.transceiver
|
||||
.topup_bandwidth(ip, Box::new(credential))
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,8 @@ use axum::{
|
||||
};
|
||||
use nym_http_api_common::{FormattedResponse, OutputParams};
|
||||
use nym_wireguard_private_metadata_shared::{
|
||||
AxumErrorResponse, AxumResult, Construct, Extract, Request, Response, interface::RequestData,
|
||||
AxumErrorResponse, AxumResult, Construct, Extract, Request, Response,
|
||||
interface::{RequestData, ResponseData},
|
||||
latest,
|
||||
};
|
||||
use tower_http::compression::CompressionLayer;
|
||||
@@ -24,15 +25,6 @@ pub(crate) fn bandwidth_routes() -> Router<AppState> {
|
||||
.layer(CompressionLayer::new())
|
||||
}
|
||||
|
||||
pub(crate) fn network_routes() -> Router<AppState> {
|
||||
Router::new()
|
||||
.route(
|
||||
"/upgrade-mode-check",
|
||||
axum::routing::post(upgrade_mode_check),
|
||||
)
|
||||
.layer(CompressionLayer::new())
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
tag = "bandwidth",
|
||||
get,
|
||||
@@ -67,17 +59,20 @@ async fn available_bandwidth(
|
||||
) -> AxumResult<FormattedResponse<Response>> {
|
||||
let output = output.output.unwrap_or_default();
|
||||
|
||||
let (RequestData::AvailableBandwidth, version) =
|
||||
let (RequestData::AvailableBandwidth(_), version) =
|
||||
request.extract().map_err(AxumErrorResponse::bad_request)?
|
||||
else {
|
||||
return Err(AxumErrorResponse::bad_request("incorrect request type"));
|
||||
};
|
||||
let available_bandwidth_response = state
|
||||
let available_bandwidth = state
|
||||
.available_bandwidth(addr.ip())
|
||||
.await
|
||||
.map_err(AxumErrorResponse::bad_request)?;
|
||||
let response = Response::construct(available_bandwidth_response, version)
|
||||
.map_err(AxumErrorResponse::bad_request)?;
|
||||
let response = Response::construct(
|
||||
ResponseData::AvailableBandwidth(available_bandwidth),
|
||||
version,
|
||||
)
|
||||
.map_err(AxumErrorResponse::bad_request)?;
|
||||
|
||||
Ok(output.to_response(response))
|
||||
}
|
||||
@@ -101,49 +96,16 @@ async fn topup_bandwidth(
|
||||
) -> AxumResult<FormattedResponse<Response>> {
|
||||
let output = output.output.unwrap_or_default();
|
||||
|
||||
let (RequestData::TopUpBandwidth { credential }, version) =
|
||||
let (RequestData::TopUpBandwidth(credential), version) =
|
||||
request.extract().map_err(AxumErrorResponse::bad_request)?
|
||||
else {
|
||||
return Err(AxumErrorResponse::bad_request("incorrect request type"));
|
||||
};
|
||||
let top_up_bandwidth_response = state
|
||||
.topup_bandwidth(addr.ip(), credential)
|
||||
let available_bandwidth = state
|
||||
.topup_bandwidth(addr.ip(), *credential)
|
||||
.await
|
||||
.map_err(AxumErrorResponse::bad_request)?;
|
||||
let response = Response::construct(top_up_bandwidth_response, version)
|
||||
.map_err(AxumErrorResponse::bad_request)?;
|
||||
|
||||
Ok(output.to_response(response))
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
tag = "network",
|
||||
post,
|
||||
request_body = Request,
|
||||
path = "/v1/network/upgrade-mode-check",
|
||||
responses(
|
||||
(status = 200, content(
|
||||
(Response = "application/bincode")
|
||||
))
|
||||
),
|
||||
)]
|
||||
async fn upgrade_mode_check(
|
||||
Query(output): Query<OutputParams>,
|
||||
State(state): State<AppState>,
|
||||
Json(request): Json<Request>,
|
||||
) -> AxumResult<FormattedResponse<Response>> {
|
||||
let output = output.output.unwrap_or_default();
|
||||
|
||||
let (RequestData::UpgradeModeCheck { typ }, version) =
|
||||
request.extract().map_err(AxumErrorResponse::bad_request)?
|
||||
else {
|
||||
return Err(AxumErrorResponse::bad_request("incorrect request type"));
|
||||
};
|
||||
let upgrade_mode_check_response = state
|
||||
.upgrade_mode_check(typ)
|
||||
.await
|
||||
.map_err(AxumErrorResponse::bad_request)?;
|
||||
let response = Response::construct(upgrade_mode_check_response, version)
|
||||
let response = Response::construct(ResponseData::TopUpBandwidth(available_bandwidth), version)
|
||||
.map_err(AxumErrorResponse::bad_request)?;
|
||||
|
||||
Ok(output.to_response(response))
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user