diff --git a/Cargo.lock b/Cargo.lock index 3e44825198..023def4b50 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6247,6 +6247,7 @@ dependencies = [ "nym-exit-policy", "nym-http-api-client", "nym-wireguard-types", + "rand_chacha 0.2.2", "schemars", "serde", "serde_json", diff --git a/nym-node/nym-node-requests/Cargo.toml b/nym-node/nym-node-requests/Cargo.toml index e8fc8c089b..1708e12293 100644 --- a/nym-node/nym-node-requests/Cargo.toml +++ b/nym-node/nym-node-requests/Cargo.toml @@ -36,6 +36,9 @@ nym-bin-common = { path = "../../common/bin-common", features = ["bin_info_schem [dev-dependencies] tokio = { workspace = true, features = ["full"] } +rand_chacha = "0.2" +nym-crypto = { path = "../../common/crypto", features = ["rand"] } + [features] default = ["client"] diff --git a/nym-node/nym-node-requests/src/api/mod.rs b/nym-node/nym-node-requests/src/api/mod.rs index 180685e284..3c57d9d75a 100644 --- a/nym-node/nym-node-requests/src/api/mod.rs +++ b/nym-node/nym-node-requests/src/api/mod.rs @@ -1,7 +1,7 @@ // Copyright 2023 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use crate::api::v1::node::models::HostInformation; +use crate::api::v1::node::models::{HostInformation, LegacyHostInformation}; use crate::error::Error; use nym_crypto::asymmetric::identity; use schemars::JsonSchema; @@ -61,7 +61,16 @@ impl SignedHostInformation { return false; }; - self.verify(&pub_key) + if self.verify(&pub_key) { + return true; + } + + // attempt to verify legacy signature + SignedData { + data: LegacyHostInformation::from(self.data.clone()), + signature: self.signature.clone(), + } + .verify(&pub_key) } } @@ -84,3 +93,85 @@ impl Display for ErrorResponse { self.message.fmt(f) } } + +#[cfg(test)] +mod tests { + use super::*; + use nym_crypto::asymmetric::{ed25519, x25519}; + use rand_chacha::rand_core::SeedableRng; + use std::net::IpAddr; + + #[derive(Serialize, Deserialize)] + struct LegacyHostInformation { + pub ip_address: Vec, + pub hostname: Option, + pub keys: LegacyKeys, + } + + #[derive(Serialize, Deserialize)] + struct LegacyKeys { + pub ed25519: String, + pub x25519: String, + } + + #[test] + fn dummy_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 host_info = 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().to_base58_string(), + x25519_sphinx: x25519_sphinx.public_key().to_base58_string(), + x25519_noise: "".to_string(), + }, + }; + + let signed_info = SignedHostInformation::new(host_info, ed22519.private_key()).unwrap(); + assert!(signed_info.verify(ed22519.public_key())); + assert!(signed_info.verify_host_information()) + } + + #[test] + fn dummy_legacy_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 legacy_info = crate::api::v1::node::models::LegacyHostInformation { + ip_address: vec!["1.1.1.1".parse().unwrap()], + hostname: Some("foomp.com".to_string()), + keys: crate::api::v1::node::models::LegacyHostKeys { + ed25519: ed22519.public_key().to_base58_string(), + x25519: x25519_sphinx.public_key().to_base58_string(), + }, + }; + + let host_info = crate::api::v1::node::models::HostInformation { + ip_address: legacy_info.ip_address.clone(), + hostname: legacy_info.hostname.clone(), + keys: crate::api::v1::node::models::HostKeys { + ed25519_identity: legacy_info.keys.ed25519.clone(), + x25519_sphinx: legacy_info.keys.x25519.clone(), + x25519_noise: "".to_string(), + }, + }; + + // signature on legacy data + let signature = SignedData::new(legacy_info, ed22519.private_key()) + .unwrap() + .signature; + + // signed blob with the 'current' structure + let current_struct = SignedData { + data: host_info, + signature, + }; + + assert!(!current_struct.verify(ed22519.public_key())); + assert!(current_struct.verify_host_information()) + } +} diff --git a/nym-node/nym-node-requests/src/api/v1/node/models.rs b/nym-node/nym-node-requests/src/api/v1/node/models.rs index 66ca13d99d..1101d1fbb1 100644 --- a/nym-node/nym-node-requests/src/api/v1/node/models.rs +++ b/nym-node/nym-node-requests/src/api/v1/node/models.rs @@ -32,6 +32,23 @@ pub struct HostInformation { pub keys: HostKeys, } +#[derive(Serialize)] +pub struct LegacyHostInformation { + pub ip_address: Vec, + pub hostname: Option, + pub keys: LegacyHostKeys, +} + +impl From for LegacyHostInformation { + fn from(value: HostInformation) -> Self { + LegacyHostInformation { + ip_address: value.ip_address, + hostname: value.hostname, + keys: value.keys.into(), + } + } +} + #[derive(Clone, Default, Debug, Serialize, Deserialize, JsonSchema)] #[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))] pub struct HostKeys { @@ -49,6 +66,21 @@ pub struct HostKeys { pub x25519_noise: String, } +impl From for LegacyHostKeys { + fn from(value: HostKeys) -> Self { + LegacyHostKeys { + ed25519: value.ed25519_identity, + x25519: value.x25519_sphinx, + } + } +} + +#[derive(Serialize)] +pub struct LegacyHostKeys { + pub ed25519: String, + pub x25519: String, +} + #[derive(Clone, Default, Debug, Serialize, Deserialize, JsonSchema)] #[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))] pub struct HostSystem {