Compare commits

..

No commits in common. 'master' and 'testing' have entirely different histories.

4
.gitignore vendored

@ -1,6 +1,2 @@
/target
*~
flamegraph.svg
perf.*
memory.png

174
Cargo.lock generated

@ -20,6 +20,15 @@ version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
[[package]]
name = "block-buffer"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4"
dependencies = [
"generic-array",
]
[[package]]
name = "cc"
version = "1.0.67"
@ -33,20 +42,32 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "chacha20"
version = "2.0.1"
name = "chachaex"
version = "0.1.0"
dependencies = [
"base64",
"getrandom",
"lazy_static",
"libc",
"mapped-file",
"getrandom 0.2.2",
"hex-literal",
"khash",
"openssl",
"rustc_version",
"smallmap",
"smallvec",
]
[[package]]
name = "cpuid-bool"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8aebca1129a03dc6dc2b127edd729435bbc4a37e1d5f4d7513165089ceb02634"
[[package]]
name = "digest"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066"
dependencies = [
"generic-array",
]
[[package]]
name = "foreign-types"
version = "0.3.2"
@ -62,45 +83,61 @@ version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
[[package]]
name = "generic-array"
version = "0.14.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "501466ecc8a30d1d3b7fc9229b122b2ce8ed6e9d9223f1138d4babb253e51817"
dependencies = [
"typenum",
"version_check",
]
[[package]]
name = "getrandom"
version = "0.2.2"
version = "0.1.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c9495705279e7140bf035dde1f6e750c162df8b625267cd52cc44e0b156732c8"
checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce"
dependencies = [
"cfg-if",
"libc",
"wasi",
"wasi 0.9.0+wasi-snapshot-preview1",
]
[[package]]
name = "lazy_static"
version = "1.4.0"
name = "getrandom"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
checksum = "c9495705279e7140bf035dde1f6e750c162df8b625267cd52cc44e0b156732c8"
dependencies = [
"cfg-if",
"libc",
"wasi 0.10.2+wasi-snapshot-preview1",
]
[[package]]
name = "libc"
version = "0.2.133"
name = "hex-literal"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c0f80d65747a3e43d1596c7c5492d95d5edddaabd45a7fcdb02b95f644164966"
checksum = "5af1f635ef1bc545d78392b136bfe1c9809e029023c84a3638a864a10b8819c8"
[[package]]
name = "mapped-file"
version = "0.0.2"
name = "khash"
version = "2.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b0228d78d6433a3ed6b3ac9b91aa6e4dac1094ac841fea554779b97fa4c111db"
checksum = "c266778a8846ff4ebcdeb6bbd7ac651d2c02a2cabb53fbc7198398dcfcd98daa"
dependencies = [
"lazy_static",
"libc",
"memchr",
"getrandom 0.1.16",
"hex-literal",
"rustc_version",
"sha2",
]
[[package]]
name = "memchr"
version = "2.5.0"
name = "libc"
version = "0.2.90"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
checksum = "ba4aede83fc3617411dc6993bc8c70919750c1c257c6ca6a502aed6e0e2394ae"
[[package]]
name = "once_cell"
@ -108,37 +145,31 @@ version = "1.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "af8b08b04175473088b46763e51ee54da5f9a164bc162f615b91bc179dbf15a3"
[[package]]
name = "opaque-debug"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5"
[[package]]
name = "openssl"
version = "0.10.45"
version = "0.10.33"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b102428fd03bc5edf97f62620f7298614c45cedf287c271e7ed450bbaf83f2e1"
checksum = "a61075b62a23fef5a29815de7536d940aa35ce96d18ce0cc5076272db678a577"
dependencies = [
"bitflags",
"cfg-if",
"foreign-types",
"libc",
"once_cell",
"openssl-macros",
"openssl-sys",
]
[[package]]
name = "openssl-macros"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b501e44f11665960c7e7fcf062c7d96a14ade4aa98116c004b2e37b5be7d736c"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "openssl-sys"
version = "0.9.80"
version = "0.9.61"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "23bbbf7854cd45b83958ebe919f0e8e516793727652e27fda10a8384cfc790b7"
checksum = "313752393519e876837e09e1fa183ddef0be7735868dced3196f4472d536277f"
dependencies = [
"autocfg",
"cc",
@ -153,24 +184,6 @@ version = "0.3.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3831453b3449ceb48b6d9c7ad7c96d5ea673e9b470a1dc578c2ce6521230884c"
[[package]]
name = "proc-macro2"
version = "1.0.51"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5d727cae5b39d21da60fa540906919ad737832fe0b1c165da3a34d6548c849d6"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b"
dependencies = [
"proc-macro2",
]
[[package]]
name = "rustc_version"
version = "0.2.3"
@ -196,12 +209,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3"
[[package]]
name = "smallmap"
version = "1.4.0"
name = "sha2"
version = "0.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2df0ae8eb5af9e6e00be6ba531cc6860ba93d5226d6d1d4f80ead510a5d6296e"
checksum = "fa827a14b29ab7f44778d14a88d3cb76e949c45083f7dbfa507d0cb699dc12de"
dependencies = [
"rustc_version",
"block-buffer",
"cfg-if",
"cpuid-bool",
"digest",
"opaque-debug",
]
[[package]]
@ -211,21 +228,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fe0f37c9e8f3c5a4a66ad655a93c74daac4ad00c441533bf5c6e7990bb42604e"
[[package]]
name = "syn"
version = "1.0.107"
name = "typenum"
version = "1.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1f4064b5b16e03ae50984a5a8ed5d4f8803e6bc1fd170a3cda91a1be4b18e3f5"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "unicode-ident"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc"
checksum = "879f6906492a7cd215bfa4cf595b600146ccfac0c79bcbd1f3000162af5e8b06"
[[package]]
name = "vcpkg"
@ -233,6 +239,18 @@ version = "0.2.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b00bca6106a5e23f3eee943593759b7fcddb00554332e856d990c893966879fb"
[[package]]
name = "version_check"
version = "0.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe"
[[package]]
name = "wasi"
version = "0.9.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519"
[[package]]
name = "wasi"
version = "0.10.2+wasi-snapshot-preview1"

@ -1,33 +1,15 @@
[package]
name = "chacha20"
description = "chacha20_poly1305 encryption tool"
version = "2.0.1"
# Change to push
name = "chachaex"
version = "0.1.0"
authors = ["Avril <flanchan@cumallover.me>"]
edition = "2021"
license = "gpl-3.0-or-later"
edition = "2018"
[features]
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 = []
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
base64 = "0.13"
getrandom = "0.2"
lazy_static = "1.4.0"
libc = { version = "0.2.133", optional = true }
mapped-file = { version = "0.0.2", features = ["file"] }
openssl = "0.10.45"
smallmap = "1.4.0"
smallvec = {version = "1.6", features=["union"]}
[build-dependencies]
rustc_version = "0.2"
hex-literal = "0.3"
khash = {version = "2.0.4", default-features=false}
openssl = "0.10"
smallvec = {version = "1.6.1", features=["write", "union"]}

@ -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");
}
}
}

