Compare commits

...

13 Commits

Author SHA1 Message Date
Vecna e2e59e50a6 Make H, BH public so knowledge of bucket can be verified 2024-02-20 16:46:38 -05:00
Vecna 65e763724b Merge changes from upstream 2024-02-20 16:45:56 -05:00
Renovate Bot 60a47c962f Update Rust crate serde_with to 3.5.0 2024-01-22 16:23:32 +00:00
onyinyang 050e628ae7
Add endpoint to return server constants 2024-01-22 11:09:31 -05:00
onyinyang 2ad2f2e211 Make lox-distributor listening port configurable 2024-01-22 15:08:39 +00:00
onyinyang 1426645a5b Completed protocol requests/responses 2024-01-22 15:03:07 +00:00
onyinyang b4e96255f6 Add pubkeys example 2024-01-22 15:03:07 +00:00
onyinyang 1605b973c9 Add Distributor API Documentation 2024-01-22 15:03:07 +00:00
onyinyang 33e24ef5b6 Fix bug in bridge_replace fn, test 2024-01-18 03:34:22 +00:00
Cecylia Bocovich 5ead4c4d9d Attempt to deserialize open invitation as lox_utls::Invite 2024-01-17 22:49:36 +00:00
onyinyang 596901b174 Fixes bug in parse_into_buckets that skips adding every 4th bridgeline 2024-01-17 16:51:53 +00:00
onyinyang 6afc557ad5 Fix length of bridge bytes and rdsys request interval 2024-01-17 16:43:39 +00:00
Cecylia Bocovich 1702027cb9
Export invitation_is_trusted for wasm_bindgen 2024-01-16 14:26:29 -05:00
18 changed files with 433 additions and 39 deletions

8
Cargo.lock generated
View File

