Compare commits

..

5 Commits

4 changed files with 384 additions and 4 deletions

View File

@ -30,3 +30,7 @@ sled = "0.34.7"
time = "0.3.30"
tokio = { version = "1", features = ["full"] }
tokio-cron = "0.1.2"
[dev-dependencies]
base64 = "0.21.7"
rand = "0.8.5"

View File

@ -561,3 +561,7 @@ pub async fn report_blockages(
let resp_str: String = serde_json::from_slice(&buf).unwrap();
assert_eq!("OK", resp_str);
}
// Unit tests
#[cfg(test)]
mod tests;

View File

@ -71,9 +71,14 @@ impl PositiveReport {
lox_cred: &Lox,
lox_pub: &IssuerPubKey,
country: String,
) -> Self {
let lox_proof = lox_pr::request(lox_cred, lox_pub).unwrap();
PositiveReport::new(bridge_id, bridge_token, lox_proof, country)
) -> Result<Self, lox_library::proto::positive_report::requestproof::ProofError> {
let lox_proof = lox_pr::request(lox_cred, lox_pub)?;
Ok(PositiveReport::new(
bridge_id,
bridge_token,
lox_proof,
country,
))
}
/// Convert report to a serializable version
@ -143,7 +148,7 @@ impl PositiveReport {
let buckets = &bridge_info.buckets;
let BP = self.lox_proof.BP;
for bucket in buckets {
if bucket * Htable != BP {
if bucket * Htable == BP {
return la.handle_positive_report(self.lox_proof, &Htable).is_ok();
}
}

367
src/tests.rs Normal file
View File

@ -0,0 +1,367 @@
#![allow(non_snake_case)]
use crate::{bridge_verification_info::BridgeVerificationInfo, *};
use lox_library::{
bridge_table::{self, BridgeLine, BridgeTable},
cred::Lox,
proto::*,
scalar_u32, BridgeAuth, BridgeDb,
};
use base64::{engine::general_purpose, Engine as _};
use curve25519_dalek::{ristretto::RistrettoBasepointTable, Scalar};
use rand::RngCore;
use sha1::{Digest, Sha1};
use std::{
collections::{BTreeMap, HashMap, HashSet},
sync::{Arc, Mutex},
};
struct TestHarness {
bdb: BridgeDb,
pub ba: BridgeAuth,
}
impl TestHarness {
fn new() -> Self {
TestHarness::new_buckets(5, 5)
}
fn new_buckets(num_buckets: u16, hot_spare: u16) -> Self {
// Create a BridegDb
let mut bdb = BridgeDb::new();
// Create a BridgeAuth
let mut ba = BridgeAuth::new(bdb.pubkey);
// Make 3 x num_buckets open invitation bridges, in sets of 3
for _ in 0..num_buckets {
let bucket = [random(), random(), random()];
let _ = ba.add_openinv_bridges(bucket, &mut bdb);
}
// Add hot_spare more hot spare buckets
for _ in 0..hot_spare {
let bucket = [random(), random(), random()];
let _ = ba.add_spare_bucket(bucket, &mut bdb);
}
// Create the encrypted bridge table
ba.enc_bridge_table();
Self { bdb, ba }
}
fn advance_days(&mut self, days: u16) {
self.ba.advance_days(days);
}
fn get_new_credential(&mut self) -> Lox {
let inv = self.bdb.invite().unwrap();
let (req, state) = open_invite::request(&inv);
let resp = self.ba.handle_open_invite(req).unwrap();
let (cred, _bridgeline) =
open_invite::handle_response(state, resp, &self.ba.lox_pub).unwrap();
cred
}
fn level_up(&mut self, cred: &Lox) -> Lox {
let current_level = scalar_u32(&cred.trust_level).unwrap();
if current_level == 0 {
self.advance_days(trust_promotion::UNTRUSTED_INTERVAL.try_into().unwrap());
let (promreq, promstate) =
trust_promotion::request(cred, &self.ba.lox_pub, self.ba.today()).unwrap();
let promresp = self.ba.handle_trust_promotion(promreq).unwrap();
let migcred = trust_promotion::handle_response(promstate, promresp).unwrap();
let (migreq, migstate) =
migration::request(cred, &migcred, &self.ba.lox_pub, &self.ba.migration_pub)
.unwrap();
let migresp = self.ba.handle_migration(migreq).unwrap();
let new_cred = migration::handle_response(migstate, migresp, &self.ba.lox_pub).unwrap();
new_cred
} else {
self.advance_days(
level_up::LEVEL_INTERVAL[usize::try_from(current_level).unwrap()]
.try_into()
.unwrap(),
);
let (id, key) = bridge_table::from_scalar(cred.bucket).unwrap();
let encbuckets = self.ba.enc_bridge_table();
let bucket =
bridge_table::BridgeTable::decrypt_bucket(id, &key, encbuckets.get(&id).unwrap())
.unwrap();
let reachcred = bucket.1.unwrap();
let (lvreq, lvstate) = level_up::request(
cred,
&reachcred,
&self.ba.lox_pub,
&self.ba.reachability_pub,
self.ba.today(),
)
.unwrap();
let lvresp = self.ba.handle_level_up(lvreq).unwrap();
let new_cred = level_up::handle_response(lvstate, lvresp, &self.ba.lox_pub).unwrap();
new_cred
}
}
fn get_bucket(&mut self, cred: &Lox) -> [BridgeLine; bridge_table::MAX_BRIDGES_PER_BUCKET] {
let (id, key) = bridge_table::from_scalar(cred.bucket).unwrap();
let encbuckets = self.ba.enc_bridge_table();
let bucket =
bridge_table::BridgeTable::decrypt_bucket(id, &key, encbuckets.get(&id).unwrap())
.unwrap();
bucket.0
}
}
pub fn random() -> BridgeLine {
let mut rng = rand::thread_rng();
let mut res: BridgeLine = BridgeLine::default();
// Pick a random 4-byte address
let mut addr: [u8; 4] = [0; 4];
rng.fill_bytes(&mut addr);
// If the leading byte is 224 or more, that's not a valid IPv4
// address. Choose an IPv6 address instead (but don't worry too
// much about it being well formed).
if addr[0] >= 224 {
rng.fill_bytes(&mut res.addr);
} else {
// Store an IPv4 address as a v4-mapped IPv6 address
res.addr[10] = 255;
res.addr[11] = 255;
res.addr[12..16].copy_from_slice(&addr);
};
let ports: [u16; 4] = [443, 4433, 8080, 43079];
let portidx = (rng.next_u32() % 4) as usize;
res.port = ports[portidx];
res.uid_fingerprint = rng.next_u64();
rng.fill_bytes(&mut res.fingerprint);
let mut cert: [u8; 52] = [0; 52];
rng.fill_bytes(&mut cert);
let infostr: String = format!(
"obfs4 cert={}, iat-mode=0",
general_purpose::STANDARD_NO_PAD.encode(cert)
);
res.info[..infostr.len()].copy_from_slice(infostr.as_bytes());
res
}
#[test]
fn test_negative_reports() {
let mut th = TestHarness::new();
// Get new level 1 credential
let cred = th.get_new_credential();
let cred = th.level_up(&cred);
let bridges = th.get_bucket(&cred);
// Create BridgeVerificationInfo for each bridge
let mut buckets = HashSet::<Scalar>::new();
buckets.insert(cred.bucket);
let bridge_info_1 = BridgeVerificationInfo {
bridge_line: bridges[0],
buckets: buckets.clone(),
pubkey: None,
};
let bridge_info_2 = BridgeVerificationInfo {
bridge_line: bridges[1],
buckets: buckets.clone(),
pubkey: None,
};
let bridge_info_3 = BridgeVerificationInfo {
bridge_line: bridges[2],
buckets: buckets.clone(),
pubkey: None,
};
// Create reports
let report_1 =
NegativeReport::from_bridgeline(bridges[0], "ru".to_string(), BridgeDistributor::Lox);
let report_2 =
NegativeReport::from_lox_bucket(bridges[1].fingerprint, cred.bucket, "ru".to_string());
let report_3 =
NegativeReport::from_lox_credential(bridges[2].fingerprint, cred, "ru".to_string());
// Verify reports
assert!(report_1.verify(&bridge_info_1));
assert!(report_2.verify(&bridge_info_2));
assert!(report_3.verify(&bridge_info_3));
// Check that deserialization fails under invalid conditions
// Date in the future
let mut invalid_report_1 =
NegativeReport::from_bridgeline(bridges[0], "ru".to_string(), BridgeDistributor::Lox)
.to_serializable_report();
invalid_report_1.date = invalid_report_1.date + 2;
// Invalid country code
let invalid_report_2 =
NegativeReport::from_bridgeline(bridges[1], "xx".to_string(), BridgeDistributor::Lox)
.to_serializable_report();
assert!(invalid_report_1.to_report().is_err());
assert!(invalid_report_2.to_report().is_err());
// Check that verification fails with incorrect data
// Incorrect BridgeLine hash
let invalid_report_3 = NegativeReport::new(
bridges[0].fingerprint,
ProofOfBridgeKnowledge::HashOfBridgeLine(HashOfBridgeLine::new(&BridgeLine::default())),
"ru".to_string(),
BridgeDistributor::Lox,
);
// Incorrect bucket hash
let invalid_report_4 = NegativeReport::new(
bridges[1].fingerprint,
ProofOfBridgeKnowledge::HashOfBucket(HashOfBucket::new(&Scalar::ZERO)),
"ru".to_string(),
BridgeDistributor::Lox,
);
assert!(!invalid_report_3.verify(&bridge_info_1));
assert!(!invalid_report_4.verify(&bridge_info_2));
}
#[test]
fn test_positive_reports() {
let mut th = TestHarness::new();
// Get new level 3 credential
let cred = th.get_new_credential();
let cred = th.level_up(&cred);
let cred = th.level_up(&cred);
let cred = th.level_up(&cred);
let bridges = th.get_bucket(&cred);
// Create BridgeVerificationInfo for each bridge
let mut buckets = HashSet::<Scalar>::new();
buckets.insert(cred.bucket);
let bridge_info_1 = BridgeVerificationInfo {
bridge_line: bridges[0],
buckets: buckets.clone(),
pubkey: None,
};
let bridge_info_2 = BridgeVerificationInfo {
bridge_line: bridges[1],
buckets: buckets.clone(),
pubkey: None,
};
let bridge_info_3 = BridgeVerificationInfo {
bridge_line: bridges[2],
buckets: buckets.clone(),
pubkey: None,
};
// Create reports
let report_1 = PositiveReport::from_lox_credential(
bridges[0].fingerprint,
None,
&cred,
&th.ba.lox_pub,
"ru".to_string(),
)
.unwrap();
let report_2 = PositiveReport::from_lox_credential(
bridges[1].fingerprint,
None,
&cred,
&th.ba.lox_pub,
"ru".to_string(),
)
.unwrap();
let report_3 = PositiveReport::from_lox_credential(
bridges[2].fingerprint,
None,
&cred,
&th.ba.lox_pub,
"ru".to_string(),
)
.unwrap();
// Compute Htable
let H = lox_library::proto::positive_report::compute_H(report_1.date);
let Htable = RistrettoBasepointTable::create(&H);
assert!(report_1.verify(&mut th.ba, &bridge_info_1, &Htable));
assert!(report_2.verify(&mut th.ba, &bridge_info_2, &Htable));
assert!(report_3.verify(&mut th.ba, &bridge_info_3, &Htable));
// Check that user cannot use credential for other bridge
// Get new credential
let cred_2 = th.get_new_credential();
let bridges_2 = th.get_bucket(&cred_2);
let mut buckets_2 = HashSet::<Scalar>::new();
buckets_2.insert(cred_2.bucket);
let bridge_info_4 = BridgeVerificationInfo {
bridge_line: bridges_2[0],
buckets: buckets_2.clone(),
pubkey: None,
};
// Use new credential to create positive report even we don't trust it
let invalid_report_1 = PositiveReport::from_lox_credential(
bridges_2[0].fingerprint,
None,
&cred_2,
&th.ba.lox_pub,
"ru".to_string(),
);
// Use first credential for bridge from second bucket
let invalid_report_2 = PositiveReport::from_lox_credential(
bridges_2[0].fingerprint,
None,
&cred,
&th.ba.lox_pub,
"ru".to_string(),
);
// Use second credential for bridge from first bucket
let invalid_report_3 = PositiveReport::from_lox_credential(
bridges[0].fingerprint,
None,
&cred_2,
&th.ba.lox_pub,
"ru".to_string(),
);
// Check that all of these fail
assert!(invalid_report_1.is_err());
assert!(!invalid_report_2
.unwrap()
.verify(&mut th.ba, &bridge_info_4, &Htable));
assert!(invalid_report_3.is_err());
// Check that deserialization fails under invalid conditions
// Date in the future
let mut invalid_report_4 = PositiveReport::from_lox_credential(
bridges[0].fingerprint,
None,
&cred,
&th.ba.lox_pub,
"ru".to_string(),
)
.unwrap()
.to_serializable_report();
invalid_report_4.date = invalid_report_4.date + 2;
// Invalid country code
let invalid_report_5 = PositiveReport::from_lox_credential(
bridges[0].fingerprint,
None,
&cred,
&th.ba.lox_pub,
"xx".to_string(),
)
.unwrap()
.to_serializable_report();
assert!(invalid_report_4.to_report().is_err());
assert!(invalid_report_5.to_report().is_err());
}