diff --git a/Cargo.lock b/Cargo.lock index f077528224..32f4992d06 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6422,16 +6422,15 @@ dependencies = [ "arc-swap", "bytes", "futures", - "log", "nym-crypto", "nym-noise-keys", "pin-project", - "serde", - "sha2 0.10.9", + "sha2 0.10.8", "snow", "thiserror 2.0.12", "tokio", "tokio-util", + "tracing", ] [[package]] diff --git a/common/nymnoise/Cargo.toml b/common/nymnoise/Cargo.toml index d1a3b001f7..07893e59b1 100644 --- a/common/nymnoise/Cargo.toml +++ b/common/nymnoise/Cargo.toml @@ -9,9 +9,8 @@ license.workspace = true arc-swap = { workspace = true } bytes = { workspace = true } futures = { workspace = true } -log = { workspace = true } +tracing = { workspace = true } pin-project = { workspace = true } -serde = { workspace = true, features = ["derive"] } sha2 = { workspace = true } snow = { workspace = true } thiserror = { workspace = true } @@ -21,3 +20,6 @@ tokio-util = { workspace = true, features = ["codec"] } # internal nym-crypto = { path = "../crypto" } nym-noise-keys = { path = "keys" } + +[lints] +workspace = true \ No newline at end of file diff --git a/common/nymnoise/keys/Cargo.toml b/common/nymnoise/keys/Cargo.toml index 67444cd589..94080a004b 100644 --- a/common/nymnoise/keys/Cargo.toml +++ b/common/nymnoise/keys/Cargo.toml @@ -11,4 +11,7 @@ serde = { workspace = true, features = ["derive"] } utoipa = { workspace = true } # internal -nym-crypto = { path = "../../crypto" } +nym-crypto = { path = "../../crypto", features = ["asymmetric", "serde"] } + +[lints] +workspace = true \ No newline at end of file diff --git a/common/nymnoise/src/config.rs b/common/nymnoise/src/config.rs index 3554d25897..fa862a404f 100644 --- a/common/nymnoise/src/config.rs +++ b/common/nymnoise/src/config.rs @@ -1,5 +1,5 @@ // Copyright 2025 - Nym Technologies SA -// SPDX-License-Identifier: GPL-3.0-only +// SPDX-License-Identifier: Apache-2.0 use std::{ collections::HashMap, @@ -26,6 +26,8 @@ impl NoisePattern { } } + // SAFETY: if unwraps failed, it means hardcoded patterns were wrong + #[allow(clippy::unwrap_used)] pub(crate) fn psk_position(&self) -> u8 { //automatic parsing, works for correct pattern, more convenient match self.as_str().find("psk") { @@ -33,7 +35,6 @@ impl NoisePattern { let psk_index = n + 3; let psk_char = self.as_str().chars().nth(psk_index).unwrap(); psk_char.to_string().parse().unwrap() - //if this fails, it means hardcoded pattern are wrong } None => 0, } diff --git a/common/nymnoise/src/connection.rs b/common/nymnoise/src/connection.rs index 1c125df7a0..6f2d64fef4 100644 --- a/common/nymnoise/src/connection.rs +++ b/common/nymnoise/src/connection.rs @@ -1,5 +1,5 @@ -// Copyright 2024 - Nym Technologies SA -// SPDX-License-Identifier: GPL-3.0-only +// Copyright 2025 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 use std::io; diff --git a/common/nymnoise/src/error.rs b/common/nymnoise/src/error.rs index 12d0ce2185..5f29d96687 100644 --- a/common/nymnoise/src/error.rs +++ b/common/nymnoise/src/error.rs @@ -1,5 +1,5 @@ -// Copyright 2023 - Nym Technologies SA -// SPDX-License-Identifier: GPL-3.0-only +// Copyright 2025 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 use snow::Error; use std::io; @@ -10,10 +10,10 @@ pub enum NoiseError { #[error("encountered a Noise decryption error")] DecryptionError, - #[error("encountered a Noise Protocol error - {0}")] + #[error("encountered a Noise Protocol error: {0}")] ProtocolError(Error), - #[error("encountered an IO error - {0}")] + #[error("encountered an IO error: {0}")] IoError(#[from] io::Error), #[error("Incorrect state")] diff --git a/common/nymnoise/src/lib.rs b/common/nymnoise/src/lib.rs index 857cec53fe..a3e6f9ab3b 100644 --- a/common/nymnoise/src/lib.rs +++ b/common/nymnoise/src/lib.rs @@ -1,16 +1,16 @@ -// Copyright 2023 - Nym Technologies SA -// SPDX-License-Identifier: GPL-3.0-only +// Copyright 2025 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 use crate::config::{NoiseConfig, NoisePattern}; use crate::connection::Connection; use crate::error::NoiseError; use crate::stream::NoiseStream; -use log::*; use nym_crypto::asymmetric::x25519; use nym_noise_keys::NoiseVersion; use sha2::{Digest, Sha256}; use snow::{error::Prerequisite, Builder, Error}; use tokio::net::TcpStream; +use tracing::*; pub mod config; pub mod connection; @@ -137,10 +137,7 @@ pub async fn upgrade_noise_responder( // Port is random and we just need the support info match config.get_noise_support(initiator_addr.ip()) { None => { - warn!( - "{:?} can't speak Noise yet, falling back to TCP", - initiator_addr - ); + warn!("{initiator_addr} can't speak Noise yet, falling back to TCP",); Ok(Connection::Tcp(conn)) } //responder's info on version is shaky, so initiator has to adapt. This behavior can change in the future diff --git a/common/nymnoise/src/stream.rs b/common/nymnoise/src/stream.rs index ef9e470cb8..a37c371b1a 100644 --- a/common/nymnoise/src/stream.rs +++ b/common/nymnoise/src/stream.rs @@ -1,5 +1,5 @@ -// Copyright 2023 - Nym Technologies SA -// SPDX-License-Identifier: GPL-3.0-only +// Copyright 2025 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 use crate::error::NoiseError; use bytes::BytesMut; diff --git a/nym-node/nym-node-requests/src/api/mod.rs b/nym-node/nym-node-requests/src/api/mod.rs index 49132ea6e2..5e630feb70 100644 --- a/nym-node/nym-node-requests/src/api/mod.rs +++ b/nym-node/nym-node-requests/src/api/mod.rs @@ -1,4 +1,4 @@ -// Copyright 2023 - Nym Technologies SA +// Copyright 2023-2025 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 use crate::api::v1::node::models::{LegacyHostInformation, LegacyHostInformationV2}; @@ -8,7 +8,6 @@ use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use std::fmt::{Display, Formatter}; use std::ops::Deref; -use utoipa::ToSchema; #[cfg(feature = "client")] pub mod client; @@ -20,7 +19,7 @@ pub use client::Client; // create the type alias manually if openapi is not enabled pub type SignedHostInformation = SignedData; -#[derive(ToSchema)] +#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))] pub struct SignedDataHostInfo { // #[serde(flatten)] pub data: crate::api::v1::node::models::HostInformation, @@ -40,6 +39,7 @@ impl SignedData { T: Serialize, { let plaintext = serde_json::to_string(&data)?; + let signature = key.sign(plaintext).to_base58_string(); Ok(SignedData { data, signature }) } @@ -66,6 +66,8 @@ impl SignedHostInformation { return true; } + // TODO: @JS: to remove downgrade support in future release(s) + // attempt to verify legacy signatures let legacy_v2 = SignedData { data: LegacyHostInformationV2::from(self.data.clone()), @@ -107,6 +109,7 @@ impl Display for ErrorResponse { mod tests { use super::*; + use crate::api::v1::node::models::HostInformation; use nym_crypto::asymmetric::{ed25519, x25519}; use nym_noise_keys::{NoiseVersion, VersionedNoiseKey}; use rand_chacha::rand_core::SeedableRng; @@ -224,6 +227,48 @@ mod tests { assert!(!current_struct_no_noise.verify(ed22519.public_key())); assert!(current_struct_no_noise.verify_host_information()); + assert!(!current_struct_noise.verify(ed22519.public_key())); + assert!(current_struct_noise.verify_host_information()) + } + + #[test] + fn dummy_current_signed_host_verification() { + let mut rng = rand_chacha::ChaCha20Rng::from_seed([0u8; 32]); + let ed22519 = ed25519::KeyPair::new(&mut rng); + let x25519_sphinx = x25519::KeyPair::new(&mut rng); + let x25519_noise = x25519::KeyPair::new(&mut rng); + + let host_info_no_noise = HostInformation { + ip_address: vec!["1.1.1.1".parse().unwrap()], + hostname: Some("foomp.com".to_string()), + keys: crate::api::v1::node::models::HostKeys { + ed25519_identity: *ed22519.public_key(), + x25519_sphinx: *x25519_sphinx.public_key(), + x25519_noise: None, + }, + }; + + let host_info_noise = crate::api::v1::node::models::HostInformation { + ip_address: vec!["1.1.1.1".parse().unwrap()], + hostname: Some("foomp.com".to_string()), + keys: crate::api::v1::node::models::HostKeys { + ed25519_identity: *ed22519.public_key(), + x25519_sphinx: *x25519_sphinx.public_key(), + x25519_noise: Some(VersionedNoiseKey { + version: NoiseVersion::V1, + x25519_pubkey: *x25519_noise.public_key(), + }), + }, + }; + + // signature on legacy data + let current_struct_no_noise = + SignedData::new(host_info_no_noise, ed22519.private_key()).unwrap(); + let current_struct_noise = SignedData::new(host_info_noise, ed22519.private_key()).unwrap(); + + assert!(current_struct_no_noise.verify(ed22519.public_key())); + assert!(current_struct_no_noise.verify_host_information()); + // if noise key is present, the signature is actually valid assert!(current_struct_noise.verify(ed22519.public_key())); assert!(current_struct_noise.verify_host_information()) diff --git a/nym-wallet/Cargo.lock b/nym-wallet/Cargo.lock index 211d475f05..2ebbe10c58 100644 --- a/nym-wallet/Cargo.lock +++ b/nym-wallet/Cargo.lock @@ -4017,6 +4017,7 @@ dependencies = [ "nym-mixnet-contract-common", "nym-network-defaults", "nym-node-requests", + "nym-noise-keys", "nym-serde-helpers", "nym-ticketbooks-merkle", "schemars", @@ -4277,6 +4278,7 @@ dependencies = [ "nym-crypto", "nym-exit-policy", "nym-http-api-client", + "nym-noise-keys", "nym-wireguard-types", "schemars", "serde", @@ -4287,6 +4289,16 @@ dependencies = [ "utoipa", ] +[[package]] +name = "nym-noise-keys" +version = "0.1.0" +dependencies = [ + "nym-crypto", + "schemars", + "serde", + "utoipa", +] + [[package]] name = "nym-pemstore" version = "0.3.0"