Add a MAX_DAILY_BRIDGES to limit invites per day

This commit is contained in:
onyinyang 2023-10-30 12:42:15 -04:00
parent 2f1c48c0ac
commit ba70b1b4d4
No known key found for this signature in database
GPG Key ID: 156A6435430C2036
7 changed files with 83 additions and 28 deletions

22
Cargo.lock generated
View File

@ -1074,6 +1074,7 @@ dependencies = [
"ed25519-dalek", "ed25519-dalek",
"hex_fmt", "hex_fmt",
"lazy_static", "lazy_static",
"prometheus",
"rand 0.8.5", "rand 0.8.5",
"serde", "serde",
"serde_with", "serde_with",
@ -1458,6 +1459,27 @@ dependencies = [
"unicode-ident", "unicode-ident",
] ]
[[package]]
name = "prometheus"
version = "0.13.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "449811d15fbdf5ceb5c1144416066429cf82316e2ec8ce0c1f6f8a02e7bbcf8c"
dependencies = [
"cfg-if",
"fnv",
"lazy_static",
"memchr",
"parking_lot 0.12.1",
"protobuf",
"thiserror",
]
[[package]]
name = "protobuf"
version = "2.28.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "106dd99e98437432fed6519dedecfade6a06a73bb7b2a1e019fdd2bee5778d94"
[[package]] [[package]]
name = "quote" name = "quote"
version = "1.0.32" version = "1.0.32"

View File

