Browse Source

API for adding bridges and marking them as unreachable

Ian Goldberg 2 years ago
parent
commit
0e5876a939
6 changed files with 276 additions and 45 deletions
  1. 24 11
      src/bridge_table.rs
  2. 150 13
      src/lib.rs
  3. 31 0
      src/migration_table.rs
  4. 1 1
      src/proto/trust_promotion.rs
  5. 66 19
      src/tests.rs
  6. 4 1
      tests/tests.rs

+ 24 - 11
src/bridge_table.rs

@@ -18,7 +18,7 @@ use curve25519_dalek::ristretto::CompressedRistretto;
 use curve25519_dalek::ristretto::RistrettoBasepointTable;
 use curve25519_dalek::scalar::Scalar;
 use rand::RngCore;
-use std::collections::HashSet;
+use std::collections::{HashMap, HashSet};
 use std::convert::TryInto;
 use subtle::ConstantTimeEq;
 
@@ -92,7 +92,7 @@ impl BridgeLine {
     /// credential if appropriate
     pub fn bucket_encode(
         bucket: &[BridgeLine; MAX_BRIDGES_PER_BUCKET],
-        reachable: &HashSet<BridgeLine>,
+        reachable: &HashMap<BridgeLine, Vec<(u32, usize)>>,
         today: u32,
         bucket_attr: &Scalar,
         reachability_priv: &IssuerPrivKey,
@@ -102,7 +102,7 @@ impl BridgeLine {
         let mut num_reachable: usize = 0;
         for bridge in bucket {
             res[pos..pos + BRIDGE_BYTES].copy_from_slice(&bridge.encode());
-            if reachable.contains(bridge) {
+            if reachable.contains_key(bridge) {
                 num_reachable += 1;
             }
             pos += BRIDGE_BYTES;
@@ -208,7 +208,13 @@ pub struct BridgeTable {
     pub keys: Vec<[u8; 16]>,
     pub buckets: Vec<[BridgeLine; MAX_BRIDGES_PER_BUCKET]>,
     pub encbuckets: Vec<[u8; ENC_BUCKET_BYTES]>,
-    pub reachable: HashSet<BridgeLine>,
+    /// Individual bridges that are reachable
+    pub reachable: HashMap<BridgeLine, Vec<(u32, usize)>>,
+    /// bucket ids of "hot spare" buckets.  These buckets are not handed
+    /// to users, nor do they have any Migration credentials pointing to
+    /// them.  When a new Migration credential is needed, a bucket is
+    /// removed from this set and used for that purpose.
+    pub spares: HashSet<u32>,
     /// The date the buckets were last encrypted to make the encbucket.
     ///
     /// The encbucket must be rebuilt each day so that the Bucket
@@ -225,20 +231,27 @@ impl BridgeTable {
         self.buckets.len()
     }
 
-    /// Append a new bucket to the bridge table
-    pub fn new_bucket(&mut self, bucket: [BridgeLine; MAX_BRIDGES_PER_BUCKET]) {
+    /// Append a new bucket to the bridge table, returning its index
+    pub fn new_bucket(&mut self, bucket: &[BridgeLine; MAX_BRIDGES_PER_BUCKET]) -> u32 {
         // Pick a random key to encrypt this bucket
         let mut rng = rand::thread_rng();
         let mut key: [u8; 16] = [0; 16];
         rng.fill_bytes(&mut key);
         self.keys.push(key);
-        self.buckets.push(bucket);
+        self.buckets.push(*bucket);
+        let bucketnum: u32 = (self.buckets.len() - 1).try_into().unwrap();
         // Mark the new bridges as available
-        for b in bucket.iter() {
+        for (i, b) in bucket.iter().enumerate() {
             if b.port > 0 {
-                self.reachable.insert(*b);
+                if let Some(v) = self.reachable.get_mut(b) {
+                    v.push((bucketnum, i));
+                } else {
+                    let v = vec![(bucketnum, i)];
+                    self.reachable.insert(*b, v);
+                }
             }
         }
+        bucketnum
     }
 
     /// Create the vector of encrypted buckets from the keys and buckets
@@ -321,7 +334,7 @@ mod tests {
         for _ in 0..20 {
             let bucket: [BridgeLine; 3] =
                 [BridgeLine::random(), Default::default(), Default::default()];
-            btable.new_bucket(bucket);
+            btable.new_bucket(&bucket);
         }
         // And 20 more with three random bridges each
         for _ in 0..20 {
@@ -330,7 +343,7 @@ mod tests {
                 BridgeLine::random(),
                 BridgeLine::random(),
             ];
-            btable.new_bucket(bucket);
+            btable.new_bucket(&bucket);
         }
         let today: u32 = time::OffsetDateTime::now_utc()
             .date()

+ 150 - 13
src/lib.rs

@@ -25,7 +25,7 @@ pub mod migration_table;
 use sha2::Sha512;
 
 use rand::rngs::OsRng;
-use rand::RngCore;
+use rand::Rng;
 use std::convert::{TryFrom, TryInto};
 
 use curve25519_dalek::constants as dalek_constants;
@@ -38,6 +38,13 @@ use curve25519_dalek::traits::IsIdentity;
 use ed25519_dalek::{Keypair, PublicKey, Signature, SignatureError, Signer, Verifier};
 use subtle::ConstantTimeEq;
 
+use std::collections::HashSet;
+
+use bridge_table::{
+    BridgeLine, BridgeTable, ENC_BUCKET_BYTES, MAX_BRIDGES_PER_BUCKET, MIN_BUCKET_REACHABILITY,
+};
+use migration_table::{MigrationTable, MigrationType};
+
 use lazy_static::lazy_static;
 
 lazy_static! {
@@ -104,8 +111,8 @@ pub struct BridgeDb {
     keypair: Keypair,
     /// The public key for verifying open invitations
     pub pubkey: PublicKey,
-    /// The number of open-invitation buckets
-    num_openinv_buckets: u32,
+    /// The set of open-invitation buckets
+    openinv_buckets: HashSet<u32>,
 }
 
 /// An open invitation is a [u8; OPENINV_LENGTH] where the first 32
@@ -119,17 +126,27 @@ pub const OPENINV_LENGTH: usize = 32 // the length of the random
 
 impl BridgeDb {
     /// Create the BridgeDb.
-    pub fn new(num_openinv_buckets: u32) -> Self {
+    pub fn new() -> Self {
         let mut csprng = OsRng {};
         let keypair = Keypair::generate(&mut csprng);
         let pubkey = keypair.public;
         Self {
             keypair,
             pubkey,
-            num_openinv_buckets,
+            openinv_buckets: Default::default(),
         }
     }
 
+    /// Insert an open-invitation bucket into the set
+    pub fn insert_openinv(&mut self, bucket: u32) {
+        self.openinv_buckets.insert(bucket);
+    }
+
+    /// Remove an open-invitation bucket from the set
+    pub fn remove_openinv(&mut self, bucket: u32) {
+        self.openinv_buckets.remove(&bucket);
+    }
+
     /// Produce an open invitation.  In this example code, we just
     /// choose a random open-invitation bucket.
     pub fn invite(&self) -> [u8; OPENINV_LENGTH] {
@@ -138,9 +155,10 @@ impl BridgeDb {
         // Choose a random invitation id (a Scalar) and serialize it
         let id = Scalar::random(&mut rng);
         res[0..32].copy_from_slice(&id.to_bytes());
-        // Choose a random bucket number (mod num_openinv_buckets) and
-        // serialize it
-        let bucket_num = rng.next_u32() % self.num_openinv_buckets;
+        // Choose a random bucket number (from the set of open
+        // invitation buckets) and serialize it
+        let openinv_vec: Vec<&u32> = self.openinv_buckets.iter().collect();
+        let bucket_num = *openinv_vec[rng.gen_range(0, openinv_vec.len())];
         res[32..(32 + 4)].copy_from_slice(&bucket_num.to_le_bytes());
         // Sign the first 36 bytes and serialize it
         let sig = self.keypair.sign(&res[0..(32 + 4)]);
@@ -171,6 +189,12 @@ impl BridgeDb {
     }
 }
 
+impl Default for BridgeDb {
+    fn default() -> Self {
+        Self::new()
+    }
+}
+
 /// The bridge authority.  This will typically be a singleton object.
 #[derive(Debug)]
 pub struct BridgeAuth {
@@ -199,10 +223,11 @@ pub struct BridgeAuth {
     pub bridgedb_pub: PublicKey,
 
     /// The bridge table
-    bridge_table: bridge_table::BridgeTable,
+    bridge_table: BridgeTable,
 
-    /// The migration table
-    migration_table: migration_table::MigrationTable,
+    /// The migration tables
+    trustup_migration_table: MigrationTable,
+    blockage_migration_table: MigrationTable,
 
     /// Duplicate filter for open invitations
     openinv_filter: dup_filter::DupFilter<Scalar>,
@@ -245,7 +270,8 @@ impl BridgeAuth {
             invitation_pub,
             bridgedb_pub,
             bridge_table: Default::default(),
-            migration_table: Default::default(),
+            trustup_migration_table: MigrationTable::new(MigrationType::TrustUpgrade),
+            blockage_migration_table: MigrationTable::new(MigrationType::Blockage),
             openinv_filter: Default::default(),
             id_filter: Default::default(),
             inv_id_filter: Default::default(),
@@ -254,6 +280,117 @@ impl BridgeAuth {
         }
     }
 
+    /// Insert a set of open invitation bridges.
+    ///
+    /// Each of the bridges will be given its own open invitation
+    /// bucket, and the BridgeDb will be informed.  A single bucket
+    /// containing all of the bridges will also be created, with a trust
+    /// upgrade migration from each of the single-bridge buckets.
+    pub fn add_openinv_bridges(
+        &mut self,
+        bridges: [BridgeLine; MAX_BRIDGES_PER_BUCKET],
+        bdb: &mut BridgeDb,
+    ) {
+        let bnum = self.bridge_table.new_bucket(&bridges);
+        let mut single = [BridgeLine::default(); MAX_BRIDGES_PER_BUCKET];
+        for b in bridges.iter() {
+            single[0] = *b;
+            let snum = self.bridge_table.new_bucket(&single);
+            bdb.insert_openinv(snum);
+            println!("Adding {} -> {}", snum, bnum);
+            self.trustup_migration_table.table.insert(snum, bnum);
+        }
+    }
+
+    /// Insert a hot spare bucket of bridges
+    pub fn add_spare_bucket(&mut self, bucket: [BridgeLine; MAX_BRIDGES_PER_BUCKET]) {
+        let bnum = self.bridge_table.new_bucket(&bucket);
+        self.bridge_table.spares.insert(bnum);
+    }
+
+    /// Mark a bridge as unreachable
+    ///
+    /// This bridge will be removed from each of the buckets that
+    /// contains it.  If any of those are open-invitation buckets, the
+    /// trust upgrade migration for that bucket will be removed and the
+    /// BridgeDb will be informed to stop handing out that bridge.  If
+    /// any of those are trusted buckets where the number of reachable
+    /// bridges has fallen below the threshold, a blockage migration
+    /// from that bucket to a spare bucket will be added, and the spare
+    /// bucket will be removed from the list of hot spares.  In
+    /// addition, if the blocked bucket was the _target_ of a blockage
+    /// migration, change the target to the new (formerly spare) bucket.
+    /// Returns true if sucessful, or false if it needed a hot spare but
+    /// there was none available.
+    pub fn bridge_unreachable(&mut self, bridge: &BridgeLine, bdb: &mut BridgeDb) -> bool {
+        let mut res: bool = true;
+        let positions = self.bridge_table.reachable.get(bridge);
+        if let Some(v) = positions {
+            for (bucketnum, offset) in v.iter() {
+                // Count how many bridges in this bucket are reachable
+                let numreachable = self.bridge_table.buckets[*bucketnum as usize]
+                    .iter()
+                    .filter(|br| self.bridge_table.reachable.get(br).is_some())
+                    .count();
+
+                // Remove the bridge from the bucket
+                assert!(self.bridge_table.buckets[*bucketnum as usize][*offset] == *bridge);
+                self.bridge_table.buckets[*bucketnum as usize][*offset] = BridgeLine::default();
+
+                // Is this bucket an open-invitation bucket?
+                if bdb.openinv_buckets.contains(bucketnum) {
+                    bdb.openinv_buckets.remove(bucketnum);
+                    self.trustup_migration_table.table.remove(bucketnum);
+                    continue;
+                }
+
+                // Does this removal cause the bucket to go below the
+                // threshold?
+                if numreachable != MIN_BUCKET_REACHABILITY {
+                    // No
+                    continue;
+                }
+
+                // This bucket is now unreachable.  Get a spare bucket
+                if self.bridge_table.spares.is_empty() {
+                    // Uh, oh.  No spares available.  Just delete any
+                    // migrations leading to this bucket.
+                    res = false;
+                    self.trustup_migration_table
+                        .table
+                        .retain(|_, &mut v| v != *bucketnum);
+                    self.blockage_migration_table
+                        .table
+                        .retain(|_, &mut v| v != *bucketnum);
+                } else {
+                    // Get the first spare and remove it from the spares
+                    // set.
+                    let spare = *self.bridge_table.spares.iter().next().unwrap();
+                    self.bridge_table.spares.remove(&spare);
+                    // Add a blockage migration from this bucket to the spare
+                    self.blockage_migration_table
+                        .table
+                        .insert(*bucketnum, spare);
+                    // Remove any trust upgrade migrations to this
+                    // bucket
+                    self.trustup_migration_table
+                        .table
+                        .retain(|_, &mut v| v != *bucketnum);
+                    // Change any blockage migrations with this bucket
+                    // as the destination to the spare
+                    for (_, v) in self.blockage_migration_table.table.iter_mut() {
+                        if *v == *bucketnum {
+                            *v = spare;
+                        }
+                    }
+                }
+            }
+        }
+        self.bridge_table.reachable.remove(bridge);
+
+        res
+    }
+
     #[cfg(test)]
     /// For testing only: manually advance the day by 1 day
     pub fn advance_day(&mut self) {
@@ -282,7 +419,7 @@ impl BridgeAuth {
     /// Be sure to call this function when you want the latest version
     /// of the table, since it will put fresh Bucket Reachability
     /// credentials in the buckets each day.
-    pub fn enc_bridge_table(&mut self) -> &Vec<[u8; bridge_table::ENC_BUCKET_BYTES]> {
+    pub fn enc_bridge_table(&mut self) -> &Vec<[u8; ENC_BUCKET_BYTES]> {
         let today = self.today();
         if self.bridge_table.date_last_enc != today {
             self.bridge_table

+ 31 - 0
src/migration_table.rs

@@ -34,10 +34,33 @@ pub const MIGRATION_BYTES: usize = 96;
 /// The size of an encrypted entry in the returned migration table
 pub const ENC_MIGRATION_BYTES: usize = MIGRATION_BYTES + 12 + 16;
 
+/// The type of migration table: TrustUpgrade is for migrations from
+/// untrusted (level 0) 1-bridge buckets to trusted (level 1) 3-bridge
+/// buckets.  Blockage is for migrations that drop you down two levels
+/// (level 3 to 1, level 4 to 2) because the bridges in your current
+/// bucket were blocked.
+pub enum MigrationType {
+    TrustUpgrade,
+    Blockage,
+}
+
+impl From<MigrationType> for Scalar {
+    /// Convert a MigrationType into the Scalar value that represents
+    /// it in the Migration credential
+    fn from(m: MigrationType) -> Self {
+        match m {
+            MigrationType::TrustUpgrade => 0u32,
+            MigrationType::Blockage => 1u32,
+        }
+        .into()
+    }
+}
+
 /// The migration table
 #[derive(Default, Debug)]
 pub struct MigrationTable {
     pub table: HashMap<u32, u32>,
+    pub migration_type: Scalar,
 }
 
 /// Create an encrypted Migration credential for returning to the user
@@ -143,6 +166,14 @@ pub fn encrypt_cred_ids(
 }
 
 impl MigrationTable {
+    /// Create a MigrationTable of the given MigrationType
+    pub fn new(table_type: MigrationType) -> Self {
+        Self {
+            table: Default::default(),
+            migration_type: table_type.into(),
+        }
+    }
+
     /// For each entry in the MigrationTable, use encrypt_cred_ids to
     /// produce an entry in an output HashMap (from labels to encrypted
     /// Migration credentials).

+ 1 - 1
src/proto/trust_promotion.rs

@@ -517,7 +517,7 @@ impl BridgeAuth {
         Ok(Response {
             Pk,
             EncQk,
-            enc_migration_table: self.migration_table.encrypt_table(
+            enc_migration_table: self.trustup_migration_table.encrypt_table(
                 &req.id,
                 &self.bridge_table,
                 &Pktable,

+ 66 - 19
src/tests.rs

@@ -13,30 +13,27 @@ struct TestHarness {
 impl TestHarness {
     fn new() -> Self {
         // Create a BridegDb
-        let bdb = BridgeDb::new(15);
+        let mut bdb = BridgeDb::new();
         // Create a BridgeAuth
         let mut ba = BridgeAuth::new(bdb.pubkey);
 
-        // Make 15 buckets with one random bridge each
-        for _ in 0..15 {
-            let bucket: [BridgeLine; 3] =
-                [BridgeLine::random(), Default::default(), Default::default()];
-            ba.bridge_table.new_bucket(bucket);
+        // Make 15 open invitation bridges, in 5 sets of 3
+        for _ in 0..5 {
+            let bucket = [
+                BridgeLine::random(),
+                BridgeLine::random(),
+                BridgeLine::random(),
+            ];
+            ba.add_openinv_bridges(bucket, &mut bdb);
         }
-        // Make 5 more buckets, each containing 3 of the previously
-        // created bridges
-        for i in 0u32..5 {
-            let iusize = i as usize;
-            let bucket: [BridgeLine; 3] = [
-                ba.bridge_table.buckets[3 * iusize][0],
-                ba.bridge_table.buckets[3 * iusize + 1][0],
-                ba.bridge_table.buckets[3 * iusize + 2][0],
+        // Add 5 more hot spare buckets
+        for _ in 0..5 {
+            let bucket = [
+                BridgeLine::random(),
+                BridgeLine::random(),
+                BridgeLine::random(),
             ];
-            ba.bridge_table.new_bucket(bucket);
-            // Add the allowed migrations to the migration table
-            ba.migration_table.table.insert(3 * i, 15 + i);
-            ba.migration_table.table.insert(3 * i + 1, 15 + i);
-            ba.migration_table.table.insert(3 * i + 2, 15 + i);
+            ba.add_spare_bucket(bucket);
         }
         // Create the encrypted bridge table
         ba.enc_bridge_table();
@@ -303,3 +300,53 @@ fn test_redeem_invite() {
     assert!(th.ba.verify_lox(&bob_cred));
     println!("bob_cred = {:?}", bob_cred);
 }
+
+#[test]
+fn test_mark_unreachable() {
+    let mut th = TestHarness::new();
+
+    println!("spares = {:?}", th.ba.bridge_table.spares);
+    println!("tmig = {:?}", th.ba.trustup_migration_table.table);
+    println!("bmig = {:?}", th.ba.blockage_migration_table.table);
+    println!("openinv = {:?}\n", th.bdb.openinv_buckets);
+
+    // Mark a bridge in an untrusted bucket as unreachable
+    let b6 = th.ba.bridge_table.buckets[6][0];
+    th.ba.bridge_unreachable(&b6, &mut th.bdb);
+
+    println!("spares = {:?}", th.ba.bridge_table.spares);
+    println!("tmig = {:?}", th.ba.trustup_migration_table.table);
+    println!("bmig = {:?}", th.ba.blockage_migration_table.table);
+    println!("openinv = {:?}\n", th.bdb.openinv_buckets);
+
+    // Mark another bridge grouped to the same trusted bucket as
+    // unreachable
+    let b7 = th.ba.bridge_table.buckets[7][0];
+    th.ba.bridge_unreachable(&b7, &mut th.bdb);
+
+    println!("spares = {:?}", th.ba.bridge_table.spares);
+    println!("tmig = {:?}", th.ba.trustup_migration_table.table);
+    println!("bmig = {:?}", th.ba.blockage_migration_table.table);
+    println!("openinv = {:?}\n", th.bdb.openinv_buckets);
+
+    // That will have introduced a blockage migration.  Get the target
+    let target: u32 = *th
+        .ba
+        .blockage_migration_table
+        .table
+        .iter()
+        .next()
+        .unwrap()
+        .1;
+
+    // Block two of the bridges in that target bucket
+    let bt1 = th.ba.bridge_table.buckets[target as usize][1];
+    let bt2 = th.ba.bridge_table.buckets[target as usize][2];
+    th.ba.bridge_unreachable(&bt1, &mut th.bdb);
+    th.ba.bridge_unreachable(&bt2, &mut th.bdb);
+
+    println!("spares = {:?}", th.ba.bridge_table.spares);
+    println!("tmig = {:?}", th.ba.trustup_migration_table.table);
+    println!("bmig = {:?}", th.ba.blockage_migration_table.table);
+    println!("openinv = {:?}\n", th.bdb.openinv_buckets);
+}

+ 4 - 1
tests/tests.rs

@@ -6,7 +6,10 @@ use curve25519_dalek::scalar::Scalar;
 
 #[test]
 fn test_bridgedb() {
-    let bdb = BridgeDb::new(20);
+    let mut bdb = BridgeDb::new();
+    for i in &[1u32, 5, 7, 12, 19, 20, 22] {
+        bdb.insert_openinv(*i);
+    }
     let inv = bdb.invite();
     println!("{:?}", inv);
     let res = BridgeDb::verify(inv, bdb.pubkey);