// For Lox-related code where points are uppercase and scalars are lowercase #![allow(non_snake_case)] 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 InvalidBridgeToken, 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 { 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: 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 (if bridge token is required) /// 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( &bridge_token.unsigned_bridge_token.to_bincode(), &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 { // Check that fields are valid 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); } 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: 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 == "" || !COUNTRY_CODES.contains(self.country.as_str()) || self.date > get_date().into() { 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: unsigned_bridge_token, sig: self.sig, }) } }