use curve25519_dalek::scalar::Scalar; use ed25519_dalek::{Signature, Signer, SigningKey, Verifier, VerifyingKey}; use lox_library::bridge_table::{BridgeLine, MAX_BRIDGES_PER_BUCKET}; use lox_library::cred::Lox; use lox_library::IssuerPubKey; use lox_library::proto::positive_report; use serde::{Deserialize, Serialize}; use sha1::{Digest, Sha1}; use sha3::Sha3_256; // for generating ed25519 keys during initial development use rand::rngs::OsRng; // TODO: These should be loaded from config file pub const REQUIRE_BRIDGE_TOKEN: bool = true; /// The minimum trust level a Lox credential must have to be allowed to /// submit a positive report pub const PR_MIN_TRUST_LEVEL: u32 = 3; /// 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() } /// Get bucket from hash of bucket ID, requires some oracle pub fn get_bucket(beta_hash: &[u8; 32]) -> [BridgeLine; MAX_BRIDGES_PER_BUCKET] { // TODO // for now just return bucket of empty bridgelines [ BridgeLine::default(), BridgeLine::default(), BridgeLine::default(), ] } /// 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(HashOfBridgeLine), /// Hash of bucket ID for Lox user HashOfBucket(HashOfBucket), } impl ProofOfBridgeKnowledge { pub fn verify(&self, bridge_fingerprint: [u8; 20]) -> bool { // TODO: It seems like there ought to be a cleaner way to do this? match self { ProofOfBridgeKnowledge::HashOfBridgeLine(bl_hash) => bl_hash.verify(bridge_fingerprint), ProofOfBridgeKnowledge::HashOfBucket(bucket_hash) => { bucket_hash.verify(bridge_fingerprint) } } } } /// Hash of bridge line to prove knowledge of that bridge #[derive(PartialEq, Serialize, Deserialize)] pub struct HashOfBridgeLine { hash: [u8; 32], } impl HashOfBridgeLine { pub fn new(bl: BridgeLine) -> Self { let mut hasher = Sha3_256::new(); hasher.update(bincode::serialize(&bl).unwrap()); let hash: [u8; 32] = hasher.finalize().into(); Self { hash } } pub fn verify(&self, bridge_fingerprint: [u8; 20]) -> bool { let bl = get_bridge_line(&bridge_fingerprint); self == &HashOfBridgeLine::new(bl) } } /// Hash of bucket ID to prove knowledge of bridges in that bucket #[derive(PartialEq, Serialize, Deserialize)] pub struct HashOfBucket { hash: [u8; 32], } impl HashOfBucket { pub fn new(bucket: Scalar) -> Self { let mut hasher = Sha3_256::new(); hasher.update(bucket.to_bytes()); let hash: [u8; 32] = hasher.finalize().into(); Self { hash } } pub fn verify(&self, bridge_fingerprint: [u8; 20]) -> bool { let bucket = get_bucket(&self.hash); for bl in bucket { let mut hasher = Sha1::new(); hasher.update(bl.uid_fingerprint.to_le_bytes()); if bridge_fingerprint == <[u8; 20]>::from(hasher.finalize()) { return true; } } false } } /// Reports from users about whether or not their bridges are blocked #[derive(Serialize, Deserialize)] pub enum Report { /// Negative report indicating user was unable to connect NegativeUserReport(NegativeUserReport), /// Positive report indicating user was able to connect PositiveUserReport(PositiveUserReport), } impl Report { fn verify(&self) -> bool { match self { Report::NegativeUserReport(report) => report.verify(), Report::PositiveUserReport(report) => report.verify(), } } } /// 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 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], bridge_pok: ProofOfBridgeKnowledge, 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_pok, country, today, } } pub fn from_bridgeline( &self, bridge_id: [u8; 20], bridgeline: BridgeLine, country: String, ) -> Self { let bridge_pok = ProofOfBridgeKnowledge::HashOfBridgeLine(HashOfBridgeLine::new(bridgeline)); NegativeUserReport::new(bridge_id, bridge_pok, country) } pub fn from_bucket(&self, bridge_id: [u8; 20], bucket: Scalar, country: String) -> Self { let mut hasher = Sha3_256::new(); hasher.update(bucket.to_bytes()); let bucket_hash: [u8; 32] = hasher.finalize().into(); let bridge_pok = ProofOfBridgeKnowledge::HashOfBucket(HashOfBucket { hash: bucket_hash }); NegativeUserReport::new(bridge_id, bridge_pok, country) } pub fn from_lox_credential(&self, bridge_id: [u8; 20], cred: Lox, country: String) -> Self { self.from_bucket(bridge_id, cred.bucket, country) } 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 bridge_token: Option, // proof of Lox cred with level >= 3 and this bridge lox_proof: positive_report::Request, /// 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: Option, lox_proof: positive_report::Request, 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, lox_proof, country, today, } } pub fn from_lox_credential(bridge_id: [u8; 20], bridge_token: Option, lox_cred: &Lox, lox_pub: &IssuerPubKey, country: String) -> Self { let lox_proof = positive_report::request(lox_cred, lox_pub).unwrap(); PositiveUserReport::new(bridge_id, bridge_token, lox_proof, country) } fn verify(&self) -> bool { // possibly include check that self.today is recent as well self.today <= today() && (!REQUIRE_BRIDGE_TOKEN || { if self.bridge_token.is_none() { false } else { let bt = self.bridge_token.as_ref().unwrap(); self.today == bt.unsigned_bridge_token.today && bt.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() } }