diff --git a/Cargo.toml b/Cargo.toml index 298edb7..ffc1113 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,3 +6,16 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +array-bytes = "6.2.0" +bincode = "1" +curve25519-dalek = { version = "4", default-features = false, features = ["serde", "rand_core", "digest"] } +ed25519-dalek = { version = "2", features = ["serde", "rand_core"] } +lox-library = { git = "https://gitlab.torproject.org/tpo/anti-censorship/lox.git", version = "0.1.0" } +serde = "1.0.192" +serde_with = {version = "3.4.0", features = ["json"]} +sha1 = "0.10" +sha3 = "0.10" +time = "0.3.30" + +# probably not needed once I can query an API +rand = { version = "0.8", features = ["std_rng"]} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..105162d --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,191 @@ +use curve25519_dalek::scalar::Scalar; +use ed25519_dalek::{Signature, Signer, SigningKey, Verifier, VerifyingKey}; +use lox_library::bridge_table::BridgeLine; +use serde::{Deserialize, Serialize}; +use sha1::{Digest, Sha1}; +use sha3::Sha3_256; + +// for generating ed25519 keys during initial development +use rand::rngs::OsRng; + +/// Get Julian date +pub fn today() -> u32 { + time::OffsetDateTime::now_utc() + .date() + .to_julian_day() + .try_into() + .unwrap() +} + +/// Get bridge line for a bridge, requires some oracle +pub fn get_bridge_line(fingerprint: &[u8; 20]) -> BridgeLine { + // TODO + // for now just return empty bridgeline + BridgeLine::default() +} + +/// Get verifying key for a bridge, requires some oracle +pub fn get_bridge_signing_pubkey(fingerprint: &[u8; 20]) -> VerifyingKey { + // TODO + // for now just return new pubkey + let mut csprng = OsRng {}; + let keypair = SigningKey::generate(&mut csprng); + keypair.verifying_key() +} + +/// Proof that the user knows (and should be able to access) a given bridge +#[derive(Serialize, Deserialize)] +pub enum ProofOfBridgeKnowledge { + /// Hash of bridge line as proof of knowledge of bridge line + HashOfBridgeLine { hash: [u8; 32] }, +} + +impl ProofOfBridgeKnowledge { + fn verify(&self, fingerprint: [u8; 20]) -> bool { + match *self { + ProofOfBridgeKnowledge::HashOfBridgeLine { ref hash } => { + let bl = get_bridge_line(&fingerprint); + let mut hasher = Sha3_256::new(); + hasher.update(bincode::serialize(&bl).unwrap()); + let bl_hash: [u8; 32] = hasher.finalize().into(); + hash == &bl_hash + } + } + } +} + +pub trait Report { + fn verify(&self) -> bool; +} + +/// A report that the user was unable to connect to the bridge +#[derive(Serialize, Deserialize)] +pub struct NegativeUserReport { + /// hashed fingerprint (SHA-1 hash of 20-byte bridge ID) + pub fingerprint: [u8; 20], + /// some way to prove knowledge of bridge + pub bridge_pok: ProofOfBridgeKnowledge, + /// user's country code, may be an empty string + pub country: String, + /// today's Julian date + pub today: u32, +} + +impl NegativeUserReport { + pub fn new(bridge_id: [u8; 20], bucket: Scalar, country: String) -> Self { + let mut hasher = Sha1::new(); + hasher.update(bridge_id); + let fingerprint: [u8; 20] = hasher.finalize().into(); + let mut hasher = Sha3_256::new(); + hasher.update(bucket.to_bytes()); + let bucket_hash: [u8; 32] = hasher.finalize().into(); + let bridge_pok = ProofOfBridgeKnowledge::HashOfBridgeLine { hash: bucket_hash }; + let today = today(); + Self { + fingerprint, + bridge_pok, + country, + today, + } + } +} + +impl Report for NegativeUserReport { + fn verify(&self) -> bool { + // possibly include check that self.today is recent as well + self.today <= today() && self.bridge_pok.verify(self.fingerprint) + } +} + +/// A report that the user was able to connect to the bridge +#[derive(Serialize, Deserialize)] +pub struct PositiveUserReport { + /// hashed fingerprint (SHA-1 hash of 20-byte bridge ID) + pub fingerprint: [u8; 20], + /// token from the bridge indicating it was reached + pub bridge_token: BridgeToken, + // TODO: proof of level, something involving credential show + /// user's country code, may be an empty string + pub country: String, + /// today's Julian date + pub today: u32, +} + +impl PositiveUserReport { + pub fn new(bridge_id: [u8; 20], bridge_token: BridgeToken, country: String) -> Self { + let mut hasher = Sha1::new(); + hasher.update(bridge_id); + let fingerprint: [u8; 20] = hasher.finalize().into(); + let today = today(); + Self { + fingerprint, + bridge_token, + country, + today, + } + } +} + +impl Report for PositiveUserReport { + fn verify(&self) -> bool { + // possibly include check that self.today is recent as well + self.today == self.bridge_token.unsigned_bridge_token.today + && self.today <= today() + && self.bridge_token.verify() + } +} + +/// An unsigned token which indicates that the bridge was reached +#[derive(Serialize, Deserialize)] +pub struct UnsignedBridgeToken { + /// hashed fingerprint (SHA-1 hash of 20-byte bridge ID) + pub fingerprint: [u8; 20], + /// client's country code + pub country: String, + /// today's Julian date + pub today: u32, +} + +impl UnsignedBridgeToken { + pub fn new(bridge_id: [u8; 20], country: String) -> Self { + let mut hasher = Sha1::new(); + hasher.update(bridge_id); + let fingerprint: [u8; 20] = hasher.finalize().into(); + let today = today(); + Self { + fingerprint, + country, + today, + } + } +} + +/// A signed token which indicates that the bridge was reached +#[derive(Serialize, Deserialize)] +pub struct BridgeToken { + /// the unsigned version of this token + pub unsigned_bridge_token: UnsignedBridgeToken, + /// signature from bridge's ed25519 key + pub sig: Signature, +} + +impl BridgeToken { + pub fn new(unsigned_bridge_token: UnsignedBridgeToken, keypair: SigningKey) -> Self { + let sig = keypair.sign(&bincode::serialize(&unsigned_bridge_token).unwrap()); + Self { + unsigned_bridge_token, + sig, + } + } + + pub fn verify(&self) -> bool { + let pubkey = get_bridge_signing_pubkey(&self.unsigned_bridge_token.fingerprint); + self.unsigned_bridge_token.today <= today() + && pubkey + .verify( + &bincode::serialize(&self.unsigned_bridge_token).unwrap(), + &self.sig, + ) + .is_ok() + } +}