Fix Heartbeat + a few more notes on metadata + security warnings and examples

This commit is contained in:
Sarah Jamie Lewis 2021-05-17 23:34:36 -07:00
parent c2a3518e96
commit 12f6b705cb
5 changed files with 114 additions and 21 deletions

1
Cargo.lock generated
View File

@ -1080,6 +1080,7 @@ dependencies = [
"fuzzytags",
"hex",
"niwl",
"rand 0.8.3",
"reqwest",
"serde",
"serde_json",

View File

@ -8,6 +8,17 @@ event has happened e.g. a new group message, a payment etc.
**niwl** provides a set of libraries, clients and servers to provide this in a metadata resistant, bandwidth
efficient way based on [fuzzytags](https://crates.io/crates/fuzzytags).
## Security (hic sunt dracones)
This crate workspace provides and documents a novel and highly experimental metadata resistant communication system.
The code has not undergone any significant review.
Further, it is based on an [experimental implementation (fuzzytags), of an experimental cryptographic scheme (FMD2)](https://git.openprivacy.ca/openprivacy/fuzzytags)
which also has a large list of security warnings.
I urge you to not rely on this code or derivative systems until it has been reviewed and given considerable thought.
# How Niwl Works
A Niwl system relies on a single, untrusted routing server that acts as a bulletin board.
@ -69,24 +80,40 @@ This broad genre of attacks can be generalized as follows:
1. REMs start with a pool of randomly generated messages, this protected initial messages sent to the REM.
2. Over time this pool is probabilistically replaced by messages from the network.
3. A malicious Niwl server, having identified a REM, can flood the REM with its own messages.
4. At a certain number of messages, the probability that a REM store contains only messages from the Niwl server approaches 1.0.
4. At a certain number of messages, the probability that a REM store contains only messages from the niwl server approaches 1.0.
5. A Niwl server can then delay every other message sent to it by other clients one-by-one.
1. If the message isn't for the REM then nothing will happen.
2. If the message is for the REM then the REM will either eject a message known to the Niwl Server, or it will eject
an unknown message than the Niwl Server can then correlate with a Sender and a set of Receivers.
Before diving into mitigation strategies it is worth outlining a few properties of Niwl that differ from other
mixing-based anonymity systems.
First, we should note that Niwl is less prone to these kinds of attacks because:
0. Using REMs are not mandatory; parties may exchange messages with each other directly. Doing so does introduce a vulnerability
to statistical analysis.
1. Different parties can rely on different REMs without compromising metadata privacy, and without negotiation.
2. If a REM becomes slow to respond or sends out and error alert, parties may choose to move to a different REM.
3. Different REMs can adopt different mixing strategies, and may be selective about what traffic they mix.
1. REMs are not, a-priori, known to the Niwl Server and such are more difficult to target than mixers in traditional mixnets.
2. Different parties can rely on different REMs without compromising metadata privacy.
Additionally, we should also enumerate what could go wrong, in addition to an active attack on a particular mix.
As such targeting a particular mix is not an effective strategy for undermining the anonymity set of the entire system.
The niwl server may deliberately drop or delay packets arbitrarily. Beyond this prototype it is worth considering
incentive mechanisms such as ([token-based services](https://openprivacy.ca/research/OPTR2019-01/)) to mitigate this.
Further, REMs employ [heartbeat messages](references/heartbeat.pdf) (messages periodically sent to the Niwl server addressed to the REM)
to detect such attacks. If a REM does not receive its own heartbeat message shortly after it is sent, it begins injecting random messages
into its pool to thwart mixers. It can also display this status publicly and/or include the status in legitimate messages alerting
other clients to the malicious Niwl Server
Niwl servers may attempt to passively profile traffic originating from clients in an attempt to determine mixing nodes.
REMs always download all messages from the niwl server and so the only available metadata exposed is the rate at which
a REM *sends* messages. This can be partially mitigated by introducing random delays between individual sends, and between
syncing periods.
REMs employ [heartbeat messages](references/heartbeat.pdf) (messages periodically sent to the Niwl server addressed to the REM)
to detect such attacks. If a REM does not receive its own heartbeat message shortly after it is sent, it begins injecting random messages
into its pool to thwart mixers. It can also display this status publicly and/or include the status in legitimate messages alerting
other clients to the malicious Niwl Server.
The rate at which a niwl sends out a heartbeat message is also a vector for passive profiling. Heartbeats must not
be distinguishable from other niwl traffic through their rate.
Finally, the fact that a REM
# Code Overview
@ -101,8 +128,9 @@ for new tags.
For a more detailed overview please check out each individual crate.
# Example
# Examples
## Simple Peer-to-Peer
niwl-client alice.profile generate "alice"
Tagging Key: auaaaaaaaaaaaylmnfrwkgaaaaaaaaaaadlbii3y7r6vmc7upbxa4myohaqmr5xl22bdxeed4abkotnovlmakzdo5stq2ibtjewm4rnkgzqwglrt72zfeyomvdpxqnu4ci4hwebyiseyn7pqfxypnvef7a3flu2hby7gdluh6wocxa5mvmimi2xorydcqaca2p2aevmue4cwyxnw2h7fkps7e6grgls66zgohbnwjibt6nlsdqjbrdjrzlsc3at3f43jyniz2i67ng6xdty5pr3elzedhjlvefhd6pjfc7g4owrz3dkq5xt2hhh3vvctkywqkcwriguayyx3pourepfs7s76bekrjgcjgj6zyid3ixmeh5ewqhkhxhzevf3uogvscxtpbksaclhccht7pj2fungnztfghshd6lsmegmysiiuyav6schtmyxmne2vfi4j4cxllm2crj3cqofsxjlxov3ms2zgtjzyxtubwtnwspc4jhijz4kufm6r3qkhpcyibx7ulceckx2a4g23tkhtgshtxq3fga7ptbhq5gcebwiq6cfolt4zbn72gbmtc43nw63vd4soxf4bnbhrykaoudfs3mh6laap6iwbngo4ylocs4w5hgd4t22yrtrmhkewsc2eytsosxyhaiuaww24mszscsojm2bcoldpokwuxbnfx7lgnzdcuae3y55zoen47noltjqgcpuqzl6upjcvutgvvro6nu2uyl36rcqmw2by2e45uqtsdnolbispxv2e5aeeuz5gytuf5f5e44nldmywtmxkfqfljml5gye6tj3qswmz6d36f2k4v7fbiuv7jzplzmghsgxvmq7fo3qp655obysbggkd3iqpk76p5umbpc2tk64oiklrponulkqf3v337aaxyn6nvzz2rpj3o374tftscsr7oilzkah63xpe2jc45dd4fuwxvlg3c33zgkminemqqfz7jdjtnawy77vpxxgnosbw4fwadhhggofmipboiqo55xygojdnfdkuzgfe4455sdqv5ytzdl55yuzlbdgsnwtgnfakmoyjhblzbuwohq7esayfxe72yqgci5dappiad7bc3ikfsydv5b7stifajkxuosu345upxg2hwzajj4uu7lxaykxgo22pslkxnidaoyevn3gamx63ec4fkhzguhbu6jt7pukr4rpafx24vd622f5wzux4corlxthjuhi2ewiu6laxx3aqfkzv2d2hhqzsac25vycmmxy
@ -115,9 +143,33 @@ For a more detailed overview please check out each individual crate.
niwl-client alice.profile tag-and-send bob
Tag for bob 7e441275a5c3f88606c34c3451a44eaeaa025680cfcb3d9db53992501cc22134 4f7a7f961bc19297fee98da5f8601aa8373429b80b10c55dbe8116aa8c497a0e 71d8da
niwl-client bob.profile detect 10
niwl-client bob.profile detect
7e441275a5c3f88606c34c3451a44eaeaa025680cfcb3d9db53992501cc22134 4f7a7f961bc19297fee98da5f8601aa8373429b80b10c55dbe8116aa8c497a0e 71d8da
## Mix and Send
// Create a mixer
niwl-rem generate mixer
<!--- snip key -->
niwl-rem run
[DEBUG] kicking off initial heartbeat...
.....
// Alice imports a keyset for a mixer and sends a message to bob via the mixer using `tag-and-mix`
niwl-client alice.niwl import-tagging-key auaaaaaaaaaaa3ljpbsxegaaaaaaaaaaabeiwbh2iiojfcurszypf4urscr5p7s6q7dzoeqyamrtx63hoakgogb7azd3ov37hippyqdar4povsf7oq25zogfr4qjabgzqcxedttrfceqffywwubechylxd4qzouedzkkhyg2f6e6aftdypvfoff6345li5hmfetjja6aswyffb5ngohin2cdg5qokko7s4kb7d7hb33ki6uuaenxi7neuden2fxxys3dczicfacw3iwhqw7kygs67kzre7tljrfktss4whzhurmozs4znyrnnjmzazsbrijl2fmcatc3v6ptxdpw35vt6zwnyz2l7fcpblmtrnthmrmxiej3hcvi5d7qiwj6s7wi2dygu2ref2o5jm2tug3lxgbbgqwsqvoo7d5eropddbkhcbr5pzls5nco5hkpnuubho54i4msm7kinzobnc5rgyduo2dpl6jo6pnlb7nckmpcgmcyrntg52xmvzhbxumrtiwvhxaqdscsrvgz7eg5szngetzsitpdfhycskxmnxwe6himyllywdxzalojuit5ap5ugfsmcmywn5hciwupx2y2asgjxowmhjhiubjph6b6y7jiuyqnyjjrwehotass4432hrilxzxzrmppdbt2yo3kfmdtxv5fseyp2k7ld2gr7z5ds3fxc5mtilvj3fzaw5tabhxtf73uykozbgjimzs7cfluhcmwitytjdw72r3ws552fjre6pq5jwx2ihd5u2odegvhq7wuqg5xmjvmayirqywobsdkm7szk7r5n4svoareaomq3cmmxwpv45ftfnp2adzmcb4bqzwvvwfsjjfsmepb7ocyw6bgy6hh7cugfafv6ww3pukhzydemisv67r4wbeoyhdebx2mp22wjcyqzcsa66k3k236uz7v3sf7n5577td52zjwiu27wiugehvymi3nnfm5qx3ps7ts7qkp6y2qqf4rdyg3z23oswhw6ku2nxlniesc4u6nhcg5h3olcrrbh4c3q3nyejnbs2msyxxuasofm6ayn5fl5rhomr74jzmp35xfzw7mu6uwciwbcommq733d3cvtpwhetcqjoxpbrydcdrhvux43ybbauc6aqkwlpoid7cfrycexadbe3ilmzlinpppr43k7y6cj3rewsu42gb5ici5a3sy7mk66xudceu3novaxdtscgucz3yy3jp26ovqqdmvedmgk33p6puqguhdwwuxqz6u5jyhjvxllg7w55xpptj64dphamnix3wxcjimginb7d2k7qz6ey
Got: mixer: aa18c8597fd54a7779a0770c15ecbcc4d247009c007425172ba560b17f180516
niwl-client alice.niwl tag-and-mix mixer bob "Hello Mixnet"
// Bob should receive the message some time later.
niwl-client bob.niwl detect
message: Hello Mixnet
## Acknowledgements
- Thanks to Erinn Atwater for helpful discussions.
- FuzzyTags is based on [Fuzzy Message Detection](https://eprint.iacr.org/2021/089) by Gabrielle Beck and Julia Len and Ian Miers and Matthew Green
## References
* Danezis, George, and Len Sassaman. "Heartbeat traffic to counter (n-1) attacks: red-green-black mixes." Proceedings of the 2003 ACM workshop on Privacy in the electronic society. 2003.

View File

@ -16,4 +16,5 @@ hex = "0.4.2"
base32 = "0.4.0"
reqwest = {version="0.11.0", features=["json"]}
tokio = "1.2.0"
chrono = {version="0.4.19", features=["serde"]}
chrono = {version="0.4.19", features=["serde"]}
rand = "0.8.3"

View File

@ -14,11 +14,15 @@ pub enum MixMessage {
pub struct RandomEjectionMix {
heartbeat_id: Tag<24>,
last_heartbeat: DateTime<Local>,
}
impl RandomEjectionMix {
pub fn init(tag: Tag<24>) -> RandomEjectionMix {
RandomEjectionMix { heartbeat_id: tag }
RandomEjectionMix {
heartbeat_id: tag,
last_heartbeat: Local::now(),
}
}
pub fn push(&mut self, tag: &Tag<24>, plaintext: &String) -> Option<MixMessage> {
@ -42,15 +46,33 @@ impl RandomEjectionMix {
}
}
fn process_heartbeat(&self, tag: &Tag<24>, heartbeat: &DateTime<Local>) -> Option<MixMessage> {
fn process_heartbeat(
&mut self,
tag: &Tag<24>,
heartbeat: &DateTime<Local>,
) -> Option<MixMessage> {
if tag == &self.heartbeat_id {
println!("Received HeartBeat @ {}", heartbeat);
let new_heartbeat = Heartbeat(self.heartbeat_id.clone(), Local::now());
println!("[DEBUG] Received HeartBeat from {}", heartbeat);
self.last_heartbeat = heartbeat.clone();
let now = Local::now();
let new_heartbeat = Heartbeat(self.heartbeat_id.clone(), now.clone());
return Some(new_heartbeat);
}
None
}
pub fn check_heartbeat(&self) -> bool {
let time_since_last = Local::now() - self.last_heartbeat;
println!(
"[DEBUG] Time since last heartbeat: {}s",
time_since_last.num_seconds()
);
if time_since_last > Duration::minutes(2) {
return false;
}
return true;
}
// Actually do the Random Ejection Mixing...
fn random_ejection_mix(&mut self, ciphertext: &TaggedCiphertext) -> TaggedCiphertext {
ciphertext.clone()

View File

@ -3,6 +3,7 @@ use clap::Clap;
use niwl::Profile;
use niwl_rem::MixMessage::Heartbeat;
use niwl_rem::{MixMessage, RandomEjectionMix};
use rand::Rng;
use std::time::Duration;
#[derive(Clap)]
@ -52,6 +53,7 @@ fn main() {
}
SubCommand::Run(_cmd) => {
let mut profile = Profile::get_profile(&opts.profile_filename);
let filename = opts.profile_filename.clone();
let server = opts.niwl_server.clone();
tokio::runtime::Builder::new_current_thread()
.enable_all()
@ -60,7 +62,7 @@ fn main() {
.block_on(async {
let random_tag = profile.root_secret.tagging_key().generate_tag();
let mut rem = RandomEjectionMix::init(random_tag.clone());
println!("kicking off initial heartbeat...");
println!("[DEBUG] kicking off initial heartbeat...");
profile
.send_to_self(
&server,
@ -68,15 +70,22 @@ fn main() {
.unwrap(),
)
.await;
println!("starting..");
println!("[DEBUG] starting mixing loop");
let detection_key = profile.root_secret.extract_detection_key(24);
loop {
if rem.check_heartbeat() == false {
println!("[ERROR] Niwl Server is Delaying Messages for more than 2 Minutes...Possible Attack...")
}
match profile.detect_tags(&server).await {
Ok(detected_tags) => {
let mut latest_tag = None;
for (tag, ciphertext) in detected_tags.detected_tags.iter() {
if detection_key.test_tag(&tag) {
random_delay().await;
let plaintext = profile.private_key.decrypt(ciphertext);
match plaintext {
Some(plaintext) => match rem.push(tag, &plaintext) {
@ -114,10 +123,10 @@ fn main() {
}
latest_tag = Some(tag.clone());
}
println!("Updating...");
match &latest_tag {
Some(tag) => {
profile.update_previously_seen_tag(tag);
profile.save(&filename);
}
_ => {}
}
@ -126,10 +135,18 @@ fn main() {
println!("Error: {}", err)
}
}
println!("sleeping..");
tokio::time::sleep(Duration::new(5, 0)).await;
random_delay().await;
}
});
}
}
}
async fn random_delay() {
let mut rng = rand::thread_rng();
let seconds = rng.gen_range(0..10);
let nanos = rng.gen_range(0..1_000_000_000);
println!("[DEBUG] Waiting {}.{}s", seconds, nanos);
tokio::time::sleep(Duration::new(seconds, nanos)).await;
}