logging & included missing base64 encoding stage

This commit is contained in:
17ms 2023-05-27 16:01:39 +03:00
parent 15579b016e
commit 0fe036b8aa
11 changed files with 327 additions and 142 deletions

149
Cargo.lock generated
View File

@ -31,9 +31,9 @@ dependencies = [
[[package]] [[package]]
name = "aes-gcm" name = "aes-gcm"
version = "0.10.1" version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "82e1366e0c69c9f927b1fa5ce2c7bf9eafc8f9268c0b9800729e8b267612447c" checksum = "209b47e8954a928e1d72e86eca7000ebb6655fe1436d33eefc2201cad027e237"
dependencies = [ dependencies = [
"aead", "aead",
"aes", "aes",
@ -43,6 +43,15 @@ dependencies = [
"subtle", "subtle",
] ]
[[package]]
name = "aho-corasick"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "67fc08ce920c31afb70f013dcce1bfc3a3195de6a228474e45e1f145b36f8d04"
dependencies = [
"memchr",
]
[[package]] [[package]]
name = "anstream" name = "anstream"
version = "0.3.1" version = "0.3.1"
@ -128,9 +137,9 @@ checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8"
[[package]] [[package]]
name = "base64" name = "base64"
version = "0.21.0" version = "0.21.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4a4ddaa51a5bc52a6948f74c06d20aaaddb71924eab79b8c97a8c556e942d6a" checksum = "604178f6c5c21f02dc555784810edfb88d34ac2c73b2eae109655649ee73ce3d"
[[package]] [[package]]
name = "bitflags" name = "bitflags"
@ -140,9 +149,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]] [[package]]
name = "block-buffer" name = "block-buffer"
version = "0.9.0" version = "0.10.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
dependencies = [ dependencies = [
"generic-array", "generic-array",
] ]
@ -189,9 +198,9 @@ dependencies = [
[[package]] [[package]]
name = "clap" name = "clap"
version = "4.2.5" version = "4.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a1f23fa97e1d1641371b51f35535cb26959b8e27ab50d167a8b996b5bada819" checksum = "93aae7a4192245f70fe75dd9157fc7b4a5bf53e88d30bd4396f7d8f9284d5acc"
dependencies = [ dependencies = [
"clap_builder", "clap_builder",
"clap_derive", "clap_derive",
@ -200,9 +209,9 @@ dependencies = [
[[package]] [[package]]
name = "clap_builder" name = "clap_builder"
version = "4.2.5" version = "4.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fdc5d93c358224b4d6867ef1356d740de2303e9892edc06c5340daeccd96bab" checksum = "4f423e341edefb78c9caba2d9c7f7687d0e72e89df3ce3394554754393ac3990"
dependencies = [ dependencies = [
"anstream", "anstream",
"anstyle", "anstyle",
@ -213,9 +222,9 @@ dependencies = [
[[package]] [[package]]
name = "clap_derive" name = "clap_derive"
version = "4.2.0" version = "4.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f9644cd56d6b87dbe899ef8b053e331c0637664e9e21a33dfcdc36093f5c5c4" checksum = "191d9573962933b4027f932c600cd252ce27a8ad5979418fe78e43c07996f27b"
dependencies = [ dependencies = [
"heck", "heck",
"proc-macro2", "proc-macro2",
@ -225,9 +234,9 @@ dependencies = [
[[package]] [[package]]
name = "clap_lex" name = "clap_lex"
version = "0.4.1" version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a2dd5a6fe8c6e3502f568a6353e5273bbb15193ad9a89e457b9970798efbea1" checksum = "2da6da31387c7e4ef160ffab6d5e7f00c42626fe39aea70a7b0f1773f7dd6c1b"
[[package]] [[package]]
name = "colorchoice" name = "colorchoice"
@ -240,8 +249,10 @@ name = "contego"
version = "0.3.0" version = "0.3.0"
dependencies = [ dependencies = [
"aes-gcm", "aes-gcm",
"base64 0.21.0", "base64 0.21.2",
"clap", "clap",
"env_logger",
"log",
"ntest", "ntest",
"rand", "rand",
"sha256", "sha256",
@ -296,7 +307,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "90f9d052967f590a76e62eb387bd0bbb1b000182c3cefe5364db6b7211651bc0" checksum = "90f9d052967f590a76e62eb387bd0bbb1b000182c3cefe5364db6b7211651bc0"
dependencies = [ dependencies = [
"byteorder", "byteorder",
"digest", "digest 0.9.0",
"rand_core 0.5.1", "rand_core 0.5.1",
"subtle", "subtle",
"zeroize", "zeroize",
@ -311,6 +322,29 @@ dependencies = [
"generic-array", "generic-array",
] ]
[[package]]
name = "digest"
version = "0.10.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
dependencies = [
"block-buffer",
"crypto-common",
]
[[package]]
name = "env_logger"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85cdab6a89accf66733ad5a1693a4dcced6aeff64602b634530dd73c1f3ee9f0"
dependencies = [
"humantime",
"is-terminal",
"log",
"regex",
"termcolor",
]
[[package]] [[package]]
name = "errno" name = "errno"
version = "0.3.1" version = "0.3.1"
@ -432,6 +466,12 @@ version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
[[package]]
name = "humantime"
version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
[[package]] [[package]]
name = "idna" name = "idna"
version = "0.3.0" version = "0.3.0"
@ -553,26 +593,19 @@ dependencies = [
[[package]] [[package]]
name = "ntest" name = "ntest"
version = "0.8.1" version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e865500b46e35210765d62d549178c520badc018b2a71a827c29b305d680d1fb" checksum = "da8ec6d2b73d45307e926f5af46809768581044384637af6b3f3fe7c3c88f512"
dependencies = [ dependencies = [
"ntest_proc_macro_helper",
"ntest_test_cases", "ntest_test_cases",
"ntest_timeout", "ntest_timeout",
] ]
[[package]]
name = "ntest_proc_macro_helper"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a0e328d267a679d683b55222b3d06c2fb7358220857945bfc4e65a6b531e9994"
[[package]] [[package]]
name = "ntest_test_cases" name = "ntest_test_cases"
version = "0.8.0" version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6f7caf063242bb66721e74515dc01a915901063fa1f994bee7a2b9136f13370e" checksum = "be7d33be719c6f4d09e64e27c1ef4e73485dc4cc1f4d22201f89860a7fe22e22"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -581,11 +614,10 @@ dependencies = [
[[package]] [[package]]
name = "ntest_timeout" name = "ntest_timeout"
version = "0.8.1" version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bca6eaadc7c104fb2eb0c6d14782b9e33775aaf5584c3bcb0f87c89e3e6d6c07" checksum = "066b468120587a402f0b47d8f80035c921f6a46f8209efd0632a89a16f5188a4"
dependencies = [ dependencies = [
"ntest_proc_macro_helper",
"proc-macro-crate", "proc-macro-crate",
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -754,6 +786,23 @@ dependencies = [
"bitflags", "bitflags",
] ]
[[package]]
name = "regex"
version = "1.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "81ca098a9821bd52d6b24fd8b10bd081f47d39c22778cafaa75a2857a62c6390"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
]
[[package]]
name = "regex-syntax"
version = "0.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "436b050e76ed2903236f032a59761c1eb99e1b0aead2c257922771dab1fc8c78"
[[package]] [[package]]
name = "ring" name = "ring"
version = "0.16.20" version = "0.16.20"
@ -813,22 +862,20 @@ dependencies = [
[[package]] [[package]]
name = "sha2" name = "sha2"
version = "0.9.9" version = "0.10.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800" checksum = "82e6b795fe2e3b1e845bafcb27aa35405c4d47cdfc92af5fc8d3002f76cebdc0"
dependencies = [ dependencies = [
"block-buffer",
"cfg-if", "cfg-if",
"cpufeatures", "cpufeatures",
"digest", "digest 0.10.7",
"opaque-debug",
] ]
[[package]] [[package]]
name = "sha256" name = "sha256"
version = "1.1.2" version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "328169f167261957e83d82be47f9e36629e257c62308129033d7f7e7c173d180" checksum = "5f9f8b5de2bac3a4ae28e9b611072a8e326d9b26c8189c0972d4c321fa684f1f"
dependencies = [ dependencies = [
"hex", "hex",
"sha2", "sha2",
@ -899,6 +946,15 @@ dependencies = [
"unicode-ident", "unicode-ident",
] ]
[[package]]
name = "termcolor"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6"
dependencies = [
"winapi-util",
]
[[package]] [[package]]
name = "tinyvec" name = "tinyvec"
version = "1.6.0" version = "1.6.0"
@ -916,9 +972,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
[[package]] [[package]]
name = "tokio" name = "tokio"
version = "1.27.0" version = "1.28.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d0de47a4eecbe11f498978a9b29d792f0d2692d1dd003650c24c76510e3bc001" checksum = "0aa32867d44e6f2ce3385e89dceb990188b8bb0fb25b0cf576647a6f98ac5105"
dependencies = [ dependencies = [
"autocfg", "autocfg",
"bytes", "bytes",
@ -930,14 +986,14 @@ dependencies = [
"signal-hook-registry", "signal-hook-registry",
"socket2", "socket2",
"tokio-macros", "tokio-macros",
"windows-sys 0.45.0", "windows-sys 0.48.0",
] ]
[[package]] [[package]]
name = "tokio-macros" name = "tokio-macros"
version = "2.0.0" version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "61a573bdc87985e9d6ddeed1b3d864e8a302c847e40d647746df2f1de209d1ce" checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -1178,6 +1234,15 @@ version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-util"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
dependencies = [
"winapi",
]
[[package]] [[package]]
name = "winapi-x86_64-pc-windows-gnu" name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0" version = "0.4.0"

View File

@ -1,21 +1,23 @@
[package] [package]
name = "contego" name = "contego"
description = "Dynamic CLI tool for secure file transfer" description = "CLI tool for file transfer"
version = "0.3.0" version = "0.3.0"
edition = "2021" edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
tokio = { version = "1.20.4", features = ["full"] } tokio = { version = "1.28.1", features = ["full"] }
rand = "0.7.0" rand = "0.7.0"
x25519-dalek = "1.2.0" x25519-dalek = "1.2.0"
aes-gcm = "0.10.1" aes-gcm = "0.10.2"
base64 = "0.21.0" base64 = "0.21.2"
sha256 = "1.1.2" sha256 = "1.1.3"
ureq = "2.6.2" ureq = "2.6.2"
clap = { version = "4.2.5", features = ["derive"] } clap = { version = "4.3.0", features = ["derive"] }
log = "0.4.17"
env_logger = "0.10.0"
[dev-dependencies] [dev-dependencies]
tokio-test = "0.4.2" tokio-test = "0.4.2"
ntest = "0.8.1" ntest = "0.9.0"

View File

View File

@ -1,5 +1,6 @@
use std::{error::Error, net::SocketAddr, path::PathBuf}; use std::{error::Error, net::SocketAddr, path::PathBuf};
use log::{debug, error, info};
use tokio::{io::AsyncWriteExt, net::TcpStream}; use tokio::{io::AsyncWriteExt, net::TcpStream};
use crate::{ use crate::{
@ -21,19 +22,31 @@ impl Client {
} }
pub async fn connection(&self) -> Result<(), Box<dyn Error + Send + Sync>> { pub async fn connection(&self) -> Result<(), Box<dyn Error + Send + Sync>> {
info!("Trying to connect to the server at {}", self.addr);
let mut socket = TcpStream::connect(self.addr).await?; let mut socket = TcpStream::connect(self.addr).await?;
debug!("Connected to the TCP socket at {}", self.addr);
let mut handler = SocketHandler::new(&mut socket); let mut handler = SocketHandler::new(&mut socket);
let crypto = Crypto::new(&mut handler, true).await?; let crypto = Crypto::new(&mut handler, true).await?;
handler.set_crypto(crypto); handler.set_crypto(crypto);
info!("Encrypted connection to {} established", self.addr);
if !self.authorize(&mut handler).await? { if !self.authorize(&mut handler).await? {
// log: invalid access key '<self.key>' error!(
"Authorization failed due to an invalid access key '{}'",
self.key
);
return Ok(()); return Ok(());
} }
let metadata = self.metadata(&mut handler).await?; let metadata = self.metadata(&mut handler).await?;
self.requests(&mut handler, metadata).await?; self.requests(&mut handler, metadata).await?;
debug!("Connection sequence done, shutting down");
Ok(()) Ok(())
} }
@ -41,6 +54,8 @@ impl Client {
&self, &self,
handler: &mut SocketHandler<'_>, handler: &mut SocketHandler<'_>,
) -> Result<bool, Box<dyn Error + Send + Sync>> { ) -> Result<bool, Box<dyn Error + Send + Sync>> {
debug!("Starting authorization");
let msg = self.key.as_bytes().to_vec(); let msg = self.key.as_bytes().to_vec();
handler.send(&msg).await?; handler.send(&msg).await?;
@ -52,6 +67,8 @@ impl Client {
return Ok(false); return Ok(false);
} }
debug!("Authorization successfully done");
Ok(true) Ok(true)
} }
@ -59,10 +76,14 @@ impl Client {
&self, &self,
handler: &mut SocketHandler<'_>, handler: &mut SocketHandler<'_>,
) -> Result<Vec<FileInfo>, Box<dyn Error + Send + Sync>> { ) -> Result<Vec<FileInfo>, Box<dyn Error + Send + Sync>> {
debug!("Starting to receive metadata");
let buf = handler.recv().await?; let buf = handler.recv().await?;
let amt = String::from_utf8(buf.clone())?.parse::<usize>()?; let amt = String::from_utf8(buf.clone())?.parse::<usize>()?;
handler.send(&buf).await?; // confirmation handler.send(&buf).await?; // confirmation
debug!("Confirmed metadata amount ({})", amt);
let mut metadata = Vec::new(); let mut metadata = Vec::new();
while metadata.len() < amt { while metadata.len() < amt {
@ -74,6 +95,8 @@ impl Client {
let size = split[1].trim().parse::<u64>()?; let size = split[1].trim().parse::<u64>()?;
let hash = split[2].trim().to_string(); let hash = split[2].trim().to_string();
debug!("Metadata of file '{}' received successfully", name);
let info = FileInfo::new(name, size, hash); let info = FileInfo::new(name, size, hash);
metadata.push(info); metadata.push(info);
@ -87,12 +110,14 @@ impl Client {
handler: &mut SocketHandler<'_>, handler: &mut SocketHandler<'_>,
metadata: Vec<FileInfo>, metadata: Vec<FileInfo>,
) -> Result<(), Box<dyn Error + Send + Sync>> { ) -> Result<(), Box<dyn Error + Send + Sync>> {
info!("Starting to send requests");
for file in metadata { for file in metadata {
let (mut handle, path) = new_file(self.output.clone(), &file.name).await?; let (mut handle, path) = new_file(self.output.clone(), &file.name).await?;
let msg = file.hash.as_bytes().to_vec(); let msg = file.hash.as_bytes().to_vec();
handler.send(&msg).await?; handler.send(&msg).await?;
// log: downloading file to <path> info!("Requesting file '{}'", file.hash);
let mut remaining = file.size; let mut remaining = file.size;
@ -101,6 +126,8 @@ impl Client {
handle.write_all(&buf).await?; handle.write_all(&buf).await?;
handle.flush().await?; handle.flush().await?;
remaining -= buf.len() as u64; remaining -= buf.len() as u64;
debug!("File '{}': {} bytes remaining", file.hash, remaining);
} }
let check_hash = crypto::try_hash(&path)?; let check_hash = crypto::try_hash(&path)?;
@ -109,9 +136,13 @@ impl Client {
if check_hash != file.hash { if check_hash != file.hash {
return Err("Unsuccessful file transfer, hashes don't match".into()); return Err("Unsuccessful file transfer, hashes don't match".into());
} // else: log that the transfer was successful }
info!("File '{}' successfully transferred", file.hash);
} }
info!("All requests successfully done");
Ok(()) Ok(())
} }
} }

View File

@ -5,6 +5,7 @@ use aes_gcm::{
aes::Aes256, aes::Aes256,
Aes256Gcm, AesGcm, KeyInit, Nonce, Aes256Gcm, AesGcm, KeyInit, Nonce,
}; };
use log::debug;
use rand::{rngs::OsRng, RngCore}; use rand::{rngs::OsRng, RngCore};
use x25519_dalek::{EphemeralSecret, PublicKey, SharedSecret}; use x25519_dalek::{EphemeralSecret, PublicKey, SharedSecret};
@ -35,10 +36,14 @@ impl Crypto {
handler: &mut SocketHandler<'_>, handler: &mut SocketHandler<'_>,
go_first: bool, go_first: bool,
) -> Result<SharedSecret, Box<dyn Error + Send + Sync>> { ) -> Result<SharedSecret, Box<dyn Error + Send + Sync>> {
debug!("Starting ECDH key exchange");
let buf: Vec<u8>; let buf: Vec<u8>;
let own_sec = EphemeralSecret::new(OsRng); let own_sec = EphemeralSecret::new(OsRng);
let own_pbk = PublicKey::from(&own_sec); let own_pbk = PublicKey::from(&own_sec);
let msg = own_pbk.as_bytes().to_vec(); let mut msg = own_pbk.as_bytes().to_vec();
msg.push(b':'); // manual delimiter
if go_first { if go_first {
handler.send_raw(&msg).await?; handler.send_raw(&msg).await?;
@ -48,20 +53,29 @@ impl Crypto {
handler.send_raw(&msg).await?; handler.send_raw(&msg).await?;
} }
debug!("Calculating PPK from the shared secret");
let slice: [u8; DH_PBK_SIZE] = buf[..DH_PBK_SIZE].try_into()?; let slice: [u8; DH_PBK_SIZE] = buf[..DH_PBK_SIZE].try_into()?;
let recv_pbk = PublicKey::from(slice); let recv_pbk = PublicKey::from(slice);
let pvk = own_sec.diffie_hellman(&recv_pbk);
Ok(own_sec.diffie_hellman(&recv_pbk)) debug!("PPK successfully generated");
Ok(pvk)
} }
fn nonce(&self) -> Nonce<U12> { fn nonce(&mut self) -> Nonce<U12> {
debug!("Generating new unique nonce (AEAD)");
let mut nonce = Nonce::default(); let mut nonce = Nonce::default();
self.rng.fill_bytes(&mut nonce); self.rng.fill_bytes(&mut nonce);
nonce nonce
} }
pub async fn encrypt(&self, data: &[u8]) -> Result<Vec<u8>, Box<dyn Error + Send + Sync>> { pub async fn encrypt(&mut self, data: &[u8]) -> Result<Vec<u8>, Box<dyn Error + Send + Sync>> {
debug!("Encrypting {} bytes payload", data.len());
let nonce = self.nonce(); let nonce = self.nonce();
let encrypted = match self.cipher.encrypt(&nonce, data.as_ref()) { let encrypted = match self.cipher.encrypt(&nonce, data.as_ref()) {
Ok(data) => data, Ok(data) => data,
@ -75,6 +89,8 @@ impl Crypto {
} }
pub async fn decrypt(&self, data: &[u8]) -> Result<Vec<u8>, Box<dyn Error + Send + Sync>> { pub async fn decrypt(&self, data: &[u8]) -> Result<Vec<u8>, Box<dyn Error + Send + Sync>> {
debug!("Decrypting {} bytes payload", data.len());
let (nonce_bytes, data) = data.split_at(AES_NONCE_SIZE); let (nonce_bytes, data) = data.split_at(AES_NONCE_SIZE);
let nonce = Nonce::from_slice(nonce_bytes); let nonce = Nonce::from_slice(nonce_bytes);
let decrypted = match self.cipher.decrypt(nonce, data.as_ref()) { let decrypted = match self.cipher.decrypt(nonce, data.as_ref()) {
@ -87,9 +103,9 @@ impl Crypto {
} }
pub fn try_hash(path: &Path) -> Result<String, Box<dyn Error + Send + Sync>> { pub fn try_hash(path: &Path) -> Result<String, Box<dyn Error + Send + Sync>> {
debug!("Calculating SHA hash");
let hash = sha256::try_digest(path)?; let hash = sha256::try_digest(path)?;
Ok(hash) Ok(hash)
} }
// TODO: unit test if deemed necessary

View File

@ -1,4 +1,3 @@
pub mod cli;
pub mod client; pub mod client;
pub mod crypto; pub mod crypto;
pub mod parser; pub mod parser;

View File

@ -2,7 +2,7 @@ use std::{error::Error, net::SocketAddr, path::PathBuf};
use clap::{command, ArgGroup, Parser, Subcommand}; use clap::{command, ArgGroup, Parser, Subcommand};
use contego::parsers::{addr_parser, dirpath_parser, filepath_parser}; use contego::parser::{addr_parser, dirpath_parser, filepath_parser};
#[derive(Debug, Parser)] #[derive(Debug, Parser)]
#[command(about, version)] #[command(about, version)]
@ -54,19 +54,21 @@ enum Commands {
#[tokio::main] #[tokio::main]
async fn main() -> Result<(), Box<dyn Error>> { async fn main() -> Result<(), Box<dyn Error>> {
let cli = Cli::parse(); // TODO: init logger with default level set to 'info'
match cli.command { let _cli = Cli::parse();
Commands::Host {
port, //match cli.command {
ipv6, // Commands::Host {
infile, // port,
files, // ipv6,
chunksize, // infile,
local, // files,
} => {} // chunksize,
Commands::Connect { addr, out, key } => {} // local,
}; // } => {}
// Commands::Connect { addr, out, key } => {}
//};
Ok(()) Ok(())
} }

View File

@ -1,5 +1,6 @@
use std::{collections::HashMap, error::Error, net::SocketAddr, path::PathBuf, sync::Arc}; use std::{collections::HashMap, error::Error, net::SocketAddr, path::PathBuf, sync::Arc};
use log::{debug, error, info};
use tokio::{ use tokio::{
fs::File, fs::File,
io::AsyncReadExt, io::AsyncReadExt,
@ -48,30 +49,40 @@ impl Server {
async fn listen(self: Arc<Self>) -> Result<(), Box<dyn Error + Send + Sync>> { async fn listen(self: Arc<Self>) -> Result<(), Box<dyn Error + Send + Sync>> {
let listener = TcpListener::bind(self.addr).await?; let listener = TcpListener::bind(self.addr).await?;
info!("Listening on {} - Access key: {}", self.addr, self.key);
loop { loop {
let this_self = self.clone(); let this_self = self.clone();
let (mut socket, addr) = listener.accept().await?; let (mut socket, addr) = listener.accept().await?;
// log: new client connected: <addr> info!("New client connected: {}", addr);
match tokio::spawn(async move { this_self.connection(&mut socket).await }).await { match tokio::spawn(async move { this_self.connection(&mut socket, &addr).await }).await
Ok(_) => {} {
Err(e) => eprintln!("Error during connection ({}): {}", addr, e), Ok(_) => info!("Client disconnected: {}", addr),
Err(e) => error!("Fatal error in connection {}: {}", addr, e),
}; };
} }
} }
async fn connection(&self, socket: &mut TcpStream) -> Result<(), Box<dyn Error + Send + Sync>> { async fn connection(
&self,
socket: &mut TcpStream,
addr: &SocketAddr,
) -> Result<(), Box<dyn Error + Send + Sync>> {
let mut handler = SocketHandler::new(socket); let mut handler = SocketHandler::new(socket);
let crypto = Crypto::new(&mut handler, true).await?; let crypto = Crypto::new(&mut handler, false).await?;
handler.set_crypto(crypto); handler.set_crypto(crypto);
if !self.authorize(&mut handler).await? { debug!("({}): Connection established", addr);
if !self.authorize(&mut handler, addr).await? {
info!("({}): Invalid access key", addr);
return Ok(()); return Ok(());
} }
self.metadata(&mut handler).await?; self.metadata(&mut handler, addr).await?;
self.requests(&mut handler).await?; self.requests(&mut handler, addr).await?;
Ok(()) Ok(())
} }
@ -79,7 +90,10 @@ impl Server {
async fn authorize( async fn authorize(
&self, &self,
handler: &mut SocketHandler<'_>, handler: &mut SocketHandler<'_>,
addr: &SocketAddr,
) -> Result<bool, Box<dyn Error + Send + Sync>> { ) -> Result<bool, Box<dyn Error + Send + Sync>> {
debug!("({}): Starting authorization", addr);
let buf = handler.recv().await?; let buf = handler.recv().await?;
let key = String::from_utf8(buf)?; let key = String::from_utf8(buf)?;
@ -96,13 +110,18 @@ impl Server {
handler.send(&res_msg).await?; handler.send(&res_msg).await?;
debug!("({}): Authorization finished", addr);
Ok(is_valid) Ok(is_valid)
} }
async fn metadata( async fn metadata(
&self, &self,
handler: &mut SocketHandler<'_>, handler: &mut SocketHandler<'_>,
addr: &SocketAddr,
) -> Result<(), Box<dyn Error + Send + Sync>> { ) -> Result<(), Box<dyn Error + Send + Sync>> {
debug!("({}): Starting to send metadata", addr);
let amt = self.metadata.len(); let amt = self.metadata.len();
let msg = amt.to_string().as_bytes().to_vec(); let msg = amt.to_string().as_bytes().to_vec();
@ -115,11 +134,15 @@ impl Server {
return Err("Broken message sequence during metadata exchange".into()); return Err("Broken message sequence during metadata exchange".into());
} }
debug!("({}): Metadata amount confirmed successfully", addr);
for file in &self.metadata { for file in &self.metadata {
let msg = format!("{}:{}:{}", file.name, file.size, file.hash) let msg = format!("{}:{}:{}", file.name, file.size, file.hash)
.as_bytes() .as_bytes()
.to_vec(); .to_vec();
handler.send(&msg).await?; handler.send(&msg).await?;
debug!("({}): Sent metadata of file '{}'", addr, file.hash);
} }
Ok(()) Ok(())
@ -128,7 +151,10 @@ impl Server {
async fn requests( async fn requests(
&self, &self,
handler: &mut SocketHandler<'_>, handler: &mut SocketHandler<'_>,
addr: &SocketAddr,
) -> Result<(), Box<dyn Error + Send + Sync>> { ) -> Result<(), Box<dyn Error + Send + Sync>> {
debug!("({}): Waiting for file requests", addr);
loop { loop {
let buf = handler.recv().await?; let buf = handler.recv().await?;
let hash = String::from_utf8(buf)?; let hash = String::from_utf8(buf)?;
@ -138,14 +164,20 @@ impl Server {
break; break;
} }
debug!("({}): Received request for file '{}'", addr, hash);
let mut file = File::open(self.index[hash].clone()).await?; let mut file = File::open(self.index[hash].clone()).await?;
let mut remaining = file.metadata().await?.len(); let mut remaining = file.metadata().await?.len();
let mut sendbuf = vec![0u8; self.chunksize]; let mut sendbuf = vec![0u8; self.chunksize];
debug!("({}): Sending bytes of '{}'", addr, hash);
while remaining != 0 { while remaining != 0 {
let n = file.read(&mut sendbuf).await?; let n = file.read(&mut sendbuf).await?;
handler.send(&sendbuf[..n].to_vec()).await?; handler.send(&sendbuf[..n]).await?;
remaining -= n as u64; remaining -= n as u64;
debug!("({}): {} bytes remaining", addr, remaining);
} }
let buf = handler.recv().await?; let buf = handler.recv().await?;
@ -155,6 +187,8 @@ impl Server {
if confirmation != hash { if confirmation != hash {
return Err("Unsuccessful file transfer, hashes don't match".into()); return Err("Unsuccessful file transfer, hashes don't match".into());
} }
debug!("({}): File '{}' successfully transferred", addr, hash);
} }
Ok(()) Ok(())

View File

@ -1,6 +1,7 @@
use std::error::Error; use std::error::Error;
use base64::{engine::general_purpose, Engine}; use base64::{engine::general_purpose, Engine};
use log::debug;
use tokio::{ use tokio::{
io::{AsyncBufReadExt, AsyncWriteExt, BufReader, BufWriter}, io::{AsyncBufReadExt, AsyncWriteExt, BufReader, BufWriter},
net::{ net::{
@ -20,8 +21,8 @@ pub struct SocketHandler<'a> {
impl<'a> SocketHandler<'a> { impl<'a> SocketHandler<'a> {
pub fn new(socket: &'a mut TcpStream) -> Self { pub fn new(socket: &'a mut TcpStream) -> Self {
let (reader, writer) = socket.split(); let (reader, writer) = socket.split();
let mut reader = BufReader::new(reader); let reader = BufReader::new(reader);
let mut writer = BufWriter::new(writer); let writer = BufWriter::new(writer);
Self { Self {
writer, writer,
@ -30,19 +31,26 @@ impl<'a> SocketHandler<'a> {
} }
} }
pub fn set_crypto(&self, crypto: Crypto) { pub fn set_crypto(&mut self, crypto: Crypto) {
// setting up AES cipher requires DH key exchange in plaintext, // setting up AES cipher requires DH key exchange in plaintext,
// meaning crypto can't be initialized at the same time as the socket handler // meaning crypto can't be initialized at the same time as the socket handler
debug!("Cryptography module initialized to the connection");
self.crypto = Some(crypto); self.crypto = Some(crypto);
} }
pub async fn send(&mut self, data: &[u8]) -> Result<(), Box<dyn Error + Send + Sync>> { pub async fn send(&mut self, data: &[u8]) -> Result<(), Box<dyn Error + Send + Sync>> {
let data = match &self.crypto { let data = match &mut self.crypto {
Some(c) => c.encrypt(data).await?, Some(c) => c.encrypt(data).await?,
None => data.to_vec(), None => data.to_vec(), // syntactic sugar, never actually called
}; };
self.send_raw(&data).await?; let mut encoded = general_purpose::STANDARD_NO_PAD
.encode(data)
.as_bytes()
.to_vec();
encoded.push(b':');
self.send_raw(&encoded).await?;
Ok(()) Ok(())
} }
@ -51,6 +59,8 @@ impl<'a> SocketHandler<'a> {
self.writer.write_all(data).await?; self.writer.write_all(data).await?;
self.writer.flush().await?; self.writer.flush().await?;
debug!("Sent {} bytes to the socket", data.len());
Ok(()) Ok(())
} }
@ -75,6 +85,8 @@ impl<'a> SocketHandler<'a> {
return Err("Received 0 bytes from the socket".into()); return Err("Received 0 bytes from the socket".into());
} }
debug!("Received {} bytes from the socket", buf.len());
Ok(buf) Ok(buf)
} }
} }

View File

@ -1,5 +1,6 @@
use std::{collections::HashMap, error::Error, fs, net::SocketAddr, path::PathBuf}; use std::{collections::HashMap, error::Error, fs, net::SocketAddr, path::PathBuf};
use log::{debug, info};
use tokio::{fs::File, io::BufWriter}; use tokio::{fs::File, io::BufWriter};
use crate::crypto; use crate::crypto;
@ -16,6 +17,8 @@ pub enum Ip {
impl Ip { impl Ip {
pub fn fetch(self, port: u16) -> Result<SocketAddr, Box<dyn Error + Send + Sync>> { pub fn fetch(self, port: u16) -> Result<SocketAddr, Box<dyn Error + Send + Sync>> {
debug!("Fetching IP information");
let addr = match self { let addr = match self {
Ip::V4 => PUBLIC_IPV4, Ip::V4 => PUBLIC_IPV4,
Ip::V6 => PUBLIC_IPV6, Ip::V6 => PUBLIC_IPV6,
@ -28,6 +31,8 @@ impl Ip {
let res = format!("{}:{}", ureq::get(addr).call()?.into_string()?.trim(), port); let res = format!("{}:{}", ureq::get(addr).call()?.into_string()?.trim(), port);
let addr = res.parse::<SocketAddr>()?; let addr = res.parse::<SocketAddr>()?;
debug!("IP: {}", res);
Ok(addr) Ok(addr)
} }
} }
@ -49,6 +54,8 @@ fn filepaths(
infile: Option<PathBuf>, infile: Option<PathBuf>,
files: Option<Vec<PathBuf>>, files: Option<Vec<PathBuf>>,
) -> Result<Vec<PathBuf>, Box<dyn Error>> { ) -> Result<Vec<PathBuf>, Box<dyn Error>> {
info!("Collecting filepaths");
let mut filepaths = Vec::new(); let mut filepaths = Vec::new();
if let Some(infile) = infile { if let Some(infile) = infile {
@ -56,20 +63,22 @@ fn filepaths(
for path in paths.lines() { for path in paths.lines() {
filepaths.push(PathBuf::from(path)); filepaths.push(PathBuf::from(path));
} }
} } else if let Some(files) = files {
if let Some(files) = files {
for file in files { for file in files {
filepaths.push(file); filepaths.push(file);
} }
} }
debug!("Filepaths collection finished (total: {})", filepaths.len());
Ok(filepaths) Ok(filepaths)
} }
pub async fn metadata( pub async fn metadata(
files: &Vec<PathBuf>, files: &Vec<PathBuf>,
) -> Result<(Vec<(String, u64, String)>, HashMap<String, PathBuf>), Box<dyn Error + Send + Sync>> { ) -> Result<(Vec<FileInfo>, HashMap<String, PathBuf>), Box<dyn Error + Send + Sync>> {
info!("Collecting metadata");
let mut metadata = Vec::new(); let mut metadata = Vec::new();
let mut index = HashMap::new(); let mut index = HashMap::new();
@ -81,11 +90,19 @@ pub async fn metadata(
let hash = crypto::try_hash(path)?; let hash = crypto::try_hash(path)?;
if size > 0 { if size > 0 {
metadata.push((name, size, hash.clone())); debug!("Collecting '{}' metadata", name);
let info = FileInfo::new(name, size, hash.clone());
metadata.push(info);
index.insert(hash, path.clone()); index.insert(hash, path.clone());
} }
} }
debug!(
"Metadata collection successfully done (total: {})",
metadata.len()
);
Ok((metadata, index)) Ok((metadata, index))
} }
@ -93,6 +110,8 @@ pub async fn new_file(
mut path: PathBuf, mut path: PathBuf,
name: &str, name: &str,
) -> Result<(BufWriter<File>, PathBuf), Box<dyn Error + Send + Sync>> { ) -> Result<(BufWriter<File>, PathBuf), Box<dyn Error + Send + Sync>> {
debug!("New file handle for '{}'", name);
path.push(name); path.push(name);
let handle = File::create(&path).await?; let handle = File::create(&path).await?;

View File

@ -1,76 +1,81 @@
use contego::{common::Message, connector::Connector, crypto, listener::Listener};
use ntest::timeout;
use rand::{distributions::Alphanumeric, thread_rng, Rng};
use std::{ use std::{
fs::{self, File}, fs::{self, File},
io::{BufWriter, Write}, io::{BufWriter, Write},
net::SocketAddr,
path::PathBuf, path::PathBuf,
str::FromStr, str::FromStr,
thread,
}; };
use tokio::sync::mpsc;
use tokio_test::block_on;
#[test] use contego::{
client::Client,
server::Server,
util::{metadata, Ip},
};
use env_logger::Env;
use log::debug;
use ntest::timeout;
use rand::{distributions::Alphanumeric, thread_rng, Rng};
use tokio::{fs::read_to_string, sync::mpsc};
#[tokio::test]
#[timeout(2000)] #[timeout(2000)]
/// Tests communication between UI and individual handlers by mocking signals. /// Ensures backend communications integrity & the ability to handle individual requests.
fn filesync_signals() { async fn sockets_integration() {
let (testdata, paths) = write_testfiles(); env_logger::Builder::from_env(Env::default().default_filter_or("debug"))
.is_test(true)
.try_init()
.unwrap();
//env_logger::builder().is_test(true).try_init().unwrap();
let output_path = PathBuf::from("./tests/output/"); debug!("Initializing and starting the test");
let addr = SocketAddr::from(([127, 0, 0, 1], 9191));
let key = crypto::keygen(); let (testdata, paths) = testdata();
let (metadata, index) = metadata(&paths).await.unwrap();
let addr = Ip::Local.fetch(8080).unwrap();
let outdir = PathBuf::from("./tests/output/");
let key = String::from("testkey");
let c_key = key.clone(); let c_key = key.clone();
let (kstx, srx) = mpsc::channel::<Message>(10); let (tx, rx) = mpsc::channel::<()>(1);
let (stx, mut lsrx) = mpsc::channel::<Message>(10);
let (lctx, crx) = mpsc::channel::<Message>(10);
let (ctx, mut lcrx) = mpsc::channel::<Message>(10);
let server_handle = thread::spawn(move || { let server_handle = tokio::spawn(async move {
let listener = Listener::new(addr, key, 8192usize).unwrap(); debug!("Initializing the asynchronous server task");
block_on(listener.start(stx, srx, paths)).unwrap(); let server = Server::new(addr, key, 8192, metadata, index);
debug!("Starting to listen to incoming connections");
server.start(rx).await.unwrap();
}); });
let server_channel_handle = thread::spawn(move || { let client_handle = tokio::spawn(async move {
block_on(lsrx.recv()).unwrap(); // ClientConnect debug!("Initializing the asynchronous client task");
block_on(lsrx.recv()).unwrap(); // ConnectionReady let client = Client::new(addr, c_key, outdir);
block_on(lsrx.recv()).unwrap(); // ClientDisconnect debug!("Connecting to the server");
block_on(kstx.send(Message::Shutdown)).unwrap(); client.connection().await.unwrap();
}); });
let client_handle = thread::spawn(move || { client_handle.await.unwrap();
let output_path = output_path.clone(); tx.send(()).await.unwrap();
let connector = Connector::new(addr, c_key, output_path); server_handle.await.unwrap();
block_on(connector.connect(ctx, crx)).unwrap()
});
let client_channel_handle = thread::spawn(move || { debug!("Checking for file integrity");
let metadata = block_on(lcrx.recv()).unwrap(); // Metadata(HashMap)
if let Message::Metadata(inner) = metadata {
assert_eq!(inner.len(), 3);
for (filename, _) in inner {
block_on(lctx.send(Message::ClientReq(filename))).unwrap();
}
}
block_on(lctx.send(Message::Shutdown)).unwrap();
});
client_handle.join().unwrap();
client_channel_handle.join().unwrap();
server_handle.join().unwrap();
server_channel_handle.join().unwrap();
for file in testdata { for file in testdata {
let path = String::from("./tests/output/") + file.0;
let recv_content = read_to_string(path).await.unwrap();
assert_eq!(
recv_content, file.1,
"Output '{}' doesn't match the input '{}'",
recv_content, file.1
);
fs::remove_file(String::from("./tests/output/") + file.0).unwrap(); fs::remove_file(String::from("./tests/output/") + file.0).unwrap();
fs::remove_file(String::from("./tests/data/") + file.0).unwrap(); fs::remove_file(String::from("./tests/data/") + file.0).unwrap();
debug!("File '{}' checked and removed successfully", file.0);
} }
} }
fn write_testfiles() -> (Vec<(&'static str, String)>, Vec<PathBuf>) { fn testdata() -> (Vec<(&'static str, String)>, Vec<PathBuf>) {
let mut paths = Vec::new(); let mut paths = Vec::new();
let testdata = vec![ let testdata = vec![
("1.txt", generate_data()), ("1.txt", generate_data()),