API for adding bridges and marking them as unreachable

This commit is contained in:
Ian Goldberg 2021-05-05 13:58:43 -04:00
parent cf19bf80ee
commit 0664dd7b45
6 changed files with 277 additions and 46 deletions

View File

@ -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()

View File

@ -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

View File

@ -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).

View File

@ -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,

View File

@ -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 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],
// Make 15 open invitation bridges, in 5 sets of 3
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_openinv_bridges(bucket, &mut bdb);
}
// Add 5 more hot spare buckets
for _ in 0..5 {
let bucket = [
BridgeLine::random(),
BridgeLine::random(),
BridgeLine::random(),
];
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);
}

View File

@ -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);