mod engine;
mod format;
mod types;
pub use self::format::{Extension, Header, DEFAULT_CONTAINER_LEN, MAX_EXTENSION_LEN};
pub use self::types::{
DerivedKey, Iterations, Limits, Password, PublicIv, SessionIv, SessionKey, DEFAULT_ITERATIONS, MAX_ITERATIONS,
MIN_ITERATIONS,
};
pub(crate) use self::engine::{
decrypt as decrypt_engine, encrypt as encrypt_engine, DecryptInfo, DecryptKey, DecryptOptions, EncryptKey,
EncryptParams,
};
use crate::detect::Version;
use crate::zeroize::Zeroizing;
use crate::{random, Error};
use std::fs::{self, File, OpenOptions};
use std::io::{BufReader, BufWriter, Read, Write};
use std::path::{Path, PathBuf};
pub fn encrypt(password: &str, plaintext: &[u8]) -> Result<Vec<u8>, Error> {
Encryptor::new(password)?.encrypt(plaintext)
}
pub fn decrypt(password: &str, data: &[u8]) -> Result<Vec<u8>, Error> {
Decryptor::new(password)?.decrypt(data)
}
pub fn read_header<R: Read>(reader: R) -> Result<Header, Error> {
read_header_with_limits(reader, Limits::DEFAULT)
}
pub fn read_header_with_limits<R: Read>(mut reader: R, limits: Limits) -> Result<Header, Error> {
format::read_header(&mut reader, true, &limits)
}
pub struct Encryptor {
password: Password,
iterations: Iterations,
extensions: Vec<Extension>,
default_extensions: bool,
constant_time: bool,
}
impl core::fmt::Debug for Encryptor {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("Encryptor")
.field("password", &self.password)
.field("iterations", &self.iterations)
.field("extensions", &self.extensions)
.field("default_extensions", &self.default_extensions)
.field("constant_time", &self.constant_time)
.finish()
}
}
impl Encryptor {
pub fn new(password: &str) -> Result<Self, Error> {
Ok(Encryptor {
password: Password::new(password)?,
iterations: Iterations::DEFAULT,
extensions: Vec::new(),
default_extensions: true,
constant_time: false,
})
}
pub fn iterations(mut self, iterations: Iterations) -> Self {
self.iterations = iterations;
self
}
pub fn extension(mut self, extension: Extension) -> Self {
self.extensions.push(extension);
self
}
pub fn without_default_extensions(mut self) -> Self {
self.default_extensions = false;
self
}
pub fn require_constant_time(mut self) -> Self {
self.constant_time = true;
self
}
fn all_extensions(&self) -> Result<Vec<Extension>, Error> {
let mut extensions = Vec::with_capacity(self.extensions.len().saturating_add(2));
if self.default_extensions {
extensions.push(Extension::new("CREATED_BY", concat!("aescry ", env!("CARGO_PKG_VERSION")))?);
}
extensions.extend(self.extensions.iter().cloned());
if self.default_extensions {
extensions.push(Extension::container(DEFAULT_CONTAINER_LEN)?);
}
Ok(extensions)
}
fn stream<R: Read, W: Write>(&self, reader: R, writer: W) -> Result<u64, Error> {
if self.constant_time {
crate::aes::Backend::constant_time()?;
}
let extensions = self.all_extensions()?;
let session_key = SessionKey::generate()?;
let params = EncryptParams {
version: Version::V3,
key: EncryptKey::Password(&self.password),
iterations: self.iterations,
extensions: &extensions,
public_iv: PublicIv::generate()?,
session_iv: SessionIv::generate()?,
session_key: &session_key,
};
encrypt_engine(¶ms, reader, writer)
}
pub fn encrypt(&self, plaintext: &[u8]) -> Result<Vec<u8>, Error> {
let mut out = Vec::with_capacity(plaintext.len().saturating_add(400));
self.stream(plaintext, &mut out)?;
Ok(out)
}
pub fn encrypt_stream<R: Read, W: Write>(&self, reader: R, writer: W) -> Result<u64, Error> {
self.stream(reader, writer)
}
pub fn encrypt_file<P: AsRef<Path>, Q: AsRef<Path>>(&self, input: P, output: Q) -> Result<u64, Error> {
let reader = BufReader::new(File::open(input)?);
write_atomically(output.as_ref(), |file| self.stream(reader, BufWriter::new(file)))
}
}
pub struct Decryptor {
password: Password,
limits: Limits,
constant_time: bool,
}
impl core::fmt::Debug for Decryptor {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("Decryptor")
.field("password", &self.password)
.field("limits", &self.limits)
.field("constant_time", &self.constant_time)
.finish()
}
}
impl Decryptor {
pub fn new(password: &str) -> Result<Self, Error> {
Ok(Decryptor { password: Password::new(password)?, limits: Limits::DEFAULT, constant_time: false })
}
pub fn limits(mut self, limits: Limits) -> Self {
self.limits = limits.at_most_default();
self
}
pub fn require_constant_time(mut self) -> Self {
self.constant_time = true;
self
}
fn stream<R: Read, W: Write>(&self, reader: R, writer: W) -> Result<DecryptInfo, Error> {
if self.constant_time {
crate::aes::Backend::constant_time()?;
}
let options = DecryptOptions::verified(self.limits);
decrypt_engine(DecryptKey::Password(&self.password), &options, reader, writer)
}
pub fn decrypt(&self, data: &[u8]) -> Result<Vec<u8>, Error> {
let mut out = Zeroizing::new(Vec::with_capacity(data.len()));
self.stream(data, &mut *out)?;
Ok(out.take())
}
pub fn decrypt_stream<R: Read, W: Write>(&self, reader: R, writer: W) -> Result<u64, Error> {
Ok(self.stream(reader, writer)?.plaintext_len)
}
pub fn decrypt_file<P: AsRef<Path>, Q: AsRef<Path>>(&self, input: P, output: Q) -> Result<u64, Error> {
let reader = BufReader::new(File::open(input)?);
write_atomically(output.as_ref(), |file| Ok(self.stream(reader, BufWriter::new(file))?.plaintext_len))
}
}
pub(crate) fn write_atomically<T>(path: &Path, write: impl FnOnce(&mut File) -> Result<T, Error>) -> Result<T, Error> {
let dir = match path.parent() {
Some(dir) if !dir.as_os_str().is_empty() => dir.to_path_buf(),
_ => PathBuf::from("."),
};
let name = path.file_name().ok_or(Error::InvalidPath)?;
let suffix: [u8; 8] = random::bytes()?;
let suffix: String = suffix.iter().map(|b| format!("{:02x}", b)).collect();
let mut temp_name = std::ffi::OsString::from(".");
temp_name.push(name);
temp_name.push(format!(".{}.aescry-tmp", suffix));
let temp = dir.join(temp_name);
let mut file = OpenOptions::new().write(true).create_new(true).open(&temp)?;
let result = write(&mut file).and_then(|value| {
file.sync_all()?;
Ok(value)
});
drop(file);
match result {
Ok(value) => match fs::rename(&temp, path) {
Ok(()) => Ok(value),
Err(e) => {
let _ = fs::remove_file(&temp);
Err(e.into())
}
},
Err(e) => {
let _ = fs::remove_file(&temp);
Err(e)
}
}
}