Explorar o código

Make reports per-country, simplify some things

Changes:

- Reports are now associated with country codes.

- report_init maps user pending values to particular buckets and country
  codes, so after initializing, the user does not need to re-send their
  bucket*H each time they do a report action.

- Tests for all the report_* protocols have been consolidated into one
  "test_report_protocols" function in report_submit.rs.
Vecna hai 5 meses
pai
achega
5532a31d48

+ 13 - 2
crates/lox-extensions/src/bridge_table.rs

@@ -429,6 +429,18 @@ impl BridgeTable {
             }
         };
 
+        match self.get_bridge_hashed_fingerprints(bucket) {
+            Ok(bridges) => Ok((bucket, bridges)),
+            Err(e) => Err(e),
+        }
+    }
+
+    /// Helper function for report protocols
+    #[cfg(feature = "bridgeauth")]
+    pub fn get_bridge_hashed_fingerprints(
+        &self,
+        bucket: Scalar,
+    ) -> Result<Vec<[u8; 20]>, CredentialError> {
         let bridges = {
             let (id, _key) = match from_scalar(bucket) {
                 Ok((id, key)) => (id, key),
@@ -452,8 +464,7 @@ impl BridgeTable {
                 ));
             }
         };
-
-        Ok((bucket, bridges))
+        Ok(bridges)
     }
 }
 

+ 29 - 15
crates/lox-extensions/src/mock_auth.rs

