logging & included missing base64 encoding stage
This commit is contained in:
parent
15579b016e
commit
0fe036b8aa
149
Cargo.lock
generated
149
Cargo.lock
generated
@ -31,9 +31,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "aes-gcm"
|
||||
version = "0.10.1"
|
||||
version = "0.10.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "82e1366e0c69c9f927b1fa5ce2c7bf9eafc8f9268c0b9800729e8b267612447c"
|
||||
checksum = "209b47e8954a928e1d72e86eca7000ebb6655fe1436d33eefc2201cad027e237"
|
||||
dependencies = [
|
||||
"aead",
|
||||
"aes",
|
||||
@ -43,6 +43,15 @@ dependencies = [
|
||||
"subtle",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "aho-corasick"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "67fc08ce920c31afb70f013dcce1bfc3a3195de6a228474e45e1f145b36f8d04"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstream"
|
||||
version = "0.3.1"
|
||||
@ -128,9 +137,9 @@ checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8"
|
||||
|
||||
[[package]]
|
||||
name = "base64"
|
||||
version = "0.21.0"
|
||||
version = "0.21.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a4a4ddaa51a5bc52a6948f74c06d20aaaddb71924eab79b8c97a8c556e942d6a"
|
||||
checksum = "604178f6c5c21f02dc555784810edfb88d34ac2c73b2eae109655649ee73ce3d"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
@ -140,9 +149,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
||||
|
||||
[[package]]
|
||||
name = "block-buffer"
|
||||
version = "0.9.0"
|
||||
version = "0.10.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4"
|
||||
checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
|
||||
dependencies = [
|
||||
"generic-array",
|
||||
]
|
||||
@ -189,9 +198,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.2.5"
|
||||
version = "4.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8a1f23fa97e1d1641371b51f35535cb26959b8e27ab50d167a8b996b5bada819"
|
||||
checksum = "93aae7a4192245f70fe75dd9157fc7b4a5bf53e88d30bd4396f7d8f9284d5acc"
|
||||
dependencies = [
|
||||
"clap_builder",
|
||||
"clap_derive",
|
||||
@ -200,9 +209,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap_builder"
|
||||
version = "4.2.5"
|
||||
version = "4.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0fdc5d93c358224b4d6867ef1356d740de2303e9892edc06c5340daeccd96bab"
|
||||
checksum = "4f423e341edefb78c9caba2d9c7f7687d0e72e89df3ce3394554754393ac3990"
|
||||
dependencies = [
|
||||
"anstream",
|
||||
"anstyle",
|
||||
@ -213,9 +222,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap_derive"
|
||||
version = "4.2.0"
|
||||
version = "4.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3f9644cd56d6b87dbe899ef8b053e331c0637664e9e21a33dfcdc36093f5c5c4"
|
||||
checksum = "191d9573962933b4027f932c600cd252ce27a8ad5979418fe78e43c07996f27b"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"proc-macro2",
|
||||
@ -225,9 +234,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap_lex"
|
||||
version = "0.4.1"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8a2dd5a6fe8c6e3502f568a6353e5273bbb15193ad9a89e457b9970798efbea1"
|
||||
checksum = "2da6da31387c7e4ef160ffab6d5e7f00c42626fe39aea70a7b0f1773f7dd6c1b"
|
||||
|
||||
[[package]]
|
||||
name = "colorchoice"
|
||||
@ -240,8 +249,10 @@ name = "contego"
|
||||
version = "0.3.0"
|
||||
dependencies = [
|
||||
"aes-gcm",
|
||||
"base64 0.21.0",
|
||||
"base64 0.21.2",
|
||||
"clap",
|
||||
"env_logger",
|
||||
"log",
|
||||
"ntest",
|
||||
"rand",
|
||||
"sha256",
|
||||
@ -296,7 +307,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "90f9d052967f590a76e62eb387bd0bbb1b000182c3cefe5364db6b7211651bc0"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
"digest",
|
||||
"digest 0.9.0",
|
||||
"rand_core 0.5.1",
|
||||
"subtle",
|
||||
"zeroize",
|
||||
@ -311,6 +322,29 @@ dependencies = [
|
||||
"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]]
|
||||
name = "errno"
|
||||
version = "0.3.1"
|
||||
@ -432,6 +466,12 @@ version = "0.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
|
||||
|
||||
[[package]]
|
||||
name = "humantime"
|
||||
version = "2.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
|
||||
|
||||
[[package]]
|
||||
name = "idna"
|
||||
version = "0.3.0"
|
||||
@ -553,26 +593,19 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ntest"
|
||||
version = "0.8.1"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e865500b46e35210765d62d549178c520badc018b2a71a827c29b305d680d1fb"
|
||||
checksum = "da8ec6d2b73d45307e926f5af46809768581044384637af6b3f3fe7c3c88f512"
|
||||
dependencies = [
|
||||
"ntest_proc_macro_helper",
|
||||
"ntest_test_cases",
|
||||
"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]]
|
||||
name = "ntest_test_cases"
|
||||
version = "0.8.0"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6f7caf063242bb66721e74515dc01a915901063fa1f994bee7a2b9136f13370e"
|
||||
checksum = "be7d33be719c6f4d09e64e27c1ef4e73485dc4cc1f4d22201f89860a7fe22e22"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@ -581,11 +614,10 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ntest_timeout"
|
||||
version = "0.8.1"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bca6eaadc7c104fb2eb0c6d14782b9e33775aaf5584c3bcb0f87c89e3e6d6c07"
|
||||
checksum = "066b468120587a402f0b47d8f80035c921f6a46f8209efd0632a89a16f5188a4"
|
||||
dependencies = [
|
||||
"ntest_proc_macro_helper",
|
||||
"proc-macro-crate",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@ -754,6 +786,23 @@ dependencies = [
|
||||
"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]]
|
||||
name = "ring"
|
||||
version = "0.16.20"
|
||||
@ -813,22 +862,20 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "sha2"
|
||||
version = "0.9.9"
|
||||
version = "0.10.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800"
|
||||
checksum = "82e6b795fe2e3b1e845bafcb27aa35405c4d47cdfc92af5fc8d3002f76cebdc0"
|
||||
dependencies = [
|
||||
"block-buffer",
|
||||
"cfg-if",
|
||||
"cpufeatures",
|
||||
"digest",
|
||||
"opaque-debug",
|
||||
"digest 0.10.7",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sha256"
|
||||
version = "1.1.2"
|
||||
version = "1.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "328169f167261957e83d82be47f9e36629e257c62308129033d7f7e7c173d180"
|
||||
checksum = "5f9f8b5de2bac3a4ae28e9b611072a8e326d9b26c8189c0972d4c321fa684f1f"
|
||||
dependencies = [
|
||||
"hex",
|
||||
"sha2",
|
||||
@ -899,6 +946,15 @@ dependencies = [
|
||||
"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]]
|
||||
name = "tinyvec"
|
||||
version = "1.6.0"
|
||||
@ -916,9 +972,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
|
||||
|
||||
[[package]]
|
||||
name = "tokio"
|
||||
version = "1.27.0"
|
||||
version = "1.28.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d0de47a4eecbe11f498978a9b29d792f0d2692d1dd003650c24c76510e3bc001"
|
||||
checksum = "0aa32867d44e6f2ce3385e89dceb990188b8bb0fb25b0cf576647a6f98ac5105"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"bytes",
|
||||
@ -930,14 +986,14 @@ dependencies = [
|
||||
"signal-hook-registry",
|
||||
"socket2",
|
||||
"tokio-macros",
|
||||
"windows-sys 0.45.0",
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-macros"
|
||||
version = "2.0.0"
|
||||
version = "2.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "61a573bdc87985e9d6ddeed1b3d864e8a302c847e40d647746df2f1de209d1ce"
|
||||
checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@ -1178,6 +1234,15 @@ version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
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]]
|
||||
name = "winapi-x86_64-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
|
16
Cargo.toml
16
Cargo.toml
@ -1,21 +1,23 @@
|
||||
[package]
|
||||
name = "contego"
|
||||
description = "Dynamic CLI tool for secure file transfer"
|
||||
description = "CLI tool for file transfer"
|
||||
version = "0.3.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
tokio = { version = "1.20.4", features = ["full"] }
|
||||
tokio = { version = "1.28.1", features = ["full"] }
|
||||
rand = "0.7.0"
|
||||
x25519-dalek = "1.2.0"
|
||||
aes-gcm = "0.10.1"
|
||||
base64 = "0.21.0"
|
||||
sha256 = "1.1.2"
|
||||
aes-gcm = "0.10.2"
|
||||
base64 = "0.21.2"
|
||||
sha256 = "1.1.3"
|
||||
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]
|
||||
tokio-test = "0.4.2"
|
||||
ntest = "0.8.1"
|
||||
ntest = "0.9.0"
|
||||
|
@ -1,5 +1,6 @@
|
||||
use std::{error::Error, net::SocketAddr, path::PathBuf};
|
||||
|
||||
use log::{debug, error, info};
|
||||
use tokio::{io::AsyncWriteExt, net::TcpStream};
|
||||
|
||||
use crate::{
|
||||
@ -21,19 +22,31 @@ impl Client {
|
||||
}
|
||||
|
||||
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?;
|
||||
|
||||
debug!("Connected to the TCP socket at {}", self.addr);
|
||||
|
||||
let mut handler = SocketHandler::new(&mut socket);
|
||||
let crypto = Crypto::new(&mut handler, true).await?;
|
||||
handler.set_crypto(crypto);
|
||||
|
||||
info!("Encrypted connection to {} established", self.addr);
|
||||
|
||||
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(());
|
||||
}
|
||||
|
||||
let metadata = self.metadata(&mut handler).await?;
|
||||
self.requests(&mut handler, metadata).await?;
|
||||
|
||||
debug!("Connection sequence done, shutting down");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@ -41,6 +54,8 @@ impl Client {
|
||||
&self,
|
||||
handler: &mut SocketHandler<'_>,
|
||||
) -> Result<bool, Box<dyn Error + Send + Sync>> {
|
||||
debug!("Starting authorization");
|
||||
|
||||
let msg = self.key.as_bytes().to_vec();
|
||||
handler.send(&msg).await?;
|
||||
|
||||
@ -52,6 +67,8 @@ impl Client {
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
debug!("Authorization successfully done");
|
||||
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
@ -59,10 +76,14 @@ impl Client {
|
||||
&self,
|
||||
handler: &mut SocketHandler<'_>,
|
||||
) -> Result<Vec<FileInfo>, Box<dyn Error + Send + Sync>> {
|
||||
debug!("Starting to receive metadata");
|
||||
|
||||
let buf = handler.recv().await?;
|
||||
let amt = String::from_utf8(buf.clone())?.parse::<usize>()?;
|
||||
handler.send(&buf).await?; // confirmation
|
||||
|
||||
debug!("Confirmed metadata amount ({})", amt);
|
||||
|
||||
let mut metadata = Vec::new();
|
||||
|
||||
while metadata.len() < amt {
|
||||
@ -74,6 +95,8 @@ impl Client {
|
||||
let size = split[1].trim().parse::<u64>()?;
|
||||
let hash = split[2].trim().to_string();
|
||||
|
||||
debug!("Metadata of file '{}' received successfully", name);
|
||||
|
||||
let info = FileInfo::new(name, size, hash);
|
||||
|
||||
metadata.push(info);
|
||||
@ -87,12 +110,14 @@ impl Client {
|
||||
handler: &mut SocketHandler<'_>,
|
||||
metadata: Vec<FileInfo>,
|
||||
) -> Result<(), Box<dyn Error + Send + Sync>> {
|
||||
info!("Starting to send requests");
|
||||
|
||||
for file in metadata {
|
||||
let (mut handle, path) = new_file(self.output.clone(), &file.name).await?;
|
||||
let msg = file.hash.as_bytes().to_vec();
|
||||
handler.send(&msg).await?;
|
||||
|
||||
// log: downloading file to <path>
|
||||
info!("Requesting file '{}'", file.hash);
|
||||
|
||||
let mut remaining = file.size;
|
||||
|
||||
@ -101,6 +126,8 @@ impl Client {
|
||||
handle.write_all(&buf).await?;
|
||||
handle.flush().await?;
|
||||
remaining -= buf.len() as u64;
|
||||
|
||||
debug!("File '{}': {} bytes remaining", file.hash, remaining);
|
||||
}
|
||||
|
||||
let check_hash = crypto::try_hash(&path)?;
|
||||
@ -109,9 +136,13 @@ impl Client {
|
||||
|
||||
if check_hash != file.hash {
|
||||
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(())
|
||||
}
|
||||
}
|
||||
|
@ -5,6 +5,7 @@ use aes_gcm::{
|
||||
aes::Aes256,
|
||||
Aes256Gcm, AesGcm, KeyInit, Nonce,
|
||||
};
|
||||
use log::debug;
|
||||
use rand::{rngs::OsRng, RngCore};
|
||||
use x25519_dalek::{EphemeralSecret, PublicKey, SharedSecret};
|
||||
|
||||
@ -35,10 +36,14 @@ impl Crypto {
|
||||
handler: &mut SocketHandler<'_>,
|
||||
go_first: bool,
|
||||
) -> Result<SharedSecret, Box<dyn Error + Send + Sync>> {
|
||||
debug!("Starting ECDH key exchange");
|
||||
|
||||
let buf: Vec<u8>;
|
||||
let own_sec = EphemeralSecret::new(OsRng);
|
||||
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 {
|
||||
handler.send_raw(&msg).await?;
|
||||
@ -48,20 +53,29 @@ impl Crypto {
|
||||
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 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();
|
||||
self.rng.fill_bytes(&mut 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 encrypted = match self.cipher.encrypt(&nonce, data.as_ref()) {
|
||||
Ok(data) => data,
|
||||
@ -75,6 +89,8 @@ impl Crypto {
|
||||
}
|
||||
|
||||
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 = Nonce::from_slice(nonce_bytes);
|
||||
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>> {
|
||||
debug!("Calculating SHA hash");
|
||||
|
||||
let hash = sha256::try_digest(path)?;
|
||||
|
||||
Ok(hash)
|
||||
}
|
||||
|
||||
// TODO: unit test if deemed necessary
|
||||
|
@ -1,4 +1,3 @@
|
||||
pub mod cli;
|
||||
pub mod client;
|
||||
pub mod crypto;
|
||||
pub mod parser;
|
||||
|
28
src/main.rs
28
src/main.rs
@ -2,7 +2,7 @@ use std::{error::Error, net::SocketAddr, path::PathBuf};
|
||||
|
||||
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)]
|
||||
#[command(about, version)]
|
||||
@ -54,19 +54,21 @@ enum Commands {
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn Error>> {
|
||||
let cli = Cli::parse();
|
||||
// TODO: init logger with default level set to 'info'
|
||||
|
||||
match cli.command {
|
||||
Commands::Host {
|
||||
port,
|
||||
ipv6,
|
||||
infile,
|
||||
files,
|
||||
chunksize,
|
||||
local,
|
||||
} => {}
|
||||
Commands::Connect { addr, out, key } => {}
|
||||
};
|
||||
let _cli = Cli::parse();
|
||||
|
||||
//match cli.command {
|
||||
// Commands::Host {
|
||||
// port,
|
||||
// ipv6,
|
||||
// infile,
|
||||
// files,
|
||||
// chunksize,
|
||||
// local,
|
||||
// } => {}
|
||||
// Commands::Connect { addr, out, key } => {}
|
||||
//};
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
use std::{collections::HashMap, error::Error, net::SocketAddr, path::PathBuf, sync::Arc};
|
||||
|
||||
use log::{debug, error, info};
|
||||
use tokio::{
|
||||
fs::File,
|
||||
io::AsyncReadExt,
|
||||
@ -48,30 +49,40 @@ impl Server {
|
||||
async fn listen(self: Arc<Self>) -> Result<(), Box<dyn Error + Send + Sync>> {
|
||||
let listener = TcpListener::bind(self.addr).await?;
|
||||
|
||||
info!("Listening on {} - Access key: {}", self.addr, self.key);
|
||||
|
||||
loop {
|
||||
let this_self = self.clone();
|
||||
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 {
|
||||
Ok(_) => {}
|
||||
Err(e) => eprintln!("Error during connection ({}): {}", addr, e),
|
||||
match tokio::spawn(async move { this_self.connection(&mut socket, &addr).await }).await
|
||||
{
|
||||
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 crypto = Crypto::new(&mut handler, true).await?;
|
||||
let crypto = Crypto::new(&mut handler, false).await?;
|
||||
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(());
|
||||
}
|
||||
|
||||
self.metadata(&mut handler).await?;
|
||||
self.requests(&mut handler).await?;
|
||||
self.metadata(&mut handler, addr).await?;
|
||||
self.requests(&mut handler, addr).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@ -79,7 +90,10 @@ impl Server {
|
||||
async fn authorize(
|
||||
&self,
|
||||
handler: &mut SocketHandler<'_>,
|
||||
addr: &SocketAddr,
|
||||
) -> Result<bool, Box<dyn Error + Send + Sync>> {
|
||||
debug!("({}): Starting authorization", addr);
|
||||
|
||||
let buf = handler.recv().await?;
|
||||
let key = String::from_utf8(buf)?;
|
||||
|
||||
@ -96,13 +110,18 @@ impl Server {
|
||||
|
||||
handler.send(&res_msg).await?;
|
||||
|
||||
debug!("({}): Authorization finished", addr);
|
||||
|
||||
Ok(is_valid)
|
||||
}
|
||||
|
||||
async fn metadata(
|
||||
&self,
|
||||
handler: &mut SocketHandler<'_>,
|
||||
addr: &SocketAddr,
|
||||
) -> Result<(), Box<dyn Error + Send + Sync>> {
|
||||
debug!("({}): Starting to send metadata", addr);
|
||||
|
||||
let amt = self.metadata.len();
|
||||
let msg = amt.to_string().as_bytes().to_vec();
|
||||
|
||||
@ -115,11 +134,15 @@ impl Server {
|
||||
return Err("Broken message sequence during metadata exchange".into());
|
||||
}
|
||||
|
||||
debug!("({}): Metadata amount confirmed successfully", addr);
|
||||
|
||||
for file in &self.metadata {
|
||||
let msg = format!("{}:{}:{}", file.name, file.size, file.hash)
|
||||
.as_bytes()
|
||||
.to_vec();
|
||||
handler.send(&msg).await?;
|
||||
|
||||
debug!("({}): Sent metadata of file '{}'", addr, file.hash);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@ -128,7 +151,10 @@ impl Server {
|
||||
async fn requests(
|
||||
&self,
|
||||
handler: &mut SocketHandler<'_>,
|
||||
addr: &SocketAddr,
|
||||
) -> Result<(), Box<dyn Error + Send + Sync>> {
|
||||
debug!("({}): Waiting for file requests", addr);
|
||||
|
||||
loop {
|
||||
let buf = handler.recv().await?;
|
||||
let hash = String::from_utf8(buf)?;
|
||||
@ -138,14 +164,20 @@ impl Server {
|
||||
break;
|
||||
}
|
||||
|
||||
debug!("({}): Received request for file '{}'", addr, hash);
|
||||
|
||||
let mut file = File::open(self.index[hash].clone()).await?;
|
||||
let mut remaining = file.metadata().await?.len();
|
||||
let mut sendbuf = vec![0u8; self.chunksize];
|
||||
|
||||
debug!("({}): Sending bytes of '{}'", addr, hash);
|
||||
|
||||
while remaining != 0 {
|
||||
let n = file.read(&mut sendbuf).await?;
|
||||
handler.send(&sendbuf[..n].to_vec()).await?;
|
||||
handler.send(&sendbuf[..n]).await?;
|
||||
remaining -= n as u64;
|
||||
|
||||
debug!("({}): {} bytes remaining", addr, remaining);
|
||||
}
|
||||
|
||||
let buf = handler.recv().await?;
|
||||
@ -155,6 +187,8 @@ impl Server {
|
||||
if confirmation != hash {
|
||||
return Err("Unsuccessful file transfer, hashes don't match".into());
|
||||
}
|
||||
|
||||
debug!("({}): File '{}' successfully transferred", addr, hash);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
@ -1,6 +1,7 @@
|
||||
use std::error::Error;
|
||||
|
||||
use base64::{engine::general_purpose, Engine};
|
||||
use log::debug;
|
||||
use tokio::{
|
||||
io::{AsyncBufReadExt, AsyncWriteExt, BufReader, BufWriter},
|
||||
net::{
|
||||
@ -20,8 +21,8 @@ pub struct SocketHandler<'a> {
|
||||
impl<'a> SocketHandler<'a> {
|
||||
pub fn new(socket: &'a mut TcpStream) -> Self {
|
||||
let (reader, writer) = socket.split();
|
||||
let mut reader = BufReader::new(reader);
|
||||
let mut writer = BufWriter::new(writer);
|
||||
let reader = BufReader::new(reader);
|
||||
let writer = BufWriter::new(writer);
|
||||
|
||||
Self {
|
||||
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,
|
||||
// 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);
|
||||
}
|
||||
|
||||
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?,
|
||||
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(())
|
||||
}
|
||||
@ -51,6 +59,8 @@ impl<'a> SocketHandler<'a> {
|
||||
self.writer.write_all(data).await?;
|
||||
self.writer.flush().await?;
|
||||
|
||||
debug!("Sent {} bytes to the socket", data.len());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@ -75,6 +85,8 @@ impl<'a> SocketHandler<'a> {
|
||||
return Err("Received 0 bytes from the socket".into());
|
||||
}
|
||||
|
||||
debug!("Received {} bytes from the socket", buf.len());
|
||||
|
||||
Ok(buf)
|
||||
}
|
||||
}
|
||||
|
29
src/util.rs
29
src/util.rs
@ -1,5 +1,6 @@
|
||||
use std::{collections::HashMap, error::Error, fs, net::SocketAddr, path::PathBuf};
|
||||
|
||||
use log::{debug, info};
|
||||
use tokio::{fs::File, io::BufWriter};
|
||||
|
||||
use crate::crypto;
|
||||
@ -16,6 +17,8 @@ pub enum Ip {
|
||||
|
||||
impl Ip {
|
||||
pub fn fetch(self, port: u16) -> Result<SocketAddr, Box<dyn Error + Send + Sync>> {
|
||||
debug!("Fetching IP information");
|
||||
|
||||
let addr = match self {
|
||||
Ip::V4 => PUBLIC_IPV4,
|
||||
Ip::V6 => PUBLIC_IPV6,
|
||||
@ -28,6 +31,8 @@ impl Ip {
|
||||
let res = format!("{}:{}", ureq::get(addr).call()?.into_string()?.trim(), port);
|
||||
let addr = res.parse::<SocketAddr>()?;
|
||||
|
||||
debug!("IP: {}", res);
|
||||
|
||||
Ok(addr)
|
||||
}
|
||||
}
|
||||
@ -49,6 +54,8 @@ fn filepaths(
|
||||
infile: Option<PathBuf>,
|
||||
files: Option<Vec<PathBuf>>,
|
||||
) -> Result<Vec<PathBuf>, Box<dyn Error>> {
|
||||
info!("Collecting filepaths");
|
||||
|
||||
let mut filepaths = Vec::new();
|
||||
|
||||
if let Some(infile) = infile {
|
||||
@ -56,20 +63,22 @@ fn filepaths(
|
||||
for path in paths.lines() {
|
||||
filepaths.push(PathBuf::from(path));
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(files) = files {
|
||||
} else if let Some(files) = files {
|
||||
for file in files {
|
||||
filepaths.push(file);
|
||||
}
|
||||
}
|
||||
|
||||
debug!("Filepaths collection finished (total: {})", filepaths.len());
|
||||
|
||||
Ok(filepaths)
|
||||
}
|
||||
|
||||
pub async fn metadata(
|
||||
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 index = HashMap::new();
|
||||
|
||||
@ -81,11 +90,19 @@ pub async fn metadata(
|
||||
let hash = crypto::try_hash(path)?;
|
||||
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
||||
debug!(
|
||||
"Metadata collection successfully done (total: {})",
|
||||
metadata.len()
|
||||
);
|
||||
|
||||
Ok((metadata, index))
|
||||
}
|
||||
|
||||
@ -93,6 +110,8 @@ pub async fn new_file(
|
||||
mut path: PathBuf,
|
||||
name: &str,
|
||||
) -> Result<(BufWriter<File>, PathBuf), Box<dyn Error + Send + Sync>> {
|
||||
debug!("New file handle for '{}'", name);
|
||||
|
||||
path.push(name);
|
||||
let handle = File::create(&path).await?;
|
||||
|
||||
|
@ -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::{
|
||||
fs::{self, File},
|
||||
io::{BufWriter, Write},
|
||||
net::SocketAddr,
|
||||
path::PathBuf,
|
||||
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)]
|
||||
/// Tests communication between UI and individual handlers by mocking signals.
|
||||
fn filesync_signals() {
|
||||
let (testdata, paths) = write_testfiles();
|
||||
/// Ensures backend communications integrity & the ability to handle individual requests.
|
||||
async fn sockets_integration() {
|
||||
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/");
|
||||
let addr = SocketAddr::from(([127, 0, 0, 1], 9191));
|
||||
let key = crypto::keygen();
|
||||
debug!("Initializing and starting the test");
|
||||
|
||||
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 (kstx, srx) = mpsc::channel::<Message>(10);
|
||||
let (stx, mut lsrx) = mpsc::channel::<Message>(10);
|
||||
let (lctx, crx) = mpsc::channel::<Message>(10);
|
||||
let (ctx, mut lcrx) = mpsc::channel::<Message>(10);
|
||||
let (tx, rx) = mpsc::channel::<()>(1);
|
||||
|
||||
let server_handle = thread::spawn(move || {
|
||||
let listener = Listener::new(addr, key, 8192usize).unwrap();
|
||||
block_on(listener.start(stx, srx, paths)).unwrap();
|
||||
let server_handle = tokio::spawn(async move {
|
||||
debug!("Initializing the asynchronous server task");
|
||||
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 || {
|
||||
block_on(lsrx.recv()).unwrap(); // ClientConnect
|
||||
block_on(lsrx.recv()).unwrap(); // ConnectionReady
|
||||
block_on(lsrx.recv()).unwrap(); // ClientDisconnect
|
||||
block_on(kstx.send(Message::Shutdown)).unwrap();
|
||||
let client_handle = tokio::spawn(async move {
|
||||
debug!("Initializing the asynchronous client task");
|
||||
let client = Client::new(addr, c_key, outdir);
|
||||
debug!("Connecting to the server");
|
||||
client.connection().await.unwrap();
|
||||
});
|
||||
|
||||
let client_handle = thread::spawn(move || {
|
||||
let output_path = output_path.clone();
|
||||
let connector = Connector::new(addr, c_key, output_path);
|
||||
block_on(connector.connect(ctx, crx)).unwrap()
|
||||
});
|
||||
client_handle.await.unwrap();
|
||||
tx.send(()).await.unwrap();
|
||||
server_handle.await.unwrap();
|
||||
|
||||
let client_channel_handle = thread::spawn(move || {
|
||||
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();
|
||||
debug!("Checking for file integrity");
|
||||
|
||||
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/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 testdata = vec![
|
||||
("1.txt", generate_data()),
|
||||
|
Loading…
Reference in New Issue
Block a user