use crate::{CONFIG, get_date, COUNTRY_CODES}; use ed25519_dalek::{Signature, Signer, SigningKey}; use lox_library::{cred::Lox, proto::positive_report as lox_pr, IssuerPubKey}; use serde::{Deserialize, Serialize}; use sha1::{Digest, Sha1}; use std::option::Option; #[derive(Debug)] pub enum PositiveReportError { DateInFuture, FailedToDeserialize, // couldn't deserialize to SerializablePositiveReport InvalidCountryCode, 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() { 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, ) -> Self { let lox_proof = lox_pr::request(lox_cred, lox_pub).unwrap(); PositiveReport::new(bridge_id, bridge_token, lox_proof, country) } /// Convert report to a serializable version pub fn to_serializable_report(self) -> SerializablePositiveReport { SerializablePositiveReport { fingerprint: self.fingerprint, bridge_token: self.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), } } } /// (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 { if CONFIG.require_bridge_token && self.bridge_token.is_none() { return Err(PositiveReportError::MissingBridgeToken); } if self.country == "" { return Err(PositiveReportError::MissingCountryCode); } if !COUNTRY_CODES.contains(self.country.as_str()) { return Err(PositiveReportError::InvalidCountryCode); } if self.date > get_date().into() { return Err(PositiveReportError::DateInFuture); } Ok(PositiveReport { fingerprint: self.fingerprint, bridge_token: self.bridge_token, lox_proof: self.lox_proof, country: self.country, date: self.date, }) } } /// 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 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, } } } /// 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, } } }