Compare commits

..

No commits in common. "d7beaad5601ad309fd5936c19e60a7ea98a05fde" and "69953bc16c0ef800952a07cf87c21058be512648" have entirely different histories.

8 changed files with 251 additions and 471 deletions

3
.gitignore vendored
View File

@ -1,3 +0,0 @@
Cargo.lock
target
*.json

View File

@ -1,13 +1,14 @@
[package]
name = "lox_cli"
name = "lox_test"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
lox-library = { git = "https://gitlab.torproject.org/vecna/lox.git", version = "0.1.0" }
lox_utils = { git = "https://gitlab.torproject.org/vecna/lox.git", version = "0.1.0" }
#lox = { git = "https://git-crysp.uwaterloo.ca/iang/lox.git", branch = "vvecna/lox_test" }
lox-library = { git = "https://gitlab.torproject.org/tpo/anti-censorship/lox-rs.git", version = "0.1.0" }
lox_utils = { git = "https://gitlab.torproject.org/tpo/anti-censorship/lox-rs.git", version = "0.1.0" }
curve25519-dalek = { package = "curve25519-dalek-ng", version = "3", default-features = false, features = ["serde", "std"] }
ed25519-dalek = { version = "1", features = ["serde"] }
getopts = "0.2"
@ -18,16 +19,11 @@ serde_with = "1.9.1"
time = "0.2"
# TODO: reduce feature set to just the ones needed
tokio = { version = "1", features = ["full"] }
hyper = { version = "0.14.28", features = ["full"] }
hyper = { version = "0.14.27", features = ["full"] }
async-trait = "0.1.68"
anyhow = "1.0"
[features]
default = ["u64_backend"]
u32_backend = ["curve25519-dalek/u32_backend"]
u64_backend = ["curve25519-dalek/u64_backend"]
simd_backend = ["curve25519-dalek/simd_backend"]
[dev-dependencies]
array-bytes = "6.2.0"
sha1 = "0.10"

View File

