Compare commits

...

11 Commits

Author SHA1 Message Date
Vecna d7beaad560 Don't panic 2024-06-08 14:49:07 -04:00
Vecna 2e94b4df34 Handle more errors 2024-06-06 17:14:06 -04:00
Vecna 4eb3204240 Don't output response code for every request 2024-05-30 23:04:24 -04:00
Vecna a87bd2b9e9 Make most functions return results 2024-05-28 19:05:35 -04:00
Vecna 3c2bd56921 Get bucket reachability credential with bucket, not level up 2024-04-30 01:18:17 -04:00
Vecna 176258afe9 Use forked lox repo for lox_utils as well 2024-04-29 16:29:53 -04:00
Vecna 4e87b13fa0 Test blockage migration
For some reason, we're unable to level up after migrating to a new bucket.
2024-04-29 15:49:26 -04:00
Vecna 1f155ac969 Refactor/clean up 2024-04-29 12:54:21 -04:00
Vecna 9f945a05af Remove TODO I am not going to do
(This is simply defined in the protocol as Scalar::ONE)
2024-04-26 23:37:44 -04:00
Vecna 10966f39e9 Make advancing date on server testing-only 2024-04-26 23:37:05 -04:00
Vecna afefe519b1 Update lox_cli test to use different endpoint for advancing time 2024-04-26 23:12:10 -04:00
8 changed files with 472 additions and 252 deletions

3
.gitignore vendored Normal file
View File

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

View File

@ -1,14 +1,13 @@
[package]
name = "lox_test"
name = "lox_cli"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
#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" }
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" }
curve25519-dalek = { package = "curve25519-dalek-ng", version = "3", default-features = false, features = ["serde", "std"] }
ed25519-dalek = { version = "1", features = ["serde"] }
getopts = "0.2"
@ -19,11 +18,16 @@ 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.27", features = ["full"] }
hyper = { version = "0.14.28", 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_test
# lox_cli
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-rs/-/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/-/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,36 +0,0 @@
// 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,29 +1,18 @@
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 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 std::collections::HashMap;
use std::time::Duration;
// 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>;
}
pub mod networking;
use crate::networking::Networking;
// Helper functions to get public keys from vector
pub fn get_lox_pub(lox_auth_pubkeys: &Vec<IssuerPubKey>) -> &IssuerPubKey {
@ -56,8 +45,17 @@ pub async fn eligible_for_trust_promotion(
Some(v) => v,
None => return false,
};
scalar_u32(&cred.trust_level).unwrap() == 0
&& level_since + trust_promotion::UNTRUSTED_INTERVAL <= get_today(net).await
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,
}
}
// Helper function to check if credential is eligible for
@ -67,52 +65,91 @@ pub async fn eligible_for_level_up(net: &dyn Networking, cred: &lox_library::cre
Some(v) => v,
None => return false,
};
let trust_level: usize = scalar_u32(&cred.trust_level).unwrap().try_into().unwrap();
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,
};
trust_level > 0
&& level_since + lox_library::proto::level_up::LEVEL_INTERVAL[trust_level]
<= get_today(net).await
&& blockages <= MAX_BLOCKAGES[trust_level as usize]
&& level_since + LEVEL_INTERVAL[trust_level as usize] <= date
}
// Get current date from Lox Auth
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
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)
}
// Download 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
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)
}
// Get encrypted bridge table
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
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)
}
// Get encrypted bridge table from BridgeDB and decrypt our entry
pub async fn get_bucket(
net: &dyn Networking,
lox_cred: &lox_library::cred::Lox,
) -> [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(encbucket) => encbucket,
None => panic!("Provided ID not found"),
) -> 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
))
}
};
BridgeTable::decrypt_bucket(id, &key, &encbucket).unwrap().0
let encbucket = match encbuckets.get(&id) {
Some(v) => v,
None => {
return Err(anyhow!(
"Unable to get encrypted bucket from encrypted bridge table"
))
}
};
match BridgeTable::decrypt_bucket(id, &key, &encbucket) {
Ok(v) => Ok(v),
Err(e) => Err(anyhow!(
"aead error returned when trying to decrypt bucket: {}",
e
)),
}
}
// Get an open invitation
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
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)
}
// Get a Lox Credential from an open invitation
@ -120,13 +157,13 @@ pub async fn get_lox_credential(
net: &dyn Networking,
open_invite: &[u8; OPENINV_LENGTH],
lox_pub: &IssuerPubKey,
) -> (lox_library::cred::Lox, BridgeLine) {
) -> Result<(lox_library::cred::Lox, BridgeLine)> {
let (req, state) = open_invite::request(&open_invite);
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)
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))
}
// Get a migration credential to migrate to higher trust
@ -134,13 +171,13 @@ pub async fn trust_promotion(
net: &dyn Networking,
lox_cred: &lox_library::cred::Lox,
lox_pub: &IssuerPubKey,
) -> 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
) -> 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)
}
// Promote from untrusted (trust level 0) to trusted (trust level 1)
@ -150,50 +187,35 @@ pub async fn trust_migration(
migration_cred: &lox_library::cred::Migration,
lox_pub: &IssuerPubKey,
migration_pub: &IssuerPubKey,
) -> 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
) -> 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)
}
// Increase trust from at least level 1 to higher levels
pub async fn level_up(
net: &dyn Networking,
lox_cred: &lox_library::cred::Lox,
encbuckets: &HashMap<u32, EncryptedBucket>,
reachcred: &cred::BucketReachability,
lox_pub: &IssuerPubKey,
reachability_pub: &IssuerPubKey,
) -> (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
) -> Result<lox_library::cred::Lox> {
let (req, state) = level_up::request(
lox_cred,
&reachcred,
lox_pub,
reachability_pub,
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)
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)
}
// Request an Invitation credential to give to a friend
@ -204,28 +226,58 @@ pub async fn issue_invite(
lox_pub: &IssuerPubKey,
reachability_pub: &IssuerPubKey,
invitation_pub: &IssuerPubKey,
) -> (lox_library::cred::Lox, lox_library::cred::Invitation) {
) -> Result<(lox_library::cred::Lox, lox_library::cred::Invitation)> {
// 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();
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 (req, state) = issue_invite::request(
lox_cred,
&reachcred,
lox_pub,
reachability_pub,
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();
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)?;
let (cred, invite) =
issue_invite::handle_response(state, decoded_resp, lox_pub, invitation_pub).unwrap();
(cred, invite)
issue_invite::handle_response(state, decoded_resp, lox_pub, invitation_pub)?;
Ok((cred, invite))
}
// Redeem an Invitation credential to start at trust level 1
@ -234,16 +286,15 @@ pub async fn redeem_invite(
invite: &lox_library::cred::Invitation,
lox_pub: &IssuerPubKey,
invitation_pub: &IssuerPubKey,
) -> (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();
) -> 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)?;
let bucket = get_bucket(net, &cred).await;
(cred, bucket)
let bucket = get_bucket(net, &cred).await?.0;
Ok((cred, bucket))
}
// Check for a migration credential to move to a new bucket
@ -251,13 +302,15 @@ pub async fn check_blockage(
net: &dyn Networking,
lox_cred: &lox_library::cred::Lox,
lox_pub: &IssuerPubKey,
) -> 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
) -> 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)
}
// Migrate to a new bucket (must be level >= 3)
@ -267,26 +320,16 @@ pub async fn blockage_migration(
migcred: &lox_library::cred::Migration,
lox_pub: &IssuerPubKey,
migration_pub: &IssuerPubKey,
) -> 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();
) -> 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)?;
let encoded_resp = net
.request("/blockagemigration".to_string(), 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
.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)
}
// 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
}
#[cfg(test)]
mod tests;

