Fixup and add comments, README to Lox Library
This commit is contained in:
parent
73b04b9a19
commit
e356034dae
|
@ -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"
|
||||
]
|
||||
}
|
||||
|
|
|
@ -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).
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
|
|
@ -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/"
|
||||
|
||||
|
|
|
@ -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"
|
||||
|
|
Loading…
Reference in New Issue