Fixup and add comments, README to Lox Library

This commit is contained in:
onyinyang 2023-11-07 18:44:25 -05:00
parent 73b04b9a19
commit e356034dae
No known key found for this signature in database
GPG Key ID: 156A6435430C2036
8 changed files with 112 additions and 59 deletions

View File

@ -9,10 +9,10 @@
},
"rtype": {
"endpoint": "http://127.0.0.1:7100/resources",
"name": "https",
"token": "HttpsApiTokenPlaceholder",
"name": "lox",
"token": "LoxApiTokenPlaceholder",
"types": [
"obfs2",
"obfs4",
"scramblesuit"
]
}

View File

@ -1,8 +1,40 @@
# Lox
Lox is a reputation-based bridge distribution system that provides privacy protection to users and their social graph and is open to all users.
Lox is written in rust and requires `cargo` to test. [Install Rust](https://www.rust-lang.org/tools/install). We used Rust version 1.56.0.
Note that this implementation is coded such that the reachability certificate expires at 00:00 UTC. In reality, if the bucket is still reachable, a user could simply request a new reachability token if their request fails for this reason (a new certificate should be available prior to the outdated certificate expiring).
The protocols in the Lox-library are consistent with the Lox system described
in [Tulloch and Goldberg](https://petsymposium.org/popets/2023/popets-2023-0029.php) (and in greater detail [here](https://uwspace.uwaterloo.ca/handle/10012/18333)). However, this implementation may diverge from the theory over time as the system is deployed and its limitations are better illuminated. The [original version of this library](https://git-crysp.uwaterloo.ca/iang/lox) will remain a more precise implementation of the theory.
Lox is written in rust and requires `cargo` to test. [Install Rust](https://www.rust-lang.org/tools/install). We used Rust version 1.65.0.
## Notable Changes from the original repository
Some changes have been made to integrate the existing Lox protocols with Tor's
bridge distributor [rdsys](https://gitlab.torproject.org/tpo/anti-censorship/rdsys),
but so far, these have not affected the Lox protocols themselves.
These changes are necessary to keep the consistentcy of bridges in buckets that Lox requires while working with the reality of how rdsys/Tor currently receives and distributes information about bridges. The changes to Lox are:
1. Add a `uid_fingerprint` field to the BridgeLine which helps with bridge lookup and
corresponds (roughly) to the unique fingerprint rdsys gives to each bridge
(made up of a hash of the IP and pluggable transport type)
2. Allow for the details of a bridge to be updated. This has been added to
[`crates/lox-library/src/lib.rs`](https://gitlab.torproject.org/tpo/anti-censorship/lox-rs/-/blob/main/crates/lox-library/src/lib.rs) and accounts for the fact that some details
of an existing bridge (i.e., that has a matching fingerprint) may be updated
from time to time.
3. Allow for a bridge to be replaced without penalty. This has also been added to
[`crates/lox-library/src/lib.rs`](https://gitlab.torproject.org/tpo/anti-censorship/lox-rs/-/blob/main/crates/lox-library/src/lib.rs)
and accounts for the fact that Tor currently does not have a robust way of
[knowing that a bridge is blocked](https://gitlab.torproject.org/tpo/anti-censorship/censorship-analysis/-/issues/40035), but does have some tests (namely,
[bridgestrap](https://gitlab.torproject.org/tpo/anti-censorship/bridgestrap) and [onbasca](https://gitlab.torproject.org/tpo/network-health/onbasca)) that help to determine if a
bridge should not be distributed. Since we do not know if the results of
these tests indicate a blocking event, we are allowing for bridges that
rdsys marks as unsuitable for distribution to be updated without penalty in the Lox library.
4. The vectors within `bridge_table.rs` have been refactored into HashMaps that use a unique `u32` for lookup. This has led to a
number of changes around how bridges are inserted/removed from the bridge table but does not impact the overall functionality of the Lox system.
5. The `DupFilter` has been changed from a `HashMap` to a `HashSet`, primarily because this is easier to Serialize/Deserialize when storing the state of the Lox system to recover from failure or to be able to roll back to a previous state.
6. The [`dalek-cryptography`](https://dalek.rs/) libraries have been updated to their most recent versions and the `zkp` library has been forked (until/unless this is fixed in one of the existing upstream repos) to fix a bug that appears when a public attribute is set to 0 (previously impacting only the blockage migration protocol when a user's invitations are set to 0 after migrating). The fork of `zkp` also includes similar updates to `dalek-cryptography` dependencies and some others such as `rand`.
7. Many tests that were used to create the Lox paper/thesis and measure the performance of the system were removed from this repository as they are unnecessary in a deployment scenario. They are still available in the [original repository](https://git-crysp.uwaterloo.ca/iang/lox).
### Other important Notes
As with the original implementation, this implementation is coded such that the reachability certificate expires at 00:00 UTC. Therefore, if an unlucky user requests a reachability certificate just before the 00:00 UTC and tries to use it just after, the request will fail. If the bucket is still reachable, a user can simply request a new reachability token if their request fails for this reason (a new certificate should be available prior to the outdated certificate expiring).

View File

@ -246,35 +246,40 @@ struct K {
#[serde_as]
#[derive(Debug, Default, Serialize, Deserialize)]
pub struct BridgeTable {
// All structures in the bridgetable are indexed by counter
/// All structures in the bridgetable are indexed by counter
pub counter: u32,
/// The keys of all buckets, indexed by counter, that are still part of the bridge table.
pub keys: HashMap<u32, [u8; 16]>,
/// All buckets, indexed by counter corresponding to the key above, that are
/// part of the bridge table.
pub buckets: HashMap<u32, [BridgeLine; MAX_BRIDGES_PER_BUCKET]>,
pub encbuckets: HashMap<u32, EncryptedBucket>,
/// Individual bridges that are reachable
/// Individual bridges that are reachable.
#[serde_as(as = "HashMap<serde_with::json::JsonString, _>")]
pub reachable: HashMap<BridgeLine, Vec<(u32, usize)>>,
/// bucket ids of "hot spare" buckets. These buckets are not handed
/// 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>,
/// In some instances a single bridge may need to be added to a bucket
/// In that case, a spare bucket will be removed from the set of spare bridges. One
/// In some instances a single bridge may need to be added to a bucket as a replacement
/// or otherwise. In that case, a spare bucket will be removed from the set of spares, one
/// bridge will be used as the replacement and the left over bridges will be appended to
/// unallocated_bridges.
pub unallocated_bridges: Vec<BridgeLine>,
// To prevent issues with a counter for the hashmap keys, we keep a list of keys that
// no longer match any buckets that can be used before increasing the counter
// To prevent issues with the counter for the hashmap keys, keep a list of keys that
// no longer match any buckets that can be used before increasing the counter.
pub recycleable_keys: Vec<u32>,
// We maintain a list of keys that have been blocked (bucket_id: u32), as well as the
// A list of keys that have been blocked (bucket_id: u32), as well as the
// time (julian_date: u32) of their blocking so that they can be repurposed with new
// buckets after the EXPIRY_DATE
// buckets after the EXPIRY_DATE.
pub blocked_keys: Vec<(u32, u32)>,
// Similarly, we maintain a list of open entry buckets (bucket_id: u32) and the time they were
// created (julian_date: u32) so they will be listed as expired after the EXPIRY_DATE
// Similarly, a list of open entry buckets (bucket_id: u32) and the time they were
// created (julian_date: u32) so they will be listed as expired after the EXPIRY_DATE.
// TODO: add open entry buckets to the open_inv_keys only once they have been distributed
pub open_inv_keys: Vec<(u32, u32)>,
/// The date the buckets were last encrypted to make the encbucket.
/// The encbucket must be rebuilt each day so that the Bucket
/// The encbucket must be rebuilt at least each day so that the Bucket
/// Reachability credentials in the buckets can be refreshed.
pub date_last_enc: u32,
}
@ -288,7 +293,7 @@ impl BridgeTable {
self.buckets.len()
}
/// Append a new bucket to the bridge table, returning its index
/// Insert a new bucket into the bridge table, returning its index
pub fn new_bucket(&mut self, index: u32, bucket: &[BridgeLine; MAX_BRIDGES_PER_BUCKET]) {
// Pick a random key to encrypt this bucket
let mut rng = rand::thread_rng();

View File

@ -2,7 +2,7 @@
In each case, (P,Q) forms the MAC on the credential. This MAC is
verifiable only by the issuing party, or if the issuing party issues a
zero-knowledge proof of its correctness (as it does at issuing time). */
zero-knowledge proof of its correctness (as it does at issuing time).*/
use curve25519_dalek::ristretto::RistrettoPoint;
use curve25519_dalek::scalar::Scalar;

View File

@ -62,10 +62,12 @@ lazy_static! {
}
// EXPIRY_DATE is set to EXPIRY_DATE days for open-entry and blocked buckets in order to match
// the expiry date for Lox credentials. This particular value (EXPIRY_DATE) is chosen because
// the expiry date for Lox credentials.This particular value (EXPIRY_DATE) is chosen because
// values that are 2^k 1 make range proofs more efficient, but this can be changed to any value
pub const EXPIRY_DATE: u32 = 511;
/// ReplaceSuccess sends a signal to the lox-distributor to inform
/// whether or not a bridge was successfully replaced
#[derive(PartialEq, Eq)]
pub enum ReplaceSuccess {
NotFound = 0,
@ -73,18 +75,23 @@ pub enum ReplaceSuccess {
Replaced = 2,
}
/// This error is thrown if the number of buckets/keys in the bridge table
/// exceeds u32 MAX.It is unlikely this error will ever occur.
#[derive(Error, Debug)]
pub enum NoAvailableIDError {
#[error("Find key exhausted with no available index found!")]
ExhaustedIndexer,
}
/// This error is thrown after the MAX_DAILY_BRIDGES threshold for bridges
/// distributed in a day has been reached
#[derive(Error, Debug)]
pub enum ExceededMaxBridgesError {
#[error("The maximum number of bridges has already been distributed today, please try again tomorrow!")]
ExceededMaxBridges,
}
/// Private Key of the Issuer
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct IssuerPrivKey {
x0tilde: Scalar,
@ -106,11 +113,13 @@ impl IssuerPrivKey {
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct IssuerPubKey {
X: Vec<RistrettoPoint>,
}
/// Public Key of the Issuer
impl IssuerPubKey {
/// Create an IssuerPubKey from the corresponding IssuerPrivKey
pub fn new(privkey: &IssuerPrivKey) -> IssuerPubKey {
@ -130,9 +139,9 @@ impl IssuerPubKey {
}
}
// Number of times a given invitation is ditributed
/// Number of times a given invitation is ditributed
pub const OPENINV_K: u32 = 10;
// TODO: Decide on maximum daily number of invitations to be distributed
/// 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
/// BridgeDb's role is simply to issue signed "open invitations" to
@ -188,6 +197,8 @@ impl BridgeDb {
self.openinv_buckets.remove(bucket);
}
/// Remove open invitation and/or otherwise distributed buckets that have
/// become blocked or are expired to free up the index for a new bucket
pub fn remove_blocked_or_expired_buckets(&mut self, bucket: &u32) {
if self.openinv_buckets.contains(bucket) {
println!("Removing a bucket that has not been distributed yet!");
@ -197,6 +208,7 @@ impl BridgeDb {
}
}
/// Mark a bucket as distributed
pub fn mark_distributed(&mut self, bucket: u32) {
self.distributed_buckets.push(bucket);
}
@ -406,6 +418,9 @@ impl BridgeAuth {
Ok(())
}
/// When syncing the Lox bridge table with rdsys, this function returns any bridges
/// that are found in the Lox bridge table that are not found in the Vector
/// of bridges received from rdsys through the Lox distributor.
pub fn find_and_remove_unaccounted_for_bridges(
&mut self,
accounted_for_bridges: Vec<u64>,
@ -419,6 +434,7 @@ impl BridgeAuth {
unaccounted_for
}
/// Allocate single left over bridges to an open invitation bucket
pub fn allocate_bridges(
&mut self,
distributor_bridges: &mut Vec<BridgeLine>,
@ -447,12 +463,10 @@ impl BridgeAuth {
// Update the details of a bridge in the bridge table. This assumes that the IP and Port
// of a given bridge remains the same and thus can be updated.
// First we must retrieve the list of reachable bridges, then we must search for any matching our partial key
// which will include the IP and Port. Then we can replace the original bridge with the updated bridge
// which will include the IP and Port. Finally we can replace the original bridge with the updated bridge.
// Returns true if the bridge has successfully updated
pub fn bridge_update(&mut self, bridge: &BridgeLine) -> bool {
let mut res: bool = false; //default False to assume that update failed
//Needs to be updated since bridge will only match on some fields.
let reachable_bridges = self.bridge_table.reachable.clone();
for reachable_bridge in reachable_bridges {
if reachable_bridge.0.uid_fingerprint == bridge.uid_fingerprint {
@ -490,6 +504,8 @@ impl BridgeAuth {
res
}
/// Attempt to remove a bridge that is failing tests and replace it with a bridge from
/// available_bridge or from a spare bucket
pub fn bridge_replace(
&mut self,
bridge: &BridgeLine,
@ -692,7 +708,7 @@ impl BridgeAuth {
}
// Since buckets are moved around in the bridge_table, finding a lookup key that
// does not overwrite existing bridges could become an issue. We keep a list
// does not overwrite existing bridges could become an issue.We keep a list
// of recycleable lookup keys from buckets that have been removed and prioritize
// this list before increasing the counter
fn find_next_available_key(&mut self, bdb: &mut BridgeDb) -> Result<u32, NoAvailableIDError> {
@ -728,6 +744,7 @@ impl BridgeAuth {
self.clean_up_open_entry(bdb);
}
/// Cleans up exipred blocked buckets
fn clean_up_blocked(&mut self) {
if !self.bridge_table.blocked_keys.is_empty()
&& self
@ -754,7 +771,7 @@ impl BridgeAuth {
if bridgeline.port > 0 {
// Move to unallocated bridges
self.bridge_table.unallocated_bridges.push(*bridgeline);
// Check if it's still in the reachable bridges. It should be if we've gotten this far.
// Check if it's still in the reachable bridges.It should be if we've gotten this far.
if let Some(_reachable_indexes_for_bridgeline) =
self.bridge_table.reachable.get(bridgeline)
{
@ -780,6 +797,7 @@ impl BridgeAuth {
}
}
/// Cleans up expired open invitation buckets
fn clean_up_open_entry(&mut self, bdb: &mut BridgeDb) {
// First check if there are any open invitation indexes that are old enough to be replaced
if !self.bridge_table.open_inv_keys.is_empty()
@ -825,14 +843,14 @@ impl BridgeAuth {
self.time_offset += time::Duration::days(1);
}
//#[cfg(test)]
///#[cfg(test)]
/// For testing only: manually advance the day by the given number
/// of days
pub fn advance_days(&mut self, days: u16) {
self.time_offset += time::Duration::days(days.into());
}
/// Get today's (real or simulated) date
/// Get today's (real or simulated) date as u32
pub fn today(&self) -> u32 {
// We will not encounter negative Julian dates (~6700 years ago)
// or ones larger than 32 bits
@ -842,7 +860,7 @@ impl BridgeAuth {
.unwrap()
}
/// Get today's (real or simulated) date
/// Get today's (real or simulated) date as a DateTime<Utc> value
pub fn today_date(&self) -> DateTime<Utc> {
Utc::now()
}

View File

@ -7,7 +7,6 @@ rust-version = "1.65"
homepage = "https://gitlab.torproject.org/tpo/anti-censorship/lox/-/wikis/home"
description = "General helpers used by Lox"
keywords = ["tor", "lox"]
# We must put *something* here and this will do
categories = ["rust-patterns"]
repository = "https://gitlab.torproject.org/tpo/anti-censorship/lox.git/"

View File

@ -10,7 +10,6 @@ categories = ["api-bindings", "encoding"]
repository = "https://gitlab.torproject.org/tpo/anti-censorship/lox/-/tree/main/crates/rdsys-backend-api"
readme = "https://gitlab.torproject.org/tpo/anti-censorship/lox/-/blob/main/crates/rdsys-backend-api/README.md"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
serde_json = "1"