commit c0d7234b5a466845e88a689cdd689ac230881d1a Author: Avril Date: Mon Mar 22 21:56:43 2021 +0000 initial commit fork from 'chacha20' diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..96ef6c0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/target +Cargo.lock diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..ba99f99 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "chacha20stream" +version = "0.1.0" +authors = ["Avril "] +edition = "2018" +license = "MIT" +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[features] +default = ["smallvec"] + +# Explicitly clear in-memory buffers with `explicit_bzero()` instead of normal `bzero()`. +explicit_clear = [] + +[dependencies] +base64 = "0.13" +getrandom = "0.2" +openssl = "0.10" +smallvec = {version = "1.6", features=["union"], optional = true} diff --git a/src/cha.rs b/src/cha.rs new file mode 100644 index 0000000..9395f10 --- /dev/null +++ b/src/cha.rs @@ -0,0 +1,38 @@ + +use openssl::{ + symm::{ + Cipher, Crypter, Mode, + }, + error::ErrorStack, +}; +use crate::key::{Key, IV}; + +pub const KEY_SIZE: usize = 32; +pub const IV_SIZE: usize = 12; + +static NEW_CIPHER: fn() -> Cipher = Cipher::chacha20_poly1305; + +#[inline] pub fn decrypter(key: impl AsRef, iv: impl AsRef) -> Result +{ + Crypter::new( + NEW_CIPHER(), + Mode::Decrypt, + key.as_ref().as_ref(), + Some(iv.as_ref().as_ref()) + ) +} +#[inline] pub fn encrypter(key: impl AsRef, iv: impl AsRef) -> Result +{ + Crypter::new( + NEW_CIPHER(), + Mode::Encrypt, + key.as_ref().as_ref(), + Some(iv.as_ref().as_ref()) + ) +} + +/// Generate a random key and IV. +#[inline(always)] pub fn keygen() -> (Key, IV) +{ + (Key::new(), IV::new()) +} diff --git a/src/ext.rs b/src/ext.rs new file mode 100644 index 0000000..a8917cd --- /dev/null +++ b/src/ext.rs @@ -0,0 +1,122 @@ +use std::{ + mem, + iter::{ + self, + ExactSizeIterator, + FusedIterator, + }, + slice, + fmt, +}; +#[derive(Debug, Clone)] +pub struct HexStringIter(I, [u8; 2]); + +impl> HexStringIter +{ + /// Write this hex string iterator to a formattable buffer + pub fn consume(self, f: &mut F) -> fmt::Result + where F: std::fmt::Write + { + if self.1[0] != 0 { + write!(f, "{}", self.1[0] as char)?; + } + if self.1[1] != 0 { + write!(f, "{}", self.1[1] as char)?; + } + + for x in self.0 { + write!(f, "{:02x}", x)?; + } + + Ok(()) + } + + /// Consume into a string + pub fn into_string(self) -> String + { + let mut output = match self.size_hint() { + (0, None) => String::new(), + (_, Some(x)) | + (x, None) => String::with_capacity(x), + }; + self.consume(&mut output).unwrap(); + output + } +} + +pub trait HexStringIterExt: Sized +{ + fn into_hex(self) -> HexStringIter; +} + +pub type HexStringSliceIter<'a> = HexStringIter>>; + +pub trait HexStringSliceIterExt +{ + fn hex(&self) -> HexStringSliceIter<'_>; +} + +impl HexStringSliceIterExt for S + where S: AsRef<[u8]> +{ + fn hex(&self) -> HexStringSliceIter<'_> + { + self.as_ref().iter().copied().into_hex() + } +} + +impl> HexStringIterExt for I +{ + #[inline] fn into_hex(self) -> HexStringIter { + HexStringIter(self.into_iter(), [0u8; 2]) + } +} + +impl> Iterator for HexStringIter +{ + type Item = char; + fn next(&mut self) -> Option + { + match self.1 { + [_, 0] => { + use std::io::Write; + write!(&mut self.1[..], "{:02x}", self.0.next()?).unwrap(); + + Some(mem::replace(&mut self.1[0], 0) as char) + }, + [0, _] => Some(mem::replace(&mut self.1[1], 0) as char), + _ => unreachable!(), + } + } + + fn size_hint(&self) -> (usize, Option) { + let (l, h) = self.0.size_hint(); + + (l * 2, h.map(|x| x*2)) + } +} + +impl + ExactSizeIterator> ExactSizeIterator for HexStringIter{} +impl + FusedIterator> FusedIterator for HexStringIter{} + +impl> From> for String +{ + fn from(from: HexStringIter) -> Self + { + from.into_string() + } +} + +impl + Clone> fmt::Display for HexStringIter +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result + { + self.clone().consume(f) + } +} + +#[macro_export] macro_rules! prog1 { + ($first:expr, $($rest:expr);+ $(;)?) => { + ($first, $( $rest ),+).0 + } +} diff --git a/src/key.rs b/src/key.rs new file mode 100644 index 0000000..f53734c --- /dev/null +++ b/src/key.rs @@ -0,0 +1,153 @@ +use getrandom::getrandom; +use std::{fmt, str}; +pub use crate::cha::{ + KEY_SIZE, + IV_SIZE, +}; +use crate::ext::*; + +#[derive(Debug, Clone, PartialEq, Eq, Hash, Copy, Default)] +#[repr(transparent)] +pub struct Key([u8; KEY_SIZE]); + +#[derive(Debug, Clone, PartialEq, Eq, Hash, Copy, Default)] +#[repr(transparent)] +pub struct IV([u8; IV_SIZE]); + +impl Key +{ + #[inline] pub fn from_bytes(k: [u8; KEY_SIZE]) -> Self + { + Self(k) + } + pub fn new() -> Self + { + let mut output = [0u8; KEY_SIZE]; + getrandom(&mut output[..]).expect("rng fatal"); + Self(output) + } +} + +impl IV +{ + + #[inline] pub fn from_bytes(k: [u8; IV_SIZE]) -> Self + { + Self(k) + } + pub fn new() -> Self + { + let mut output = [0u8; IV_SIZE]; + getrandom(&mut output[..]).expect("rng fatal"); + Self(output) + } +} + +impl From<[u8; KEY_SIZE]> for Key +{ + #[inline] fn from(from: [u8; KEY_SIZE]) -> Self + { + Self(from) + } +} + +impl From<[u8; IV_SIZE]> for IV +{ + fn from(from: [u8; IV_SIZE]) -> Self + { + Self(from) + } +} + + +impl AsRef<[u8]> for Key +{ + fn as_ref(&self) -> &[u8] + { + &self.0[..] + } +} +impl AsRef<[u8]> for IV +{ + fn as_ref(&self) -> &[u8] + { + &self.0[..] + } +} + +impl AsMut<[u8]> for Key +{ + fn as_mut(&mut self) -> &mut [u8] + { + &mut self.0[..] + } +} + +impl AsMut<[u8]> for IV +{ + fn as_mut(&mut self) -> &mut [u8] + { + &mut self.0[..] + } +} + +impl AsRef for Key +{ + #[inline] fn as_ref(&self) -> &Key + { + self + } +} +impl AsRef for IV +{ + #[inline] fn as_ref(&self) -> &IV + { + self + } +} + +impl fmt::Display for Key +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result + { + write!(f, "{}", self.0.iter().copied().into_hex()) + } +} + +impl fmt::Display for IV +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result + { + write!(f, "{}", self.0.iter().copied().into_hex()) + } +} + +impl str::FromStr for Key +{ + type Err = base64::DecodeError; + + fn from_str(s: &str) -> Result { + let mut buffer = Vec::with_capacity(KEY_SIZE); + base64::decode_config_buf(s.as_bytes(), base64::STANDARD, &mut buffer)?; + + let mut this = Self::default(); + let sz = std::cmp::min(KEY_SIZE, buffer.len()); + (&mut this.0[..sz]).copy_from_slice(&buffer[..sz]); + Ok(this) + } +} + +impl str::FromStr for IV +{ + type Err = base64::DecodeError; + + fn from_str(s: &str) -> Result { + let mut buffer = Vec::with_capacity(IV_SIZE); + base64::decode_config_buf(s.as_bytes(), base64::STANDARD, &mut buffer)?; + + let mut this = Self::default(); + let sz = std::cmp::min(IV_SIZE, buffer.len()); + (&mut this.0[..sz]).copy_from_slice(&buffer[..sz]); + Ok(this) + } +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..710995f --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,16 @@ +#![allow(dead_code)] + +//extern crate test; + +#[macro_use] mod ext; #[allow(unused_imports)] use ext::*; + +pub mod key; +mod cha; +mod stream; + +pub use stream::Sink; +pub use key::{ + Key, IV, +}; + +pub use cha::keygen; diff --git a/src/stream.rs b/src/stream.rs new file mode 100644 index 0000000..d29eb45 --- /dev/null +++ b/src/stream.rs @@ -0,0 +1,250 @@ +#![allow(dead_code)] + +use super::*; +use key::*; + +use std::io::{self, Write}; +use std::fmt; +use openssl::{ + symm::Crypter, + error::ErrorStack, +}; + +#[cfg(feature="smallvec")] +pub const BUFFER_SIZE: usize = 32; + +#[cfg(feature="smallvec")] +type BufferVec = smallvec::SmallVec<[u8; BUFFER_SIZE]>; +#[cfg(not(feature="smallvec"))] +type BufferVec = Vec; + +pub type Error = ErrorStack; + +/// ChaCha Sink +/// +/// # Note +/// When writing, a temporary buffer stored in the structure is used. This buffer is **not** cleared after a write, for efficiency reasons. This may leave sensitive information in the buffer after the write operation. +/// The `flush()` implementation *does* clear this buffer. +/// You can use the `prune()` function to zero out this buffer manually too. +//#[derive(Debug)] +pub struct Sink +{ + stream: W, + crypter: Crypter, // for chacha, finalize does nothing it seems. we can also call it multiple times. + + buffer: BufferVec, // used to buffer the operation +} + +impl fmt::Debug for Sink +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result + { + write!(f, "Sink({:?}, ({} buffer cap))", self.stream, self.buffer.capacity()) + } +} + +impl Sink +where W: Write +{ + /// Create a new Chacha Sink stream wrapper + #[inline] fn new(stream: W, crypter: Crypter) -> Self + { + Self{stream, crypter, buffer: BufferVec::new()} + } + + /// Create an encrypting Chacha Sink stream wrapper + pub fn encrypt(stream: W, key: Key, iv: IV) -> Result + { + Ok(Self::new(stream, cha::encrypter(key, iv)?)) + } + + /// Create a decrypting Chacha Sink stream wrapper + pub fn decrypt(stream: W, key: Key, iv: IV) -> Result + { + Ok(Self::new(stream, cha::decrypter(key, iv)?)) + } + + + /// Consume into the inner stream + #[inline] pub fn into_inner(self) -> W + { + self.stream + } + + /// Consume into the inner stream and crypter + #[inline] pub fn into_parts(self) -> (W, Crypter) + { + (self.stream, self.crypter) + } + + /// The crypter of this instance + #[inline] pub fn crypter(&self) -> &Crypter + { + &self.crypter + } + + /// The crypter of this instance + #[inline] pub fn crypter_mut(&mut self) -> &mut Crypter + { + &mut self.crypter + } + + /// The inner stream + #[inline] pub fn inner(&self) -> &W + { + &self.stream + } + + /// The inner stream + #[inline] pub fn inner_mut(&mut self) -> &mut W + { + &mut self.stream + } + + /// Perform the cipher transform on this input to the inner buffer, returning the number of bytes updated. + fn transform(&mut self, buf: &[u8]) -> Result + { + if buf.len() > self.buffer.len() { + self.buffer.resize(buf.len(), 0); + } + + let n = self.crypter.update(&buf[..], &mut self.buffer[..])?; + let _f = self.crypter.finalize(&mut self.buffer[..n])?; // I don't know if this is needed. + debug_assert_eq!(_f, 0); + + Ok(n) + } + + /// Clear the internal buffer while keeping it allocated for further use. + /// + /// This does not affect operations at all, all it does is 0 out the left-over temporary buffer from the last operation(s). + pub fn prune(&mut self) + { + #[cfg(feature="explicit_clear")] + { + use std::ffi::c_void; + extern "C" { + fn explicit_bzero(_: *mut c_void, _:usize); + } + unsafe { + explicit_bzero(self.buffer.as_mut_ptr() as *mut c_void, self.buffer.len()); + } + return; + } + #[cfg(not(feature="explicit_clear"))] + unsafe { + std::ptr::write_bytes(self.buffer.as_mut_ptr(), 0, self.buffer.len()); + } + } +} + +impl Write for Sink +{ + #[inline] fn write(&mut self, buf: &[u8]) -> io::Result { + let n = self.transform(buf)?; + + self.stream.write(&self.buffer[..n]) + + } + #[inline] fn write_all(&mut self, buf: &[u8]) -> io::Result<()> { + let n = self.transform(buf)?; + + self.stream.write_all(&self.buffer[..n]) + } + #[inline] fn flush(&mut self) -> io::Result<()> { + #[cfg(feature="explicit_clear")] self.prune(); + self.buffer.clear(); + + self.stream.flush() + } +} + +#[cfg(test)] +mod tests +{ + use super::*; + + const INPUT: &'static str = "Hello world!Hello world!Hello world!Hello world!Hello world!Hello world!Hello world!Hello world!Hello world!Hello world!Hello world!Hello world!Hello world!"; + + fn enc_stream(input: impl AsRef<[u8]>, key: Key, iv: IV) -> Sink> + { + let enc_buffer = Vec::new(); + let input = input.as_ref(); + + eprintln!("(enc) Key: {}, IV: {}, Input: ({}, {})", key, iv, input.len(), input.hex()); + + let mut stream = Sink::encrypt(enc_buffer, key, iv).expect("sink::enc"); + assert_eq!(stream.write(input).unwrap(), input.len()); + stream.flush().unwrap(); + + eprintln!("Output encrypted: {}", stream.inner().hex()); + + stream + } + + #[test] + fn enc() + { + let (key, iv) = cha::keygen(); + + eprintln!("Sink ends: {:?}", enc_stream(INPUT.as_bytes(), key, iv)); + } + + #[test] + fn dec() + { + println!(">>> Sink's size with ref is {}", std::mem::size_of::>>()); + let (key, iv) = cha::keygen(); + eprintln!("Input unencrypted: {}", INPUT.hex()); + + let input = enc_stream(INPUT.as_bytes(), key.clone(), iv.clone()).into_inner(); + + let mut dec_buffer = Vec::new(); + { + let mut stream = Sink::decrypt(&mut dec_buffer, key, iv).expect("sink::dec"); + + stream.write_all(&input[..]).unwrap(); + stream.flush().unwrap(); + + eprintln!("Output decrypted: {}", stream.inner().hex()); + } + assert_eq!(&dec_buffer[..], INPUT.as_bytes()); + } + + /// Checks if explicit clear is actually clearing. + #[cfg(feature="explicit_clear")] + #[test] + fn remainder() + { + let mut dec_buffer = Vec::new(); + + let (buf, off, _s) = { + let (key, iv) = cha::keygen(); + + let input = enc_stream(INPUT.as_bytes(), key.clone(), iv.clone()).into_inner(); + + { + let mut stream = Sink::decrypt(&mut dec_buffer, key, iv).expect("sink::rem"); + + stream.write_all(&input[..]).unwrap(); + + let by = stream.buffer[0]; + //stream.prune(); + stream.flush().unwrap(); + (by, (stream.buffer.as_ptr() as u64), stream) + } + }; + + // Check to see if the buffer remains in our process's memory. + use std::fs::OpenOptions; + use std::io::{Seek, SeekFrom, Read}; + let mut file = OpenOptions::new().read(true).open("/proc/self/mem").unwrap(); + + file.seek(SeekFrom::Start(off)).unwrap(); + let mut chk = [0u8; 10]; + file.read_exact(&mut chk).unwrap(); + assert!(buf != chk[0]); + } +} + +