use std::io::Write;
use byteorder::{LittleEndian, WriteBytesExt};
use crate::{
crypt,
db::{Database, HeaderAttachment},
error::DatabaseSaveError,
format::{
DatabaseVersion,
kdbx4::{
HEADER_COMPRESSION_ID, HEADER_ENCRYPTION_IV, HEADER_END, HEADER_KDF_PARAMS, HEADER_MASTER_SEED, HEADER_MASTER_SEED_SIZE,
HEADER_OUTER_ENCRYPTION_ID, INNER_HEADER_BINARY_ATTACHMENTS, INNER_HEADER_END, INNER_HEADER_RANDOM_STREAM_ID,
INNER_HEADER_RANDOM_STREAM_KEY, KDBX4InnerHeader, KDBX4OuterHeader,
},
},
hmac_block_stream,
io::WriteLengthTaggedExt,
key::DatabaseKey,
variant_dictionary::VariantDictionary,
};
use super::HEADER_PUBLIC_CUSTOM_DATA;
#[allow(dead_code)]
pub fn dump_kdbx4(db: &Database, db_key: &DatabaseKey, writer: &mut dyn Write) -> Result<(), DatabaseSaveError> {
if !matches!(db.config.version, DatabaseVersion::KDB4(_)) {
return Err(DatabaseSaveError::UnsupportedVersion);
}
let mut master_seed = vec![0; HEADER_MASTER_SEED_SIZE];
getrandom::fill(&mut master_seed)?;
let mut outer_iv = vec![0; db.config.outer_cipher_config.get_iv_size()];
getrandom::fill(&mut outer_iv)?;
let mut inner_random_stream_key = vec![0; db.config.inner_cipher_config.get_key_size()];
getrandom::fill(&mut inner_random_stream_key)?;
let (kdf, kdf_seed) = db.config.kdf_config.get_kdf_and_seed()?;
#[cfg(feature = "challenge_response")]
let db_key = db_key.clone().perform_challenge(&kdf_seed)?;
let mut header_data = Vec::new();
KDBX4OuterHeader {
version: db.config.version.clone(),
outer_cipher_config: db.config.outer_cipher_config.clone(),
compression_config: db.config.compression_config.clone(),
master_seed: master_seed.clone(),
outer_iv: outer_iv.clone(),
kdf_config: db.config.kdf_config.clone(),
kdf_seed,
public_custom_data: db.config.public_custom_data.clone(),
}
.dump(&mut header_data)?;
let header_sha256 = crypt::calculate_sha256(&[&header_data]);
_ = writer.write(&header_data)?;
_ = writer.write(header_sha256.as_slice())?;
let key_elements = db_key.get_key_elements()?;
let key_elements: Vec<&[u8]> = key_elements.iter().map(|v| &v[..]).collect();
let composite_key = crypt::calculate_sha256(&key_elements);
let transformed_key = kdf.transform_key(&composite_key)?;
let master_key = crypt::calculate_sha256(&[&master_seed, transformed_key.as_slice()]);
let hmac_key = crypt::calculate_sha512(&[&master_seed, transformed_key.as_slice(), &hmac_block_stream::HMAC_KEY_END]);
let header_hmac_key = hmac_block_stream::get_hmac_block_key(u64::MAX, &hmac_key);
let header_hmac = crypt::calculate_hmac(&[&header_data], header_hmac_key.as_slice())?;
_ = writer.write(header_hmac.as_slice())?;
let mut inner_cipher = db.config.inner_cipher_config.get_cipher(&inner_random_stream_key);
let mut payload = Vec::new();
KDBX4InnerHeader {
inner_random_stream: db.config.inner_cipher_config.clone(),
inner_random_stream_key,
}
.dump(&db.header_attachments, &mut payload)?;
crate::xml_db::dump::dump(db, &mut *inner_cipher, &mut payload)?;
let payload_compressed = db.config.compression_config.get_compression().compress(&payload)?;
let payload_encrypted = db
.config
.outer_cipher_config
.get_cipher(master_key.as_slice(), &outer_iv)?
.encrypt(&payload_compressed)?;
let payload_hmac = hmac_block_stream::write_hmac_block_stream(&payload_encrypted, &hmac_key)?;
_ = writer.write(&payload_hmac)?;
Ok(())
}
impl HeaderAttachment {
fn dump(&self, writer: &mut dyn Write) -> Result<(), std::io::Error> {
writer.write_u8(self.flags)?;
_ = writer.write(&self.content)?;
Ok(())
}
}
impl KDBX4OuterHeader {
#[allow(dead_code)]
fn dump(&self, writer: &mut dyn Write) -> Result<(), DatabaseSaveError> {
self.version.dump(writer)?;
writer.write_u8(HEADER_OUTER_ENCRYPTION_ID)?;
writer.write_with_len(&self.outer_cipher_config.dump())?;
writer.write_u8(HEADER_COMPRESSION_ID)?;
writer.write_with_len(&self.compression_config.dump())?;
writer.write_u8(HEADER_ENCRYPTION_IV)?;
writer.write_with_len(&self.outer_iv)?;
writer.write_u8(HEADER_MASTER_SEED)?;
writer.write_with_len(&self.master_seed)?;
let vd: VariantDictionary = self.kdf_config.to_variant_dictionary(&self.kdf_seed);
let mut vd_buffer = Vec::new();
vd.dump(&mut vd_buffer)?;
writer.write_u8(HEADER_KDF_PARAMS)?;
writer.write_with_len(&vd_buffer)?;
writer.write_u8(HEADER_END)?;
writer.write_with_len(&[])?;
if let Some(pcd) = &self.public_custom_data {
let mut vd_buffer = Vec::new();
pcd.dump(&mut vd_buffer)?;
writer.write_u8(HEADER_PUBLIC_CUSTOM_DATA)?;
writer.write_with_len(&vd_buffer)?;
}
Ok(())
}
}
impl KDBX4InnerHeader {
#[allow(dead_code)]
fn dump(&self, header_attachments: &[HeaderAttachment], writer: &mut dyn Write) -> Result<(), DatabaseSaveError> {
_ = writer.write(&[INNER_HEADER_RANDOM_STREAM_ID])?;
writer.write_u32::<LittleEndian>(4)?;
writer.write_u32::<LittleEndian>(self.inner_random_stream.dump())?;
writer.write_u8(INNER_HEADER_RANDOM_STREAM_KEY)?;
writer.write_with_len(&self.inner_random_stream_key)?;
for attachment in header_attachments {
writer.write_u8(INNER_HEADER_BINARY_ATTACHMENTS)?;
#[allow(clippy::cast_possible_truncation)]
writer.write_u32::<LittleEndian>((attachment.content.len() + 1) as u32)?;
attachment.dump(writer)?;
}
writer.write_u8(INNER_HEADER_END)?;
writer.write_with_len(&[])?;
Ok(())
}
}