// For Lox-related code where points are uppercase and scalars are lowercase #![allow(non_snake_case)] // TODO: Make SerializableBridgeToken, check its fields while deserializing, // check that its fields match the report's fields while deserializing a report use crate::{get_date, CONFIG, COUNTRY_CODES}; use curve25519_dalek::Scalar; use ed25519_dalek::{Signature, Signer, SigningKey, Verifier, VerifyingKey}; 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), } } /// Verify everything except the Lox proof. /// Parameters: /// - The bucket ID for the bucket containing this bridge /// - The bridge verifying key for this bridge /// These parameters are assumed to be correct and are NOT checked against /// the fingerprint listed in the report. pub fn verify_excluding_lox_proof( self, bucket: Scalar, bridge_key: Option, ) -> bool { // Verify bridge token if CONFIG.require_bridge_token { let bridge_token = self.bridge_token.unwrap(); if bridge_key.is_none() { return false; } if bridge_key .unwrap() .verify( &bincode::serialize(&bridge_token.unsigned_bridge_token).unwrap(), &bridge_token.sig, ) .is_err() { return false; } } // Verify knowledge of bucket ID let H = self.lox_proof.H; let BP = self.lox_proof.BP; if bucket * H != BP { return false; } true } } /// (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, } } }