@ -1919,9 +1919,9 @@ dependencies = [
[[package]] [[package]]
name = "serde_with" name = "serde_with"
version = "3.4.0" version = "3.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "64cd236ccc1b7a29e7e2739f27c0b2dd199804abc4290e32f59f3b68d6405c23" checksum = "f58c3a1b3e418f61c25b2aeb43fc6c95eaa252b8cecdda67f401943e9e08d33f"
dependencies = [ dependencies = [
"base64", "base64",
"chrono", "chrono",
@ -1936,9 +1936,9 @@ dependencies = [
[[package]] [[package]]
name = "serde_with_macros" name = "serde_with_macros"
version = "3.4.0" version = "3.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "93634eb5f75a2323b16de4748022ac4297f9e76b6dced2be287a099f41b5e788" checksum = "d2068b437a31fc68f25dd7edc296b078f04b45145c199d8eed9866e45f1ff274"
dependencies = [ dependencies = [
"darling", "darling",
"proc-macro2", "proc-macro2",

View File

@ -25,7 +25,7 @@ tokio = { version = "1", features = ["full", "macros", "signal"] }
rand = "0.8.5" rand = "0.8.5"
reqwest = { version = "0.11", features = ["json", "stream"]} reqwest = { version = "0.11", features = ["json", "stream"]}
serde = { version = "1.0", features = ["derive", "rc"] } serde = { version = "1.0", features = ["derive", "rc"] }
serde_with = "3.4.0" serde_with = "3.5.0"
lox-zkp = { git = "https://gitlab.torproject.org/onyinyang/lox-zkp", version = "0.8.0" } lox-zkp = { git = "https://gitlab.torproject.org/onyinyang/lox-zkp", version = "0.8.0" }
lox-library = { path = "../lox-library", version = "0.1.0"} lox-library = { path = "../lox-library", version = "0.1.0"}
lox_utils = { path = "../lox-utils", version = "0.1.0"} lox_utils = { path = "../lox-utils", version = "0.1.0"}

View File

@ -3,6 +3,7 @@
"db_path": "lox_db" "db_path": "lox_db"
}, },
"lox_authority_port": 8001,
"metrics_port": 5222, "metrics_port": 5222,
"bridge_config": { "bridge_config": {
"watched_blockages": [ "watched_blockages": [

File diff suppressed because one or more lines are too long

View File

@ -499,7 +499,23 @@ impl LoxServerContext {
Ok(resp) => prepare_header(resp), Ok(resp) => prepare_header(resp),
Err(e) => { Err(e) => {
println!("Error parsing today to JSON"); println!("Error parsing today to JSON");
prepare_error_header(e.to_string()) let response = json!({"error": e.to_string()});
let val = serde_json::to_string(&response).unwrap();
return prepare_header(val);
}
}
}
// Return the serialized pubkeys for the Bridge Authority as an HTTP response
pub fn send_constants(self) -> Response<Body> {
let constants = lox_utils::LOX_SYSTEM_INFO;
match serde_json::to_string(&constants) {
Ok(resp) => prepare_header(resp),
Err(e) => {
println!("Error parsing Constants to JSON");
let response = json!({"error": e.to_string()});
let val = serde_json::to_string(&response).unwrap();
prepare_header(val)
} }
} }
} }
@ -710,7 +726,11 @@ impl LoxServerContext {
pub fn advance_days_with_response_test(self, request: Bytes) -> Response<Body> { pub fn advance_days_with_response_test(self, request: Bytes) -> Response<Body> {
let req: u16 = match serde_json::from_slice(&request) { let req: u16 = match serde_json::from_slice(&request) {
Ok(req) => req, Ok(req) => req,
Err(e) => return prepare_error_header(e.to_string()), Err(e) => {
let response = json!({"error": e.to_string()});
let val = serde_json::to_string(&response).unwrap();
return prepare_header(val);
}
}; };
self.advance_days_test(req); self.advance_days_test(req);
self.send_today() self.send_today()

View File

@ -63,6 +63,7 @@ struct Args {
struct Config { struct Config {
db: DbConfig, db: DbConfig,
metrics_port: u16, metrics_port: u16,
lox_authority_port: u16,
bridge_config: BridgeConfig, bridge_config: BridgeConfig,
rtype: ResourceInfo, rtype: ResourceInfo,
} }
@ -121,7 +122,7 @@ async fn rdsys_request_creator(
// Makes a request to rdsys for the full set of Resources assigned to lox every interval // Makes a request to rdsys for the full set of Resources assigned to lox every interval
// (defined in the function) // (defined in the function)
async fn rdsys_request(rtype: ResourceInfo, tx: mpsc::Sender<ResourceState>) { async fn rdsys_request(rtype: ResourceInfo, tx: mpsc::Sender<ResourceState>) {
let mut interval = interval(Duration::from_secs(5)); let mut interval = interval(Duration::from_secs(120));
loop { loop {
interval.tick().await; interval.tick().await;
let resources = match request_resources( let resources = match request_resources(
@ -355,7 +356,7 @@ async fn main() {
async move { Ok::<_, Infallible>(service) } async move { Ok::<_, Infallible>(service) }
}); });
let addr = SocketAddr::from(([127, 0, 0, 1], 8001)); let addr = SocketAddr::from(([127, 0, 0, 1], config.lox_authority_port));
let server = Server::bind(&addr).serve(make_service); let server = Server::bind(&addr).serve(make_service);
let graceful = server.with_graceful_shutdown(shutdown_signal()); let graceful = server.with_graceful_shutdown(shutdown_signal());
println!("Listening on {}", addr); println!("Listening on {}", addr);

View File

@ -22,6 +22,7 @@ pub async fn handle(
} }
(&Method::POST, "/pubkeys") => Ok::<_, Infallible>(cloned_context.send_keys()), (&Method::POST, "/pubkeys") => Ok::<_, Infallible>(cloned_context.send_keys()),
(&Method::POST, "/today") => Ok::<_, Infallible>(cloned_context.send_today()), (&Method::POST, "/today") => Ok::<_, Infallible>(cloned_context.send_today()),
(&Method::POST, "/constants") => Ok::<_, Infallible>(cloned_context.send_constants()),
(&Method::POST, "/openreq") => Ok::<_, Infallible>({ (&Method::POST, "/openreq") => Ok::<_, Infallible>({
let bytes = body::to_bytes(req.into_body()).await.unwrap(); let bytes = body::to_bytes(req.into_body()).await.unwrap();
cloned_context.verify_and_send_open_cred(bytes) cloned_context.verify_and_send_open_cred(bytes)
@ -93,6 +94,7 @@ mod tests {
fn invite(&self) -> Request<Body>; fn invite(&self) -> Request<Body>;
fn reachability(&self) -> Request<Body>; fn reachability(&self) -> Request<Body>;
fn pubkeys(&self) -> Request<Body>; fn pubkeys(&self) -> Request<Body>;
fn constants(&self) -> Request<Body>;
fn openinvite(&self, request: proto::open_invite::Request) -> Request<Body>; fn openinvite(&self, request: proto::open_invite::Request) -> Request<Body>;
fn trustpromo(&self, request: proto::trust_promotion::Request) -> Request<Body>; fn trustpromo(&self, request: proto::trust_promotion::Request) -> Request<Body>;
fn trustmigration(&self, request: proto::migration::Request) -> Request<Body>; fn trustmigration(&self, request: proto::migration::Request) -> Request<Body>;
@ -129,6 +131,14 @@ mod tests {
.unwrap() .unwrap()
} }
fn constants(&self) -> Request<Body> {
Request::builder()
.method("POST")
.uri("http://localhost/constants")
.body(Body::empty())
.unwrap()
}
fn openinvite(&self, request: proto::open_invite::Request) -> Request<Body> { fn openinvite(&self, request: proto::open_invite::Request) -> Request<Body> {
let req_str = serde_json::to_string(&request).unwrap(); let req_str = serde_json::to_string(&request).unwrap();
Request::builder() Request::builder()
@ -378,6 +388,16 @@ mod tests {
assert_eq!(pubkey_response.status(), StatusCode::OK); assert_eq!(pubkey_response.status(), StatusCode::OK);
} }
#[tokio::test]
async fn test_handle_constants() {
let th = TestHarness::new();
let lc = LoxClientMock {};
// Test Pubkeys
let constant_request = lc.constants();
let constant_response = handle(th.context.clone(), constant_request).await.unwrap();
assert_eq!(constant_response.status(), StatusCode::OK);
}
#[tokio::test] #[tokio::test]
async fn test_handle_lox_protocols() { async fn test_handle_lox_protocols() {
let mut th = TestHarness::new(); let mut th = TestHarness::new();

View File

@ -76,8 +76,9 @@ pub fn parse_into_buckets(
count += 1; count += 1;
} else { } else {
buckets.push(bucket); buckets.push(bucket);
count = 0;
bucket = [BridgeLine::default(); MAX_BRIDGES_PER_BUCKET]; bucket = [BridgeLine::default(); MAX_BRIDGES_PER_BUCKET];
bucket[0] = bridgeline;
count = 1;
} }
} }
// Handle the extra buckets that were not allocated already // Handle the extra buckets that were not allocated already

View File

@ -20,7 +20,7 @@ bincode = "1"
chrono = "0.4" chrono = "0.4"
rand = { version = "0.8", features = ["std_rng"]} rand = { version = "0.8", features = ["std_rng"]}
serde = "1.0.195" serde = "1.0.195"
serde_with = {version = "3.4.0", features = ["json"]} serde_with = {version = "3.5.0", features = ["json"]}
sha2 = "0.10" sha2 = "0.10"
statistical = "1.0.0" statistical = "1.0.0"
lazy_static = "1" lazy_static = "1"

View File

@ -25,7 +25,7 @@ use std::convert::{TryFrom, TryInto};
use subtle::ConstantTimeEq; use subtle::ConstantTimeEq;
/// Each bridge information line is serialized into this many bytes /// Each bridge information line is serialized into this many bytes
pub const BRIDGE_BYTES: usize = 200; pub const BRIDGE_BYTES: usize = 250;
/// The max number of bridges per bucket /// The max number of bridges per bucket
pub const MAX_BRIDGES_PER_BUCKET: usize = 3; pub const MAX_BRIDGES_PER_BUCKET: usize = 3;

View File

@ -556,8 +556,24 @@ impl BridgeAuth {
} }
res = ReplaceSuccess::Replaced res = ReplaceSuccess::Replaced
} else if !self.bridge_table.spares.is_empty() { } else if !self.bridge_table.spares.is_empty() {
// First get the bucketnums for the replacement bridge in case it is a spare
let mut bucketnums: Vec<u32> = Vec::new();
for (bucketnum, _) in positions.iter() {
bucketnums.push(*bucketnum);
}
// Get the first spare and remove it from the spares set. // Get the first spare and remove it from the spares set.
let spare = *self.bridge_table.spares.iter().next().unwrap(); let mut spare = *self.bridge_table.spares.iter().next().unwrap();
// Check that the first spare in the list of spares is not the one to be replaced
if bucketnums.contains(&spare) {
// If it is, take the last spare instead
spare = *self.bridge_table.spares.iter().last().unwrap();
// If this is the same bucketnum, there is only one spare bucket with the bridge
// to be replaced in it, so don't replace it.
if bucketnums.contains(&spare) {
res = ReplaceSuccess::NotReplaced;
return res;
}
}
self.bridge_table.spares.remove(&spare); self.bridge_table.spares.remove(&spare);
self.bridge_table.recycleable_keys.push(spare); self.bridge_table.recycleable_keys.push(spare);
// Get the actual bridges from the spare bucket // Get the actual bridges from the spare bucket
@ -596,6 +612,7 @@ impl BridgeAuth {
} }
res = ReplaceSuccess::Replaced res = ReplaceSuccess::Replaced
} else { } else {
println!("No available bridges");
// If there are no available bridges that can be assigned here, the only thing // If there are no available bridges that can be assigned here, the only thing
// that can be done is return an indication that updating the gone bridge // that can be done is return an indication that updating the gone bridge
// didn't work. // didn't work.

View File

@ -44,8 +44,8 @@ pub struct Request {
CQ: RistrettoPoint, CQ: RistrettoPoint,
// Fields for proving which bucket we have // Fields for proving which bucket we have
H: RistrettoPoint, pub H: RistrettoPoint,
BP: RistrettoPoint, pub BP: RistrettoPoint,
// Fields for proving 3 <= trust_level <= 4 // Fields for proving 3 <= trust_level <= 4
// CG can be computed by verifier // CG can be computed by verifier

View File

@ -1057,13 +1057,15 @@ fn test_update_bridge() {
#[test] #[test]
fn test_bridge_replace() { fn test_bridge_replace() {
// Create 3 open invitation buckets and 3 spare buckets // Create 3 open invitation buckets and 3 spare buckets
let cases = vec!["not found", "available", "unallocated", "failed", "spare"]; let cases = vec!["not found", "available", "unallocated", "spare", "failed"];
let num_buckets = 5;
let hot_spare = 0;
for case in cases { for case in cases {
let mut th: TestHarness; let mut th: TestHarness;
if case != "failed" { if String::from(case) != "failed" {
th = TestHarness::new(); th = TestHarness::new();
} else { } else {
th = TestHarness::new_buckets(5, 0); th = TestHarness::new_buckets(num_buckets, hot_spare);
} }
// Randomly select a bridge to replace // Randomly select a bridge to replace
@ -1072,6 +1074,7 @@ fn test_bridge_replace() {
while !th.ba.bridge_table.buckets.contains_key(&num) { while !th.ba.bridge_table.buckets.contains_key(&num) {
num = rand::thread_rng().gen_range(0..th.ba.bridge_table.counter); num = rand::thread_rng().gen_range(0..th.ba.bridge_table.counter);
} }
println!("chosen num is: {:?}", num);
let replaceable_bucket = *th.ba.bridge_table.buckets.get(&num).unwrap(); let replaceable_bucket = *th.ba.bridge_table.buckets.get(&num).unwrap();
let replacement_bridge = &replaceable_bucket[0]; let replacement_bridge = &replaceable_bucket[0];
assert!( assert!(
@ -1207,7 +1210,7 @@ fn test_bridge_replace() {
"Extra spare bridges not added to unallocated bridges" "Extra spare bridges not added to unallocated bridges"
); );
println!("Successfully added unallocated bridgeline"); println!("Successfully added bridgeline from spare");
} }
"failed" => { "failed" => {
// Case four: available_bridge == None and unallocated_bridges == None and spare buckets == None // Case four: available_bridge == None and unallocated_bridges == None and spare buckets == None

View File

@ -15,7 +15,7 @@ repository = "https://gitlab.torproject.org/tpo/anti-censorship/lox.git/"
lox-library = {path = "../lox-library", version = "0.1.0"} lox-library = {path = "../lox-library", version = "0.1.0"}
serde = "1" serde = "1"
serde_json = "1.0.108" serde_json = "1.0.108"
serde_with = "3.4.0" serde_with = "3.5.0"
[features] [features]

View File

@ -2,7 +2,7 @@ use lox_library::bridge_table::{
from_scalar, BridgeLine, BridgeTable, EncryptedBucket, MAX_BRIDGES_PER_BUCKET, from_scalar, BridgeLine, BridgeTable, EncryptedBucket, MAX_BRIDGES_PER_BUCKET,
}; };
use lox_library::cred::{BucketReachability, Invitation, Lox}; use lox_library::cred::{BucketReachability, Invitation, Lox};
use lox_library::proto; use lox_library::proto::{self, check_blockage, level_up, trust_promotion};
use lox_library::{IssuerPubKey, OPENINV_LENGTH}; use lox_library::{IssuerPubKey, OPENINV_LENGTH};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use serde_with::serde_as; use serde_with::serde_as;
@ -75,17 +75,21 @@ pub struct PubKeys {
#[derive(Debug, Deserialize, Serialize)] #[derive(Debug, Deserialize, Serialize)]
pub struct LoxSystemInfo { pub struct LoxSystemInfo {
max_blockages: [u32; proto::level_up::MAX_LEVEL + 1], pub max_level: usize,
level_interval: [u32; proto::level_up::MAX_LEVEL + 1], pub untrusted_interval: u32,
level_invitations: [u32; proto::level_up::MAX_LEVEL + 1], pub max_blockages: [u32; level_up::MAX_LEVEL + 1],
min_trust_level: u32, pub level_interval: [u32; level_up::MAX_LEVEL + 1],
pub level_invitations: [u32; level_up::MAX_LEVEL + 1],
pub min_blockage_migration_trust_level: u32,
} }
pub const LOX_SYSTEM_INFO: LoxSystemInfo = LoxSystemInfo { pub const LOX_SYSTEM_INFO: LoxSystemInfo = LoxSystemInfo {
max_blockages: proto::level_up::MAX_BLOCKAGES, max_level: level_up::MAX_LEVEL,
level_interval: proto::level_up::LEVEL_INTERVAL, untrusted_interval: trust_promotion::UNTRUSTED_INTERVAL,
level_invitations: proto::level_up::LEVEL_INVITATIONS, max_blockages: level_up::MAX_BLOCKAGES,
min_trust_level: proto::check_blockage::MIN_TRUST_LEVEL, level_interval: level_up::LEVEL_INTERVAL,
level_invitations: level_up::LEVEL_INVITATIONS,
min_blockage_migration_trust_level: check_blockage::MIN_TRUST_LEVEL,
}; };
#[serde_as] #[serde_as]

View File

@ -19,6 +19,8 @@ import init, {
set_panic_hook, get_last_upgrade_time, get_trust_level, get_invites_remaining, get_issued_invite_expiry, get_received_invite_expiry, get_bridgelines_from_bucket} from "./pkg/lox_wasm.js"; set_panic_hook, get_last_upgrade_time, get_trust_level, get_invites_remaining, get_issued_invite_expiry, get_received_invite_expiry, get_bridgelines_from_bucket} from "./pkg/lox_wasm.js";
let pubkeys = await simple_request("/pubkeys"); let pubkeys = await simple_request("/pubkeys");
console.log("Got pubkeys: " + pubkeys); console.log("Got pubkeys: " + pubkeys);
let constants = await simple_request("/constants");
console.log("Got constants: " + constants);
// Get Lox Invitation // Get Lox Invitation
let requested_invite = await init().then(() => { let requested_invite = await init().then(() => {

View File

@ -789,19 +789,12 @@ pub fn get_bridgelines_from_bucket(
} }
} }
pub fn get_constants() -> Result<String, JsValue> { #[wasm_bindgen]
match serde_json::to_string(&lox_utils::LOX_SYSTEM_INFO) {
Ok(system_info_str) => Ok(system_info_str),
Err(e) => Err(JsValue::from(e.to_string())),
}
}
pub fn invitation_is_trusted(unspecified_invitation_str: String) -> Result<bool, JsValue> { pub fn invitation_is_trusted(unspecified_invitation_str: String) -> Result<bool, JsValue> {
match serde_json::from_str::<Invitation>(&unspecified_invitation_str) { match serde_json::from_str::<Invitation>(&unspecified_invitation_str) {
Ok(_) => Ok(true), Ok(_) => Ok(true),
Err(_) => { Err(_) => {
let invite = unspecified_invitation_str.as_bytes(); match serde_json::from_str::<lox_utils::Invite>(&unspecified_invitation_str){
match lox_utils::validate(invite) {
Ok(_) => Ok(false), Ok(_) => Ok(false),
Err(e) => Err(JsValue::from(e.to_string())), Err(e) => Err(JsValue::from(e.to_string())),
} }

332
doc/lox-distributor-api.md Normal file

File diff suppressed because one or more lines are too long