diff --git a/crates/lox-distributor/src/lox_context.rs b/crates/lox-distributor/src/lox_context.rs index 458e265..4a4732b 100644 --- a/crates/lox-distributor/src/lox_context.rs +++ b/crates/lox-distributor/src/lox_context.rs @@ -794,6 +794,34 @@ impl LoxServerContext { // Troll Patrol-related tasks + // Troll Patrol reports that a bridge is blocked somewhere + pub fn report_blocked_bridges(self, request: Bytes) -> Response { + let blocked_bridges: HashMap> = + match serde_json::from_slice(&request) { + Ok(req) => req, + Err(e) => { + let response = json!({"error": e.to_string()}); + let val = serde_json::to_string(&response).unwrap(); + return prepare_header(val); + } + }; + // TODO: Forward this information to rdsys + for bridge_str in blocked_bridges.keys() { + let bridge = array_bytes::hex2array(bridge_str).unwrap(); + if self.tp_bridge_infos.lock().unwrap().contains_key(&bridge) { + let bl = self + .tp_bridge_infos + .lock() + .unwrap() + .get(&bridge) + .unwrap() + .bridge_line; + self.mark_blocked(bl); + } + } + prepare_header("OK".to_string()) + } + // Verify one negative report, return true if verification succeeds pub fn verify_negative_report(&self, report: NegativeReport) -> bool { match self diff --git a/crates/lox-distributor/src/troll_patrol_handler.rs b/crates/lox-distributor/src/troll_patrol_handler.rs index 3bbdec2..aceb3d0 100644 --- a/crates/lox-distributor/src/troll_patrol_handler.rs +++ b/crates/lox-distributor/src/troll_patrol_handler.rs @@ -18,6 +18,10 @@ pub async fn handle( .body(Body::from("Allow POST")) .unwrap()), _ => match (req.method(), req.uri().path()) { + (&Method::POST, "/reportblocked") => Ok::<_, Infallible>({ + let bytes = body::to_bytes(req.into_body()).await.unwrap(); + cloned_context.report_blocked_bridges(bytes) + }), (&Method::POST, "/verifynegative") => Ok::<_, Infallible>({ let bytes = body::to_bytes(req.into_body()).await.unwrap(); cloned_context.verify_negative_reports(bytes) @@ -49,6 +53,7 @@ mod tests { proto::*, scalar_u32, BridgeAuth, BridgeDb, }; + use lox_zkp::ProofError; use rand::RngCore; use sha1::{Digest, Sha1}; use std::{ @@ -65,6 +70,8 @@ mod tests { use super::*; trait TpClient { + fn reportblocked(&self, blocked_bridges: HashMap>) + -> Request; fn verifynegative(&self, reports: BTreeMap) -> Request; fn verifypositive(&self, reports: Vec) -> Request; } @@ -72,6 +79,18 @@ mod tests { struct TpClientMock {} impl TpClient for TpClientMock { + fn reportblocked( + &self, + blocked_bridges: HashMap>, + ) -> Request { + let req = serde_json::to_string(&blocked_bridges).unwrap(); + Request::builder() + .method("POST") + .uri("http://localhost/reportblocked") + .body(Body::from(req)) + .unwrap() + } + fn verifynegative(&self, reports: BTreeMap) -> Request { let req = serde_json::to_string(&reports).unwrap(); Request::builder() @@ -165,6 +184,19 @@ mod tests { String::from_utf8(body_bytes.to_vec()).unwrap() } + async fn get_bucket( + th: &mut TestHarness, + cred: &Lox, + ) -> [BridgeLine; bridge_table::MAX_BRIDGES_PER_BUCKET] { + let (id, key) = bridge_table::from_scalar(cred.bucket).unwrap(); + let mut ba = th.context.ba.lock().unwrap(); + let encbuckets = ba.enc_bridge_table(); + let bucket = + bridge_table::BridgeTable::decrypt_bucket(id, &key, encbuckets.get(&id).unwrap()) + .unwrap(); + bucket.0 + } + async fn get_new_credential(th: &mut TestHarness) -> Lox { let inv = th.context.db.lock().unwrap().invite().unwrap(); let (req, state) = open_invite::request(&inv); @@ -181,7 +213,7 @@ mod tests { cred } - async fn level_up(th: &mut TestHarness, cred: &Lox) -> Lox { + async fn level_up(th: &mut TestHarness, cred: &Lox) -> Result { let current_level = scalar_u32(&cred.trust_level).unwrap(); if current_level == 0 { th.context @@ -189,13 +221,13 @@ mod tests { let mut ba = th.context.ba.lock().unwrap(); let (promreq, promstate) = trust_promotion::request(cred, &ba.lox_pub, ba.today()).unwrap(); - let promresp = ba.handle_trust_promotion(promreq).unwrap(); - let migcred = trust_promotion::handle_response(promstate, promresp).unwrap(); + let promresp = ba.handle_trust_promotion(promreq)?; + let migcred = trust_promotion::handle_response(promstate, promresp)?; let (migreq, migstate) = migration::request(cred, &migcred, &ba.lox_pub, &ba.migration_pub).unwrap(); - let migresp = ba.handle_migration(migreq).unwrap(); + let migresp = ba.handle_migration(migreq)?; let new_cred = migration::handle_response(migstate, migresp, &ba.lox_pub).unwrap(); - new_cred + Ok(new_cred) } else { th.context.advance_days_test( level_up::LEVEL_INTERVAL[usize::try_from(current_level).unwrap()] @@ -208,7 +240,11 @@ mod tests { let bucket = bridge_table::BridgeTable::decrypt_bucket(id, &key, encbuckets.get(&id).unwrap()) .unwrap(); - let reachcred = bucket.1.unwrap(); + let reachcred = match bucket.1 { + Some(v) => v, + None => return Err(ProofError::VerificationFailure), + }; + //let reachcred = bucket.1.unwrap(); let (lvreq, lvstate) = level_up::request( cred, &reachcred, @@ -217,12 +253,103 @@ mod tests { ba.today(), ) .unwrap(); - let lvresp = ba.handle_level_up(lvreq).unwrap(); + let lvresp = ba.handle_level_up(lvreq)?; let new_cred = level_up::handle_response(lvstate, lvresp, &ba.lox_pub).unwrap(); - new_cred + Ok(new_cred) } } + #[tokio::test] + async fn test_report_blocked_bridges() { + let mut th = TestHarness::new(); + let tpc = TpClientMock {}; + let mut Htables = HashMap::::new(); + + // 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> { + let mut blocked_bridges = HashMap::>::new(); + for i in 0..num_bridges_to_block { + let mut hasher = Sha1::new(); + hasher.update(bucket[i].fingerprint); + let mut countries = HashSet::::new(); + countries.insert("RU".to_string()); + blocked_bridges.insert(array_bytes::bytes2hex("", hasher.finalize()), countries); + } + blocked_bridges + } + + // Get new level 0 credential + let cred = get_new_credential(&mut th).await; + + th.context.generate_tp_bridge_infos(); + + let bridges = get_bucket(&mut th, &cred).await; + + // Block our first (and only) bridge + let blocked_bridges = bridges_to_block(bridges, 1); + let request = tpc.reportblocked(blocked_bridges); + let response = handle(th.context.clone(), &mut Htables, request) + .await + .unwrap(); + assert_eq!(response.status(), StatusCode::OK); + let resp_str = body_to_string(response).await; + assert_eq!(resp_str, "OK"); + + th.context.generate_tp_bridge_infos(); + + // We should not be able to migrate to level 1 + assert!(level_up(&mut th, &cred).await.is_err()); + + // Get new level 1 credential + let cred = get_new_credential(&mut th).await; + let cred = level_up(&mut th, &cred).await.unwrap(); + + th.context.generate_tp_bridge_infos(); + + let bridges = get_bucket(&mut th, &cred).await; + + // Block as many bridges as possible without preventing level up + let blocked_bridges = bridges_to_block( + bridges, + bridge_table::MAX_BRIDGES_PER_BUCKET - bridge_table::MIN_BUCKET_REACHABILITY, + ); + let request = tpc.reportblocked(blocked_bridges); + let response = handle(th.context.clone(), &mut Htables, request) + .await + .unwrap(); + assert_eq!(response.status(), StatusCode::OK); + let resp_str = body_to_string(response).await; + assert_eq!(resp_str, "OK"); + + th.context.generate_tp_bridge_infos(); + + // We should still be able to level up + let cred = level_up(&mut th, &cred).await.unwrap(); + + th.context.generate_tp_bridge_infos(); + + let bridges = get_bucket(&mut th, &cred).await; + + // Block enough bridges to prevent level up + let blocked_bridges = bridges_to_block( + bridges, + bridge_table::MAX_BRIDGES_PER_BUCKET - bridge_table::MIN_BUCKET_REACHABILITY + 1, + ); + let request = tpc.reportblocked(blocked_bridges); + let response = handle(th.context.clone(), &mut Htables, request) + .await + .unwrap(); + assert_eq!(response.status(), StatusCode::OK); + let resp_str = body_to_string(response).await; + assert_eq!(resp_str, "OK"); + + // We should not be able to level up + assert!(level_up(&mut th, &cred).await.is_err()); + } + #[tokio::test] async fn test_negative_reports() { let mut th = TestHarness::new(); @@ -231,19 +358,11 @@ mod tests { // Get new level 1 credential let cred = get_new_credential(&mut th).await; - let cred = level_up(&mut th, &cred).await; + let cred = level_up(&mut th, &cred).await.unwrap(); th.context.generate_tp_bridge_infos(); - let mut ba = th.context.ba.lock().unwrap(); - - // Get bucket - let (id, key) = bridge_table::from_scalar(cred.bucket).unwrap(); - let encbuckets = ba.enc_bridge_table(); - let bucket = - bridge_table::BridgeTable::decrypt_bucket(id, &key, encbuckets.get(&id).unwrap()) - .unwrap(); - let bridges = bucket.0; + let bridges = get_bucket(&mut th, &cred).await; // Create random number of negative reports for each bridge in bucket let mut rng = rand::thread_rng(); @@ -340,21 +459,13 @@ mod tests { // Get new level 3 credential let cred = get_new_credential(&mut th).await; - let cred = level_up(&mut th, &cred).await; - let cred = level_up(&mut th, &cred).await; - let cred = level_up(&mut th, &cred).await; + let cred = level_up(&mut th, &cred).await.unwrap(); + let cred = level_up(&mut th, &cred).await.unwrap(); + let cred = level_up(&mut th, &cred).await.unwrap(); th.context.generate_tp_bridge_infos(); - let mut ba = th.context.ba.lock().unwrap(); - - // Get bucket - let (id, key) = bridge_table::from_scalar(cred.bucket).unwrap(); - let encbuckets = ba.enc_bridge_table(); - let bucket = - bridge_table::BridgeTable::decrypt_bucket(id, &key, encbuckets.get(&id).unwrap()) - .unwrap(); - let bridges = bucket.0; + let bridges = get_bucket(&mut th, &cred).await; // Create a positive report for each bridge in bucket let mut reports = Vec::::new(); @@ -363,7 +474,7 @@ mod tests { bridge.fingerprint, None, &cred, - &ba.lox_pub, + &th.context.ba.lock().unwrap().lox_pub, "ru".to_string(), ); reports.push(report.to_serializable_report()); @@ -377,7 +488,7 @@ mod tests { bridges[0].fingerprint, None, &cred, - &ba.lox_pub, + &th.context.ba.lock().unwrap().lox_pub, "ru".to_string(), ); invalid_report_1.date = invalid_report_1.date + 2; @@ -388,7 +499,7 @@ mod tests { bridges[1].fingerprint, None, &cred, - &ba.lox_pub, + &th.context.ba.lock().unwrap().lox_pub, "xx".to_string(), ); reports.push(invalid_report_2.to_serializable_report()); @@ -404,15 +515,12 @@ mod tests { bridges[2].fingerprint, None, &cred, - &ba.lox_pub, + &th.context.ba.lock().unwrap().lox_pub, "ru".to_string(), ); invalid_report_3.fingerprint = empty_bridgeline_fingerprint; reports.push(invalid_report_3.to_serializable_report()); - // Release lock so BA can be used to verify reports below - drop(ba); - let request = tpc.verifypositive(reports); let response = handle(th.context.clone(), &mut Htables, request) .await