Implemented P2P traffic encryption.

Changed serialization format of P2P messages.
Refactored P2P network code.
This commit is contained in:
Revertron
2021-05-30 00:33:13 +02:00
parent 5398410d8d
commit 319051edbd
15 changed files with 857 additions and 493 deletions
+4 -1
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "alfis" name = "alfis"
version = "0.5.9" version = "0.6.0"
authors = ["Revertron <alfis@revertron.com>"] authors = ["Revertron <alfis@revertron.com>"]
edition = "2018" edition = "2018"
build = "build.rs" build = "build.rs"
@@ -18,7 +18,9 @@ toml = "0.5.8"
digest = "0.9.0" digest = "0.9.0"
sha2 = "0.9.5" sha2 = "0.9.5"
ed25519-dalek = "1.0.1" ed25519-dalek = "1.0.1"
x25519-dalek = "1.1.1"
ecies-ed25519 = "0.5.1" ecies-ed25519 = "0.5.1"
chacha20poly1305 = "0.8.0"
signature = "1.3.0" signature = "1.3.0"
blakeout = "0.3.0" blakeout = "0.3.0"
num_cpus = "1.13.0" num_cpus = "1.13.0"
@@ -26,6 +28,7 @@ byteorder = "1.4.3"
serde = { version = "1.0.126", features = ["derive"] } serde = { version = "1.0.126", features = ["derive"] }
serde_json = "1.0.64" serde_json = "1.0.64"
bincode = "1.3.3" bincode = "1.3.3"
serde_cbor = "0.11.1"
base64 = "0.13.0" base64 = "0.13.0"
num-bigint = "0.4.0" num-bigint = "0.4.0"
num-traits = "0.2.14" num-traits = "0.2.14"
+10
View File
@@ -61,13 +61,23 @@ impl Block {
} }
} }
pub fn from_bytes(data: &[u8]) -> serde_cbor::Result<Self> {
serde_cbor::from_slice(data)
}
pub fn is_genesis(&self) -> bool { pub fn is_genesis(&self) -> bool {
self.index == 1 && self.index == 1 &&
matches!(Transaction::get_type(&self.transaction), TransactionType::Origin) && matches!(Transaction::get_type(&self.transaction), TransactionType::Origin) &&
self.prev_block_hash == Bytes::default() self.prev_block_hash == Bytes::default()
} }
/// Serializes block to CBOR for network
pub fn as_bytes(&self) -> Vec<u8> { pub fn as_bytes(&self) -> Vec<u8> {
serde_cbor::to_vec(&self).unwrap()
}
/// Serializes block to bincode format for hashing.
pub fn as_bytes_compact(&self) -> Vec<u8> {
bincode::serialize(&self).unwrap() bincode::serialize(&self).unwrap()
} }
+27 -1
View File
@@ -1010,8 +1010,10 @@ impl SignersCache {
pub mod tests { pub mod tests {
use log::LevelFilter; use log::LevelFilter;
use simplelog::{ColorChoice, ConfigBuilder, TerminalMode, TermLogger}; use simplelog::{ColorChoice, ConfigBuilder, TerminalMode, TermLogger};
#[allow(unused_imports)]
use log::{debug, error, info, trace, warn};
use crate::{Chain, Settings}; use crate::{Chain, Settings, Block};
fn init_logger() { fn init_logger() {
let config = ConfigBuilder::new() let config = ConfigBuilder::new()
@@ -1035,4 +1037,28 @@ pub mod tests {
chain.check_chain(u64::MAX); chain.check_chain(u64::MAX);
assert_eq!(chain.get_height(), 149); assert_eq!(chain.get_height(), 149);
} }
#[test]
pub fn check_serde() {
let settings = Settings::default();
let chain = Chain::new(&settings, "./tests/blockchain.db");
// Check the first block, its transaction doesn't have identity
let block = chain.get_block(1).unwrap();
let buf = serde_cbor::to_vec(&block).unwrap();
let block2: Block = serde_cbor::from_slice(&buf[..]).unwrap();
assert_eq!(block, block2);
// Check second block, it is common "full" block with domain
let block = chain.get_block(2).unwrap();
let buf = serde_cbor::to_vec(&block).unwrap();
let block2: Block = serde_cbor::from_slice(&buf[..]).unwrap();
assert_eq!(block, block2);
// Check block 36, it is an "empty" block, used to sign full blocks
let block = chain.get_block(36).unwrap();
let buf = serde_cbor::to_vec(&block).unwrap();
let block2: Block = serde_cbor::from_slice(&buf[..]).unwrap();
assert_eq!(block, block2);
}
} }
+2 -2
View File
@@ -9,7 +9,7 @@ pub fn check_block_hash(block: &Block) -> bool {
let mut copy: Block = block.clone(); let mut copy: Block = block.clone();
copy.hash = Bytes::default(); copy.hash = Bytes::default();
copy.signature = Bytes::default(); copy.signature = Bytes::default();
blakeout_data(&copy.as_bytes()) == block.hash blakeout_data(&copy.as_bytes_compact()) == block.hash
} }
/// Hashes data by given hasher /// Hashes data by given hasher
@@ -23,7 +23,7 @@ pub fn blakeout_data(data: &[u8]) -> Bytes {
pub fn check_block_signature(block: &Block) -> bool { pub fn check_block_signature(block: &Block) -> bool {
let mut copy = block.clone(); let mut copy = block.clone();
copy.signature = Bytes::default(); copy.signature = Bytes::default();
Keystore::check(&copy.as_bytes(), &copy.pub_key, &block.signature) Keystore::check(&copy.as_bytes_compact(), &copy.pub_key, &block.signature)
} }
/// Hashes some identity (domain in case of DNS). If you give it a public key, it will hash with it as well. /// Hashes some identity (domain in case of DNS). If you give it a public key, it will hash with it as well.
-5
View File
@@ -49,11 +49,6 @@ impl Transaction {
} }
} }
pub fn get_bytes(&self) -> Vec<u8> {
// Let it panic if something is not okay
serde_json::to_vec(&self).unwrap()
}
pub fn to_string(&self) -> String { pub fn to_string(&self) -> String {
// Let it panic if something is not okay // Let it panic if something is not okay
serde_json::to_string(&self).unwrap() serde_json::to_string(&self).unwrap()
-8
View File
@@ -1,7 +1,6 @@
use std::net::IpAddr; use std::net::IpAddr;
use std::num; use std::num;
use mio::Token;
use rand::Rng; use rand::Rng;
#[cfg(not(target_os = "macos"))] #[cfg(not(target_os = "macos"))]
use thread_priority::*; use thread_priority::*;
@@ -116,13 +115,6 @@ pub fn is_yggdrasil(addr: &IpAddr) -> bool {
false false
} }
/// Gets new token from old token, mutating the last
pub fn next(current: &mut Token) -> Token {
let next = current.0;
current.0 += 1;
Token(next)
}
/// Checks if this record has IP from Yggdrasil network /// Checks if this record has IP from Yggdrasil network
/// https://yggdrasil-network.github.io /// https://yggdrasil-network.github.io
pub fn is_yggdrasil_record(record: &DnsRecord) -> bool { pub fn is_yggdrasil_record(record: &DnsRecord) -> bool {
+66
View File
@@ -0,0 +1,66 @@
use chacha20poly1305::{ChaCha20Poly1305, Key, Nonce};
use chacha20poly1305::aead::{Aead, NewAead};
use std::fmt::{Debug, Formatter};
use std::fmt;
pub const ZERO_NONCE: [u8; 12] = [0u8; 12];
const FAILURE: &str = "encryption failure!";
/// A small wrap-up to use Chacha20 encryption for domain names.
#[derive(Clone)]
pub struct Chacha {
cipher: ChaCha20Poly1305,
nonce: [u8; 12]
}
impl Chacha {
pub fn new(key: &[u8], nonce: &[u8]) -> Self {
let key = Key::from_slice(key);
let cipher = ChaCha20Poly1305::new(key);
let mut buf = [0u8; 12];
buf.copy_from_slice(nonce);
Chacha { cipher, nonce: buf }
}
pub fn encrypt(&self, data: &[u8]) -> Vec<u8> {
let nonce = Nonce::from(self.nonce.clone());
self.cipher.encrypt(&nonce, data.as_ref()).expect(FAILURE)
}
pub fn decrypt(&self, data: &[u8]) -> Vec<u8> {
let nonce = Nonce::from(self.nonce.clone());
self.cipher.decrypt(&nonce, data.as_ref()).expect(FAILURE)
}
pub fn get_nonce(&self) -> &[u8; 12] {
&self.nonce
}
}
impl Debug for Chacha {
fn fmt(&self, fmt: &mut Formatter<'_>) -> fmt::Result {
fmt.write_str("ChaCha20Poly1305")
}
}
#[cfg(test)]
mod tests {
use crate::crypto::Chacha;
use crate::{to_hex};
#[test]
pub fn test_chacha() {
let buf = b"178135D209C697625E3EC71DA5C760382E54936F824EE5083908DA66B14ECE18";
let chacha1 = Chacha::new(b"178135D209C697625E3EC71DA5C76038", &buf[..12]);
let bytes1 = chacha1.encrypt(b"TEST");
println!("{}", to_hex(&bytes1));
let chacha2 = Chacha::new(b"178135D209C697625E3EC71DA5C76038", &buf[..12]);
let bytes2 = chacha2.decrypt(&bytes1);
assert_eq!(String::from_utf8(bytes2).unwrap(), "TEST");
let bytes2 = chacha2.encrypt(b"TEST");
assert_eq!(bytes1, bytes2);
}
}
+4 -1
View File
@@ -1,3 +1,6 @@
mod crypto_box; mod crypto_box;
mod chacha;
pub use crypto_box::CryptoBox; pub use crypto_box::CryptoBox;
pub use chacha::Chacha;
pub use chacha::ZERO_NONCE;
+5 -1
View File
@@ -184,7 +184,11 @@ fn main() {
let miner: Arc<Mutex<Miner>> = Arc::new(Mutex::new(miner_obj)); let miner: Arc<Mutex<Miner>> = Arc::new(Mutex::new(miner_obj));
let mut network = Network::new(Arc::clone(&context)); let mut network = Network::new(Arc::clone(&context));
network.start().expect("Error starting network component"); thread::spawn(move || {
// Give UI some time to appear :)
thread::sleep(Duration::from_millis(1000));
network.start();
});
create_genesis_if_needed(&context, &miner); create_genesis_if_needed(&context, &miner);
if no_gui { if no_gui {
+2 -2
View File
@@ -280,7 +280,7 @@ impl Miner {
Some(mut block) => { Some(mut block) => {
let index = block.index; let index = block.index;
let mut context = context.lock().unwrap(); let mut context = context.lock().unwrap();
block.signature = Bytes::from_bytes(&job.keystore.sign(&block.as_bytes())); block.signature = Bytes::from_bytes(&job.keystore.sign(&block.as_bytes_compact()));
let mut success = false; let mut success = false;
if context.chain.check_new_block(&block) != BlockQuality::Good { if context.chain.check_new_block(&block) != BlockQuality::Good {
warn!("Error adding mined block!"); warn!("Error adding mined block!");
@@ -341,7 +341,7 @@ fn find_hash(context: Arc<Mutex<Context>>, mut block: Block, running: Arc<Atomic
block.nonce = nonce; block.nonce = nonce;
digest.reset(); digest.reset();
digest.update(&block.as_bytes()); digest.update(&block.as_bytes_compact());
let diff = hash_difficulty(digest.result()); let diff = hash_difficulty(digest.result());
if diff >= target_diff { if diff >= target_diff {
block.hash = Bytes::from_bytes(digest.result()); block.hash = Bytes::from_bytes(digest.result());
+10 -28
View File
@@ -7,8 +7,8 @@ use crate::Bytes;
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Serialize, Deserialize)]
pub enum Message { pub enum Message {
Error, Error,
Hand { #[serde(default = "default_version")] app_version: String, origin: String, version: u32, public: bool, #[serde(default)] rand: String }, Hand { app_version: String, origin: String, version: u32, public: bool, rand_id: String, },
Shake { #[serde(default = "default_version")] app_version: String, origin: String, version: u32, ok: bool, height: u64 }, Shake { app_version: String, origin: String, version: u32, public: bool, rand_id: String, height: u64 },
Ping { height: u64, hash: Bytes }, Ping { height: u64, hash: Bytes },
Pong { height: u64, hash: Bytes }, Pong { height: u64, hash: Bytes },
Twin, Twin,
@@ -16,24 +16,23 @@ pub enum Message {
GetPeers, GetPeers,
Peers { peers: Vec<String> }, Peers { peers: Vec<String> },
GetBlock { index: u64 }, GetBlock { index: u64 },
Block { index: u64, block: String }, Block { index: u64, block: Vec<u8> },
} }
impl Message { impl Message {
pub fn from_bytes(bytes: Vec<u8>) -> Result<Self, ()> { pub fn from_bytes(bytes: Vec<u8>) -> Result<Self, ()> {
let text = String::from_utf8(bytes).unwrap_or(String::from("Error{}")); match serde_cbor::from_slice(bytes.as_slice()) {
match serde_json::from_str(&text) {
Ok(cmd) => Ok(cmd), Ok(cmd) => Ok(cmd),
Err(_) => Err(()) Err(_) => Err(())
} }
} }
pub fn hand(app_version: &str, origin: &str, version: u32, public: bool, rand: &str) -> Self { pub fn hand(app_version: &str, origin: &str, version: u32, public: bool, rand_id: &str) -> Self {
Message::Hand { app_version: app_version.to_owned(), origin: origin.to_owned(), version, public, rand: rand.to_owned() } Message::Hand { app_version: app_version.to_owned(), origin: origin.to_owned(), version, public, rand_id: rand_id.to_owned() }
} }
pub fn shake(app_version: &str, origin: &str, version: u32, ok: bool, height: u64) -> Self { pub fn shake(app_version: &str, origin: &str, version: u32, public: bool, rand_id: &str, height: u64) -> Self {
Message::Shake { app_version: app_version.to_owned(), origin: origin.to_owned(), version, ok, height } Message::Shake { app_version: app_version.to_owned(), origin: origin.to_owned(), version, public, rand_id: rand_id.to_owned(), height }
} }
pub fn ping(height: u64, hash: Bytes) -> Self { pub fn ping(height: u64, hash: Bytes) -> Self {
@@ -44,24 +43,7 @@ impl Message {
Message::Pong { height, hash } Message::Pong { height, hash }
} }
pub fn block(height: u64, str: String) -> Self { pub fn block(height: u64, block: Vec<u8>) -> Self {
Message::Block { index: height, block: str } Message::Block { index: height, block }
} }
}
fn default_version() -> String {
String::from("0.0.0")
}
#[cfg(test)]
mod tests {
use crate::p2p::Message;
#[test]
pub fn test_hand() {
assert!(serde_json::from_str::<Message>("\"Error\"").is_ok());
assert!(serde_json::from_str::<Message>("{\"Hand\":{\"origin\":\"\",\"version\":1,\"public\":false,\"rand\":\"123\"}}").is_ok());
assert!(serde_json::from_str::<Message>("{\"Hand\":{\"origin\":\"\",\"version\":1,\"public\":false}}").is_ok());
}
} }
+683 -441
View File
File diff suppressed because it is too large Load Diff
+18
View File
@@ -3,6 +3,7 @@ use std::collections::HashMap;
use mio::net::TcpStream; use mio::net::TcpStream;
use crate::p2p::State; use crate::p2p::State;
use crate::Block; use crate::Block;
use crate::crypto::Chacha;
#[derive(Debug)] #[derive(Debug)]
pub struct Peer { pub struct Peer {
@@ -16,6 +17,7 @@ pub struct Peer {
active: bool, active: bool,
reconnects: u32, reconnects: u32,
received_block: u64, received_block: u64,
cipher: Option<Chacha>,
fork: HashMap<u64, Block> fork: HashMap<u64, Block>
} }
@@ -32,10 +34,26 @@ impl Peer {
active: false, active: false,
reconnects: 0, reconnects: 0,
received_block: 0, received_block: 0,
cipher: None,
fork: HashMap::new() fork: HashMap::new()
} }
} }
pub fn set_cipher(&mut self, cipher: Chacha) {
self.cipher = Some(cipher);
}
pub fn get_cipher(&self) -> &Option<Chacha> {
&self.cipher
}
pub fn get_nonce(&self) -> &[u8; 12] {
match &self.cipher {
None => { &crate::crypto::ZERO_NONCE }
Some(chacha) => { chacha.get_nonce() }
}
}
pub fn get_addr(&self) -> SocketAddr { pub fn get_addr(&self) -> SocketAddr {
self.addr.clone() self.addr.clone()
} }
+22 -1
View File
@@ -12,7 +12,6 @@ use rand::seq::IteratorRandom;
use crate::{Bytes, commons}; use crate::{Bytes, commons};
use crate::commons::*; use crate::commons::*;
use crate::p2p::{Message, Peer, State}; use crate::p2p::{Message, Peer, State};
use crate::commons::next;
use std::io; use std::io;
const PING_PERIOD: u64 = 30; const PING_PERIOD: u64 = 30;
@@ -84,6 +83,12 @@ impl Peers {
State::Twin => { State::Twin => {
info!("Peer connection {} to {:?} is a twin", &token.0, &peer.get_addr()); info!("Peer connection {} to {:?} is a twin", &token.0, &peer.get_addr());
} }
State::ServerHandshake => {
info!("Peer connection {} from {:?} didn't shake hands", &token.0, &peer.get_addr());
}
State::HandshakeFinished => {
info!("Peer connection {} from {:?} shaked hands, but then failed", &token.0, &peer.get_addr());
}
} }
self.peers.remove(token); self.peers.remove(token);
@@ -193,6 +198,15 @@ impl Peers {
count count
} }
pub fn is_tween_connect(&self, id: &str) -> bool {
for (_, peer) in self.peers.iter() {
if peer.active() && peer.get_id() == id {
return true;
}
}
false
}
pub fn get_peers_banned_count(&self) -> usize { pub fn get_peers_banned_count(&self) -> usize {
self.ignored.len() self.ignored.len()
} }
@@ -401,6 +415,13 @@ impl Peers {
} }
} }
/// Gets new token from old token, mutating the last
pub fn next(current: &mut Token) -> Token {
let next = current.0;
current.0 += 1;
Token(next)
}
fn skip_private_addr(addr: &SocketAddr) -> bool { fn skip_private_addr(addr: &SocketAddr) -> bool {
if addr.ip().is_loopback() { if addr.ip().is_loopback() {
return true; return true;
+4 -2
View File
@@ -5,6 +5,8 @@ use crate::p2p::Message;
pub enum State { pub enum State {
Connecting, Connecting,
Connected, Connected,
ServerHandshake,
HandshakeFinished,
Idle { from: Instant }, Idle { from: Instant },
Message { data: Vec<u8> }, Message { data: Vec<u8> },
Error, Error,
@@ -25,8 +27,8 @@ impl State {
} }
pub fn message(message: Message) -> Self { pub fn message(message: Message) -> Self {
let response = serde_json::to_string(&message).unwrap(); let data = serde_cbor::to_vec(&message).unwrap();
State::Message {data: Vec::from(response.as_bytes()) } State::Message { data }
} }
pub fn is_idle(&self) -> bool { pub fn is_idle(&self) -> bool {