Skip to content

Commit

Permalink
v0.9.0 partial
Browse files Browse the repository at this point in the history
  • Loading branch information
chanderlud committed Jan 3, 2024
1 parent 0c3202c commit 583b54c
Show file tree
Hide file tree
Showing 12 changed files with 739 additions and 323 deletions.
6 changes: 4 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "cccp"
version = "0.8.0"
version = "0.9.0"
edition = "2021"
build = "build.rs"
repository = "https://github.com/chanderlud/cccp"
Expand All @@ -10,10 +10,11 @@ authors = ["Chander Luderman <me@chanchan.dev>"]

[dependencies]
clap = { version = "4.4", features = ["derive"] }
tokio = { version = "1.35", default-features = false, features = ["macros", "fs", "io-util"] }
tokio = { version = "1.35", default-features = false, features = ["macros", "fs", "io-util", "signal"] }
futures = "0.3"
log = { version = "0.4", features = ["std"] }
async-ssh2-tokio = "0.8"
russh = "0.38"
simple-logging = "2.0"
regex = "1.10"
dirs = "5.0"
Expand All @@ -29,6 +30,7 @@ ctr = "0.9"
aes = "0.8"
whoami = "1.4"
cipher = "0.4"
rand = "0.8"

[target.'cfg(unix)'.dependencies]
nix = { version = "0.27", features = ["fs"] }
Expand Down
2 changes: 2 additions & 0 deletions rust-toolchain.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[toolchain]
channel = "nightly"
81 changes: 65 additions & 16 deletions src/cipher.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
use aes::{Aes128, Aes256};
use aes::{Aes128, Aes192, Aes256};
use chacha20::{ChaCha20, ChaCha8};
use cipher::{KeyIvInit, StreamCipher, StreamCipherSeek};
use ctr::Ctr128BE;
use prost::Message;
use rand::rngs::{OsRng, StdRng};
use rand::{RngCore, SeedableRng};
use tokio::io;
use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt};

use crate::items::{Cipher, Crypto};
use crate::Result;

