Include nonce in negative reports

This commit is contained in:
Vecna 2024-04-12 02:38:35 -04:00
parent e1588aac0e
commit c82e604e3d
5 changed files with 148 additions and 47 deletions

View File

@ -21,6 +21,7 @@ hyper-util = { version = "0.1", features = ["full"] }
julianday = "1.2.0"
lazy_static = "1"
lox-library = { git = "https://gitlab.torproject.org/vecna/lox.git", version = "0.1.0" }
rand = { version = "0.8" }
#select = "0.6.0"
serde = "1.0.197"
serde_json = "1.0"
@ -34,4 +35,3 @@ tokio-cron = "0.1.2"
[dev-dependencies]
base64 = "0.21.7"
rand = "0.8.5"

View File

@ -29,6 +29,9 @@ lazy_static! {
pub static ref COUNTRY_CODES: HashSet<&'static str> = HashSet::from(["??","ad","ae","af","ag","ai","al","am","ao","ap","aq","ar","as","at","au","aw","ax","az","ba","bb","bd","be","bf","bg","bh","bi","bj","bl","bm","bn","bo","bq","br","bs","bt","bv","bw","by","bz","ca","cc","cd","cf","cg","ch","ci","ck","cl","cm","cn","co","cr","cs","cu","cv","cw","cx","cy","cz","de","dj","dk","dm","do","dz","ec","ee","eg","eh","er","es","et","eu","fi","fj","fk","fm","fo","fr","ga","gb","gd","ge","gf","gg","gh","gi","gl","gm","gn","gp","gq","gr","gs","gt","gu","gw","gy","hk","hm","hn","hr","ht","hu","id","ie","il","im","in","io","iq","ir","is","it","je","jm","jo","jp","ke","kg","kh","ki","km","kn","kp","kr","kw","ky","kz","la","lb","lc","li","lk","lr","ls","lt","lu","lv","ly","ma","mc","md","me","mf","mg","mh","mk","ml","mm","mn","mo","mp","mq","mr","ms","mt","mu","mv","mw","mx","my","mz","na","nc","ne","nf","ng","ni","nl","no","np","nr","nu","nz","om","pa","pe","pf","pg","ph","pk","pl","pm","pn","pr","ps","pt","pw","py","qa","re","ro","rs","ru","rw","sa","sb","sc","sd","se","sg","sh","si","sj","sk","sl","sm","sn","so","sr","ss","st","sv","sx","sy","sz","tc","td","tf","tg","th","tj","tk","tl","tm","tn","to","tr","tt","tv","tw","tz","ua","ug","um","us","uy","uz","va","vc","ve","vg","vi","vn","vu","wf","ws","ye","yt","za","zm","zw"]);
}
/// We will accept reports up to this many days old.
pub const MAX_BACKDATE: u32 = 3;
/// Get Julian date
pub fn get_date() -> u32 {
time::OffsetDateTime::now_utc()
@ -315,14 +318,12 @@ pub async fn update_extra_infos(
// Process negative reports
/// Negative reports can be deduplicated, so we store to-be-processed
/// negative reports as a map of [report] to [count of report]. Add this
/// NR to that map (or create a new map if necessary).
/// We store to-be-processed negative reports as a vector. Add this NR
/// to that vector (or create a new vector if necessary)
pub fn save_negative_report_to_process(db: &Db, nr: NegativeReport) {
// We serialize the negative reports as strings to use them as map keys.
let mut reports = match db.get("nrs-to-process").unwrap() {
Some(v) => bincode::deserialize(&v).unwrap(),
None => BTreeMap::<String, BTreeMap<String, u32>>::new(),
None => BTreeMap::<String, Vec<SerializableNegativeReport>>::new(),
};
// Store to-be-processed reports with key [fingerprint]_[country]_[date]
let map_key = format!(
@ -331,19 +332,15 @@ pub fn save_negative_report_to_process(db: &Db, nr: NegativeReport) {
&nr.country,
&nr.date,
);
let serialized_nr = nr.to_json();
if reports.contains_key(&map_key) {
let nr_map = reports.get_mut(&map_key).unwrap();
if nr_map.contains_key(&serialized_nr) {
let prev_count = nr_map.get(&serialized_nr).unwrap();
nr_map.insert(serialized_nr, prev_count + 1);
} else {
nr_map.insert(serialized_nr, 1);
}
reports
.get_mut(&map_key)
.unwrap()
.push(nr.to_serializable_report());
} else {
let mut nr_map = BTreeMap::<String, u32>::new();
nr_map.insert(serialized_nr, 1);
reports.insert(map_key, nr_map);
let mut nrs = Vec::<SerializableNegativeReport>::new();
nrs.push(nr.to_serializable_report());
reports.insert(map_key, nrs);
}
// Commit changes to database
db.insert("nrs-to-process", bincode::serialize(&reports).unwrap())
@ -356,15 +353,14 @@ pub fn save_negative_report_to_process(db: &Db, nr: NegativeReport) {
/// distributor.
pub async fn verify_negative_reports(
distributors: &BTreeMap<BridgeDistributor, String>,
reports: &BTreeMap<String, u32>,
reports: &Vec<SerializableNegativeReport>,
) -> u32 {
// Don't make a network call if we don't have any reports anyway
if reports.is_empty() {
return 0;
}
// Get one report, assume the rest have the same distributor
let first_report: SerializableNegativeReport =
serde_json::from_str(reports.first_key_value().unwrap().0).unwrap();
let first_report = &reports[0];
let distributor = first_report.distributor;
let client = Client::new();
let uri: String = (distributors.get(&distributor).unwrap().to_owned() + "/verifynegative")
@ -385,17 +381,16 @@ pub async fn verify_negative_reports(
pub async fn update_negative_reports(db: &Db, distributors: &BTreeMap<BridgeDistributor, String>) {
let mut all_negative_reports = match db.get("nrs-to-process").unwrap() {
Some(v) => bincode::deserialize(&v).unwrap(),
None => BTreeMap::<String, BTreeMap<String, u32>>::new(),
None => BTreeMap::<String, Vec<SerializableNegativeReport>>::new(),
};
// Key is [fingerprint]_[country]_[date]
for bridge_country_date in all_negative_reports.keys() {
let reports = all_negative_reports.get(bridge_country_date).unwrap();
if !reports.is_empty() {
let first_report: SerializableNegativeReport =
serde_json::from_str(reports.first_key_value().unwrap().0).unwrap();
let first_report = &reports[0];
let fingerprint = first_report.fingerprint;
let date = first_report.date;
let country = first_report.country;
let country = first_report.country.clone();
let count_valid = verify_negative_reports(&distributors, reports).await;
// Get bridge info or make new one
@ -421,7 +416,6 @@ pub async fn update_negative_reports(db: &Db, distributors: &BTreeMap<BridgeDist
.info_by_country
.insert(country, bridge_country_info);
}
// Commit changes to database
db.insert(fingerprint, bincode::serialize(&bridge_info).unwrap())
.unwrap();

View File

@ -1,10 +1,11 @@
use crate::{
bridge_verification_info::BridgeVerificationInfo, get_date, BridgeDistributor, COUNTRY_CODES,
MAX_BACKDATE,
};
use curve25519_dalek::scalar::Scalar;
use lox_library::{bridge_table::BridgeLine, cred::Lox};
use rand::RngCore;
use serde::{Deserialize, Serialize};
use sha1::{Digest, Sha1};
use sha3::Sha3_256;
@ -12,6 +13,7 @@ use sha3::Sha3_256;
#[derive(Debug, Serialize)]
pub enum NegativeReportError {
DateInFuture,
DateInPast, // report is more than MAX_BACKDATE days old
FailedToDeserialize, // couldn't deserialize to SerializableNegativeReport
InvalidCountryCode,
MissingCountryCode,
@ -32,6 +34,9 @@ pub struct NegativeReport {
/// today's Julian date
pub date: u32,
/// a random nonce used in the bridge_pok
pub nonce: [u8; 32],
/// the bridge distributor, e.g., Lox, Https, or Moat
pub distributor: BridgeDistributor,
}
@ -42,6 +47,7 @@ impl NegativeReport {
bridge_pok: ProofOfBridgeKnowledge,
country: String,
date: u32,
nonce: [u8; 32],
distributor: BridgeDistributor,
) -> Self {
let mut hasher = Sha1::new();
@ -52,6 +58,7 @@ impl NegativeReport {
bridge_pok,
country,
date,
nonce,
distributor,
}
}
@ -62,24 +69,42 @@ impl NegativeReport {
distributor: BridgeDistributor,
) -> Self {
let date = get_date();
let bridge_pok =
ProofOfBridgeKnowledge::HashOfBridgeLine(HashOfBridgeLine::new(&bridgeline, date));
NegativeReport::new(
let mut rng = rand::thread_rng();
let mut nonce = [0; 32];
rng.fill_bytes(&mut nonce);
let bridge_pok = ProofOfBridgeKnowledge::HashOfBridgeLine(HashOfBridgeLine::new(
&bridgeline,
date,
nonce,
));
Self::new(
bridgeline.fingerprint,
bridge_pok,
country,
date,
nonce,
distributor,
)
}
pub fn from_lox_bucket(bridge_id: [u8; 20], bucket: Scalar, country: String) -> Self {
let date = get_date();
let bridge_pok = ProofOfBridgeKnowledge::HashOfBucket(HashOfBucket::new(&bucket, date));
NegativeReport::new(bridge_id, bridge_pok, country, date, BridgeDistributor::Lox)
let mut rng = rand::thread_rng();
let mut nonce = [0; 32];
rng.fill_bytes(&mut nonce);
let bridge_pok =
ProofOfBridgeKnowledge::HashOfBucket(HashOfBucket::new(&bucket, date, nonce));
Self::new(
bridge_id,
bridge_pok,
country,
date,
nonce,
BridgeDistributor::Lox,
)
}
pub fn from_lox_credential(bridge_id: [u8; 20], cred: Lox, country: String) -> Self {
pub fn from_lox_credential(bridge_id: [u8; 20], cred: &Lox, country: String) -> Self {
NegativeReport::from_lox_bucket(bridge_id, cred.bucket, country)
}
@ -90,6 +115,7 @@ impl NegativeReport {
bridge_pok: self.bridge_pok,
country: self.country,
date: self.date,
nonce: self.nonce,
distributor: self.distributor,
}
}
@ -119,12 +145,12 @@ impl NegativeReport {
pub fn verify(self, bridge_info: &BridgeVerificationInfo) -> bool {
match self.bridge_pok {
ProofOfBridgeKnowledge::HashOfBridgeLine(pok) => {
let hash = HashOfBridgeLine::new(&bridge_info.bridge_line, self.date);
let hash = HashOfBridgeLine::new(&bridge_info.bridge_line, self.date, self.nonce);
hash == pok
}
ProofOfBridgeKnowledge::HashOfBucket(pok) => {
for b in &bridge_info.buckets {
let hash = HashOfBucket::new(&b, self.date);
let hash = HashOfBucket::new(&b, self.date, self.nonce);
if hash == pok {
return true;
}
@ -143,6 +169,7 @@ pub struct SerializableNegativeReport {
bridge_pok: ProofOfBridgeKnowledge,
pub country: String,
pub date: u32,
pub nonce: [u8; 32],
pub distributor: BridgeDistributor,
}
@ -154,14 +181,19 @@ impl SerializableNegativeReport {
if !COUNTRY_CODES.contains(self.country.as_str()) {
return Err(NegativeReportError::InvalidCountryCode);
}
if self.date > get_date().into() {
let date = get_date();
if self.date > date {
return Err(NegativeReportError::DateInFuture);
}
if self.date < date - MAX_BACKDATE {
return Err(NegativeReportError::DateInPast);
}
Ok(NegativeReport {
fingerprint: self.fingerprint,
bridge_pok: self.bridge_pok,
country: self.country.to_string(),
date: self.date.try_into().unwrap(),
nonce: self.nonce,
distributor: self.distributor,
})
}
@ -184,9 +216,10 @@ pub struct HashOfBridgeLine {
}
impl HashOfBridgeLine {
pub fn new(bl: &BridgeLine, date: u32) -> Self {
pub fn new(bl: &BridgeLine, date: u32, nonce: [u8; 32]) -> Self {
let mut hasher = Sha3_256::new();
hasher.update(date.to_le_bytes());
hasher.update(nonce);
hasher.update(bincode::serialize(&bl).unwrap());
let hash: [u8; 32] = hasher.finalize().into();
Self { hash }
@ -200,9 +233,10 @@ pub struct HashOfBucket {
}
impl HashOfBucket {
pub fn new(bucket: &Scalar, date: u32) -> Self {
pub fn new(bucket: &Scalar, date: u32, nonce: [u8; 32]) -> Self {
let mut hasher = Sha3_256::new();
hasher.update(date.to_le_bytes());
hasher.update(nonce);
hasher.update(bucket.to_bytes());
let hash: [u8; 32] = hasher.finalize().into();
Self { hash }

View File

@ -1,7 +1,9 @@
// For Lox-related code where points are uppercase and scalars are lowercase
#![allow(non_snake_case)]
use crate::{bridge_verification_info::BridgeVerificationInfo, get_date, COUNTRY_CODES};
use crate::{
bridge_verification_info::BridgeVerificationInfo, get_date, COUNTRY_CODES, MAX_BACKDATE,
};
use curve25519_dalek::ristretto::RistrettoBasepointTable;
use ed25519_dalek::{Signature, Signer, SigningKey, Verifier};
@ -15,6 +17,7 @@ pub const REQUIRE_BRIDGE_TOKEN: bool = false;
#[derive(Debug, Serialize)]
pub enum PositiveReportError {
DateInFuture,
DateInPast, // report is more than MAX_BACKDATE days old
FailedToDeserialize, // couldn't deserialize to SerializablePositiveReport
InvalidBridgeToken,
InvalidCountryCode,
@ -180,10 +183,13 @@ impl SerializablePositiveReport {
if !COUNTRY_CODES.contains(self.country.as_str()) {
return Err(PositiveReportError::InvalidCountryCode);
}
let date: u32 = get_date().into();
let date: u32 = get_date();
if self.date > date {
return Err(PositiveReportError::DateInFuture);
}
if self.date < date - MAX_BACKDATE {
return Err(PositiveReportError::DateInPast);
}
if self.lox_proof.date != date {
return Err(PositiveReportError::InvalidLoxProof);
}

View File

@ -210,12 +210,64 @@ fn test_negative_reports() {
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());
NegativeReport::from_lox_credential(bridges[2].fingerprint, &cred, "ru".to_string());
// Backdated reports
let date = get_date();
let mut rng = rand::thread_rng();
let mut nonce = [0; 32];
rng.fill_bytes(&mut nonce);
let report_4 = NegativeReport::new(
bridges[0].fingerprint,
ProofOfBridgeKnowledge::HashOfBridgeLine(HashOfBridgeLine::new(
&bridges[0],
date - 1,
nonce,
)),
"ru".to_string(),
date - 1,
nonce,
BridgeDistributor::Lox,
);
let mut nonce = [0; 32];
rng.fill_bytes(&mut nonce);
let report_5 = NegativeReport::new(
bridges[1].fingerprint,
ProofOfBridgeKnowledge::HashOfBridgeLine(HashOfBridgeLine::new(
&bridges[1],
date - 2,
nonce,
)),
"ru".to_string(),
date - 2,
nonce,
BridgeDistributor::Lox,
);
let mut nonce = [0; 32];
rng.fill_bytes(&mut nonce);
let report_6 = NegativeReport::new(
bridges[2].fingerprint,
ProofOfBridgeKnowledge::HashOfBridgeLine(HashOfBridgeLine::new(
&bridges[2],
date - 3,
nonce,
)),
"ru".to_string(),
date - 3,
nonce,
BridgeDistributor::Lox,
);
// Verify reports
assert!(report_1.verify(&bridge_info_1));
assert!(report_2.verify(&bridge_info_2));
assert!(report_3.verify(&bridge_info_3));
assert!(report_4.verify(&bridge_info_1));
assert!(report_5.verify(&bridge_info_2));
assert!(report_6.verify(&bridge_info_3));
// Check that deserialization fails under invalid conditions
@ -225,41 +277,56 @@ fn test_negative_reports() {
.to_serializable_report();
invalid_report_1.date = invalid_report_1.date + 2;
// Date too far in past
let mut invalid_report_2 =
NegativeReport::from_bridgeline(bridges[1], "ru".to_string(), BridgeDistributor::Lox)
.to_serializable_report();
invalid_report_2.date = invalid_report_2.date - MAX_BACKDATE - 1;
// Invalid country code
let invalid_report_2 =
NegativeReport::from_bridgeline(bridges[1], "xx".to_string(), BridgeDistributor::Lox)
let invalid_report_3 =
NegativeReport::from_bridgeline(bridges[2], "xx".to_string(), BridgeDistributor::Lox)
.to_serializable_report();
assert!(invalid_report_1.to_report().is_err());
assert!(invalid_report_2.to_report().is_err());
assert!(invalid_report_3.to_report().is_err());
// Check that verification fails with incorrect data
let date = get_date();
let mut rng = rand::thread_rng();
// Incorrect BridgeLine hash
let invalid_report_3 = NegativeReport::new(
let mut nonce = [0; 32];
rng.fill_bytes(&mut nonce);
let invalid_report_4 = NegativeReport::new(
bridges[0].fingerprint,
ProofOfBridgeKnowledge::HashOfBridgeLine(HashOfBridgeLine::new(
&BridgeLine::default(),
date,
nonce,
)),
"ru".to_string(),
date,
nonce,
BridgeDistributor::Lox,
);
// Incorrect bucket hash
let invalid_report_4 = NegativeReport::new(
let mut nonce = [0; 32];
rng.fill_bytes(&mut nonce);
let invalid_report_5 = NegativeReport::new(
bridges[1].fingerprint,
ProofOfBridgeKnowledge::HashOfBucket(HashOfBucket::new(&Scalar::ZERO, date)),
ProofOfBridgeKnowledge::HashOfBucket(HashOfBucket::new(&Scalar::ZERO, date, nonce)),
"ru".to_string(),
date,
nonce,
BridgeDistributor::Lox,
);
assert!(!invalid_report_3.verify(&bridge_info_1));
assert!(!invalid_report_4.verify(&bridge_info_2));
assert!(!invalid_report_4.verify(&bridge_info_1));
assert!(!invalid_report_5.verify(&bridge_info_2));
}
#[test]