Skip to content

Commit

Permalink
refactor(key)!: use a builder-like pattern and owned internal represe…
Browse files Browse the repository at this point in the history
…ntation (#168)

* refactor(key)!: change DatabaseKey to owned representation

in preparation of adding additional key element types, change
DatabaseKey to use an owned representation instead.

BREAKING CHANGE: cases where DatabaseKey objects were directly
constructed will need to be updated

* refactor(key)!: builder-like DatabaseKey

Change DatabaseKey objects to be initialized empty and accept with_*
functions to provide key elements. This should greatly simplify adding
additional key element types in the future.

BREAKING CHANGE: Key construction is changed for all users of the
library.

* refactor(key)!: make member variables private

In preparation for future key elements, hide the internal structure of
the DatabaseKey objects to prevent direct construction, so that
additional elements can be added without breaking backward
compatibility.

BREAKING CHANGE: If elements were provided directly, this is no longer
supported.

---------

Co-authored-by: Stefan Seemayer <stefan@seemayer.de>
Co-authored-by: louib <code@louib.net>
  • Loading branch information
3 people authored Jul 22, 2023
1 parent ae1360f commit c89df31
Show file tree
Hide file tree
Showing 14 changed files with 132 additions and 142 deletions.
12 changes: 5 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,10 @@ use keepass::{
use std::fs::File;

fn main() -> Result<(), DatabaseOpenError> {
// Open KeePass database
let path = std::path::Path::new("tests/resources/test_db_with_password.kdbx");
let db = Database::open(
&mut File::open(path)?, // the database
DatabaseKey::with_password("demopass"), // password (keyfile is also supported)
)?;
// Open KeePass database using a password (keyfile is also supported)
let mut file = File::open("tests/resources/test_db_with_password.kdbx")?;
let key = DatabaseKey::new().with_password("demopass");
let db = Database::open(&mut file, key)?;

// Iterate over all `Group`s and `Entry`s
for node in &db.root {
Expand Down Expand Up @@ -90,7 +88,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
#[cfg(feature = "save_kdbx4")]
db.save(
&mut File::create("demo.kdbx")?,
DatabaseKey::with_password("demopass"),
DatabaseKey::new().with_password("demopass"),
)?;

Ok(())
Expand Down
18 changes: 9 additions & 9 deletions src/bin/kp-dump-json.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/// utility to dump keepass database as JSON document
use std::{fs::File, io::Read};
use std::fs::File;

use anyhow::Result;
use clap::Parser;
Expand All @@ -21,20 +21,20 @@ pub fn main() -> Result<()> {
let args = Args::parse();

let mut source = File::open(args.in_kdbx)?;
let mut keyfile: Option<File> = args.keyfile.and_then(|f| File::open(f).ok());
let mut key = DatabaseKey::new();

if let Some(f) = args.keyfile {
key = key.with_keyfile(&mut File::open(f)?)?;
}

let password =
rpassword::prompt_password("Password (or blank for none): ").expect("Read password");

let password = if password.is_empty() {
None
} else {
Some(&password[..])
if !password.is_empty() {
key = key.with_password(&password);
};

let keyfile = keyfile.as_mut().map(|kf| kf as &mut dyn Read);

let db = Database::open(&mut source, DatabaseKey { password, keyfile })?;
let db = Database::open(&mut source, key)?;

let stdout = std::io::stdout().lock();
serde_json::ser::to_writer(stdout, &db)?;
Expand Down
22 changes: 11 additions & 11 deletions src/bin/kp-dump-xml.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/// utility to dump keepass database internal XML data.
use std::fs::File;
use std::io::{Read, Write};
use std::io::Write;

use anyhow::Result;
use clap::Parser;
Expand All @@ -25,20 +25,20 @@ pub fn main() -> Result<()> {
let args = Args::parse();

let mut source = File::open(args.in_kdbx)?;
let mut keyfile: Option<File> = args.keyfile.and_then(|f| File::open(f).ok());
let mut key = DatabaseKey::new();

let password = rpassword::prompt_password("Password (or blank for none): ")
.expect("Could not read password from TTY");
if let Some(f) = args.keyfile {
key = key.with_keyfile(&mut File::open(f)?)?;
}

let password = if password.is_empty() {
None
} else {
Some(&password[..])
};
let password =
rpassword::prompt_password("Password (or blank for none): ").expect("Read password");

let keyfile = keyfile.as_mut().map(|kf| kf as &mut dyn Read);
if !password.is_empty() {
key = key.with_password(&password);
};

let xml = Database::get_xml(&mut source, DatabaseKey { password, keyfile })?;
let xml = Database::get_xml(&mut source, key)?;

File::create(args.out_xml)?.write_all(&xml)?;

Expand Down
40 changes: 11 additions & 29 deletions src/bin/kp-rewrite.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
/// utility to parse a KeePass database, and then write it out again, to see if anything is lost.
use std::fs::File;
use std::io::{Cursor, Read};

use anyhow::Result;
use clap::Parser;
Expand All @@ -25,40 +24,23 @@ pub fn main() -> Result<()> {
let args = Args::parse();

let mut source = File::open(args.in_kdbx)?;
let mut key = DatabaseKey::new();

let password = rpassword::prompt_password("Password (or blank for none): ")
.expect("Could not read password from TTY");
if let Some(f) = args.keyfile {
key = key.with_keyfile(&mut File::open(f)?)?;
}

let password = if password.is_empty() {
None
} else {
Some(&password[..])
};
let password =
rpassword::prompt_password("Password (or blank for none): ").expect("Read password");

let mut keyfile: Option<Cursor<_>> = if let Some(kf) = args.keyfile {
let mut f = File::open(kf)?;
let mut buf = Vec::new();
f.read_to_end(&mut buf)?;
Some(Cursor::new(buf))
} else {
None
if !password.is_empty() {
key = key.with_password(&password);
};

let db = Database::open(
&mut source,
DatabaseKey {
password,
keyfile: keyfile.as_mut().map(|kf| kf as &mut dyn Read),
},
)?;
let db = Database::open(&mut source, key.clone())?;

db.save(
&mut File::create(args.out_kdbx)?,
DatabaseKey {
password,
keyfile: keyfile.as_mut().map(|kf| kf as &mut dyn Read),
},
)?;
let mut out_file = File::create(args.out_kdbx)?;
db.save(&mut out_file, key)?;

Ok(())
}
21 changes: 10 additions & 11 deletions src/bin/kp-show-db.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
/// utility to show a parsed KeePass database
use std::fs::File;
use std::io::Read;

use anyhow::Result;
use clap::Parser;
Expand All @@ -22,20 +21,20 @@ pub fn main() -> Result<()> {
let args = Args::parse();

let mut source = File::open(args.in_kdbx)?;
let mut keyfile: Option<File> = args.keyfile.and_then(|f| File::open(f).ok());
let mut key = DatabaseKey::new();

let password = rpassword::prompt_password("Password (or blank for none): ")
.expect("Could not read password from TTY");
if let Some(f) = args.keyfile {
key = key.with_keyfile(&mut File::open(f)?)?;
}

let password = if password.is_empty() {
None
} else {
Some(&password[..])
};
let password =
rpassword::prompt_password("Password (or blank for none): ").expect("Read password");

let keyfile = keyfile.as_mut().map(|kf| kf as &mut dyn Read);
if !password.is_empty() {
key = key.with_password(&password);
};

let db = Database::open(&mut source, DatabaseKey { password, keyfile })?;
let db = Database::open(&mut source, key)?;

println!("{:#?}", db);

Expand Down
16 changes: 7 additions & 9 deletions src/bin/kp-show-otp.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
/// utility to dump keepass database internal XML data.
use std::fs::File;
use std::io::Read;

use anyhow::Result;
use clap::Parser;
Expand All @@ -24,21 +23,20 @@ pub fn main() -> Result<()> {
let args = Args::parse();

let mut source = File::open(args.in_kdbx)?;
let mut key = DatabaseKey::new();

let mut keyfile: Option<File> = args.keyfile.and_then(|f| File::open(f).ok());
if let Some(f) = args.keyfile {
key = key.with_keyfile(&mut File::open(f)?)?;
}

let password =
rpassword::prompt_password("Password (or blank for none): ").expect("Read password");

let password = if password.is_empty() {
None
} else {
Some(&password[..])
if !password.is_empty() {
key = key.with_password(&password);
};

let keyfile = keyfile.as_mut().map(|kf| kf as &mut dyn Read);

let db = Database::open(&mut source, DatabaseKey { password, keyfile })?;
let db = Database::open(&mut source, key)?;

if let Some(NodeRef::Entry(e)) = db.root.get(&[&args.entry]) {
let totp = e.get_otp().unwrap();
Expand Down
8 changes: 4 additions & 4 deletions src/db/group.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,12 +68,12 @@ impl Group {
/// Recursively get a Group or Entry reference by specifying a path relative to the current Group
/// ```
/// use keepass::{Database, DatabaseKey, db::NodeRef};
/// use std::{fs::File, path::Path};
/// use std::fs::File;
///
/// let path = Path::new("tests/resources/test_db_with_password.kdbx");
/// let mut file = File::open("tests/resources/test_db_with_password.kdbx").unwrap();
/// let db = Database::open(
/// &mut File::open(path).unwrap(),
/// DatabaseKey::with_password("demopass")
/// &mut file,
/// DatabaseKey::new().with_password("demopass")
/// ).unwrap();
///
/// if let Some(NodeRef::Entry(e)) = db.root.get(&["General", "Sample Entry #2"]) {
Expand Down
6 changes: 3 additions & 3 deletions src/db/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -330,7 +330,7 @@ mod database_tests {
fn test_xml() -> Result<(), DatabaseOpenError> {
let xml = Database::get_xml(
&mut File::open("tests/resources/test_db_with_password.kdbx")?,
DatabaseKey::with_password("demopass"),
DatabaseKey::new().with_password("demopass"),
)?;

assert!(xml.len() > 100);
Expand All @@ -350,12 +350,12 @@ mod database_tests {

let mut buffer = Vec::new();

db.save(&mut buffer, DatabaseKey::with_password("testing"))
db.save(&mut buffer, DatabaseKey::new().with_password("testing"))
.unwrap();

let db_loaded = Database::open(
&mut buffer.as_slice(),
DatabaseKey::with_password("testing"),
DatabaseKey::new().with_password("testing"),
)
.unwrap();

Expand Down
5 changes: 4 additions & 1 deletion src/db/otp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,10 @@ mod kdbx4_otp_tests {
fn kdbx4_entry() -> Result<(), Box<dyn std::error::Error>> {
// KDBX4 database format Base64 encodes ExpiryTime (and all other XML timestamps)
let path = Path::new("tests/resources/test_db_kdbx4_with_totp_entry.kdbx");
let db = Database::open(&mut File::open(path)?, DatabaseKey::with_password("test"))?;
let db = Database::open(
&mut File::open(path)?,
DatabaseKey::new().with_password("test"),
)?;

let otp_str = "otpauth://totp/KeePassXC:none?secret=JBSWY3DPEHPK3PXP&period=30&digits=6&issuer=KeePassXC";

Expand Down
6 changes: 4 additions & 2 deletions src/format/kdbx4/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,8 @@ mod kdbx4_tests {
password += &std::char::from_u32(random_char as u32).unwrap().to_string();
}

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

Expand Down Expand Up @@ -172,7 +173,8 @@ mod kdbx4_tests {

db.root.children.push(Node::Entry(entry));

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

Expand Down
Loading

0 comments on commit c89df31

Please sign in to comment.