@ -123,12 +123,13 @@ fn use_last_context(lox_db: sled::Db) -> lox_context::LoxServerContext {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::lox_context::LoxServerContext; use super::lox_context::LoxServerContext;
use super::DB;
use super::DbConfig; use super::DbConfig;
use super::DB;
#[test] #[test]
fn test_write_context() { fn test_write_context() {
let (mut lox_db, _context) = DB::open_new_or_existing_db(DbConfig::default(), None).unwrap(); let (mut lox_db, _context) =
DB::open_new_or_existing_db(DbConfig::default(), None).unwrap();
assert!( assert!(
lox_db.db.is_empty(), lox_db.db.is_empty(),
"db read from context that shouldn't exist" "db read from context that shouldn't exist"

View File

@ -6,7 +6,7 @@ use lox_library::{
blockage_migration, check_blockage, issue_invite, level_up, migration, open_invite, blockage_migration, check_blockage, issue_invite, level_up, migration, open_invite,
redeem_invite, trust_promotion, redeem_invite, trust_promotion,
}, },
BridgeAuth, BridgeDb, IssuerPubKey, BridgeAuth, BridgeDb, ExceededMaxBridgesError, IssuerPubKey,
}; };
use rdsys_backend::proto::{Resource, ResourceState}; use rdsys_backend::proto::{Resource, ResourceState};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@ -317,10 +317,11 @@ impl LoxServerContext {
] ]
} }
fn gen_invite(&self) -> lox_utils::Invite { fn gen_invite(&self) -> Result<lox_utils::Invite, ExceededMaxBridgesError> {
let mut obj = self.db.lock().unwrap(); let mut obj = self.db.lock().unwrap();
lox_utils::Invite { match obj.invite() {
invite: obj.invite(), Ok(invite) => Ok(lox_utils::Invite { invite }),
Err(e) => Err(e),
} }
} }
@ -382,8 +383,14 @@ impl LoxServerContext {
// Generate and return an open invitation token // Generate and return an open invitation token
pub fn generate_invite(self) -> Response<Body> { pub fn generate_invite(self) -> Response<Body> {
let invite = self.gen_invite(); let invite = self.gen_invite();
match serde_json::to_string(&invite) { match invite {
Ok(resp) => prepare_header(resp), Ok(invite) => match serde_json::to_string(&invite) {
Ok(resp) => prepare_header(resp),
Err(e) => {
println!("Error parsing Invite to JSON");
prepare_error_header(e.to_string())
}
},
Err(e) => { Err(e) => {
println!("Error parsing Invite to JSON"); println!("Error parsing Invite to JSON");
prepare_error_header(e.to_string()) prepare_error_header(e.to_string())

View File

@ -23,6 +23,7 @@ hex_fmt = "0.3"
aes-gcm = { version = "0.10", features =["aes"]} aes-gcm = { version = "0.10", features =["aes"]}
base64 = "0.21" base64 = "0.21"
time = "0.3.30" time = "0.3.30"
prometheus = "0.13.3"
subtle = "2.5" subtle = "2.5"
thiserror = "1.0.49" thiserror = "1.0.49"
zkp = { git = "https://gitlab.torproject.org/onyinyang/lox-zkp" } zkp = { git = "https://gitlab.torproject.org/onyinyang/lox-zkp" }

View File

@ -22,6 +22,7 @@ pub mod cred;
pub mod dup_filter; pub mod dup_filter;
pub mod migration_table; pub mod migration_table;
use chrono::Duration;
use chrono::{DateTime, Utc}; use chrono::{DateTime, Utc};
use sha2::Sha512; use sha2::Sha512;
@ -78,6 +79,12 @@ pub enum NoAvailableIDError {
ExhaustedIndexer, ExhaustedIndexer,
} }
#[derive(Error, Debug)]
pub enum ExceededMaxBridgesError {
#[error("The maximum number of bridges has already been distributed today, please try again tomorrow!")]
ExceededMaxBridges,
}
#[derive(Clone, Debug, Serialize, Deserialize)] #[derive(Clone, Debug, Serialize, Deserialize)]
pub struct IssuerPrivKey { pub struct IssuerPrivKey {
x0tilde: Scalar, x0tilde: Scalar,
@ -123,7 +130,10 @@ impl IssuerPubKey {
} }
} }
// Number of times a given invitation is ditributed
pub const OPENINV_K: u32 = 10; pub const OPENINV_K: u32 = 10;
// TODO: Decide on maximum daily number of invitations to be distributed
pub const MAX_DAILY_BRIDGES: u32 = 100;
/// The BridgeDb. This will typically be a singleton object. The /// The BridgeDb. This will typically be a singleton object. The
/// BridgeDb's role is simply to issue signed "open invitations" to /// BridgeDb's role is simply to issue signed "open invitations" to
/// people who are not yet part of the system. /// people who are not yet part of the system.
@ -136,7 +146,10 @@ pub struct BridgeDb {
/// The set of open-invitation buckets /// The set of open-invitation buckets
openinv_buckets: HashSet<u32>, openinv_buckets: HashSet<u32>,
distributed_buckets: Vec<u32>, distributed_buckets: Vec<u32>,
current_k: u32, #[serde(skip)]
today: DateTime<Utc>,
pub current_k: u32,
pub daily_bridges_distributed: u32,
} }
/// An open invitation is a [u8; OPENINV_LENGTH] where the first 32 /// An open invitation is a [u8; OPENINV_LENGTH] where the first 32
@ -159,7 +172,9 @@ impl BridgeDb {
pubkey, pubkey,
openinv_buckets: Default::default(), openinv_buckets: Default::default(),
distributed_buckets: Default::default(), distributed_buckets: Default::default(),
today: Utc::now(),
current_k: 0, current_k: 0,
daily_bridges_distributed: 0,
} }
} }
@ -189,30 +204,39 @@ impl BridgeDb {
/// Produce an open invitation such that the next k users, where k is < /// Produce an open invitation such that the next k users, where k is <
/// OPENINV_K, will receive the same open invitation bucket /// OPENINV_K, will receive the same open invitation bucket
/// chosen randomly from the set of open-invitation buckets. /// chosen randomly from the set of open-invitation buckets.
pub fn invite(&mut self) -> [u8; OPENINV_LENGTH] { pub fn invite(&mut self) -> Result<[u8; OPENINV_LENGTH], ExceededMaxBridgesError> {
let mut res: [u8; OPENINV_LENGTH] = [0; OPENINV_LENGTH]; let mut res: [u8; OPENINV_LENGTH] = [0; OPENINV_LENGTH];
let mut rng = rand::thread_rng(); let mut rng = rand::thread_rng();
// Choose a random invitation id (a Scalar) and serialize it // Choose a random invitation id (a Scalar) and serialize it
let id = Scalar::random(&mut rng); let id = Scalar::random(&mut rng);
res[0..32].copy_from_slice(&id.to_bytes()); res[0..32].copy_from_slice(&id.to_bytes());
let bucket_num: u32; let bucket_num: u32;
if self.current_k < OPENINV_K && !self.distributed_buckets.is_empty() { if Utc::now() >= (self.today + Duration::days(1)) {
bucket_num = *self.distributed_buckets.last().unwrap(); self.today = Utc::now();
self.current_k += 1; self.daily_bridges_distributed = 0;
} else { }
// Choose a random bucket number (from the set of open if self.daily_bridges_distributed < MAX_DAILY_BRIDGES {
// invitation buckets) and serialize it if self.current_k < OPENINV_K && !self.distributed_buckets.is_empty() {
let openinv_vec: Vec<&u32> = self.openinv_buckets.iter().collect(); bucket_num = *self.distributed_buckets.last().unwrap();
bucket_num = *openinv_vec[rng.gen_range(0..openinv_vec.len())]; self.current_k += 1;
self.mark_distributed(bucket_num); } else {
self.remove_openinv(&bucket_num); // Choose a random bucket number (from the set of open
self.current_k = 1; // invitation buckets) and serialize it
let openinv_vec: Vec<&u32> = self.openinv_buckets.iter().collect();
bucket_num = *openinv_vec[rng.gen_range(0..openinv_vec.len())];
self.mark_distributed(bucket_num);
self.remove_openinv(&bucket_num);
self.current_k = 1;
self.daily_bridges_distributed += 1;
}
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)]);
res[(32 + 4)..].copy_from_slice(&sig.to_bytes());
Ok(res)
} else {
Err(ExceededMaxBridgesError::ExceededMaxBridges)
} }
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)]);
res[(32 + 4)..].copy_from_slice(&sig.to_bytes());
res
} }
/// Verify an open invitation. Returns the invitation id and the /// Verify an open invitation. Returns the invitation id and the

View File

@ -64,7 +64,7 @@ impl TestHarness {
fn open_invite(&mut self) -> (PerfStat, (cred::Lox, bridge_table::BridgeLine)) { fn open_invite(&mut self) -> (PerfStat, (cred::Lox, bridge_table::BridgeLine)) {
// Issue an open invitation // Issue an open invitation
let inv = self.bdb.invite(); let inv = self.bdb.invite().unwrap();
let req_start = Instant::now(); let req_start = Instant::now();
// Use it to get a Lox credential // Use it to get a Lox credential

View File

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