Endpoint for TP system to report bridges are blocked

This commit is contained in:
Vecna 2024-03-15 19:31:18 -04:00
parent 07f0e38a40
commit 94dc1c063c
2 changed files with 173 additions and 37 deletions

View File

@ -794,6 +794,34 @@ impl LoxServerContext {
// Troll Patrol-related tasks // Troll Patrol-related tasks
// Troll Patrol reports that a bridge is blocked somewhere
pub fn report_blocked_bridges(self, request: Bytes) -> Response<Body> {
let blocked_bridges: HashMap<String, HashSet<String>> =
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 // Verify one negative report, return true if verification succeeds
pub fn verify_negative_report(&self, report: NegativeReport) -> bool { pub fn verify_negative_report(&self, report: NegativeReport) -> bool {
match self match self

View File

@ -18,6 +18,10 @@ pub async fn handle(
.body(Body::from("Allow POST")) .body(Body::from("Allow POST"))
.unwrap()), .unwrap()),
_ => match (req.method(), req.uri().path()) { _ => 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>({ (&Method::POST, "/verifynegative") => 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_negative_reports(bytes) cloned_context.verify_negative_reports(bytes)
@ -49,6 +53,7 @@ mod tests {
proto::*, proto::*,
scalar_u32, BridgeAuth, BridgeDb, scalar_u32, BridgeAuth, BridgeDb,
}; };
use lox_zkp::ProofError;
use rand::RngCore; use rand::RngCore;
use sha1::{Digest, Sha1}; use sha1::{Digest, Sha1};
use std::{ use std::{
@ -65,6 +70,8 @@ mod tests {
use super::*; use super::*;
trait TpClient { trait TpClient {
fn reportblocked(&self, blocked_bridges: HashMap<String, HashSet<String>>)
-> Request<Body>;
fn verifynegative(&self, reports: BTreeMap<String, u32>) -> Request<Body>; fn verifynegative(&self, reports: BTreeMap<String, u32>) -> Request<Body>;
fn verifypositive(&self, reports: Vec<SerializablePositiveReport>) -> Request<Body>; fn verifypositive(&self, reports: Vec<SerializablePositiveReport>) -> Request<Body>;
} }
@ -72,6 +79,18 @@ mod tests {
struct TpClientMock {} struct TpClientMock {}
impl TpClient for TpClientMock { impl TpClient for TpClientMock {
fn reportblocked(
&self,
blocked_bridges: HashMap<String, HashSet<String>>,
) -> Request<Body> {
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<String, u32>) -> Request<Body> { fn verifynegative(&self, reports: BTreeMap<String, u32>) -> Request<Body> {
let req = serde_json::to_string(&reports).unwrap(); let req = serde_json::to_string(&reports).unwrap();
Request::builder() Request::builder()
@ -165,6 +184,19 @@ mod tests {
String::from_utf8(body_bytes.to_vec()).unwrap() 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 { async fn get_new_credential(th: &mut TestHarness) -> Lox {
let inv = th.context.db.lock().unwrap().invite().unwrap(); let inv = th.context.db.lock().unwrap().invite().unwrap();
let (req, state) = open_invite::request(&inv); let (req, state) = open_invite::request(&inv);
@ -181,7 +213,7 @@ mod tests {
cred cred
} }
async fn level_up(th: &mut TestHarness, cred: &Lox) -> Lox { async fn level_up(th: &mut TestHarness, cred: &Lox) -> Result<Lox, ProofError> {
let current_level = scalar_u32(&cred.trust_level).unwrap(); let current_level = scalar_u32(&cred.trust_level).unwrap();
if current_level == 0 { if current_level == 0 {
th.context th.context
@ -189,13 +221,13 @@ mod tests {
let mut ba = th.context.ba.lock().unwrap(); let mut ba = th.context.ba.lock().unwrap();
let (promreq, promstate) = let (promreq, promstate) =
trust_promotion::request(cred, &ba.lox_pub, ba.today()).unwrap(); trust_promotion::request(cred, &ba.lox_pub, ba.today()).unwrap();
let promresp = ba.handle_trust_promotion(promreq).unwrap(); let promresp = ba.handle_trust_promotion(promreq)?;
let migcred = trust_promotion::handle_response(promstate, promresp).unwrap(); let migcred = trust_promotion::handle_response(promstate, promresp)?;
let (migreq, migstate) = let (migreq, migstate) =
migration::request(cred, &migcred, &ba.lox_pub, &ba.migration_pub).unwrap(); 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(); let new_cred = migration::handle_response(migstate, migresp, &ba.lox_pub).unwrap();
new_cred Ok(new_cred)
} else { } else {
th.context.advance_days_test( th.context.advance_days_test(
level_up::LEVEL_INTERVAL[usize::try_from(current_level).unwrap()] level_up::LEVEL_INTERVAL[usize::try_from(current_level).unwrap()]
@ -208,7 +240,11 @@ mod tests {
let bucket = let bucket =
bridge_table::BridgeTable::decrypt_bucket(id, &key, encbuckets.get(&id).unwrap()) bridge_table::BridgeTable::decrypt_bucket(id, &key, encbuckets.get(&id).unwrap())
.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( let (lvreq, lvstate) = level_up::request(
cred, cred,
&reachcred, &reachcred,
@ -217,12 +253,103 @@ mod tests {
ba.today(), ba.today(),
) )
.unwrap(); .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(); 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::<u32, RistrettoBasepointTable>::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<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
}
// 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] #[tokio::test]
async fn test_negative_reports() { async fn test_negative_reports() {
let mut th = TestHarness::new(); let mut th = TestHarness::new();
@ -231,19 +358,11 @@ mod tests {
// Get new level 1 credential // Get new level 1 credential
let cred = get_new_credential(&mut th).await; 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(); th.context.generate_tp_bridge_infos();
let mut ba = th.context.ba.lock().unwrap(); let bridges = get_bucket(&mut th, &cred).await;
// 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;
// Create random number of negative reports for each bridge in bucket // Create random number of negative reports for each bridge in bucket
let mut rng = rand::thread_rng(); let mut rng = rand::thread_rng();
@ -340,21 +459,13 @@ mod tests {
// Get new level 3 credential // Get new level 3 credential
let cred = get_new_credential(&mut th).await; 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();
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; let cred = level_up(&mut th, &cred).await.unwrap();
th.context.generate_tp_bridge_infos(); th.context.generate_tp_bridge_infos();
let mut ba = th.context.ba.lock().unwrap(); let bridges = get_bucket(&mut th, &cred).await;
// 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;
// Create a positive report for each bridge in bucket // Create a positive report for each bridge in bucket
let mut reports = Vec::<SerializablePositiveReport>::new(); let mut reports = Vec::<SerializablePositiveReport>::new();
@ -363,7 +474,7 @@ mod tests {
bridge.fingerprint, bridge.fingerprint,
None, None,
&cred, &cred,
&ba.lox_pub, &th.context.ba.lock().unwrap().lox_pub,
"ru".to_string(), "ru".to_string(),
); );
reports.push(report.to_serializable_report()); reports.push(report.to_serializable_report());
@ -377,7 +488,7 @@ mod tests {
bridges[0].fingerprint, bridges[0].fingerprint,
None, None,
&cred, &cred,
&ba.lox_pub, &th.context.ba.lock().unwrap().lox_pub,
"ru".to_string(), "ru".to_string(),
); );
invalid_report_1.date = invalid_report_1.date + 2; invalid_report_1.date = invalid_report_1.date + 2;
@ -388,7 +499,7 @@ mod tests {
bridges[1].fingerprint, bridges[1].fingerprint,
None, None,
&cred, &cred,
&ba.lox_pub, &th.context.ba.lock().unwrap().lox_pub,
"xx".to_string(), "xx".to_string(),
); );
reports.push(invalid_report_2.to_serializable_report()); reports.push(invalid_report_2.to_serializable_report());
@ -404,15 +515,12 @@ mod tests {
bridges[2].fingerprint, bridges[2].fingerprint,
None, None,
&cred, &cred,
&ba.lox_pub, &th.context.ba.lock().unwrap().lox_pub,
"ru".to_string(), "ru".to_string(),
); );
invalid_report_3.fingerprint = empty_bridgeline_fingerprint; invalid_report_3.fingerprint = empty_bridgeline_fingerprint;
reports.push(invalid_report_3.to_serializable_report()); 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 request = tpc.verifypositive(reports);
let response = handle(th.context.clone(), &mut Htables, request) let response = handle(th.context.clone(), &mut Htables, request)
.await .await