Check fields when deserializing user reports
This commit is contained in:
parent
76dd724613
commit
1e44e16bb1
|
@ -10,8 +10,10 @@ array-bytes = "6.2.0"
|
||||||
bincode = "1"
|
bincode = "1"
|
||||||
curve25519-dalek = { version = "4", default-features = false, features = ["serde", "rand_core", "digest"] }
|
curve25519-dalek = { version = "4", default-features = false, features = ["serde", "rand_core", "digest"] }
|
||||||
ed25519-dalek = { version = "2", features = ["serde", "rand_core"] }
|
ed25519-dalek = { version = "2", features = ["serde", "rand_core"] }
|
||||||
|
lazy_static = "1"
|
||||||
lox-library = { git = "https://gitlab.torproject.org/vecna/lox.git", version = "0.1.0" }
|
lox-library = { git = "https://gitlab.torproject.org/vecna/lox.git", version = "0.1.0" }
|
||||||
serde = "1.0.195"
|
serde = "1.0.195"
|
||||||
|
serde_json = "1.0"
|
||||||
serde_with = {version = "3.4.0", features = ["json"]}
|
serde_with = {version = "3.4.0", features = ["json"]}
|
||||||
sha1 = "0.10"
|
sha1 = "0.10"
|
||||||
sha3 = "0.10"
|
sha3 = "0.10"
|
||||||
|
|
439
src/lib.rs
439
src/lib.rs
|
@ -1,25 +1,35 @@
|
||||||
use curve25519_dalek::scalar::Scalar;
|
use curve25519_dalek::scalar::Scalar;
|
||||||
use ed25519_dalek::{Signature, Signer, SigningKey, Verifier, VerifyingKey};
|
use ed25519_dalek::{Signature, Signer, SigningKey, Verifier, VerifyingKey};
|
||||||
|
use lazy_static::lazy_static;
|
||||||
use lox_library::bridge_table::{BridgeLine, MAX_BRIDGES_PER_BUCKET};
|
use lox_library::bridge_table::{BridgeLine, MAX_BRIDGES_PER_BUCKET};
|
||||||
use lox_library::cred::Lox;
|
use lox_library::cred::Lox;
|
||||||
use lox_library::IssuerPubKey;
|
|
||||||
use lox_library::proto::positive_report;
|
use lox_library::proto::positive_report;
|
||||||
|
use lox_library::IssuerPubKey;
|
||||||
|
use serde::de::{self, Deserializer, MapAccess, SeqAccess, Unexpected, Visitor};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use sha1::{Digest, Sha1};
|
use sha1::{Digest, Sha1};
|
||||||
use sha3::Sha3_256;
|
use sha3::Sha3_256;
|
||||||
|
use std::collections::HashSet;
|
||||||
|
use std::fmt;
|
||||||
|
use std::option::Option;
|
||||||
|
|
||||||
// for generating ed25519 keys during initial development
|
// for generating ed25519 keys during initial development
|
||||||
use rand::rngs::OsRng;
|
use rand::rngs::OsRng;
|
||||||
|
|
||||||
// TODO: These should be loaded from config file
|
// TODO: These should be loaded from config file
|
||||||
pub const REQUIRE_BRIDGE_TOKEN: bool = true;
|
pub const REQUIRE_BRIDGE_TOKEN: bool = false;
|
||||||
|
|
||||||
|
lazy_static! {
|
||||||
|
// known country codes, TODO: Verify these are correct
|
||||||
|
pub static ref COUNTRY_CODES: HashSet<&'static str> = HashSet::from(["ac","af","ax","al","dz","ad","ao","ai","aq","ag","ar","am","aw","au","at","az","bs","bh","bd","bb","by","be","bz","bj","bm","bt","bo","ba","bw","bv","br","io","vg","bn","bg","bf","bi","kh","cm","ca","cv","ky","cf","td","cl","cn","cx","cc","co","km","cg","cd","ck","cr","ci","hr","cu","cy","cz","dk","dj","dm","do","tp","ec","eg","sv","gq","ee","et","fk","fo","fj","fi","fr","fx","gf","pf","tf","ga","gm","ge","de","gh","gi","gr","gl","gd","gp","gu","gt","gn","gw","gy","ht","hm","hn","hk","hu","is","in","id","ir","iq","ie","im","il","it","jm","jp","jo","kz","ke","ki","kp","kr","kw","kg","la","lv","lb","ls","lr","ly","li","lt","lu","mo","mk","mg","mw","my","mv","ml","mt","mh","mq","mr","mu","yt","mx","fm","md","mc","mn","me","ms","ma","mz","mm","na","nr","np","an","nl","nc","nz","ni","ne","ng","nu","nf","mp","no","om","pk","pw","ps","pa","pg","py","pe","ph","pn","pl","pt","pr","qa","re","ro","ru","rw","ws","sm","st","sa","uk","sn","rs","sc","sl","sg","sk","si","sb","so","as","za","gs","su","es","lk","sh","kn","lc","pm","vc","sd","sr","sj","sz","se","ch","sy","tw","tj","tz","th","tg","tk","to","tt","tn","tr","tm","tc","tv","ug","ua","ae","gb","uk","us","um","uy","uz","vu","va","ve","vn","vi","wf","eh","ye","zm","zw"]);
|
||||||
|
}
|
||||||
|
|
||||||
/// The minimum trust level a Lox credential must have to be allowed to
|
/// The minimum trust level a Lox credential must have to be allowed to
|
||||||
/// submit a positive report
|
/// submit a positive report
|
||||||
pub const PR_MIN_TRUST_LEVEL: u32 = 3;
|
pub const PR_MIN_TRUST_LEVEL: u32 = 3;
|
||||||
|
|
||||||
/// Get Julian date
|
/// Get Julian date
|
||||||
pub fn today() -> u32 {
|
pub fn get_date() -> u32 {
|
||||||
time::OffsetDateTime::now_utc()
|
time::OffsetDateTime::now_utc()
|
||||||
.date()
|
.date()
|
||||||
.to_julian_day()
|
.to_julian_day()
|
||||||
|
@ -126,23 +136,23 @@ impl HashOfBucket {
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
pub enum Report {
|
pub enum Report {
|
||||||
/// Negative report indicating user was unable to connect
|
/// Negative report indicating user was unable to connect
|
||||||
NegativeUserReport(NegativeUserReport),
|
NegativeReport(NegativeReport),
|
||||||
/// Positive report indicating user was able to connect
|
/// Positive report indicating user was able to connect
|
||||||
PositiveUserReport(PositiveUserReport),
|
PositiveReport(PositiveReport),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Report {
|
impl Report {
|
||||||
fn verify(&self) -> bool {
|
fn verify(&self) -> bool {
|
||||||
match self {
|
match self {
|
||||||
Report::NegativeUserReport(report) => report.verify(),
|
Report::NegativeReport(report) => report.verify(),
|
||||||
Report::PositiveUserReport(report) => report.verify(),
|
Report::PositiveReport(report) => report.verify(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A report that the user was unable to connect to the bridge
|
/// A report that the user was unable to connect to the bridge
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize)]
|
||||||
pub struct NegativeUserReport {
|
pub struct NegativeReport {
|
||||||
/// hashed fingerprint (SHA-1 hash of 20-byte bridge ID)
|
/// hashed fingerprint (SHA-1 hash of 20-byte bridge ID)
|
||||||
pub fingerprint: [u8; 20],
|
pub fingerprint: [u8; 20],
|
||||||
/// some way to prove knowledge of bridge
|
/// some way to prove knowledge of bridge
|
||||||
|
@ -150,55 +160,197 @@ pub struct NegativeUserReport {
|
||||||
/// user's country code, may be an empty string
|
/// user's country code, may be an empty string
|
||||||
pub country: String,
|
pub country: String,
|
||||||
/// today's Julian date
|
/// today's Julian date
|
||||||
pub today: u32,
|
pub date: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl NegativeUserReport {
|
// Ensure public fields are legal while deserializing
|
||||||
|
// Based on https://serde.rs/deserialize-struct.html
|
||||||
|
impl<'de> Deserialize<'de> for NegativeReport {
|
||||||
|
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||||
|
where
|
||||||
|
D: de::Deserializer<'de>,
|
||||||
|
{
|
||||||
|
enum Field {
|
||||||
|
Fingerprint,
|
||||||
|
BridgePOK,
|
||||||
|
Country,
|
||||||
|
Date,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'de> Deserialize<'de> for Field {
|
||||||
|
fn deserialize<D>(deserializer: D) -> Result<Field, D::Error>
|
||||||
|
where
|
||||||
|
D: Deserializer<'de>,
|
||||||
|
{
|
||||||
|
struct FieldVisitor;
|
||||||
|
|
||||||
|
impl<'de> Visitor<'de> for FieldVisitor {
|
||||||
|
type Value = Field;
|
||||||
|
|
||||||
|
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
formatter.write_str("`fingerprint`, `bridge_pok`, `country`, or `date`")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visit_str<E>(self, value: &str) -> Result<Field, E>
|
||||||
|
where
|
||||||
|
E: de::Error,
|
||||||
|
{
|
||||||
|
match value {
|
||||||
|
"fingerprint" => Ok(Field::Fingerprint),
|
||||||
|
"bridge_pok" => Ok(Field::BridgePOK),
|
||||||
|
"country" => Ok(Field::Country),
|
||||||
|
"date" => Ok(Field::Date),
|
||||||
|
_ => Err(de::Error::unknown_field(value, FIELDS)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
deserializer.deserialize_identifier(FieldVisitor)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct NegativeReportVisitor;
|
||||||
|
|
||||||
|
impl<'de> de::Visitor<'de> for NegativeReportVisitor {
|
||||||
|
type Value = NegativeReport;
|
||||||
|
|
||||||
|
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
formatter.write_str(
|
||||||
|
"negative report with valid country code and date no later than today",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visit_seq<V>(self, mut seq: V) -> Result<NegativeReport, V::Error>
|
||||||
|
where
|
||||||
|
V: SeqAccess<'de>,
|
||||||
|
{
|
||||||
|
let fingerprint = seq
|
||||||
|
.next_element()?
|
||||||
|
.ok_or_else(|| de::Error::invalid_length(0, &self))?;
|
||||||
|
let bridge_pok = seq
|
||||||
|
.next_element()?
|
||||||
|
.ok_or_else(|| de::Error::invalid_length(1, &self))?;
|
||||||
|
let country = seq
|
||||||
|
.next_element()?
|
||||||
|
.ok_or_else(|| de::Error::invalid_length(2, &self))?;
|
||||||
|
let date = seq
|
||||||
|
.next_element()?
|
||||||
|
.ok_or_else(|| de::Error::invalid_length(3, &self))?;
|
||||||
|
Ok(NegativeReport {
|
||||||
|
fingerprint,
|
||||||
|
bridge_pok,
|
||||||
|
country,
|
||||||
|
date,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visit_map<V>(self, mut map: V) -> Result<NegativeReport, V::Error>
|
||||||
|
where
|
||||||
|
V: MapAccess<'de>,
|
||||||
|
{
|
||||||
|
let mut fingerprint = None;
|
||||||
|
let mut bridge_pok = None;
|
||||||
|
let mut country: Option<String> = None;
|
||||||
|
let mut date = None;
|
||||||
|
while let Some(key) = map.next_key()? {
|
||||||
|
match key {
|
||||||
|
Field::Fingerprint => {
|
||||||
|
if fingerprint.is_some() {
|
||||||
|
return Err(de::Error::duplicate_field("fingerprint"));
|
||||||
|
}
|
||||||
|
fingerprint = Some(map.next_value()?);
|
||||||
|
}
|
||||||
|
Field::BridgePOK => {
|
||||||
|
if bridge_pok.is_some() {
|
||||||
|
return Err(de::Error::duplicate_field("bridge_pok"));
|
||||||
|
}
|
||||||
|
bridge_pok = Some(map.next_value()?);
|
||||||
|
}
|
||||||
|
Field::Country => {
|
||||||
|
if country.is_some() {
|
||||||
|
return Err(de::Error::duplicate_field("country"));
|
||||||
|
}
|
||||||
|
country = Some(map.next_value()?);
|
||||||
|
}
|
||||||
|
Field::Date => {
|
||||||
|
if date.is_some() {
|
||||||
|
return Err(de::Error::duplicate_field("date"));
|
||||||
|
}
|
||||||
|
date = Some(map.next_value()?);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let fingerprint =
|
||||||
|
fingerprint.ok_or_else(|| de::Error::missing_field("fingerprint"))?;
|
||||||
|
let bridge_pok =
|
||||||
|
bridge_pok.ok_or_else(|| de::Error::missing_field("bridge_pok"))?;
|
||||||
|
let country = country.ok_or_else(|| de::Error::missing_field("country"))?;
|
||||||
|
if country != "" && !COUNTRY_CODES.contains(country.as_str()) {
|
||||||
|
return Err(de::Error::invalid_value(
|
||||||
|
Unexpected::Str(&country),
|
||||||
|
&"a country code or empty string",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
let date = date.ok_or_else(|| de::Error::missing_field("date"))?;
|
||||||
|
if date > get_date().into() {
|
||||||
|
return Err(de::Error::invalid_value(
|
||||||
|
Unexpected::Unsigned(date),
|
||||||
|
&"report date no later than today",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
Ok(NegativeReport {
|
||||||
|
fingerprint: fingerprint,
|
||||||
|
bridge_pok: bridge_pok,
|
||||||
|
country: country.to_string(),
|
||||||
|
date: date.try_into().unwrap(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const FIELDS: &'static [&'static str] = &["fingerprint", "bridge_pok", "country", "date"];
|
||||||
|
deserializer.deserialize_struct("NegativeReport", FIELDS, NegativeReportVisitor)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl NegativeReport {
|
||||||
pub fn new(bridge_id: [u8; 20], bridge_pok: ProofOfBridgeKnowledge, country: String) -> Self {
|
pub fn new(bridge_id: [u8; 20], bridge_pok: ProofOfBridgeKnowledge, country: String) -> Self {
|
||||||
let mut hasher = Sha1::new();
|
let mut hasher = Sha1::new();
|
||||||
hasher.update(bridge_id);
|
hasher.update(bridge_id);
|
||||||
let fingerprint: [u8; 20] = hasher.finalize().into();
|
let fingerprint: [u8; 20] = hasher.finalize().into();
|
||||||
let today = today();
|
let date = get_date();
|
||||||
Self {
|
Self {
|
||||||
fingerprint,
|
fingerprint,
|
||||||
bridge_pok,
|
bridge_pok,
|
||||||
country,
|
country,
|
||||||
today,
|
date,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn from_bridgeline(
|
pub fn from_bridgeline(bridge_id: [u8; 20], bridgeline: BridgeLine, country: String) -> Self {
|
||||||
&self,
|
|
||||||
bridge_id: [u8; 20],
|
|
||||||
bridgeline: BridgeLine,
|
|
||||||
country: String,
|
|
||||||
) -> Self {
|
|
||||||
let bridge_pok =
|
let bridge_pok =
|
||||||
ProofOfBridgeKnowledge::HashOfBridgeLine(HashOfBridgeLine::new(bridgeline));
|
ProofOfBridgeKnowledge::HashOfBridgeLine(HashOfBridgeLine::new(bridgeline));
|
||||||
NegativeUserReport::new(bridge_id, bridge_pok, country)
|
NegativeReport::new(bridge_id, bridge_pok, country)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn from_bucket(&self, bridge_id: [u8; 20], bucket: Scalar, country: String) -> Self {
|
pub fn from_bucket(bridge_id: [u8; 20], bucket: Scalar, country: String) -> Self {
|
||||||
let mut hasher = Sha3_256::new();
|
let mut hasher = Sha3_256::new();
|
||||||
hasher.update(bucket.to_bytes());
|
hasher.update(bucket.to_bytes());
|
||||||
let bucket_hash: [u8; 32] = hasher.finalize().into();
|
let bucket_hash: [u8; 32] = hasher.finalize().into();
|
||||||
let bridge_pok = ProofOfBridgeKnowledge::HashOfBucket(HashOfBucket { hash: bucket_hash });
|
let bridge_pok = ProofOfBridgeKnowledge::HashOfBucket(HashOfBucket { hash: bucket_hash });
|
||||||
NegativeUserReport::new(bridge_id, bridge_pok, country)
|
NegativeReport::new(bridge_id, bridge_pok, country)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn from_lox_credential(&self, bridge_id: [u8; 20], cred: Lox, country: String) -> Self {
|
pub fn from_lox_credential(bridge_id: [u8; 20], cred: Lox, country: String) -> Self {
|
||||||
self.from_bucket(bridge_id, cred.bucket, country)
|
NegativeReport::from_bucket(bridge_id, cred.bucket, country)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn verify(&self) -> bool {
|
fn verify(&self) -> bool {
|
||||||
// possibly include check that self.today is recent as well
|
self.bridge_pok.verify(self.fingerprint)
|
||||||
self.today <= today() && self.bridge_pok.verify(self.fingerprint)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A report that the user was able to connect to the bridge
|
/// A report that the user was able to connect to the bridge
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize)]
|
||||||
pub struct PositiveUserReport {
|
pub struct PositiveReport {
|
||||||
/// hashed fingerprint (SHA-1 hash of 20-byte bridge ID)
|
/// hashed fingerprint (SHA-1 hash of 20-byte bridge ID)
|
||||||
pub fingerprint: [u8; 20],
|
pub fingerprint: [u8; 20],
|
||||||
/// token from the bridge indicating it was reached
|
/// token from the bridge indicating it was reached
|
||||||
|
@ -208,40 +360,229 @@ pub struct PositiveUserReport {
|
||||||
/// user's country code, may be an empty string
|
/// user's country code, may be an empty string
|
||||||
pub country: String,
|
pub country: String,
|
||||||
/// today's Julian date
|
/// today's Julian date
|
||||||
pub today: u32,
|
pub date: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PositiveUserReport {
|
// Ensure public fields are legal while deserializing
|
||||||
pub fn new(bridge_id: [u8; 20], bridge_token: Option<BridgeToken>, lox_proof: positive_report::Request, country: String) -> Self {
|
// Based on https://serde.rs/deserialize-struct.html
|
||||||
|
impl<'de> Deserialize<'de> for PositiveReport {
|
||||||
|
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||||
|
where
|
||||||
|
D: de::Deserializer<'de>,
|
||||||
|
{
|
||||||
|
enum Field {
|
||||||
|
Fingerprint,
|
||||||
|
BridgeToken,
|
||||||
|
LoxProof,
|
||||||
|
Country,
|
||||||
|
Date,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'de> Deserialize<'de> for Field {
|
||||||
|
fn deserialize<D>(deserializer: D) -> Result<Field, D::Error>
|
||||||
|
where
|
||||||
|
D: Deserializer<'de>,
|
||||||
|
{
|
||||||
|
struct FieldVisitor;
|
||||||
|
|
||||||
|
impl<'de> Visitor<'de> for FieldVisitor {
|
||||||
|
type Value = Field;
|
||||||
|
|
||||||
|
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
formatter.write_str(
|
||||||
|
"`fingerprint`, `bridge_token`, `lox_proof`, `country`, or `date`",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visit_str<E>(self, value: &str) -> Result<Field, E>
|
||||||
|
where
|
||||||
|
E: de::Error,
|
||||||
|
{
|
||||||
|
match value {
|
||||||
|
"fingerprint" => Ok(Field::Fingerprint),
|
||||||
|
"bridge_token" => Ok(Field::BridgeToken),
|
||||||
|
"lox_proof" => Ok(Field::LoxProof),
|
||||||
|
"country" => Ok(Field::Country),
|
||||||
|
"date" => Ok(Field::Date),
|
||||||
|
_ => Err(de::Error::unknown_field(value, FIELDS)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
deserializer.deserialize_identifier(FieldVisitor)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct PositiveReportVisitor;
|
||||||
|
|
||||||
|
impl<'de> de::Visitor<'de> for PositiveReportVisitor {
|
||||||
|
type Value = PositiveReport;
|
||||||
|
|
||||||
|
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
formatter.write_str(
|
||||||
|
"positive report with valid country code and date no later than today",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visit_seq<V>(self, mut seq: V) -> Result<PositiveReport, V::Error>
|
||||||
|
where
|
||||||
|
V: SeqAccess<'de>,
|
||||||
|
{
|
||||||
|
let fingerprint = seq
|
||||||
|
.next_element()?
|
||||||
|
.ok_or_else(|| de::Error::invalid_length(0, &self))?;
|
||||||
|
let bridge_token = seq
|
||||||
|
.next_element()?
|
||||||
|
.ok_or_else(|| de::Error::invalid_length(1, &self))?;
|
||||||
|
let lox_proof = seq
|
||||||
|
.next_element()?
|
||||||
|
.ok_or_else(|| de::Error::invalid_length(2, &self))?;
|
||||||
|
let country = seq
|
||||||
|
.next_element()?
|
||||||
|
.ok_or_else(|| de::Error::invalid_length(3, &self))?;
|
||||||
|
let date = seq
|
||||||
|
.next_element()?
|
||||||
|
.ok_or_else(|| de::Error::invalid_length(4, &self))?;
|
||||||
|
Ok(PositiveReport {
|
||||||
|
fingerprint,
|
||||||
|
bridge_token,
|
||||||
|
lox_proof,
|
||||||
|
country,
|
||||||
|
date,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visit_map<V>(self, mut map: V) -> Result<PositiveReport, V::Error>
|
||||||
|
where
|
||||||
|
V: MapAccess<'de>,
|
||||||
|
{
|
||||||
|
let mut fingerprint = None;
|
||||||
|
let mut bridge_token: Option<Option<BridgeToken>> = None;
|
||||||
|
let mut lox_proof = None;
|
||||||
|
let mut country: Option<String> = None;
|
||||||
|
let mut date = None;
|
||||||
|
while let Some(key) = map.next_key()? {
|
||||||
|
match key {
|
||||||
|
Field::Fingerprint => {
|
||||||
|
if fingerprint.is_some() {
|
||||||
|
return Err(de::Error::duplicate_field("fingerprint"));
|
||||||
|
}
|
||||||
|
fingerprint = Some(map.next_value()?);
|
||||||
|
}
|
||||||
|
Field::BridgeToken => {
|
||||||
|
if bridge_token.is_some() {
|
||||||
|
return Err(de::Error::duplicate_field("bridge_token"));
|
||||||
|
}
|
||||||
|
bridge_token = Some(map.next_value()?);
|
||||||
|
}
|
||||||
|
Field::LoxProof => {
|
||||||
|
if lox_proof.is_some() {
|
||||||
|
return Err(de::Error::duplicate_field("lox_proof"));
|
||||||
|
}
|
||||||
|
lox_proof = Some(map.next_value()?);
|
||||||
|
}
|
||||||
|
Field::Country => {
|
||||||
|
if country.is_some() {
|
||||||
|
return Err(de::Error::duplicate_field("country"));
|
||||||
|
}
|
||||||
|
country = Some(map.next_value()?);
|
||||||
|
}
|
||||||
|
Field::Date => {
|
||||||
|
if date.is_some() {
|
||||||
|
return Err(de::Error::duplicate_field("date"));
|
||||||
|
}
|
||||||
|
date = Some(map.next_value()?);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let fingerprint =
|
||||||
|
fingerprint.ok_or_else(|| de::Error::missing_field("fingerprint"))?;
|
||||||
|
let bridge_token =
|
||||||
|
bridge_token.ok_or_else(|| de::Error::missing_field("bridge_token"))?;
|
||||||
|
if REQUIRE_BRIDGE_TOKEN && bridge_token.is_none() {
|
||||||
|
return Err(de::Error::invalid_value(
|
||||||
|
Unexpected::Option,
|
||||||
|
&"a bridge token (mandatory, per system configuration)",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
let lox_proof = lox_proof.ok_or_else(|| de::Error::missing_field("lox_proof"))?;
|
||||||
|
let country = country.ok_or_else(|| de::Error::missing_field("country"))?;
|
||||||
|
if country != "" && !COUNTRY_CODES.contains(country.as_str()) {
|
||||||
|
return Err(de::Error::invalid_value(
|
||||||
|
Unexpected::Str(&country),
|
||||||
|
&"a country code or empty string",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
let date = date.ok_or_else(|| de::Error::missing_field("date"))?;
|
||||||
|
if date > get_date().into() {
|
||||||
|
return Err(de::Error::invalid_value(
|
||||||
|
Unexpected::Unsigned(date),
|
||||||
|
&"report date no later than today",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
Ok(PositiveReport {
|
||||||
|
fingerprint: fingerprint,
|
||||||
|
bridge_token: bridge_token,
|
||||||
|
lox_proof: lox_proof,
|
||||||
|
country: country.to_string(),
|
||||||
|
date: date.try_into().unwrap(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const FIELDS: &'static [&'static str] = &[
|
||||||
|
"fingerprint",
|
||||||
|
"bridge_token",
|
||||||
|
"lox_proof",
|
||||||
|
"country",
|
||||||
|
"date",
|
||||||
|
];
|
||||||
|
deserializer.deserialize_struct("PositiveReport", FIELDS, PositiveReportVisitor)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PositiveReport {
|
||||||
|
pub fn new(
|
||||||
|
bridge_id: [u8; 20],
|
||||||
|
bridge_token: Option<BridgeToken>,
|
||||||
|
lox_proof: positive_report::Request,
|
||||||
|
country: String,
|
||||||
|
) -> Self {
|
||||||
|
if REQUIRE_BRIDGE_TOKEN && bridge_token.is_none() {
|
||||||
|
panic!("Bridge tokens are required for positive reports.");
|
||||||
|
}
|
||||||
let mut hasher = Sha1::new();
|
let mut hasher = Sha1::new();
|
||||||
hasher.update(bridge_id);
|
hasher.update(bridge_id);
|
||||||
let fingerprint: [u8; 20] = hasher.finalize().into();
|
let fingerprint: [u8; 20] = hasher.finalize().into();
|
||||||
let today = today();
|
let date = get_date();
|
||||||
Self {
|
Self {
|
||||||
fingerprint,
|
fingerprint,
|
||||||
bridge_token,
|
bridge_token,
|
||||||
lox_proof,
|
lox_proof,
|
||||||
country,
|
country,
|
||||||
today,
|
date,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn from_lox_credential(bridge_id: [u8; 20], bridge_token: Option<BridgeToken>, lox_cred: &Lox, lox_pub: &IssuerPubKey, country: String) -> Self {
|
pub fn from_lox_credential(
|
||||||
|
bridge_id: [u8; 20],
|
||||||
|
bridge_token: Option<BridgeToken>,
|
||||||
|
lox_cred: &Lox,
|
||||||
|
lox_pub: &IssuerPubKey,
|
||||||
|
country: String,
|
||||||
|
) -> Self {
|
||||||
let lox_proof = positive_report::request(lox_cred, lox_pub).unwrap();
|
let lox_proof = positive_report::request(lox_cred, lox_pub).unwrap();
|
||||||
PositiveUserReport::new(bridge_id, bridge_token, lox_proof, country)
|
PositiveReport::new(bridge_id, bridge_token, lox_proof, country)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn verify(&self) -> bool {
|
fn verify(&self) -> bool {
|
||||||
// possibly include check that self.today is recent as well
|
!REQUIRE_BRIDGE_TOKEN || {
|
||||||
self.today <= today()
|
if self.bridge_token.is_none() {
|
||||||
&& (!REQUIRE_BRIDGE_TOKEN || {
|
false
|
||||||
if self.bridge_token.is_none() {
|
} else {
|
||||||
false
|
let bt = self.bridge_token.as_ref().unwrap();
|
||||||
} else {
|
bt.verify()
|
||||||
let bt = self.bridge_token.as_ref().unwrap();
|
}
|
||||||
self.today == bt.unsigned_bridge_token.today && bt.verify()
|
}
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -253,7 +594,7 @@ pub struct UnsignedBridgeToken {
|
||||||
/// client's country code
|
/// client's country code
|
||||||
pub country: String,
|
pub country: String,
|
||||||
/// today's Julian date
|
/// today's Julian date
|
||||||
pub today: u32,
|
pub date: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl UnsignedBridgeToken {
|
impl UnsignedBridgeToken {
|
||||||
|
@ -261,11 +602,11 @@ impl UnsignedBridgeToken {
|
||||||
let mut hasher = Sha1::new();
|
let mut hasher = Sha1::new();
|
||||||
hasher.update(bridge_id);
|
hasher.update(bridge_id);
|
||||||
let fingerprint: [u8; 20] = hasher.finalize().into();
|
let fingerprint: [u8; 20] = hasher.finalize().into();
|
||||||
let today = today();
|
let date = get_date();
|
||||||
Self {
|
Self {
|
||||||
fingerprint,
|
fingerprint,
|
||||||
country,
|
country,
|
||||||
today,
|
date,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -290,7 +631,7 @@ impl BridgeToken {
|
||||||
|
|
||||||
pub fn verify(&self) -> bool {
|
pub fn verify(&self) -> bool {
|
||||||
let pubkey = get_bridge_signing_pubkey(&self.unsigned_bridge_token.fingerprint);
|
let pubkey = get_bridge_signing_pubkey(&self.unsigned_bridge_token.fingerprint);
|
||||||
self.unsigned_bridge_token.today <= today()
|
self.unsigned_bridge_token.date <= get_date()
|
||||||
&& pubkey
|
&& pubkey
|
||||||
.verify(
|
.verify(
|
||||||
&bincode::serialize(&self.unsigned_bridge_token).unwrap(),
|
&bincode::serialize(&self.unsigned_bridge_token).unwrap(),
|
||||||
|
|
Loading…
Reference in New Issue