View File

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

36
src/networking.rs Normal file
View File

@ -0,0 +1,36 @@
// 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-rs/-/tree/main/crates/lox-distributor
https://gitlab.torproject.org/vecna/lox/-/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,14 +11,64 @@ 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 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 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 std::cmp::min;
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
}
// These are all combined into the same test because otherwise we run into
// issues with server state due to asynchronicity.
@ -27,25 +77,33 @@ async fn test_credential_operations() {
let net = HyperNet {
hostname: "http://localhost:8001".to_string(),
};
let la_pubkeys = get_lox_auth_keys(&net).await;
let net_test = HyperNet {
hostname: "http://localhost:8005".to_string(),
};
let la_pubkeys = get_lox_auth_keys(&net).await.unwrap();
// Get new Lox credential
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;
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;
assert_eq!(bucket[0], bridgeline);
//assert_eq!(bucket[0], bridgeline); // For some reason, this sometimes fails.
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, u16::try_from(UNTRUSTED_INTERVAL).unwrap()).await;
advance_days(&net_test, 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;
let migration_cred = trust_promotion(&net, &cred, get_lox_pub(&la_pubkeys))
.await
.unwrap();
cred = trust_migration(
&net,
&cred,
@ -53,27 +111,18 @@ async fn test_credential_operations() {
get_lox_pub(&la_pubkeys),
get_migration_pub(&la_pubkeys),
)
.await;
.await
.unwrap();
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()
);
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;
cred = test_level_up(&net, &net_test, &cred, &la_pubkeys).await;
// Assert that we increased level by 1 or stayed at 4
assert_eq!(
@ -87,7 +136,8 @@ async fn test_credential_operations() {
// Invite as many friends as possible
for j in 0..LEVEL_INVITATIONS[i] {
let encbuckets = get_reachability_credential(&net).await;
println!("Inviting friend {}", j);
let encbuckets = get_reachability_credential(&net).await.unwrap();
let (new_cred, invite) = issue_invite(
&net,
&cred,
@ -96,23 +146,148 @@ async fn test_credential_operations() {
get_reachability_pub(&la_pubkeys),
get_invitation_pub(&la_pubkeys),
)
.await;
.await
.unwrap();
let (friend_cred, friend_bucket) = redeem_invite(
&net,
&invite,
get_lox_pub(&la_pubkeys),
get_invitation_pub(&la_pubkeys),
)
.await;
.await
.unwrap();
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);
}