#![warn(rustdoc::missing_doc_code_examples)]
#![allow(
clippy::missing_errors_doc,
clippy::missing_panics_doc,
clippy::module_name_repetitions,
clippy::must_use_candidate,
clippy::cast_possible_truncation,
clippy::similar_names,
clippy::implicit_hasher,
clippy::redundant_else
)]
use std::collections::HashSet;
use std::io::{self, Read, Write};
use std::sync::Once;
use header::DecryptedHeaderPackets;
use sodiumoxide::crypto::aead::chacha20poly1305_ietf::{self, Key, Nonce};
use crate::error::Crypt4GHError;
pub mod header;
pub mod keys;
pub mod error;
const CHUNK_SIZE: usize = 4096;
pub const SEGMENT_SIZE: usize = 65_536;
const CIPHER_DIFF: usize = 28;
const CIPHER_SEGMENT_SIZE: usize = SEGMENT_SIZE + CIPHER_DIFF;
pub struct WriteInfo<'a, W: Write> {
offset: usize,
limit: Option<usize>,
write_buffer: &'a mut W,
}
impl<'a, W: Write> WriteInfo<'a, W> {
pub fn new(offset: usize, limit: Option<usize>, write_buffer: &'a mut W) -> Self {
Self {
offset,
limit,
write_buffer,
}
}
fn write_all(&mut self, data: &[u8]) -> Result<(), Crypt4GHError> {
match &mut self.limit {
Some(limit) => {
if *limit >= data.len() {
self.write_buffer.write_all(data)?;
*limit -= data.len();
}
else {
self.write_buffer.write_all(&data[..*limit])?;
*limit = 0;
}
},
None => self.write_buffer.write_all(&data[self.offset..])?,
}
Ok(())
}
}
#[derive(Debug, PartialEq, Eq, Hash, Clone)]
pub struct Keys {
pub method: u8,
pub privkey: Vec<u8>,
pub recipient_pubkey: Vec<u8>,
}
pub(crate) static SODIUM_INIT: Once = Once::new();
pub(crate) fn init() {
SODIUM_INIT.call_once(|| {
sodiumoxide::init().expect("Unable to initialize libsodium");
});
}
pub fn encrypt<R: Read, W: Write>(
recipient_keys: &HashSet<Keys>,
read_buffer: &mut R,
write_buffer: &mut W,
range_start: usize,
range_span: Option<usize>,
) -> Result<(), Crypt4GHError> {
crate::init();
log::debug!("Start: {}, Span: {:?}", range_start, range_span);
if recipient_keys.is_empty() {
return Err(Crypt4GHError::NoRecipients);
}
log::info!("Encrypting the file");
log::debug!(" Start Coordinate: {}", range_start);
if range_start > 0 {
log::info!("Forwarding to position: {}", range_start);
}
read_buffer
.by_ref()
.take(range_start as u64)
.read_to_end(&mut Vec::new())
.map_err(|e| Crypt4GHError::NotEnoughInput(range_start, e.into()))?;
log::debug!(" Span: {:?}", range_span);
log::info!("Creating Crypt4GH header");
let mut session_key = [0_u8; 32];
sodiumoxide::randombytes::randombytes_into(&mut session_key);
let header_bytes = encrypt_header(recipient_keys, &Some(session_key))?;
log::debug!("header length: {}", header_bytes.len());
write_buffer.write_all(&header_bytes)?;
log::info!("Streaming content");
let mut segment = [0_u8; SEGMENT_SIZE];
match range_span {
None | Some(0) => loop {
let segment_len = read_buffer.read(&mut segment)?;
if segment_len == 0 {
break;
}
else if segment_len < SEGMENT_SIZE {
let (data, _) = segment.split_at(segment_len);
let nonce = Nonce::from_slice(&sodiumoxide::randombytes::randombytes(12))
.ok_or(Crypt4GHError::NoRandomNonce)?;
let key = Key::from_slice(&session_key).ok_or(Crypt4GHError::NoKey)?;
let encrypted_data = encrypt_segment(data, nonce, &key);
write_buffer.write_all(&encrypted_data)?;
break;
}
else {
let nonce = Nonce::from_slice(&sodiumoxide::randombytes::randombytes(12))
.ok_or(Crypt4GHError::NoRandomNonce)?;
let key = Key::from_slice(&session_key).ok_or(Crypt4GHError::NoKey)?;
let encrypted_data = encrypt_segment(&segment, nonce, &key);
write_buffer.write_all(&encrypted_data)?;
}
},
Some(mut remaining_length) => {
while remaining_length > 0 {
let segment_len = read_buffer.read(&mut segment)?;
if segment_len >= remaining_length {
let (data, _) = segment.split_at(remaining_length);
let nonce = Nonce::from_slice(&sodiumoxide::randombytes::randombytes(12))
.ok_or(Crypt4GHError::NoRandomNonce)?;
let key = Key::from_slice(&session_key).ok_or(Crypt4GHError::NoKey)?;
let encrypted_data = encrypt_segment(data, nonce, &key);
write_buffer.write_all(&encrypted_data)?;
break;
}
if segment_len < SEGMENT_SIZE {
let (data, _) = segment.split_at(segment_len);
let nonce = Nonce::from_slice(&sodiumoxide::randombytes::randombytes(12))
.ok_or(Crypt4GHError::NoRandomNonce)?;
let key = Key::from_slice(&session_key).ok_or(Crypt4GHError::NoKey)?;
let encrypted_data = encrypt_segment(data, nonce, &key);
write_buffer.write_all(&encrypted_data)?;
break;
}
let nonce = Nonce::from_slice(&sodiumoxide::randombytes::randombytes(12))
.ok_or(Crypt4GHError::NoRandomNonce)?;
let key = Key::from_slice(&session_key).ok_or(Crypt4GHError::NoKey)?;
let encrypted_data = encrypt_segment(&segment, nonce, &key);
write_buffer.write_all(&encrypted_data)?;
remaining_length -= segment_len;
}
},
}
log::info!("Encryption Successful");
Ok(())
}
pub fn encrypt_header(
recipient_keys: &HashSet<Keys>,
session_key: &Option<[u8; 32]>,
) -> Result<Vec<u8>, Crypt4GHError> {
let encryption_method = 0;
let session_key_or_new = session_key.unwrap_or_else(|| {
crate::init();
let mut session_key = [0_u8; 32];
sodiumoxide::randombytes::randombytes_into(&mut session_key);
session_key
});
let header_content = header::make_packet_data_enc(encryption_method, &session_key_or_new);
let header_packets = header::encrypt(&header_content, recipient_keys)?;
let header_bytes = header::serialize(header_packets);
Ok(header_bytes)
}
pub fn encrypt_segment(data: &[u8], nonce: Nonce, key: &Key) -> Vec<u8> {
vec![nonce.0.to_vec(), chacha20poly1305_ietf::seal(data, None, &nonce, key)].concat()
}
pub fn decrypt<R: Read, W: Write>(
keys: &[Keys],
read_buffer: &mut R,
write_buffer: &mut W,
range_start: usize,
range_span: Option<usize>,
sender_pubkey: &Option<Vec<u8>>,
) -> Result<(), Crypt4GHError> {
range_span.map_or_else(
|| {
log::info!("Decrypting file | Range: [{}, EOF)", range_start);
},
|span| {
log::info!("Decrypting file | Range: [{}, {})", range_start, range_start + span + 1);
},
);
let mut temp_buf = [0_u8; 16]; read_buffer
.read_exact(&mut temp_buf)
.map_err(|e| Crypt4GHError::ReadHeaderError(e.into()))?;
let header_info: header::HeaderInfo = header::deconstruct_header_info(&temp_buf)?;
let encrypted_packets = (0..header_info.packets_count)
.map(|_| {
let mut length_buffer = [0_u8; 4];
read_buffer
.read_exact(&mut length_buffer)
.map_err(|e| Crypt4GHError::ReadHeaderPacketLengthError(e.into()))?;
let length = bincode::deserialize::<u32>(&length_buffer)
.map_err(|e| Crypt4GHError::ParseHeaderPacketLengthError(e))?;
let length = length - 4;
let mut encrypted_data = vec![0_u8; length as usize];
read_buffer
.read_exact(&mut encrypted_data)
.map_err(|e| Crypt4GHError::ReadHeaderPacketDataError(e.into()))?;
Ok(encrypted_data)
})
.collect::<Result<Vec<Vec<u8>>, Crypt4GHError>>()?;
let DecryptedHeaderPackets {
data_enc_packets: session_keys,
edit_list_packet: edit_list,
} = header::deconstruct_header_body(encrypted_packets, keys, sender_pubkey)?;
range_span.map_or_else(
|| {
log::info!("Slicing from {} | Keeping all bytes", range_start);
},
|span| {
log::info!("Slicing from {} | Keeping {} bytes", range_start, span);
},
);
if range_span.is_some() && range_span.unwrap() == 0 {
return Err(Crypt4GHError::InvalidRangeSpan(range_span));
}
let mut write_info = WriteInfo::new(range_start, range_span, write_buffer);
match edit_list {
None => body_decrypt(read_buffer, &session_keys, &mut write_info, range_start)?,
Some(edit_list_content) => body_decrypt_parts(read_buffer, session_keys, write_info, edit_list_content)?,
}
log::info!("Decryption Over");
Ok(())
}
struct DecryptedBuffer<'a, W: Write> {
read_buffer: &'a mut dyn Read,
session_keys: Vec<Vec<u8>>,
buf: Vec<u8>,
is_decrypted: bool,
block: u64,
output: WriteInfo<'a, W>,
index: usize,
}
impl<'a, W: Write> DecryptedBuffer<'a, W> {
fn new(read_buffer: &'a mut impl Read, session_keys: Vec<Vec<u8>>, output: WriteInfo<'a, W>) -> Self {
let mut decryptor = Self {
read_buffer,
session_keys,
buf: Vec::with_capacity(CIPHER_SEGMENT_SIZE),
is_decrypted: false,
block: 0,
output,
index: 0,
};
decryptor.fetch();
decryptor.decrypt();
log::debug!("Index = {}", decryptor.index);
log::debug!("");
decryptor
}
fn fetch(&mut self) {
log::debug!("Fetching block {}", self.block);
self.block += 1;
self.buf.clear();
self.read_buffer
.take(CIPHER_SEGMENT_SIZE as u64)
.read_to_end(&mut self.buf)
.unwrap();
self.is_decrypted = false;
log::debug!("");
}
fn decrypt(&mut self) {
if !self.is_decrypted {
log::debug!("Decrypting block");
self.buf = decrypt_block(&self.buf, &self.session_keys).unwrap();
self.is_decrypted = true;
}
log::debug!("");
}
fn skip(&mut self, size: usize) {
assert!(size > 0, "You shouldn't skip 0 bytes");
log::debug!("Skipping {} bytes | Buffer size: {}", size, self.buf.len());
let mut remaining_size = size;
while remaining_size > 0 {
log::debug!("Left to skip: {} | Buffer size: {}", remaining_size, self.buf.len());
if remaining_size >= SEGMENT_SIZE {
self.fetch();
remaining_size -= SEGMENT_SIZE;
}
else {
if (self.index + remaining_size) > SEGMENT_SIZE {
self.fetch();
}
self.index = (self.index + remaining_size) % SEGMENT_SIZE;
log::debug!("Index = {}", self.index);
remaining_size -= remaining_size;
}
}
log::debug!("Finished skipping");
log::debug!("");
self.decrypt();
}
fn read(&mut self, size: usize) -> usize {
assert!(size > 0, "You shouldn't read 0 bytes");
log::debug!("Reading {} bytes | Buffer size: {}", size, self.buf.len());
let mut remaining_size = size;
while remaining_size > 0 {
log::debug!("Left to read: {} | Buffer size: {}", remaining_size, self.buf.len());
let n_bytes = usize::min(SEGMENT_SIZE - self.index, remaining_size);
self.decrypt();
self.output
.write_all(&self.buf[self.index..self.index + n_bytes])
.unwrap();
self.index = (self.index + n_bytes) % self.buf.len();
log::debug!("Index = {}", self.index);
if self.index == 0 {
self.fetch();
}
remaining_size -= n_bytes;
}
log::debug!("Finished reading");
log::debug!("");
size
}
}
pub fn body_decrypt_parts<W: Write>(
mut read_buffer: impl Read,
session_keys: Vec<Vec<u8>>,
output: WriteInfo<W>,
edit_list: Vec<u64>,
) -> Result<(), Crypt4GHError> {
log::debug!("Edit List: {:?}", edit_list);
if edit_list.is_empty() {
return Err(Crypt4GHError::EmptyEditList);
}
let mut decrypted = DecryptedBuffer::new(&mut read_buffer, session_keys, output);
let mut skip = true;
for edit_length in edit_list {
if skip {
if edit_length != 0 {
decrypted.skip(edit_length as usize);
}
}
else {
decrypted.read(edit_length as usize);
}
skip = !skip;
}
if !skip {
loop {
let n = decrypted.read(SEGMENT_SIZE);
if n == 0 {
break;
}
}
}
Ok(())
}
pub fn body_decrypt<W: Write>(
mut read_buffer: impl Read,
session_keys: &[Vec<u8>],
output: &mut WriteInfo<W>,
range_start: usize,
) -> Result<(), Crypt4GHError> {
if range_start >= SEGMENT_SIZE {
let start_segment = range_start / SEGMENT_SIZE;
log::info!("Fast-forwarding {} segments", start_segment);
let start_ciphersegment = start_segment * CIPHER_SEGMENT_SIZE;
read_buffer
.read_exact(&mut vec![0_u8; start_ciphersegment])
.map_err(|e| Crypt4GHError::BadStartRange(e.into()))?;
}
loop {
let mut chunk = Vec::with_capacity(CIPHER_SEGMENT_SIZE);
let n = read_buffer
.by_ref()
.take(CIPHER_SEGMENT_SIZE as u64)
.read_to_end(&mut chunk)
.map_err(|e| Crypt4GHError::ReadBlockError(e.into()))?;
if n == 0 {
break;
}
let segment = decrypt_block(&chunk, session_keys)?;
output
.write_all(&segment)
.map_err(|e| Crypt4GHError::UnableToWrite(e.into()))?;
if n < CIPHER_SEGMENT_SIZE {
break;
}
}
Ok(())
}
fn decrypt_block(ciphersegment: &[u8], session_keys: &[Vec<u8>]) -> Result<Vec<u8>, Crypt4GHError> {
let (nonce_slice, data) = ciphersegment.split_at(12);
let nonce = Nonce::from_slice(nonce_slice).ok_or(Crypt4GHError::UnableToWrapNonce)?;
session_keys
.iter()
.find_map(|key| Key::from_slice(key).and_then(|key| chacha20poly1305_ietf::open(data, None, &nonce, &key).ok()))
.ok_or(Crypt4GHError::UnableToDecryptBlock)
}
pub fn reencrypt<R: Read, W: Write>(
keys: &[Keys],
recipient_keys: &HashSet<Keys>,
read_buffer: &mut R,
write_buffer: &mut W,
trim: bool,
) -> Result<(), Crypt4GHError> {
let mut temp_buf = [0_u8; 16]; read_buffer
.read_exact(&mut temp_buf)
.map_err(|e| Crypt4GHError::ReadHeaderError(e.into()))?;
let header_info: header::HeaderInfo = header::deconstruct_header_info(&temp_buf)?;
let header_packets = (0..header_info.packets_count)
.map(|_| {
let mut length_buffer = [0_u8; 4];
read_buffer
.read_exact(&mut length_buffer)
.map_err(|e| Crypt4GHError::ReadHeaderPacketLengthError(e.into()))?;
let length = bincode::deserialize::<u32>(&length_buffer)
.map_err(|e| Crypt4GHError::ParseHeaderPacketLengthError(e))?;
let length = length - 4;
let mut encrypted_data = vec![0_u8; length as usize];
read_buffer
.read_exact(&mut encrypted_data)
.map_err(|e| Crypt4GHError::ReadHeaderPacketDataError(e.into()))?;
Ok(encrypted_data)
})
.collect::<Result<Vec<Vec<u8>>, Crypt4GHError>>()?;
let packets = header::reencrypt(header_packets, keys, recipient_keys, trim)?;
write_buffer.write_all(&header::serialize(packets))?;
log::info!("Streaming the remainder of the file");
loop {
let mut buf = Vec::with_capacity(CHUNK_SIZE);
let data = read_buffer.by_ref().take(CHUNK_SIZE as u64).read_to_end(&mut buf);
match data {
Ok(0) => break,
Ok(n) => write_buffer.write_all(&buf[0..n])?,
Err(e) if e.kind() == io::ErrorKind::Interrupted => (),
Err(e) => return Err(Crypt4GHError::ReadRemainderError(e.into())),
}
}
log::info!("Reencryption successful");
Ok(())
}
pub fn rearrange<R: Read, W: Write>(
keys: Vec<Keys>,
read_buffer: &mut R,
write_buffer: &mut W,
range_start: usize,
range_span: Option<usize>,
) -> Result<(), Crypt4GHError> {
let mut temp_buf = [0_u8; 16]; read_buffer
.read_exact(&mut temp_buf)
.map_err(|e| Crypt4GHError::ReadHeaderError(e.into()))?;
let header_info: header::HeaderInfo = header::deconstruct_header_info(&temp_buf)?;
let header_packets = (0..header_info.packets_count)
.map(|_| {
let mut length_buffer = [0_u8; 4];
read_buffer
.read_exact(&mut length_buffer)
.map_err(|e| Crypt4GHError::ReadHeaderPacketLengthError(e.into()))?;
let length = bincode::deserialize::<u32>(&length_buffer)
.map_err(|e| Crypt4GHError::ParseHeaderPacketLengthError(e))?;
let length = length - 4;
let mut encrypted_data = vec![0_u8; length as usize];
read_buffer
.read_exact(&mut encrypted_data)
.map_err(|e| Crypt4GHError::ReadHeaderPacketDataError(e.into()))?;
Ok(encrypted_data)
})
.collect::<Result<Vec<Vec<u8>>, Crypt4GHError>>()?;
let (packets, mut segment_oracle) = header::rearrange(header_packets, keys, range_start, range_span, &None)?;
write_buffer.write_all(&header::serialize(packets))?;
log::info!("Streaming the remainder of the file");
loop {
let mut buf = Vec::with_capacity(SEGMENT_SIZE + CIPHER_DIFF);
let data = read_buffer
.by_ref()
.take((SEGMENT_SIZE + CIPHER_DIFF) as u64)
.read_to_end(&mut buf);
let keep_segment = segment_oracle.next().unwrap();
log::debug!("Keep segment: {:?}", keep_segment);
match data {
Ok(0) => break,
Ok(n) => {
if keep_segment {
write_buffer.write_all(&buf[0..n])?;
}
},
Err(e) if e.kind() == io::ErrorKind::Interrupted => (),
Err(e) => return Err(Crypt4GHError::ReadRemainderError(e.into())),
}
}
log::info!("Rearrangement successful");
Ok(())
}