From b4883c414a86d530e40c0b34cd6b0d0a982899b8 Mon Sep 17 00:00:00 2001 From: realaravinth Date: Mon, 8 Mar 2021 19:43:26 +0530 Subject: [PATCH] doc --- src/cache/hashcache.rs | 20 +---- src/cache/mod.rs | 5 +- src/data.rs | 166 +++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 1 + src/master.rs | 102 +++++-------------------- src/mcaptcha.rs | 2 +- src/pow.rs | 151 ++----------------------------------- 7 files changed, 199 insertions(+), 248 deletions(-) create mode 100644 src/data.rs diff --git a/src/cache/hashcache.rs b/src/cache/hashcache.rs index 9508ca6..62984b6 100644 --- a/src/cache/hashcache.rs +++ b/src/cache/hashcache.rs @@ -15,7 +15,7 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ - +//! In-memory cache implementation that uses [HashMap] use std::collections::HashMap; use actix::prelude::*; @@ -26,6 +26,7 @@ use crate::errors::*; use crate::pow::PoWConfig; #[derive(Clone, Default)] +/// cache datastructure implementing [Save] pub struct HashCache(HashMap); impl HashCache { @@ -86,21 +87,4 @@ mod tests { let difficulty_factor = addr.send(Retrive(string)).await.unwrap().unwrap(); assert_eq!(difficulty_factor.unwrap(), 54); } - // - // #[actix_rt::test] - // async fn counter_defense_loosenup_works() { - // use actix::clock::delay_for; - // let addr: MyActor = get_counter().start(); - // - // race(addr.clone(), LEVEL_2).await; - // race(addr.clone(), LEVEL_2).await; - // let mut difficulty_factor = addr.send(Visitor).await.unwrap(); - // assert_eq!(difficulty_factor.difficulty_factor, LEVEL_2.1); - // - // let duration = Duration::new(DURATION, 0); - // delay_for(duration).await; - // - // difficulty_factor = addr.send(Visitor).await.unwrap(); - // assert_eq!(difficulty_factor.difficulty_factor, LEVEL_1.1); - // } } diff --git a/src/cache/mod.rs b/src/cache/mod.rs index fe6a828..c877134 100644 --- a/src/cache/mod.rs +++ b/src/cache/mod.rs @@ -15,15 +15,18 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ - +//! Cache is used to save proofof work details and nonces to prevent replay attacks +//! and rainbow/dictionary attacks pub use hashcache::HashCache; use messages::*; pub mod hashcache; +/// Describes actor handler trait impls that are required by a cache implementation pub trait Save: actix::Actor + actix::Handler + actix::Handler {} pub mod messages { + //! Messages that can be sent to cache data structures implementing [Save][super::Save] use crate::pow::PoWConfig; use actix::dev::*; diff --git a/src/data.rs b/src/data.rs new file mode 100644 index 0000000..ba47bcd --- /dev/null +++ b/src/data.rs @@ -0,0 +1,166 @@ +/* + * mCaptcha - A proof of work based DoS protection system + * Copyright © 2021 Aravinth Manivannan + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +//! module describing various bits of data required for an mCaptcha system +use actix::dev::*; +use derive_builder::Builder; +use pow_sha256::{Config, PoW}; + +use crate::cache::messages; +use crate::cache::Save; +use crate::errors::*; +use crate::master::Master; +use crate::pow::*; + +/// struct describing various bits of data required for an mCaptcha system +#[derive(Clone, Builder)] +pub struct Data { + master: Addr>, + cache: Addr, + pow: Config, +} + +impl Data +where + T: Save, + ::Context: ToEnvelope + ToEnvelope, +{ + /// utility function to get difficulty factor of site `id` and cache it + pub async fn get_pow(&self, id: String) -> Option { + use crate::cache::messages::Cache; + use crate::master::GetSite; + use crate::mcaptcha::Visitor; + + let site_addr = self.master.send(GetSite(id)).await.unwrap(); + if site_addr.is_none() { + return None; + } + let difficulty_factor = site_addr.unwrap().send(Visitor).await.unwrap(); + let pow_config = PoWConfig::new(difficulty_factor); + self.cache + .send(Cache(pow_config.clone())) + .await + .unwrap() + .unwrap(); + Some(pow_config) + } + + /// utility function to verify [Work] + pub async fn verify_pow(&self, work: Work) -> CaptchaResult { + use crate::cache::messages::Retrive; + + let string = work.string.clone(); + let msg = Retrive(string.clone()); + let difficulty = self.cache.send(msg).await.unwrap(); + let pow: PoW = work.into(); + match difficulty { + Ok(Some(difficulty)) => { + if self.pow.is_sufficient_difficulty(&pow, difficulty) { + Ok(self.pow.is_valid_proof(&pow, &string)) + } else { + Err(CaptchaError::InsuffiencientDifficulty) + } + } + Ok(None) => Err(CaptchaError::StringNotFound), + Err(_) => Err(CaptchaError::Default), + } + } +} + +#[cfg(test)] +mod tests { + + use pow_sha256::ConfigBuilder; + + use super::*; + use crate::cache::HashCache; + use crate::master::*; + use crate::mcaptcha::tests::*; + + const MCAPTCHA_NAME: &str = "batsense.net"; + + async fn boostrap_system() -> Data { + let master = Master::new().start(); + let mcaptcha = get_counter().start(); + let pow = get_config(); + + let cache = HashCache::default().start(); + let msg = AddSiteBuilder::default() + .id(MCAPTCHA_NAME.into()) + .addr(mcaptcha.clone()) + .build() + .unwrap(); + + master.send(msg).await.unwrap(); + + DataBuilder::default() + .master(master) + .cache(cache) + .pow(pow) + .build() + .unwrap() + } + + fn get_config() -> Config { + ConfigBuilder::default() + .salt("myrandomsaltisnotlongenoug".into()) + .build() + .unwrap() + } + + #[actix_rt::test] + async fn get_pow_works() { + let actors = boostrap_system().await; + let pow = actors.get_pow(MCAPTCHA_NAME.into()).await.unwrap(); + assert_eq!(pow.difficulty_factor, LEVEL_1.0); + } + + #[actix_rt::test] + async fn verify_pow_works() { + let actors = boostrap_system().await; + let work_req = actors.get_pow(MCAPTCHA_NAME.into()).await.unwrap(); + let config = get_config(); + + let work = config + .prove_work(&work_req.string, work_req.difficulty_factor) + .unwrap(); + + let insufficient_work = config.prove_work(&work_req.string, 1).unwrap(); + let insufficient_work_payload = Work { + string: work_req.string.clone(), + result: insufficient_work.result, + nonce: insufficient_work.nonce, + }; + + let mut payload = Work { + string: work_req.string, + result: work.result, + nonce: work.nonce, + }; + + let res = actors.verify_pow(payload.clone()).await.unwrap(); + assert!(res); + + payload.string = "wrongstring".into(); + let res = actors.verify_pow(payload.clone()).await; + assert_eq!(res, Err(CaptchaError::StringNotFound)); + + let res = actors.verify_pow(insufficient_work_payload.clone()).await; + + assert_eq!(res, Err(CaptchaError::InsuffiencientDifficulty)); + } +} diff --git a/src/lib.rs b/src/lib.rs index dbaaafb..f68a93d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -105,6 +105,7 @@ pub mod message { /// message datatypes to interact with [MCaptcha] actor pub mod cache; +pub mod data; pub mod pow; mod utils; diff --git a/src/master.rs b/src/master.rs index b7a04a1..87e1466 100644 --- a/src/master.rs +++ b/src/master.rs @@ -15,6 +15,7 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ +//! [Master] actor module that manages [MCaptcha] actors use std::collections::BTreeMap; use actix::dev::*; @@ -22,24 +23,29 @@ use derive_builder::Builder; use crate::mcaptcha::MCaptcha; -/// This struct represents the mCaptcha state and is used -/// to configure leaky-bucket lifetime and manage defense +/// This Actor manages the [MCaptcha] actors. +/// A service can have several [MCaptcha] actors with +/// varying [Defense][crate::defense::Defense] configurations +/// so a "master" actor is needed to manage them all #[derive(Clone)] pub struct Master<'a> { sites: BTreeMap<&'a str, Addr>, } impl Master<'static> { + /// add [MCaptcha] actor to [Master] pub fn add_site(&mut self, details: AddSite) { self.sites.insert(details.id, details.addr.to_owned()); } + /// create new master pub fn new() -> Self { Master { sites: BTreeMap::new(), } } + /// get [MCaptcha] actor from [Master] pub fn get_site<'a, 'b>(&'a self, id: &'b str) -> Option<&'a Addr> { self.sites.get(id) } @@ -49,7 +55,7 @@ impl Actor for Master<'static> { type Context = Context; } -/// Message to increment the visitor count +/// Message to get an [MCaptcha] actor from master #[derive(Message)] #[rtype(result = "Option>")] pub struct GetSite(pub String); @@ -67,7 +73,7 @@ impl Handler for Master<'static> { } } -/// Message to increment the visitor count +/// Message to add an [MCaptcha] actor to [Master] #[derive(Message, Builder)] #[rtype(result = "()")] pub struct AddSite { @@ -83,98 +89,28 @@ impl Handler for Master<'static> { } } -///// Message to decrement the visitor count -//#[derive(Message, Deserialize)] -//#[rtype(result = "()")] -//pub struct VerifyPoW { -// pow: ShaPoW>, -// id: String, -//} -// -//impl Handler for MCaptcha { -// type Result = (); -// fn handle(&mut self, msg: VerifyPoW, _ctx: &mut Self::Context) -> Self::Result { -// self.decrement_visiotr(); -// } -//} - #[cfg(test)] mod tests { use super::*; - use crate::defense::*; - - // use crate::cache::HashCache; - // - // // constants for testing - // // (visitor count, level) - const LEVEL_1: (u32, u32) = (50, 50); - const LEVEL_2: (u32, u32) = (500, 500); - const DURATION: u64 = 10; - - type MyActor = Addr; - - fn get_defense() -> Defense { - DefenseBuilder::default() - .add_level( - LevelBuilder::default() - .visitor_threshold(LEVEL_1.0) - .difficulty_factor(LEVEL_1.1) - .unwrap() - .build() - .unwrap(), - ) - .unwrap() - .add_level( - LevelBuilder::default() - .visitor_threshold(LEVEL_2.0) - .difficulty_factor(LEVEL_2.1) - .unwrap() - .build() - .unwrap(), - ) - .unwrap() - .build() - .unwrap() - } - - fn get_counter() -> MCaptcha { - use crate::MCaptchaBuilder; - - MCaptchaBuilder::default() - .defense(get_defense()) - .duration(DURATION) - .build() - .unwrap() - } + use crate::mcaptcha::tests::*; #[actix_rt::test] - async fn master() { + async fn master_actor_works() { let addr = Master::new().start(); let id = "yo"; let mcaptcha = get_counter().start(); let msg = AddSiteBuilder::default() .id(id) - .addr(mcaptcha) + .addr(mcaptcha.clone()) .build() .unwrap(); addr.send(msg).await.unwrap(); + + let mcaptcha_addr = addr.send(GetSite(id.into())).await.unwrap(); + assert_eq!(mcaptcha_addr, Some(mcaptcha)); + + let addr_doesnt_exist = addr.send(GetSite("a".into())).await.unwrap(); + assert!(addr_doesnt_exist.is_none()); } - // - // #[actix_rt::test] - // async fn counter_defense_loosenup_works() { - // use actix::clock::delay_for; - // let addr: MyActor = get_counter().start(); - // - // race(addr.clone(), LEVEL_2).await; - // race(addr.clone(), LEVEL_2).await; - // let mut difficulty_factor = addr.send(Visitor).await.unwrap().unwrap(); - // assert_eq!(difficulty_factor, LEVEL_2.1); - // - // let duration = Duration::new(DURATION, 0); - // delay_for(duration).await; - // - // difficulty_factor = addr.send(Visitor).await.unwrap().unwrap(); - // assert_eq!(difficulty_factor, LEVEL_1.1); - // } } diff --git a/src/mcaptcha.rs b/src/mcaptcha.rs index c483bd8..e66d5c4 100644 --- a/src/mcaptcha.rs +++ b/src/mcaptcha.rs @@ -83,7 +83,7 @@ use crate::defense::Defense; /// This struct represents the mCaptcha state and is used /// to configure leaky-bucket lifetime and manage defense -#[derive(Clone, Builder)] +#[derive(Clone, Debug, Builder)] pub struct MCaptcha { #[builder(default = "0", setter(skip))] visitor_threshold: u32, diff --git a/src/pow.rs b/src/pow.rs index 08cd74c..ed55c19 100644 --- a/src/pow.rs +++ b/src/pow.rs @@ -15,23 +15,19 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -use actix::dev::*; -use derive_builder::Builder; -use pow_sha256::{Config, PoW}; + +//! PoW datatypes used in client-server interaction +use pow_sha256::PoW; use serde::Serialize; -use crate::cache::messages; -use crate::cache::Save; -use crate::errors::*; -use crate::master::Master; - -/// PoW Config that will be sent to clients for generating PoW +/// PoW requirement datatype that is be sent to clients for generating PoW #[derive(Clone, Serialize, Debug)] pub struct PoWConfig { pub string: String, pub difficulty_factor: u32, } impl PoWConfig { + /// create new instance of [PoWConfig] pub fn new(m: u32) -> Self { use crate::utils::get_random; @@ -42,58 +38,7 @@ impl PoWConfig { } } -#[derive(Clone, Builder)] -pub struct Actors { - master: Addr>, - cache: Addr, - pow: Config, -} - -impl Actors -where - T: Save, - ::Context: ToEnvelope + ToEnvelope, -{ - pub async fn get_pow(&self, id: String) -> Option { - use crate::cache::messages::Cache; - use crate::master::GetSite; - use crate::mcaptcha::Visitor; - - let site_addr = self.master.send(GetSite(id)).await.unwrap(); - if site_addr.is_none() { - return None; - } - let difficulty_factor = site_addr.unwrap().send(Visitor).await.unwrap(); - let pow_config = PoWConfig::new(difficulty_factor); - self.cache - .send(Cache(pow_config.clone())) - .await - .unwrap() - .unwrap(); - Some(pow_config) - } - - pub async fn verify_pow(&self, work: Work) -> CaptchaResult { - use crate::cache::messages::Retrive; - - let string = work.string.clone(); - let msg = Retrive(string.clone()); - let difficulty = self.cache.send(msg).await.unwrap(); - let pow: PoW = work.into(); - match difficulty { - Ok(Some(difficulty)) => { - if self.pow.is_sufficient_difficulty(&pow, difficulty) { - Ok(self.pow.is_valid_proof(&pow, &string)) - } else { - Err(CaptchaError::InsuffiencientDifficulty) - } - } - Ok(None) => Err(CaptchaError::StringNotFound), - Err(_) => Err(CaptchaError::Default), - } - } -} - +/// PoW datatype that clients send to server #[derive(Clone, Serialize, Debug)] pub struct Work { pub string: String, @@ -111,87 +56,3 @@ impl From for PoW { .unwrap() } } - -#[cfg(test)] -mod tests { - - use pow_sha256::ConfigBuilder; - - use super::*; - use crate::cache::HashCache; - use crate::master::*; - use crate::mcaptcha::tests::*; - - const MCAPTCHA_NAME: &str = "batsense.net"; - - async fn boostrap_system() -> Actors { - let master = Master::new().start(); - let mcaptcha = get_counter().start(); - let pow = get_config(); - - let cache = HashCache::default().start(); - let msg = AddSiteBuilder::default() - .id(MCAPTCHA_NAME.into()) - .addr(mcaptcha.clone()) - .build() - .unwrap(); - - master.send(msg).await.unwrap(); - - ActorsBuilder::default() - .master(master) - .cache(cache) - .pow(pow) - .build() - .unwrap() - } - - fn get_config() -> Config { - ConfigBuilder::default() - .salt("myrandomsaltisnotlongenoug".into()) - .build() - .unwrap() - } - - #[actix_rt::test] - async fn get_pow_works() { - let actors = boostrap_system().await; - let pow = actors.get_pow(MCAPTCHA_NAME.into()).await.unwrap(); - assert_eq!(pow.difficulty_factor, LEVEL_1.0); - } - - #[actix_rt::test] - async fn verify_pow_works() { - let actors = boostrap_system().await; - let work_req = actors.get_pow(MCAPTCHA_NAME.into()).await.unwrap(); - let config = get_config(); - - let work = config - .prove_work(&work_req.string, work_req.difficulty_factor) - .unwrap(); - - let insufficient_work = config.prove_work(&work_req.string, 1).unwrap(); - let insufficient_work_payload = Work { - string: work_req.string.clone(), - result: insufficient_work.result, - nonce: insufficient_work.nonce, - }; - - let mut payload = Work { - string: work_req.string, - result: work.result, - nonce: work.nonce, - }; - - let res = actors.verify_pow(payload.clone()).await.unwrap(); - assert!(res); - - payload.string = "wrongstring".into(); - let res = actors.verify_pow(payload.clone()).await; - assert_eq!(res, Err(CaptchaError::StringNotFound)); - - let res = actors.verify_pow(insufficient_work_payload.clone()).await; - - assert_eq!(res, Err(CaptchaError::InsuffiencientDifficulty)); - } -}