Compare commits
No commits in common. 'master' and 'testing' have entirely different histories.
@ -1,33 +1,15 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "chacha20"
|
name = "chachaex"
|
||||||
description = "chacha20_poly1305 encryption tool"
|
version = "0.1.0"
|
||||||
version = "2.0.1"
|
|
||||||
# Change to push
|
|
||||||
authors = ["Avril <flanchan@cumallover.me>"]
|
authors = ["Avril <flanchan@cumallover.me>"]
|
||||||
edition = "2021"
|
edition = "2018"
|
||||||
license = "gpl-3.0-or-later"
|
|
||||||
|
|
||||||
[features]
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
default = ["mmap"]
|
|
||||||
|
|
||||||
# Try to map inputs/outputs before using buffers
|
|
||||||
mmap = ["libc"]
|
|
||||||
# Forcefully map all output real files.
|
|
||||||
# This is unsafe because we cannot distinguish the offset at which to map the file descriptor, or if there even is one.
|
|
||||||
unsafe-mappings = ["mmap"]
|
|
||||||
|
|
||||||
# Explicitly clear buffers and cache after use
|
|
||||||
explicit_clear = []
|
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
base64 = "0.13"
|
base64 = "0.13"
|
||||||
getrandom = "0.2"
|
getrandom = "0.2"
|
||||||
lazy_static = "1.4.0"
|
hex-literal = "0.3"
|
||||||
libc = { version = "0.2.133", optional = true }
|
khash = {version = "2.0.4", default-features=false}
|
||||||
mapped-file = { version = "0.0.2", features = ["file"] }
|
openssl = "0.10"
|
||||||
openssl = "0.10.45"
|
smallvec = {version = "1.6.1", features=["write", "union"]}
|
||||||
smallmap = "1.4.0"
|
|
||||||
smallvec = {version = "1.6", features=["union"]}
|
|
||||||
|
|
||||||
[build-dependencies]
|
|
||||||
rustc_version = "0.2"
|
|
||||||
|
@ -1,56 +0,0 @@
|
|||||||
# chacha20
|
|
||||||
A simple chacha20_poly1305 CLI encryption tool
|
|
||||||
|
|
||||||
## Building
|
|
||||||
Requires Rust and Cargo to build; also requires OpenSSL v1.1.0 or higher.
|
|
||||||
Run `cargo build --release`, the binary will be built to `./target/release/chacha20`.
|
|
||||||
|
|
||||||
### Testing
|
|
||||||
Run `cargo test && cargo build && ./test.sh debug` to test the program.
|
|
||||||
Alternatively, run `./test.sh` after building to test the release build's correctness.
|
|
||||||
|
|
||||||
## Features
|
|
||||||
To enable explicit buffer clearing, compile with the option `--features explicit_clear`.
|
|
||||||
|
|
||||||
The `explicit_clear` feature forces any temporary work buffers to be zeroed out in memory when the corresponding stream is flushed itself.
|
|
||||||
Unless being built with the Rust nightly toolchain, it requires the nonstandard glibc extension `explicit_bzero(void*, size_t)` to build.
|
|
||||||
On x86 targets with the Rust nightly toolchain, it will also force a cache flush of the corresponding memory address after the zeroing.
|
|
||||||
|
|
||||||
This feature is *usually not needed*, and can cause a slowdown; but it prevents any lingering data being left in the buffer.
|
|
||||||
The unit test `remainder()` checks the process' memory map for leftover data in the working buffer when testing with this feature enabled. It is still unlikely data will remain even without this feature, depending on your system; you should only use it if you are very paranoid.
|
|
||||||
|
|
||||||
# Usage
|
|
||||||
Copies stdin to stdout while encrypting or decrypting with the stream cipher `chacha20_poly1305`.
|
|
||||||
|
|
||||||
## Modes
|
|
||||||
* Encrypt - Encrypt stdin to stdout
|
|
||||||
* Decrypt - Decrypt stdin to stdout
|
|
||||||
* Keygen - Generate a random key and IV and print them to stdout
|
|
||||||
|
|
||||||
To see a more detailed explenation run `chacha20 help`.
|
|
||||||
|
|
||||||
## Formats
|
|
||||||
The key and IV is expected/generated in base64 format.
|
|
||||||
The key and IV sizes respectively are 32 and 12 bytes.
|
|
||||||
|
|
||||||
The ciphertext input and output is raw binary data. You can encode this to text formats if you want with whatever tool you choose (Example with `base64` below.)
|
|
||||||
|
|
||||||
## Example
|
|
||||||
|
|
||||||
Encrypting and decrypting a string to binary with randomly generated keys
|
|
||||||
```shell
|
|
||||||
$ echo "Hello world!" | chacha20 e 2>keys.cck > output.cc20
|
|
||||||
$ chacha20 d $(cat keys.cck) < output.cc20
|
|
||||||
Hello world!
|
|
||||||
```
|
|
||||||
|
|
||||||
The same but with text instead of binary ciphertexts
|
|
||||||
|
|
||||||
``` shell
|
|
||||||
$ echo "Hello world!" | chacha20 e 2>keys.cck | base64 > output.cc20.b64
|
|
||||||
$ cat output.cc20.b64 | base64 --decode | chacha20 d $(cat keys.cck)
|
|
||||||
Hello world!
|
|
||||||
```
|
|
||||||
|
|
||||||
# License
|
|
||||||
GPL'd with <3
|
|
@ -1,24 +0,0 @@
|
|||||||
|
|
||||||
extern crate rustc_version;
|
|
||||||
use rustc_version::{version, version_meta, Channel};
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
// Assert we haven't travelled back in time
|
|
||||||
assert!(version().unwrap().major >= 1);
|
|
||||||
|
|
||||||
// Set cfg flags depending on release channel
|
|
||||||
match version_meta().unwrap().channel {
|
|
||||||
Channel::Stable => {
|
|
||||||
println!("cargo:rustc-cfg=stable");
|
|
||||||
}
|
|
||||||
Channel::Beta => {
|
|
||||||
println!("cargo:rustc-cfg=beta");
|
|
||||||
}
|
|
||||||
Channel::Nightly => {
|
|
||||||
println!("cargo:rustc-cfg=nightly");
|
|
||||||
}
|
|
||||||
Channel::Dev => {
|
|
||||||
println!("cargo:rustc-cfg=dev");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,679 +0,0 @@
|
|||||||
//! Use memory mapping to process the entire stream at once
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
use std::os::unix::prelude::*;
|
|
||||||
use std::{
|
|
||||||
io,
|
|
||||||
fs,
|
|
||||||
ptr,
|
|
||||||
ops,
|
|
||||||
mem::{
|
|
||||||
self,
|
|
||||||
MaybeUninit,
|
|
||||||
},
|
|
||||||
borrow::{BorrowMut, Cow, Borrow},
|
|
||||||
convert::{TryFrom, TryInto,},
|
|
||||||
fmt, error,
|
|
||||||
};
|
|
||||||
use libc::{
|
|
||||||
mmap, munmap, madvise, MAP_FAILED,
|
|
||||||
};
|
|
||||||
use mapped_file::{
|
|
||||||
MappedFile,
|
|
||||||
Perm,
|
|
||||||
Flags,
|
|
||||||
file::memory::{
|
|
||||||
MemoryFile,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
use openssl::symm::Crypter;
|
|
||||||
/*
|
|
||||||
#[derive(Debug)]
|
|
||||||
struct MapInner
|
|
||||||
{
|
|
||||||
mem: ptr::NonNull<u8>,
|
|
||||||
size: usize
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ops::Drop for MapInner
|
|
||||||
{
|
|
||||||
#[inline]
|
|
||||||
fn drop(&mut self)
|
|
||||||
{
|
|
||||||
unsafe {
|
|
||||||
munmap(self.mem.as_ptr() as *mut _, self.size);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}*/
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn raw_file_size(fd: &(impl AsRawFd + ?Sized)) -> io::Result<u64>
|
|
||||||
{
|
|
||||||
use libc::fstat;
|
|
||||||
let mut stat = MaybeUninit::uninit();
|
|
||||||
match unsafe { fstat(fd.as_raw_fd(), stat.as_mut_ptr()) } {
|
|
||||||
0 => match unsafe {stat.assume_init()}.st_size {
|
|
||||||
x if x < 0 => Err(io::Error::new(io::ErrorKind::InvalidInput, format!("File from {} is too large", fd.as_raw_fd()))),
|
|
||||||
x => Ok(x as u64),
|
|
||||||
},
|
|
||||||
_ => Err(io::Error::last_os_error()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn try_truncate(fd: &(impl AsRawFd + ?Sized), to: u64) -> io::Result<()>
|
|
||||||
{
|
|
||||||
use libc::ftruncate;
|
|
||||||
let to = to.try_into().map_err(|e| io::Error::new(io::ErrorKind::InvalidInput, e))?;
|
|
||||||
match unsafe { ftruncate(fd.as_raw_fd(), to) } {
|
|
||||||
0 => Ok(()),
|
|
||||||
_ => Err(io::Error::last_os_error()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/*
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct Mapped<T: ?Sized>
|
|
||||||
{
|
|
||||||
mem: MapInner,
|
|
||||||
file: T
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Hash, Copy, PartialOrd, Ord)]
|
|
||||||
pub enum MapMode
|
|
||||||
{
|
|
||||||
ReadOnly,
|
|
||||||
ReadWrite,
|
|
||||||
WriteOnly,
|
|
||||||
}
|
|
||||||
//TODO: impl MapMode -> fn as_prot() -> c_int, fn as_flags() -> c_int
|
|
||||||
|
|
||||||
impl<T: AsRawFd> Mapped<T>
|
|
||||||
{
|
|
||||||
pub fn try_new_sized(file: T, size: u64, rw: MapMode) -> io::Result<Self>
|
|
||||||
{
|
|
||||||
todo!("mem: mmap(0, size, rw.prot(), MAP_SHARED (For rw: *Write*), MAP_PRIVATE [TODO: add support for| MAP_HUGE_] (For rw: ReadOnly), file.as_raw_fd(), 0) != MAP_FAILED (TODO: maybe madvise?)")
|
|
||||||
}
|
|
||||||
#[inline(always)]
|
|
||||||
pub fn try_new(file: T, rw: MapMode) -> io::Result<Self>
|
|
||||||
{
|
|
||||||
let size = raw_file_size(&file)?;
|
|
||||||
Self::try_new_sized(file, size, rw)
|
|
||||||
}
|
|
||||||
}*/
|
|
||||||
/*
|
|
||||||
impl<T: AsRawFd> TryFrom<T> for Mapped<T>
|
|
||||||
{
|
|
||||||
type Error = io::Error;
|
|
||||||
|
|
||||||
fn try_from(from: T) -> Result<Self, Self::Error>
|
|
||||||
{
|
|
||||||
Self::try_new(from, raw_file_size(&from)?)
|
|
||||||
}
|
|
||||||
}*/
|
|
||||||
|
|
||||||
|
|
||||||
fn process_mapped_files<T, U>(mode: &mut Crypter, input: &mut MappedFile<T>, output: &mut MappedFile<U>) -> io::Result<()>
|
|
||||||
where T: AsRawFd,
|
|
||||||
U: AsRawFd,
|
|
||||||
{
|
|
||||||
mode.update(&input[..], &mut output[..])?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn try_map_sized<T: AsRawFd>(file: T, perm: mapped_file::Perm, flags: impl mapped_file::MapFlags) -> Result<MappedFile<T>, T>
|
|
||||||
{
|
|
||||||
macro_rules! unwrap {
|
|
||||||
($res:expr) => {
|
|
||||||
match $res {
|
|
||||||
Ok(v) => v,
|
|
||||||
_ => return Err(file)
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
if let Ok(sz) = raw_file_size(&file) {
|
|
||||||
let sz = unwrap!(sz.try_into());
|
|
||||||
MappedFile::try_new(file, sz, perm, flags).map_err(|err| err.into_inner())
|
|
||||||
} else {
|
|
||||||
Err(file)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn try_map_to<T: AsRawFd + ?Sized>(file: &T, file_size: usize) -> bool
|
|
||||||
{
|
|
||||||
let file_size = match file_size.try_into() {
|
|
||||||
Ok(v) => v,
|
|
||||||
_ => return false
|
|
||||||
};
|
|
||||||
if let Ok(stdout_size) = raw_file_size(file) {
|
|
||||||
if stdout_size >= file_size {
|
|
||||||
// Size already ok
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Grow file
|
|
||||||
unsafe {
|
|
||||||
libc::ftruncate(file.as_raw_fd(), if file_size > i64::MAX as u64 { i64::MAX } else { file_size as i64 }) == 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn translate_hugetlb_size(size: usize) -> mapped_file::hugetlb::HugePage
|
|
||||||
{
|
|
||||||
#[inline]
|
|
||||||
fn check_func(sizes_kb: &[usize]) -> Option<&usize>
|
|
||||||
{
|
|
||||||
sizes_kb.iter().skip_while(|&&kb| kb < 1024 * 1024).next()
|
|
||||||
.or_else(|| sizes_kb.iter().nth(1)
|
|
||||||
.or_else(|| sizes_kb.first()))
|
|
||||||
}
|
|
||||||
const MB: usize = 1024*1024;
|
|
||||||
const GB: usize = 1024 * MB;
|
|
||||||
|
|
||||||
#[allow(overlapping_range_endpoints)]
|
|
||||||
match size {
|
|
||||||
0..=MB => mapped_file::hugetlb::HugePage::Smallest,
|
|
||||||
MB..=GB => mapped_file::hugetlb::HugePage::Selected(check_func),
|
|
||||||
_very_high => mapped_file::hugetlb::HugePage::Largest,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Create and map a temporary memory file of this size.
|
|
||||||
///
|
|
||||||
/// # Hugetlb
|
|
||||||
/// This function may optionally choose to huge hugepages if `size` is large enough and `HUGE` is set to true.
|
|
||||||
/// If you want to read/write from this file too, call `create_sized_temp_mapping_at::<false>()` instead, or `create_sized_basic_mapping()`.
|
|
||||||
#[inline]
|
|
||||||
fn create_sized_temp_mapping(size: usize) -> io::Result<(MappedFile<MemoryFile>, bool)>
|
|
||||||
{
|
|
||||||
create_sized_temp_mapping_at::<true>(size)
|
|
||||||
}
|
|
||||||
/// Create and map a temporary memory file of this size.
|
|
||||||
///
|
|
||||||
/// # Hugetlb
|
|
||||||
/// This function may optionally choose to huge hugepages if `size` is large enough and `HUGE` is set to true.
|
|
||||||
/// If you want to read/write from this file too, call with `HUGE = false`.
|
|
||||||
fn create_sized_temp_mapping_at<const HUGE: bool>(size: usize) -> io::Result<(MappedFile<MemoryFile>, bool)>
|
|
||||||
{
|
|
||||||
const HUGETLB_THRESH: usize = 1024 * 1024; // 1MB
|
|
||||||
let file = match size {
|
|
||||||
1..=HUGETLB_THRESH => MemoryFile::with_size(size),
|
|
||||||
size if HUGE => {
|
|
||||||
let hugetlb = translate_hugetlb_size(size);
|
|
||||||
let file = if let Some(flag) =hugetlb.compute_huge() {
|
|
||||||
MemoryFile::with_size_hugetlb(size, flag)?
|
|
||||||
} else {
|
|
||||||
MemoryFile::with_size(size)?
|
|
||||||
};
|
|
||||||
return MappedFile::new(file, size, Perm::ReadWrite, Flags::Shared.with_hugetlb(hugetlb)).map(|x| (x, true));
|
|
||||||
},
|
|
||||||
0 |
|
|
||||||
_ => MemoryFile::new(),
|
|
||||||
}?;
|
|
||||||
MappedFile::new(file, size, Perm::ReadWrite, Flags::Shared).map(|x| (x, false))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline(always)]
|
|
||||||
fn create_sized_basic_mapping(size: usize) -> io::Result<MappedFile<MemoryFile>>
|
|
||||||
{
|
|
||||||
create_sized_temp_mapping(size).map(|(x, _)| x)
|
|
||||||
}
|
|
||||||
|
|
||||||
type MappedMemoryFile = MappedFile<mapped_file::file::memory::MemoryFile>;
|
|
||||||
|
|
||||||
/// How a mapped operation should optimally take place
|
|
||||||
//TODO: Make optimised mapping operations for each one, like `fn process(self, enc: &mut Crypter) -> io::Result<usize>`
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub enum OpTable<T, U>
|
|
||||||
{
|
|
||||||
/// Both input and output are mapped
|
|
||||||
Both(MappedFile<T>, MappedFile<U>),
|
|
||||||
/// Input is mapped, but output is not. Work is done through a temporary map, then copied to `U`.
|
|
||||||
Input(MappedFile<T>, MappedMemoryFile, U),
|
|
||||||
/// Output is mapped, but input is not. Work is done from a temporary map
|
|
||||||
Output(T, MappedMemoryFile, MappedFile<U>),
|
|
||||||
/// Streaming mode (`read()`+`write()` only)
|
|
||||||
Neither(T, U),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T, U> OpTable<T, U>
|
|
||||||
{
|
|
||||||
/// Consume into either a mapping line, or the in/out tuple if neither are mapped.
|
|
||||||
#[inline]
|
|
||||||
pub fn only_mapped(self) -> Result<Self, (T, U)>
|
|
||||||
{
|
|
||||||
match self {
|
|
||||||
Self::Neither(t, u) => Err((t, u)),
|
|
||||||
this => Ok(this),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: io::Read+AsRawFd, U: io::Write+AsRawFd> OpTable<T, U>
|
|
||||||
{
|
|
||||||
fn pre_process(&mut self) -> io::Result<()>
|
|
||||||
{
|
|
||||||
match self {
|
|
||||||
Self::Both(input, output)
|
|
||||||
=> {
|
|
||||||
let _ = input.advise(mapped_file::Advice::Sequential, Some(true));
|
|
||||||
let _ = output.advise(mapped_file::Advice::Sequential, None);
|
|
||||||
},
|
|
||||||
Self::Input(input, mem, _)
|
|
||||||
=> {
|
|
||||||
let _ = input.advise(mapped_file::Advice::Sequential, Some(true));
|
|
||||||
let _ = mem.advise(mapped_file::Advice::Sequential, None);
|
|
||||||
},
|
|
||||||
Self::Output(input, mem, _)
|
|
||||||
=> {
|
|
||||||
let _ = mem.advise(mapped_file::Advice::Sequential, Some(true));
|
|
||||||
std::io::copy(input, &mut &mut mem[..])?; //TODO: When mapped_file is updated to add `inner_mut()`, use that instead of the mapped array as destination (gives access to splice et all.)
|
|
||||||
},
|
|
||||||
_ => (),
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
fn post_process(&mut self) -> io::Result<()>
|
|
||||||
{
|
|
||||||
match self {
|
|
||||||
Self::Both(_, output) => drop(output.flush(mapped_file::Flush::Wait)?),
|
|
||||||
Self::Input(_, ref mut mem, ref mut output) => drop(std::io::copy(&mut &mem[..], output)?), //TODO: When mapped_file is updated to add `inner_mut()`, use that instead of the mapped array as source (gives access to splice et all.)
|
|
||||||
Self::Output(_, _, ref mut output) => drop(output.flush(mapped_file::Flush::Wait)?),
|
|
||||||
Self::Neither(_, stream) => stream.flush()?,
|
|
||||||
//_ => (),
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
/// Execute this en/decryption in an optimised function
|
|
||||||
pub fn execute(mut self, mut mode: impl BorrowMut<Crypter>) -> io::Result<usize>
|
|
||||||
{
|
|
||||||
self.pre_process()?;
|
|
||||||
let mode: &mut Crypter = mode.borrow_mut();
|
|
||||||
match &mut self {
|
|
||||||
Self::Both(input, output) => {
|
|
||||||
let len = std::cmp::min(input.len(), output.len());
|
|
||||||
process_mapped_files(mode, input, output)?;
|
|
||||||
|
|
||||||
self.post_process()?;
|
|
||||||
Ok(len)
|
|
||||||
},
|
|
||||||
Self::Input(input, output, _) => {
|
|
||||||
let len = std::cmp::min(input.len(), output.len());
|
|
||||||
process_mapped_files(mode, input, output)?;
|
|
||||||
|
|
||||||
self.post_process()?;
|
|
||||||
Ok(len)
|
|
||||||
},
|
|
||||||
Self::Output(_, input, output) => {
|
|
||||||
let len = std::cmp::min(input.len(), output.len());
|
|
||||||
process_mapped_files(mode, input, output)?;
|
|
||||||
|
|
||||||
self.post_process()?;
|
|
||||||
Ok(len)
|
|
||||||
},
|
|
||||||
Self::Neither(sin, sout) => {
|
|
||||||
const BUFFER_SIZE: usize = 1024*1024;
|
|
||||||
enum CowMut<'a, T: ?Sized + ToOwned> {
|
|
||||||
Borrowed(&'a mut T),
|
|
||||||
Owned(<T as ToOwned>::Owned),
|
|
||||||
}
|
|
||||||
impl<'a, T: ?Sized + ToOwned> ops::Deref for CowMut<'a, T>
|
|
||||||
{
|
|
||||||
type Target = T;
|
|
||||||
#[inline]
|
|
||||||
fn deref(&self) -> &Self::Target {
|
|
||||||
match self {
|
|
||||||
Self::Borrowed(b) => b,
|
|
||||||
Self::Owned(b) => b.borrow(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a, T: ?Sized + ToOwned> ops::DerefMut for CowMut<'a, T>
|
|
||||||
where T::Owned: BorrowMut<T>,
|
|
||||||
{
|
|
||||||
#[inline]
|
|
||||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
|
||||||
match self {
|
|
||||||
Self::Borrowed(b) => b,
|
|
||||||
Self::Owned(b) => b.borrow_mut(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
macro_rules! try_allocmem {
|
|
||||||
($size:expr) => {
|
|
||||||
{
|
|
||||||
let size = usize::from($size);
|
|
||||||
let hsz = mapped_file::hugetlb::HugePage::Dynamic { kilobytes: size/1024 }; // 1MB buffer
|
|
||||||
hsz.compute_huge()
|
|
||||||
.and_then(|huge| mapped_file::file::memory::MemoryFile::with_size_hugetlb(size, huge).ok())
|
|
||||||
.or_else(|| mapped_file::file::memory::MemoryFile::with_size(size).ok())
|
|
||||||
.and_then(|file| MappedFile::new(file, size, Perm::ReadWrite, Flags::Private).ok())
|
|
||||||
}
|
|
||||||
};
|
|
||||||
($mem:expr, $size:expr) => {
|
|
||||||
$mem.as_mut().map(|x| CowMut::Borrowed(&mut x[..])).unwrap_or_else(|| CowMut::Owned(vec![0u8; $size]))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut _mem = try_allocmem!(BUFFER_SIZE);
|
|
||||||
let mut _memo = try_allocmem!(BUFFER_SIZE);
|
|
||||||
let mut buffer = try_allocmem!(_mem, BUFFER_SIZE);
|
|
||||||
let mut buffero = try_allocmem!(_memo, BUFFER_SIZE);
|
|
||||||
|
|
||||||
let mut read =0;
|
|
||||||
let mut cur;
|
|
||||||
while { cur = sin.read(&mut buffer[..])?; cur > 0 } {
|
|
||||||
/*let cur =*/ mode.update(&buffer[..cur], &mut buffero[..cur])?;
|
|
||||||
sout.write_all(&buffero[..cur])?;
|
|
||||||
read += cur;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.post_process()?;
|
|
||||||
Ok(read)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn sized_then_or<T: AsRawFd, U, F>(stream: T, trans: F) -> Result<U, T>
|
|
||||||
where F: FnOnce(T, usize) -> U
|
|
||||||
{
|
|
||||||
let Some(size) = raw_file_size(&stream).ok().and_then(|x| u64::try_into(x).ok()) else { return Err(stream); };
|
|
||||||
Ok(trans(stream, size))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn map_size_or<T: AsRawFd, U, F>(stream: T, size: usize, trans: F) -> Result<U, T>
|
|
||||||
where F: FnOnce(MappedFile<T>, usize) -> U
|
|
||||||
{
|
|
||||||
if try_map_to(&stream, size) {
|
|
||||||
// Sized
|
|
||||||
if cfg!(feature="unsafe-mappings") {
|
|
||||||
// Ensure the output file is open for writing
|
|
||||||
// XXX: This is not safe, we can't tell if it's a new file being created or if it's appending. I dunno how to find out, lseek doesn't say.
|
|
||||||
if let Err(err) = reopen_rw(&stream) {
|
|
||||||
// If this fails, we cannot map it.
|
|
||||||
if cfg!(debug_assertions) {
|
|
||||||
eprintln!("Warning: Failed to re-open stdout: {}", err);
|
|
||||||
}
|
|
||||||
return Err(stream);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Then map read+write
|
|
||||||
match MappedFile::try_new(stream, size, Perm::ReadWrite, Flags::Shared) {
|
|
||||||
Ok(map) => Ok(trans(map, size)),
|
|
||||||
Err(e) => Err(e.into_inner()),
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Unsized
|
|
||||||
Err(stream)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
#[non_exhaustive]
|
|
||||||
pub enum ProcessErrorKind
|
|
||||||
{
|
|
||||||
Unknown,
|
|
||||||
IO(io::Error),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Display for ProcessErrorKind
|
|
||||||
{
|
|
||||||
#[inline(always)]
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
|
|
||||||
{
|
|
||||||
match self {
|
|
||||||
Self::IO(io) => write!(f, "io error: {}", io),
|
|
||||||
_ => f.write_str("unknown"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct ProcessError {
|
|
||||||
kind: ProcessErrorKind,
|
|
||||||
context: Option<Box<Dynamic>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Debug for ProcessError
|
|
||||||
{
|
|
||||||
#[inline]
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
|
|
||||||
{
|
|
||||||
f.debug_struct("ProcessError")
|
|
||||||
.field("kind", &self.kind)
|
|
||||||
.finish_non_exhaustive()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl error::Error for ProcessError
|
|
||||||
{
|
|
||||||
#[inline]
|
|
||||||
fn source(&self) -> Option<&(dyn error::Error + 'static)> {
|
|
||||||
Some(match self.kind {
|
|
||||||
ProcessErrorKind::IO(ref io) => io,
|
|
||||||
_ => return None
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl fmt::Display for ProcessError
|
|
||||||
{
|
|
||||||
#[inline]
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
|
|
||||||
{
|
|
||||||
f.write_str("fatal processing error: ")?;
|
|
||||||
self.kind.fmt(f)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
impl From<io::Error> for ProcessError
|
|
||||||
{
|
|
||||||
#[inline]
|
|
||||||
fn from(from: io::Error) -> Self
|
|
||||||
{
|
|
||||||
Self {
|
|
||||||
kind: ProcessErrorKind::IO(from),
|
|
||||||
context: None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ProcessError
|
|
||||||
{
|
|
||||||
#[inline]
|
|
||||||
pub fn context_mut(&mut self) -> Option<&mut Dynamic>
|
|
||||||
{
|
|
||||||
self.context.as_deref_mut()
|
|
||||||
}
|
|
||||||
#[inline]
|
|
||||||
pub fn into_context(self) -> Option<Box<Dynamic>>
|
|
||||||
{
|
|
||||||
self.context
|
|
||||||
}
|
|
||||||
#[inline]
|
|
||||||
pub fn context(&self) -> Option<&Dynamic>
|
|
||||||
{
|
|
||||||
self.context.as_deref()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Reopen a file-descriptor as read+write.
|
|
||||||
pub fn reopen_rw(fd: &(impl AsRawFd+?Sized)) -> io::Result<()>
|
|
||||||
{
|
|
||||||
let fd = fd.as_raw_fd();
|
|
||||||
let file = fs::OpenOptions::new()
|
|
||||||
.read(true)
|
|
||||||
.write(true)
|
|
||||||
.open(format!("/proc/self/fd/{fd}"))?;
|
|
||||||
|
|
||||||
// Attempt to seek new fd to old's position, this is important
|
|
||||||
if unsafe {
|
|
||||||
let size = libc::lseek(fd, 0, libc::SEEK_CUR);
|
|
||||||
|
|
||||||
libc::lseek(file.as_raw_fd(), match size {
|
|
||||||
-1 => return Err(io::Error::last_os_error()),
|
|
||||||
v => v,
|
|
||||||
}, libc::SEEK_SET)
|
|
||||||
} < 0 {
|
|
||||||
return Err(io::Error::last_os_error());
|
|
||||||
}
|
|
||||||
|
|
||||||
// File descriptor set up to track accurately
|
|
||||||
unsafe {
|
|
||||||
let res = libc::dup2(file.as_raw_fd(), fd);
|
|
||||||
if res < 0 {
|
|
||||||
return Err(io::Error::last_os_error());
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Create an optimised call table for the cryptographic transformation from `from` to `to`.
|
|
||||||
pub fn try_create_process<T: AsRawFd + io::Read, U: AsRawFd + io::Write>(from: T, to: U) -> Result<OpTable<T, U>, ProcessError>
|
|
||||||
{
|
|
||||||
let (input, buffsz) = match sized_then_or(from, |input, input_size| {
|
|
||||||
(match MappedFile::try_new(input, input_size, Perm::Readonly, Flags::Private) {
|
|
||||||
Ok(m) => Ok(m),
|
|
||||||
Err(e) => Err(e.into_inner()),
|
|
||||||
}, input_size)
|
|
||||||
}) {
|
|
||||||
Ok((i, bs)) => (i, Some(bs)),
|
|
||||||
Err(e) => (Err(e), None),
|
|
||||||
};
|
|
||||||
|
|
||||||
let (output, outsz) = {
|
|
||||||
if let Some(buffsz) = buffsz.or_else(|| raw_file_size(&to).ok().and_then(|x| usize::try_from(x).ok())) {
|
|
||||||
match map_size_or(to, buffsz, |mmap, size| {
|
|
||||||
(mmap, size)
|
|
||||||
}) {
|
|
||||||
Ok((m, s)) => (Ok(m), Some(s)),
|
|
||||||
Err(e) => (Err(e), if buffsz == 0 { None } else { Some(buffsz) }),
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
(Err(to), None)
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(match ((input, buffsz), (output, outsz)) {
|
|
||||||
// Check for all combinations of mapping successes or failures
|
|
||||||
((Ok(min), isz), (Ok(mout), osz)) => OpTable::Both(min, mout),
|
|
||||||
((Ok(min), isz), (Err(sout), osz)) => OpTable::Input(min, create_sized_temp_mapping(isz.or(osz).unwrap_or(0))?.0, sout),
|
|
||||||
((Err(sin), isz), (Ok(mout), osz)) => OpTable::Output(sin, create_sized_basic_mapping(osz.or(isz).unwrap_or(0))?, mout),
|
|
||||||
((Err(sin), isz), (Err(sout), osz)) => OpTable::Neither(sin, sout),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
//TODO: Add metrics, status, progress, diagnostics, etc. reporting
|
|
||||||
pub fn try_process(mode: impl BorrowMut<Crypter>) -> Result<usize, ProcessError>
|
|
||||||
{
|
|
||||||
let sin = io::stdin().lock();
|
|
||||||
let sout = io::stdout().lock();
|
|
||||||
let proc = try_create_process(sin, sout)?;
|
|
||||||
if cfg!(debug_assertions) {
|
|
||||||
eprintln!("Process is: {:?}", proc);
|
|
||||||
}
|
|
||||||
Ok(proc.execute(mode)?)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature="try_process-old")]
|
|
||||||
const _:() = {
|
|
||||||
pub fn try_process(mut mode: impl BorrowMut<Crypter>) -> io::Result<io::Result<()>>
|
|
||||||
{
|
|
||||||
let mode = mode.borrow_mut();
|
|
||||||
let stdin = io::stdin().lock();
|
|
||||||
let stdout = io::stdout().lock();
|
|
||||||
|
|
||||||
let file_size = raw_file_size(&stdin)?;
|
|
||||||
|
|
||||||
macro_rules! attempt_mapping {
|
|
||||||
($file:expr, $perm:expr, $flags:expr) => {
|
|
||||||
{
|
|
||||||
let file = $file;
|
|
||||||
if try_map_to(&file, file_size) {
|
|
||||||
MappedFile::try_new(file, file_size, $perm, $flags).map_err(|e| e.into_inner())
|
|
||||||
} else {
|
|
||||||
return Err(io::Error::new(io::ErrorKind::InvalidData, concat!("Failed to truncate ", stringify!($file), " to size")));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
($file:expr, $perm:expr) => {
|
|
||||||
attempt_mapping($file, $perm, Flags::default())
|
|
||||||
};
|
|
||||||
}
|
|
||||||
// Try map stdout
|
|
||||||
Ok(match attempt_mapping!(stdout, Perm::ReadWrite, Flags::Shared) {
|
|
||||||
Ok(mut mstdout) => {
|
|
||||||
let res = match try_map_sized(stdin, mapped_file::Perm::Readonly, mapped_file::Flags::Private)
|
|
||||||
{
|
|
||||||
Ok(mut mstdin) => {
|
|
||||||
// Stdin and stdout are mapped. (3)
|
|
||||||
process_mapped_files(&mut mode, &mut mstdin, &mut mstdout)
|
|
||||||
},
|
|
||||||
Err(_) => {
|
|
||||||
// Only stdout is mapped. (1)
|
|
||||||
let size = file_size as usize;
|
|
||||||
let is_huge = size >= 1024*1024;
|
|
||||||
if is_huge {
|
|
||||||
let (mapped_memfd, _) = create_sized_temp_mapping(size)?;
|
|
||||||
io::copy(&mut stdin, &mut &mapped_memfd[..]).and_then(move |_| mapped_memfd)
|
|
||||||
} else {
|
|
||||||
MemoryFile::with_size(size).or_else(|_| MemoryFile::new()).and_then(|mut memfd| {
|
|
||||||
let size = io::copy(&mut stdin, &mut memfd)?;
|
|
||||||
MappedFile::new(memfd, size as usize, Perm::ReadWrite, Flags::Shared)
|
|
||||||
})
|
|
||||||
}.and_then(move |mut mapped_memfd| {
|
|
||||||
process_mapped_files(mode, &mut mapped_memfd, &mut mstdout)
|
|
||||||
})
|
|
||||||
//todo!("Copy stdin into a memory-file, and then map that and process it to stdout (XXX: What if the file is too large, but we cannot tell the size of stdin?)")
|
|
||||||
}
|
|
||||||
};
|
|
||||||
if res.is_ok() {
|
|
||||||
mstdout.flush(mapped_file::Flush::Wait)
|
|
||||||
} else {
|
|
||||||
res
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Err(mut stdout) => {
|
|
||||||
match try_map_sized(stdin, mapped_file::Perm::Readonly, mapped_file::Flags::Private)
|
|
||||||
{
|
|
||||||
Ok(mut mstdin) => {
|
|
||||||
// Only stdin is mapped. (2)
|
|
||||||
if cfg!(feature="sodiumoxide") {
|
|
||||||
todo!("XXX: When we switch to `sodiumoxide`, we won't *need* this, we can mutate the *private* stdin mapping directly.")
|
|
||||||
} else {
|
|
||||||
//TODO: XXX: When we switch to `sodiumoxide`, we won't *need* this, we can mutate the *private* stdin mapping directly.
|
|
||||||
|
|
||||||
|
|
||||||
//todo!("Create a memory file (possibly with hugetlb, depending on `file_size`), map that, process_mapped_files(...) into that memory file, then `io::copy(&mut &memfile[..], &stdout)`")
|
|
||||||
let (mapped_memfd, is_huge) = create_sized_temp_mapping(file_size as usize)?;
|
|
||||||
process_mapped_files(&mut mode, &mut mstdin, &mut mapped_memfd).and_then(move |_| {
|
|
||||||
if is_huge {
|
|
||||||
// Cannot use fd syscalls on `MFD_HUGELB`, copy into stdout from the mapped buffer itself.
|
|
||||||
io::copy(&mut &mapped_file[..], &mut stdout)
|
|
||||||
} else {
|
|
||||||
// Sync the contents into the memory file then use fd syscalls to copy into stdout.
|
|
||||||
mapped_memfd.flush(mapped_file::Flush::Wait).and_then(move |_| {
|
|
||||||
let mut memfd = mapped_memfd.into_inner();
|
|
||||||
io::copy(&mut memfd, &mut stdout)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}).map(|_| ())
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Err(_) => {
|
|
||||||
// Neither are mapped. (0)
|
|
||||||
return Err(io::Error::new(io::ErrorKind::InvalidInput, "cannot map stdin or stdout"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
})
|
|
||||||
/*let mstdin = Mapped::try_new(input)?;
|
|
||||||
let mstdout = Mapped::try_new(output)?;*/ //TODO: if failed to map output, but input is successful (TODO: XXX: implement other way around), we can fall back to wrapping output in `Sink`.
|
|
||||||
//todo!("Try to map the stdin and stdout streams, if that fails, return Err(last_os_err).");
|
|
||||||
//todo!("return Ok(process_mapped_files(mode, mstdin, mstdout, key, iv))")
|
|
||||||
}
|
|
||||||
};
|
|
@ -1,51 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
# Tests `chacha20`
|
|
||||||
#
|
|
||||||
# Usage: ./test.sh [debug|release]
|
|
||||||
# The binary must have already been built (use `cargo build [--release]` first)
|
|
||||||
|
|
||||||
mkdir -p test || exit -1
|
|
||||||
cd test || exit -1
|
|
||||||
|
|
||||||
PROG=../target/${1:-release}/chacha20
|
|
||||||
TEST_ENV="${TEST_ENV:-""}"
|
|
||||||
|
|
||||||
if [[ ! -f $PROG ]]; then
|
|
||||||
echo "Couldn't find executable \"$PROG\""
|
|
||||||
exit -1
|
|
||||||
else
|
|
||||||
echo "Testing executable \"$PROG\""
|
|
||||||
echo ""
|
|
||||||
fi
|
|
||||||
|
|
||||||
INPUT_SIZE=${INPUT_SIZE:-4096}
|
|
||||||
|
|
||||||
echo ">>> Generating random input ($INPUT_SIZE bytes)"
|
|
||||||
dd if=/dev/urandom "bs=$INPUT_SIZE" count=1 | base64 > test.txt
|
|
||||||
|
|
||||||
echo ">>> Generating key + IV"
|
|
||||||
KEYS=$($PROG k)
|
|
||||||
echo "$KEYS"
|
|
||||||
|
|
||||||
echo ">>> Encrypting"
|
|
||||||
time $TEST_ENV $PROG e $KEYS < test.txt > test.cc20 || exit 1
|
|
||||||
echo ">>> Decrypting"
|
|
||||||
time $TEST_ENV $PROG d $KEYS < test.cc20 > test.out.txt || exit 2
|
|
||||||
|
|
||||||
echo ">>> Comparing"
|
|
||||||
echo "Input (SHA256 sum): $(sha256sum test.txt)"
|
|
||||||
echo "Encrypted (SHA256 sum): $(sha256sum test.cc20)"
|
|
||||||
echo "Output (SHA256 sum): $(sha256sum test.out.txt)"
|
|
||||||
|
|
||||||
echo "---"
|
|
||||||
if cmp --silent -- test.txt test.out.txt; then
|
|
||||||
echo "Pass!"
|
|
||||||
else
|
|
||||||
echo "Failed"
|
|
||||||
exit 3
|
|
||||||
fi
|
|
||||||
|
|
||||||
rm test.*
|
|
||||||
|
|
||||||
cd ..
|
|
||||||
rmdir test || exit -1
|
|
Loading…
Reference in new issue