troll-patrol/src/lib.rs

643 lines
24 KiB
Rust
Raw Normal View History

2023-11-28 13:18:08 -05:00
use curve25519_dalek::scalar::Scalar;
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::cred::Lox;
2024-01-17 18:53:40 -05:00
use lox_library::proto::positive_report;
use lox_library::IssuerPubKey;
use serde::de::{self, Deserializer, MapAccess, SeqAccess, Unexpected, Visitor};
2023-11-28 13:18:08 -05:00
use serde::{Deserialize, Serialize};
use sha1::{Digest, Sha1};
use sha3::Sha3_256;
use std::collections::HashSet;
use std::fmt;
use std::option::Option;
2023-11-28 13:18:08 -05:00
// for generating ed25519 keys during initial development
use rand::rngs::OsRng;
2023-12-05 18:05:44 -05:00
// TODO: These should be loaded from config file
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"]);
}
2023-12-05 18:05:44 -05:00
2024-01-17 18:53:40 -05:00
/// The minimum trust level a Lox credential must have to be allowed to
/// submit a positive report
pub const PR_MIN_TRUST_LEVEL: u32 = 3;
2023-11-28 13:18:08 -05:00
/// Get Julian date
pub fn get_date() -> u32 {
2023-11-28 13:18:08 -05:00
time::OffsetDateTime::now_utc()
.date()
.to_julian_day()
.try_into()
.unwrap()
}
/// Get bridge line for a bridge, requires some oracle
pub fn get_bridge_line(fingerprint: &[u8; 20]) -> BridgeLine {
// TODO
// for now just return empty bridgeline
BridgeLine::default()
}
/// Get verifying key for a bridge, requires some oracle
pub fn get_bridge_signing_pubkey(fingerprint: &[u8; 20]) -> VerifyingKey {
// TODO
// for now just return new pubkey
let mut csprng = OsRng {};
let keypair = SigningKey::generate(&mut csprng);
keypair.verifying_key()
}
/// Get bucket from hash of bucket ID, requires some oracle
pub fn get_bucket(beta_hash: &[u8; 32]) -> [BridgeLine; MAX_BRIDGES_PER_BUCKET] {
// TODO
// for now just return bucket of empty bridgelines
[
BridgeLine::default(),
BridgeLine::default(),
BridgeLine::default(),
]
}
2023-11-28 13:18:08 -05:00
/// 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),
2023-11-28 13:18:08 -05:00
}
impl ProofOfBridgeKnowledge {
pub fn verify(&self, bridge_fingerprint: [u8; 20]) -> bool {
// TODO: It seems like there ought to be a cleaner way to do this?
match self {
ProofOfBridgeKnowledge::HashOfBridgeLine(bl_hash) => bl_hash.verify(bridge_fingerprint),
ProofOfBridgeKnowledge::HashOfBucket(bucket_hash) => {
bucket_hash.verify(bridge_fingerprint)
2023-11-28 13:18:08 -05:00
}
}
}
}
/// 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 }
}
pub fn verify(&self, bridge_fingerprint: [u8; 20]) -> bool {
let bl = get_bridge_line(&bridge_fingerprint);
self == &HashOfBridgeLine::new(bl)
}
}
/// 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 }
}
pub fn verify(&self, bridge_fingerprint: [u8; 20]) -> bool {
let bucket = get_bucket(&self.hash);
for bl in bucket {
let mut hasher = Sha1::new();
hasher.update(bl.uid_fingerprint.to_le_bytes());
if bridge_fingerprint == <[u8; 20]>::from(hasher.finalize()) {
return true;
}
2023-11-28 13:18:08 -05:00
}
false
2023-11-28 13:18:08 -05:00
}
}
/// Reports from users about whether or not their bridges are blocked
#[derive(Serialize, Deserialize)]
pub enum Report {
/// Negative report indicating user was unable to connect
NegativeReport(NegativeReport),
/// Positive report indicating user was able to connect
PositiveReport(PositiveReport),
}
impl Report {
fn verify(&self) -> bool {
match self {
Report::NegativeReport(report) => report.verify(),
Report::PositiveReport(report) => report.verify(),
}
}
2023-11-28 13:18:08 -05:00
}
/// A report that the user was unable to connect to the bridge
#[derive(Serialize)]
pub struct NegativeReport {
2023-11-28 13:18:08 -05:00
/// hashed fingerprint (SHA-1 hash of 20-byte bridge ID)
pub fingerprint: [u8; 20],
/// some way to prove knowledge of bridge
bridge_pok: ProofOfBridgeKnowledge,
2023-11-28 13:18:08 -05:00
/// user's country code, may be an empty string
pub country: String,
/// today's Julian date
pub date: u32,
}
// 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)
}
2023-11-28 13:18:08 -05:00
}
impl NegativeReport {
pub fn new(bridge_id: [u8; 20], bridge_pok: ProofOfBridgeKnowledge, country: String) -> Self {
2023-11-28 13:18:08 -05:00
let mut hasher = Sha1::new();
hasher.update(bridge_id);
let fingerprint: [u8; 20] = hasher.finalize().into();
let date = get_date();
2023-11-28 13:18:08 -05:00
Self {
fingerprint,
bridge_pok,
country,
date,
2023-11-28 13:18:08 -05:00
}
}
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)
}
2023-11-28 13:18:08 -05:00
fn verify(&self) -> bool {
self.bridge_pok.verify(self.fingerprint)
2023-11-28 13:18:08 -05:00
}
}
/// A report that the user was able to connect to the bridge
#[derive(Serialize)]
pub struct PositiveReport {
2023-11-28 13:18:08 -05:00
/// 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<BridgeToken>,
2024-01-17 18:53:40 -05:00
// proof of Lox cred with level >= 3 and this bridge
lox_proof: positive_report::Request,
2023-11-28 13:18:08 -05:00
/// user's country code, may be an empty string
pub country: String,
/// today's Julian date
pub date: u32,
}
// Ensure public fields are legal while deserializing
// 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)
}
2023-11-28 13:18:08 -05:00
}
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.");
}
2023-11-28 13:18:08 -05:00
let mut hasher = Sha1::new();
hasher.update(bridge_id);
let fingerprint: [u8; 20] = hasher.finalize().into();
let date = get_date();
2023-11-28 13:18:08 -05:00
Self {
fingerprint,
bridge_token,
2024-01-17 18:53:40 -05:00
lox_proof,
2023-11-28 13:18:08 -05:00
country,
date,
2023-11-28 13:18:08 -05:00
}
}
pub fn from_lox_credential(
bridge_id: [u8; 20],
bridge_token: Option<BridgeToken>,
lox_cred: &Lox,
lox_pub: &IssuerPubKey,
country: String,
) -> Self {
2024-01-17 18:53:40 -05:00
let lox_proof = positive_report::request(lox_cred, lox_pub).unwrap();
PositiveReport::new(bridge_id, bridge_token, lox_proof, country)
2024-01-17 18:53:40 -05:00
}
2023-11-28 13:18:08 -05:00
fn verify(&self) -> bool {
!REQUIRE_BRIDGE_TOKEN || {
if self.bridge_token.is_none() {
false
} else {
let bt = self.bridge_token.as_ref().unwrap();
bt.verify()
}
}
2023-11-28 13:18:08 -05:00
}
}
/// 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,
2023-11-28 13:18:08 -05:00
}
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();
2023-11-28 13:18:08 -05:00
Self {
fingerprint,
country,
date,
2023-11-28 13:18:08 -05:00
}
}
}
/// 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,
}
}
pub fn verify(&self) -> bool {
let pubkey = get_bridge_signing_pubkey(&self.unsigned_bridge_token.fingerprint);
self.unsigned_bridge_token.date <= get_date()
2023-11-28 13:18:08 -05:00
&& pubkey
.verify(
&bincode::serialize(&self.unsigned_bridge_token).unwrap(),
&self.sig,
)
.is_ok()
}
}