@ -11,11 +11,6 @@ use std::{
#[derive(Debug, Clone)]
pub struct HexStringIter<I>(I, [u8; 2]);
/// Represents any value that can be sent and shared across threads
pub type Dynamic = dyn std::any::Any + Send + Sync + 'static;
/// Represents any thread-local value
pub type LocalDynamic = dyn std::any::Any + 'static;
impl<I: Iterator<Item = u8>> HexStringIter<I>
{
/// Write this hex string iterator to a formattable buffer
@ -62,7 +57,7 @@ pub trait HexStringSliceIterExt
}
impl<S> HexStringSliceIterExt for S
where S: AsRef<[u8]>
where S: AsRef<[u8]>
{
fn hex(&self) -> HexStringSliceIter<'_>
{
@ -125,25 +120,3 @@ impl<I: Iterator<Item = u8> + Clone> fmt::Display for HexStringIter<I>
($first, $( $rest ),+).0
}
}
#[cfg(feature="explicit_clear")]
#[inline(never)]
pub fn explicit_prune(buffer : &mut[u8]) {
use std::ffi::c_void;
unsafe {
std::ptr::write_bytes(buffer.as_mut_ptr() as * mut c_void, 0, buffer.len());
#[cfg(nightly)]
if cfg!(target_arch = "x86_64") || cfg!(target_arch = "x86") {
asm!("clflush [{}]", in(reg)buffer.as_mut_ptr());
} else {
asm!("")
}
#[cfg(not(nightly))] {
extern "C" {
fn explicit_bzero(_: *mut c_void, _:usize);
}
explicit_bzero(buffer.as_mut_ptr() as *mut c_void, buffer.len());
}
}
}

