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",
"async-recursion",
"async-trait",
"base64 0.13.1",
"bincode",
"blake3",
"bytes",

View File

@ -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"

View File

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

View File

@ -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::<bool>();
let keyfile = fs::read(format!("{}/.keys", home_directory_path)).await;
let (our, networking_keypair, jwt_secret_bytes, file_key): (
Identity,
signature::Ed25519KeyPair,
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),
let disk_keyfile = match fs::read(format!("{}/.keys", home_directory_path)).await {
Ok(keyfile) => keyfile,
Err(_) => Vec::new(),
};
// 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::<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)
let (tx, mut rx) = mpsc::channel::<(Identity, Keyfile, Vec<u8>)>(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, password, serialized_networking_keypair, jwt_secret_bytes) = async {
while let Some(fin) = rx.recv().await {
return fin
}
(our, decoded_keyfile, encoded_keyfile) = async {
while let Some(fin) = rx.recv().await { return fin }
panic!("registration failed")
} => (our, password, serialized_networking_keypair, jwt_secret_bytes),
} => (our, decoded_keyfile, encoded_keyfile),
};
println!(
"saving encrypted networking keys to {}/.keys",
home_directory_path
);
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();
fs::write(format!("{}/.keys", home_directory_path), encoded_keyfile)
.await.unwrap();
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(
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::<Result<()>>::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(),

View File

@ -1,4 +1,5 @@
use aes_gcm::aead::KeyInit;
use base64;
use hmac::Hmac;
use jwt::SignWithKey;
use ring::pkcs8::Document;
@ -8,7 +9,7 @@ use sha2::Sha256;
use std::sync::{Arc, Mutex};
use tokio::sync::{mpsc, oneshot};
use warp::{
http::header::{HeaderValue, SET_COOKIE},
http::{ StatusCode, header::{HeaderValue, SET_COOKIE}, },
Filter, Rejection, Reply,
};
@ -16,7 +17,7 @@ use crate::http_server;
use crate::keygen;
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> {
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>,
ip: String,
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 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()
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_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::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<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,
our_get: Arc<Mutex<Option<Identity>>>,
networking_keypair_post: Arc<Mutex<Option<Document>>>,
our_arc: Arc<Mutex<Option<Identity>>>,
networking_keypair_arc: Arc<Mutex<Option<Document>>>,
) -> Result<impl Reply, Rejection> {
// 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<u8>,
) -> Result<impl Reply, Rejection> {
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();

View File

@ -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,