reorganized register code to create keyfile on boot route

This commit is contained in:
realisation 2023-10-16 17:44:58 -04:00
parent 165746204a
commit 2784260c53
6 changed files with 203 additions and 227 deletions

1
Cargo.lock generated
View File

@ -4826,6 +4826,7 @@ dependencies = [
"anyhow", "anyhow",
"async-recursion", "async-recursion",
"async-trait", "async-trait",
"base64 0.13.1",
"bincode", "bincode",
"blake3", "blake3",
"bytes", "bytes",

View File

@ -16,6 +16,7 @@ aes-gcm = "0.10.2"
anyhow = "1.0.71" anyhow = "1.0.71"
async-recursion = "1.0.4" async-recursion = "1.0.4"
async-trait = "0.1.71" async-trait = "0.1.71"
base64 = "0.13.0"
bincode = "1.3.3" bincode = "1.3.3"
blake3 = "1.4.1" blake3 = "1.4.1"
bytes = "1.4.0" bytes = "1.4.0"

View File

@ -27,7 +27,6 @@ pub fn encode_keyfile(
jwt: Vec<u8>, jwt: Vec<u8>,
file_key: Vec<u8>, file_key: Vec<u8>,
) -> Vec<u8> { ) -> Vec<u8> {
println!("generating disk encryption keys...");
let mut disk_key: DiskKey = [0u8; CREDENTIAL_LEN]; let mut disk_key: DiskKey = [0u8; CREDENTIAL_LEN];
let rng = SystemRandom::new(); let rng = SystemRandom::new();

View File

@ -169,9 +169,7 @@ async fn main() {
{ {
ip ip
} else { } else {
println!( println!( "\x1b[38;5;196mfailed to find public IPv4 address: booting as a routed node\x1b[0m");
"\x1b[38;5;196mfailed to find public IPv4 address: booting as a routed node\x1b[0m"
);
std::net::Ipv4Addr::LOCALHOST std::net::Ipv4Addr::LOCALHOST
} }
}; };
@ -189,200 +187,35 @@ async fn main() {
// that updates their PKI info on-chain. // that updates their PKI info on-chain.
let http_server_port = http_server::find_open_port(8080).await.unwrap(); let http_server_port = http_server::find_open_port(8080).await.unwrap();
let (kill_tx, kill_rx) = oneshot::channel::<bool>(); let (kill_tx, kill_rx) = oneshot::channel::<bool>();
let keyfile = fs::read(format!("{}/.keys", home_directory_path)).await;
let (our, networking_keypair, jwt_secret_bytes, file_key): ( let disk_keyfile = match fs::read(format!("{}/.keys", home_directory_path)).await {
Identity, Ok(keyfile) => keyfile,
signature::Ed25519KeyPair, Err(_) => Vec::new(),
Vec<u8>,
Vec<u8>,
) = 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<String>,
signature::Ed25519KeyPair,
Vec<u8>,
Vec<u8>,
)>(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, let (tx, mut rx) = mpsc::channel::<(Identity, Keyfile, Vec<u8>)>(1);
// if not, prompt user to reset them. let (mut our, decoded_keyfile, encoded_keyfile) = tokio::select! {
let Ok(Ok(ws_rpc)) = timeout( _ = register::register(tx, kill_rx, our_ip.to_string(), http_server_port, disk_keyfile)
tokio::time::Duration::from_secs(10),
Provider::<Ws>::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/<your-api-key>");
};
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 != <std::net::Ipv4Addr as Into<u32>>::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<u8>)>(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"), => panic!("registration failed"),
(our, password, serialized_networking_keypair, jwt_secret_bytes) = async { (our, decoded_keyfile, encoded_keyfile) = async {
while let Some(fin) = rx.recv().await { while let Some(fin) = rx.recv().await { return fin }
return fin
}
panic!("registration failed") panic!("registration failed")
} => (our, password, serialized_networking_keypair, jwt_secret_bytes), } => (our, decoded_keyfile, encoded_keyfile),
}; };
println!( println!("saving encrypted networking keys to {}/.keys", home_directory_path);
"saving encrypted networking keys to {}/.keys",
home_directory_path
);
let networking_keypair = fs::write(format!("{}/.keys", home_directory_path), encoded_keyfile)
signature::Ed25519KeyPair::from_pkcs8(serialized_networking_keypair.as_ref()).unwrap(); .await.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!"); println!("registration complete!");
(
our,
networking_keypair,
jwt_secret_bytes.to_vec(),
file_key.to_vec(),
)
};
let (kernel_process_map, manifest, vfs_messages) = filesystem::load_fs( let (kernel_process_map, manifest, vfs_messages) = filesystem::load_fs(
our.name.clone(), our.name.clone(),
home_directory_path.clone(), home_directory_path.clone(),
file_key, decoded_keyfile.file_key,
fs_config, fs_config,
) ).await.expect("fs load failed!");
.await
.expect("fs load failed!");
let _ = kill_tx.send(true); let _ = kill_tx.send(true);
let _ = print_sender let _ = print_sender
@ -398,7 +231,7 @@ async fn main() {
* *
* if any of these modules fail, the program exits with an error. * 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::<Result<()>>::new(); let mut tasks = tokio::task::JoinSet::<Result<()>>::new();
tasks.spawn(kernel::kernel( tasks.spawn(kernel::kernel(
@ -443,7 +276,7 @@ async fn main() {
tasks.spawn(http_server::http_server( tasks.spawn(http_server::http_server(
our.name.clone(), our.name.clone(),
http_server_port, http_server_port,
jwt_secret_bytes.clone(), decoded_keyfile.jwt_secret_bytes.clone(),
http_server_receiver, http_server_receiver,
kernel_message_sender.clone(), kernel_message_sender.clone(),
print_sender.clone(), print_sender.clone(),

View File

@ -1,4 +1,5 @@
use aes_gcm::aead::KeyInit; use aes_gcm::aead::KeyInit;
use base64;
use hmac::Hmac; use hmac::Hmac;
use jwt::SignWithKey; use jwt::SignWithKey;
use ring::pkcs8::Document; use ring::pkcs8::Document;
@ -8,7 +9,7 @@ use sha2::Sha256;
use std::sync::{Arc, Mutex}; use std::sync::{Arc, Mutex};
use tokio::sync::{mpsc, oneshot}; use tokio::sync::{mpsc, oneshot};
use warp::{ use warp::{
http::header::{HeaderValue, SET_COOKIE}, http::{ StatusCode, header::{HeaderValue, SET_COOKIE}, },
Filter, Rejection, Reply, Filter, Rejection, Reply,
}; };
@ -16,7 +17,7 @@ use crate::http_server;
use crate::keygen; use crate::keygen;
use crate::types::*; use crate::types::*;
type RegistrationSender = mpsc::Sender<(Identity, String, Document, Vec<u8>)>; type RegistrationSender = mpsc::Sender<(Identity, Keyfile, Vec<u8>)>;
pub fn generate_jwt(jwt_secret_bytes: &[u8], username: String) -> Option<String> { pub fn generate_jwt(jwt_secret_bytes: &[u8], username: String) -> Option<String> {
let jwt_secret: Hmac<Sha256> = match Hmac::new_from_slice(&jwt_secret_bytes) { let jwt_secret: Hmac<Sha256> = match Hmac::new_from_slice(&jwt_secret_bytes) {
@ -41,36 +42,47 @@ pub async fn register(
kill_rx: oneshot::Receiver<bool>, kill_rx: oneshot::Receiver<bool>,
ip: String, ip: String,
port: u16, port: u16,
redir_port: u16, keyfile: Vec<u8>
) { ) {
let our = Arc::new(Mutex::new(None));
let networking_keypair = Arc::new(Mutex::new(None));
let our_get = our.clone(); let our_arc = Arc::new(Mutex::new(None));
let networking_keypair_post = networking_keypair.clone(); 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 static_files = warp::path("static").and(warp::fs::dir("./src/register/build/static/"));
let react_app = warp::path::end() let react_app = warp::path::end()
.and(warp::get()) .and(warp::get())
.and(warp::fs::file("./src/register/build/index.html")); .and(warp::fs::file("./src/register/build/index.html"));
let api = warp::path("get-ws-info").and( let api = warp::path("has-keyfile")
// 1. Get uqname (already on chain) and return networking information .and(warp::get()
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 || ip.clone()))
.and(warp::any().map(move || our_get.clone())) .and(warp::any().map(move || our_ws_info.clone()))
.and(warp::any().map(move || networking_keypair_post.clone())) .and(warp::any().map(move || net_keypair_ws_info.clone()))
.and_then(handle_get) .and_then(handle_info)))
// 2. trigger for finalizing registration once on-chain actions are done .or(warp::path("vet-keyfile")
.or(warp::post() .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::content_length_limit(1024 * 16))
.and(warp::body::json()) .and(warp::body::json())
.and(warp::any().map(move || tx.clone())) .and(warp::any().map(move || tx.clone()))
.and(warp::any().map(move || our.lock().unwrap().take().unwrap())) .and(warp::any().map(move || our_arc.lock().unwrap().take().unwrap()))
.and(warp::any().map(move || networking_keypair.lock().unwrap().take().unwrap())) .and(warp::any().map(move || net_keypair_arc.lock().unwrap().take().unwrap()))
.and(warp::any().map(move || redir_port)) .and(warp::any().map(move || keyfile_arc.lock().unwrap().take().unwrap()))
.and_then(handle_post)), .and_then(handle_boot)));
);
let routes = static_files.or(react_app).or(api); let routes = static_files.or(react_app).or(api);
@ -83,14 +95,130 @@ pub async fn register(
.await; .await;
} }
async fn handle_get( async fn handle_has_keyfile(
keyfile: Arc<Mutex<Option<Vec<u8>>>>,
) -> Result<impl Reply, Rejection> {
Ok(warp::reply::json(&keyfile.lock().unwrap().is_some()))
}
async fn handle_keyfile_check(
payload: KeyfileCheck
) -> Result<impl Reply, Rejection> {
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<Mutex<Option<Identity>>>,
networking_keypair: Arc<Mutex<Option<Document>>>,
jwt_secret: Arc<Mutex<Option<Vec<u8>>>>,
) -> Result<impl Reply, Rejection> {
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<u8>,
) -> Result<impl Reply, Rejection> {
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, ip: String,
our_get: Arc<Mutex<Option<Identity>>>, our_arc: Arc<Mutex<Option<Identity>>>,
networking_keypair_post: Arc<Mutex<Option<Document>>>, networking_keypair_arc: Arc<Mutex<Option<Document>>>,
) -> Result<impl Reply, Rejection> { ) -> Result<impl Reply, Rejection> {
// 1. Generate networking keys // 1. Generate networking keys
let (public_key, serialized_networking_keypair) = keygen::generate_networking_key(); 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... // 2. set our...
// TODO: if IP is localhost, assign a router... // 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)) Ok(warp::reply::json(&our))
} }
async fn handle_post( async fn handle_post(
@ -118,7 +246,7 @@ async fn handle_post(
sender: RegistrationSender, sender: RegistrationSender,
mut our: Identity, mut our: Identity,
networking_keypair: Document, networking_keypair: Document,
_redir_port: u16, keyfile: Vec<u8>,
) -> Result<impl Reply, Rejection> { ) -> Result<impl Reply, Rejection> {
if info.direct { if info.direct {
our.allowed_routers = vec![]; our.allowed_routers = vec![];
@ -139,10 +267,10 @@ async fn handle_post(
let cookie_value = format!("uqbar-auth_{}={};", &our.name, &token); let cookie_value = format!("uqbar-auth_{}={};", &our.name, &token);
let ws_cookie_value = format!("uqbar-ws-auth_{}={};", &our.name, &token); let ws_cookie_value = format!("uqbar-ws-auth_{}={};", &our.name, &token);
sender // sender
.send((our, info.password, networking_keypair, jwt_secret.to_vec())) // .send((our, info.password, networking_keypair, jwt_secret.to_vec(), keyfile))
.await // .await
.unwrap(); // .unwrap();
let mut response = warp::reply::html("Success".to_string()).into_response(); let mut response = warp::reply::html("Success".to_string()).into_response();

View File

@ -53,6 +53,20 @@ pub struct Registration {
pub direct: bool, 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)] #[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Identity { pub struct Identity {
pub name: NodeId, pub name: NodeId,