Skip to content

Commit

Permalink
refactor: delay the generation of key elements (#182)
Browse files Browse the repository at this point in the history
This will make it easier to introduce challenge-response keys, since
we can perform the challenge before the final call to
`get_key_elements()`.
  • Loading branch information
louib authored Oct 15, 2023
1 parent 799d0b9 commit aa3a2ea
Show file tree
Hide file tree
Showing 8 changed files with 49 additions and 57 deletions.
18 changes: 6 additions & 12 deletions src/db/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,18 +61,16 @@ impl Database {
source: &mut dyn std::io::Read,
key: DatabaseKey,
) -> Result<Database, DatabaseOpenError> {
let key_elements = key.get_key_elements()?;

let mut data = Vec::new();
source.read_to_end(&mut data)?;

let database_version = DatabaseVersion::parse(data.as_ref())?;

match database_version {
DatabaseVersion::KDB(_) => parse_kdb(data.as_ref(), &key_elements),
DatabaseVersion::KDB(_) => parse_kdb(data.as_ref(), &key),
DatabaseVersion::KDB2(_) => Err(DatabaseOpenError::UnsupportedVersion.into()),
DatabaseVersion::KDB3(_) => parse_kdbx3(data.as_ref(), &key_elements),
DatabaseVersion::KDB4(_) => parse_kdbx4(data.as_ref(), &key_elements),
DatabaseVersion::KDB3(_) => parse_kdbx3(data.as_ref(), &key),
DatabaseVersion::KDB4(_) => parse_kdbx4(data.as_ref(), &key),
}
}

Expand All @@ -86,13 +84,11 @@ impl Database {
use crate::error::DatabaseSaveError;
use crate::format::kdbx4::dump_kdbx4;

let key_elements = key.get_key_elements()?;

match self.config.version {
DatabaseVersion::KDB(_) => Err(DatabaseSaveError::UnsupportedVersion.into()),
DatabaseVersion::KDB2(_) => Err(DatabaseSaveError::UnsupportedVersion.into()),
DatabaseVersion::KDB3(_) => Err(DatabaseSaveError::UnsupportedVersion.into()),
DatabaseVersion::KDB4(_) => dump_kdbx4(self, &key_elements, destination),
DatabaseVersion::KDB4(_) => dump_kdbx4(self, &key, destination),
}
}

Expand All @@ -101,8 +97,6 @@ impl Database {
source: &mut dyn std::io::Read,
key: DatabaseKey,
) -> Result<Vec<u8>, DatabaseOpenError> {
let key_elements = key.get_key_elements()?;

let mut data = Vec::new();
source.read_to_end(&mut data)?;

Expand All @@ -111,8 +105,8 @@ impl Database {
let data = match database_version {
DatabaseVersion::KDB(_) => return Err(DatabaseOpenError::UnsupportedVersion),
DatabaseVersion::KDB2(_) => return Err(DatabaseOpenError::UnsupportedVersion),
DatabaseVersion::KDB3(_) => decrypt_kdbx3(data.as_ref(), &key_elements)?.2,
DatabaseVersion::KDB4(_) => decrypt_kdbx4(data.as_ref(), &key_elements)?.3,
DatabaseVersion::KDB3(_) => decrypt_kdbx3(data.as_ref(), &key)?.2,
DatabaseVersion::KDB4(_) => decrypt_kdbx4(data.as_ref(), &key)?.3,
};

Ok(data)
Expand Down
7 changes: 3 additions & 4 deletions src/format/kdb.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use crate::{
db::{Database, Entry, Group, NodeRefMut, Value},
error::{DatabaseIntegrityError, DatabaseKeyError, DatabaseOpenError},
format::DatabaseVersion,
key::DatabaseKey,
};

use byteorder::{ByteOrder, LittleEndian};
Expand Down Expand Up @@ -295,17 +296,15 @@ fn parse_db(header: &KDBHeader, data: &[u8]) -> Result<Group, DatabaseIntegrityE
Ok(root)
}

pub(crate) fn parse_kdb(
data: &[u8],
key_elements: &[Vec<u8>],
) -> Result<Database, DatabaseOpenError> {
pub(crate) fn parse_kdb(data: &[u8], db_key: &DatabaseKey) -> Result<Database, DatabaseOpenError> {
let header = parse_header(data)?;
let version = DatabaseVersion::KDB(header.subversion as u16);

// Rest of file after header is payload
let payload_encrypted = &data[HEADER_SIZE..];

// derive master key from composite key, transform_seed, transform_rounds and master_seed
let key_elements = db_key.get_key_elements()?;
let key_elements: Vec<&[u8]> = key_elements.iter().map(|v| &v[..]).collect();
let composite_key = if key_elements.len() == 1 {
let key_element: [u8; 32] = key_elements[0].try_into().unwrap();
Expand Down
8 changes: 5 additions & 3 deletions src/format/kdbx3.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use crate::{
db::Database,
error::{BlockStreamError, DatabaseIntegrityError, DatabaseKeyError, DatabaseOpenError},
format::DatabaseVersion,
key::DatabaseKey,
};

use byteorder::{ByteOrder, LittleEndian};
Expand Down Expand Up @@ -162,9 +163,9 @@ fn parse_outer_header(data: &[u8]) -> Result<KDBX3Header, DatabaseOpenError> {
/// Open, decrypt and parse a KeePass database from a source and a password
pub(crate) fn parse_kdbx3(
data: &[u8],
key_elements: &[Vec<u8>],
db_key: &DatabaseKey,
) -> Result<Database, DatabaseOpenError> {
let (config, mut inner_decryptor, xml) = decrypt_kdbx3(data, key_elements)?;
let (config, mut inner_decryptor, xml) = decrypt_kdbx3(data, db_key)?;

// Parse XML data blocks
let database_content = crate::xml_db::parse::parse(&xml, &mut *inner_decryptor)
Expand All @@ -184,7 +185,7 @@ pub(crate) fn parse_kdbx3(
/// Open and decrypt a KeePass KDBX3 database from a source and a password
pub(crate) fn decrypt_kdbx3(
data: &[u8],
key_elements: &[Vec<u8>],
db_key: &DatabaseKey,
) -> Result<(DatabaseConfig, Box<dyn Cipher>, Vec<u8>), DatabaseOpenError> {
let version = DatabaseVersion::parse(data)?;
let header = parse_outer_header(data)?;
Expand Down Expand Up @@ -215,6 +216,7 @@ pub(crate) fn decrypt_kdbx3(
let payload_encrypted = &data[pos..];

// derive master key from composite key, transform_seed, transform_rounds and master_seed
let key_elements = db_key.get_key_elements()?;
let key_elements: Vec<&[u8]> = key_elements.iter().map(|v| &v[..]).collect();
let composite_key = calculate_sha256(&key_elements)?;

Expand Down
4 changes: 3 additions & 1 deletion src/format/kdbx4/dump.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,14 @@ use crate::{
},
hmac_block_stream,
io::WriteLengthTaggedExt,
key::DatabaseKey,
variant_dictionary::VariantDictionary,
};

/// Dump a KeePass database using the key elements
pub fn dump_kdbx4(
db: &Database,
key_elements: &[Vec<u8>],
db_key: &DatabaseKey,
writer: &mut dyn Write,
) -> Result<(), DatabaseSaveError> {
if !matches!(db.config.version, DatabaseVersion::KDB4(_)) {
Expand Down Expand Up @@ -62,6 +63,7 @@ pub fn dump_kdbx4(
writer.write(&header_sha256)?;

// derive master key from composite key, transform_seed, transform_rounds and master_seed
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)?;
Expand Down
18 changes: 6 additions & 12 deletions src/format/kdbx4/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,15 +81,12 @@ mod kdbx4_tests {
password += &std::char::from_u32(random_char as u32).unwrap().to_string();
}

let key_elements = DatabaseKey::new()
.with_password(&password)
.get_key_elements()
.unwrap();
let db_key = DatabaseKey::new().with_password(&password);

let mut encrypted_db = Vec::new();
dump_kdbx4(&db, &key_elements, &mut encrypted_db).unwrap();
dump_kdbx4(&db, &db_key, &mut encrypted_db).unwrap();

let decrypted_db = parse_kdbx4(&encrypted_db, &key_elements).unwrap();
let decrypted_db = parse_kdbx4(&encrypted_db, &db_key).unwrap();

assert_eq!(decrypted_db.root.children.len(), 3);
}
Expand Down Expand Up @@ -173,15 +170,12 @@ mod kdbx4_tests {

db.root.add_child(entry);

let key_elements = DatabaseKey::new()
.with_password("test")
.get_key_elements()
.unwrap();
let db_key = DatabaseKey::new().with_password("test");

let mut encrypted_db = Vec::new();
dump_kdbx4(&db, &key_elements, &mut encrypted_db).unwrap();
dump_kdbx4(&db, &db_key, &mut encrypted_db).unwrap();

let decrypted_db = parse_kdbx4(&encrypted_db, &key_elements).unwrap();
let decrypted_db = parse_kdbx4(&encrypted_db, &db_key).unwrap();

assert_eq!(decrypted_db.root.children.len(), 1);

Expand Down
8 changes: 5 additions & 3 deletions src/format/kdbx4/parse.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ use crate::{
DatabaseVersion,
},
hmac_block_stream,
key::DatabaseKey,
variant_dictionary::VariantDictionary,
};

Expand All @@ -34,9 +35,9 @@ impl From<&[u8]> for HeaderAttachment {
/// Open, decrypt and parse a KeePass database from a source and key elements
pub(crate) fn parse_kdbx4(
data: &[u8],
key_elements: &[Vec<u8>],
db_key: &DatabaseKey,
) -> Result<Database, DatabaseOpenError> {
let (config, header_attachments, mut inner_decryptor, xml) = decrypt_kdbx4(data, key_elements)?;
let (config, header_attachments, mut inner_decryptor, xml) = decrypt_kdbx4(data, db_key)?;

let database_content = crate::xml_db::parse::parse(&xml, &mut *inner_decryptor)?;

Expand All @@ -54,7 +55,7 @@ pub(crate) fn parse_kdbx4(
/// Open and decrypt a KeePass KDBX4 database from a source and key elements
pub(crate) fn decrypt_kdbx4(
data: &[u8],
key_elements: &[Vec<u8>],
db_key: &DatabaseKey,
) -> Result<
(
DatabaseConfig,
Expand All @@ -78,6 +79,7 @@ pub(crate) fn decrypt_kdbx4(
let hmac_block_stream = &data[(inner_header_start + 64)..];

// derive master key from composite key, transform_seed, transform_rounds and master_seed
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 = outer_header
Expand Down
11 changes: 7 additions & 4 deletions src/key.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,10 @@ use xml::reader::{EventReader, XmlEvent};

use crate::{crypt::calculate_sha256, error::DatabaseKeyError};

fn parse_xml_keyfile(xml: &[u8]) -> Result<Vec<u8>, DatabaseKeyError> {
pub type KeyElement = Vec<u8>;
pub type KeyElements = Vec<KeyElement>;

fn parse_xml_keyfile(xml: &[u8]) -> Result<KeyElement, DatabaseKeyError> {
let parser = EventReader::new(xml);

let mut tag_stack = Vec::new();
Expand Down Expand Up @@ -42,7 +45,7 @@ fn parse_xml_keyfile(xml: &[u8]) -> Result<Vec<u8>, DatabaseKeyError> {
Err(DatabaseKeyError::InvalidKeyFile)
}

fn parse_keyfile(buffer: &[u8]) -> Result<Vec<u8>, DatabaseKeyError> {
fn parse_keyfile(buffer: &[u8]) -> Result<KeyElement, DatabaseKeyError> {
// try to parse the buffer as XML, if successful, use that data instead of full file
if let Ok(v) = parse_xml_keyfile(&buffer) {
Ok(v)
Expand Down Expand Up @@ -80,10 +83,10 @@ impl DatabaseKey {
Default::default()
}

pub(crate) fn get_key_elements(self) -> Result<Vec<Vec<u8>>, DatabaseKeyError> {
pub(crate) fn get_key_elements(&self) -> Result<KeyElements, DatabaseKeyError> {
let mut out = Vec::new();

if let Some(p) = self.password {
if let Some(p) = &self.password {
out.push(calculate_sha256(&[p.as_bytes()])?.to_vec());
}

Expand Down
32 changes: 14 additions & 18 deletions src/xml_db/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ mod tests {
key::DatabaseKey,
};

fn make_key() -> Vec<Vec<u8>> {
fn make_key() -> DatabaseKey {
let mut password_bytes: Vec<u8> = vec![];
let mut password: String = "".to_string();
password_bytes.resize(40, 0);
Expand All @@ -35,11 +35,7 @@ mod tests {
password += &std::char::from_u32(random_char as u32).unwrap().to_string();
}

let key_elements = DatabaseKey::new()
.with_password(&password)
.get_key_elements()
.unwrap();
key_elements
DatabaseKey::new().with_password(&password)
}

#[test]
Expand Down Expand Up @@ -110,11 +106,11 @@ mod tests {
let mut db = Database::new(DatabaseConfig::default());
db.root = root_group;

let key_elements = make_key();
let db_key = make_key();

let mut encrypted_db = Vec::new();
kdbx4::dump_kdbx4(&db, &key_elements, &mut encrypted_db).unwrap();
let decrypted_db = kdbx4::parse_kdbx4(&encrypted_db, &key_elements).unwrap();
kdbx4::dump_kdbx4(&db, &db_key, &mut encrypted_db).unwrap();
let decrypted_db = kdbx4::parse_kdbx4(&encrypted_db, &db_key).unwrap();

assert_eq!(decrypted_db.root.children.len(), 1);

Expand Down Expand Up @@ -169,11 +165,11 @@ mod tests {
let mut db = Database::new(DatabaseConfig::default());
db.root = root_group.clone();

let key_elements = make_key();
let db_key = make_key();

let mut encrypted_db = Vec::new();
kdbx4::dump_kdbx4(&db, &key_elements, &mut encrypted_db).unwrap();
let decrypted_db = kdbx4::parse_kdbx4(&encrypted_db, &key_elements).unwrap();
kdbx4::dump_kdbx4(&db, &db_key, &mut encrypted_db).unwrap();
let decrypted_db = kdbx4::parse_kdbx4(&encrypted_db, &db_key).unwrap();

assert_eq!(decrypted_db.root.children.len(), 2);

Expand Down Expand Up @@ -278,11 +274,11 @@ mod tests {

db.meta = meta.clone();

let key_elements = make_key();
let db_key = make_key();

let mut encrypted_db = Vec::new();
kdbx4::dump_kdbx4(&db, &key_elements, &mut encrypted_db).unwrap();
let decrypted_db = kdbx4::parse_kdbx4(&encrypted_db, &key_elements).unwrap();
kdbx4::dump_kdbx4(&db, &db_key, &mut encrypted_db).unwrap();
let decrypted_db = kdbx4::parse_kdbx4(&encrypted_db, &db_key).unwrap();

assert_eq!(decrypted_db.meta, meta);
}
Expand All @@ -301,11 +297,11 @@ mod tests {
},
];

let key_elements = make_key();
let db_key = make_key();

let mut encrypted_db = Vec::new();
kdbx4::dump_kdbx4(&db, &key_elements, &mut encrypted_db).unwrap();
let decrypted_db = kdbx4::parse_kdbx4(&encrypted_db, &key_elements).unwrap();
kdbx4::dump_kdbx4(&db, &db_key, &mut encrypted_db).unwrap();
let decrypted_db = kdbx4::parse_kdbx4(&encrypted_db, &db_key).unwrap();

assert_eq!(decrypted_db, db);
}
Expand Down

0 comments on commit aa3a2ea

Please sign in to comment.