// 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, MAX_BACKDATE, }; use curve25519_dalek::ristretto::RistrettoBasepointTable; use ed25519_dalek::{Signature, Signer, SigningKey, Verifier}; use lox_library::{cred::Lox, proto::positive_report as lox_pr, BridgeAuth, IssuerPubKey}; use serde::{Deserialize, Serialize}; use sha1::{Digest, Sha1}; use std::option::Option; 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 PositiveReport InvalidBridgeToken, InvalidCountryCode, InvalidLoxProof, MissingBridgeToken, MissingCountryCode, } /// A report that the user was able to connect to the bridge pub struct PositiveReport { /// 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: lox_pr::Request, /// user's country code, may be an empty string pub country: String, /// today's Julian date pub date: u32, } impl PositiveReport { pub fn new( bridge_id: [u8; 20], bridge_token: Option, lox_proof: lox_pr::Request, country: String, ) -> Self { //if CONFIG.require_bridge_token && bridge_token.is_none() { if REQUIRE_BRIDGE_TOKEN && bridge_token.is_none() { panic!("Bridge tokens are required for positive reports."); } let mut hasher = Sha1::new(); hasher.update(bridge_id); let fingerprint: [u8; 20] = hasher.finalize().into(); let date = get_date(); Self { fingerprint, bridge_token, lox_proof, country, date, } } pub fn from_lox_credential( bridge_id: [u8; 20], bridge_token: Option, lox_cred: &Lox, lox_pub: &IssuerPubKey, country: String, ) -> Result { 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 pub fn to_serializable_report(self) -> SerializablePositiveReport { let bridge_token = if self.bridge_token.is_none() { None } else { Some(self.bridge_token.unwrap().to_serializable_bridge_token()) }; SerializablePositiveReport { fingerprint: self.fingerprint, bridge_token, lox_proof: self.lox_proof, country: self.country, date: self.date, } } /// Serializes the report, eliding the underlying process pub fn to_json(self) -> String { serde_json::to_string(&self.to_serializable_report()).unwrap() } /// Deserializes the report, eliding the underlying process pub fn from_json(str: String) -> Result { match serde_json::from_str::(&str) { Ok(v) => v.to_report(), Err(_) => Err(PositiveReportError::FailedToDeserialize), } } /// Deserializes the report from slice, eliding the underlying process pub fn from_slice(slice: &[u8]) -> Result { match serde_json::from_slice::(slice) { Ok(v) => v.to_report(), Err(_) => Err(PositiveReportError::FailedToDeserialize), } } /// Verify report pub fn verify( self, la: &mut BridgeAuth, bridge_info: &BridgeVerificationInfo, Htable: &RistrettoBasepointTable, ) -> bool { // Verify bridge token //if CONFIG.require_bridge_token { if REQUIRE_BRIDGE_TOKEN { let bridge_token = self.bridge_token.unwrap(); let bridge_key = bridge_info.pubkey; if bridge_key.is_none() { return false; } if bridge_key .unwrap() .verify( &bridge_token.unsigned_bridge_token.to_bincode(), &bridge_token.sig, ) .is_err() { return false; } } // Verify knowledge of bucket ID let buckets = &bridge_info.buckets; let BP = self.lox_proof.BP; for bucket in buckets { if bucket * Htable == BP { return la.handle_positive_report(self.lox_proof, Htable).is_ok(); } } false } } /// (De)serializable positive report object which must be consumed by the /// checking function before it can be used #[derive(Deserialize, Serialize)] pub struct SerializablePositiveReport { pub fingerprint: [u8; 20], bridge_token: Option, lox_proof: lox_pr::Request, pub country: String, pub date: u32, } impl SerializablePositiveReport { pub fn to_report(self) -> Result { // Check that fields are valid //if CONFIG.require_bridge_token && self.bridge_token.is_none() { if REQUIRE_BRIDGE_TOKEN && self.bridge_token.is_none() { return Err(PositiveReportError::MissingBridgeToken); } if self.country.is_empty() { return Err(PositiveReportError::MissingCountryCode); } if !COUNTRY_CODES.contains(self.country.as_str()) { return Err(PositiveReportError::InvalidCountryCode); } 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); } let bridge_token = if self.bridge_token.is_none() { None } else { let bridge_token_unchecked = self.bridge_token.unwrap().to_bridge_token()?; // Check that bridge token fields match report fields... // The user may override the bridge's autodetected country code, // so allow the country code to be different. if self.fingerprint != bridge_token_unchecked.unsigned_bridge_token.fingerprint || self.date != bridge_token_unchecked.unsigned_bridge_token.date { return Err(PositiveReportError::InvalidBridgeToken); } Some(bridge_token_unchecked) }; Ok(PositiveReport { fingerprint: self.fingerprint, bridge_token, lox_proof: self.lox_proof, country: self.country, date: self.date, }) } } /// An unsigned token which indicates that the bridge was reached 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 date: 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 date = get_date(); Self { fingerprint, country, date, } } pub fn to_serializable_unsigned_bridge_token(self) -> SerializableUnsignedBridgeToken { SerializableUnsignedBridgeToken { fingerprint: self.fingerprint, country: self.country, date: self.date, } } /// Serializes the token, eliding the underlying process pub fn to_bincode(self) -> Vec { bincode::serialize(&self.to_serializable_unsigned_bridge_token()).unwrap() } /// Deserializes the token, eliding the underlying process pub fn from_bincode(vec: Vec) -> Result { match bincode::deserialize::(&vec[..]) { Ok(v) => v.to_unsigned_bridge_token(), Err(_) => Err(PositiveReportError::FailedToDeserialize), } } } /// (De)serializable unsigned bridge token object which must be consumed by the /// checking function before it can be used #[derive(Serialize, Deserialize)] pub struct SerializableUnsignedBridgeToken { pub fingerprint: [u8; 20], pub country: String, pub date: u32, } impl SerializableUnsignedBridgeToken { pub fn to_unsigned_bridge_token(self) -> Result { if self.country.is_empty() || !COUNTRY_CODES.contains(self.country.as_str()) || self.date > get_date() { return Err(PositiveReportError::InvalidBridgeToken); } Ok(UnsignedBridgeToken { fingerprint: self.fingerprint, country: self.country, date: self.date, }) } } /// A signed token which indicates that the bridge was reached pub struct BridgeToken { /// the unsigned version of this token pub unsigned_bridge_token: UnsignedBridgeToken, /// signature from bridge's ed25519 key sig: Signature, } impl BridgeToken { pub fn new(unsigned_bridge_token: UnsignedBridgeToken, keypair: SigningKey) -> Self { let bin = unsigned_bridge_token.to_bincode(); let sig = keypair.sign(&bin); let unsigned_bridge_token = UnsignedBridgeToken::from_bincode(bin).unwrap(); Self { unsigned_bridge_token, sig, } } /// Convert bridge token to a serializable version pub fn to_serializable_bridge_token(self) -> SerializableBridgeToken { SerializableBridgeToken { unsigned_bridge_token: self .unsigned_bridge_token .to_serializable_unsigned_bridge_token(), sig: self.sig, } } /// Serializes the bridge token, eliding the underlying process pub fn to_json(self) -> String { serde_json::to_string(&self.to_serializable_bridge_token()).unwrap() } /// Deserializes the bridge token, eliding the underlying process pub fn from_json(str: String) -> Result { match serde_json::from_str::(&str) { Ok(v) => v.to_bridge_token(), Err(_) => Err(PositiveReportError::InvalidBridgeToken), } } } /// (De)serializable bridge token object which must be consumed by the /// checking function before it can be used #[derive(Serialize, Deserialize)] pub struct SerializableBridgeToken { pub unsigned_bridge_token: SerializableUnsignedBridgeToken, sig: Signature, } impl SerializableBridgeToken { pub fn to_bridge_token(self) -> Result { let unsigned_bridge_token = self.unsigned_bridge_token.to_unsigned_bridge_token()?; Ok(BridgeToken { unsigned_bridge_token, sig: self.sig, }) } }