From 4f2cec3fa70039d7a1e4397b13960b34c19bbdfb Mon Sep 17 00:00:00 2001 From: ghe0 Date: Sun, 18 Aug 2024 04:49:29 +0300 Subject: [PATCH] succesfully injecting datastore into webserver --- Cargo.lock | 97 +++++++++++++++++++ Cargo.toml | 3 +- proto/challenge.proto | 27 ++---- src/database.rs | 141 --------------------------- src/datastore.rs | 218 ++++++++++++++++++++++++++++++++++++++++++ src/http_server.rs | 34 ++++--- src/main.rs | 16 ++-- 7 files changed, 356 insertions(+), 180 deletions(-) delete mode 100644 src/database.rs create mode 100644 src/datastore.rs diff --git a/Cargo.lock b/Cargo.lock index 957ae1c..a18d3a6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -470,6 +470,16 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "etag" +version = "4.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b3d0661a2ccddc26cba0b834e9b717959ed6fdd76c7129ee159c170a875bf44" +dependencies = [ + "str-buf", + "xxhash-rust", +] + [[package]] name = "fastrand" version = "2.1.0" @@ -669,6 +679,7 @@ dependencies = [ name = "hacker-challenge" version = "0.1.0" dependencies = [ + "anyhow", "ed25519-dalek", "futures", "hex", @@ -1677,6 +1688,7 @@ dependencies = [ "salvo-jwt-auth", "salvo-proxy", "salvo_core", + "salvo_extra", ] [[package]] @@ -1772,6 +1784,26 @@ dependencies = [ "tracing", ] +[[package]] +name = "salvo_extra" +version = "0.70.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11a1d577b798b1fd642bc8d3a80b2bb9299c9236a0a2e072c597099daee28557" +dependencies = [ + "base64 0.22.1", + "etag", + "futures-util", + "hyper", + "pin-project", + "salvo_core", + "serde", + "serde_json", + "tokio", + "tokio-tungstenite", + "tracing", + "ulid", +] + [[package]] name = "salvo_macros" version = "0.70.0" @@ -1976,6 +2008,12 @@ dependencies = [ "der", ] +[[package]] +name = "str-buf" +version = "3.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ceb97b7225c713c2fd4db0153cb6b3cab244eb37900c3f634ed4d43310d8c34" + [[package]] name = "subtle" version = "2.6.1" @@ -2198,6 +2236,18 @@ dependencies = [ "tokio", ] +[[package]] +name = "tokio-tungstenite" +version = "0.23.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6989540ced10490aaf14e6bad2e3d33728a2813310a0c71d1574304c49631cd" +dependencies = [ + "futures-util", + "log", + "tokio", + "tungstenite", +] + [[package]] name = "tokio-util" version = "0.7.11" @@ -2340,12 +2390,37 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" +[[package]] +name = "tungstenite" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e2e2ce1e47ed2994fd43b04c8f618008d4cabdd5ee34027cf14f9d918edd9c8" +dependencies = [ + "byteorder", + "bytes", + "log", + "rand", + "thiserror", + "utf-8", +] + [[package]] name = "typenum" version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" +[[package]] +name = "ulid" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04f903f293d11f31c0c29e4148f6dc0d033a7f80cebc0282bea147611667d289" +dependencies = [ + "getrandom", + "rand", + "web-time", +] + [[package]] name = "unicase" version = "2.7.0" @@ -2409,6 +2484,12 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "utf-8" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" + [[package]] name = "vcpkg" version = "0.2.15" @@ -2526,6 +2607,16 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "web-time" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + [[package]] name = "windows-sys" version = "0.48.0" @@ -2699,6 +2790,12 @@ version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "539a77ee7c0de333dcc6da69b177380a0b81e0dacfa4f7344c465a36871ee601" +[[package]] +name = "xxhash-rust" +version = "0.8.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a5cbf750400958819fb6178eaa83bee5cd9c29a26a40cc241df8c70fdd46984" + [[package]] name = "zerocopy" version = "0.7.35" diff --git a/Cargo.toml b/Cargo.toml index 6b638c5..2d8f468 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,6 +4,7 @@ version = "0.1.0" edition = "2021" [dependencies] +anyhow = "1.0.86" ed25519-dalek = { version = "2.1.1", features = ["rand_core", "serde"] } futures = "0.3.30" hex = "0.4.3" @@ -11,7 +12,7 @@ once_cell = "1.19.0" prost = "0.13.1" prost-types = "0.13.1" rand = "0.8.5" -salvo = "0.70.0" +salvo = { version = "0.70.0", features = ["affix"] } tabled = "0.16.0" tokio = { version = "1.39.2", features = ["macros"] } tonic = "0.12.1" diff --git a/proto/challenge.proto b/proto/challenge.proto index a2838f4..e2dd74f 100644 --- a/proto/challenge.proto +++ b/proto/challenge.proto @@ -2,25 +2,14 @@ syntax = "proto3"; package challenge; import "google/protobuf/timestamp.proto"; -import "google/protobuf/empty.proto"; + +message NodeUpdate { + string ip = 1; + string keypair = 2; + google.protobuf.Timestamp updated_at = 3; + bool online = 4; +} service KeyDistribution { - rpc UpdateKey (UpdateKeyReq) returns (google.protobuf.Empty); - rpc UpdateNode (UpdateNodeReq) returns (google.protobuf.Empty); - rpc RemoveNode (RemoveNodeReq) returns (google.protobuf.Empty); -} - -message UpdateKeyReq { - string keypair = 1; - google.protobuf.Timestamp updated_at = 2; -} - -message UpdateNodeReq { - string keypair = 1; - google.protobuf.Timestamp updated_at = 2; - string ip = 3; -} - -message RemoveNodeReq { - string ip = 1; + rpc UpdateStreaming (stream NodeUpdate) returns (stream NodeUpdate); } diff --git a/src/database.rs b/src/database.rs deleted file mode 100644 index 3610f56..0000000 --- a/src/database.rs +++ /dev/null @@ -1,141 +0,0 @@ -#![allow(dead_code)] -use ed25519_dalek::{Signer, SigningKey, VerifyingKey}; -use once_cell::sync::Lazy; -use rand::rngs::OsRng; -use std::collections::HashMap; -use std::sync::Mutex; -use std::thread; -use std::time::Duration; -use std::time::SystemTime; -use tabled::{Table, Tabled}; - -pub enum SigningError { - CorruptedKey, - KeyNotFound, -} - -impl std::fmt::Display for SigningError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let error_message = match self { - SigningError::CorruptedKey => "The public key is corrupted", - SigningError::KeyNotFound => "Did not find the public key", - }; - write!(f, "{}", error_message) - } -} - -impl From for SigningError { - fn from(_: hex::FromHexError) -> Self { - Self::CorruptedKey - } -} - -impl From for SigningError { - fn from(_: ed25519_dalek::ed25519::Error) -> Self { - Self::CorruptedKey - } -} - -impl From for SigningError { - fn from(_: std::array::TryFromSliceError) -> Self { - Self::CorruptedKey - } -} - -#[derive(Clone)] -pub struct NodeInfo { - pub pubkey: VerifyingKey, - pub updated_at: SystemTime, -} - -static NODES: Lazy>> = Lazy::new(|| Mutex::new(HashMap::new())); - -static KEYS: Lazy>> = - Lazy::new(|| Mutex::new(HashMap::new())); - -pub fn add_key(pubkey: VerifyingKey, privkey: SigningKey) { - let mut keys = KEYS.lock().unwrap(); - keys.insert(pubkey, privkey); -} - -pub fn remove_key(pubkey: &VerifyingKey) { - let mut keys = KEYS.lock().unwrap(); - keys.remove(pubkey); -} - -fn get_privkey(pubkey: &VerifyingKey) -> Option { - let keys = KEYS.lock().unwrap(); - keys.get(pubkey).cloned() -} - -pub fn sign_message_with_key(pubkey: &str, message: &str) -> Result { - // Parse the hex string into a VerifyingKey - let key_bytes = hex::decode(pubkey)?; - let pubkey = VerifyingKey::from_bytes(&key_bytes.as_slice().try_into()?)?; - -// Lock the hashmap and try to get the SigningKey - let key_store = KEYS.lock().unwrap(); - let signing_key = match key_store.get(&pubkey) { - Some(k) => k, - None => return Err(SigningError::KeyNotFound), - }; - - // TODO: check if to_bytes returns the signature in a format that people can verify from bash - let signature = hex::encode(signing_key.sign(message.as_bytes()).to_bytes()); - - Ok(signature) -} - -pub fn add_node(ip: String, info: NodeInfo) { - let mut nodes = NODES.lock().unwrap(); - nodes.insert(ip, info); -} - -pub fn remove_node(ip: &str) { - let mut nodes = NODES.lock().unwrap(); - nodes.remove(ip); -} - -pub fn get_pubkey(ip: &str) -> Option { - let nodes = NODES.lock().unwrap(); - nodes.get(ip).cloned() -} - -pub fn get_nodes_as_html_tabe() -> String { - #[derive(Tabled)] - struct OutputRow { - ip: String, - pubkey: String, - age: u64, - } - let mut output = vec![]; - for (ip, node_info) in NODES.lock().unwrap().iter() { - let ip = ip.clone(); - let pubkey = hex::encode(node_info.pubkey.as_bytes()); - let age = std::time::SystemTime::now() - .duration_since(node_info.updated_at) - .unwrap_or(Duration::ZERO) - .as_secs(); - output.push(OutputRow { ip, pubkey, age }); - } - Table::new(output).to_string() -} - -pub fn cycle_keys() { - thread::spawn(|| { - let mut csprng = OsRng; - loop { - // TODO: save old private key to disk using SGX Sealing - let privkey = ed25519_dalek::SigningKey::generate(&mut csprng); - add_node( - "localhost".to_string(), - NodeInfo { - pubkey: privkey.verifying_key(), - updated_at: std::time::SystemTime::now(), - }, - ); - add_key(privkey.verifying_key(), privkey); - thread::sleep(Duration::from_secs(60)); - } - }); -} diff --git a/src/datastore.rs b/src/datastore.rs new file mode 100644 index 0000000..5889fae --- /dev/null +++ b/src/datastore.rs @@ -0,0 +1,218 @@ +#![allow(dead_code)] +use ed25519_dalek::{Signer, SigningKey, VerifyingKey}; +use once_cell::sync::Lazy; +use rand::rngs::OsRng; +use std::collections::HashMap; +use std::time::Duration; +use std::time::SystemTime; +use tabled::{Table, Tabled}; +use tokio::sync::Mutex; + +type IP = String; + +#[derive(Clone)] +pub struct NodeInfo { + pub pubkey: VerifyingKey, + pub updated_at: SystemTime, + pub online: bool, +} + +/// Needs to be surrounded in an Arc. +pub struct Store { + nodes: Mutex>, + keys: Mutex>, +} + +impl Store { + pub fn init() -> Self { + Self { + nodes: Mutex::new(HashMap::new()), + keys: Mutex::new(HashMap::new()), + } + } + + pub async fn add_mock_node(&self, ip: String) { + let mut csprng = OsRng; + let privkey = ed25519_dalek::SigningKey::generate(&mut csprng); + { + let mut nodes = self.nodes.lock().await; + nodes.insert( + ip, + NodeInfo { + pubkey: privkey.verifying_key(), + updated_at: std::time::SystemTime::now(), + online: true, + }, + ); + } + { + let mut keys = self.keys.lock().await; + keys.insert(privkey.verifying_key(), privkey); + } + } + + pub async fn tabled_node_list(&self) -> String { + #[derive(Tabled)] + struct OutputRow { + ip: String, + pubkey: String, + age: u64, + } + let mut output = vec![]; + for (ip, node_info) in self.nodes.lock().await.iter() { + let ip = ip.clone(); + let pubkey = hex::encode(node_info.pubkey.as_bytes()); + let age = std::time::SystemTime::now() + .duration_since(node_info.updated_at) + .unwrap_or(Duration::ZERO) + .as_secs(); + output.push(OutputRow { ip, pubkey, age }); + } + Table::new(output).to_string() + } + + pub async fn sign_message_with_key( + &self, + message: &str, + pubkey: &str, + ) -> Result { + let key_bytes = hex::decode(pubkey)?; + let pubkey = VerifyingKey::from_bytes(&key_bytes.as_slice().try_into()?)?; + + let key_store = self.keys.lock().await; + let signing_key = match { key_store.get(&pubkey) } { + Some(k) => k, + None => return Err(SigningError::KeyNotFound), + }; + + // TODO: check if to_bytes returns the signature in a format that people can verify from bash + let signature = hex::encode(signing_key.sign(message.as_bytes()).to_bytes()); + + Ok(signature) + } +} + +static NODES: Lazy>> = Lazy::new(|| Mutex::new(HashMap::new())); + +static KEYS: Lazy>> = + Lazy::new(|| Mutex::new(HashMap::new())); + +pub enum SigningError { + CorruptedKey, + KeyNotFound, +} + +impl std::fmt::Display for SigningError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let error_message = match self { + SigningError::CorruptedKey => "The public key is corrupted", + SigningError::KeyNotFound => "Did not find the public key", + }; + write!(f, "{}", error_message) + } +} + +impl From for SigningError { + fn from(_: hex::FromHexError) -> Self { + Self::CorruptedKey + } +} + +impl From for SigningError { + fn from(_: ed25519_dalek::ed25519::Error) -> Self { + Self::CorruptedKey + } +} + +impl From for SigningError { + fn from(_: std::array::TryFromSliceError) -> Self { + Self::CorruptedKey + } +} + +pub async fn add_key(pubkey: VerifyingKey, privkey: SigningKey) { + let mut keys = KEYS.lock().await; + keys.insert(pubkey, privkey); +} + +pub async fn remove_key(pubkey: &VerifyingKey) { + let mut keys = KEYS.lock().await; + keys.remove(pubkey); +} + +async fn get_privkey(pubkey: &VerifyingKey) -> Option { + let keys = KEYS.lock().await; + keys.get(pubkey).cloned() +} + +pub async fn sign_message_with_key(pubkey: &str, message: &str) -> Result { + // Parse the hex string into a VerifyingKey + let key_bytes = hex::decode(pubkey)?; + let pubkey = VerifyingKey::from_bytes(&key_bytes.as_slice().try_into()?)?; + + // Lock the hashmap and try to get the SigningKey + let key_store = KEYS.lock().await; + let signing_key = match key_store.get(&pubkey) { + Some(k) => k, + None => return Err(SigningError::KeyNotFound), + }; + + // TODO: check if to_bytes returns the signature in a format that people can verify from bash + let signature = hex::encode(signing_key.sign(message.as_bytes()).to_bytes()); + + Ok(signature) +} + +pub async fn add_node(ip: String, info: NodeInfo) { + let mut nodes = NODES.lock().await; + nodes.insert(ip, info); +} + +pub async fn remove_node(ip: &str) { + let mut nodes = NODES.lock().await; + nodes.remove(ip); +} + +pub async fn get_pubkey(ip: &str) -> Option { + let nodes = NODES.lock().await; + nodes.get(ip).cloned() +} + +pub async fn get_nodes_as_html_tabe() -> String { + #[derive(Tabled)] + struct OutputRow { + ip: String, + pubkey: String, + age: u64, + } + let mut output = vec![]; + for (ip, node_info) in NODES.lock().await.iter() { + let ip = ip.clone(); + let pubkey = hex::encode(node_info.pubkey.as_bytes()); + let age = std::time::SystemTime::now() + .duration_since(node_info.updated_at) + .unwrap_or(Duration::ZERO) + .as_secs(); + output.push(OutputRow { ip, pubkey, age }); + } + Table::new(output).to_string() +} + +// pub fn cycle_keys() { +// thread::spawn(|| { +// let mut csprng = OsRng; +// loop { +// // TODO: save old private key to disk using SGX Sealing +// let privkey = ed25519_dalek::SigningKey::generate(&mut csprng); +// add_node( +// "localhost".to_string(), +// NodeInfo { +// pubkey: privkey.verifying_key(), +// updated_at: std::time::SystemTime::now(), +// }, +// ); +// add_key(privkey.verifying_key(), privkey); +// thread::sleep(Duration::from_secs(60)); +// } +// }); +// } diff --git a/src/http_server.rs b/src/http_server.rs index 62cafe0..0172683 100644 --- a/src/http_server.rs +++ b/src/http_server.rs @@ -1,13 +1,18 @@ -use crate::database::get_nodes_as_html_tabe; +use crate::datastore::Store; +use std::sync::Arc; + use salvo::prelude::*; +use salvo::affix; #[handler] -async fn homepage() -> String { - get_nodes_as_html_tabe() +async fn homepage(depot: &mut Depot) -> String { + let ds = depot.obtain::>().unwrap(); + ds.tabled_node_list().await } #[handler] -async fn sign(req: &mut Request) -> String { +async fn sign(req: &mut Request, depot: &mut Depot) -> String { + let ds = depot.obtain::>().unwrap(); let pubkey = match req.query::("pubkey") { Some(k) => k, None => return "pubkey must be specified as GET param".to_string(), @@ -18,17 +23,22 @@ async fn sign(req: &mut Request) -> String { None => return "something must be specified as GET param".to_string(), }; - match crate::database::sign_message_with_key(&pubkey, &something) { + match ds.sign_message_with_key(&something, &pubkey).await { Ok(s) => s, Err(e) => e.to_string(), } } -pub async fn start() { - let acceptor = TcpListener::new("0.0.0.0:31372").bind().await; - let router = Router::new() - .get(homepage) - .push(Router::with_path("sign").get(sign)); - println!("{:?}", router); - Server::new(acceptor).serve(router).await; +pub fn init(ds: Arc) -> std::thread::JoinHandle<()> { + std::thread::spawn(|| { + tokio::runtime::Runtime::new().unwrap().block_on(async { + let acceptor = TcpListener::new("0.0.0.0:31372").bind().await; + let router = Router::new() + .hoop(affix::inject(ds)) + .get(homepage) + .push(Router::with_path("sign").get(sign)); + println!("{:?}", router); + Server::new(acceptor).serve(router).await; + }); + }) } diff --git a/src/main.rs b/src/main.rs index fdc7ed6..ace520d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,12 +1,14 @@ -mod grpc; +mod datastore; mod http_server; -mod database; +use crate::datastore::Store; +use std::sync::Arc; #[tokio::main] async fn main() { - crate::database::cycle_keys(); - grpc::add_node("1.1.1.1".to_string()); - grpc::add_node("1.2.3.4".to_string()); - grpc::add_node("2.2.2.2".to_string()); - crate::http_server::start().await; + let ds: Arc = Arc::new(Store::init()); + ds.add_mock_node("1.1.1.1".to_string()).await; + ds.add_mock_node("1.2.3.4".to_string()).await; + ds.add_mock_node("1.2.2.2".to_string()).await; + let thread_result = crate::http_server::init(ds).join(); + println!("{thread_result:?}"); }