From ed53adccf14c927b779742cc4e7234a58f1ca7da Mon Sep 17 00:00:00 2001 From: Vecna Date: Sun, 19 Mar 2023 14:47:08 -0400 Subject: [PATCH] Client library to work with lox-server --- src/bin/lox_client.rs | 98 ++++++++++++------ src/bin/lox_client_2.rs | 65 ------------ src/client_lib.rs | 219 ++++++++++++++++++++++++++++++++++++---- src/client_net.rs | 69 +++++-------- src/hyper_client_net.rs | 25 ----- 5 files changed, 290 insertions(+), 186 deletions(-) delete mode 100644 src/bin/lox_client_2.rs delete mode 100644 src/hyper_client_net.rs diff --git a/src/bin/lox_client.rs b/src/bin/lox_client.rs index b3a268b..cef200c 100644 --- a/src/bin/lox_client.rs +++ b/src/bin/lox_client.rs @@ -1,42 +1,78 @@ -// This seems like probably not the best way to do this, but it works. -#[path = "../client_net.rs"] -mod client_net; -use crate::client_net::send; +// During a later cleanup, this file will replace lox_client.rs. + +// This seems like probably not the best way to do this, but it works. +#[path = "../client_lib.rs"] +mod client_lib; +use client_lib::*; -use ed25519_dalek::PublicKey; use lox::IssuerPubKey; use std::env::args; use std::fs::File; +use std::io::Write; +use std::path::Path; #[tokio::main] async fn main() { - let bridgedb_pubkey_filename = "bridgedb_pubkey.json"; + // TODO: Do proper argument handling + let server_addr = args().nth(1).unwrap(); // must include http:// + + // Get Lox Authority public keys + // TODO: Make this filename configurable let lox_auth_pubkeys_filename = "lox_auth_pubkeys.json"; - // TODO: make argument passing nicer (maybe flags, config files, etc.) + let lox_auth_pubkeys: Vec = if Path::new(lox_auth_pubkeys_filename).exists() { + // read in file + let lox_auth_pubkeys_infile = File::open(lox_auth_pubkeys_filename).unwrap(); + serde_json::from_reader(lox_auth_pubkeys_infile).unwrap() + } else { + // download from Lox Auth + let pubkeys = get_lox_auth_keys(&server_addr).await; + // save to file for next time + let mut lox_auth_pubkeys_outfile = File::create(lox_auth_pubkeys_filename) + .expect("Failed to create lox_auth pubkeys file"); + write!( + lox_auth_pubkeys_outfile, + "{}", + serde_json::to_string(&pubkeys).unwrap() + ) + .expect("Failed to write to lox_auth pubkeys file"); + pubkeys + }; - // network address to connect to, e.g., localhost:8080 - let addr = args().nth(1).unwrap(); - - // message to send - // TODO: determine commands so the client sends something meaningful - let msg = args().nth(2).unwrap(); - - // import bridgedb pubkey - let bridgedb_pubkey_infile = File::open(bridgedb_pubkey_filename).unwrap(); - let bridgedb_pubkey: PublicKey = serde_json::from_reader(bridgedb_pubkey_infile).unwrap(); - - // import lox_auth pubkeys - let lox_auth_pubkeys_infile = File::open(lox_auth_pubkeys_filename).unwrap(); - let lox_auth_pubkeys: Vec = - serde_json::from_reader(lox_auth_pubkeys_infile).unwrap(); - let lox_pub = &lox_auth_pubkeys[0]; - let migration_pub = &lox_auth_pubkeys[1]; - let migrationkey_pub = &lox_auth_pubkeys[2]; - let reachability_pub = &lox_auth_pubkeys[3]; - let invitation_pub = &lox_auth_pubkeys[4]; - - let s = send(addr, msg.into()).await; - - println!("{}", serde_json::to_string(&s).unwrap()); + // Get Lox Credential and BridgeLine + // TODO: Make these filenames configurable + let lox_cred_filename = "lox_cred.json"; + let bridgeline_filename = "bridgeline.json"; + let (lox_cred, bridgeline) = if Path::new(lox_cred_filename).exists() + && Path::new(bridgeline_filename).exists() + { + let lox_cred_infile = File::open(lox_cred_filename).unwrap(); + let lox_cred = serde_json::from_reader(lox_cred_infile).unwrap(); + let bridgeline_infile = File::open(bridgeline_filename).unwrap(); + let bridgeline = serde_json::from_reader(bridgeline_infile).unwrap(); + (lox_cred, bridgeline) + } else { + // get new credential based on an open invite + let open_invite = get_open_invitation(&server_addr).await; + let (cred, bridgeline) = + get_lox_credential(&server_addr, &open_invite, get_lox_pub(&lox_auth_pubkeys)).await; + // save to files for next time + let mut lox_cred_outfile = + File::create(lox_cred_filename).expect("Failed to create lox credential file"); + write!( + lox_cred_outfile, + "{}", + serde_json::to_string(&cred).unwrap() + ) + .expect("Failed to write to lox credential file"); + let mut bridgeline_outfile = + File::create(bridgeline_filename).expect("Failed to create bridgeline file"); + write!( + bridgeline_outfile, + "{}", + serde_json::to_string(&bridgeline).unwrap() + ) + .expect("Failed to write to bridgeline file"); + (cred, bridgeline) + }; } diff --git a/src/bin/lox_client_2.rs b/src/bin/lox_client_2.rs deleted file mode 100644 index a00a137..0000000 --- a/src/bin/lox_client_2.rs +++ /dev/null @@ -1,65 +0,0 @@ -// During a later cleanup, this file will replace lox_client.rs. - -// This seems like probably not the best way to do this, but it works. -#[path = "../client_lib.rs"] -mod client_lib; -use client_lib::*; - -use lox::IssuerPubKey; -use std::env::args; -use std::fs::File; -use std::io::Write; -use std::path::Path; - -#[tokio::main] -async fn main() { - // TODO: Do proper argument handling - let server_addr = args().nth(1).unwrap(); // must include http:// - - - // Get Lox Authority public keys - // TODO: Make this filename configurable - let lox_auth_pubkeys_filename = "lox_auth_pubkeys.json"; - - let lox_auth_pubkeys: Vec = if Path::new(lox_auth_pubkeys_filename).exists() { - // read in file - let lox_auth_pubkeys_infile = File::open(lox_auth_pubkeys_filename).unwrap(); - serde_json::from_reader(lox_auth_pubkeys_infile).unwrap() - } else { - // download from Lox Auth - let pubkeys = get_lox_auth_keys(&server_addr).await; - // save to file for next time - let mut lox_auth_pubkeys_outfile = File::create(lox_auth_pubkeys_filename).expect("Failed to create lox_auth pubkeys file"); - write!( - lox_auth_pubkeys_outfile, - "{}", - serde_json::to_string(&pubkeys).unwrap() - ).expect("Failed to write to lox_auth pubkeys file"); - pubkeys - }; - let lox_pub = lox_auth_pubkeys[0].clone(); - let migration_pub = lox_auth_pubkeys[1].clone(); - let migrationkey_pub = lox_auth_pubkeys[2].clone(); - let reachability_pub = lox_auth_pubkeys[3].clone(); - let invitation_pub = lox_auth_pubkeys[4].clone(); - - // Get Lox Credential - // TODO: Make this filename configurable - let lox_cred_filename = "lox_cred.json"; - let lox_cred = if Path::new(lox_cred_filename).exists() { - let lox_cred_infile = File::open(lox_cred_filename).unwrap(); - serde_json::from_reader(lox_cred_infile).unwrap() - } else { - // get new credential based on an open invite - let open_invite = get_open_invitation(&server_addr).await; - let cred = get_lox_credential(&server_addr, open_invite, lox_pub).await; - // save to file for next time - let mut lox_cred_outfile = File::create(lox_cred_filename).expect("Failed to create lox credential file"); - write!( - lox_cred_outfile, - "{}", - serde_json::to_string(&cred).unwrap() - ).expect("Failed to write to lox credential file"); - cred - }; -} diff --git a/src/client_lib.rs b/src/client_lib.rs index a83848a..95a06b9 100644 --- a/src/client_lib.rs +++ b/src/client_lib.rs @@ -1,10 +1,12 @@ -mod hyper_client_net; -use hyper_client_net::net_request; +mod client_net; +use client_net::net_request; +use lox::bridge_table::BridgeLine; +use lox::bridge_table::ENC_BUCKET_BYTES; +use lox::proto::*; use lox::IssuerPubKey; use lox::OPENINV_LENGTH; -use lox::proto::*; -use serde::{Serialize, Deserialize}; +use serde::{Deserialize, Serialize}; use serde_with::serde_as; use std::time::Duration; @@ -16,6 +18,12 @@ pub struct Invite { #[serde_as(as = "[_; OPENINV_LENGTH]")] invite: [u8; OPENINV_LENGTH], } +#[serde_as] +#[derive(Serialize, Deserialize)] +pub struct EncBridgeTable { + #[serde_as(as = "Vec<[_; ENC_BUCKET_BYTES]>")] + etable: Vec<[u8; ENC_BUCKET_BYTES]>, +} /// Get today's (real or simulated) date /// @@ -29,37 +37,204 @@ fn today(time_offset: Duration) -> u32 { .unwrap() } +// Helper functions to get public keys from vector +pub fn get_lox_pub(lox_auth_pubkeys: &Vec) -> &IssuerPubKey { + &lox_auth_pubkeys[0] +} + +pub fn get_migration_pub(lox_auth_pubkeys: &Vec) -> &IssuerPubKey { + &lox_auth_pubkeys[1] +} + +pub fn get_migrationkey_pub(lox_auth_pubkeys: &Vec) -> &IssuerPubKey { + &lox_auth_pubkeys[2] +} + +pub fn get_reachability_pub(lox_auth_pubkeys: &Vec) -> &IssuerPubKey { + &lox_auth_pubkeys[3] +} + +pub fn get_invitation_pub(lox_auth_pubkeys: &Vec) -> &IssuerPubKey { + &lox_auth_pubkeys[4] +} + // Download Lox Auth pubkeys pub async fn get_lox_auth_keys(server_addr: &str) -> Vec { - let lox_auth_pubkeys_resp = net_request(server_addr.to_string() + "/pubkeys", [].to_vec()).await; - let lox_auth_pubkeys: Vec = serde_json::from_slice(&lox_auth_pubkeys_resp).unwrap(); + let resp = net_request(server_addr.to_string() + "/pubkeys", [].to_vec()).await; + let lox_auth_pubkeys: Vec = serde_json::from_slice(&resp).unwrap(); lox_auth_pubkeys } +// Get encrypted bridge table +pub async fn get_reachability_credential(server_addr: &str) -> EncBridgeTable { + let resp = net_request(server_addr.to_string() + "/reachability", [].to_vec()).await; + let reachability_cred = serde_json::from_slice(&resp).unwrap(); + reachability_cred +} + // Get an open invitation pub async fn get_open_invitation(server_addr: &str) -> [u8; OPENINV_LENGTH] { - let open_invite_resp = net_request(server_addr.to_string() + "/invite", [].to_vec()).await; - let open_invite: [u8; OPENINV_LENGTH] = serde_json::from_slice::(&open_invite_resp).unwrap().invite; + let resp = net_request(server_addr.to_string() + "/invite", [].to_vec()).await; + let open_invite: [u8; OPENINV_LENGTH] = serde_json::from_slice::(&resp).unwrap().invite; open_invite } // Get a Lox Credential from an open invitation -pub async fn get_lox_credential(server_addr: &str, open_invite: [u8; OPENINV_LENGTH], lox_pub: IssuerPubKey) -> lox::cred::Lox { - let (open_inv_req, state) = open_invite::request(&open_invite); - let encoded_open_inv_req: Vec = serde_json::to_vec(&open_inv_req).unwrap(); - let encoded_open_inv_resp = net_request(server_addr.to_string() + "/openreq", encoded_open_inv_req).await; - let decoded_open_inv_resp: open_invite::Response = serde_json::from_slice(&encoded_open_inv_resp).unwrap(); - let (cred, bridgeline) = open_invite::handle_response(state, decoded_open_inv_resp, &lox_pub).unwrap(); - cred - // TODO: Also return the bridgeline +pub async fn get_lox_credential( + server_addr: &str, + open_invite: &[u8; OPENINV_LENGTH], + lox_pub: &IssuerPubKey, +) -> (lox::cred::Lox, BridgeLine) { + let (req, state) = open_invite::request(&open_invite); + let encoded_req: Vec = serde_json::to_vec(&req).unwrap(); + let encoded_resp = net_request(server_addr.to_string() + "/openreq", encoded_req).await; + let decoded_resp: open_invite::Response = serde_json::from_slice(&encoded_resp).unwrap(); + let (cred, bridgeline) = open_invite::handle_response(state, decoded_resp, &lox_pub).unwrap(); + (cred, bridgeline) } // Get a migration credential to migrate to higher trust -pub async fn trust_promotion(server_addr: &str, lox_cred: lox::cred::Lox, lox_pub: IssuerPubKey) -> lox::cred::Migration { - let (prom_req, state) = trust_promotion::request(&lox_cred, &lox_pub, today(Duration::ZERO)).unwrap(); - let encoded_prom_req: Vec = serde_json::to_vec(&prom_req).unwrap(); - let encoded_prom_resp = net_request(server_addr.to_string() + "/promreq", encoded_prom_req).await; - let decoded_prom_resp: trust_promotion::Response = serde_json::from_slice(&encoded_prom_resp).unwrap(); - let migration_cred = trust_promotion::handle_response(state, decoded_prom_resp).unwrap(); +pub async fn trust_promotion( + server_addr: &str, + lox_cred: &lox::cred::Lox, + lox_pub: &IssuerPubKey, +) -> lox::cred::Migration { + let (req, state) = + trust_promotion::request(&lox_cred, &lox_pub, today(Duration::ZERO)).unwrap(); + let encoded_req: Vec = serde_json::to_vec(&req).unwrap(); + let encoded_resp = net_request(server_addr.to_string() + "/promreq", encoded_req).await; + let decoded_resp: trust_promotion::Response = serde_json::from_slice(&encoded_resp).unwrap(); + let migration_cred = trust_promotion::handle_response(state, decoded_resp).unwrap(); migration_cred } + +// Promote from untrusted (trust level 0) to trusted (trust level 1) +pub async fn trust_migration( + server_addr: &str, + lox_cred: &lox::cred::Lox, + migration_cred: &lox::cred::Migration, + lox_pub: &IssuerPubKey, + migration_pub: &IssuerPubKey, +) -> lox::cred::Lox { + let (req, state) = + migration::request(lox_cred, migration_cred, lox_pub, migration_pub).unwrap(); + let encoded_req: Vec = serde_json::to_vec(&req).unwrap(); + let encoded_resp = net_request(server_addr.to_string() + "/trustmig", encoded_req).await; + let decoded_resp: migration::Response = serde_json::from_slice(&encoded_resp).unwrap(); + let cred = migration::handle_response(state, decoded_resp, lox_pub).unwrap(); + cred +} + +// Increase trust from at least level 1 to higher levels +pub async fn level_up( + server_addr: &str, + lox_cred: &lox::cred::Lox, + encbuckets: &Vec<[u8; ENC_BUCKET_BYTES]>, + lox_pub: &IssuerPubKey, + reachability_pub: &IssuerPubKey, +) -> lox::cred::Lox { + // Read the bucket in the credential to get today's Bucket + // Reachability credential + + let (id, key) = lox::bridge_table::from_scalar(lox_cred.bucket).unwrap(); + let bucket = + lox::bridge_table::BridgeTable::decrypt_bucket(id, &key, &encbuckets[id as usize]).unwrap(); + let reachcred = bucket.1.unwrap(); + + // Use the Bucket Reachability credential to advance to the next + // level + let (req, state) = level_up::request( + lox_cred, + &reachcred, + lox_pub, + reachability_pub, + today(Duration::ZERO), + ) + .unwrap(); + let encoded_req: Vec = serde_json::to_vec(&req).unwrap(); + let encoded_resp = net_request(server_addr.to_string() + "/levelup", encoded_req).await; + let decoded_resp: level_up::Response = serde_json::from_slice(&encoded_resp).unwrap(); + let cred = level_up::handle_response(state, decoded_resp, lox_pub).unwrap(); + cred +} + +// Request an Invitation credential to give to a friend +pub async fn issue_invite( + server_addr: &str, + lox_cred: &lox::cred::Lox, + encbuckets: &Vec<[u8; ENC_BUCKET_BYTES]>, + lox_pub: &IssuerPubKey, + reachability_pub: &IssuerPubKey, + invitation_pub: &IssuerPubKey, +) -> (lox::cred::Lox, lox::cred::Invitation) { + // Read the bucket in the credential to get today's Bucket + // Reachability credential + + let (id, key) = lox::bridge_table::from_scalar(lox_cred.bucket).unwrap(); + let bucket = + lox::bridge_table::BridgeTable::decrypt_bucket(id, &key, &encbuckets[id as usize]).unwrap(); + let reachcred = bucket.1.unwrap(); + + let (req, state) = issue_invite::request( + lox_cred, + &reachcred, + lox_pub, + reachability_pub, + today(Duration::ZERO), + ) + .unwrap(); + let encoded_req: Vec = serde_json::to_vec(&req).unwrap(); + let encoded_resp = net_request(server_addr.to_string() + "/issueinvite", encoded_req).await; + let decoded_resp: issue_invite::Response = serde_json::from_slice(&encoded_resp).unwrap(); + let (cred, invite) = + issue_invite::handle_response(state, decoded_resp, lox_pub, invitation_pub).unwrap(); + (cred, invite) +} + +// Redeem an Invitation credential to start at trust level 1 +pub async fn redeem_invite( + server_addr: &str, + invite: &lox::cred::Invitation, + lox_pub: &IssuerPubKey, + invitation_pub: &IssuerPubKey, +) -> lox::cred::Lox { + let (req, state) = + redeem_invite::request(invite, invitation_pub, today(Duration::ZERO)).unwrap(); + let encoded_req: Vec = serde_json::to_vec(&req).unwrap(); + let encoded_resp = net_request(server_addr.to_string() + "/redeem", encoded_req).await; + let decoded_resp: redeem_invite::Response = serde_json::from_slice(&encoded_resp).unwrap(); + let cred = redeem_invite::handle_response(state, decoded_resp, lox_pub).unwrap(); + cred +} + +// Check for a migration credential to move to a new bucket +pub async fn check_blockage( + server_addr: &str, + lox_cred: &lox::cred::Lox, + lox_pub: &IssuerPubKey, +) -> lox::cred::Migration { + let (req, state) = check_blockage::request(lox_cred, lox_pub).unwrap(); + let encoded_req: Vec = serde_json::to_vec(&req).unwrap(); + let encoded_resp = net_request(server_addr.to_string() + "/checkblockage", encoded_req).await; + let decoded_resp: check_blockage::Response = serde_json::from_slice(&encoded_resp).unwrap(); + let migcred = check_blockage::handle_response(state, decoded_resp).unwrap(); + migcred +} + +// Migrate to a new bucket (must be level >= 3) +pub async fn blockage_migration( + server_addr: &str, + lox_cred: &lox::cred::Lox, + migcred: &lox::cred::Migration, + lox_pub: &IssuerPubKey, + migration_pub: &IssuerPubKey, +) -> lox::cred::Lox { + let (req, state) = + blockage_migration::request(lox_cred, migcred, lox_pub, migration_pub).unwrap(); + let encoded_req: Vec = serde_json::to_vec(&req).unwrap(); + let encoded_resp = + net_request(server_addr.to_string() + "/blockagemigration", encoded_req).await; + let decoded_resp: blockage_migration::Response = serde_json::from_slice(&encoded_resp).unwrap(); + let cred = blockage_migration::handle_response(state, decoded_resp, lox_pub).unwrap(); + cred +} diff --git a/src/client_net.rs b/src/client_net.rs index 2fefad3..a2885a9 100644 --- a/src/client_net.rs +++ b/src/client_net.rs @@ -1,47 +1,30 @@ -/*! The networking methods for our client components to call. In -particular, this file provides a send() method to handle connecting -to a server process and sending it data. */ +// This file provides networking using hyper (which +// https://gitlab.torproject.org/onyinyang/lox-server uses). -use tokio::io::{AsyncReadExt, AsyncWriteExt}; -use tokio::net::TcpStream; +use hyper::{Body, Client, Method, Request}; -// may need to change strings to byte vectors in the future -pub async fn send(addr: String, payload: Vec) -> Vec { - let mut stream = TcpStream::connect(&addr) +pub async fn net_request(url: String, body: Vec) -> Vec { + let client = Client::new(); + + let uri = url.parse().expect("Failed to parse URL"); + + let resp = if body.len() > 0 { + // make a POST with a body + let req = Request::builder() + .method(Method::POST) + .uri(uri) + .body(Body::from(body)) + .expect("Failed to create POST request"); + client.request(req).await.expect("Failed to POST") + } else { + // make a GET request + client.get(uri).await.expect("Failed to GET") + }; + + println!("Response: {}", resp.status()); + + let buf = hyper::body::to_bytes(resp) .await - .expect("Failed to create TcpStream"); - - // send number of bytes in payload - let payload_size = u32::to_be_bytes(payload.len().try_into().unwrap()); - stream - .write_all(&payload_size) - .await - .expect("Failed to write number of bytes to listen for"); - - // send data - stream - .write_all(&payload) - .await - .expect("Failed to write data to stream"); - - // get number of bytes in response - let mut nbuf: [u8; 4] = [0; 4]; - stream - .read(&mut nbuf) - .await - .expect("Failed to get number of bytes to read"); - let n = u32::from_be_bytes(nbuf); - - if n == 0 { - return vec![0; 0]; - } - - // receive response - let mut buf = vec![0; n.try_into().unwrap()]; - stream - .read(&mut buf) - .await - .expect("Failed to read data from socket"); - - buf + .expect("Failed to concat bytes"); + buf.to_vec() } diff --git a/src/hyper_client_net.rs b/src/hyper_client_net.rs deleted file mode 100644 index c4c02f2..0000000 --- a/src/hyper_client_net.rs +++ /dev/null @@ -1,25 +0,0 @@ -// This file provides networking using hyper (which -// https://gitlab.torproject.org/onyinyang/lox-server uses). -// During a later cleanup, this will replace client_net.rs. - -use hyper::{Body, Client, Method, Request}; - -pub async fn net_request(url: String, body: Vec) -> Vec { - let client = Client::new(); - - let uri = url.parse().expect("Failed to parse URL"); - - let resp = if body.len() > 0 { - // make a POST with a body - let req = Request::builder().method(Method::POST).uri(uri).body(Body::from(body)).expect("Failed to create POST request"); - client.request(req).await.expect("Failed to POST") - } else { - // make a GET request - client.get(uri).await.expect("Failed to GET") - }; - - println!("Response: {}", resp.status()); - - let buf = hyper::body::to_bytes(resp).await.expect("Failed to concat bytes"); - buf.to_vec() -}