@ -1,25 +1,22 @@
use getrandom::getrandom;
use std::{fmt, str};
use std::fmt;
use crate::cha::{
KEY_SIZE,
IV_SIZE,
};
use crate::ext::*;
#[derive(Debug, Clone, PartialEq, Eq, Hash, Copy, Default)]
#[derive(Debug, Clone, PartialEq, Eq, Hash, Copy)]
#[repr(transparent)]
pub struct Key([u8; KEY_SIZE]);
#[derive(Debug, Clone, PartialEq, Eq, Hash, Copy, Default)]
#[derive(Debug, Clone, PartialEq, Eq, Hash, Copy)]
#[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];
@ -30,11 +27,6 @@ impl Key
impl IV
{
#[inline] pub fn from_bytes(k: [u8; IV_SIZE]) -> Self
{
Self(k)
}
pub fn new() -> Self
{
let mut output = [0u8; IV_SIZE];
@ -43,23 +35,6 @@ impl IV
}
}
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]
@ -110,7 +85,7 @@ impl fmt::Display for Key
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
{
write!(f, "{}", self.0.iter().copied().into_hex())
write!(f, "Key({})", self.0.iter().copied().into_hex())
}
}
@ -118,36 +93,6 @@ 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<Self, Self::Err> {
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<Self, Self::Err> {
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)
write!(f, "Key({})", self.0.iter().copied().into_hex())
}
}

