#![cfg_attr(not(feature = "std"), no_std)]
#![cfg_attr(docsrs, feature(doc_cfg))]
#![doc(html_root_url = "https://docs.rs/pwbox/0.5.0")]
#![warn(missing_docs, missing_debug_implementations)]
#![warn(clippy::all, clippy::pedantic)]
#![allow(
clippy::missing_errors_doc,
clippy::must_use_candidate,
clippy::module_name_repetitions,
clippy::doc_markdown
)]
use rand_core::{CryptoRng, RngCore};
use serde_json::Error as JsonError;
use core::{fmt, marker::PhantomData};
mod cipher_with_mac;
mod erased;
mod traits;
mod utils;
mod alloc {
#[cfg(not(feature = "std"))]
extern crate alloc;
#[cfg(not(feature = "std"))]
pub use alloc::{
borrow::ToOwned, boxed::Box, collections::BTreeMap, string::String, vec, vec::Vec,
};
#[cfg(feature = "std")]
pub use std::{
borrow::ToOwned, boxed::Box, collections::BTreeMap, string::String, vec, vec::Vec,
};
}
#[cfg(feature = "pure")]
#[cfg_attr(docsrs, doc(cfg(feature = "pure")))]
pub mod pure;
#[cfg(feature = "rust-crypto")]
#[cfg_attr(docsrs, doc(cfg(feature = "rust-crypto")))]
pub mod rcrypto;
#[cfg(feature = "exonum_sodiumoxide")]
#[cfg_attr(docsrs, doc(cfg(feature = "exonum_sodiumoxide")))]
pub mod sodium;
pub use crate::{
cipher_with_mac::{CipherWithMac, Mac, UnauthenticatedCipher},
erased::{EraseError, ErasedPwBox, Eraser, Suite},
traits::{Cipher, CipherOutput, DeriveKey, MacMismatch},
utils::{ScryptParams, SensitiveData},
};
use crate::{
alloc::{Box, String, Vec},
traits::{CipherObject, ObjectSafeCipher},
};
#[derive(Debug)]
pub enum Error {
NoCipher(String),
NoKdf(String),
KdfParams(JsonError),
NonceLen,
MacLen,
SaltLen,
MacMismatch,
DeriveKey(anyhow::Error),
}
impl From<MacMismatch> for Error {
fn from(_: MacMismatch) -> Self {
Self::MacMismatch
}
}
impl fmt::Display for Error {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Error::NoCipher(cipher) => write!(formatter, "unknown cipher: {}", cipher),
Error::NoKdf(kdf) => write!(formatter, "unknown KDF: {}", kdf),
Error::KdfParams(e) => write!(formatter, "failed to parse KDF parameters: {}", e),
Error::NonceLen => formatter.write_str("incorrect nonce length"),
Error::MacLen => formatter.write_str("incorrect MAC length"),
Error::SaltLen => formatter.write_str("incorrect salt length"),
Error::MacMismatch => formatter.write_str("incorrect password or corrupted box"),
Error::DeriveKey(e) => write!(formatter, "error during key derivation: {}", e),
}
}
}
#[cfg(feature = "std")]
impl std::error::Error for Error {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Error::KdfParams(e) => Some(e),
Error::DeriveKey(e) => Some(e.as_ref()),
_ => None,
}
}
}
#[derive(Debug)]
struct PwBoxInner<K, C> {
salt: Vec<u8>,
nonce: Vec<u8>,
encrypted: CipherOutput,
kdf: K,
cipher: C,
}
impl<K: DeriveKey, C: ObjectSafeCipher> PwBoxInner<K, C> {
fn seal<R: RngCore + ?Sized>(
kdf: K,
cipher: C,
rng: &mut R,
password: impl AsRef<[u8]>,
message: impl AsRef<[u8]>,
) -> anyhow::Result<Self> {
let mut salt = SensitiveData::zeros(kdf.salt_len());
rng.fill_bytes(salt.bytes_mut());
let mut nonce = SensitiveData::zeros(cipher.nonce_len());
rng.fill_bytes(nonce.bytes_mut());
let mut key = SensitiveData::zeros(cipher.key_len());
kdf.derive_key(key.bytes_mut(), password.as_ref(), &*salt)?;
let encrypted = cipher.seal(message.as_ref(), &*nonce, &*key);
Ok(PwBoxInner {
salt: salt[..].to_vec(),
nonce: nonce[..].to_vec(),
encrypted,
kdf,
cipher,
})
}
fn len(&self) -> usize {
self.encrypted.ciphertext.len()
}
fn open_into(
&self,
mut output: impl AsMut<[u8]>,
password: impl AsRef<[u8]>,
) -> Result<(), Error> {
assert_eq!(
output.as_mut().len(),
self.len(),
"please check `PwBox::len()` and provide output of fitting size"
);
let key_len = self.cipher.key_len();
let mut key = SensitiveData::zeros(key_len);
self.kdf
.derive_key(key.bytes_mut(), password.as_ref(), &self.salt)
.map_err(Error::DeriveKey)?;
self.cipher
.open(output.as_mut(), &self.encrypted, &self.nonce, &*key)
.map_err(From::from)
}
fn open(&self, password: impl AsRef<[u8]>) -> Result<SensitiveData, Error> {
let mut output = SensitiveData::zeros(self.len());
self.open_into(output.bytes_mut(), password)
.map(|()| output)
}
}
#[derive(Debug)]
pub struct PwBox<K, C> {
inner: PwBoxInner<K, CipherObject<C>>,
}
impl<K: DeriveKey + Default, C: Cipher> PwBox<K, C> {
pub fn new<R: RngCore + CryptoRng>(
rng: &mut R,
password: impl AsRef<[u8]>,
message: impl AsRef<[u8]>,
) -> anyhow::Result<Self> {
let (kdf, cipher) = (K::default(), CipherObject::default());
PwBoxInner::seal(kdf, cipher, rng, password, message).map(|inner| PwBox { inner })
}
}
#[allow(clippy::len_without_is_empty)]
impl<K: DeriveKey, C: Cipher> PwBox<K, C> {
pub fn len(&self) -> usize {
self.inner.len()
}
pub fn open_into(
&self,
output: impl AsMut<[u8]>,
password: impl AsRef<[u8]>,
) -> Result<(), Error> {
self.inner.open_into(output, password)
}
pub fn open(&self, password: impl AsRef<[u8]>) -> Result<SensitiveData, Error> {
self.inner.open(password)
}
}
pub struct RestoredPwBox {
inner: PwBoxInner<Box<dyn DeriveKey>, Box<dyn ObjectSafeCipher>>,
}
impl fmt::Debug for RestoredPwBox {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.debug_struct("RestoredPwBox").finish()
}
}
#[allow(clippy::len_without_is_empty)]
impl RestoredPwBox {
pub fn len(&self) -> usize {
self.inner.len()
}
pub fn open_into(
&self,
output: impl AsMut<[u8]>,
password: impl AsRef<[u8]>,
) -> Result<(), Error> {
self.inner.open_into(output, password)
}
pub fn open(&self, password: impl AsRef<[u8]>) -> Result<SensitiveData, Error> {
self.inner.open(password)
}
}
pub struct PwBoxBuilder<'a, K, C> {
kdf: Option<K>,
rng: &'a mut dyn RngCore,
_cipher: PhantomData<C>,
}
impl<'a, K, C> fmt::Debug for PwBoxBuilder<'a, K, C> {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter
.debug_struct("PwBoxBuilder")
.field("custom_kdf", &self.kdf.is_some())
.finish()
}
}
impl<'a, K, C> PwBoxBuilder<'a, K, C>
where
K: DeriveKey + Clone + Default,
C: Cipher,
{
pub fn new<R: RngCore + CryptoRng>(rng: &'a mut R) -> Self {
PwBoxBuilder {
kdf: None,
rng,
_cipher: PhantomData,
}
}
pub fn kdf(&mut self, kdf: K) -> &mut Self {
self.kdf = Some(kdf);
self
}
pub fn seal(
&mut self,
password: impl AsRef<[u8]>,
data: impl AsRef<[u8]>,
) -> anyhow::Result<PwBox<K, C>> {
let cipher = CipherObject::<C>::default();
let kdf = self.kdf.clone().unwrap_or_default();
PwBoxInner::seal(kdf, cipher, self.rng, password, data).map(|inner| PwBox { inner })
}
}
#[cfg(test)]
#[doc(hidden)]
pub fn test_kdf_and_cipher<K, C>(kdf: K)
where
K: DeriveKey + Clone + Default,
C: Cipher,
{
use alloc::vec;
use rand::thread_rng;
const PASSWORD: &str = "correct horse battery staple";
let mut rng = thread_rng();
let mut message = vec![0_u8; 64];
rng.fill_bytes(&mut message);
let pwbox = PwBoxBuilder::<_, C>::new(&mut rng)
.kdf(kdf)
.seal(PASSWORD, &message)
.unwrap();
assert_eq!(message.len(), pwbox.len());
assert_eq!(message, &*pwbox.open(PASSWORD).unwrap());
let mut buffer = [0_u8; 64];
pwbox.open_into(&mut buffer[..], PASSWORD).unwrap();
assert_eq!(buffer[..], *message);
}