@@ -9,7 +9,11 @@ use super::proto::{
 use super::*;
 
 #[cfg(all(test, feature = "bridgeauth"))]
-use crate::{bridge_table::BridgeLine, lox_creds::BucketReachability, report_table::ReportStatus};
+use crate::{
+    bridge_table::BridgeLine,
+    lox_creds::BucketReachability,
+    report_table::{CountryCode, ReportStatus},
+};
 
 #[cfg(all(test, feature = "bridgeauth"))]
 use rand::RngCore;
@@ -299,18 +303,28 @@ impl TestHarness {
         new_cred.unwrap()
     }
 
-    pub fn process_scan_result(&mut self, bridge: &[u8; 20], status: report_table::ReportStatus) {
-        self.ba.report_table.process_scan_result(bridge, status);
+    pub fn process_scan_result(
+        &mut self,
+        bridge: &[u8; 20],
+        cc: CountryCode,
+        status: report_table::ReportStatus,
+    ) {
+        self.ba.report_table.process_scan_result(bridge, cc, status);
     }
 
-    pub fn report_init(&mut self, rng: &mut (impl CryptoRng + RngCore), cred: Lox) -> Lox {
-        let report_init_request = report_init::request(rng, cred);
+    pub fn report_init(
+        &mut self,
+        rng: &mut (impl CryptoRng + RngCore),
+        cred: Lox,
+        cc: CountryCode,
+    ) -> Lox {
+        let report_init_request = report_init::request(rng, cred, cc);
         assert!(
             report_init_request.is_ok(),
             "Report init request should succeed"
         );
-        let (ri_request, ri_client_state) = report_init_request.unwrap();
-        let report_init_response = self.ba.handle_report_init(ri_request);
+        let ((ri_request, ri_client_state), cc, D) = report_init_request.unwrap();
+        let report_init_response = self.ba.handle_report_init(ri_request, cc, D);
         assert!(
             report_init_response.is_ok(),
             "Report init response from server should succeed"
@@ -331,8 +345,8 @@ impl TestHarness {
             report_status_request.is_ok(),
             "Report status request should succeed"
         );
-        let ((rs_request, rs_client_state), D_server) = report_status_request.unwrap();
-        let report_status_response = self.ba.handle_report_status(rs_request, D_server);
+        let (rs_request, rs_client_state) = report_status_request.unwrap();
+        let report_status_response = self.ba.handle_report_status(rs_request);
         assert!(
             report_status_response.is_ok(),
             "Report status response from server should succeed"
@@ -356,11 +370,11 @@ impl TestHarness {
             report_submit_request.is_ok(),
             "Report submit request should succeed"
         );
-        let ((rs_request, rs_client_state), D_server, bridges_being_reported_server) =
+        let ((rs_request, rs_client_state), bridges_being_reported_server) =
             report_submit_request.unwrap();
-        let report_submit_response =
-            self.ba
-                .handle_report_submit(rs_request, D_server, bridges_being_reported_server);
+        let report_submit_response = self
+            .ba
+            .handle_report_submit(rs_request, bridges_being_reported_server);
         assert!(
             report_submit_response.is_ok(),
             "Report submit response from server should succeed"
@@ -382,8 +396,8 @@ impl TestHarness {
             report_resolve_request.is_ok(),
             "Report resolve request should succeed"
         );
-        let ((rr_request, rr_client_state), D_server) = report_resolve_request.unwrap();
-        let report_resolve_response = self.ba.handle_report_resolve(rr_request, D_server);
+        let (rr_request, rr_client_state) = report_resolve_request.unwrap();
+        let report_resolve_response = self.ba.handle_report_resolve(rr_request);
         assert!(
             report_resolve_response.is_ok(),
             "Report resolve response from server should succeed"

+ 52 - 115
crates/lox-extensions/src/proto/report_init.rs

@@ -23,6 +23,10 @@ The user presents their current Lox credential:
   MAX_FALSE_REPORTS
 - pending: revealed to be 0
 
+and D = bucket * H to indicate which bucket they have
+
+and cc, their country code
+
 and a new Lox credential to be issued:
 
 - id: jointly chosen by the user and BA
@@ -40,12 +44,23 @@ and a new Lox credential to be issued:
   Lox credential above
 - pending: chosen randomly by the BA
 
+The BridgeAuthority associates their pending value with their bucket and
+country code, so the user does not need to re-send this information in
+future report_* protocols. This also means that if the user moves to a
+different country, they should report_resolve and then report_init
+again. Also, users must report_resolve before they can perform any
+action that changes their bucket.
+
 */
 #[cfg(feature = "bridgeauth")]
 use super::super::dup_filter::SeenType;
 #[cfg(feature = "bridgeauth")]
 use super::super::BridgeAuth;
-use super::super::{report_table::MAX_FALSE_REPORTS, Scalar, G};
+use super::super::{
+    get_H,
+    report_table::{CountryCode, MAX_FALSE_REPORTS},
+    Scalar, G,
+};
 use super::errors::CredentialError;
 use crate::lox_creds::Lox;
 use cmz::*;
@@ -55,7 +70,7 @@ use sha2::Sha512;
 
 const SESSION_ID: &[u8] = b"report_init";
 
-muCMZProtocol! { report_init<max_false_reports>,
+muCMZProtocol! { report_init<max_false_reports, bucket>,
     L: Lox { id: R, bucket: H, trust_level: H, level_since: H, invites_remaining: H, blockages: H, false_reports: H, pending: R },
     N: Lox { id: J, bucket: H, trust_level: H, level_since: H, invites_remaining: H, blockages: H, false_reports: H, pending: S },
     N.bucket = L.bucket,
@@ -65,12 +80,26 @@ muCMZProtocol! { report_init<max_false_reports>,
     N.blockages = L.blockages,
     N.false_reports = L.false_reports,
     (0..=max_false_reports).contains(L.false_reports),
+
+    // Before checking this proof, we need to determine (from D =
+    // bucket*H submitted in the report) which bucket the user is
+    // claiming to have in their credential. Check that the user's
+    // claimed bucket is correct.
+    L.bucket = bucket,
 }
 
 pub fn request(
     rng: &mut (impl CryptoRng + RngCore),
     L: Lox,
-) -> Result<(report_init::Request, report_init::ClientState), CredentialError> {
+    cc: CountryCode,
+) -> Result<
+    (
+        (report_init::Request, report_init::ClientState),
+        CountryCode,
+        G,
+    ),
+    CredentialError,
+> {
     cmz_group_init(G::hash_from_bytes::<Sha512>(b"CMZ Generator A"));
 
     // Ensure the credential can be correctly shown; that is, the
@@ -79,6 +108,9 @@ pub fn request(
         return Err(CredentialError::CredentialPending);
     }
 
+    let H = get_H();
+    let D = L.bucket.unwrap() * H;
+
     let mut N = Lox::using_pubkey(L.get_pubkey());
     N.bucket = L.bucket;
     N.trust_level = L.trust_level;
@@ -89,9 +121,10 @@ pub fn request(
 
     let params = report_init::Params {
         max_false_reports: MAX_FALSE_REPORTS.into(),
+        bucket: L.bucket.unwrap(),
     };
     match report_init::prepare(rng, SESSION_ID, &L, N, &params) {
-        Ok(req_state) => Ok(req_state),
+        Ok(req_state) => Ok((req_state, cc, D)),
         Err(e) => Err(CredentialError::CMZError(e)),
     }
 }
@@ -101,11 +134,20 @@ impl BridgeAuth {
     pub fn handle_report_init(
         &mut self,
         req: report_init::Request,
+        cc: CountryCode,
+        D: G,
     ) -> Result<report_init::Reply, CredentialError> {
         let mut rng = rand::thread_rng();
         let reqbytes = req.as_bytes();
         let recvreq = report_init::Request::try_from(&reqbytes[..]).unwrap();
 
+        let (bucket, _bridges) = match self.bridge_table.get_bucket_and_bridges(D) {
+            Ok((bucket, bridges)) => (bucket, bridges),
+            Err(e) => {
+                return Err(e);
+            }
+        };
+
         let pending = Scalar::random(&mut rng);
 
         match report_init::handle(
@@ -120,6 +162,7 @@ impl BridgeAuth {
 
                 Ok(report_init::Params {
                     max_false_reports: MAX_FALSE_REPORTS.into(),
+                    bucket: bucket,
                 })
             },
             |L: &Lox, _N: &Lox| {
@@ -129,7 +172,10 @@ impl BridgeAuth {
                 Ok(())
             },
         ) {
-            Ok((response, (_L_issuer, _N_issuer))) => Ok(response),
+            Ok((response, (_L_issuer, _N_issuer))) => {
+                self.report_table.add_user(pending, bucket, cc);
+                Ok(response)
+            }
             Err(e) => Err(CredentialError::CMZError(e)),
         }
     }
@@ -147,113 +193,4 @@ pub fn handle_response(
     }
 }
 
-#[cfg(all(test, feature = "bridgeauth"))]
-mod tests {
-    use crate::mock_auth::TestHarness;
-
-    use crate::proto::{issue_invite, redeem_invite, trust_promotion};
-    use crate::Scalar;
-
-    #[test]
-    fn test_report_init() {
-        let mut th = TestHarness::new();
-        let rng = &mut rand::thread_rng();
-        let invite = th.bdb.invite().unwrap();
-        let mut lox_cred = th.open_invite(rng, &invite);
-        th.verify_lox(&lox_cred);
-        assert_eq!(lox_cred.pending.unwrap(), Scalar::ZERO);
-
-        // Enter pending state
-        lox_cred = th.report_init(rng, lox_cred.clone());
-        th.verify_lox(&lox_cred);
-        let pending_1 = lox_cred.pending.unwrap();
-        assert!(pending_1 != Scalar::ZERO);
-
-        // Resolve pending state
-        lox_cred = th.report_resolve(rng, lox_cred.clone(), 0);
-        th.verify_lox(&lox_cred);
-        assert_eq!(lox_cred.pending.unwrap(), Scalar::ZERO);
-
-        // Enter pending state again
-        lox_cred = th.report_init(rng, lox_cred.clone());
-        th.verify_lox(&lox_cred);
-        let pending_2 = lox_cred.pending.unwrap();
-        assert!(pending_2 != Scalar::ZERO);
-        assert!(pending_1 != pending_2);
-
-        // Attempt to migrate. We should not be able.
-        th.advance_days(
-            (trust_promotion::UNTRUSTED_INTERVAL + 1)
-                .try_into()
-                .unwrap(),
-        );
-        let mig_err = trust_promotion::request(
-            rng,
-            lox_cred.clone(),
-            th.ba.migrationkey_pub.clone(),
-            th.ba.today(),
-        );
-        assert!(mig_err.is_err());
-
-        // Credential should still be valid
-        th.verify_lox(&lox_cred);
-
-        // Resolve pending state
-        lox_cred = th.report_resolve(rng, lox_cred.clone(), 0);
-        th.verify_lox(&lox_cred);
-        assert_eq!(lox_cred.pending.unwrap(), Scalar::ZERO);
-
-        // We should now be able to migrate
-        let mig_cred = th.trust_promotion(rng, lox_cred.clone());
-        lox_cred = th.migration(rng, lox_cred.clone(), mig_cred.clone());
-        th.verify_lox(&lox_cred);
-
-        // Enter pending state again
-        lox_cred = th.report_init(rng, lox_cred.clone());
-        th.verify_lox(&lox_cred);
-        let pending_3 = lox_cred.pending.unwrap();
-        assert!(pending_3 != Scalar::ZERO);
-        assert!(pending_3 != pending_1);
-        assert!(pending_3 != pending_2);
-
-        // We should be able to level up
-        lox_cred = th.level_up(rng, lox_cred.clone());
-        th.verify_lox(&lox_cred);
-        let pending_4 = lox_cred.pending.unwrap();
-        // Pending value should be unchanged
-        assert_eq!(pending_4, pending_3);
-
-        // Level up again
-        lox_cred = th.level_up(rng, lox_cred.clone());
-
-        // We should not be able to invite friends
-        let reachcred = th.reach_cred(lox_cred.clone());
-        let issue_invite_request = issue_invite::request(
-            rng,
-            lox_cred.clone(),
-            reachcred,
-            th.ba.invitation_pub.clone(),
-            th.ba.today(),
-        );
-        assert!(issue_invite_request.is_err());
-
-        // Resolve pending state
-        lox_cred = th.report_resolve(rng, lox_cred.clone(), 0);
-        th.verify_lox(&lox_cred);
-        assert_eq!(lox_cred.pending.unwrap(), Scalar::ZERO);
-
-        // Now, let's invite a friend
-        let ii_resp = th.issue_invite(rng, lox_cred.clone());
-        let issue_invite_cred = ii_resp.0.clone();
-        lox_cred = ii_resp.1.clone();
-        th.verify_lox(&lox_cred);
-        th.verify_invitation(&issue_invite_cred);
-        let redeem_invite_request =
-            redeem_invite::request(rng, issue_invite_cred, th.ba.lox_pub.clone(), th.ba.today());
-        let (redeem_invite_request, redeem_invite_client_state) = redeem_invite_request.unwrap();
-        let redeem_invite_response = th.ba.handle_redeem_invite(redeem_invite_request);
-        let response = redeem_invite_response.unwrap();
-        let r_cred = redeem_invite::handle_response(redeem_invite_client_state, response);
-        th.verify_lox(&r_cred.unwrap());
-    }
-}
+// This function is implicitly tested by report_submit tests.

+ 49 - 25
crates/lox-extensions/src/proto/report_resolve.rs

@@ -14,7 +14,7 @@ the pending value. We will call this new_false_reports.
 
 The user presents their current Lox credential:
 - id: revealed
-- bucket: blinded
+- bucket: blinded (but known to the server)
 - trust_level: blinded
 - level_since: blinded
 - invites_remaining: blinded
@@ -22,8 +22,6 @@ The user presents their current Lox credential:
 - false_reports: blinded
 - pending: revealed
 
-and D = bucket * H to indicate which bucket they have
-
 and a new Lox credential to be issued:
 
 - id: jointly chosen by the user and BA
@@ -44,9 +42,9 @@ and a new Lox credential to be issued:
 */
 #[cfg(feature = "bridgeauth")]
 use super::super::dup_filter::SeenType;
-use super::super::{get_H, Scalar, G};
 #[cfg(feature = "bridgeauth")]
 use super::super::{report_table::ReportStatus, BridgeAuth, MAX_BRIDGES_PER_BUCKET};
+use super::super::{Scalar, G};
 use super::errors::CredentialError;
 use crate::lox_creds::Lox;
 use cmz::*;
@@ -65,19 +63,13 @@ muCMZProtocol! { report_resolve<new_false_reports, bucket>,
     N.invites_remaining = L.invites_remaining,
     N.blockages = L.blockages,
     N.false_reports = L.false_reports + new_false_reports,
-
-    // Before checking this proof, we need to determine (from D =
-    // bucket*H submitted in the report) which bucket the user is
-    // claiming to have in their credential. Check that the user's
-    // claimed bucket is correct.
-    L.bucket = bucket,
 }
 
 pub fn request(
     rng: &mut (impl CryptoRng + RngCore),
     L: Lox,
     new_false_reports: u32, // 0, 1, 2, or 3
-) -> Result<((report_resolve::Request, report_resolve::ClientState), G), CredentialError> {
+) -> Result<(report_resolve::Request, report_resolve::ClientState), CredentialError> {
     cmz_group_init(G::hash_from_bytes::<Sha512>(b"CMZ Generator A"));
 
     // Ensure the credential can be correctly shown; that is, the
@@ -86,9 +78,6 @@ pub fn request(
         return Err(CredentialError::CredentialNotPending);
     }
 
-    let H = get_H();
-    let D = L.bucket.unwrap() * H;
-
     let mut N = Lox::using_pubkey(L.get_pubkey());
     N.bucket = L.bucket;
     N.trust_level = L.trust_level;
@@ -103,7 +92,7 @@ pub fn request(
         bucket: L.bucket.unwrap(),
     };
     match report_resolve::prepare(rng, SESSION_ID, &L, N, &params) {
-        Ok(req_state) => Ok((req_state, D)),
+        Ok(req_state) => Ok(req_state),
         Err(e) => Err(CredentialError::CMZError(e)),
     }
 }
@@ -113,19 +102,11 @@ impl BridgeAuth {
     pub fn handle_report_resolve(
         &mut self,
         req: report_resolve::Request,
-        D: G,
     ) -> Result<report_resolve::Reply, CredentialError> {
         let mut rng = rand::thread_rng();
         let reqbytes = req.as_bytes();
         let recvreq = report_resolve::Request::try_from(&reqbytes[..]).unwrap();
 
-        let (bucket, bridges) = match self.bridge_table.get_bucket_and_bridges(D) {
-            Ok((bucket, bridges)) => (bucket, bridges),
-            Err(e) => {
-                return Err(e);
-            }
-        };
-
         let mut bridges_with_old_reports = Vec::<[u8; 20]>::new();
 
         match report_resolve::handle(
@@ -136,6 +117,29 @@ impl BridgeAuth {
                 L.set_privkey(&self.lox_priv);
                 N.set_privkey(&self.lox_priv);
 
+                let bucket = match self
+                    .report_table
+                    .get_user_bucket(L.pending.unwrap().clone())
+                {
+                    Some(bucket) => bucket,
+                    None => {
+                        return Err(CMZError::RevealAttrMissing(
+                            "pending",
+                            "Pending value is incorrect",
+                        ));
+                    }
+                };
+
+                let bridges = match self.bridge_table.get_bridge_hashed_fingerprints(bucket) {
+                    Ok(bridges) => bridges,
+                    Err(_) => {
+                        return Err(CMZError::RevealAttrMissing(
+                            "bucket",
+                            "Bucket value is incorrect",
+                        ));
+                    }
+                };
+
                 let mut new_false_reports: u32 = 0;
 
                 for i in 0..MAX_BRIDGES_PER_BUCKET {
@@ -177,11 +181,32 @@ impl BridgeAuth {
             },
         ) {
             Ok((response, (L_issuer, _N_issuer))) => {
+                // Get bucket and bridge hashed fingerprints again
+                let bucket = match self
+                    .report_table
+                    .get_user_bucket(L_issuer.pending.unwrap().clone())
+                {
+                    Some(bucket) => bucket,
+                    None => {
+                        return Err(CredentialError::InvalidField(
+                            String::from("pending"),
+                            String::from("Pending value is incorrect"),
+                        ));
+                    }
+                };
+                let bridges = match self.bridge_table.get_bridge_hashed_fingerprints(bucket) {
+                    Ok(bridges) => bridges,
+                    Err(e) => {
+                        return Err(e);
+                    }
+                };
+
                 // Only if everything went okay, remove old reports from this user
                 if self
                     .report_table
                     .remove_old_reports(&bridges, L_issuer.pending.unwrap())
                 {
+                    self.report_table.remove_user(L_issuer.pending.unwrap());
                     Ok(response)
                 } else {
                     // TODO: Fix this error message/type
@@ -208,5 +233,4 @@ pub fn handle_response(
     }
 }
 
-// This function is implicitly tested by report_init and report_submit
-// tests.
+// This function is implicitly tested by report_submit tests.

+ 28 - 27
crates/lox-extensions/src/proto/report_status.rs

@@ -11,7 +11,7 @@ to add to their credential.
 
 The user presents their current Lox credential:
 - id: revealed
-- bucket: blinded
+- bucket: blinded (but known to the server)
 - trust_level: blinded
 - level_since: blinded
 - invites_remaining: blinded
@@ -19,8 +19,6 @@ The user presents their current Lox credential:
 - false_reports: blinded
 - pending: revealed
 
-and D = bucket * H to indicate which bucket they have
-
 and a new Lox credential to be issued:
 
 - id: jointly chosen by the user and BA
@@ -44,9 +42,7 @@ and a new Lox credential to be issued:
 use super::super::dup_filter::SeenType;
 #[cfg(feature = "bridgeauth")]
 use super::super::BridgeAuth;
-use super::super::{
-    bridge_table::MAX_BRIDGES_PER_BUCKET, get_H, report_table::ReportStatus, Scalar, G,
-};
+use super::super::{bridge_table::MAX_BRIDGES_PER_BUCKET, report_table::ReportStatus, Scalar, G};
 use super::errors::CredentialError;
 use crate::lox_creds::Lox;
 use cmz::*;
@@ -65,18 +61,12 @@ muCMZProtocol! { report_status<bucket>,
     N.invites_remaining = L.invites_remaining,
     N.blockages = L.blockages,
     N.false_reports = L.false_reports,
-
-    // Before checking this proof, we need to determine (from D =
-    // bucket*H submitted in the report) which bucket the user is
-    // claiming to have in their credential. Check that the user's
-    // claimed bucket is correct.
-    L.bucket = bucket,
 }
 
 pub fn request(
     rng: &mut (impl CryptoRng + RngCore),
     L: Lox,
-) -> Result<((report_status::Request, report_status::ClientState), G), CredentialError> {
+) -> Result<(report_status::Request, report_status::ClientState), CredentialError> {
     cmz_group_init(G::hash_from_bytes::<Sha512>(b"CMZ Generator A"));
 
     // Ensure the credential can be correctly shown; that is, the
@@ -85,9 +75,6 @@ pub fn request(
         return Err(CredentialError::CredentialNotPending);
     }
 
-    let H = get_H();
-    let D = L.bucket.unwrap() * H;
-
     let mut N = Lox::using_pubkey(L.get_pubkey());
     N.bucket = L.bucket;
     N.trust_level = L.trust_level;
@@ -101,7 +88,7 @@ pub fn request(
         bucket: L.bucket.unwrap(),
     };
     match report_status::prepare(rng, SESSION_ID, &L, N, &params) {
-        Ok(req_state) => Ok((req_state, D)),
+        Ok(req_state) => Ok(req_state),
         Err(e) => Err(CredentialError::CMZError(e)),
     }
 }
@@ -111,20 +98,12 @@ impl BridgeAuth {
     pub fn handle_report_status(
         &mut self,
         req: report_status::Request,
-        D: G,
     ) -> Result<(report_status::Reply, [ReportStatus; MAX_BRIDGES_PER_BUCKET]), CredentialError>
     {
         let mut rng = rand::thread_rng();
         let reqbytes = req.as_bytes();
         let recvreq = report_status::Request::try_from(&reqbytes[..]).unwrap();
 
-        let (bucket, bridges) = match self.bridge_table.get_bucket_and_bridges(D) {
-            Ok((bucket, bridges)) => (bucket, bridges),
-            Err(e) => {
-                return Err(e);
-            }
-        };
-
         let mut result = [ReportStatus::NoReport; 3];
 
         match report_status::handle(
@@ -135,6 +114,29 @@ impl BridgeAuth {
                 L.set_privkey(&self.lox_priv);
                 N.set_privkey(&self.lox_priv);
 
+                let bucket = match self
+                    .report_table
+                    .get_user_bucket(L.pending.unwrap().clone())
+                {
+                    Some(bucket) => bucket,
+                    None => {
+                        return Err(CMZError::RevealAttrMissing(
+                            "pending",
+                            "Pending value is incorrect",
+                        ));
+                    }
+                };
+
+                let bridges = match self.bridge_table.get_bridge_hashed_fingerprints(bucket) {
+                    Ok(bridges) => bridges,
+                    Err(_) => {
+                        return Err(CMZError::RevealAttrMissing(
+                            "bucket",
+                            "Bucket value is incorrect",
+                        ));
+                    }
+                };
+
                 for i in 0..MAX_BRIDGES_PER_BUCKET {
                     result[i] = self
                         .report_table
@@ -170,5 +172,4 @@ pub fn handle_response(
     }
 }
 
-// This function is implicitly tested by report_init and report_submit
-// tests.
+// This function is implicitly tested by report_submit tests.

+ 86 - 67
crates/lox-extensions/src/proto/report_submit.rs

@@ -20,7 +20,7 @@ threshold, the user's report submission will be rejected.
 
 The user presents their current Lox credential:
 - id: revealed
-- bucket: blinded
+- bucket: blinded (but known to the server)
 - trust_level: blinded
 - level_since: blinded
 - invites_remaining: blinded
@@ -28,8 +28,6 @@ The user presents their current Lox credential:
 - false_reports: blinded
 - pending: revealed
 
-and D = bucket * H to indicate which bucket they have
-
 and a new Lox credential to be issued:
 
 - id: jointly chosen by the user and BA
@@ -52,8 +50,7 @@ and a new Lox credential to be issued:
 #[cfg(feature = "bridgeauth")]
 use super::super::dup_filter::SeenType;
 use super::super::{
-    bridge_table::MAX_BRIDGES_PER_BUCKET, get_H, report_table::MAX_FALSE_REPORTS, scalar_u32,
-    Scalar, G,
+    bridge_table::MAX_BRIDGES_PER_BUCKET, report_table::MAX_FALSE_REPORTS, scalar_u32, Scalar, G,
 };
 #[cfg(feature = "bridgeauth")]
 use super::super::{report_table::ReportStatus, BridgeAuth, BridgeLine};
@@ -66,7 +63,7 @@ use sha2::Sha512;
 
 const SESSION_ID: &[u8] = b"report_submit";
 
-muCMZProtocol! { report_submit<max_false_reports, new_false_reports, bucket>,
+muCMZProtocol! { report_submit<max_false_reports, new_false_reports>,
     L: Lox { id: R, bucket: H, trust_level: H, level_since: H, invites_remaining: H, blockages: H, false_reports: H, pending: R },
     N: Lox { id: J, bucket: H, trust_level: H, level_since: H, invites_remaining: H, blockages: H, false_reports: H, pending: I },
     N.bucket = L.bucket,
@@ -76,12 +73,6 @@ muCMZProtocol! { report_submit<max_false_reports, new_false_reports, bucket>,
     N.blockages = L.blockages,
     (0..=max_false_reports).contains(L.false_reports + new_false_reports),
     N.false_reports = L.false_reports + new_false_reports,
-
-    // Before checking this proof, we need to determine (from D =
-    // bucket*H submitted in the report) which bucket the user is
-    // claiming to have in their credential. Check that the user's
-    // claimed bucket is correct.
-    L.bucket = bucket,
 }
 
 pub fn request(
@@ -92,7 +83,6 @@ pub fn request(
 ) -> Result<
     (
         (report_submit::Request, report_submit::ClientState),
-        G,
         [bool; MAX_BRIDGES_PER_BUCKET],
     ),
     CredentialError,
@@ -135,9 +125,6 @@ pub fn request(
         return Err(CredentialError::NoBridgesReported);
     }
 
-    let H = get_H();
-    let D = L.bucket.unwrap() * H;
-
     let mut N = Lox::using_pubkey(L.get_pubkey());
     N.bucket = L.bucket;
     N.trust_level = L.trust_level;
@@ -150,10 +137,9 @@ pub fn request(
     let params = report_submit::Params {
         max_false_reports: MAX_FALSE_REPORTS.into(),
         new_false_reports: new_false_reports.into(),
-        bucket: L.bucket.unwrap(),
     };
     match report_submit::prepare(rng, SESSION_ID, &L, N, &params) {
-        Ok(req_state) => Ok((req_state, D, bridges_being_reported)),
+        Ok(req_state) => Ok((req_state, bridges_being_reported)),
         Err(e) => Err(CredentialError::CMZError(e)),
     }
 }
@@ -163,35 +149,12 @@ impl BridgeAuth {
     pub fn handle_report_submit(
         &mut self,
         req: report_submit::Request,
-        D: G,
         bridges_being_reported: [bool; MAX_BRIDGES_PER_BUCKET],
     ) -> Result<report_submit::Reply, CredentialError> {
         let mut rng = rand::thread_rng();
         let reqbytes = req.as_bytes();
         let recvreq = report_submit::Request::try_from(&reqbytes[..]).unwrap();
 
-        let (bucket, bridges) = match self.bridge_table.get_bucket_and_bridges(D) {
-            Ok((bucket, bridges)) => (bucket, bridges),
-            Err(e) => {
-                return Err(e);
-            }
-        };
-
-        // Check that user indicated at least one non-default bridge to
-        // submit a report for
-        let mut reporting_a_bridge = false;
-        for i in 0..MAX_BRIDGES_PER_BUCKET {
-            if bridges_being_reported[i] {
-                if bridges[i] == BridgeLine::default().get_hashed_fingerprint() {
-                    return Err(CredentialError::NonExistentBridgeReported);
-                }
-                reporting_a_bridge = true;
-            }
-        }
-        if !reporting_a_bridge {
-            return Err(CredentialError::NoBridgesReported);
-        }
-
         let mut bridges_with_old_reports = Vec::<[u8; 20]>::new();
 
         match report_submit::handle(
@@ -202,6 +165,50 @@ impl BridgeAuth {
                 L.set_privkey(&self.lox_priv);
                 N.set_privkey(&self.lox_priv);
 
+                let bucket = match self
+                    .report_table
+                    .get_user_bucket(L.pending.unwrap().clone())
+                {
+                    Some(bucket) => bucket,
+                    None => {
+                        return Err(CMZError::RevealAttrMissing(
+                            "pending",
+                            "Pending value is incorrect",
+                        ));
+                    }
+                };
+
+                let bridges = match self.bridge_table.get_bridge_hashed_fingerprints(bucket) {
+                    Ok(bridges) => bridges,
+                    Err(_) => {
+                        return Err(CMZError::RevealAttrMissing(
+                            "bucket",
+                            "Bucket value is incorrect",
+                        ));
+                    }
+                };
+
+                // Check that user indicated at least one non-default bridge to
+                // submit a report for
+                let mut reporting_a_bridge = false;
+                for i in 0..MAX_BRIDGES_PER_BUCKET {
+                    if bridges_being_reported[i] {
+                        if bridges[i] == BridgeLine::default().get_hashed_fingerprint() {
+                            return Err(CMZError::RevealAttrMissing(
+                                "bridges_being_reported",
+                                "Report submitted for a bridge that does not exist",
+                            ));
+                        }
+                        reporting_a_bridge = true;
+                    }
+                }
+                if !reporting_a_bridge {
+                    return Err(CMZError::RevealAttrMissing(
+                        "bridges_being_reported",
+                        "No bridges are being reported",
+                    ));
+                }
+
                 let mut new_false_reports: u32 = 0;
 
                 for i in 0..MAX_BRIDGES_PER_BUCKET {
@@ -243,7 +250,6 @@ impl BridgeAuth {
                 Ok(report_submit::Params {
                     max_false_reports: MAX_FALSE_REPORTS.into(),
                     new_false_reports: new_false_reports.into(),
-                    bucket: bucket,
                 })
             },
             |L: &Lox, _N: &Lox| {
@@ -290,11 +296,16 @@ mod tests {
     use crate::mock_auth::TestHarness;
 
     use crate::proto::*;
-    use crate::{report_table::ReportStatus, BridgeLine, Scalar, MAX_BRIDGES_PER_BUCKET};
+    use crate::{
+        report_table::{CountryCode, ReportStatus},
+        BridgeLine, Scalar, MAX_BRIDGES_PER_BUCKET,
+    };
     use std::time::SystemTime;
 
     #[test]
-    fn test_report_submit() {
+    fn test_report_protocols() {
+        let cc = CountryCode::RU;
+
         let mut th = TestHarness::new();
         let rng = &mut rand::thread_rng();
         let invite = th.bdb.invite().unwrap();
@@ -303,7 +314,7 @@ mod tests {
         assert_eq!(lox_cred.pending.unwrap(), Scalar::ZERO);
 
         // Enter pending state
-        lox_cred = th.report_init(rng, lox_cred.clone());
+        lox_cred = th.report_init(rng, lox_cred.clone(), cc);
         th.verify_lox(&lox_cred);
         let pending_1 = lox_cred.pending.unwrap();
         assert!(pending_1 != Scalar::ZERO);
@@ -325,11 +336,11 @@ mod tests {
         // report for bridge 1 or 2.
         let report_submit_request =
             report_submit::request(rng, lox_cred.clone(), 0, [false, true, false]);
-        let ((rs_request, _rs_client_state), D_server, bridges_being_reported_server) =
+        let ((rs_request, _rs_client_state), bridges_being_reported_server) =
             report_submit_request.unwrap();
-        let report_submit_response =
-            th.ba
-                .handle_report_submit(rs_request, D_server, bridges_being_reported_server);
+        let report_submit_response = th
+            .ba
+            .handle_report_submit(rs_request, bridges_being_reported_server);
         assert!(report_submit_response.is_err());
 
         // Submit a report for bridge 0
@@ -354,24 +365,25 @@ mod tests {
         // We should be unable to resolve pending state due to pending
         // report
         let report_resolve_request = report_resolve::request(rng, lox_cred.clone(), 0);
-        let ((rr_request, _rr_client_state), D_server) = report_resolve_request.unwrap();
-        let report_resolve_response = th.ba.handle_report_resolve(rr_request, D_server);
+        let (rr_request, _rr_client_state) = report_resolve_request.unwrap();
+        let report_resolve_response = th.ba.handle_report_resolve(rr_request);
         assert!(report_resolve_response.is_err());
 
         // We should be unable to submit another report for bridge 0 due
         // to pending report for bridge 0
         let report_submit_request =
             report_submit::request(rng, lox_cred.clone(), 0, [true, false, false]);
-        let ((rs_request, _rs_client_state), D_server, bridges_being_reported_server) =
+        let ((rs_request, _rs_client_state), bridges_being_reported_server) =
             report_submit_request.unwrap();
-        let report_submit_response =
-            th.ba
-                .handle_report_submit(rs_request, D_server, bridges_being_reported_server);
+        let report_submit_response = th
+            .ba
+            .handle_report_submit(rs_request, bridges_being_reported_server);
         assert!(report_submit_response.is_err());
 
         // Let the report time out
         th.ba.report_table.mark_old_reports_simulated_time(
             &bucket[0].get_hashed_fingerprint(),
+            cc,
             SystemTime::now(),
         );
 
@@ -383,6 +395,7 @@ mod tests {
         // Bridge was not blocked
         th.ba.report_table.process_scan_result(
             &bucket[0].get_hashed_fingerprint(),
+            cc,
             ReportStatus::NotBlocked,
         );
 
@@ -421,6 +434,7 @@ mod tests {
         // Time out
         th.ba.report_table.mark_old_reports_simulated_time(
             &bucket[0].get_hashed_fingerprint(),
+            cc,
             SystemTime::now(),
         );
 
@@ -446,7 +460,7 @@ mod tests {
         assert!(report_submit_request.is_err());
 
         // Enter pending state
-        lox_cred = th.report_init(rng, lox_cred.clone());
+        lox_cred = th.report_init(rng, lox_cred.clone(), cc);
 
         // Submit reports for bridges 0 and 2
         lox_cred = th.report_submit(rng, lox_cred.clone(), 0, [true, false, true]);
@@ -455,16 +469,17 @@ mod tests {
         // there is a pending report for bridge 2)
         let report_submit_request =
             report_submit::request(rng, lox_cred.clone(), 0, [false, true, true]);
-        let ((rs_request, _rs_client_state), D_server, bridges_being_reported_server) =
+        let ((rs_request, _rs_client_state), bridges_being_reported_server) =
             report_submit_request.unwrap();
-        let report_submit_response =
-            th.ba
-                .handle_report_submit(rs_request, D_server, bridges_being_reported_server);
+        let report_submit_response = th
+            .ba
+            .handle_report_submit(rs_request, bridges_being_reported_server);
         assert!(report_submit_response.is_err());
 
         // Bridge 2 is not blocked
         th.ba.report_table.process_scan_result(
             &bucket[2].get_hashed_fingerprint(),
+            cc,
             ReportStatus::NotBlocked,
         );
 
@@ -499,16 +514,20 @@ mod tests {
         );
 
         // Two of them are accessible, and one is blocked
-        th.ba
-            .report_table
-            .process_scan_result(&bucket[0].get_hashed_fingerprint(), ReportStatus::Blocked);
+        th.ba.report_table.process_scan_result(
+            &bucket[0].get_hashed_fingerprint(),
+            cc,
+            ReportStatus::Blocked,
+        );
         th.ba.bridge_blocked(&bucket[0], &mut th.bdb);
         th.ba.report_table.process_scan_result(
             &bucket[1].get_hashed_fingerprint(),
+            cc,
             ReportStatus::NotBlocked,
         );
         th.ba.report_table.process_scan_result(
             &bucket[2].get_hashed_fingerprint(),
+            cc,
             ReportStatus::NotBlocked,
         );
 
@@ -534,11 +553,11 @@ mod tests {
         // Can't just lie about the number of false reports
         let report_submit_request =
             report_submit::request(rng, lox_cred.clone(), 0, [false, true, true]);
-        let ((rs_request, _rs_client_state), D_server, bridges_being_reported_server) =
+        let ((rs_request, _rs_client_state), bridges_being_reported_server) =
             report_submit_request.unwrap();
-        let report_submit_response =
-            th.ba
-                .handle_report_submit(rs_request, D_server, bridges_being_reported_server);
+        let report_submit_response = th
+            .ba
+            .handle_report_submit(rs_request, bridges_being_reported_server);
         assert!(report_submit_response.is_err());
 
         // Resolve

+ 351 - 12
crates/lox-extensions/src/report_table.rs

@@ -18,6 +18,276 @@ const TIMEOUT_PERIOD: Duration = Duration::new(60 * 60 * 12, 0);
 // allowed to submit more reports.
 pub const MAX_FALSE_REPORTS: u32 = 2;
 
+// Country codes present in Tor's GeoIP database
+// List generated with: `cat /usr/share/tor/geoip{,6} | grep -v ^# | grep -o ..$ | sort | uniq | while read cc; do echo "    ${cc},";done | tail -n+2`
+#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)]
+pub enum CountryCode {
+    AB,
+    AD,
+    AE,
+    AF,
+    AG,
+    AI,
+    AL,
+    AM,
+    AN,
+    AO,
+    AP,
+    AQ,
+    AR,
+    AS,
+    AT,
+    AU,
+    AW,
+    AX,
+    AZ,
+    BA,
+    BB,
+    BD,
+    BE,
+    BF,
+    BG,
+    BH,
+    BI,
+    BJ,
+    BL,
+    BM,
+    BN,
+    BO,
+    BP,
+    BQ,
+    BR,
+    BS,
+    BT,
+    BV,
+    BW,
+    BY,
+    BZ,
+    CA,
+    CC,
+    CD,
+    CF,
+    CG,
+    CH,
+    CI,
+    CK,
+    CL,
+    CM,
+    CN,
+    CO,
+    CR,
+    CS,
+    CU,
+    CV,
+    CW,
+    CX,
+    CY,
+    CZ,
+    DD,
+    DE,
+    DJ,
+    DK,
+    DM,
+    DO,
+    DZ,
+    EC,
+    EE,
+    EG,
+    EH,
+    ER,
+    ES,
+    ET,
+    EU,
+    FI,
+    FJ,
+    FK,
+    FM,
+    FO,
+    FQ,
+    FR,
+    GA,
+    GB,
+    GD,
+    GE,
+    GF,
+    GG,
+    GH,
+    GI,
+    GL,
+    GM,
+    GN,
+    GP,
+    GQ,
+    GR,
+    GS,
+    GT,
+    GU,
+    GW,
+    GY,
+    HK,
+    HM,
+    HN,
+    HR,
+    HT,
+    HU,
+    HV,
+    ID,
+    IE,
+    IL,
+    IM,
+    IN,
+    IO,
+    IQ,
+    IR,
+    IS,
+    IT,
+    JE,
+    JM,
+    JO,
+    JP,
+    KE,
+    KG,
+    KH,
+    KI,
+    KM,
+    KN,
+    KP,
+    KR,
+    KW,
+    KY,
+    KZ,
+    LA,
+    LB,
+    LC,
+    LI,
+    LK,
+    LR,
+    LS,
+    LT,
+    LU,
+    LV,
+    LY,
+    MA,
+    MC,
+    MD,
+    ME,
+    MF,
+    MG,
+    MH,
+    MK,
+    ML,
+    MM,
+    MN,
+    MO,
+    MP,
+    MQ,
+    MR,
+    MS,
+    MT,
+    MU,
+    MV,
+    MW,
+    MX,
+    MY,
+    MZ,
+    NA,
+    NC,
+    NE,
+    NF,
+    NG,
+    NI,
+    NL,
+    NO,
+    NP,
+    NR,
+    NU,
+    NZ,
+    OM,
+    OS,
+    PA,
+    PE,
+    PF,
+    PG,
+    PH,
+    PK,
+    PL,
+    PM,
+    PN,
+    PR,
+    PS,
+    PT,
+    PW,
+    PY,
+    QA,
+    RE,
+    RH,
+    RO,
+    RS,
+    RU,
+    RW,
+    SA,
+    SB,
+    SC,
+    SD,
+    SE,
+    SG,
+    SH,
+    SI,
+    SJ,
+    SK,
+    SL,
+    SM,
+    SN,
+    SO,
+    SR,
+    SS,
+    ST,
+    SU,
+    SV,
+    SX,
+    SY,
+    SZ,
+    TC,
+    TD,
+    TF,
+    TG,
+    TH,
+    TJ,
+    TK,
+    TL,
+    TM,
+    TN,
+    TO,
+    TR,
+    TT,
+    TV,
+    TW,
+    TZ,
+    UA,
+    UG,
+    UK,
+    UM,
+    US,
+    UY,
+    UZ,
+    VA,
+    VC,
+    VE,
+    VG,
+    VI,
+    VN,
+    VU,
+    WF,
+    WK,
+    WS,
+    YE,
+    YT,
+    ZA,
+    ZM,
+    ZR,
+    ZW,
+}
+
 #[derive(Clone, Copy, Default, Debug, PartialEq, Serialize, Deserialize)]
 pub enum ReportStatus {
     NotBlocked = 0,
@@ -44,6 +314,22 @@ impl BridgeReportInfo {
     // in the new_reports list but too old to the old_reports list. In
     // other words, handle timeouts.
     fn mark_old_reports(&mut self) {
+        // TODO: If report_resolve_emergency is implemented (resolve
+        // immediately, even if you have pending reports, but increase
+        // your false_reports count to MAX_FALSE_REPORTS + 1 so you
+        // can't submit future reports), this will need to be reworked a
+        // bit. In particular, reports from non-existent users should be
+        // allowed to still exist in the new_reports list, but they
+        // should not be allowed to migrate to the old_reports list. The
+        // challenge is that the list of pending users is managed by the
+        // ReportTable, not the BridgeReportInfo, so this function
+        // doesn't have access to make this change.
+
+        // Don't obsolete reports if a scan has been triggered
+        if self.scan_in_progress {
+            return;
+        }
+
         let current_time = SystemTime::now();
         // new_reports should be ordered by timestamps, so it might be
         // possible to abort early for better efficiency
@@ -129,14 +415,39 @@ impl BridgeReportInfo {
 #[derive(Default, Debug, Serialize, Deserialize)]
 #[cfg(feature = "bridgeauth")]
 pub struct ReportTable {
-    // Map of hashed bridge fingerprint to info about reports for the
-    // bridge
-    data: HashMap<[u8; 20], BridgeReportInfo>,
+    // Map of hashed bridge fingerprint and country code to info about
+    // reports for the bridge from that country
+    data: HashMap<([u8; 20], CountryCode), BridgeReportInfo>,
+    // Map of pending user scalars to their buckets and country codes
+    pending_users: HashMap<Scalar, (Scalar, CountryCode)>,
 }
 
 // Public interface
 #[cfg(feature = "bridgeauth")]
 impl ReportTable {
+    // When the user does report_init, add them to pending_users
+    pub fn add_user(&mut self, pending: Scalar, bucket: Scalar, cc: CountryCode) {
+        self.pending_users.insert(pending, (bucket, cc));
+    }
+
+    // When the user does report_resolve, remove them from pending_users
+    pub fn remove_user(&mut self, pending: Scalar) {
+        let result = self.pending_users.remove(&pending);
+        if result.is_none() {
+            // If the user doesn't exist, just print a message about it
+            eprintln!("Tried to remove a user that didn't exist");
+        }
+    }
+
+    // Get the bucket for a given pending user
+    pub fn get_user_bucket(&self, pending: Scalar) -> Option<Scalar> {
+        if let Some(user_data) = self.pending_users.get(&pending) {
+            Some(user_data.0)
+        } else {
+            None
+        }
+    }
+
     // Process a new report for a particular bridge. Returns true if
     // successful, false otherwise.
     pub fn process_new_report(&mut self, hashed_fingerprint: [u8; 20], pending: Scalar) -> bool {
@@ -144,11 +455,16 @@ impl ReportTable {
             return false;
         }
 
-        if !self.data.contains_key(&hashed_fingerprint) {
+        let Some((_bucket, cc)) = self.pending_users.get(&pending) else {
+            return false;
+        };
+
+        if !self.data.contains_key(&(hashed_fingerprint, *cc)) {
             let new_bridge_report_info = BridgeReportInfo::default();
-            self.data.insert(hashed_fingerprint, new_bridge_report_info);
+            self.data
+                .insert((hashed_fingerprint, *cc), new_bridge_report_info);
         }
-        let bridge_report_info = self.data.get_mut(&hashed_fingerprint).unwrap();
+        let bridge_report_info = self.data.get_mut(&(hashed_fingerprint, *cc)).unwrap();
 
         bridge_report_info.process_new_report(pending)
     }
@@ -157,9 +473,13 @@ impl ReportTable {
     // the given user for those bridges. Return true if successful,
     // false otherwise.
     pub fn remove_old_reports(&mut self, bridges: &[[u8; 20]], pending: Scalar) -> bool {
+        let Some((_bucket, cc)) = self.pending_users.get(&pending) else {
+            return false;
+        };
+
         for bridge in bridges {
             if *bridge != BridgeLine::default().get_hashed_fingerprint() {
-                if let Some(bridge_data) = self.data.get_mut(bridge) {
+                if let Some(bridge_data) = self.data.get_mut(&(*bridge, *cc)) {
                     if !bridge_data.remove_report(pending) {
                         return false;
                     }
@@ -171,8 +491,12 @@ impl ReportTable {
 
     // Get the status of user's report for a bridge
     pub fn get_report_status(&self, bridge: &[u8; 20], pending: Scalar) -> ReportStatus {
+        let Some((_bucket, cc)) = self.pending_users.get(&pending) else {
+            return ReportStatus::NoReport;
+        };
+
         if *bridge != BridgeLine::default().get_hashed_fingerprint() {
-            if let Some(bridge_data) = self.data.get(bridge) {
+            if let Some(bridge_data) = self.data.get(&(*bridge, *cc)) {
                 if bridge_data.old_reports.contains_key(&pending) {
                     return *bridge_data.old_reports.get(&pending).unwrap();
                 }
@@ -190,8 +514,13 @@ impl ReportTable {
 
     // Given the result of a scan, add that information to the bridge
     // info
-    pub fn process_scan_result(&mut self, bridge: &[u8; 20], status: ReportStatus) {
-        if let Some(bridge_data) = self.data.get_mut(bridge) {
+    pub fn process_scan_result(
+        &mut self,
+        bridge: &[u8; 20],
+        cc: CountryCode,
+        status: ReportStatus,
+    ) {
+        if let Some(bridge_data) = self.data.get_mut(&(*bridge, cc)) {
             bridge_data.process_scan_result(status);
         }
     }
@@ -200,8 +529,18 @@ impl ReportTable {
     // means older than the timestamp passed as a parameter. This is for
     // testing, when we need to simulate the passage of time.
     #[cfg(test)]
-    pub fn mark_old_reports_simulated_time(&mut self, bridge: &[u8; 20], current_time: SystemTime) {
-        if let Some(bridge_data) = self.data.get_mut(bridge) {
+    pub fn mark_old_reports_simulated_time(
+        &mut self,
+        bridge: &[u8; 20],
+        cc: CountryCode,
+        current_time: SystemTime,
+    ) {
+        if let Some(bridge_data) = self.data.get_mut(&(*bridge, cc)) {
+            // Don't obsolete reports if a scan has been triggered
+            if bridge_data.scan_in_progress {
+                return;
+            }
+
             // new_reports should be ordered by timestamps, so it might be
             // possible to abort early for better efficiency
             let mut new_reports = Vec::<(SystemTime, Scalar)>::new();