@ -1,13 +1,7 @@
//#![cfg_attr(nightly, feature(asm))]
#[macro_use] extern crate hex_literal;
#![allow(dead_code)]
#[macro_use] extern crate lazy_static;
//extern crate test;
#[macro_use] mod ext; #[allow(unused_imports)] use ext::*;
mod ext; #[macro_use] use ext::*;
mod key;
mod cha;
@ -15,132 +9,60 @@ mod stream;
use key::{Key, IV};
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum Mode
fn encrypt((key, iv): &(Key, IV), input: impl AsRef<[u8]>) -> Result<String, openssl::error::ErrorStack>
{
Encrypt, Decrypt, Keygen
let input = input.as_ref();
let mut output = vec![0u8; input.len()];
eprintln!("(enc) Key: {}, IV: {}, Input: ({}, {})", key, iv, input.len(), input.hex());
let mut enc = cha::encrypter(key, iv)?;
let n = enc.update(&input[..], &mut output[..])?;
eprintln!("(enc) Written {} bytes", n);
println!(">> {}", (&output[..n]).hex());
assert!(enc.finalize(&mut output[..n])? == 0);
println!(">> {}", (&output[..n]).hex());
Ok(base64::encode(&output[..n]))
}
fn keys() -> Result<(Mode, Key, IV), base64::DecodeError>
fn decrypt((key, iv): &(Key, IV), input: impl AsRef<str>) -> Result<Vec<u8>, openssl::error::ErrorStack>
{
let mut args = std::env::args();
let prog_name = args.next().unwrap();
let mode = match args.next()
.map(|x| x.chars().next().map(|x| x.to_ascii_lowercase()))
.flatten()
{
Some('e') => Mode::Encrypt,
Some('d') => Mode::Decrypt,
Some('k') => Mode::Keygen,
other => {
eprintln!("{} (v{}) - chacha20_poly1305 command line encryption tool",
env!("CARGO_PKG_NAME"),
env!("CARGO_PKG_VERSION"));
eprintln!(" by {} with <3 (licensed GPL v3.0 or later)", env!("CARGO_PKG_AUTHORS"));
eprintln!("\nStreams stdin to stdout through a chacha20_poly1305 cipher.");
eprintln!();
eprintln!("Usage: {} encrypt [<base64 key>] [<base64 iv>]", prog_name);
eprintln!("Usage: {} decrypt [<base64 key>] [<base64 iv>]", prog_name);
eprintln!("Usage: {} keygen [<base64 key>] [<base64 iv>]", prog_name);
eprintln!("Usage: {} help", prog_name);
eprintln!();
eprintln!("(Key size is {}, IV size is {})", cha::KEY_SIZE, cha::IV_SIZE);
eprintln!("(requires OpenSSL 1.1.0 or newer)");
eprintln!("\nencrypt/decrypt:\n\tIf a key and/or IV are not provided, they are generated randomly and printed to stderr in order on one line each.");
eprintln!("\tIf the key and/or IV provided's size is lower than the cipher's key/IV size, the rest of the key/IV is padded with 0s. If the size is higher, the extra bytes are ignored.");
eprintln!("\nkeygen:\n\tThe key/iv is printed in the same way as auto-generated keys for the en/decryption modes, but to stdout instead of stderr. If a key is given as parameter, the key is not printed. If the iv is given as a parameter also, nothing is printed.");
eprintln!("\nhelp:\n\tPrint this message to stderr then exit with code 0");
std::process::exit(if other == Some('h') {0} else {1})
}
};
let key = match args.next() {
Some(key) => key.parse()?,
None => {
let key = Key::new();
if mode == Mode::Keygen {
println!("{}", base64::encode(&key));
} else {
eprintln!("{}", base64::encode(&key));
}
key
},
};
let iv = match args.next() {
Some(iv) => iv.parse()?,
None => {
let iv = IV::new();
if mode == Mode::Keygen {
println!("{}", base64::encode(&iv));
} else {
eprintln!("{}", base64::encode(&iv));
}
iv
},
};
Ok((mode, key, iv))
}
let input = base64::decode(input.as_ref()).expect("invalid base64");
let mut output = vec![0u8; input.len()];
const USE_MMAP: bool = if cfg!(feature="mmap") {
true
} else {
false
};
eprintln!("(dec) Key: {}, IV: {}, Input: ({}, {})", key, iv, input.len(), input.hex());
#[cfg(feature="mmap")]
mod mapped;
let mut dec = cha::decrypter(key, iv)?;
#[allow(unreachable_code)]
fn try_mmap(decrypt: bool, key: Key, iv: IV) -> Result<i32, mapped::ProcessError>
{
#[cfg(feature="mmap")] return mapped::try_process(if decrypt {
cha::decrypter(key, iv).expect("Failed to create decrypter")
} else {
cha::encrypter(key, iv).expect("Failed to create encrypter")
}).map(|_| 0i32);
let n = dec.update(&input[..], &mut output[..])?;
eprintln!("(dec) Written {} bytes", n);
println!(">> {}", (&output[..n]).hex());
assert!(dec.finalize(&mut output[..n])? == 0);
// assert!(dec.finalize(&mut output[..n])? == 0);
println!(">> {}", (&output[..n]).hex());
unreachable!("Built without feature `mmap`, but still tried to call into it. This is a bug")
output.truncate(n);
Ok(output)
}
fn main() {
let (mode, key, iv) = keys().expect("Failed to read keys from argv (base64)");
// Attempt a mapped solution
if USE_MMAP && mode != Mode::Keygen {
match try_mmap(mode == Mode::Decrypt, key, iv) {
Ok(0) => return,
Ok(n) => std::process::exit(n),
Err(err) => if cfg!(debug_assertions) {
eprintln!("Failed to mmap input or output for processing, falling back to stream: {}", &err);
eprintln!("\t{:?}", err);
}
}
}
let stdout = std::io::stdout();
let input = std::io::stdin();
// Streaming
use std::io::Write;
match mode
{
Mode::Encrypt => {
let mut output = stream::Sink::encrypt(stdout.lock(), key, iv).expect("Failed to create encrypter");
std::io::copy(&mut input.lock(), &mut output).expect("Failed to encrypt");
output.flush().expect("Failed to flush stdout");
},
Mode::Decrypt => {
let mut output = stream::Sink::decrypt(stdout.lock(), key, iv).expect("Failed to create decrypter");
std::io::copy(&mut input.lock(), &mut output).expect("Failed to decrypt");
output.flush().expect("Failed to flush stdout");
},
Mode::Keygen => {
//println!("{}", base64::encode(&key));
//println!("{}", base64::encode(&iv));
},
}
let input = std::env::args().nth(1).unwrap_or({
let mut input = [0u8; 16];
getrandom::getrandom(&mut input[..]).expect("rng fatal");
khash::generate(&Default::default(), input).expect("kana-hash fatal")
//input.hex().into()
});
let key = cha::keygen();
let enc = encrypt(&key, &input).expect("encrypt");
println!("{}", enc);
let dec = decrypt(&key, enc).expect("decrypt");
let output = std::str::from_utf8(&dec[..]).unwrap();
println!("{:?}", output);
assert_eq!(output, &input[..]);
}

