diff --git a/Cargo.lock b/Cargo.lock index 950c69b3..5da41dd7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4826,6 +4826,7 @@ dependencies = [ "anyhow", "async-recursion", "async-trait", + "base64 0.13.1", "bincode", "blake3", "bytes", diff --git a/Cargo.toml b/Cargo.toml index 257710e3..d07eb803 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,6 +16,7 @@ aes-gcm = "0.10.2" anyhow = "1.0.71" async-recursion = "1.0.4" async-trait = "0.1.71" +base64 = "0.13.0" bincode = "1.3.3" blake3 = "1.4.1" bytes = "1.4.0" diff --git a/src/keygen.rs b/src/keygen.rs index b4b3d289..76e0c37a 100644 --- a/src/keygen.rs +++ b/src/keygen.rs @@ -27,7 +27,6 @@ pub fn encode_keyfile( jwt: Vec, file_key: Vec, ) -> Vec { - println!("generating disk encryption keys..."); let mut disk_key: DiskKey = [0u8; CREDENTIAL_LEN]; let rng = SystemRandom::new(); diff --git a/src/main.rs b/src/main.rs index 1b383c89..778d4832 100644 --- a/src/main.rs +++ b/src/main.rs @@ -169,9 +169,7 @@ async fn main() { { ip } else { - println!( - "\x1b[38;5;196mfailed to find public IPv4 address: booting as a routed node\x1b[0m" - ); + println!( "\x1b[38;5;196mfailed to find public IPv4 address: booting as a routed node\x1b[0m"); std::net::Ipv4Addr::LOCALHOST } }; @@ -189,200 +187,35 @@ async fn main() { // that updates their PKI info on-chain. let http_server_port = http_server::find_open_port(8080).await.unwrap(); let (kill_tx, kill_rx) = oneshot::channel::(); - let keyfile = fs::read(format!("{}/.keys", home_directory_path)).await; - let (our, networking_keypair, jwt_secret_bytes, file_key): ( - Identity, - signature::Ed25519KeyPair, - Vec, - Vec, - ) = if keyfile.is_ok() { - // LOGIN flow - println!( - "\u{1b}]8;;{}\u{1b}\\{}\u{1b}]8;;\u{1b}\\", - format!("http://localhost:{}/login", http_server_port), - "Click here to log in to your node.", - ); - println!("(http://localhost:{}/login)", http_server_port); - if our_ip != std::net::Ipv4Addr::LOCALHOST { - println!( - "(if on a remote machine: http://{}:{}/login)", - our_ip, http_server_port - ); - } - - let (tx, mut rx) = mpsc::channel::<( - String, - Vec, - signature::Ed25519KeyPair, - Vec, - Vec, - )>(1); - let (username, routers, networking_keypair, jwt_secret_bytes, file_key) = tokio::select! { - _ = register::login( - tx, - kill_rx, - keyfile.unwrap(), - http_server_port, - ) => panic!("login failed"), - (username, routers, networking_keypair, jwt_secret_bytes, file_key) = async { - while let Some(fin) = rx.recv().await { - return fin - } - panic!("login failed") - } => (username, routers, networking_keypair, jwt_secret_bytes, file_key), - }; - - // check if Identity for this username has correct networking keys, - // if not, prompt user to reset them. - let Ok(Ok(ws_rpc)) = timeout( - tokio::time::Duration::from_secs(10), - Provider::::connect(rpc_url.clone()), - ) - .await - else { - panic!("rpc: couldn't connect to blockchain wss endpoint. you MUST set an endpoint with --rpc flag, go to alchemy.com and get a free API key, then use the wss endpoint that looks like this: wss://eth-sepolia.g.alchemy.com/v2/"); - }; - let Ok(Ok(_)) = timeout( - tokio::time::Duration::from_secs(10), - ws_rpc.get_block_number(), - ) - .await - else { - panic!("error: RPC endpoint not responding, try setting one with --rpc flag"); - }; - let qns_address: EthAddress = QNS_SEPOLIA_ADDRESS.parse().unwrap(); - let contract = QNSRegistry::new(qns_address, ws_rpc.into()); - let node_id: U256 = namehash(&username).as_bytes().into(); - let Ok(onchain_id) = contract.ws(node_id).call().await else { - panic!("error: RPC endpoint failed to fetch our node_id"); - }; - print_sender - .send(Printout { - verbosity: 0, - content: "established connection to Sepolia RPC".to_string(), - }) - .await - .unwrap(); - // double check that routers match on-chain information - let namehashed_routers: Vec<[u8; 32]> = routers - .clone() - .into_iter() - .map(|name| { - let hash = namehash(&name); - let mut result = [0u8; 32]; - result.copy_from_slice(hash.as_bytes()); - result - }) - .collect(); - - // double check that keys match on-chain information - if onchain_id.routers != namehashed_routers - || onchain_id.public_key != networking_keypair.public_key().as_ref() - || (onchain_id.ip != 0 - && onchain_id.ip != >::into(our_ip)) - { - panic!("CRITICAL: your routing information does not match on-chain records"); - } - - let our_identity = Identity { - name: username.clone(), - networking_key: format!( - "0x{}", - hex::encode(networking_keypair.public_key().as_ref()) - ), - ws_routing: if onchain_id.ip > 0 && onchain_id.port > 0 { - let ip = format!( - "{}.{}.{}.{}", - (onchain_id.ip >> 24) & 0xFF, - (onchain_id.ip >> 16) & 0xFF, - (onchain_id.ip >> 8) & 0xFF, - onchain_id.ip & 0xFF - ); - Some((ip, onchain_id.port)) - } else { - None - }, - allowed_routers: routers, - }; - - ( - our_identity.clone(), - networking_keypair, - jwt_secret_bytes, - file_key, - ) - } else { - // REGISTER flow - println!( - "\u{1b}]8;;{}\u{1b}\\{}\u{1b}]8;;\u{1b}\\", - format!("http://localhost:{}", http_server_port), - "Click here to register your node.", - ); - println!("(http://localhost:{})", http_server_port); - if our_ip != std::net::Ipv4Addr::LOCALHOST { - println!( - "(if on a remote machine: http://{}:{})", - our_ip, http_server_port - ); - } - - let (tx, mut rx) = mpsc::channel::<(Identity, String, Document, Vec)>(1); - let (mut our, password, serialized_networking_keypair, jwt_secret_bytes) = tokio::select! { - _ = register::register(tx, kill_rx, our_ip.to_string(), http_server_port, http_server_port) - => panic!("registration failed"), - (our, password, serialized_networking_keypair, jwt_secret_bytes) = async { - while let Some(fin) = rx.recv().await { - return fin - } - panic!("registration failed") - } => (our, password, serialized_networking_keypair, jwt_secret_bytes), - }; - - println!( - "saving encrypted networking keys to {}/.keys", - home_directory_path - ); - - let networking_keypair = - signature::Ed25519KeyPair::from_pkcs8(serialized_networking_keypair.as_ref()).unwrap(); - - // TODO fix register frontend so this isn't necessary - our.networking_key = format!("0x{}", our.networking_key); - - let file_key = keygen::generate_file_key(); - - fs::write( - format!("{}/.keys", home_directory_path), - keygen::encode_keyfile( - password, - our.name.clone(), - our.allowed_routers.clone(), - serialized_networking_keypair, - jwt_secret_bytes.clone(), - file_key.clone(), - ), - ) - .await - .unwrap(); - - println!("registration complete!"); - ( - our, - networking_keypair, - jwt_secret_bytes.to_vec(), - file_key.to_vec(), - ) + let disk_keyfile = match fs::read(format!("{}/.keys", home_directory_path)).await { + Ok(keyfile) => keyfile, + Err(_) => Vec::new(), }; + let (tx, mut rx) = mpsc::channel::<(Identity, Keyfile, Vec)>(1); + let (mut our, decoded_keyfile, encoded_keyfile) = tokio::select! { + _ = register::register(tx, kill_rx, our_ip.to_string(), http_server_port, disk_keyfile) + => panic!("registration failed"), + (our, decoded_keyfile, encoded_keyfile) = async { + while let Some(fin) = rx.recv().await { return fin } + panic!("registration failed") + } => (our, decoded_keyfile, encoded_keyfile), + }; + + println!("saving encrypted networking keys to {}/.keys", home_directory_path); + + fs::write(format!("{}/.keys", home_directory_path), encoded_keyfile) + .await.unwrap(); + + println!("registration complete!"); + let (kernel_process_map, manifest, vfs_messages) = filesystem::load_fs( our.name.clone(), home_directory_path.clone(), - file_key, + decoded_keyfile.file_key, fs_config, - ) - .await - .expect("fs load failed!"); + ).await.expect("fs load failed!"); let _ = kill_tx.send(true); let _ = print_sender @@ -398,7 +231,7 @@ async fn main() { * * if any of these modules fail, the program exits with an error. */ - let networking_keypair_arc = Arc::new(networking_keypair); + let networking_keypair_arc = Arc::new(decoded_keyfile.networking_keypair); let mut tasks = tokio::task::JoinSet::>::new(); tasks.spawn(kernel::kernel( @@ -443,7 +276,7 @@ async fn main() { tasks.spawn(http_server::http_server( our.name.clone(), http_server_port, - jwt_secret_bytes.clone(), + decoded_keyfile.jwt_secret_bytes.clone(), http_server_receiver, kernel_message_sender.clone(), print_sender.clone(), diff --git a/src/register.rs b/src/register.rs index 972cce84..9843c7a3 100644 --- a/src/register.rs +++ b/src/register.rs @@ -1,4 +1,5 @@ use aes_gcm::aead::KeyInit; +use base64; use hmac::Hmac; use jwt::SignWithKey; use ring::pkcs8::Document; @@ -8,15 +9,15 @@ use sha2::Sha256; use std::sync::{Arc, Mutex}; use tokio::sync::{mpsc, oneshot}; use warp::{ - http::header::{HeaderValue, SET_COOKIE}, - Filter, Rejection, Reply, + http::{ StatusCode, header::{HeaderValue, SET_COOKIE}, }, + Filter, Rejection, Reply, }; use crate::http_server; use crate::keygen; use crate::types::*; -type RegistrationSender = mpsc::Sender<(Identity, String, Document, Vec)>; +type RegistrationSender = mpsc::Sender<(Identity, Keyfile, Vec)>; pub fn generate_jwt(jwt_secret_bytes: &[u8], username: String) -> Option { let jwt_secret: Hmac = match Hmac::new_from_slice(&jwt_secret_bytes) { @@ -41,36 +42,47 @@ pub async fn register( kill_rx: oneshot::Receiver, ip: String, port: u16, - redir_port: u16, + keyfile: Vec ) { - let our = Arc::new(Mutex::new(None)); - let networking_keypair = Arc::new(Mutex::new(None)); - let our_get = our.clone(); - let networking_keypair_post = networking_keypair.clone(); + let our_arc = Arc::new(Mutex::new(None)); + let our_ws_info = our_arc.clone(); + + let net_keypair_arc = Arc::new(Mutex::new(None)); + let net_keypair_ws_info = net_keypair_arc.clone(); + + let keyfile_arc = Arc::new(Mutex::new(Some(keyfile))); + let keyfile_has = keyfile_arc.clone(); let static_files = warp::path("static").and(warp::fs::dir("./src/register/build/static/")); let react_app = warp::path::end() .and(warp::get()) .and(warp::fs::file("./src/register/build/index.html")); - let api = warp::path("get-ws-info").and( - // 1. Get uqname (already on chain) and return networking information - warp::get() - .and(warp::any().map(move || ip.clone())) - .and(warp::any().map(move || our_get.clone())) - .and(warp::any().map(move || networking_keypair_post.clone())) - .and_then(handle_get) - // 2. trigger for finalizing registration once on-chain actions are done - .or(warp::post() - .and(warp::body::content_length_limit(1024 * 16)) + let api = warp::path("has-keyfile") + .and(warp::get() + .and(warp::any().map(move || keyfile_has.clone())) + .and_then(handle_has_keyfile)) + .or(warp::path("info") + .and(warp::get() + .and(warp::any().map(move || ip.clone())) + .and(warp::any().map(move || our_ws_info.clone())) + .and(warp::any().map(move || net_keypair_ws_info.clone())) + .and_then(handle_info))) + .or(warp::path("vet-keyfile") + .and(warp::post() + .and(warp::body::content_length_limit(1024 * 16)) + .and(warp::body::json()) + .and_then(handle_keyfile_check))) + .or(warp::path("boot") + .and(warp::put() + .and(warp::body::content_length_limit(1024 * 16)) .and(warp::body::json()) .and(warp::any().map(move || tx.clone())) - .and(warp::any().map(move || our.lock().unwrap().take().unwrap())) - .and(warp::any().map(move || networking_keypair.lock().unwrap().take().unwrap())) - .and(warp::any().map(move || redir_port)) - .and_then(handle_post)), - ); + .and(warp::any().map(move || our_arc.lock().unwrap().take().unwrap())) + .and(warp::any().map(move || net_keypair_arc.lock().unwrap().take().unwrap())) + .and(warp::any().map(move || keyfile_arc.lock().unwrap().take().unwrap())) + .and_then(handle_boot))); let routes = static_files.or(react_app).or(api); @@ -83,14 +95,130 @@ pub async fn register( .await; } -async fn handle_get( +async fn handle_has_keyfile( + keyfile: Arc>>>, +) -> Result { + + Ok(warp::reply::json(&keyfile.lock().unwrap().is_some())) + +} + +async fn handle_keyfile_check( + payload: KeyfileCheck +) -> Result { + + let keyfile = base64::decode(payload.keyfile).unwrap(); + + match keygen::decode_keyfile(keyfile, &payload.password) { + Ok(_) => Ok(warp::reply::with_status(warp::reply(), StatusCode::OK)), + Err(_) => Err(warp::reject()), + } + +} + +async fn handle_keyfile_gen( + payload: Registration, + our: Arc>>, + networking_keypair: Arc>>, + jwt_secret: Arc>>>, +) -> Result { + + Ok(warp::reply::with_status(warp::reply(), StatusCode::OK)) + +} + +async fn handle_boot( + info: BootInfo, + sender: RegistrationSender, + mut our: Identity, + networking_keypair: Document, + mut encoded_keyfile: Vec, +) -> Result { + + println!("hello"); + + if info.direct { + our.allowed_routers = vec![]; + } else { + our.ws_routing = None; + } + + println!("~~~~~"); + + if encoded_keyfile.is_empty() && !info.keyfile.is_empty() { + match base64::decode(info.keyfile) { + Ok(k) => encoded_keyfile = k, + Err(_) => return Err(warp::reject()), + } + } + + println!("_____"); + + let decoded_keyfile = if !encoded_keyfile.is_empty() { + match keygen::decode_keyfile(encoded_keyfile.clone(), &info.password) { + Ok(k) => k, + Err(_) => return Err(warp::reject()), + } + } else { + let seed = SystemRandom::new(); + let mut jwt_secret = [0u8, 32]; + ring::rand::SecureRandom::fill(&seed, &mut jwt_secret).unwrap(); + + let networking_pair = signature::Ed25519KeyPair::from_pkcs8(networking_keypair.as_ref()).unwrap(); + + Keyfile { + username: our.name.clone(), + routers: our.allowed_routers.clone(), + networking_keypair: signature::Ed25519KeyPair + ::from_pkcs8(networking_keypair.as_ref()).unwrap(), + jwt_secret_bytes: jwt_secret.to_vec(), + file_key: keygen::generate_file_key(), + } + }; + + println!(">>>>>"); + + if encoded_keyfile.is_empty() { + encoded_keyfile = keygen::encode_keyfile( + info.password, + decoded_keyfile.username.clone(), + decoded_keyfile.routers.clone(), + networking_keypair, + decoded_keyfile.jwt_secret_bytes.clone(), + decoded_keyfile.file_key.clone(), + ); + } + + println!("<<<<<"); + + let token = match generate_jwt(&decoded_keyfile.jwt_secret_bytes, our.name.clone()) { + Some(token) => token, + None => return Err(warp::reject()), + }; + + sender.send((our.clone(), decoded_keyfile, encoded_keyfile.clone())).await.unwrap(); + + let mut response = warp::reply::html("Success".to_string()).into_response(); + + println!("ioioioio"); + + let headers = response.headers_mut(); + headers.append(SET_COOKIE, HeaderValue::from_str( + &format!("uqbar-auth_{}={};", &our.name, &token)).unwrap()); + headers.append(SET_COOKIE, HeaderValue::from_str( + &format!("uqbar-ws-auth_{}={};", &our.name, &token)).unwrap()); + + Ok(warp::reply::with_status(warp::reply(), StatusCode::OK)) +} + +async fn handle_info( ip: String, - our_get: Arc>>, - networking_keypair_post: Arc>>, + our_arc: Arc>>, + networking_keypair_arc: Arc>>, ) -> Result { // 1. Generate networking keys let (public_key, serialized_networking_keypair) = keygen::generate_networking_key(); - *networking_keypair_post.lock().unwrap() = Some(serialized_networking_keypair); + *networking_keypair_arc.lock().unwrap() = Some(serialized_networking_keypair); // 2. set our... // TODO: if IP is localhost, assign a router... @@ -107,10 +235,10 @@ async fn handle_get( ], }; - *our_get.lock().unwrap() = Some(our.clone()); + *our_arc.lock().unwrap() = Some(our.clone()); - // return response containing networking information Ok(warp::reply::json(&our)) + } async fn handle_post( @@ -118,7 +246,7 @@ async fn handle_post( sender: RegistrationSender, mut our: Identity, networking_keypair: Document, - _redir_port: u16, + keyfile: Vec, ) -> Result { if info.direct { our.allowed_routers = vec![]; @@ -139,10 +267,10 @@ async fn handle_post( let cookie_value = format!("uqbar-auth_{}={};", &our.name, &token); let ws_cookie_value = format!("uqbar-ws-auth_{}={};", &our.name, &token); - sender - .send((our, info.password, networking_keypair, jwt_secret.to_vec())) - .await - .unwrap(); + // sender + // .send((our, info.password, networking_keypair, jwt_secret.to_vec(), keyfile)) + // .await + // .unwrap(); let mut response = warp::reply::html("Success".to_string()).into_response(); diff --git a/src/types.rs b/src/types.rs index efabcd02..1abfe5bc 100644 --- a/src/types.rs +++ b/src/types.rs @@ -53,6 +53,20 @@ pub struct Registration { pub direct: bool, } +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct KeyfileCheck { + pub password: String, + pub keyfile: String, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct BootInfo { + pub password: String, + pub keyfile: String, + pub username: String, + pub direct: bool, +} + #[derive(Clone, Debug, Serialize, Deserialize)] pub struct Identity { pub name: NodeId,