pub(crate) trait StreamCipherWrapper: Send + Sync {
fn seek(&mut self, index: u64);
Expand All @@ -17,10 +20,12 @@ impl<T> StreamCipherWrapper for T
where
T: StreamCipherSeek + StreamCipher + Send + Sync,
{
#[inline(always)]
fn seek(&mut self, index: u64) {
StreamCipherSeek::seek(self, index);
}

#[inline(always)]
fn apply_keystream(&mut self, buf: &mut [u8]) {
StreamCipher::apply_keystream(self, buf);
}
Expand All @@ -32,15 +37,15 @@ pub(crate) struct CipherStream<S: AsyncWrite + AsyncRead + Unpin> {
}

impl<S: AsyncWrite + AsyncRead + Unpin> CipherStream<S> {
pub(crate) fn new(stream: S, crypto: &Crypto) -> crate::Result<Self> {
pub(crate) fn new(stream: S, crypto: &Crypto) -> Result<Self> {
Ok(Self {
stream,
cipher: make_cipher(crypto)?,
cipher: crypto.make_cipher()?,
})
}

/// write a `Message` to the stream
pub(crate) async fn write_message<M: Message>(&mut self, message: &M) -> crate::Result<()> {
pub(crate) async fn write_message<M: Message>(&mut self, message: &M) -> Result<()> {
let len = message.encoded_len(); // get the length of the message
self.write_u32(len as u32).await?; // write the length of the message

Expand All @@ -53,7 +58,7 @@ impl<S: AsyncWrite + AsyncRead + Unpin> CipherStream<S> {
}

/// read a `Message` from the stream
pub(crate) async fn read_message<M: Message + Default>(&mut self) -> crate::Result<M> {
pub(crate) async fn read_message<M: Message + Default>(&mut self) -> Result<M> {
let len = self.read_u32().await? as usize; // read the length of the message

let mut buffer = vec![0; len]; // create a buffer to read the message into
Expand Down Expand Up @@ -87,15 +92,59 @@ impl<S: AsyncWrite + AsyncRead + Unpin> CipherStream<S> {
}
}

pub(crate) fn make_cipher(crypto: &Crypto) -> crate::Result<Box<dyn StreamCipherWrapper>> {
let cipher: Cipher = crypto.cipher.try_into()?;
let key = &crypto.key[..cipher.key_length()];
let iv = &crypto.iv[..cipher.iv_length()];

Ok(match cipher {
Cipher::Aes128 => Box::new(Ctr128BE::<Aes128>::new(key.into(), iv.into())),
Cipher::Aes256 => Box::new(Ctr128BE::<Aes256>::new(key.into(), iv.into())),
Cipher::Chacha8 => Box::new(ChaCha8::new(key.into(), iv.into())),
Cipher::Chacha20 => Box::new(ChaCha20::new(key.into(), iv.into())),
})
struct NoCipher;

impl StreamCipherWrapper for NoCipher {
#[inline(always)]
fn seek(&mut self, _index: u64) {}
#[inline(always)]
fn apply_keystream(&mut self, _data: &mut [u8]) {}
}

impl Crypto {
/// deterministically derive a new iv from the given iv
pub(crate) fn next_iv(&mut self) {
if self.cipher == i32::from(Cipher::None) {
return;
}

// create a seed from the first 8 bytes of the iv
let seed = u64::from_be_bytes(self.iv[..8].try_into().unwrap());
// create a random number generator from the seed
let mut rng = StdRng::seed_from_u64(seed);
let mut bytes = vec![0; self.iv.len()]; // buffer for new iv
rng.fill_bytes(&mut bytes); // fill the buffer with random bytes
self.iv = bytes; // set the new iv
}

/// randomize the iv
pub(crate) fn random_iv(&mut self) {
if self.cipher == i32::from(Cipher::None) {
return;
}

OsRng.fill_bytes(&mut self.iv);
}

/// create a new cipher
pub(crate) fn make_cipher(&self) -> Result<Box<dyn StreamCipherWrapper>> {
let cipher: Cipher = self.cipher.try_into()?;
let key = &self.key[..cipher.key_length()];
let iv = &self.iv[..cipher.iv_length()];

Ok(match cipher {
Cipher::None => Box::new(NoCipher),
Cipher::Aes128 => Box::new(Ctr128BE::<Aes128>::new(key.into(), iv.into())),
Cipher::Aes192 => Box::new(Ctr128BE::<Aes192>::new(key.into(), iv.into())),
Cipher::Aes256 => Box::new(Ctr128BE::<Aes256>::new(key.into(), iv.into())),
Cipher::Chacha8 => Box::new(ChaCha8::new(key.into(), iv.into())),
Cipher::Chacha20 => Box::new(ChaCha20::new(key.into(), iv.into())),
})
}
}

pub(crate) fn random_bytes(len: usize) -> Vec<u8> {
let mut bytes = vec![0; len];
OsRng.fill_bytes(&mut bytes);
bytes
}
26 changes: 23 additions & 3 deletions src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,9 @@ pub(crate) enum ErrorKind {
#[cfg(unix)]
Nix(nix::Error),
StripPrefix(std::path::StripPrefixError),
Ssh(async_ssh2_tokio::Error),
AsyncSsh(async_ssh2_tokio::Error),
RuSsh(russh::Error),
Base64Decode(base64::DecodeError),
MissingQueue,
MaxRetries,
#[cfg(windows)]
Expand Down Expand Up @@ -130,7 +132,23 @@ impl From<std::path::StripPrefixError> for Error {
impl From<async_ssh2_tokio::Error> for Error {
fn from(error: async_ssh2_tokio::Error) -> Self {
Self {
kind: ErrorKind::Ssh(error),
kind: ErrorKind::AsyncSsh(error),
}
}
}

impl From<russh::Error> for Error {
fn from(error: russh::Error) -> Self {
Self {
kind: ErrorKind::RuSsh(error),
}
}
}

impl From<base64::DecodeError> for Error {
fn from(error: base64::DecodeError) -> Self {
Self {
kind: ErrorKind::Base64Decode(error),
}
}
}
Expand All @@ -151,7 +169,9 @@ impl std::fmt::Display for Error {
#[cfg(unix)]
ErrorKind::Nix(ref error) => write!(f, "Nix error: {}", error),
ErrorKind::StripPrefix(ref error) => write!(f, "StripPrefix error: {}", error),
ErrorKind::Ssh(ref error) => write!(f, "SSH error: {}", error),
ErrorKind::AsyncSsh(ref error) => write!(f, "SSH error: {}", error),
ErrorKind::RuSsh(ref error) => write!(f, "SSH error: {}", error),
ErrorKind::Base64Decode(ref error) => write!(f, "Base64 decode error: {}", error),
ErrorKind::MissingQueue => write!(f, "Missing queue"),
ErrorKind::MaxRetries => write!(f, "Max retries"),
#[cfg(windows)]
Expand Down
18 changes: 13 additions & 5 deletions src/items.proto
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ message FileDetail {
string path = 1; // file path relative to the destination directory
uint64 size = 2; // file size in bytes
optional bytes signature = 3; // blake3 hash of file
optional Crypto crypto = 4; // encryption details
Crypto crypto = 4; // encryption details
}

message Crypto {
Expand All @@ -35,10 +35,12 @@ message Crypto {
}

enum Cipher {
CHACHA8 = 0;
AES128 = 1;
CHACHA20 = 2;
AES256 = 3;
NONE = 0;
CHACHA8 = 1;
AES128 = 2;
AES192 = 3;
CHACHA20 =43;
AES256 = 5;
}

// the receiver already had these files
Expand Down Expand Up @@ -81,4 +83,10 @@ message Failure {
// signals the receiver that the sender won't start new transfers
message Done {
uint32 reason = 1;
}

message Stats {
uint64 confirmed_packets = 1;
uint64 sent_packets = 2;
uint64 total_data = 3;
}
27 changes: 25 additions & 2 deletions src/items.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,17 @@
use crate::TransferStats;
use std::collections::HashMap;
use std::fmt::Display;
use std::sync::atomic::Ordering::Relaxed;

include!(concat!(env!("OUT_DIR"), "/cccp.items.rs"));

impl Message {
pub(crate) fn manifest(manifest: &Manifest) -> Self {
Self {
message: Some(message::Message::Manifest(manifest.clone())),
}
}

pub(crate) fn start(id: u32) -> Self {
Self {
message: Some(message::Message::Start(Start { id })),
Expand Down Expand Up @@ -49,24 +57,29 @@ impl Cipher {
/// the length of the key in bytes
pub(crate) fn key_length(&self) -> usize {
match self {
Self::Chacha20 | Self::Chacha8 | Self::Aes256 => 32,
Self::None => 0,
Self::Aes128 => 16,
Self::Aes192 => 24,
Self::Chacha20 | Self::Chacha8 | Self::Aes256 => 32,
}
}

/// the length of the iv in bytes
pub(crate) fn iv_length(&self) -> usize {
match self {
Self::None => 0,
Self::Chacha20 | Self::Chacha8 => 12,
Self::Aes256 | Self::Aes128 => 16,
Self::Aes256 | Self::Aes128 | Self::Aes192 => 16,
}
}
}

impl Display for Cipher {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let cipher = match self {
Self::None => "NONE",
Self::Aes128 => "AES128",
Self::Aes192 => "AES192",
Self::Aes256 => "AES256",
Self::Chacha8 => "CHACHA8",
Self::Chacha20 => "CHACHA20",
Expand All @@ -87,3 +100,13 @@ impl Manifest {
self.files.is_empty() && self.directories.is_empty()
}
}

impl Stats {
pub(crate) fn from(stats: &TransferStats) -> Self {
Self {
confirmed_packets: stats.confirmed_packets.load(Relaxed) as u64,
sent_packets: stats.sent_packets.load(Relaxed) as u64,
total_data: stats.total_data.load(Relaxed) as u64,
}
}
}
Loading

0 comments on commit 583b54c

Please sign in to comment.