@ -1,10 +1,10 @@
# lox_cli
# lox_test
This is an in-development client library and CLI for Lox, designed to talk to the Lox Distributor.
## Server
The Lox Distributor code can be found [here](https://gitlab.torproject.org/tpo/anti-censorship/lox/-/tree/main/crates/lox-distributor). I am aiming to keep this project up-to-date to work with that repository. For testing, use [this fork](https://gitlab.torproject.org/vecna/lox/-/tree/main/crates/lox-distributor) which adds an endpoint to the server to allow artificially increasing the number of days that have passed.
The Lox Distributor code can be found [here](https://gitlab.torproject.org/tpo/anti-censorship/lox/-/tree/main/crates/lox-distributor). I am aiming to keep this project up-to-date to work with that repository. For testing, use [this fork](https://gitlab.torproject.org/vecna/lox-rs/-/tree/main/crates/lox-distributor) which adds an endpoint to the server to allow artificially increasing the number of days that have passed.
## Usage

View File

@ -1,18 +1,29 @@
use anyhow::{anyhow, Result};
use lox_library::{
bridge_table::{from_scalar, BridgeLine, BridgeTable, EncryptedBucket, MAX_BRIDGES_PER_BUCKET},
cred,
proto::{
level_up::{LEVEL_INTERVAL, MAX_BLOCKAGES},
*,
},
scalar_u32, IssuerPubKey, OPENINV_LENGTH,
};
use lox_utils::{EncBridgeTable, Invite};
use async_trait::async_trait;
use curve25519_dalek::scalar::Scalar;
use lox_library::bridge_table::from_scalar;
use lox_library::bridge_table::BridgeLine;
use lox_library::bridge_table::BridgeTable;
use lox_library::bridge_table::EncryptedBucket;
use lox_library::bridge_table::MAX_BRIDGES_PER_BUCKET;
use lox_library::proto::*;
use lox_library::scalar_u32;
use lox_library::IssuerPubKey;
use lox_library::OPENINV_LENGTH;
use lox_utils::EncBridgeTable;
use lox_utils::Invite;
use serde::{Deserialize, Serialize};
use serde_with::serde_as;
use std::collections::HashMap;
use std::time::Duration;
pub mod networking;
use crate::networking::Networking;
// used for testing function
use std::io::Write;
// provides a generic way to make network requests
#[async_trait]
pub trait Networking {
async fn request(&self, endpoint: String, body: Vec<u8>) -> Vec<u8>;
}
// Helper functions to get public keys from vector
pub fn get_lox_pub(lox_auth_pubkeys: &Vec<IssuerPubKey>) -> &IssuerPubKey {
@ -45,17 +56,8 @@ pub async fn eligible_for_trust_promotion(
Some(v) => v,
None => return false,
};
let date = match get_today(net).await {
Ok(v) => v,
Err(e) => {
eprintln!("Failed to get date, error: {}", e);
return false;
}
};
match scalar_u32(&cred.trust_level) {
Some(v) => v == 0 && level_since + trust_promotion::UNTRUSTED_INTERVAL <= date,
None => false,
}
scalar_u32(&cred.trust_level).unwrap() == 0
&& level_since + trust_promotion::UNTRUSTED_INTERVAL <= get_today(net).await
}
// Helper function to check if credential is eligible for
@ -65,91 +67,52 @@ pub async fn eligible_for_level_up(net: &dyn Networking, cred: &lox_library::cre
Some(v) => v,
None => return false,
};
let date = match get_today(net).await {
Ok(v) => v,
Err(e) => {
eprintln!("Failed to get date, error: {}", e);
return false;
}
};
let trust_level = match scalar_u32(&cred.trust_level) {
Some(v) => v,
None => return false,
};
let blockages = match scalar_u32(&cred.blockages) {
Some(v) => v,
None => return false,
};
let trust_level: usize = scalar_u32(&cred.trust_level).unwrap().try_into().unwrap();
trust_level > 0
&& blockages <= MAX_BLOCKAGES[trust_level as usize]
&& level_since + LEVEL_INTERVAL[trust_level as usize] <= date
&& level_since + lox_library::proto::level_up::LEVEL_INTERVAL[trust_level]
<= get_today(net).await
}
// Get current date from Lox Auth
pub async fn get_today(net: &dyn Networking) -> Result<u32> {
let resp = net.request("/today".to_string(), [].to_vec()).await?;
let today: u32 = serde_json::from_slice(&resp)?;
Ok(today)
pub async fn get_today(net: &dyn Networking) -> u32 {
let resp = net.request("/today".to_string(), [].to_vec()).await;
let today: u32 = serde_json::from_slice(&resp).unwrap();
today
}
// Download Lox Auth pubkeys
pub async fn get_lox_auth_keys(net: &dyn Networking) -> Result<Vec<IssuerPubKey>> {
let resp = net.request("/pubkeys".to_string(), [].to_vec()).await?;
let lox_auth_pubkeys: Vec<IssuerPubKey> = serde_json::from_slice(&resp)?;
Ok(lox_auth_pubkeys)
pub async fn get_lox_auth_keys(net: &dyn Networking) -> Vec<IssuerPubKey> {
let resp = net.request("/pubkeys".to_string(), [].to_vec()).await;
let lox_auth_pubkeys: Vec<IssuerPubKey> = serde_json::from_slice(&resp).unwrap();
lox_auth_pubkeys
}
// Get encrypted bridge table
pub async fn get_reachability_credential(
net: &dyn Networking,
) -> Result<HashMap<u32, EncryptedBucket>> {
let resp = net
.request("/reachability".to_string(), [].to_vec())
.await?;
let reachability_cred: EncBridgeTable = serde_json::from_slice(&resp)?;
Ok(reachability_cred.etable)
pub async fn get_reachability_credential(net: &dyn Networking) -> HashMap<u32, EncryptedBucket> {
let resp = net.request("/reachability".to_string(), [].to_vec()).await;
let reachability_cred: EncBridgeTable = serde_json::from_slice(&resp).unwrap();
reachability_cred.etable
}
// Get encrypted bridge table from BridgeDB and decrypt our entry
pub async fn get_bucket(
net: &dyn Networking,
lox_cred: &lox_library::cred::Lox,
) -> Result<(
[BridgeLine; MAX_BRIDGES_PER_BUCKET],
Option<cred::BucketReachability>,
)> {
let encbuckets = get_reachability_credential(net).await?;
let (id, key) = match from_scalar(lox_cred.bucket) {
Ok((id, key)) => (id, key),
Err(e) => {
return Err(anyhow!(
"aead error returned when trying to get id and key from bucket: {}",
e
))
}
};
) -> [BridgeLine; MAX_BRIDGES_PER_BUCKET] {
let encbuckets = get_reachability_credential(net).await;
let (id, key) = from_scalar(lox_cred.bucket).unwrap();
let encbucket = match encbuckets.get(&id) {
Some(v) => v,
None => {
return Err(anyhow!(
"Unable to get encrypted bucket from encrypted bridge table"
))
}
Some(encbucket) => encbucket,
None => panic!("Provided ID not found"),
};
match BridgeTable::decrypt_bucket(id, &key, &encbucket) {
Ok(v) => Ok(v),
Err(e) => Err(anyhow!(
"aead error returned when trying to decrypt bucket: {}",
e
)),
}
BridgeTable::decrypt_bucket(id, &key, &encbucket).unwrap().0
}
// Get an open invitation
pub async fn get_open_invitation(net: &dyn Networking) -> Result<[u8; OPENINV_LENGTH]> {
let resp = net.request("/invite".to_string(), [].to_vec()).await?;
let open_invite: [u8; OPENINV_LENGTH] = serde_json::from_slice::<Invite>(&resp)?.invite;
Ok(open_invite)
pub async fn get_open_invitation(net: &dyn Networking) -> [u8; OPENINV_LENGTH] {
let resp = net.request("/invite".to_string(), [].to_vec()).await;
let open_invite: [u8; OPENINV_LENGTH] = serde_json::from_slice::<Invite>(&resp).unwrap().invite;
open_invite
}
// Get a Lox Credential from an open invitation
@ -157,13 +120,13 @@ pub async fn get_lox_credential(
net: &dyn Networking,
open_invite: &[u8; OPENINV_LENGTH],
lox_pub: &IssuerPubKey,
) -> Result<(lox_library::cred::Lox, BridgeLine)> {
) -> (lox_library::cred::Lox, BridgeLine) {
let (req, state) = open_invite::request(&open_invite);
let encoded_req: Vec<u8> = serde_json::to_vec(&req)?;
let encoded_resp = net.request("/openreq".to_string(), encoded_req).await?;
let decoded_resp: open_invite::Response = serde_json::from_slice(&encoded_resp)?;
let (cred, bridgeline) = open_invite::handle_response(state, decoded_resp, &lox_pub)?;
Ok((cred, bridgeline))
let encoded_req: Vec<u8> = serde_json::to_vec(&req).unwrap();
let encoded_resp = net.request("/openreq".to_string(), 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
@ -171,13 +134,13 @@ pub async fn trust_promotion(
net: &dyn Networking,
lox_cred: &lox_library::cred::Lox,
lox_pub: &IssuerPubKey,
) -> Result<lox_library::cred::Migration> {
let (req, state) = trust_promotion::request(&lox_cred, &lox_pub, get_today(net).await?)?;
let encoded_req: Vec<u8> = serde_json::to_vec(&req)?;
let encoded_resp = net.request("/trustpromo".to_string(), encoded_req).await?;
let decoded_resp: trust_promotion::Response = serde_json::from_slice(&encoded_resp)?;
let migration_cred = trust_promotion::handle_response(state, decoded_resp)?;
Ok(migration_cred)
) -> lox_library::cred::Migration {
let (req, state) = trust_promotion::request(&lox_cred, &lox_pub, get_today(net).await).unwrap();
let encoded_req: Vec<u8> = serde_json::to_vec(&req).unwrap();
let encoded_resp = net.request("/trustpromo".to_string(), 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)
@ -187,35 +150,50 @@ pub async fn trust_migration(
migration_cred: &lox_library::cred::Migration,
lox_pub: &IssuerPubKey,
migration_pub: &IssuerPubKey,
) -> Result<lox_library::cred::Lox> {
let (req, state) = migration::request(lox_cred, migration_cred, lox_pub, migration_pub)?;
let encoded_req: Vec<u8> = serde_json::to_vec(&req)?;
let encoded_resp = net.request("/trustmig".to_string(), encoded_req).await?;
let decoded_resp: migration::Response = serde_json::from_slice(&encoded_resp)?;
let cred = migration::handle_response(state, decoded_resp, lox_pub)?;
Ok(cred)
) -> lox_library::cred::Lox {
let (req, state) =
migration::request(lox_cred, migration_cred, lox_pub, migration_pub).unwrap();
let encoded_req: Vec<u8> = serde_json::to_vec(&req).unwrap();
let encoded_resp = net.request("/trustmig".to_string(), 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(
net: &dyn Networking,
lox_cred: &lox_library::cred::Lox,
reachcred: &cred::BucketReachability,
encbuckets: &HashMap<u32, EncryptedBucket>,
lox_pub: &IssuerPubKey,
reachability_pub: &IssuerPubKey,
) -> Result<lox_library::cred::Lox> {
) -> (lox_library::cred::Lox, [BridgeLine; MAX_BRIDGES_PER_BUCKET]) {
// Read the bucket in the credential to get today's Bucket
// Reachability credential
let (id, key) = from_scalar(lox_cred.bucket).unwrap();
let bucket = BridgeTable::decrypt_bucket(id, &key, &encbuckets.get(&id).unwrap()).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,
get_today(net).await?,
)?;
let encoded_req: Vec<u8> = serde_json::to_vec(&req)?;
let encoded_resp = net.request("/levelup".to_string(), encoded_req).await?;
let decoded_resp: level_up::Response = serde_json::from_slice(&encoded_resp)?;
let cred = level_up::handle_response(state, decoded_resp, lox_pub)?;
Ok(cred)
get_today(net).await,
)
.unwrap();
let encoded_req: Vec<u8> = serde_json::to_vec(&req).unwrap();
let encoded_resp = net.request("/levelup".to_string(), 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();
// Get bucket
let (id, key) = from_scalar(lox_cred.bucket).unwrap();
let bucket = BridgeTable::decrypt_bucket(id, &key, &encbuckets.get(&id).unwrap()).unwrap();
(cred, bucket.0)
}
// Request an Invitation credential to give to a friend
@ -226,58 +204,28 @@ pub async fn issue_invite(
lox_pub: &IssuerPubKey,
reachability_pub: &IssuerPubKey,
invitation_pub: &IssuerPubKey,
) -> Result<(lox_library::cred::Lox, lox_library::cred::Invitation)> {
) -> (lox_library::cred::Lox, lox_library::cred::Invitation) {
// Read the bucket in the credential to get today's Bucket
// Reachability credential
let (id, key) = match from_scalar(lox_cred.bucket) {
Ok((id, key)) => (id, key),
Err(e) => {
return Err(anyhow!(
"aead error returned when trying to get id and key from bucket: {}",
e
))
}
};
let encbucket = match encbuckets.get(&id) {
Some(v) => v,
None => {
return Err(anyhow!(
"Unable to get encrypted bucket from encrypted bridge table"
))
}
};
let bucket = match BridgeTable::decrypt_bucket(id, &key, encbucket) {
Ok(v) => v,
Err(e) => {
return Err(anyhow!(
"aead error returned when trying to decrypt bucket: {}",
e
))
}
};
let reachcred = match bucket.1 {
Some(v) => v,
None => {
return Err(anyhow!(
"Expected reachability credential but none was found"
))
}
};
let (id, key) = from_scalar(lox_cred.bucket).unwrap();
let bucket = BridgeTable::decrypt_bucket(id, &key, &encbuckets.get(&id).unwrap()).unwrap();
let reachcred = bucket.1.unwrap();
let (req, state) = issue_invite::request(
lox_cred,
&reachcred,
lox_pub,
reachability_pub,
get_today(net).await?,
)?;
let encoded_req: Vec<u8> = serde_json::to_vec(&req)?;
let encoded_resp = net.request("/issueinvite".to_string(), encoded_req).await?;
let decoded_resp: issue_invite::Response = serde_json::from_slice(&encoded_resp)?;
get_today(net).await,
)
.unwrap();
let encoded_req: Vec<u8> = serde_json::to_vec(&req).unwrap();
let encoded_resp = net.request("/issueinvite".to_string(), 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)?;
Ok((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
@ -286,15 +234,16 @@ pub async fn redeem_invite(
invite: &lox_library::cred::Invitation,
lox_pub: &IssuerPubKey,
invitation_pub: &IssuerPubKey,
) -> Result<(lox_library::cred::Lox, [BridgeLine; MAX_BRIDGES_PER_BUCKET])> {
let (req, state) = redeem_invite::request(invite, invitation_pub, get_today(net).await?)?;
let encoded_req: Vec<u8> = serde_json::to_vec(&req)?;
let encoded_resp = net.request("/redeem".to_string(), encoded_req).await?;
let decoded_resp: redeem_invite::Response = serde_json::from_slice(&encoded_resp)?;
let cred = redeem_invite::handle_response(state, decoded_resp, lox_pub)?;
) -> (lox_library::cred::Lox, [BridgeLine; MAX_BRIDGES_PER_BUCKET]) {
let (req, state) =
redeem_invite::request(invite, invitation_pub, get_today(net).await).unwrap();
let encoded_req: Vec<u8> = serde_json::to_vec(&req).unwrap();
let encoded_resp = net.request("/redeem".to_string(), 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();
let bucket = get_bucket(net, &cred).await?.0;
Ok((cred, bucket))
let bucket = get_bucket(net, &cred).await;
(cred, bucket)
}
// Check for a migration credential to move to a new bucket
@ -302,15 +251,13 @@ pub async fn check_blockage(
net: &dyn Networking,
lox_cred: &lox_library::cred::Lox,
lox_pub: &IssuerPubKey,
) -> Result<lox_library::cred::Migration> {
let (req, state) = check_blockage::request(lox_cred, lox_pub)?;
let encoded_req: Vec<u8> = serde_json::to_vec(&req)?;
let encoded_resp = net
.request("/checkblockage".to_string(), encoded_req)
.await?;
let decoded_resp: check_blockage::Response = serde_json::from_slice(&encoded_resp)?;
let migcred = check_blockage::handle_response(state, decoded_resp)?;
Ok(migcred)
) -> lox_library::cred::Migration {
let (req, state) = check_blockage::request(lox_cred, lox_pub).unwrap();
let encoded_req: Vec<u8> = serde_json::to_vec(&req).unwrap();
let encoded_resp = net.request("/checkblockage".to_string(), 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)
@ -320,16 +267,26 @@ pub async fn blockage_migration(
migcred: &lox_library::cred::Migration,
lox_pub: &IssuerPubKey,
migration_pub: &IssuerPubKey,
) -> Result<lox_library::cred::Lox> {
let (req, state) = blockage_migration::request(lox_cred, migcred, lox_pub, migration_pub)?;
let encoded_req: Vec<u8> = serde_json::to_vec(&req)?;
) -> lox_library::cred::Lox {
let (req, state) =
blockage_migration::request(lox_cred, migcred, lox_pub, migration_pub).unwrap();
let encoded_req: Vec<u8> = serde_json::to_vec(&req).unwrap();
let encoded_resp = net
.request("/blockagemigration".to_string(), encoded_req)
.await?;
let decoded_resp: blockage_migration::Response = serde_json::from_slice(&encoded_resp)?;
let cred = blockage_migration::handle_response(state, decoded_resp, lox_pub)?;
Ok(cred)
.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
}
#[cfg(test)]
mod tests;
// Advance days on server (ONLY FOR TESTING)
pub async fn advance_days(net: &dyn Networking, days: u16) -> u32 {
let resp = net
.request(
"/advancedays".to_string(),
serde_json::to_vec(&days).unwrap(),
)
.await;
let today: u32 = serde_json::from_slice(&resp).unwrap();
today
}

36
src/client_net.rs Normal file
View File

@ -0,0 +1,36 @@
// This file provides networking using hyper (which
// https://gitlab.torproject.org/onyinyang/lox-server uses).
use crate::client_lib::Networking;
use async_trait::async_trait;
use hyper::{Body, Client, Method, Request};
pub struct HyperNet {
pub hostname: String,
}
#[async_trait]
impl Networking for HyperNet {
async fn request(&self, endpoint: String, body: Vec<u8>) -> Vec<u8> {
let client = Client::new();
let url = self.hostname.to_string() + &endpoint;
let uri: hyper::Uri = url.parse().expect("Failed to parse URL");
// always POST even if body is empty
let req = Request::builder()
.method(Method::POST)
.uri(uri)
.body(Body::from(body))
.expect("Failed to create POST request");
let resp = client.request(req).await.expect("Failed to POST");
println!("Response: {}", resp.status());
let buf = hyper::body::to_bytes(resp)
.await
.expect("Failed to concat bytes");
buf.to_vec()
}
}

View File

@ -1,12 +1,21 @@
use lox_cli::{self, networking::HyperNet, *};
mod client_lib;
use client_lib::*;
mod client_net;
use client_net::HyperNet;
use curve25519_dalek::scalar::Scalar;
use getopts::Options;
use lox_library::{
bridge_table::{BridgeLine, MAX_BRIDGES_PER_BUCKET},
scalar_u32, IssuerPubKey,
};
use lox_library::bridge_table::BridgeLine;
use lox_library::bridge_table::MAX_BRIDGES_PER_BUCKET;
use lox_library::scalar_u32;
use lox_library::IssuerPubKey;
use serde::Serialize;
use std::{env::args, fs::File, io::Write, path::Path};
use std::env::args;
use std::fs::File;
use std::io::Write;
use std::path::Path;
use std::str::FromStr;
// Prints the argument details for this program
fn print_usage(program: &str, opts: Options) {
@ -73,7 +82,7 @@ async fn main() {
serde_json::from_reader(lox_auth_pubkeys_infile).unwrap()
} else {
// download from Lox Auth
let pubkeys = get_lox_auth_keys(&net).await.unwrap();
let pubkeys = get_lox_auth_keys(&net).await;
// save to file for next time
save_object(&pubkeys, &lox_auth_pubkeys_filename);
pubkeys
@ -90,8 +99,7 @@ async fn main() {
get_lox_pub(&lox_auth_pubkeys),
get_invitation_pub(&lox_auth_pubkeys),
)
.await
.unwrap();
.await;
// save to files for future use
save_object(&cred, &lox_cred_filename);
@ -102,10 +110,9 @@ async fn main() {
|| !Path::new(bucket_filename).exists()
{
// get new Lox Credential
let open_invite = get_open_invitation(&net).await.unwrap();
let (cred, bl) = get_lox_credential(&net, &open_invite, get_lox_pub(&lox_auth_pubkeys))
.await
.unwrap();
let open_invite = get_open_invitation(&net).await;
let (cred, bl) =
get_lox_credential(&net, &open_invite, get_lox_pub(&lox_auth_pubkeys)).await;
let mut bucket = [BridgeLine::default(); MAX_BRIDGES_PER_BUCKET];
// note: this is a bucket with one real bridgeline and n-1
// default (zeroed out) bridgelines
@ -129,9 +136,7 @@ async fn main() {
let (cred, bucket) = if old_level == 0 {
if eligible_for_trust_promotion(&net, &lox_cred).await {
let migration_cred =
trust_promotion(&net, &lox_cred, get_lox_pub(&lox_auth_pubkeys))
.await
.unwrap();
trust_promotion(&net, &lox_cred, get_lox_pub(&lox_auth_pubkeys)).await;
let cred = trust_migration(
&net,
&lox_cred,
@ -139,26 +144,23 @@ async fn main() {
get_lox_pub(&lox_auth_pubkeys),
get_migration_pub(&lox_auth_pubkeys),
)
.await
.unwrap();
let bucket = get_bucket(&net, &cred).await.unwrap().0;
.await;
let bucket = get_bucket(&net, &cred).await;
(cred, bucket)
} else {
(lox_cred, bucket)
}
} else {
if eligible_for_level_up(&net, &lox_cred).await {
let (bucket, reachcred) = get_bucket(&net, &lox_cred).await.unwrap();
let cred = level_up(
let encbuckets = get_reachability_credential(&net).await;
let (cred, bucket) = level_up(
&net,
&lox_cred,
&reachcred.unwrap(),
&encbuckets,
get_lox_pub(&lox_auth_pubkeys),
get_reachability_pub(&lox_auth_pubkeys),
)
.await
.unwrap();
let bucket = get_bucket(&net, &lox_cred).await.unwrap().0;
.await;
(cred, bucket)
} else {
(lox_cred, bucket)
@ -183,13 +185,12 @@ async fn main() {
let (cred, invite) = issue_invite(
&net,
&lox_cred,
&get_reachability_credential(&net).await.unwrap(),
&get_reachability_credential(&net).await,
get_lox_pub(&lox_auth_pubkeys),
get_reachability_pub(&lox_auth_pubkeys),
get_invitation_pub(&lox_auth_pubkeys),
)
.await
.unwrap();
.await;
// TODO: Make this unique per-run (e.g., add timestamp)
save_object(&invite, &invite_filename);
save_object(&cred, &lox_cred_filename);
@ -207,3 +208,7 @@ async fn main() {
lox_cred
};
}
// Unit tests
#[cfg(test)]
mod tests;

View File

@ -1,36 +0,0 @@
// This file provides a Networking trait and a working hyper implementation
use anyhow::Result;
use async_trait::async_trait;
use hyper::{Body, Client, Method, Request};
// provides a generic way to make network requests
#[async_trait]
pub trait Networking {
async fn request(&self, endpoint: String, body: Vec<u8>) -> Result<Vec<u8>>;
}
pub struct HyperNet {
pub hostname: String,
}
#[async_trait]
impl Networking for HyperNet {
async fn request(&self, endpoint: String, body: Vec<u8>) -> Result<Vec<u8>> {
let client = Client::new();
let url = self.hostname.to_string() + &endpoint;
let uri: hyper::Uri = url.parse()?;
// always POST even if body is empty
let req = Request::builder()
.method(Method::POST)
.uri(uri)
.body(Body::from(body))?;
let resp = client.request(req).await?;
let buf = hyper::body::to_bytes(resp).await?;
Ok(buf.to_vec())
}
}

View File

@ -1,5 +1,5 @@
/*! Unit tests. Note that these require
https://gitlab.torproject.org/vecna/lox/-/tree/main/crates/lox-distributor
https://gitlab.torproject.org/vecna/lox-rs/-/tree/main/crates/lox-distributor
to be running. That fork adds an endpoint which allows for artificially
increasing the number of days that have passed, which allows us to test
trust migration and level up functions. */
@ -11,64 +11,14 @@ trust migration and level up functions. */
// Note: We can't run multiple time-changing tests simultaneously because
// they will invalidate invites that haven't been redeemed yet.
use crate::{networking::*, *};
use lox_library::{
bridge_table::{self, from_scalar, BridgeLine, BridgeTable},
cred::Lox,
proto::{
level_up::{LEVEL_INTERVAL, LEVEL_INVITATIONS},
trust_promotion::UNTRUSTED_INTERVAL,
},
scalar_u32,
};
use super::client_lib::*;
use super::client_net::HyperNet;
use lox_library::bridge_table::BridgeLine;
use lox_library::proto::level_up::{LEVEL_INTERVAL, LEVEL_INVITATIONS};
use lox_library::proto::trust_promotion::UNTRUSTED_INTERVAL;
use lox_library::scalar_u32;
use array_bytes;
use sha1::{Digest, Sha1};
use std::{
cmp::min,
collections::{HashMap, HashSet},
};
// Advance days on server
pub async fn advance_days(net: &dyn Networking, days: u16) -> u32 {
let resp = net
.request(
"/advancedays".to_string(),
serde_json::to_vec(&days).unwrap(),
)
.await
.unwrap();
let today: u32 = serde_json::from_slice(&resp).unwrap();
today
}
// Advance days and level up
pub async fn test_level_up(
net: &dyn Networking,
net_test: &dyn Networking,
cred: &Lox,
la_pubkeys: &Vec<IssuerPubKey>,
) -> Lox {
let level = scalar_u32(&cred.trust_level).unwrap();
advance_days(
net_test,
u16::try_from(LEVEL_INTERVAL[usize::try_from(level).unwrap()]).unwrap(),
)
.await;
assert!(eligible_for_level_up(net, cred).await);
let (bucket, reachcred) = get_bucket(net, cred).await.unwrap();
println!("Leveling up from level {} to {}", level, min(4, level + 1));
let new_cred = level_up(
net,
cred,
&reachcred.unwrap(),
get_lox_pub(la_pubkeys),
get_reachability_pub(la_pubkeys),
)
.await
.unwrap();
new_cred
}
use std::cmp::min;
// These are all combined into the same test because otherwise we run into
// issues with server state due to asynchronicity.
@ -77,33 +27,25 @@ async fn test_credential_operations() {
let net = HyperNet {
hostname: "http://localhost:8001".to_string(),
};
let net_test = HyperNet {
hostname: "http://localhost:8005".to_string(),
};
let la_pubkeys = get_lox_auth_keys(&net).await.unwrap();
let la_pubkeys = get_lox_auth_keys(&net).await;
// Get new Lox credential
println!("Getting new open-entry Lox credential");
let open_inv = get_open_invitation(&net).await.unwrap();
let (mut cred, bridgeline) = get_lox_credential(&net, &open_inv, get_lox_pub(&la_pubkeys))
.await
.unwrap();
let bucket = get_bucket(&net, &cred).await.unwrap().0;
let open_inv = get_open_invitation(&net).await;
let (mut cred, bridgeline) =
get_lox_credential(&net, &open_inv, get_lox_pub(&la_pubkeys)).await;
let bucket = get_bucket(&net, &cred).await;
//assert_eq!(bucket[0], bridgeline); // For some reason, this sometimes fails.
assert_eq!(bucket[0], bridgeline);
assert_eq!(bucket[1], BridgeLine::default());
assert_eq!(bucket[2], BridgeLine::default());
// Level up Lox Credential
println!("Leveling up to level 1");
assert_eq!(scalar_u32(&cred.trust_level).unwrap(), 0);
// Advance server time and trust migrate
advance_days(&net_test, u16::try_from(UNTRUSTED_INTERVAL).unwrap()).await;
advance_days(&net, u16::try_from(UNTRUSTED_INTERVAL).unwrap()).await;
assert!(eligible_for_trust_promotion(&net, &cred).await);
let migration_cred = trust_promotion(&net, &cred, get_lox_pub(&la_pubkeys))
.await
.unwrap();
let migration_cred = trust_promotion(&net, &cred, get_lox_pub(&la_pubkeys)).await;
cred = trust_migration(
&net,
&cred,
@ -111,18 +53,27 @@ async fn test_credential_operations() {
get_lox_pub(&la_pubkeys),
get_migration_pub(&la_pubkeys),
)
.await
.unwrap();
.await;
assert_eq!(scalar_u32(&cred.trust_level).unwrap(), 1);
// Advance server time and level up
for i in 1..LEVEL_INTERVAL.len() {
println!("Leveling up to level {}", min(i, LEVEL_INTERVAL.len() - 1));
assert_eq!(
scalar_u32(&cred.trust_level).unwrap(),
u32::try_from(i).unwrap()
);
cred = test_level_up(&net, &net_test, &cred, &la_pubkeys).await;
advance_days(&net, u16::try_from(LEVEL_INTERVAL[i]).unwrap()).await;
assert!(eligible_for_level_up(&net, &cred).await);
let encbuckets = get_reachability_credential(&net).await;
cred = level_up(
&net,
&cred,
&encbuckets,
get_lox_pub(&la_pubkeys),
get_reachability_pub(&la_pubkeys),
)
.await
.0;
// Assert that we increased level by 1 or stayed at 4
assert_eq!(
@ -136,8 +87,7 @@ async fn test_credential_operations() {
// Invite as many friends as possible
for j in 0..LEVEL_INVITATIONS[i] {
println!("Inviting friend {}", j);
let encbuckets = get_reachability_credential(&net).await.unwrap();
let encbuckets = get_reachability_credential(&net).await;
let (new_cred, invite) = issue_invite(
&net,
&cred,
@ -146,148 +96,23 @@ async fn test_credential_operations() {
get_reachability_pub(&la_pubkeys),
get_invitation_pub(&la_pubkeys),
)
.await
.unwrap();
.await;
let (friend_cred, friend_bucket) = redeem_invite(
&net,
&invite,
get_lox_pub(&la_pubkeys),
get_invitation_pub(&la_pubkeys),
)
.await
.unwrap();
.await;
cred = new_cred;
assert_eq!(
scalar_u32(&cred.invites_remaining).unwrap(),
LEVEL_INVITATIONS[i] - j - 1
);
// TODO: Where is this defined? Should I use the library constant?
assert_eq!(scalar_u32(&friend_cred.trust_level).unwrap(), 1);
assert_eq!(&cred.bucket, &friend_cred.bucket);
}
}
let net_tp = HyperNet {
hostname: "http://localhost:8002".to_string(),
};
// helper function to create map of bridges from bucket to mark blocked
fn bridges_to_block(
bucket: [BridgeLine; bridge_table::MAX_BRIDGES_PER_BUCKET],
num_bridges_to_block: usize,
) -> HashMap<String, HashSet<String>> {
let mut blocked_bridges = HashMap::<String, HashSet<String>>::new();
for i in 0..num_bridges_to_block {
let mut hasher = Sha1::new();
hasher.update(bucket[i].fingerprint);
let mut countries = HashSet::<String>::new();
countries.insert("RU".to_string());
blocked_bridges.insert(array_bytes::bytes2hex("", hasher.finalize()), countries);
}
blocked_bridges
}
// Block 1 bridge
println!("Marking one bridge blocked");
let bridges = get_bucket(&net, &cred).await.unwrap().0;
let blocked_bridges = bridges_to_block(bridges, 1);
let response = net_tp
.request(
"/reportblocked".to_string(),
serde_json::to_string(&blocked_bridges).unwrap().into(),
)
.await
.unwrap();
assert_eq!(String::from_utf8(response).unwrap(), "OK");
// Time passes...
advance_days(&net_test, 1).await;
// Check that we still have a Bucket Reachability credential
let (bucket, reachcred) = get_bucket(&net, &cred).await.unwrap();
assert!(reachcred.is_some());
println!("Can still obtain bucket reachability credential");
// Block 2 bridges
println!("Marking two bridges blocked");
let bridges = get_bucket(&net, &cred).await.unwrap().0;
let blocked_bridges = bridges_to_block(bridges, 2);
let response = net_tp
.request(
"/reportblocked".to_string(),
serde_json::to_string(&blocked_bridges).unwrap().into(),
)
.await
.unwrap();
assert_eq!(String::from_utf8(response).unwrap(), "OK");
// Time passes...
advance_days(&net_test, 1).await;
// Check that we don't have a Bucket Reachability credential
let (bucket, reachcred) = get_bucket(&net, &cred).await.unwrap();
assert!(reachcred.is_none());
println!("Cannot obtain bucket reachability credential");
// Migrate to a new bucket
println!("Migrating to a new bucket");
let migration_cred = check_blockage(&net, &cred, get_lox_pub(&la_pubkeys))
.await
.unwrap();
cred = blockage_migration(
&net,
&cred,
&migration_cred,
get_lox_pub(&la_pubkeys),
get_migration_pub(&la_pubkeys),
)
.await
.unwrap();
assert_eq!(scalar_u32(&cred.trust_level).unwrap(), 2);
assert_eq!(scalar_u32(&cred.blockages).unwrap(), 1);
// Level up to level 3
cred = test_level_up(&net, &net_test, &cred, &la_pubkeys).await;
assert_eq!(scalar_u32(&cred.trust_level).unwrap(), 3);
assert_eq!(scalar_u32(&cred.blockages).unwrap(), 1);
// Another blockage happens
println!("Marking three bridges blocked");
let bridges = get_bucket(&net, &cred).await.unwrap().0;
let blocked_bridges = bridges_to_block(bridges, 3);
let response = net_tp
.request(
"/reportblocked".to_string(),
serde_json::to_string(&blocked_bridges).unwrap().into(),
)
.await
.unwrap();
assert_eq!(String::from_utf8(response).unwrap(), "OK");
// Time passes...
advance_days(&net_test, 1).await;
// Migrate again
println!("Migrating to a new bucket");
let migration_cred = check_blockage(&net, &cred, get_lox_pub(&la_pubkeys))
.await
.unwrap();
cred = blockage_migration(
&net,
&cred,
&migration_cred,
get_lox_pub(&la_pubkeys),
get_migration_pub(&la_pubkeys),
)
.await
.unwrap();
assert_eq!(scalar_u32(&cred.trust_level).unwrap(), 1);
// Level up to level 2
cred = test_level_up(&net, &net_test, &cred, &la_pubkeys).await;
// Can't level up to level 3
assert!(!eligible_for_level_up(&net, &cred).await);
}