diff --git a/common/crypto/src/asymmetric/identity/mod.rs b/common/crypto/src/asymmetric/identity/mod.rs index ae001c2df6..a56abdee32 100644 --- a/common/crypto/src/asymmetric/identity/mod.rs +++ b/common/crypto/src/asymmetric/identity/mod.rs @@ -145,8 +145,12 @@ impl PublicKey { Self::from_bytes(&bytes) } - pub fn verify(&self, message: &[u8], signature: &Signature) -> Result<(), SignatureError> { - self.0.verify(message, &signature.0) + pub fn verify>( + &self, + message: M, + signature: &Signature, + ) -> Result<(), SignatureError> { + self.0.verify(message.as_ref(), &signature.0) } } @@ -239,16 +243,16 @@ impl PrivateKey { Self::from_bytes(&bytes) } - pub fn sign(&self, message: &[u8]) -> Signature { + pub fn sign>(&self, message: M) -> Signature { let expanded_secret_key = ed25519_dalek::ExpandedSecretKey::from(&self.0); let public_key: PublicKey = self.into(); - let sig = expanded_secret_key.sign(message, &public_key.0); + let sig = expanded_secret_key.sign(message.as_ref(), &public_key.0); Signature(sig) } /// Signs text with the provided Ed25519 private key, returning a base58 signature pub fn sign_text(&self, text: &str) -> String { - let signature_bytes = self.sign(text.as_ref()).to_bytes(); + let signature_bytes = self.sign(text).to_bytes(); bs58::encode(signature_bytes).into_string() } } diff --git a/gateway/src/http/mod.rs b/gateway/src/http/mod.rs index 614d649f79..8d2c64d37b 100644 --- a/gateway/src/http/mod.rs +++ b/gateway/src/http/mod.rs @@ -11,8 +11,14 @@ use nym_sphinx::addressing::clients::Recipient; use nym_task::TaskClient; fn load_gateway_details( - config: &Config, + _config: &Config, ) -> Result { + Ok(http::api::v1::gateway::types::Gateway {}) +} + +fn load_host_details( + config: &Config, +) -> Result { let identity_public_key: identity::PublicKey = load_public_key( &config.storage_paths.keys.public_identity_key_file, "gateway identity", @@ -23,9 +29,14 @@ fn load_gateway_details( "gateway sphinx", )?; - Ok(http::api::v1::gateway::types::Gateway { - encoded_identity_key: identity_public_key.to_base58_string(), - encoded_sphinx_key: sphinx_public_key.to_base58_string(), + Ok(http::api::v1::node::types::HostInformation { + // TODO: this should be extracted differently, i.e. it's the issue of the public/private address + ip_address: vec![config.gateway.listening_address], + hostname: None, + keys: http::api::v1::node::types::HostKeys { + ed25519: identity_public_key.to_base58_string(), + x25519: sphinx_public_key.to_base58_string(), + }, }) } @@ -80,8 +91,9 @@ pub(crate) fn start_http_api( // and makes the code a bit nicer to manage. on top of it, all of it will refactored anyway at some point // (famous last words, eh? - 22.09.23) - let mut config = nym_node::http::Config::new(bin_info_owned!()) - .with_gateway(load_gateway_details(gateway_config)?); + let mut config = + nym_node::http::Config::new(bin_info_owned!(), load_host_details(gateway_config)?) + .with_gateway(load_gateway_details(gateway_config)?); if let Some(nr_config) = network_requester_config { config = config diff --git a/nym-node/Cargo.toml b/nym-node/Cargo.toml index 01e6622f39..8b4c87ae1d 100644 --- a/nym-node/Cargo.toml +++ b/nym-node/Cargo.toml @@ -38,4 +38,5 @@ utoipa-swagger-ui = { workspace = true, features = ["axum"] } nym-bin-common = { path = "../common/bin-common", features = ["openapi"] } +nym-crypto = { path = "../common/crypto", features = ["asymmetric"] } nym-task = { path = "../common/task" } diff --git a/nym-node/src/http/router/api/mod.rs b/nym-node/src/http/router/api/mod.rs index 5d626a7844..37b887dd6f 100644 --- a/nym-node/src/http/router/api/mod.rs +++ b/nym-node/src/http/router/api/mod.rs @@ -6,7 +6,9 @@ use axum::http::{header, HeaderValue, StatusCode}; use axum::response::{IntoResponse, Response}; use axum::{Json, Router}; use bytes::{BufMut, BytesMut}; +use nym_crypto::asymmetric::identity; use serde::{Deserialize, Serialize}; +use std::ops::Deref; use utoipa::{IntoParams, ToSchema}; pub mod v1; @@ -32,6 +34,48 @@ pub(super) fn routes(config: Config) -> Router { // data: T, // } +#[derive(Debug, Clone, ToSchema)] +pub struct SignedResponse { + pub response: T, + pub signature: String, +} + +impl SignedResponse { + pub fn new(response: T, key: &identity::PrivateKey) -> serde_json::Result + where + T: Serialize, + { + let plaintext = serde_json::to_string(&response)?; + let signature = key.sign(plaintext).to_base58_string(); + Ok(SignedResponse { + response, + signature, + }) + } + + pub fn verify(&self, key: &identity::PublicKey) -> bool + where + T: Serialize, + { + let Ok(plaintext) = serde_json::to_string(&self.response) else { + return false; + }; + let Ok(signature) = identity::Signature::from_base58_string(&self.signature) else { + return false; + }; + + key.verify(plaintext, &signature).is_ok() + } +} + +impl Deref for SignedResponse { + type Target = T; + + fn deref(&self) -> &Self::Target { + &self.response + } +} + #[derive(Debug, Clone, ToSchema)] pub enum FormattedResponse { Json(Json), diff --git a/nym-node/src/http/router/api/v1/gateway/types.rs b/nym-node/src/http/router/api/v1/gateway/types.rs index df16b609ba..19893fb3f2 100644 --- a/nym-node/src/http/router/api/v1/gateway/types.rs +++ b/nym-node/src/http/router/api/v1/gateway/types.rs @@ -6,10 +6,10 @@ use utoipa::ToSchema; #[derive(Serialize, Debug, Clone, ToSchema)] pub struct Gateway { - /// Base58 encoded ed25519 EdDSA public key of the gateway used for deriving shared keys with clients - /// and for signing any messages - pub encoded_identity_key: String, - - /// Base58-encoded x25519 public key used for sphinx key derivation. - pub encoded_sphinx_key: String, + // /// Base58 encoded ed25519 EdDSA public key of the gateway used for deriving shared keys with clients + // /// and for signing any messages + // pub encoded_identity_key: String, + // + // /// Base58-encoded x25519 public key used for sphinx key derivation. + // pub encoded_sphinx_key: String, } diff --git a/nym-node/src/http/router/api/v1/mixnode/types.rs b/nym-node/src/http/router/api/v1/mixnode/types.rs index 30c03957a4..a4ca77e7fe 100644 --- a/nym-node/src/http/router/api/v1/mixnode/types.rs +++ b/nym-node/src/http/router/api/v1/mixnode/types.rs @@ -6,9 +6,9 @@ use utoipa::ToSchema; #[derive(Serialize, Debug, Clone, ToSchema)] pub struct Mixnode { - /// Base58 encoded ed25519 EdDSA public key of the mixnode. - pub encoded_identity_key: String, - - /// Base58-encoded x25519 public key used for sphinx key derivation. - pub encoded_sphinx_key: String, + // /// Base58 encoded ed25519 EdDSA public key of the mixnode. + // pub encoded_identity_key: String, + // + // /// Base58-encoded x25519 public key used for sphinx key derivation. + // pub encoded_sphinx_key: String, } diff --git a/nym-node/src/http/router/api/v1/node/host_information.rs b/nym-node/src/http/router/api/v1/node/host_information.rs index 38dc36ebc3..07c9590b0b 100644 --- a/nym-node/src/http/router/api/v1/node/host_information.rs +++ b/nym-node/src/http/router/api/v1/node/host_information.rs @@ -1,2 +1,30 @@ // Copyright 2023 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 + +use crate::http::api::v1::node::types::HostInformation; +use crate::http::api::{FormattedResponse, OutputParams}; +use axum::extract::Query; + +/// Returns host information of this node. +#[utoipa::path( + get, + path = "/host-information", + context_path = "/api/v1", + tag = "Node", + responses( + (status = 200, content( + ("application/json" = HostInformation), + ("application/yaml" = HostInformation) + )) + ), + params(OutputParams) +)] +pub(crate) async fn host_information( + host_information: HostInformation, + Query(output): Query, +) -> HostInformationResponse { + let output = output.output.unwrap_or_default(); + output.to_response(host_information) +} + +pub type HostInformationResponse = FormattedResponse; diff --git a/nym-node/src/http/router/api/v1/node/mod.rs b/nym-node/src/http/router/api/v1/node/mod.rs index e5e8692771..220f0e6244 100644 --- a/nym-node/src/http/router/api/v1/node/mod.rs +++ b/nym-node/src/http/router/api/v1/node/mod.rs @@ -2,8 +2,9 @@ // SPDX-License-Identifier: Apache-2.0 use crate::http::api::v1::node::build_information::build_information; +use crate::http::api::v1::node::host_information::host_information; use crate::http::api::v1::node::roles::roles; -use crate::http::api::v1::node::types::NodeRoles; +use crate::http::api::v1::node::types::{HostInformation, NodeRoles}; use crate::http::state::AppState; use axum::routing::get; use axum::Router; @@ -17,12 +18,13 @@ pub mod types; pub(crate) mod routes { pub(crate) const ROLES: &str = "/roles"; pub(crate) const BUILD_INFO: &str = "/build-information"; - // pub(crate) const HOST_INFO: &str = "/host-information"; + pub(crate) const HOST_INFO: &str = "/host-information"; } #[derive(Debug, Clone)] pub struct Config { pub build_information: BinaryBuildInformationOwned, + pub host_information: HostInformation, pub roles: NodeRoles, } @@ -42,4 +44,11 @@ pub(super) fn routes(config: Config) -> Router { move |query| roles(node_roles, query) }), ) + .route( + routes::HOST_INFO, + get({ + let host_info = config.host_information; + move |query| host_information(host_info, query) + }), + ) } diff --git a/nym-node/src/http/router/api/v1/node/types.rs b/nym-node/src/http/router/api/v1/node/types.rs index 45a1510eb3..8d78ffbb9a 100644 --- a/nym-node/src/http/router/api/v1/node/types.rs +++ b/nym-node/src/http/router/api/v1/node/types.rs @@ -3,6 +3,7 @@ pub(crate) use nym_bin_common::build_information::BinaryBuildInformationOwned; use serde::Serialize; +use std::net::IpAddr; use utoipa::ToSchema; #[derive(Clone, Default, Debug, Copy, ToSchema, Serialize)] @@ -11,3 +12,25 @@ pub struct NodeRoles { pub gateway_enabled: bool, pub network_requester_enabled: bool, } + +#[derive(Clone, Default, Debug, ToSchema, Serialize)] +pub struct HostInformation { + /// Ip address(es) of this host, such as `1.1.1.1`. + pub ip_address: Vec, + + /// Optional hostname of this node, for example `nymtech.net`. + pub hostname: Option, + + /// Public keys associated with this node. + pub keys: HostKeys, +} + +#[derive(Clone, Default, Debug, ToSchema, Serialize)] +pub struct HostKeys { + /// Base58-encoded ed25519 public key of this node. Currently it corresponds to either mixnode's or gateway's identity. + pub ed25519: String, + + /// Base58-encoded x25519 public key of this node used for sphinx/outfox packet creation. + /// Currently it corresponds to either mixnode's or gateway's key. + pub x25519: String, +} diff --git a/nym-node/src/http/router/api/v1/openapi.rs b/nym-node/src/http/router/api/v1/openapi.rs index 561244e7b4..bf3d4d13b6 100644 --- a/nym-node/src/http/router/api/v1/openapi.rs +++ b/nym-node/src/http/router/api/v1/openapi.rs @@ -12,6 +12,7 @@ use utoipa_swagger_ui::SwaggerUi; info(title = "NymNode API"), paths( api::v1::node::build_information::build_information, + api::v1::node::host_information::host_information, api::v1::node::roles::roles, api::v1::gateway::root::root_gateway, api::v1::mixnode::root::root_mixnode, @@ -21,13 +22,14 @@ use utoipa_swagger_ui::SwaggerUi; api::Output, api::OutputParams, api::v1::node::types::BinaryBuildInformationOwned, + api::v1::node::types::HostInformation, + api::v1::node::types::HostKeys, api::v1::node::types::NodeRoles, api::v1::gateway::types::Gateway, api::v1::mixnode::types::Mixnode, api::v1::network_requester::types::NetworkRequester, )) )] - pub(crate) struct ApiDoc; pub(crate) fn route() -> Router { diff --git a/nym-node/src/http/router/mod.rs b/nym-node/src/http/router/mod.rs index 1bc9d7b9df..5a2d6e86bc 100644 --- a/nym-node/src/http/router/mod.rs +++ b/nym-node/src/http/router/mod.rs @@ -5,6 +5,7 @@ use crate::error::NymNodeError; use crate::http::api::v1::gateway::types::Gateway; use crate::http::api::v1::mixnode::types::Mixnode; use crate::http::api::v1::network_requester::types::NetworkRequester; +use crate::http::api::v1::node::types::HostInformation; use crate::http::middleware::logging; use crate::http::state::AppState; use crate::http::NymNodeHTTPServer; @@ -31,14 +32,18 @@ pub struct Config { } impl Config { - pub fn new(binary_info: BinaryBuildInformationOwned) -> Self { + pub fn new( + build_information: BinaryBuildInformationOwned, + host_information: HostInformation, + ) -> Self { Config { landing: Default::default(), policy: Default::default(), api: api::Config { v1_config: api::v1::Config { node: api::v1::node::Config { - build_information: binary_info, + build_information, + host_information, roles: Default::default(), }, gateway: Default::default(),