Browse Source

Add tests for reports

Vecna 2 months ago
parent
commit
bc35163084
2 changed files with 371 additions and 0 deletions
  1. 4 0
      src/lib.rs
  2. 367 0
      src/tests.rs

+ 4 - 0
src/lib.rs

@@ -561,3 +561,7 @@ pub async fn report_blockages(
     let resp_str: String = serde_json::from_slice(&buf).unwrap();
     assert_eq!("OK", resp_str);
 }
+
+// Unit tests
+#[cfg(test)]
+mod tests;

+ 367 - 0
src/tests.rs

@@ -0,0 +1,367 @@
+#![allow(non_snake_case)]
+
+use crate::{bridge_verification_info::BridgeVerificationInfo, *};
+use lox_library::{
+    bridge_table::{self, BridgeLine, BridgeTable},
+    cred::Lox,
+    proto::*,
+    scalar_u32, BridgeAuth, BridgeDb,
+};
+
+use base64::{engine::general_purpose, Engine as _};
+use curve25519_dalek::{ristretto::RistrettoBasepointTable, Scalar};
+use rand::RngCore;
+use sha1::{Digest, Sha1};
+use std::{
+    collections::{BTreeMap, HashMap, HashSet},
+    sync::{Arc, Mutex},
+};
+
+struct TestHarness {
+    bdb: BridgeDb,
+    pub ba: BridgeAuth,
+}
+
+impl TestHarness {
+    fn new() -> Self {
+        TestHarness::new_buckets(5, 5)
+    }
+
+    fn new_buckets(num_buckets: u16, hot_spare: u16) -> Self {
+        // Create a BridegDb
+        let mut bdb = BridgeDb::new();
+        // Create a BridgeAuth
+        let mut ba = BridgeAuth::new(bdb.pubkey);
+
+        // Make 3 x num_buckets open invitation bridges, in sets of 3
+        for _ in 0..num_buckets {
+            let bucket = [random(), random(), random()];
+            let _ = ba.add_openinv_bridges(bucket, &mut bdb);
+        }
+        // Add hot_spare more hot spare buckets
+        for _ in 0..hot_spare {
+            let bucket = [random(), random(), random()];
+            let _ = ba.add_spare_bucket(bucket, &mut bdb);
+        }
+        // Create the encrypted bridge table
+        ba.enc_bridge_table();
+
+        Self { bdb, ba }
+    }
+
+    fn advance_days(&mut self, days: u16) {
+        self.ba.advance_days(days);
+    }
+
+    fn get_new_credential(&mut self) -> Lox {
+        let inv = self.bdb.invite().unwrap();
+        let (req, state) = open_invite::request(&inv);
+        let resp = self.ba.handle_open_invite(req).unwrap();
+        let (cred, _bridgeline) =
+            open_invite::handle_response(state, resp, &self.ba.lox_pub).unwrap();
+        cred
+    }
+
+    fn level_up(&mut self, cred: &Lox) -> Lox {
+        let current_level = scalar_u32(&cred.trust_level).unwrap();
+        if current_level == 0 {
+            self.advance_days(trust_promotion::UNTRUSTED_INTERVAL.try_into().unwrap());
+            let (promreq, promstate) =
+                trust_promotion::request(cred, &self.ba.lox_pub, self.ba.today()).unwrap();
+            let promresp = self.ba.handle_trust_promotion(promreq).unwrap();
+            let migcred = trust_promotion::handle_response(promstate, promresp).unwrap();
+            let (migreq, migstate) =
+                migration::request(cred, &migcred, &self.ba.lox_pub, &self.ba.migration_pub)
+                    .unwrap();
+            let migresp = self.ba.handle_migration(migreq).unwrap();
+            let new_cred = migration::handle_response(migstate, migresp, &self.ba.lox_pub).unwrap();
+            new_cred
+        } else {
+            self.advance_days(
+                level_up::LEVEL_INTERVAL[usize::try_from(current_level).unwrap()]
+                    .try_into()
+                    .unwrap(),
+            );
+            let (id, key) = bridge_table::from_scalar(cred.bucket).unwrap();
+            let encbuckets = self.ba.enc_bridge_table();
+            let bucket =
+                bridge_table::BridgeTable::decrypt_bucket(id, &key, encbuckets.get(&id).unwrap())
+                    .unwrap();
+            let reachcred = bucket.1.unwrap();
+            let (lvreq, lvstate) = level_up::request(
+                cred,
+                &reachcred,
+                &self.ba.lox_pub,
+                &self.ba.reachability_pub,
+                self.ba.today(),
+            )
+            .unwrap();
+            let lvresp = self.ba.handle_level_up(lvreq).unwrap();
+            let new_cred = level_up::handle_response(lvstate, lvresp, &self.ba.lox_pub).unwrap();
+            new_cred
+        }
+    }
+
+    fn get_bucket(&mut self, cred: &Lox) -> [BridgeLine; bridge_table::MAX_BRIDGES_PER_BUCKET] {
+        let (id, key) = bridge_table::from_scalar(cred.bucket).unwrap();
+        let encbuckets = self.ba.enc_bridge_table();
+        let bucket =
+            bridge_table::BridgeTable::decrypt_bucket(id, &key, encbuckets.get(&id).unwrap())
+                .unwrap();
+        bucket.0
+    }
+}
+
+pub fn random() -> BridgeLine {
+    let mut rng = rand::thread_rng();
+    let mut res: BridgeLine = BridgeLine::default();
+    // Pick a random 4-byte address
+    let mut addr: [u8; 4] = [0; 4];
+    rng.fill_bytes(&mut addr);
+    // If the leading byte is 224 or more, that's not a valid IPv4
+    // address.  Choose an IPv6 address instead (but don't worry too
+    // much about it being well formed).
+    if addr[0] >= 224 {
+        rng.fill_bytes(&mut res.addr);
+    } else {
+        // Store an IPv4 address as a v4-mapped IPv6 address
+        res.addr[10] = 255;
+        res.addr[11] = 255;
+        res.addr[12..16].copy_from_slice(&addr);
+    };
+    let ports: [u16; 4] = [443, 4433, 8080, 43079];
+    let portidx = (rng.next_u32() % 4) as usize;
+    res.port = ports[portidx];
+    res.uid_fingerprint = rng.next_u64();
+    rng.fill_bytes(&mut res.fingerprint);
+    let mut cert: [u8; 52] = [0; 52];
+    rng.fill_bytes(&mut cert);
+    let infostr: String = format!(
+        "obfs4 cert={}, iat-mode=0",
+        general_purpose::STANDARD_NO_PAD.encode(cert)
+    );
+    res.info[..infostr.len()].copy_from_slice(infostr.as_bytes());
+    res
+}
+
+#[test]
+fn test_negative_reports() {
+    let mut th = TestHarness::new();
+
+    // Get new level 1 credential
+    let cred = th.get_new_credential();
+    let cred = th.level_up(&cred);
+
+    let bridges = th.get_bucket(&cred);
+
+    // Create BridgeVerificationInfo for each bridge
+    let mut buckets = HashSet::<Scalar>::new();
+    buckets.insert(cred.bucket);
+    let bridge_info_1 = BridgeVerificationInfo {
+        bridge_line: bridges[0],
+        buckets: buckets.clone(),
+        pubkey: None,
+    };
+    let bridge_info_2 = BridgeVerificationInfo {
+        bridge_line: bridges[1],
+        buckets: buckets.clone(),
+        pubkey: None,
+    };
+    let bridge_info_3 = BridgeVerificationInfo {
+        bridge_line: bridges[2],
+        buckets: buckets.clone(),
+        pubkey: None,
+    };
+
+    // Create reports
+    let report_1 =
+        NegativeReport::from_bridgeline(bridges[0], "ru".to_string(), BridgeDistributor::Lox);
+    let report_2 =
+        NegativeReport::from_lox_bucket(bridges[1].fingerprint, cred.bucket, "ru".to_string());
+    let report_3 =
+        NegativeReport::from_lox_credential(bridges[2].fingerprint, cred, "ru".to_string());
+
+    // Verify reports
+    assert!(report_1.verify(&bridge_info_1));
+    assert!(report_2.verify(&bridge_info_2));
+    assert!(report_3.verify(&bridge_info_3));
+
+    // Check that deserialization fails under invalid conditions
+
+    // Date in the future
+    let mut invalid_report_1 =
+        NegativeReport::from_bridgeline(bridges[0], "ru".to_string(), BridgeDistributor::Lox)
+            .to_serializable_report();
+    invalid_report_1.date = invalid_report_1.date + 2;
+
+    // Invalid country code
+    let invalid_report_2 =
+        NegativeReport::from_bridgeline(bridges[1], "xx".to_string(), BridgeDistributor::Lox)
+            .to_serializable_report();
+
+    assert!(invalid_report_1.to_report().is_err());
+    assert!(invalid_report_2.to_report().is_err());
+
+    // Check that verification fails with incorrect data
+
+    // Incorrect BridgeLine hash
+    let invalid_report_3 = NegativeReport::new(
+        bridges[0].fingerprint,
+        ProofOfBridgeKnowledge::HashOfBridgeLine(HashOfBridgeLine::new(&BridgeLine::default())),
+        "ru".to_string(),
+        BridgeDistributor::Lox,
+    );
+
+    // Incorrect bucket hash
+    let invalid_report_4 = NegativeReport::new(
+        bridges[1].fingerprint,
+        ProofOfBridgeKnowledge::HashOfBucket(HashOfBucket::new(&Scalar::ZERO)),
+        "ru".to_string(),
+        BridgeDistributor::Lox,
+    );
+
+    assert!(!invalid_report_3.verify(&bridge_info_1));
+    assert!(!invalid_report_4.verify(&bridge_info_2));
+}
+
+#[test]
+fn test_positive_reports() {
+    let mut th = TestHarness::new();
+
+    // Get new level 3 credential
+    let cred = th.get_new_credential();
+    let cred = th.level_up(&cred);
+    let cred = th.level_up(&cred);
+    let cred = th.level_up(&cred);
+
+    let bridges = th.get_bucket(&cred);
+
+    // Create BridgeVerificationInfo for each bridge
+    let mut buckets = HashSet::<Scalar>::new();
+    buckets.insert(cred.bucket);
+    let bridge_info_1 = BridgeVerificationInfo {
+        bridge_line: bridges[0],
+        buckets: buckets.clone(),
+        pubkey: None,
+    };
+    let bridge_info_2 = BridgeVerificationInfo {
+        bridge_line: bridges[1],
+        buckets: buckets.clone(),
+        pubkey: None,
+    };
+    let bridge_info_3 = BridgeVerificationInfo {
+        bridge_line: bridges[2],
+        buckets: buckets.clone(),
+        pubkey: None,
+    };
+
+    // Create reports
+    let report_1 = PositiveReport::from_lox_credential(
+        bridges[0].fingerprint,
+        None,
+        &cred,
+        &th.ba.lox_pub,
+        "ru".to_string(),
+    )
+    .unwrap();
+    let report_2 = PositiveReport::from_lox_credential(
+        bridges[1].fingerprint,
+        None,
+        &cred,
+        &th.ba.lox_pub,
+        "ru".to_string(),
+    )
+    .unwrap();
+    let report_3 = PositiveReport::from_lox_credential(
+        bridges[2].fingerprint,
+        None,
+        &cred,
+        &th.ba.lox_pub,
+        "ru".to_string(),
+    )
+    .unwrap();
+
+    // Compute Htable
+    let H = lox_library::proto::positive_report::compute_H(report_1.date);
+    let Htable = RistrettoBasepointTable::create(&H);
+
+    assert!(report_1.verify(&mut th.ba, &bridge_info_1, &Htable));
+    assert!(report_2.verify(&mut th.ba, &bridge_info_2, &Htable));
+    assert!(report_3.verify(&mut th.ba, &bridge_info_3, &Htable));
+
+    // Check that user cannot use credential for other bridge
+
+    // Get new credential
+    let cred_2 = th.get_new_credential();
+    let bridges_2 = th.get_bucket(&cred_2);
+
+    let mut buckets_2 = HashSet::<Scalar>::new();
+    buckets_2.insert(cred_2.bucket);
+    let bridge_info_4 = BridgeVerificationInfo {
+        bridge_line: bridges_2[0],
+        buckets: buckets_2.clone(),
+        pubkey: None,
+    };
+
+    // Use new credential to create positive report even we don't trust it
+    let invalid_report_1 = PositiveReport::from_lox_credential(
+        bridges_2[0].fingerprint,
+        None,
+        &cred_2,
+        &th.ba.lox_pub,
+        "ru".to_string(),
+    );
+
+    // Use first credential for bridge from second bucket
+    let invalid_report_2 = PositiveReport::from_lox_credential(
+        bridges_2[0].fingerprint,
+        None,
+        &cred,
+        &th.ba.lox_pub,
+        "ru".to_string(),
+    );
+
+    // Use second credential for bridge from first bucket
+    let invalid_report_3 = PositiveReport::from_lox_credential(
+        bridges[0].fingerprint,
+        None,
+        &cred_2,
+        &th.ba.lox_pub,
+        "ru".to_string(),
+    );
+
+    // Check that all of these fail
+    assert!(invalid_report_1.is_err());
+    assert!(!invalid_report_2
+        .unwrap()
+        .verify(&mut th.ba, &bridge_info_4, &Htable));
+    assert!(invalid_report_3.is_err());
+
+    // Check that deserialization fails under invalid conditions
+
+    // Date in the future
+    let mut invalid_report_4 = PositiveReport::from_lox_credential(
+        bridges[0].fingerprint,
+        None,
+        &cred,
+        &th.ba.lox_pub,
+        "ru".to_string(),
+    )
+    .unwrap()
+    .to_serializable_report();
+    invalid_report_4.date = invalid_report_4.date + 2;
+
+    // Invalid country code
+    let invalid_report_5 = PositiveReport::from_lox_credential(
+        bridges[0].fingerprint,
+        None,
+        &cred,
+        &th.ba.lox_pub,
+        "xx".to_string(),
+    )
+    .unwrap()
+    .to_serializable_report();
+
+    assert!(invalid_report_4.to_report().is_err());
+    assert!(invalid_report_5.to_report().is_err());
+}