use std::sync::{Arc, RwLock};
use oxicrypto::{new_rng, XChaCha20Poly1305};
use crate::error::EncryptError;
use crate::keyring::{generate_dek, Keyring};
use oxistore_core::{KvSnapshot, KvStore, KvTxn, RangeIter, StoreError};
const KEK_VERSION_LEN: usize = 4; const WRAP_NONCE_LEN: usize = 24; const WRAPPED_DEK_LEN: usize = 48; const DATA_NONCE_LEN: usize = 24; const DATA_TAG_LEN: usize = 16;
const WRAP_NONCE_OFFSET: usize = KEK_VERSION_LEN;
const WRAPPED_DEK_OFFSET: usize = WRAP_NONCE_OFFSET + WRAP_NONCE_LEN;
const DATA_NONCE_OFFSET: usize = WRAPPED_DEK_OFFSET + WRAPPED_DEK_LEN;
const DATA_CT_OFFSET: usize = DATA_NONCE_OFFSET + DATA_NONCE_LEN;
pub const MIN_ENVELOPE_LEN: usize = DATA_CT_OFFSET + DATA_TAG_LEN;
#[derive(Clone, Debug)]
pub struct EnvelopeCipher {
keyring: Arc<RwLock<Keyring>>,
}
impl EnvelopeCipher {
pub fn new(keyring: Keyring) -> Self {
Self {
keyring: Arc::new(RwLock::new(keyring)),
}
}
pub fn encrypt(&self, plaintext: &[u8], aad: &[u8]) -> Result<Vec<u8>, EncryptError> {
let (kek_version, kek) = {
let ring = self
.keyring
.read()
.map_err(|_| EncryptError::LockPoisoned)?;
let v = ring.active_version()?;
let k = *ring.active_kek()?;
(v, k)
};
let dek = generate_dek()?;
let wrap_nonce = random_nonce_24()?;
let data_nonce = random_nonce_24()?;
let cipher = XChaCha20Poly1305;
let version_aad = kek_version.to_le_bytes();
let mut wrapped_dek = [0u8; WRAPPED_DEK_LEN];
cipher
.seal(&kek, &wrap_nonce, &version_aad, &dek, &mut wrapped_dek)
.map_err(|e| EncryptError::EncryptionFailed(e.to_string()))?;
let data_ct_len = plaintext.len() + DATA_TAG_LEN;
let mut output = vec![0u8; DATA_CT_OFFSET + data_ct_len];
output[..KEK_VERSION_LEN].copy_from_slice(&kek_version.to_le_bytes());
output[WRAP_NONCE_OFFSET..WRAPPED_DEK_OFFSET].copy_from_slice(&wrap_nonce);
output[WRAPPED_DEK_OFFSET..DATA_NONCE_OFFSET].copy_from_slice(&wrapped_dek);
output[DATA_NONCE_OFFSET..DATA_CT_OFFSET].copy_from_slice(&data_nonce);
cipher
.seal(
&dek,
&data_nonce,
aad,
plaintext,
&mut output[DATA_CT_OFFSET..],
)
.map_err(|e| EncryptError::EncryptionFailed(e.to_string()))?;
Ok(output)
}
pub fn decrypt(&self, ciphertext: &[u8], aad: &[u8]) -> Result<Vec<u8>, EncryptError> {
if ciphertext.len() < MIN_ENVELOPE_LEN {
return Err(EncryptError::CiphertextTooShort {
min_expected: MIN_ENVELOPE_LEN,
got: ciphertext.len(),
});
}
let kek_version =
u32::from_le_bytes(ciphertext[..KEK_VERSION_LEN].try_into().map_err(|_| {
EncryptError::CiphertextTooShort {
min_expected: MIN_ENVELOPE_LEN,
got: ciphertext.len(),
}
})?);
let wrap_nonce: &[u8; WRAP_NONCE_LEN] = ciphertext[WRAP_NONCE_OFFSET..WRAPPED_DEK_OFFSET]
.try_into()
.map_err(|_| EncryptError::CiphertextTooShort {
min_expected: MIN_ENVELOPE_LEN,
got: ciphertext.len(),
})?;
let wrapped_dek_bytes = &ciphertext[WRAPPED_DEK_OFFSET..DATA_NONCE_OFFSET];
let data_nonce: &[u8; DATA_NONCE_LEN] = ciphertext[DATA_NONCE_OFFSET..DATA_CT_OFFSET]
.try_into()
.map_err(|_| EncryptError::CiphertextTooShort {
min_expected: MIN_ENVELOPE_LEN,
got: ciphertext.len(),
})?;
let data_ct = &ciphertext[DATA_CT_OFFSET..];
let kek = {
let ring = self
.keyring
.read()
.map_err(|_| EncryptError::LockPoisoned)?;
ring.kek_for_version(kek_version)
.copied()
.ok_or(EncryptError::MissingKekVersion(kek_version))?
};
let cipher = XChaCha20Poly1305;
let version_aad = kek_version.to_le_bytes();
let mut dek = [0u8; 32];
cipher
.open(&kek, wrap_nonce, &version_aad, wrapped_dek_bytes, &mut dek)
.map_err(|_| EncryptError::AuthenticationFailed)?;
let pt_len = data_ct.len().saturating_sub(DATA_TAG_LEN);
let mut plaintext = vec![0u8; pt_len];
cipher
.open(&dek, data_nonce, aad, data_ct, &mut plaintext)
.map_err(|_| EncryptError::AuthenticationFailed)?;
Ok(plaintext)
}
pub fn active_version(&self) -> Result<u32, EncryptError> {
let ring = self
.keyring
.read()
.map_err(|_| EncryptError::LockPoisoned)?;
ring.active_version()
}
pub fn add_kek_version(&self, new_kek: [u8; 32]) -> Result<u32, EncryptError> {
let mut ring = self
.keyring
.write()
.map_err(|_| EncryptError::LockPoisoned)?;
ring.rotate(new_kek)
}
pub(crate) fn rewrap_envelope(&self, envelope: &[u8]) -> Result<Vec<u8>, EncryptError> {
if envelope.len() < MIN_ENVELOPE_LEN {
return Err(EncryptError::CiphertextTooShort {
min_expected: MIN_ENVELOPE_LEN,
got: envelope.len(),
});
}
let old_version =
u32::from_le_bytes(envelope[..KEK_VERSION_LEN].try_into().map_err(|_| {
EncryptError::CiphertextTooShort {
min_expected: MIN_ENVELOPE_LEN,
got: envelope.len(),
}
})?);
let old_wrap_nonce: &[u8; WRAP_NONCE_LEN] = envelope[WRAP_NONCE_OFFSET..WRAPPED_DEK_OFFSET]
.try_into()
.map_err(|_| EncryptError::CiphertextTooShort {
min_expected: MIN_ENVELOPE_LEN,
got: envelope.len(),
})?;
let old_wrapped_dek = &envelope[WRAPPED_DEK_OFFSET..DATA_NONCE_OFFSET];
let (old_kek, new_kek_version, new_kek) = {
let ring = self
.keyring
.read()
.map_err(|_| EncryptError::LockPoisoned)?;
let old_kek = ring
.kek_for_version(old_version)
.copied()
.ok_or(EncryptError::MissingKekVersion(old_version))?;
let new_v = ring.active_version()?;
let new_kek = *ring.active_kek()?;
(old_kek, new_v, new_kek)
};
let cipher = XChaCha20Poly1305;
let old_version_aad = old_version.to_le_bytes();
let mut dek = [0u8; 32];
cipher
.open(
&old_kek,
old_wrap_nonce,
&old_version_aad,
old_wrapped_dek,
&mut dek,
)
.map_err(|_| EncryptError::AuthenticationFailed)?;
let new_wrap_nonce = random_nonce_24()?;
let new_version_aad = new_kek_version.to_le_bytes();
let mut new_wrapped_dek = [0u8; WRAPPED_DEK_LEN];
cipher
.seal(
&new_kek,
&new_wrap_nonce,
&new_version_aad,
&dek,
&mut new_wrapped_dek,
)
.map_err(|e| EncryptError::EncryptionFailed(e.to_string()))?;
let mut new_envelope = Vec::with_capacity(envelope.len());
new_envelope.extend_from_slice(&new_kek_version.to_le_bytes());
new_envelope.extend_from_slice(&new_wrap_nonce);
new_envelope.extend_from_slice(&new_wrapped_dek);
new_envelope.extend_from_slice(&envelope[DATA_NONCE_OFFSET..]);
Ok(new_envelope)
}
}
pub fn rotate_all_keys<S: KvStore>(
store: &mut S,
cipher: &mut EnvelopeCipher,
new_kek: [u8; 32],
) -> Result<u64, EncryptError> {
cipher.add_kek_version(new_kek)?;
let entries: Vec<(Vec<u8>, Vec<u8>)> = store
.iter()
.map_err(|e| EncryptError::Store(e.to_string()))?
.map(|item| item.map_err(|e| EncryptError::Store(e.to_string())))
.collect::<Result<Vec<_>, _>>()?;
let mut count: u64 = 0;
for (key, old_envelope) in &entries {
let new_envelope = cipher.rewrap_envelope(old_envelope)?;
store
.put(key, &new_envelope)
.map_err(|e| EncryptError::Store(e.to_string()))?;
count += 1;
}
Ok(count)
}
pub struct EncryptedKvEnvelope<S: KvStore> {
inner: Arc<S>,
cipher: EnvelopeCipher,
}
impl<S: KvStore> EncryptedKvEnvelope<S> {
pub fn new(inner: S, cipher: EnvelopeCipher) -> Self {
Self {
inner: Arc::new(inner),
cipher,
}
}
pub fn cipher(&self) -> &EnvelopeCipher {
&self.cipher
}
pub fn rotate_kek(&mut self, new_kek: [u8; 32]) -> Result<u64, StoreError> {
let entries: Vec<(Vec<u8>, Vec<u8>)> = self
.inner
.iter()
.map_err(|e| StoreError::Other(e.to_string()))?
.map(|item| item.map_err(|e| StoreError::Other(e.to_string())))
.collect::<Result<Vec<_>, _>>()?;
self.cipher
.add_kek_version(new_kek)
.map_err(|e| StoreError::Other(e.to_string()))?;
let mut count: u64 = 0;
for (key, old_envelope) in &entries {
let new_envelope = self
.cipher
.rewrap_envelope(old_envelope)
.map_err(|e| StoreError::Other(e.to_string()))?;
self.inner.put(key, &new_envelope)?;
count += 1;
}
Ok(count)
}
}
impl<S: KvStore> KvStore for EncryptedKvEnvelope<S> {
fn get(&self, key: &[u8]) -> Result<Option<Vec<u8>>, StoreError> {
match self.inner.get(key)? {
None => Ok(None),
Some(ct) => {
let pt = self.cipher.decrypt(&ct, key).map_err(StoreError::from)?;
Ok(Some(pt))
}
}
}
fn put(&self, key: &[u8], value: &[u8]) -> Result<(), StoreError> {
let ct = self.cipher.encrypt(value, key).map_err(StoreError::from)?;
self.inner.put(key, &ct)
}
fn delete(&self, key: &[u8]) -> Result<(), StoreError> {
self.inner.delete(key)
}
fn contains(&self, key: &[u8]) -> Result<bool, StoreError> {
self.inner.contains(key)
}
fn range<'a>(&'a self, lo: &[u8], hi: &[u8]) -> Result<RangeIter<'a>, StoreError> {
let cipher = self.cipher.clone();
let raw_iter = self.inner.range(lo, hi)?;
let iter = raw_iter.map(move |item| {
let (k, ct) = item?;
let pt = cipher.decrypt(&ct, &k).map_err(StoreError::from)?;
Ok((k, pt))
});
Ok(Box::new(iter))
}
fn transaction(&self) -> Result<Box<dyn KvTxn + '_>, StoreError> {
Err(StoreError::Other(
"EncryptedKvEnvelope: encrypted transactions not yet supported".to_string(),
))
}
fn snapshot(&self) -> Result<Box<dyn KvSnapshot + '_>, StoreError> {
Err(StoreError::Other(
"EncryptedKvEnvelope: encrypted snapshots not yet supported".to_string(),
))
}
fn iter<'a>(&'a self) -> Result<RangeIter<'a>, StoreError> {
let cipher = self.cipher.clone();
let raw_iter = self.inner.iter()?;
let iter = raw_iter.map(move |item| {
let (k, ct) = item?;
let pt = cipher.decrypt(&ct, &k).map_err(StoreError::from)?;
Ok((k, pt))
});
Ok(Box::new(iter))
}
fn flush(&self) -> Result<(), StoreError> {
self.inner.flush()
}
}
fn random_nonce_24() -> Result<[u8; 24], EncryptError> {
let mut rng = new_rng().map_err(|_| EncryptError::RngFailed)?;
let mut nonce = [0u8; 24];
rng.fill(&mut nonce).map_err(|_| EncryptError::RngFailed)?;
Ok(nonce)
}