diff --git a/src/negative_report.rs b/src/negative_report.rs new file mode 100644 index 0000000..e6ccdf2 --- /dev/null +++ b/src/negative_report.rs @@ -0,0 +1,149 @@ +use crate::{get_date, COUNTRY_CODES}; + +use curve25519_dalek::scalar::Scalar; +use lox_library::{bridge_table::BridgeLine, cred::Lox}; + +use serde::{Deserialize, Serialize}; +use sha1::{Digest, Sha1}; +use sha3::Sha3_256; + +#[derive(Debug)] +pub enum NegativeReportError { + DateInFuture, + FailedToDeserialize, // couldn't deserialize to SerializableNegativeReport + InvalidCountryCode, +} + +/// A report that the user was unable to connect to the bridge +pub struct NegativeReport { + /// 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 date: u32, +} + +impl NegativeReport { + 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 date = get_date(); + Self { + fingerprint, + bridge_pok, + country, + date, + } + } + + pub fn from_bridgeline(bridge_id: [u8; 20], bridgeline: BridgeLine, country: String) -> Self { + let bridge_pok = + ProofOfBridgeKnowledge::HashOfBridgeLine(HashOfBridgeLine::new(bridgeline)); + NegativeReport::new(bridge_id, bridge_pok, country) + } + + pub fn from_bucket(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 }); + NegativeReport::new(bridge_id, bridge_pok, country) + } + + pub fn from_lox_credential(bridge_id: [u8; 20], cred: Lox, country: String) -> Self { + NegativeReport::from_bucket(bridge_id, cred.bucket, country) + } + + /// Convert report to a serializable version + pub fn to_serializable_report(self) -> SerializableNegativeReport { + SerializableNegativeReport { + fingerprint: self.fingerprint, + bridge_pok: self.bridge_pok, + 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(NegativeReportError::FailedToDeserialize), + } + } +} + +/// (De)serializable negative report object which must be consumed by the +/// checking function before it can be used +#[derive(Deserialize, Serialize)] +pub struct SerializableNegativeReport { + pub fingerprint: [u8; 20], + bridge_pok: ProofOfBridgeKnowledge, + pub country: String, + pub date: u32, +} + +impl SerializableNegativeReport { + pub fn to_report(self) -> Result { + if self.country != "" && !COUNTRY_CODES.contains(self.country.as_str()) { + return Err(NegativeReportError::InvalidCountryCode); + } + if self.date > get_date().into() { + return Err(NegativeReportError::DateInFuture); + } + Ok(NegativeReport { + fingerprint: self.fingerprint, + bridge_pok: self.bridge_pok, + country: self.country.to_string(), + date: self.date.try_into().unwrap(), + }) + } +} + +/// 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), +} + +/// 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 } + } +} + +/// 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 } + } +} diff --git a/src/positive_report.rs b/src/positive_report.rs new file mode 100644 index 0000000..b9244ce --- /dev/null +++ b/src/positive_report.rs @@ -0,0 +1,168 @@ +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, + } + } +}