@ -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))")
}
};

@ -15,10 +15,6 @@ pub const BUFFER_SIZE: usize = 32;
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.
//#[derive(Debug)]
pub struct Sink<W>
{
@ -40,7 +36,7 @@ impl<W> Sink<W>
where W: Write
{
/// Create a new Chacha Sink stream wrapper
#[inline] fn new(stream: W, crypter: Crypter) -> Self
pub fn new(stream: W, crypter: Crypter) -> Self
{
Self{stream, crypter, buffer: SmallVec::new()}
}
@ -59,90 +55,69 @@ where W: Write
/// Consume into the inner stream
#[inline] pub fn into_inner(self) -> W
pub fn into_inner(self) -> W
{
self.stream
}
/// Consume into the inner stream and crypter
#[inline] pub fn into_parts(self) -> (W, Crypter)
pub fn into_parts(self) -> (W, Crypter)
{
(self.stream, self.crypter)
}
/// The crypter of this instance
#[inline] pub fn crypter(&self) -> &Crypter
pub fn crypter(&self) -> &Crypter
{
&self.crypter
}
/// The crypter of this instance
#[inline] pub fn crypter_mut(&mut self) -> &mut Crypter
pub fn crypter_mut(&mut self) -> &mut Crypter
{
&mut self.crypter
}
/// The inner stream
#[inline] pub fn inner(&self) -> &W
pub fn inner(&self) -> &W
{
&self.stream
}
/// The inner stream
#[inline] pub fn inner_mut(&mut self) -> &mut W
pub fn inner_mut(&mut self) -> &mut W
{
&mut self.stream
}
/// 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")]
{
explicit_prune(&mut self.buffer[..]);
return;
}
#[cfg(not(feature="explicit_clear"))]
unsafe {
std::ptr::write_bytes(self.buffer.as_mut_ptr(), 0, self.buffer.len());
}
}
/// Perform the cipher transform on this input to the inner buffer, returning the number of bytes updated.
fn transform(&mut self, buf: &[u8]) -> Result<usize, ErrorStack>
{
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)
}
}
impl<W: Write> Write for Sink<W>
{
#[inline] fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
let n = self.transform(buf)?;
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
prog1!{
{
self.buffer.write_all(buf).unwrap();
let n = self.crypter.update(&buf[..], &mut self.buffer[..])?;
self.crypter.finalize(&mut self.buffer[..n])?; // I don't think this is needed
self.stream.write(&self.buffer[..n])
},
self.buffer.clear();
}
#[inline] fn write_all(&mut self, buf: &[u8]) -> io::Result<()> {
let n = self.transform(buf)?;
}
fn write_all(&mut self, buf: &[u8]) -> io::Result<()> {
prog1!{
{
self.buffer.write_all(buf).unwrap();
let n = self.crypter.update(&buf[..], &mut self.buffer[..])?;
self.crypter.finalize(&mut self.buffer[..n])?;
self.stream.write_all(&self.buffer[..n])
},
self.buffer.clear();
}
}
#[inline] fn flush(&mut self) -> io::Result<()> {
#[cfg(feature="explicit_clear")] self.prune();
self.buffer.clear();
self.stream.flush()
}
}
@ -198,41 +173,5 @@ mod tests
}
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]);
}
}

@ -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…
Cancel
Save