use hex_buffer_serde::{Hex as _Hex, HexForm};
use rand_core::{CryptoRng, RngCore};
use serde::{de::DeserializeOwned, Deserialize, Serialize};
use serde_json::{self, Error as JsonError, Value as JsonValue};
use core::{any::TypeId, fmt};
use crate::{
alloc::{BTreeMap, Box, String, ToOwned as _, Vec},
traits::{CipherObject, ObjectSafeCipher},
Cipher, CipherOutput, DeriveKey, Error, PwBox, PwBoxBuilder, PwBoxInner, RestoredPwBox,
};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ErasedPwBox {
#[serde(flatten)]
encrypted: CipherOutput,
kdf: String,
cipher: String,
#[serde(rename = "kdfparams")]
kdf_params: KdfParams,
#[serde(rename = "cipherparams")]
cipher_params: CipherParams,
}
#[allow(clippy::len_without_is_empty)]
impl ErasedPwBox {
pub fn len(&self) -> usize {
self.encrypted.ciphertext.len()
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
struct KdfParams {
#[serde(with = "HexForm")]
salt: Vec<u8>,
#[serde(flatten)]
inner: JsonValue,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
struct CipherParams {
#[serde(with = "HexForm")]
iv: Vec<u8>,
}
type CipherFactory = Box<dyn Fn() -> Box<dyn ObjectSafeCipher>>;
type KdfFactory = Box<dyn Fn(JsonValue) -> Result<Box<dyn DeriveKey>, JsonError>>;
#[derive(Debug)]
pub enum EraseError {
NoKdf,
NoCipher,
SerializeKdf(JsonError),
}
impl fmt::Display for EraseError {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
EraseError::NoKdf => {
formatter.write_str("KDF used in the box is not registered with the `Eraser`")
}
EraseError::NoCipher => {
formatter.write_str("cipher used in the box is not registered with the `Eraser`")
}
EraseError::SerializeKdf(e) => write!(formatter, "error serializing KDF params: {}", e),
}
}
}
#[cfg(feature = "std")]
impl std::error::Error for EraseError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
if let EraseError::SerializeKdf(e) = self {
Some(e)
} else {
None
}
}
}
pub struct Eraser {
ciphers: BTreeMap<String, CipherFactory>,
kdfs: BTreeMap<String, KdfFactory>,
cipher_names: BTreeMap<TypeId, String>,
kdf_names: BTreeMap<TypeId, String>,
}
impl fmt::Debug for Eraser {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter
.debug_struct("Eraser")
.field("ciphers", &self.ciphers.keys().collect::<Vec<_>>())
.field("kdfs", &self.kdfs.keys().collect::<Vec<_>>())
.finish()
}
}
impl Default for Eraser {
fn default() -> Self {
Eraser::new()
}
}
impl Eraser {
pub fn new() -> Self {
Eraser {
ciphers: BTreeMap::new(),
kdfs: BTreeMap::new(),
cipher_names: BTreeMap::new(),
kdf_names: BTreeMap::new(),
}
}
pub fn add_cipher<C>(&mut self, cipher_name: &str) -> &mut Self
where
C: Cipher,
{
let factory = || {
let cipher_object = CipherObject::<C>::default();
Box::new(cipher_object) as Box<dyn ObjectSafeCipher>
};
let old_cipher = self
.ciphers
.insert(cipher_name.to_owned(), Box::new(factory));
assert!(
old_cipher.is_none(),
"cipher name already registered: {}",
cipher_name
);
let old_name = self
.cipher_names
.insert(TypeId::of::<C>(), cipher_name.to_owned());
if let Some(old_name) = old_name {
panic!(
"cipher {} already registered under name {}",
cipher_name, old_name
);
}
self
}
pub fn add_kdf<K>(&mut self, kdf_name: &str) -> &mut Self
where
K: DeriveKey + DeserializeOwned + Default,
{
let factory = |options| {
let kdf: K = serde_json::from_value(options)?;
Ok(Box::new(kdf) as Box<dyn DeriveKey>)
};
let old_kdf = self.kdfs.insert(kdf_name.to_owned(), Box::new(factory));
assert!(
old_kdf.is_none(),
"cipher name already registered: {}",
kdf_name
);
let old_name = self
.kdf_names
.insert(TypeId::of::<K>(), kdf_name.to_owned());
if let Some(old_name) = old_name {
panic!(
"KDF {} already registered under name {}",
kdf_name, old_name
);
}
self
}
pub fn add_suite<S: Suite>(&mut self) -> &mut Self {
S::add_ciphers_and_kdfs(self);
assert!(
self.lookup_kdf::<S::DeriveKey>().is_some(),
"recommended KDF from suite not added"
);
assert!(
self.lookup_cipher::<S::Cipher>().is_some(),
"recommended cipher from suite not added"
);
self
}
fn lookup_cipher<C>(&self) -> Option<&String>
where
C: Cipher,
{
self.cipher_names.get(&TypeId::of::<C>())
}
fn lookup_kdf<K>(&self) -> Option<&String>
where
K: DeriveKey,
{
self.kdf_names.get(&TypeId::of::<K>())
}
pub fn erase<K, C>(&self, pwbox: &PwBox<K, C>) -> Result<ErasedPwBox, EraseError>
where
K: DeriveKey + Serialize,
C: Cipher,
{
let kdf = match self.lookup_kdf::<K>() {
Some(kdf) => kdf,
None => return Err(EraseError::NoKdf),
};
let cipher = match self.lookup_cipher::<C>() {
Some(cipher) => cipher,
None => return Err(EraseError::NoCipher),
};
let kdf_params = match serde_json::to_value(&pwbox.inner.kdf) {
Ok(params) => params,
Err(e) => return Err(EraseError::SerializeKdf(e)),
};
let pwbox = &pwbox.inner;
Ok(ErasedPwBox {
encrypted: pwbox.encrypted.clone(),
kdf: kdf.to_owned(),
kdf_params: KdfParams {
salt: pwbox.salt.clone(),
inner: kdf_params,
},
cipher: cipher.to_owned(),
cipher_params: CipherParams {
iv: pwbox.nonce.clone(),
},
})
}
pub fn restore(&self, erased: &ErasedPwBox) -> Result<RestoredPwBox, Error> {
let kdf_factory = self
.kdfs
.get(&erased.kdf)
.ok_or_else(|| Error::NoKdf(erased.kdf.clone()))?;
let cipher = self
.ciphers
.get(&erased.cipher)
.ok_or_else(|| Error::NoCipher(erased.cipher.clone()))?();
let kdf = kdf_factory(erased.kdf_params.inner.clone()).map_err(Error::KdfParams)?;
if erased.kdf_params.salt.len() != kdf.salt_len() {
return Err(Error::SaltLen);
}
if erased.cipher_params.iv.len() != cipher.nonce_len() {
return Err(Error::NonceLen);
}
if erased.encrypted.mac.len() != cipher.mac_len() {
return Err(Error::MacLen);
}
let inner = PwBoxInner {
salt: erased.kdf_params.salt.clone(),
nonce: erased.cipher_params.iv.clone(),
encrypted: erased.encrypted.clone(),
kdf,
cipher,
};
Ok(RestoredPwBox { inner })
}
}
pub trait Suite {
type Cipher: Cipher;
type DeriveKey: DeriveKey + Clone + Default;
fn build_box<R: RngCore + CryptoRng>(
rng: &mut R,
) -> PwBoxBuilder<'_, Self::DeriveKey, Self::Cipher> {
PwBoxBuilder::new(rng)
}
fn add_ciphers_and_kdfs(eraser: &mut Eraser);
}
#[cfg(test)]
pub fn test_kdf_and_cipher_corruption<K, C>(kdf: K)
where
K: DeriveKey + Clone + Default + Serialize + DeserializeOwned,
C: Cipher,
{
use crate::alloc::vec;
use assert_matches::assert_matches;
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();
let mut eraser = Eraser::new();
let eraser = eraser.add_cipher::<C>("cipher").add_kdf::<K>("kdf");
let mut erased_box = eraser.erase(&pwbox).unwrap();
erased_box.encrypted.mac.push(b'!');
assert_matches!(
eraser.restore(&erased_box).map(drop).unwrap_err(),
Error::MacLen
);
erased_box.encrypted.mac.pop();
if let Some(last_byte) = erased_box.encrypted.mac.pop() {
assert_matches!(
eraser.restore(&erased_box).map(drop).unwrap_err(),
Error::MacLen
);
erased_box.encrypted.mac.push(last_byte);
}
erased_box.kdf_params.salt.push(b'!');
assert_matches!(
eraser.restore(&erased_box).map(drop).unwrap_err(),
Error::SaltLen
);
erased_box.kdf_params.salt.pop();
if let Some(last_byte) = erased_box.kdf_params.salt.pop() {
assert_matches!(
eraser.restore(&erased_box).map(drop).unwrap_err(),
Error::SaltLen
);
erased_box.kdf_params.salt.push(last_byte);
}
erased_box.cipher_params.iv.push(b'!');
assert_matches!(
eraser.restore(&erased_box).map(drop).unwrap_err(),
Error::NonceLen
);
erased_box.cipher_params.iv.pop();
if let Some(last_byte) = erased_box.cipher_params.iv.pop() {
assert_matches!(
eraser.restore(&erased_box).map(drop).unwrap_err(),
Error::NonceLen
);
erased_box.cipher_params.iv.push(last_byte);
}
erased_box.encrypted.mac[0] ^= 1;
let restored = eraser.restore(&erased_box).unwrap();
assert_matches!(restored.open(PASSWORD).unwrap_err(), Error::MacMismatch);
erased_box.encrypted.mac[0] ^= 1;
erased_box.encrypted.ciphertext[1] ^= 128;
let restored = eraser.restore(&erased_box).unwrap();
assert_matches!(restored.open(PASSWORD).unwrap_err(), Error::MacMismatch);
erased_box.encrypted.ciphertext[1] ^= 128;
let mut password = PASSWORD.as_bytes().to_vec();
password[2] ^= 16;
assert_matches!(restored.open(&password).unwrap_err(), Error::MacMismatch);
}
#[cfg(feature = "exonum_sodiumoxide")]
#[test]
fn erase_pwbox() {
use crate::sodium::{Scrypt, XSalsa20Poly1305};
use rand::thread_rng;
const PASSWORD: &str = "correct horse battery staple";
const MESSAGE: &[u8] = b"1234567890";
let mut eraser = Eraser::new();
let eraser = eraser
.add_kdf::<Scrypt>("scrypt-nacl")
.add_cipher::<XSalsa20Poly1305>("xsalsa20-poly1305");
let pwbox =
PwBox::<Scrypt, XSalsa20Poly1305>::new(&mut thread_rng(), PASSWORD, MESSAGE).unwrap();
let erased_box = eraser.erase(&pwbox).unwrap();
let pwbox_copy = eraser.restore(&erased_box).unwrap();
assert_eq!(MESSAGE.len(), pwbox_copy.len());
assert_eq!(MESSAGE, &*pwbox_copy.open(PASSWORD).unwrap());
}