├── .gitignore ├── test ├── 128Bkey ├── 32Bkey ├── 64Bkey ├── 64Bkey_alt ├── 2048Bkey ├── 4096Bkey ├── test_key ├── test_both.kdb ├── test_save.kdb ├── test_128B_key.kdb ├── test_32B_key.kdb ├── test_64B_key.kdb ├── test_keyfile.kdb ├── test_parsing.kdb ├── test_password.kdb ├── test_2048B_key.kdb ├── test_4096B_key.kdb └── test_64B_alt_key.kdb ├── src ├── common │ ├── mod.rs │ ├── common_error.rs │ └── common.rs ├── lib.rs ├── kpdb │ ├── tm.rs.bak │ ├── mod.rs │ ├── v1entry.rs │ ├── v1header.rs │ ├── v1error.rs │ ├── v1group.rs │ ├── tests_parser.rs │ ├── tests_crypter.rs │ ├── tests_v1kpdb.rs │ ├── crypter.rs.bak │ ├── v1kpdb.rs │ ├── crypter.rs │ └── parser.rs └── sec_str │ └── mod.rs ├── .travis.yml ├── Cargo.toml ├── LICENSE.md └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | keepass 2 | *.rlib 3 | *~ 4 | Cargo.lock 5 | 6 | target/ 7 | -------------------------------------------------------------------------------- /test/128Bkey: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raymontag/rust-keepass/HEAD/test/128Bkey -------------------------------------------------------------------------------- /test/32Bkey: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raymontag/rust-keepass/HEAD/test/32Bkey -------------------------------------------------------------------------------- /test/64Bkey: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raymontag/rust-keepass/HEAD/test/64Bkey -------------------------------------------------------------------------------- /test/64Bkey_alt: -------------------------------------------------------------------------------- 1 | FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF -------------------------------------------------------------------------------- /test/2048Bkey: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raymontag/rust-keepass/HEAD/test/2048Bkey -------------------------------------------------------------------------------- /test/4096Bkey: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raymontag/rust-keepass/HEAD/test/4096Bkey -------------------------------------------------------------------------------- /test/test_key: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raymontag/rust-keepass/HEAD/test/test_key -------------------------------------------------------------------------------- /test/test_both.kdb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raymontag/rust-keepass/HEAD/test/test_both.kdb -------------------------------------------------------------------------------- /test/test_save.kdb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raymontag/rust-keepass/HEAD/test/test_save.kdb -------------------------------------------------------------------------------- /test/test_128B_key.kdb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raymontag/rust-keepass/HEAD/test/test_128B_key.kdb -------------------------------------------------------------------------------- /test/test_32B_key.kdb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raymontag/rust-keepass/HEAD/test/test_32B_key.kdb -------------------------------------------------------------------------------- /test/test_64B_key.kdb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raymontag/rust-keepass/HEAD/test/test_64B_key.kdb -------------------------------------------------------------------------------- /test/test_keyfile.kdb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raymontag/rust-keepass/HEAD/test/test_keyfile.kdb -------------------------------------------------------------------------------- /test/test_parsing.kdb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raymontag/rust-keepass/HEAD/test/test_parsing.kdb -------------------------------------------------------------------------------- /test/test_password.kdb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raymontag/rust-keepass/HEAD/test/test_password.kdb -------------------------------------------------------------------------------- /test/test_2048B_key.kdb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raymontag/rust-keepass/HEAD/test/test_2048B_key.kdb -------------------------------------------------------------------------------- /test/test_4096B_key.kdb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raymontag/rust-keepass/HEAD/test/test_4096B_key.kdb -------------------------------------------------------------------------------- /test/test_64B_alt_key.kdb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raymontag/rust-keepass/HEAD/test/test_64B_alt_key.kdb -------------------------------------------------------------------------------- /src/common/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod common; 2 | pub mod common_error; 3 | 4 | //#[cfg(tests)] 5 | //mod tests_common; 6 | 7 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: rust 2 | rust: 3 | - stable 4 | sudo: false 5 | after_success: 6 | - if [[ "$TRAVIS_RUST_VERSION" == "stable" ]] && [[ "$TRAVIS_OS_NAME" == "linux" ]]; then travis-cargo coveralls --no-sudo --verify; fi 7 | os: 8 | - linux 9 | - osx 10 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![crate_name="keepass"] 2 | #![crate_type = "dylib"] 3 | #![crate_type = "rlib"] 4 | 5 | extern crate libc; 6 | extern crate openssl; 7 | extern crate rustc_serialize; 8 | extern crate chrono; 9 | extern crate rand; 10 | extern crate uuid; 11 | 12 | pub mod sec_str; 13 | pub mod kpdb; 14 | pub mod common; 15 | 16 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | 3 | name = "keepass" 4 | version = "0.0.1" 5 | authors = ["GrayFox ", "Jan S. "] 6 | 7 | [lib] 8 | 9 | name = "keepass" 10 | path = "src/lib.rs" 11 | 12 | test = true 13 | doctest = true 14 | doc = true 15 | 16 | [dependencies] 17 | 18 | rustc-serialize = "*" 19 | libc = "*" 20 | rand = "*" 21 | chrono = "*" 22 | uuid = { version = "*", features = ["v4"] } 23 | openssl = "*" 24 | 25 | -------------------------------------------------------------------------------- /src/kpdb/tm.rs.bak: -------------------------------------------------------------------------------- 1 | #[derive(Debug, Copy)] 2 | pub struct Tm { 3 | pub year: i32, 4 | pub month: i32, 5 | pub day: i32, 6 | pub hour: i32, 7 | pub minute: i32, 8 | pub second: i32, 9 | } 10 | 11 | impl Tm { 12 | pub fn new() -> Tm { 13 | Tm { year: 0, 14 | month: 0, 15 | day: 0, 16 | hour: 0, 17 | minute: 0, 18 | second: 0, 19 | } 20 | } 21 | } 22 | 23 | -------------------------------------------------------------------------------- /src/common/common_error.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | use std::error; 3 | 4 | pub use self::CommonError::*; 5 | 6 | #[doc = " 7 | Use this for catching various errors that 8 | can happen when using V1Kpdb. 9 | "] 10 | #[derive(Debug, Clone, PartialEq, Eq, Copy)] 11 | pub enum CommonError { 12 | /// Some error in parsing 13 | ConvertErr, 14 | } 15 | 16 | impl fmt::Display for CommonError { 17 | fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { 18 | fmt.write_str(error::Error::description(self)) 19 | } 20 | } 21 | 22 | impl error::Error for CommonError { 23 | fn description(&self) -> &str { 24 | match *self { 25 | ConvertErr => "Some error while converting datatypes", 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | ## ISC License 2 | 3 | Copyright (c) 2014-2015, Karsten-Kai König 4 | 5 | Permission to use, copy, modify, and/or distribute this software for any 6 | purpose with or without fee is hereby granted, provided that the above 7 | copyright notice and this permission notice appear in all copies. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 10 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 11 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 12 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 13 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 14 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 15 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | rust-keepass 2 | ============ 3 | 4 | [![Build Status](https://travis-ci.org/raymontag/rust-keepass.svg?branch=master)](https://travis-ci.org/raymontag/rust-keepass) [![Coverage Status](https://coveralls.io/repos/github/raymontag/rust-keepass/badge.svg?branch=master)](https://coveralls.io/github/raymontag/rust-keepass?branch=master) 5 | 6 | Usage 7 | ----- 8 | 9 | We try to take care that all security related functions are not optimized away by the compiler (see issue #4). However we can not ensure that this really works. If you want to be on the safe side, turn optimization with the opt-level-option off like it is described [here](http://doc.crates.io/manifest.html#the-profile-sections). It is necessary that you do this in the top-level project as dependency options are overwritten! 10 | 11 | License 12 | ------- 13 | 14 | See the [LICENSE](LICENSE.md) file for license rights and limitations (ISC). 15 | -------------------------------------------------------------------------------- /src/kpdb/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod v1kpdb; 2 | pub mod v1error; 3 | pub mod v1group; 4 | pub mod v1entry; 5 | pub mod v1header; 6 | 7 | mod crypter; 8 | mod parser; 9 | 10 | #[cfg(test)] 11 | mod tests_v1kpdb; 12 | mod tests_parser; 13 | mod tests_crypter; 14 | 15 | use std::rc::Weak; 16 | 17 | use self::v1error::V1KpdbError; 18 | 19 | trait GetIndex { 20 | fn get_index(&self, item: &T) -> Result; 21 | } 22 | 23 | impl GetIndex for Vec { 24 | fn get_index(&self, item: &T) -> Result { 25 | for index in 0..self.len() { 26 | if self[index] == *item { 27 | return Ok(index); 28 | } 29 | } 30 | 31 | Err(V1KpdbError::IndexErr) 32 | } 33 | } 34 | 35 | impl GetIndex for Vec> { 36 | fn get_index(&self, item: &T) -> Result { 37 | for index in 0..self.len() { 38 | if let Some(cmp_item) = self[index].upgrade() { 39 | if *cmp_item == *item { 40 | return Ok(index); 41 | } 42 | } else { 43 | return Err(V1KpdbError::WeakErr); 44 | } 45 | } 46 | 47 | Err(V1KpdbError::IndexErr) 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/common/common.rs: -------------------------------------------------------------------------------- 1 | use std::ptr; 2 | 3 | use common::common_error::CommonError; 4 | 5 | pub fn slice_to_u16(slice: &[u8]) -> Result { 6 | if slice.len() < 2 { 7 | return Err(CommonError::ConvertErr); 8 | } 9 | 10 | let value = (slice[1] as u16) << 8; 11 | Ok(value | slice[0] as u16) 12 | } 13 | 14 | pub fn slice_to_u32(slice: &[u8]) -> Result { 15 | if slice.len() < 4 { 16 | return Err(CommonError::ConvertErr); 17 | } 18 | 19 | let mut value = (slice[3] as u32) << 24; 20 | value |= (slice[2] as u32) << 16; 21 | value |= (slice[1] as u32) << 8; 22 | Ok(value | slice[0] as u32) 23 | } 24 | 25 | pub fn u16_to_vec_u8(value: u16) -> Vec { 26 | let mut ret: Vec = vec![0,0]; 27 | ret[0] |= (value & 0xFF) as u8; 28 | ret[1] |= ((value & (0xFF << 8)) >> 8) as u8; 29 | ret 30 | } 31 | 32 | pub fn u32_to_vec_u8(value: u32) -> Vec { 33 | let mut ret: Vec = vec![0,0,0,0]; 34 | ret[0] |= (value & 0xFF) as u8; 35 | ret[1] |= ((value & (0xFF << 8)) >> 8) as u8; 36 | ret[2] |= ((value & (0xFF << 16)) >> 16) as u8; 37 | ret[3] |= ((value & (0xFF << 24)) >> 24) as u8; 38 | ret 39 | } 40 | 41 | pub unsafe fn write_array_volatile(dst: *mut u8, val: u8, count: usize) { 42 | for i in 0..count { 43 | ptr::write_volatile(dst.offset(i as isize), val); 44 | } 45 | } 46 | 47 | -------------------------------------------------------------------------------- /src/kpdb/v1entry.rs: -------------------------------------------------------------------------------- 1 | use std::cell::RefCell; 2 | use std::rc::Rc; 3 | 4 | use chrono::{DateTime, Local, TimeZone}; 5 | use uuid::Uuid; 6 | 7 | use super::v1group::V1Group; 8 | use super::super::sec_str::SecureString; 9 | 10 | #[doc = " 11 | Implements an entry in a KeePass v1.x database. 12 | "] 13 | pub struct V1Entry { 14 | /// UUID of the entry 15 | pub uuid: Uuid, 16 | /// ID of the group holding the entry 17 | pub group_id: u32, 18 | /// Reference to the group holding the entry 19 | pub group: Option>>, 20 | /// Used to specify an icon for the entry 21 | pub image: u32, 22 | /// Title of the entry 23 | pub title: String, 24 | /// URL for the login 25 | pub url: Option, 26 | /// Username for the login 27 | pub username: Option, 28 | /// Password for the login 29 | pub password: Option, 30 | /// Some comment about the entry 31 | pub comment: Option, 32 | /// Descripton of the binary content 33 | pub binary_desc: Option, 34 | /// ??? 35 | pub binary: Option>, 36 | /// Date of creation 37 | pub creation: DateTime, 38 | /// Date of last modification 39 | pub last_mod: DateTime, 40 | /// Date of last access 41 | pub last_access: DateTime, 42 | /// Expiration date 43 | pub expire: DateTime, 44 | } 45 | 46 | impl V1Entry { 47 | /// Don't use this to create an empty entry. 48 | /// Normally you want to use the API 49 | /// of V1Kpdb to do this 50 | pub fn new() -> V1Entry { 51 | V1Entry { 52 | uuid: Uuid::new_v4(), 53 | group_id: 0, 54 | group: None, 55 | image: 0, 56 | title: "".to_string(), 57 | url: None, 58 | username: None, 59 | password: None, 60 | comment: None, 61 | binary_desc: None, 62 | binary: None, 63 | creation: Local::now(), 64 | last_mod: Local::now(), 65 | last_access: Local::now(), 66 | expire: Local.ymd(2999, 12, 28).and_hms(23, 59, 59), 67 | } 68 | } 69 | } 70 | 71 | impl PartialEq for V1Entry { 72 | fn eq(&self, other: &V1Entry) -> bool { 73 | self.uuid == other.uuid 74 | } 75 | } 76 | 77 | impl Eq for V1Entry {} 78 | -------------------------------------------------------------------------------- /src/kpdb/v1header.rs: -------------------------------------------------------------------------------- 1 | use kpdb::v1error::V1KpdbError; 2 | 3 | // Todo: 4 | // * Drop for critical data 5 | // * Parsing into LoadParser 6 | 7 | #[doc = " 8 | V1Header implements the header of a KeePass v1.x database. 9 | Normally you don't need to mess with this yourself. 10 | "] 11 | #[derive(Clone)] 12 | pub struct V1Header { 13 | /// File signature 14 | pub signature1: u32, 15 | /// File signature 16 | pub signature2: u32, 17 | /// Describes which encryption algorithm was used. 18 | /// 0b10 is for AES, 0b1000 is for Twofish (not 19 | /// supported, yet) 20 | pub enc_flag: u32, 21 | /// Version of the database. 0x00030002 is for v1.x 22 | pub version: u32, 23 | /// A seed used to create the final key 24 | pub final_randomseed: Vec, 25 | /// IV for AEC_CBC to de-/encrypt the database 26 | pub iv: Vec, 27 | /// Total number of groups in database 28 | pub num_groups: u32, 29 | /// Total number of entries in database 30 | pub num_entries: u32, 31 | /// Hash of the encrypted content to check success 32 | /// of decryption 33 | pub content_hash: Vec, 34 | /// A seed used to create the final key 35 | pub transf_randomseed: Vec, 36 | /// Specifies number of rounds of AES_ECB to create 37 | /// the final key 38 | pub key_transf_rounds: u32, 39 | } 40 | 41 | impl V1Header { 42 | /// Use this to create a new empty header 43 | pub fn new() -> V1Header { 44 | V1Header { 45 | signature1: 0, 46 | signature2: 0, 47 | enc_flag: 0, 48 | version: 0, 49 | final_randomseed: vec![], 50 | iv: vec![], 51 | num_groups: 0, 52 | num_entries: 0, 53 | content_hash: vec![], 54 | transf_randomseed: vec![], 55 | key_transf_rounds: 0, 56 | } 57 | } 58 | 59 | // Checks file signatures 60 | pub fn check_signatures(&self) -> Result<(), V1KpdbError> { 61 | if self.signature1 != 0x9AA2D903u32 || self.signature2 != 0xB54BFB65u32 { 62 | return Err(V1KpdbError::SignatureErr); 63 | } 64 | Ok(()) 65 | } 66 | 67 | // Checks encryption flag 68 | pub fn check_enc_flag(&self) -> Result<(), V1KpdbError> { 69 | if self.enc_flag & 2 != 2 { 70 | return Err(V1KpdbError::EncFlagErr); 71 | } 72 | Ok(()) 73 | } 74 | 75 | // Checks database version 76 | pub fn check_version(&self) -> Result<(), V1KpdbError> { 77 | if self.version != 0x00030002u32 { 78 | return Err(V1KpdbError::VersionErr); 79 | } 80 | Ok(()) 81 | } 82 | } 83 | 84 | -------------------------------------------------------------------------------- /src/kpdb/v1error.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | use std::error; 3 | 4 | pub use self::V1KpdbError::*; 5 | 6 | #[doc = " 7 | Use this for catching various errors that 8 | can happen when using V1Kpdb. 9 | "] 10 | #[derive(Debug, Clone, PartialEq, Eq, Copy)] 11 | pub enum V1KpdbError { 12 | /// E.g. Couldn't open a file or file is to 13 | /// small. 14 | FileErr, 15 | /// Something went wrong while the database is 16 | /// readed in 17 | ReadErr, 18 | /// Something went wrong while the database was written 19 | WriteErr, 20 | /// The file signature in the header is wrong 21 | SignatureErr, 22 | /// Not supported encryption used 23 | EncFlagErr, 24 | /// Wrong database version 25 | VersionErr, 26 | /// Some error in encryption 27 | EncryptErr, 28 | /// Some error in decryption 29 | DecryptErr, 30 | /// Hash of decrypted content is wrong. 31 | /// Probably the wrong password and/or keyfile 32 | /// was used 33 | HashErr, 34 | /// Some error in parsing 35 | ConvertErr, 36 | /// Some error in parsing. Probably corrupted database 37 | OffsetErr, 38 | /// Group tree is corrupted 39 | TreeErr, 40 | /// Password and/or keyfile needed but at least one of both 41 | PassErr, 42 | /// Can't find item in Vec 43 | IndexErr, 44 | /// Tried upgrade of weak reference without strong one 45 | WeakErr, 46 | } 47 | 48 | impl fmt::Display for V1KpdbError { 49 | fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { 50 | fmt.write_str(error::Error::description(self)) 51 | } 52 | } 53 | 54 | impl error::Error for V1KpdbError { 55 | fn description(&self) -> &str { 56 | match *self { 57 | FileErr => "Couldn't open file or database is too small", 58 | ReadErr => "Couldn't read file", 59 | WriteErr => "Couldn't write file", 60 | SignatureErr => "File signature in header is wrong", 61 | EncFlagErr => "Encryption algorithm not supported", 62 | VersionErr => "Wrong database version", 63 | EncryptErr => "Something went wrong during encryption", 64 | DecryptErr => "Something went wrong during decryption", 65 | HashErr => "Content's hash is wrong, probably wrong password", 66 | ConvertErr => "Some error while parsing the database", 67 | OffsetErr => "Some error while parsing the database. Probably a corrupted file", 68 | TreeErr => "Group tree is corrupted", 69 | PassErr => "Password and/or keyfile needed but at least one of both", 70 | IndexErr => "Can't find item in Vec", 71 | WeakErr => "Tried upgrade of weak reference without strong one", 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/kpdb/v1group.rs: -------------------------------------------------------------------------------- 1 | use std::cell::RefCell; 2 | use std::rc::{Rc, Weak}; 3 | 4 | use chrono::{DateTime, Local, TimeZone}; 5 | 6 | use kpdb::GetIndex; 7 | use kpdb::v1entry::V1Entry; 8 | use kpdb::v1error::V1KpdbError; 9 | 10 | #[doc = " 11 | Implements a group of a KeePass v1.x database 12 | "] 13 | pub struct V1Group { 14 | /// Group id unique in the database 15 | pub id: u32, 16 | /// Title of the group 17 | pub title: String, 18 | /// Number to specify a icon for the group 19 | pub image: u32, 20 | /// Level in group tree 21 | pub level: u16, 22 | /// Date of creation 23 | pub creation: DateTime, 24 | /// Date of last modification 25 | pub last_mod: DateTime, 26 | /// Date of last access 27 | pub last_access: DateTime, 28 | /// Expiration date for the whole group 29 | pub expire: DateTime, 30 | /// ?? 31 | pub flags: u32, 32 | /// Pointer to the parent group 33 | pub parent: Option>>, 34 | /// Array of weak references to the children 35 | pub children: Vec>>, 36 | /// Array of weak references to the entries 37 | pub entries: Vec>>, // db: Box>, 38 | } 39 | 40 | impl V1Group { 41 | /// Don't use this to create an empty group. 42 | /// Normally you want to use the API 43 | /// of V1Kpdb to do this. 44 | pub fn new() -> V1Group { 45 | V1Group { 46 | id: 0, 47 | title: "".to_string(), 48 | image: 0, 49 | level: 0, 50 | creation: Local::now(), 51 | last_mod: Local::now(), 52 | last_access: Local::now(), 53 | expire: Local.ymd(2999, 12, 28).and_hms(23, 59, 59), 54 | flags: 0, 55 | parent: None, 56 | children: vec![], 57 | entries: vec![], // db: box None, 58 | } 59 | } 60 | 61 | pub fn drop_weak_child_reference(&mut self, 62 | child: &Rc>) 63 | -> Result<(), V1KpdbError> { 64 | let index = try!(self.children.get_index(child)); 65 | let weak_group_reference = self.children.remove(index); 66 | drop(weak_group_reference); 67 | Ok(()) 68 | } 69 | 70 | pub fn drop_weak_entry_reference(&mut self, 71 | entry: &Rc>) 72 | -> Result<(), V1KpdbError> { 73 | let index = try!(self.entries.get_index(entry)); 74 | let weak_entry_reference = self.entries.remove(index); 75 | drop(weak_entry_reference); 76 | Ok(()) 77 | } 78 | } 79 | 80 | impl PartialEq for V1Group { 81 | fn eq(&self, other: &V1Group) -> bool { 82 | self.id == other.id 83 | } 84 | } 85 | 86 | impl Eq for V1Group {} 87 | -------------------------------------------------------------------------------- /src/sec_str/mod.rs: -------------------------------------------------------------------------------- 1 | use libc::{c_void, mlock, munlock, size_t}; 2 | use openssl::symm; 3 | use rand; 4 | 5 | use common::common::write_array_volatile; 6 | 7 | #[doc = " 8 | SecureString implements a secure string. This means in particular: 9 | 10 | * The input string moves to the struct, i.e. it's not just borrowed 11 | 12 | * The string is encrypted with a random password for obfuscation 13 | 14 | * mlock() is called on the string to prevent swapping 15 | 16 | * A method to overwrite the string with zeroes is implemented 17 | 18 | * The overwrite method is called on drop of the struct automatically 19 | 20 | * Implements fmt::Show to prevent logging of the secrets, i.e. you can 21 | access the plaintext string only via the string value. 22 | "] 23 | pub struct SecureString { 24 | /// Holds the decrypted string if unlock() is called. 25 | /// Don't forget to call delete if you don't need the decrypted 26 | /// string anymore. 27 | /// Use String as type to move ownership to the struct. 28 | pub string: String, 29 | // Use of Vec instead of &[u8] because specific lifetimes aren't needed 30 | encrypted_string: Vec, 31 | password: Vec, 32 | iv: Vec, 33 | } 34 | 35 | impl SecureString { 36 | /// Create a new SecureString 37 | /// The input string should already lie on the heap, i.e. the type should 38 | /// be String and not &str, otherwise a copy of the plain text string would 39 | /// lie in memory. The string will be automatically encrypted and deleted. 40 | pub fn new(string: String) -> SecureString { 41 | // Lock the string against swapping 42 | unsafe { 43 | mlock(string.as_ptr() as *const c_void, string.len() as size_t); 44 | } 45 | let mut sec_str = SecureString { 46 | string: string, 47 | encrypted_string: vec![], 48 | password: (0..32).map(|_| rand::random::()).collect(), 49 | iv: (0..16).map(|_| rand::random::()).collect(), 50 | }; 51 | unsafe { 52 | mlock(sec_str.encrypted_string.as_ptr() as *const c_void, 53 | sec_str.encrypted_string.len() as size_t); 54 | } 55 | sec_str.lock(); 56 | sec_str.delete(); 57 | sec_str 58 | } 59 | 60 | /// Overwrite the string with zeroes. Call this everytime after unlock() if you don't 61 | /// need the string anymore. 62 | pub fn delete(&self) { 63 | // Use volatile to make sure that the operation is executed. 64 | unsafe { 65 | write_array_volatile(self.string.as_ptr() as *mut u8, 66 | 0u8, 67 | self.string.len()) 68 | }; 69 | } 70 | 71 | fn lock(&mut self) { 72 | self.encrypted_string = symm::encrypt(symm::Cipher::aes_256_cbc(), 73 | &self.password, 74 | Some(&self.iv), 75 | self.string.as_bytes()).expect("Can't encrypt string!?"); 76 | } 77 | 78 | /// Unlock the string, i.e. decrypt it and make it available via the string value. 79 | /// Don't forget to call delete() if you don't need the plain text anymore. 80 | pub fn unlock(&mut self) { 81 | self.string = String::from_utf8(symm::decrypt(symm::Cipher::aes_256_cbc(), 82 | &self.password, 83 | Some(&self.iv), 84 | &self.encrypted_string).expect("Can't decrypt string!?")).unwrap(); 85 | } 86 | } 87 | 88 | // string value and encrypted_string value will be overwritten with zeroes after drop of struct 89 | impl Drop for SecureString { 90 | fn drop(&mut self) { 91 | self.delete(); 92 | unsafe { 93 | munlock(self.string.as_ptr() as *const c_void, 94 | self.string.len() as size_t); 95 | write_array_volatile(self.encrypted_string.as_ptr() as *mut u8, 96 | 0u8, 97 | self.encrypted_string.len()); 98 | munlock(self.encrypted_string.as_ptr() as *const c_void, 99 | self.encrypted_string.len() as size_t); 100 | } 101 | } 102 | } 103 | 104 | #[cfg(test)] 105 | mod tests { 106 | use super::SecureString; 107 | use std::str; 108 | use std::ptr::copy; 109 | 110 | #[test] 111 | fn test_drop() { 112 | let mut test_vec: Vec = Vec::with_capacity(4); 113 | let mut test_vec2: Vec = Vec::with_capacity(4); 114 | unsafe { 115 | test_vec.set_len(4); 116 | test_vec2.set_len(4); 117 | let str = "drop".to_string(); 118 | let mut sec_str = SecureString::new(str); 119 | let enc_str_ptr = sec_str.encrypted_string.as_mut_ptr(); 120 | let str_ptr = sec_str.string.as_mut_vec().as_mut_ptr(); 121 | drop(sec_str); 122 | copy(enc_str_ptr, test_vec.as_mut_ptr(), 4); 123 | copy(str_ptr, test_vec2.as_mut_ptr(), 4); 124 | } 125 | assert_eq!(test_vec, vec![0u8, 0u8, 0u8, 0u8]); 126 | assert_eq!(test_vec2, vec![0u8, 0u8, 0u8, 0u8]); 127 | } 128 | #[test] 129 | fn test_new() { 130 | let str = "Hello, box!".to_string(); 131 | // Ownership of str moves to SecureString <- secure input interface 132 | let mut sec_str = SecureString::new(str); 133 | sec_str.unlock(); 134 | assert_eq!(sec_str.string, "Hello, box!"); 135 | } 136 | 137 | #[test] 138 | fn test_delete() { 139 | let str = "delete".to_string(); 140 | let sec_str = SecureString::new(str); 141 | assert_eq!(sec_str.string, "\0\0\0\0\0\0"); 142 | 143 | // Test with umlauts 144 | let str = "ä".to_string(); 145 | let sec_str = SecureString::new(str); 146 | assert_eq!(sec_str.string, "\0\0"); 147 | } 148 | 149 | #[test] 150 | fn test_lock() { 151 | let str = "delete".to_string(); 152 | let mut sec_str = SecureString::new(str); 153 | 154 | assert!(str::from_utf8(&sec_str.encrypted_string) != Ok("delete")); 155 | 156 | sec_str.unlock(); 157 | assert_eq!(sec_str.string, "delete"); 158 | } 159 | 160 | #[test] 161 | fn test_encryption() { 162 | let str = "delete".to_string(); 163 | let sec_str = SecureString::new(str); 164 | 165 | let str = "delete".to_string(); 166 | let mut sec_str2 = SecureString::new(str); 167 | assert!(sec_str.encrypted_string != sec_str2.encrypted_string); 168 | 169 | sec_str2.unlock(); 170 | sec_str2.iv = sec_str.iv.clone(); 171 | sec_str2.password = sec_str.password.clone(); 172 | sec_str2.lock(); 173 | assert_eq!(sec_str.encrypted_string, sec_str2.encrypted_string); 174 | } 175 | } 176 | -------------------------------------------------------------------------------- /src/kpdb/tests_parser.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code, unused_imports)] 2 | use std::io::{Seek, SeekFrom, Read, Write}; 3 | use std::fs::File; 4 | 5 | use chrono::Datelike; 6 | use uuid::Uuid; 7 | 8 | use kpdb::crypter::Crypter; 9 | use kpdb::parser::{HeaderLoadParser, LoadParser,SaveParser}; 10 | use kpdb::v1header::V1Header; 11 | use kpdb::v1kpdb::V1Kpdb; 12 | use super::super::sec_str::SecureString; 13 | 14 | fn setup(path: String, password: Option, keyfile: Option) -> LoadParser { 15 | let mut file = File::open(path.clone()).unwrap(); 16 | let mut raw: Vec = vec![]; 17 | let _ = file.read_to_end(&mut raw); 18 | let encrypted_database = raw.split_off(124); 19 | let header_parser = HeaderLoadParser::new(raw); 20 | let header = header_parser.parse_header().unwrap(); 21 | 22 | let mut crypter = Crypter::new(password, keyfile).unwrap(); 23 | 24 | let mut decrypted_database: Vec = vec![]; 25 | match crypter.decrypt_database(&header, encrypted_database) { 26 | Ok(e) => { 27 | decrypted_database = e; 28 | } 29 | Err(_) => assert!(false), 30 | }; 31 | 32 | LoadParser::new(decrypted_database, header.num_groups, header.num_entries) 33 | } 34 | 35 | #[test] 36 | fn test_parse_groups() { 37 | let mut parser = setup("test/test_password.kdb".to_string(), 38 | Some("test".to_string()), 39 | None); 40 | 41 | let mut groups = vec![]; 42 | match parser.parse_groups() { 43 | Ok((e, _)) => { 44 | groups = e; 45 | } 46 | Err(_) => assert!(false), 47 | } 48 | 49 | assert_eq!(groups[0].borrow().id, 1); 50 | assert_eq!(groups[0].borrow().title, "Internet"); 51 | assert_eq!(groups[0].borrow().image, 1); 52 | assert_eq!(groups[0].borrow().level, 0); 53 | 54 | assert_eq!(groups[1].borrow().id, 2); 55 | assert_eq!(groups[1].borrow().title, "test"); 56 | assert_eq!(groups[1].borrow().image, 1); 57 | assert_eq!(groups[1].borrow().level, 0); 58 | assert_eq!(groups[1].borrow().creation.year(), 2014); 59 | assert_eq!(groups[1].borrow().creation.month(), 2); 60 | assert_eq!(groups[1].borrow().creation.day(), 26); 61 | } 62 | 63 | #[test] 64 | fn test_parse_entries() { 65 | let uuid = Uuid::from_bytes(&[0x0c, 0x31, 0xac, 0x94, 0x23, 0x47, 0x66, 0x36, 0xb8, 0xc0, 66 | 0x42, 0x81, 0x5e, 0x5a, 0x14, 0x60]) 67 | .unwrap(); 68 | 69 | let mut parser = setup("test/test_password.kdb".to_string(), 70 | Some("test".to_string()), 71 | None); 72 | 73 | match parser.parse_groups() { 74 | Ok((_, _)) => {} 75 | Err(_) => assert!(false), 76 | }; 77 | 78 | let mut entries = vec![]; 79 | match parser.parse_entries() { 80 | Ok(e) => { 81 | entries = e; 82 | } 83 | Err(_) => assert!(false), 84 | } 85 | 86 | entries[0].borrow_mut().username.as_mut().unwrap().unlock(); 87 | entries[0].borrow_mut().password.as_mut().unwrap().unlock(); 88 | 89 | assert_eq!(entries[0].borrow().uuid, uuid); 90 | assert_eq!(entries[0].borrow().title, "foo"); 91 | assert_eq!(entries[0].borrow().url, Some("foo".to_string())); 92 | assert_eq!(entries[0].borrow().username.as_ref().unwrap().string, "foo"); 93 | assert_eq!(entries[0].borrow().password.as_ref().unwrap().string, 94 | "DLE\"H String { 103 | let mut group = db.groups[index].borrow_mut(); 104 | let parent = group.parent.as_mut().unwrap().borrow(); 105 | parent.title.clone() 106 | } 107 | 108 | fn get_children_title(parent_index: usize, children_index: usize, db: &V1Kpdb) -> String { 109 | let group = db.groups[parent_index].borrow_mut(); 110 | let children_ref = group.children[children_index].upgrade().unwrap(); 111 | let children = children_ref.borrow(); 112 | children.title.clone() 113 | } 114 | 115 | fn get_entry_parent_title(index: usize, db: &V1Kpdb) -> String { 116 | let mut entry = db.entries[index].borrow_mut(); 117 | let group = entry.group.as_mut().unwrap().borrow(); 118 | group.title.clone() 119 | } 120 | #[test] 121 | fn test_create_group_tree() { 122 | let mut db = V1Kpdb::new("test/test_parsing.kdb".to_string(), 123 | Some("test".to_string()), 124 | None) 125 | .ok() 126 | .unwrap(); 127 | assert_eq!(db.load().is_ok(), true); 128 | 129 | assert_eq!(get_parent_title(1, &db), "Internet"); 130 | assert_eq!(get_parent_title(2, &db), "Internet"); 131 | assert_eq!(get_children_title(2, 0, &db), "22"); 132 | assert_eq!(get_children_title(2, 1, &db), "21"); 133 | assert_eq!(get_parent_title(3, &db), "11"); 134 | assert_eq!(get_parent_title(4, &db), "11"); 135 | assert_eq!(get_children_title(4, 0, &db), "32"); 136 | assert_eq!(get_children_title(4, 1, &db), "31"); 137 | assert_eq!(get_parent_title(5, &db), "21"); 138 | assert_eq!(get_parent_title(6, &db), "21"); 139 | 140 | assert_eq!(get_entry_parent_title(0, &db), "Internet"); 141 | assert_eq!(get_entry_parent_title(1, &db), "11"); 142 | assert_eq!(get_entry_parent_title(2, &db), "12"); 143 | assert_eq!(get_entry_parent_title(3, &db), "21"); 144 | assert_eq!(get_entry_parent_title(4, &db), "22"); 145 | } 146 | 147 | 148 | #[test] 149 | fn test_read_header() { 150 | let mut file = File::open("test/test_password.kdb".to_string()).unwrap(); 151 | let mut raw: Vec = vec![]; 152 | let _ = file.read_to_end(&mut raw); 153 | let _ = raw.split_off(124); 154 | let header_parser = HeaderLoadParser::new(raw); 155 | 156 | let mut header = V1Header::new(); 157 | match header_parser.parse_header() { 158 | Ok(h) => { 159 | header = h; 160 | } 161 | Err(_) => assert!(false), 162 | } 163 | 164 | assert_eq!(header.signature1, 0x9AA2D903u32); 165 | assert_eq!(header.signature2, 0xB54BFB65u32); 166 | assert_eq!(header.enc_flag & 2, 2); 167 | assert_eq!(header.version, 0x00030002u32); 168 | assert_eq!(header.num_groups, 2); 169 | assert_eq!(header.num_entries, 1); 170 | assert_eq!(header.key_transf_rounds, 150000); 171 | assert_eq!(header.final_randomseed[0], 0xB0u8); 172 | assert_eq!(header.final_randomseed[15], 0xE1u8); 173 | assert_eq!(header.iv[0], 0x15u8); 174 | assert_eq!(header.iv[15], 0xE5u8); 175 | assert_eq!(header.content_hash[0], 0xCBu8); 176 | assert_eq!(header.content_hash[15], 0x4Eu8); 177 | assert_eq!(header.transf_randomseed[0], 0x69u8); 178 | assert_eq!(header.transf_randomseed[15], 0x9Fu8); 179 | } 180 | 181 | #[test] 182 | fn test_prepare_save() { 183 | let test_1 = vec![0x01, 0x00, 0x04, 0x00, 184 | 0x00, 0x00, 0x01, 0x00, 185 | 0x00, 0x00, 0x02, 0x00, 186 | 0x09, 0x00, 0x00, 0x00, 187 | 0x49, 0x6e, 0x74, 0x65, 188 | 0x72, 0x6e, 0x65, 0x74, 189 | 0x00, 0x03, 0x00, 0x05, 190 | 0x00, 0x00, 0x00, 0x1f, 191 | 0x80, 0xae, 0xf4, 0x64, 192 | ]; 193 | let test_2 = vec![0x00, 0x00, 0x00, 0x01, 194 | 0x00, 0x00, 0x00, 0x08, 195 | 0x00, 0x02, 0x00, 0x00, 196 | 0x00, 0x00, 0x00, 0x09, 197 | 0x00, 0x04, 0x00, 0x00, 198 | 0x00, 0x00, 0x00, 0x00, 199 | 0x00, 0xff, 0xff, 0x00, 200 | 0x00, 0x00, 0x00, 0x01, 201 | 0x00, 0x04, 0x00, 0x00, 202 | ]; 203 | let test_3 = vec![0x05, 0x00, 0x05, 0x00, 204 | 0x00, 0x00, 0x1f, 0x79, 205 | 0x09, 0x71, 0x08, 0x06, 206 | 0x00, 0x05, 0x00, 0x00, 207 | 0x00, 0x2e, 0xdf, 0x39, 208 | 0x7e, 0xfb, 0x07, 0x00, 209 | 0x04, 0x00, 0x00, 0x00, 210 | 0x01, 0x00, 0x00, 0x00, 211 | 0x08, 0x00, 0x02, 0x00, 212 | ]; 213 | let test_4 = vec![0x31, 0x31, 0x00, 0x03, 214 | 0x00, 0x05, 0x00, 0x00, 215 | 0x00, 0x1f, 0x79, 0x09, 216 | 0x71, 0x04, 0x04, 0x00, 217 | 0x05, 0x00, 0x00, 0x00, 218 | 0x1f, 0x79, 0x09, 0x71, 219 | 0x0e, 0x05, 0x00, 0x05, 220 | 0x00, 0x00, 0x00, 0x1f, 221 | 0x79, 0x09, 0x71, 0x04, 222 | ]; 223 | 224 | let mut db = V1Kpdb::new("test/test_save.kdb".to_string(), 225 | Some("test".to_string()), 226 | None) 227 | .ok() 228 | .unwrap(); 229 | assert_eq!(db.load().is_ok(), true); 230 | 231 | let mut parser = SaveParser::new(); 232 | parser.prepare(&db); 233 | 234 | println!("{:?}", parser.database); 235 | assert_eq!(test_1[..], parser.database[0..36]); 236 | assert_eq!(test_2[..], parser.database[72..108]); 237 | assert_eq!(test_3[..], parser.database[144..180]); 238 | assert_eq!(test_4[..], parser.database[216..252]); 239 | } 240 | 241 | -------------------------------------------------------------------------------- /src/kpdb/tests_crypter.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code)] 2 | use std::fs::File; 3 | use std::io::Read; 4 | 5 | use kpdb::parser::HeaderLoadParser; 6 | use kpdb::crypter::Crypter; 7 | use kpdb::v1header::V1Header; 8 | use super::super::sec_str::SecureString; 9 | 10 | fn setup(path: String, 11 | password: Option, 12 | keyfile: Option) 13 | -> (Crypter, V1Header, Vec) { 14 | let mut file = File::open(path.clone()).unwrap(); 15 | let mut raw: Vec = vec![]; 16 | let _ = file.read_to_end(&mut raw); 17 | let encrypted_database = raw.split_off(124); 18 | let header_parser = HeaderLoadParser::new(raw); 19 | let header = header_parser.parse_header().unwrap(); 20 | 21 | let crypter = Crypter::new(password, keyfile).unwrap(); 22 | 23 | (crypter, header, encrypted_database) 24 | } 25 | 26 | #[test] 27 | fn test_decrypt_it_w_pass() { 28 | let test_content1: Vec = vec![0x01, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 29 | 0x02, 0x00, 0x09, 0x00, 0x00, 0x00]; 30 | let test_content2: Vec = vec![0x00, 0x05, 0x00, 0x00, 0x00, 0x1F, 0x7C, 0xB5, 0x7E, 0xFB, 31 | 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00]; 32 | 33 | let (mut crypter, header, encrypted_database) = setup("test/test_password.kdb".to_string(), 34 | Some("test".to_string()), 35 | None); 36 | 37 | let mut db_tmp: Vec = vec![]; 38 | match crypter.decrypt_database(&header, encrypted_database) { 39 | Ok(e) => db_tmp = e, 40 | Err(_) => assert!(false), 41 | }; 42 | 43 | let db_len = db_tmp.len(); 44 | let test1 = &db_tmp[0..16]; 45 | let test2 = &db_tmp[db_len - 16..db_len]; 46 | 47 | assert_eq!(test_content1, test1); 48 | assert_eq!(test_content2, test2); 49 | } 50 | 51 | #[test] 52 | fn test_decrypt_it_w_32_b_key() { 53 | let test_content1: Vec = vec![0x01, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 54 | 0x02, 0x00, 0x09, 0x00, 0x00, 0x00]; 55 | let test_content2: Vec = vec![0x00, 0x05, 0x00, 0x00, 0x00, 0x1F, 0x7C, 0xB5, 0x7E, 0xFB, 56 | 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00]; 57 | 58 | let (mut crypter, header, encrypted_database) = setup("test/test_32B_key.kdb".to_string(), 59 | None, 60 | Some("test/32Bkey".to_string())); 61 | 62 | let mut db_tmp: Vec = vec![]; 63 | match crypter.decrypt_database(&header, encrypted_database) { 64 | Ok(e) => db_tmp = e, 65 | Err(_) => assert!(false), 66 | }; 67 | 68 | let db_len = db_tmp.len(); 69 | let test1 = &db_tmp[0..16]; 70 | let test2 = &db_tmp[db_len - 16..db_len]; 71 | 72 | assert_eq!(test_content1, test1); 73 | assert_eq!(test_content2, test2); 74 | } 75 | 76 | #[test] 77 | fn test_decrypt_it_w_64_b_key() { 78 | let test_content1: Vec = vec![0x01, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 79 | 0x02, 0x00, 0x09, 0x00, 0x00, 0x00]; 80 | let test_content2: Vec = vec![0x00, 0x05, 0x00, 0x00, 0x00, 0x1F, 0x7C, 0xB5, 0x7E, 0xFB, 81 | 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00]; 82 | 83 | let (mut crypter, header, encrypted_database) = setup("test/test_64B_key.kdb".to_string(), 84 | None, 85 | Some("test/64Bkey".to_string())); 86 | 87 | let mut db_tmp: Vec = vec![]; 88 | match crypter.decrypt_database(&header, encrypted_database) { 89 | Ok(e) => db_tmp = e, 90 | Err(_) => assert!(false), 91 | }; 92 | 93 | let db_len = db_tmp.len(); 94 | let test1 = &db_tmp[0..16]; 95 | let test2 = &db_tmp[db_len - 16..db_len]; 96 | 97 | assert_eq!(test_content1, test1); 98 | assert_eq!(test_content2, test2); 99 | } 100 | 101 | #[test] 102 | fn test_decrypt_it_w_64_b_alt_key() { 103 | let test_content1: Vec = vec![0x01, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 104 | 0x02, 0x00, 0x09, 0x00, 0x00, 0x00]; 105 | let test_content2: Vec = vec![0x00, 0x05, 0x00, 0x00, 0x00, 0x1F, 0x7C, 0xB5, 0x7E, 0xFB, 106 | 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00]; 107 | 108 | let (mut crypter, header, encrypted_database) = setup("test/test_64B_alt_key.kdb".to_string(), 109 | None, 110 | Some("test/64Bkey_alt".to_string())); 111 | 112 | let mut db_tmp: Vec = vec![]; 113 | match crypter.decrypt_database(&header, encrypted_database) { 114 | Ok(e) => db_tmp = e, 115 | Err(_) => assert!(false), 116 | }; 117 | 118 | let db_len = db_tmp.len(); 119 | let test1 = &db_tmp[0..16]; 120 | let test2 = &db_tmp[db_len - 16..db_len]; 121 | 122 | assert_eq!(test_content1, test1); 123 | assert_eq!(test_content2, test2); 124 | } 125 | 126 | #[test] 127 | fn test_decrypt_it_w_128_b_key() { 128 | let test_content1: Vec = vec![0x01, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 129 | 0x02, 0x00, 0x09, 0x00, 0x00, 0x00]; 130 | let test_content2: Vec = vec![0x00, 0x05, 0x00, 0x00, 0x00, 0x1F, 0x7C, 0xB5, 0x7E, 0xFB, 131 | 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00]; 132 | 133 | let (mut crypter, header, encrypted_database) = setup("test/test_128B_key.kdb".to_string(), 134 | None, 135 | Some("test/128Bkey".to_string())); 136 | 137 | let mut db_tmp: Vec = vec![]; 138 | match crypter.decrypt_database(&header, encrypted_database) { 139 | Ok(e) => db_tmp = e, 140 | Err(_) => assert!(false), 141 | }; 142 | 143 | let db_len = db_tmp.len(); 144 | let test1 = &db_tmp[0..16]; 145 | let test2 = &db_tmp[db_len - 16..db_len]; 146 | 147 | assert_eq!(test_content1, test1); 148 | assert_eq!(test_content2, test2); 149 | } 150 | 151 | #[test] 152 | fn test_decrypt_it_w_2048_b_key() { 153 | let test_content1: Vec = vec![0x01, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 154 | 0x02, 0x00, 0x09, 0x00, 0x00, 0x00]; 155 | let test_content2: Vec = vec![0x00, 0x05, 0x00, 0x00, 0x00, 0x1F, 0x7C, 0xB5, 0x7E, 0xFB, 156 | 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00]; 157 | 158 | let (mut crypter, header, encrypted_database) = setup("test/test_2048B_key.kdb".to_string(), 159 | None, 160 | Some("test/2048Bkey".to_string())); 161 | 162 | let mut db_tmp: Vec = vec![]; 163 | match crypter.decrypt_database(&header, encrypted_database) { 164 | Ok(e) => db_tmp = e, 165 | Err(_) => assert!(false), 166 | }; 167 | 168 | let db_len = db_tmp.len(); 169 | let test1 = &db_tmp[0..16]; 170 | let test2 = &db_tmp[db_len - 16..db_len]; 171 | 172 | assert_eq!(test_content1, test1); 173 | assert_eq!(test_content2, test2); 174 | } 175 | 176 | #[test] 177 | fn test_decrypt_it_w_4096_b_key() { 178 | let test_content1: Vec = vec![0x01, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 179 | 0x02, 0x00, 0x09, 0x00, 0x00, 0x00]; 180 | let test_content2: Vec = vec![0x00, 0x05, 0x00, 0x00, 0x00, 0x1F, 0x7C, 0xB5, 0x7E, 0xFB, 181 | 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00]; 182 | 183 | let (mut crypter, header, encrypted_database) = setup("test/test_4096B_key.kdb".to_string(), 184 | None, 185 | Some("test/4096Bkey".to_string())); 186 | 187 | let mut db_tmp: Vec = vec![]; 188 | match crypter.decrypt_database(&header, encrypted_database) { 189 | Ok(e) => db_tmp = e, 190 | Err(_) => assert!(false), 191 | }; 192 | 193 | let db_len = db_tmp.len(); 194 | let test1 = &db_tmp[0..16]; 195 | let test2 = &db_tmp[db_len - 16..db_len]; 196 | 197 | assert_eq!(test_content1, test1); 198 | assert_eq!(test_content2, test2); 199 | } 200 | 201 | #[test] 202 | fn test_decrypt_it_w_both() { 203 | let test_content1: Vec = vec![0x01, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 204 | 0x02, 0x00, 0x09, 0x00, 0x00, 0x00]; 205 | let test_content2: Vec = vec![0x00, 0x05, 0x00, 0x00, 0x00, 0x1F, 0x7C, 0xB5, 0x7E, 0xFB, 206 | 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00]; 207 | 208 | let (mut crypter, header, encrypted_database) = setup("test/test_both.kdb".to_string(), 209 | Some("test".to_string()), 210 | Some("test/test_key".to_string())); 211 | 212 | let mut db_tmp: Vec = vec![]; 213 | match crypter.decrypt_database(&header, encrypted_database) { 214 | Ok(e) => db_tmp = e, 215 | Err(_) => assert!(false), 216 | }; 217 | 218 | let db_len = db_tmp.len(); 219 | let test1 = &db_tmp[0..16]; 220 | let test2 = &db_tmp[db_len - 16..db_len]; 221 | 222 | assert_eq!(test_content1, test1); 223 | assert_eq!(test_content2, test2); 224 | } 225 | -------------------------------------------------------------------------------- /src/kpdb/tests_v1kpdb.rs: -------------------------------------------------------------------------------- 1 | use chrono::{Timelike, Local, TimeZone, Datelike}; 2 | 3 | use kpdb::v1kpdb::V1Kpdb; 4 | use kpdb::v1error::V1KpdbError; 5 | 6 | #[test] 7 | fn test_new() { 8 | // No keyfile and password should give error as result 9 | let mut result = V1Kpdb::new("test/test_password.kdb".to_string(), None, None); 10 | match result { 11 | Ok(_) => assert!(false), 12 | Err(e) => assert_eq!(e, V1KpdbError::PassErr), 13 | }; 14 | 15 | // Test load at all and parameters 16 | result = V1Kpdb::new("test/test_both.kdb".to_string(), 17 | Some("test".to_string()), 18 | Some("test/test_key".to_string())); 19 | assert!(result.is_ok()); 20 | let mut db = result.ok().unwrap(); 21 | assert_eq!(db.load().is_ok(), true); 22 | assert_eq!(db.path, "test/test_both.kdb"); 23 | 24 | // Test fail of load with wrong password 25 | result = V1Kpdb::new("test/test_password.kdb".to_string(), 26 | Some("tes".to_string()), 27 | None); 28 | assert!(result.is_ok()); 29 | db = result.ok().unwrap(); 30 | match db.load() { 31 | Ok(_) => assert!(false), 32 | Err(e) => assert_eq!(e, V1KpdbError::DecryptErr), 33 | }; 34 | } 35 | 36 | #[test] 37 | fn test_save() { 38 | // if this test fails one has to copy test_password.kdb to test_save.kdb 39 | let mut result = V1Kpdb::new("test/test_save.kdb".to_string(), Some("test".to_string()), None); 40 | assert!(result.is_ok()); 41 | let mut db = result.ok().unwrap(); 42 | 43 | assert!(db.load().is_ok()); 44 | assert!(db.save(None, None, None).is_ok()); 45 | assert!(db.load().is_ok()); 46 | 47 | assert!(db.save(Some("test/new_path.kdb".to_string()), None, None).is_ok()); 48 | result = V1Kpdb::new("test/new_path.kdb".to_string(), Some("test".to_string()), None); 49 | db = result.ok().unwrap(); 50 | assert!(db.load().is_ok()); 51 | 52 | assert!(db.save(Some("test/new_pass.kdb".to_string()), Some("new".to_string()), None).is_ok()); 53 | result = V1Kpdb::new("test/new_pass.kdb".to_string(), Some("new".to_string()), None); 54 | db = result.ok().unwrap(); 55 | assert!(db.load().is_ok()); 56 | 57 | assert!(db.save(Some("test/new_keyfile.kdb".to_string()), None, Some("test/64Bkey".to_string())).is_ok()); 58 | result = V1Kpdb::new("test/new_keyfile.kdb".to_string(), Some("new".to_string()), Some("test/64Bkey".to_string())); 59 | db = result.ok().unwrap(); 60 | assert!(db.load().is_ok()); 61 | 62 | } 63 | 64 | #[test] 65 | fn test_create_group_w_title_only() { 66 | let mut result = V1Kpdb::new("test/test_password.kdb".to_string(), 67 | Some("test".to_string()), 68 | None); 69 | match result { 70 | Ok(ref mut e) => assert_eq!(e.load().is_ok(), true), 71 | Err(_) => assert!(false), 72 | }; 73 | let mut db = result.unwrap(); 74 | 75 | let num_groups_before = db.header.num_groups; 76 | 77 | assert_eq!(db.create_group("test".to_string(), None, None, None).is_ok(), 78 | true); 79 | 80 | let mut new_group = db.groups[db.groups.len() - 1].borrow_mut(); 81 | assert_eq!(new_group.title, "test"); 82 | assert_eq!((new_group.expire.year(), 83 | new_group.expire.month(), 84 | new_group.expire.day()), 85 | (2999, 12, 28)); 86 | assert_eq!((new_group.expire.hour(), 87 | new_group.expire.minute(), 88 | new_group.expire.second()), 89 | (23, 59, 59)); 90 | assert_eq!(new_group.image, 0); 91 | 92 | let parent = new_group.parent.as_mut().unwrap(); 93 | assert_eq!(parent.borrow().id, 0); 94 | 95 | assert_eq!(db.header.num_groups, num_groups_before + 1); 96 | } 97 | 98 | #[test] 99 | fn test_create_group_w_everything() { 100 | let mut result = V1Kpdb::new("test/test_parsing.kdb".to_string(), 101 | Some("test".to_string()), 102 | None); 103 | match result { 104 | Ok(ref mut e) => assert_eq!(e.load().is_ok(), true), 105 | Err(_) => assert!(false), 106 | }; 107 | let mut db = result.unwrap(); 108 | 109 | let num_groups_before = db.header.num_groups; 110 | 111 | let expire = Local.ymd(2015, 2, 28).and_hms(10, 10, 10); 112 | let parent = db.groups[1].clone(); 113 | let image = 2; 114 | 115 | assert_eq!(db.create_group("test".to_string(), Some(expire), Some(image), Some(parent)) 116 | .is_ok(), 117 | true); 118 | 119 | let mut new_group = db.groups[2].borrow_mut(); 120 | assert_eq!(new_group.title, "test"); 121 | assert_eq!((new_group.expire.year(), 122 | new_group.expire.month(), 123 | new_group.expire.day()), 124 | (2015, 2, 28)); 125 | assert_eq!(new_group.image, 2); 126 | 127 | let parent = new_group.parent.as_mut().unwrap(); 128 | assert_eq!(parent.borrow().title, "12"); 129 | 130 | assert_eq!(db.header.num_groups, num_groups_before + 1); 131 | } 132 | 133 | #[test] 134 | fn test_create_entry() { 135 | let mut result = V1Kpdb::new("test/test_password.kdb".to_string(), 136 | Some("test".to_string()), 137 | None); 138 | match result { 139 | Ok(ref mut e) => assert_eq!(e.load().is_ok(), true), 140 | Err(_) => assert!(false), 141 | }; 142 | let mut db = result.unwrap(); 143 | 144 | let num_entries_before = db.header.num_entries; 145 | let group = db.groups[0].clone(); 146 | let expire = Local.ymd(2015, 2, 28).and_hms(10, 10, 10); 147 | db.create_entry(group, 148 | "test".to_string(), 149 | Some(expire), 150 | Some(5), 151 | Some("http://foo".to_string()), 152 | Some("foo".to_string()), 153 | Some("bar".to_string()), 154 | Some("foobar".to_string())); 155 | 156 | let mut new_entry = db.entries[db.entries.len() - 1].borrow_mut(); 157 | new_entry.username.as_mut().unwrap().unlock(); 158 | new_entry.password.as_mut().unwrap().unlock(); 159 | assert_eq!(new_entry.title, "test"); 160 | assert_eq!((new_entry.expire.year(), 161 | new_entry.expire.month(), 162 | new_entry.expire.day()), 163 | (2015, 2, 28)); 164 | assert_eq!((new_entry.expire.hour(), 165 | new_entry.expire.minute(), 166 | new_entry.expire.second()), 167 | (10, 10, 10)); 168 | assert_eq!(new_entry.image, 5); 169 | assert_eq!(new_entry.url.as_ref().unwrap(), "http://foo"); 170 | assert_eq!(new_entry.comment.as_ref().unwrap(), "foo"); 171 | assert_eq!(new_entry.username.as_ref().unwrap().string, "bar"); 172 | assert_eq!(new_entry.password.as_ref().unwrap().string, "foobar"); 173 | 174 | assert_eq!(db.header.num_entries, num_entries_before + 1); 175 | } 176 | 177 | #[test] 178 | fn test_remove_group() { 179 | let mut result = V1Kpdb::new("test/test_parsing.kdb".to_string(), 180 | Some("test".to_string()), 181 | None); 182 | match result { 183 | Ok(ref mut e) => assert_eq!(e.load().is_ok(), true), 184 | Err(_) => assert!(false), 185 | }; 186 | let mut db = result.unwrap(); 187 | 188 | let group = db.groups[2].clone(); 189 | let num_groups_before = db.header.num_groups; 190 | let num_groups_in_root = db.groups[0].borrow().children.len(); 191 | let num_entries_before = db.header.num_entries; 192 | let num_entries_in_db = db.entries.len(); 193 | 194 | assert_eq!(db.remove_group(group).is_ok(), true); 195 | assert_eq!(db.groups.len() as u32, num_groups_before - 5); 196 | assert_eq!(db.header.num_groups, num_groups_before - 5); 197 | assert_eq!(db.groups[0].borrow().children.len(), num_groups_in_root - 1); 198 | assert_eq!(db.header.num_entries, num_entries_before - 3); 199 | assert_eq!(db.entries.len(), num_entries_in_db - 3); 200 | } 201 | 202 | #[test] 203 | fn test_remove_group_easy() { 204 | let mut result = V1Kpdb::new("test/test_password.kdb".to_string(), 205 | Some("test".to_string()), 206 | None); 207 | match result { 208 | Ok(ref mut e) => assert_eq!(e.load().is_ok(), true), 209 | Err(_) => assert!(false), 210 | }; 211 | let mut db = result.unwrap(); 212 | 213 | let num_groups_before = db.header.num_groups; 214 | let num_groups_in_root = db.root_group.borrow().children.len(); 215 | let num_entries_before = db.header.num_entries; 216 | let num_entries_in_db = db.entries.len(); 217 | 218 | let group = db.groups[0].clone(); 219 | assert_eq!(db.remove_group(group).is_ok(), true); 220 | assert_eq!(db.groups.len() as u32, num_groups_before - 1); 221 | assert_eq!(db.header.num_groups, num_groups_before - 1); 222 | assert_eq!(db.root_group.borrow().children.len(), 223 | num_groups_in_root - 1); 224 | assert_eq!(db.header.num_entries, num_entries_before - 1); 225 | assert_eq!(db.entries.len(), num_entries_in_db - 1); 226 | } 227 | 228 | #[test] 229 | fn test_remove_entry() { 230 | let mut result = V1Kpdb::new("test/test_password.kdb".to_string(), 231 | Some("test".to_string()), 232 | None); 233 | match result { 234 | Ok(ref mut e) => assert_eq!(e.load().is_ok(), true), 235 | Err(_) => assert!(false), 236 | }; 237 | let mut db = result.unwrap(); 238 | 239 | let entry = db.entries[0].clone(); 240 | let num_entries_before = db.header.num_entries; 241 | let num_entries_in_db = db.entries.len(); 242 | let num_entries_in_group = db.groups[0].borrow().entries.len(); 243 | 244 | assert_eq!(db.remove_entry(entry).is_ok(), true); 245 | assert_eq!(db.header.num_entries, num_entries_before - 1); 246 | assert_eq!(db.entries.len(), num_entries_in_db - 1); 247 | assert_eq!(db.groups[0].borrow().entries.len(), 248 | num_entries_in_group - 1); 249 | } 250 | -------------------------------------------------------------------------------- /src/kpdb/crypter.rs.bak: -------------------------------------------------------------------------------- 1 | use libc::{c_void, size_t}; 2 | use libc::funcs::posix88::mman; 3 | use std::old_path::posix::Path; 4 | use std::old_io::SeekStyle; 5 | use std::old_io::fs::File; 6 | use std::old_io::IoErrorKind::EndOfFile; 7 | use std::io::Write; 8 | use std::ptr; 9 | 10 | use openssl::crypto::hash::{Hasher, Type}; 11 | use openssl::crypto::symm; 12 | use rustc_serialize::hex::FromHex; 13 | 14 | use super::v1header::V1Header; 15 | use super::v1error::V1KpdbError; 16 | use super::super::sec_str::SecureString; 17 | 18 | // implements a crypter to de- and encrypt a KeePass DB 19 | pub struct Crypter { 20 | path: String, 21 | password: Option, 22 | keyfile: Option, 23 | } 24 | 25 | impl Crypter { 26 | // Decrypt the database and return the raw data as Vec 27 | pub fn new(path: String, password: Option, 28 | keyfile: Option) -> Crypter { 29 | Crypter { path: path, password: password, keyfile: keyfile } 30 | } 31 | 32 | pub fn decrypt_database(&mut self, header: &V1Header) -> Result, V1KpdbError> { 33 | let mut file = try!(File::open(&Path::new(self.path.clone())) 34 | .map_err(|_| V1KpdbError::FileErr)); 35 | try!(file.seek(124i64, SeekStyle::SeekSet) 36 | .map_err(|_| V1KpdbError::FileErr)); 37 | let crypted_database = try!(file.read_to_end() 38 | .map_err(|_| V1KpdbError::ReadErr)); 39 | 40 | // Create the key and decrypt the database finally 41 | let masterkey = match (&mut self.password, &mut self.keyfile) { 42 | // Only password provided 43 | (&mut Some(ref mut p), &mut None) => try!(Crypter::get_passwordkey(p)), 44 | // Only keyfile provided 45 | (&mut None, &mut Some(ref mut k)) => try!(Crypter::get_keyfilekey(k)), 46 | // Both provided 47 | (&mut Some(ref mut p), &mut Some(ref mut k)) => { 48 | // Get hashed keys... 49 | let passwordkey = try!(Crypter::get_passwordkey(p)); 50 | unsafe { mman::mlock(passwordkey.as_ptr() as *const c_void, 51 | passwordkey.len() as size_t); } 52 | 53 | let keyfilekey = try!(Crypter::get_keyfilekey(k)); 54 | unsafe { mman::mlock(keyfilekey.as_ptr() as *const c_void, 55 | keyfilekey.len() as size_t); } 56 | 57 | // ...and hash them together 58 | let mut hasher = Hasher::new(Type::SHA256); 59 | try!(hasher.write_all(passwordkey.as_slice()) 60 | .map_err(|_| V1KpdbError::DecryptErr)); 61 | try!(hasher.write_all(keyfilekey.as_slice()) 62 | .map_err(|_| V1KpdbError::DecryptErr)); 63 | 64 | // Zero out unneeded keys 65 | unsafe { ptr::zero_memory(passwordkey.as_ptr() as *mut c_void, 66 | passwordkey.len()); 67 | ptr::zero_memory(keyfilekey.as_ptr() as *mut c_void, 68 | keyfilekey.len()); 69 | mman::munlock(passwordkey.as_ptr() as *const c_void, 70 | passwordkey.len() as size_t); 71 | mman::munlock(keyfilekey.as_ptr() as *const c_void, 72 | keyfilekey.len() as size_t); } 73 | 74 | hasher.finish() 75 | }, 76 | (&mut None, &mut None) => return Err(V1KpdbError::PassErr), 77 | }; 78 | unsafe { mman::mlock(masterkey.as_ptr() as *const c_void, 79 | masterkey.len() as size_t); } 80 | 81 | let finalkey = try!(Crypter::transform_key(masterkey, header)); 82 | unsafe { mman::mlock(finalkey.as_ptr() as *const c_void, 83 | finalkey.len() as size_t); } 84 | 85 | let decrypted_database = Crypter::decrypt_it(finalkey, crypted_database, header); 86 | 87 | try!(Crypter::check_decryption_success(header, &decrypted_database)); 88 | try!(Crypter::check_content_hash(header, &decrypted_database)); 89 | 90 | // Prevent swapping of raw data 91 | unsafe { mman::mlock(decrypted_database.as_ptr() as *const c_void, 92 | decrypted_database.len() as size_t); } 93 | 94 | Ok(decrypted_database) 95 | } 96 | 97 | // Hash the password string to create a decryption key from that 98 | fn get_passwordkey(password: &mut SecureString) -> Result, V1KpdbError> { 99 | // unlock SecureString 100 | password.unlock(); 101 | // password.string.as_bytes() is secure as just a reference is returned 102 | let password_string = password.string.as_bytes(); 103 | 104 | let mut hasher = Hasher::new(Type::SHA256); 105 | try!(hasher.write_all(password_string) 106 | .map_err(|_| V1KpdbError::DecryptErr)); 107 | // Zero out plaintext password 108 | password.delete(); 109 | 110 | Ok(hasher.finish()) 111 | } 112 | 113 | // Get key from keyfile 114 | fn get_keyfilekey(keyfile: &mut SecureString) -> Result, V1KpdbError> { 115 | //unlock SecureString 116 | keyfile.unlock(); 117 | // keyfile.string.as_bytes() is secure as just a reference is returned 118 | let keyfile_path = keyfile.string.as_bytes(); 119 | 120 | let mut file = try!(File::open(&Path::new(keyfile_path)) 121 | .map_err(|_| V1KpdbError::FileErr)); 122 | // Zero out plaintext keyfile path 123 | keyfile.delete(); 124 | 125 | try!(file.seek(0i64, SeekStyle::SeekEnd) 126 | .map_err(|_| V1KpdbError::FileErr)); 127 | let file_size = try!(file.tell().map_err(|_| V1KpdbError::FileErr)); 128 | try!(file.seek(0i64, SeekStyle::SeekSet) 129 | .map_err(|_| V1KpdbError::FileErr)); 130 | 131 | if file_size == 32 { 132 | let mut key: Vec; 133 | key = try!(file.read_to_end().map_err(|_| V1KpdbError::ReadErr)); 134 | return Ok(key); 135 | } else if file_size == 64 { 136 | // interpret characters as encoded hex if possible (e.g. "FF" => 0xff) 137 | match file.read_to_string() { 138 | Ok(e1) => { 139 | match e1.as_slice().from_hex() { 140 | Ok(e2) => return Ok(e2), 141 | Err(_) => {}, 142 | } 143 | }, 144 | Err(_) => {}, 145 | } 146 | try!(file.seek(0i64, SeekStyle::SeekSet) 147 | .map_err(|_| V1KpdbError::FileErr)); 148 | } 149 | 150 | // Read up to 2048 bytes and hash them 151 | let mut hasher = Hasher::new(Type::SHA256); 152 | 153 | loop { 154 | let mut read_bytes = 0; 155 | let mut buf: Vec = vec![]; 156 | 157 | // We use this construct instead of file.read() 158 | // to handle EndOfFile _and_ get the number 159 | // of read bytes 160 | for _ in (0..2048) { 161 | match file.read_byte() { 162 | Ok(o) => buf.push(o), 163 | Err(e) => { 164 | if e.kind == EndOfFile { 165 | break; 166 | } else { 167 | return Err(V1KpdbError::ReadErr); 168 | } 169 | } 170 | } 171 | read_bytes += 1; 172 | } 173 | println!("{} {} {}", buf[0], buf[5], buf[10]); 174 | try!(hasher.write_all(buf.as_slice()) 175 | .map_err(|_| V1KpdbError::DecryptErr)); 176 | if read_bytes < 2048 { 177 | break; 178 | } 179 | } 180 | 181 | let key = hasher.finish(); 182 | Ok(key) 183 | } 184 | 185 | // Create the finalkey from the masterkey by encrypting it with some 186 | // random seeds from the database header and AES_ECB 187 | fn transform_key(mut masterkey: Vec, header: &V1Header) -> Result, V1KpdbError> { 188 | let crypter = symm::Crypter::new(symm::Type::AES_256_ECB); 189 | crypter.init(symm::Mode::Encrypt, 190 | header.transf_randomseed.as_slice(), vec![]); 191 | for _ in (0..header.key_transf_rounds) { 192 | masterkey = crypter.update(masterkey.as_slice()); 193 | } 194 | let mut hasher = Hasher::new(Type::SHA256); 195 | try!(hasher.write_all(masterkey.as_slice()) 196 | .map_err(|_| V1KpdbError::DecryptErr)); 197 | masterkey = hasher.finish(); 198 | 199 | let mut hasher = Hasher::new(Type::SHA256); 200 | try!(hasher.write_all(header.final_randomseed.as_slice()) 201 | .map_err(|_| V1KpdbError::DecryptErr)); 202 | try!(hasher.write_all(masterkey.as_slice()) 203 | .map_err(|_| V1KpdbError::DecryptErr)); 204 | 205 | // Zero out masterkey as it is not needed anymore 206 | unsafe { ptr::zero_memory(masterkey.as_ptr() as *mut c_void, 207 | masterkey.len()); 208 | mman::munlock(masterkey.as_ptr() as *const c_void, 209 | masterkey.len() as size_t); } 210 | 211 | Ok(hasher.finish()) 212 | } 213 | 214 | // Decrypt the raw data and return it 215 | fn decrypt_it(finalkey: Vec, 216 | crypted_database: Vec, 217 | header: &V1Header) -> Vec { 218 | let mut db_tmp = symm::decrypt(symm::Type::AES_256_CBC, 219 | finalkey.as_slice(), 220 | header.iv.clone(), 221 | crypted_database.as_slice()); 222 | 223 | // Zero out finalkey as it is not needed anymore 224 | unsafe { ptr::zero_memory(finalkey.as_ptr() as *mut c_void, 225 | finalkey.len()); 226 | mman::munlock(finalkey.as_ptr() as *const c_void, 227 | finalkey.len() as size_t); } 228 | 229 | // Delete padding from decrypted data 230 | let padding = db_tmp[db_tmp.len() - 1] as usize; 231 | let length = db_tmp.len(); 232 | 233 | // resize() is safe as just padding is dropped 234 | db_tmp.resize(length-padding, 0); 235 | db_tmp 236 | } 237 | 238 | // Check some conditions 239 | fn check_decryption_success(header: &V1Header, 240 | decrypted_content: &Vec) -> Result<(), V1KpdbError> { 241 | if (decrypted_content.len() > 2147483446) || 242 | (decrypted_content.len() == 0 && header.num_groups > 0) { 243 | return Err(V1KpdbError::DecryptErr); 244 | } 245 | Ok(()) 246 | } 247 | 248 | // Check some more conditions 249 | fn check_content_hash(header: &V1Header, 250 | decrypted_content: &Vec) -> Result<(), V1KpdbError> { 251 | let mut hasher = Hasher::new(Type::SHA256); 252 | try!(hasher.write_all(decrypted_content.as_slice()) 253 | .map_err(|_| V1KpdbError::DecryptErr)); 254 | if hasher.finish() != header.contents_hash { 255 | return Err(V1KpdbError::HashErr); 256 | } 257 | Ok(()) 258 | } 259 | } 260 | -------------------------------------------------------------------------------- /src/kpdb/v1kpdb.rs: -------------------------------------------------------------------------------- 1 | use std::cell::RefCell; 2 | use std::rc::Rc; 3 | use std::io::{Read, Write}; 4 | use std::fs::File; 5 | 6 | use chrono::{DateTime, Local}; 7 | use rand; 8 | 9 | use kpdb::GetIndex; 10 | use kpdb::crypter::Crypter; 11 | use kpdb::parser::{HeaderLoadParser, HeaderSaveParser, LoadParser, SaveParser}; 12 | use kpdb::v1error::V1KpdbError; 13 | use kpdb::v1group::V1Group; 14 | use kpdb::v1entry::V1Entry; 15 | use kpdb::v1header::V1Header; 16 | use super::super::sec_str::SecureString; 17 | 18 | #[doc = " 19 | V1Kpdb implements a KeePass v1.x database. Some notes on the file format: 20 | 21 | * Database is encrypted with AES (Twofish currently not supported by this 22 | module) with a password and/or a keyfile. 23 | * Database holds entries which describes the credentials (username, password 24 | URL...) and are sorted in groups 25 | * The groups themselves can hold subgroups 26 | * Entries have titles for better identification by the user and expiration 27 | dates to remind that the password should be changed after some period 28 | 29 | TODO: 30 | 31 | * saving 32 | * editing 33 | * use more pattern matching 34 | * usage examples 35 | * use mlock in proper places (editing) 36 | "] 37 | pub struct V1Kpdb { 38 | /// Filepath of the database 39 | pub path: String, 40 | /// Holds the header. Normally you don't need 41 | /// to manipulate this yourself 42 | pub header: V1Header, 43 | /// The groups which hold the entries 44 | pub groups: Vec>>, 45 | /// The entries of the whole database 46 | pub entries: Vec>>, 47 | /// A group which holds all groups of level 0 48 | /// as a subgroup (all groups which are not a 49 | /// subgroup of another group ) 50 | pub root_group: Rc>, 51 | // Used to de- and encrypt the database 52 | crypter: Crypter, 53 | } 54 | 55 | impl V1Kpdb { 56 | /// Call this to create a new database instance. You have to call load 57 | /// to start decrypting and parsing of an existing database! 58 | /// path is the filepath of the database, password is the database password 59 | /// and keyfile is the filepath to the keyfile. 60 | /// password should already lie on the heap as a String type and not &str 61 | /// as it will be encrypted automatically and otherwise the plaintext 62 | /// would lie in the memory though 63 | pub fn new(path: String, 64 | password: Option, 65 | keyfile: Option) 66 | -> Result { 67 | Ok(V1Kpdb { 68 | path: path, 69 | header: V1Header::new(), 70 | groups: vec![], 71 | entries: vec![], 72 | root_group: Rc::new(RefCell::new(V1Group::new())), 73 | crypter: try!(Crypter::new(password, keyfile)), 74 | }) 75 | } 76 | 77 | /// Decrypt and parse the database. 78 | pub fn load(&mut self) -> Result<(), V1KpdbError> { 79 | let (header, encrypted_database) = try!(self.read_in_file()); 80 | 81 | // First read header and decrypt the database 82 | let header_parser = HeaderLoadParser::new(header); 83 | self.header = try!(header_parser.parse_header()); 84 | try!(self.check_header()); 85 | let decrypted_database = try!(self.crypter 86 | .decrypt_database(&self.header, encrypted_database)); 87 | 88 | // Next parse groups and entries. 89 | // pos is needed to remember position after group parsing 90 | let mut parser = LoadParser::new(decrypted_database, 91 | self.header.num_groups, 92 | self.header.num_entries); 93 | let (groups, levels) = try!(parser.parse_groups()); 94 | self.groups = groups; 95 | self.entries = try!(parser.parse_entries()); 96 | parser.delete_decrypted_content(); 97 | 98 | // Now create the group tree and sort the entries to their groups 99 | try!(LoadParser::create_group_tree(self, levels)); 100 | Ok(()) 101 | } 102 | 103 | fn read_in_file(&self) -> Result<(Vec, Vec), V1KpdbError> { 104 | let mut file = try!(File::open(&self.path).map_err(|_| V1KpdbError::FileErr)); 105 | let mut raw: Vec = vec![]; 106 | try!(file.read_to_end(&mut raw).map_err(|_| V1KpdbError::ReadErr)); 107 | let encrypted_database = raw.split_off(124); 108 | Ok((raw, encrypted_database)) 109 | } 110 | 111 | fn check_header(&self) -> Result<(), V1KpdbError> { 112 | try!(self.header.check_signatures()); 113 | try!(self.header.check_enc_flag()); 114 | try!(self.header.check_version()); 115 | Ok(()) 116 | } 117 | 118 | pub fn save(&mut self, 119 | path: Option, 120 | password: Option, 121 | keyfile: Option) -> Result<(), V1KpdbError> { 122 | let mut parser = SaveParser::new(); 123 | parser.prepare(self); 124 | 125 | let mut header = self.header.clone(); 126 | header.final_randomseed = (0..16).map(|_| rand::random::()).collect(); 127 | header.iv = (0..16).map(|_| rand::random::()).collect(); 128 | header.content_hash = try!(Crypter::get_content_hash(&parser.database)); 129 | 130 | 131 | if let Some(new_password) = password { 132 | if new_password == "".to_string() { 133 | self.crypter.change_password(None); 134 | } 135 | else { 136 | self.crypter.change_password(Some(new_password)); 137 | } 138 | } 139 | 140 | if let Some(new_keyfile) = keyfile { 141 | if new_keyfile == "".to_string() { 142 | self.crypter.change_keyfile(None); 143 | } 144 | else { 145 | self.crypter.change_keyfile(Some(new_keyfile)); 146 | } 147 | } 148 | 149 | let encrypted_database = try!(self.crypter.encrypt_database(&header, parser.database)); 150 | 151 | let mut header_parser = HeaderSaveParser::new(header); 152 | let header_raw = header_parser.parse_header(); 153 | 154 | if let Some(new_path) = path { 155 | self.path = new_path 156 | } 157 | let mut file = try!(File::create(&self.path).map_err(|_| V1KpdbError::FileErr)); 158 | try!(file.write_all(&header_raw).map_err(|_| V1KpdbError::WriteErr)); 159 | try!(file.write_all(&encrypted_database).map_err(|_| V1KpdbError::WriteErr)); 160 | try!(file.flush().map_err(|_| V1KpdbError::WriteErr)); 161 | 162 | Ok(()) 163 | } 164 | 165 | /// Create a new group 166 | /// 167 | /// * title: title of the new group 168 | /// 169 | /// * expire: expiration date of the group 170 | /// None means that the group expires never which itself 171 | /// corresponds to the date 28-12-2999 23:59:59 172 | /// 173 | /// * image: an image number, used in KeePass and KeePassX for the group 174 | /// icon. None means 0 175 | /// 176 | /// * parent: a group inside the groups vector which should be the parent in 177 | /// the group tree. None means that the root group is the parent 178 | pub fn create_group(&mut self, 179 | title: String, 180 | expire: Option>, 181 | image: Option, 182 | parent: Option>>) 183 | -> Result<(), V1KpdbError> { 184 | let mut new_id: u32 = 1; 185 | for group in self.groups.iter() { 186 | let id = group.borrow().id; 187 | if id >= new_id { 188 | new_id = id + 1; 189 | } 190 | } 191 | 192 | let new_group = Rc::new(RefCell::new(V1Group::new())); 193 | new_group.borrow_mut().id = new_id; 194 | new_group.borrow_mut().title = title; 195 | new_group.borrow_mut().creation = Local::now(); 196 | new_group.borrow_mut().last_mod = Local::now(); 197 | new_group.borrow_mut().last_access = Local::now(); 198 | match expire { 199 | Some(s) => new_group.borrow_mut().expire = s, 200 | None => {} // is 12-28-2999 23:59:59 through V1Group::new 201 | } 202 | match image { 203 | Some(s) => new_group.borrow_mut().image = s, 204 | None => {} // is 0 through V1Group::new 205 | } 206 | match parent { 207 | Some(s) => { 208 | let index = try!(self.groups.get_index(&s)); 209 | new_group.borrow_mut().parent = Some(s.clone()); 210 | s.borrow_mut().children.push(Rc::downgrade(&new_group.clone())); 211 | self.groups.insert(index + 1, new_group); 212 | 213 | } 214 | None => { 215 | new_group.borrow_mut().parent = Some(self.root_group 216 | .clone()); 217 | self.root_group.borrow_mut().children.push(Rc::downgrade(&new_group.clone())); 218 | self.groups.push(new_group); 219 | } 220 | } 221 | 222 | self.header.num_groups += 1; 223 | Ok(()) 224 | } 225 | 226 | /// Create a new entry 227 | /// 228 | /// * group: group which should hold the entry 229 | /// 230 | /// * title: title of the new entry 231 | /// 232 | /// * expire: expiration date of the group 233 | /// None means that the group expires never which itself 234 | /// corresponds to the date 28-12-2999 23:59:59 235 | /// 236 | /// * image: an image number, used in KeePass and KeePassX for the group 237 | /// icon. None means 0 238 | /// 239 | /// * url: URL from where the credentials are 240 | /// 241 | /// * comment: some free-text-comment about the entry 242 | /// 243 | /// * username: username for the URL 244 | /// 245 | /// * password: password for the URL 246 | /// 247 | /// Note: username and password should be of type String at creation. If you have a 248 | /// &str which you convert into a String with to_string() the plaintext will remain 249 | /// in memory as the new created String is a copy of the original &str. If you use 250 | /// String this function call is a move so that the String remains where it was 251 | /// created. 252 | /// 253 | pub fn create_entry(&mut self, 254 | group: Rc>, 255 | title: String, 256 | expire: Option>, 257 | image: Option, 258 | url: Option, 259 | comment: Option, 260 | username: Option, 261 | password: Option) { 262 | // Automatically creates a UUID for the entry 263 | let new_entry = Rc::new(RefCell::new(V1Entry::new())); 264 | new_entry.borrow_mut().title = title; 265 | new_entry.borrow_mut().group = Some(group.clone()); 266 | group.borrow_mut().entries.push(Rc::downgrade(&new_entry.clone())); 267 | new_entry.borrow_mut().group_id = group.borrow().id; 268 | new_entry.borrow_mut().creation = Local::now(); 269 | new_entry.borrow_mut().last_mod = Local::now(); 270 | new_entry.borrow_mut().last_access = Local::now(); 271 | match expire { 272 | Some(s) => new_entry.borrow_mut().expire = s, 273 | None => {} // is 12-28-2999 23:59:59 through V1Entry::new() 274 | }; 275 | match image { 276 | Some(s) => new_entry.borrow_mut().image = s, 277 | None => {} // is 0 through V1Entry::new() 278 | } 279 | new_entry.borrow_mut().url = url; 280 | new_entry.borrow_mut().comment = comment; 281 | match username { 282 | Some(s) => new_entry.borrow_mut().username = Some(SecureString::new(s)), 283 | None => {} 284 | }; 285 | match password { 286 | Some(s) => new_entry.borrow_mut().password = Some(SecureString::new(s)), 287 | None => {} 288 | }; 289 | 290 | self.entries.push(new_entry); 291 | self.header.num_entries += 1; 292 | } 293 | 294 | /// Remove a group 295 | /// 296 | /// * group: The group to remove 297 | /// 298 | /// Note: Entries and children of the group are deleted, too. 299 | /// 300 | /// The group should be given to the function as a move. If this is done, the rc counter 301 | /// is 0 at the end of the function and therefore sensitive data is deleted correctly. 302 | pub fn remove_group(&mut self, group: Rc>) -> Result<(), V1KpdbError> { 303 | // Sensitive data (e.g. SecureString) is automatically dropped at the end of this 304 | // function as Rc is 0 then 305 | try!(self.remove_group_from_db(&group)); 306 | try!(self.remove_entries(&group)); 307 | if let Some(ref parent) = group.borrow().parent { 308 | try!(parent.borrow_mut().drop_weak_child_reference(&group)); 309 | drop(parent); 310 | } 311 | try!(self.remove_children(&group)); 312 | Ok(()) 313 | } 314 | 315 | fn remove_group_from_db(&mut self, group: &Rc>) -> Result<(), V1KpdbError> { 316 | let index = try!(self.groups.get_index(group)); 317 | let db_reference = self.groups.remove(index); 318 | drop(db_reference); 319 | self.header.num_groups -= 1; 320 | Ok(()) 321 | } 322 | 323 | fn remove_entry_from_db(&mut self, entry: &Rc>) -> Result<(), V1KpdbError> { 324 | let index = try!(self.entries.get_index(entry)); 325 | let db_reference = self.entries.remove(index); 326 | drop(db_reference); 327 | self.header.num_entries -= 1; 328 | Ok(()) 329 | } 330 | 331 | fn remove_entries(&mut self, group: &Rc>) -> Result<(), V1KpdbError> { 332 | // Clone needed to prevent thread panning through borrowing 333 | let entries = group.borrow().entries.clone(); 334 | for entry in entries { 335 | if let Some(entry_strong) = entry.upgrade() { 336 | try!(self.remove_entry(entry_strong)); 337 | } else { 338 | return Err(V1KpdbError::WeakErr); 339 | } 340 | } 341 | Ok(()) 342 | } 343 | 344 | fn remove_children(&mut self, group: &Rc>) -> Result<(), V1KpdbError> { 345 | // Clone needed to prevent thread panning through borrowing 346 | let children = group.borrow().children.clone(); 347 | for child in children { 348 | if let Some(child_strong) = child.upgrade() { 349 | try!(self.remove_group(child_strong)); 350 | } else { 351 | return Err(V1KpdbError::WeakErr); 352 | } 353 | } 354 | Ok(()) 355 | } 356 | 357 | /// Remove a group 358 | /// 359 | /// * entry: The entry to remove. 360 | /// 361 | /// Note: The entry should be given to the function as a move. If this is done, the rc counter 362 | /// is 0 at the end of the function and therefore sensitive data is deleted correctly. 363 | pub fn remove_entry(&mut self, entry: Rc>) -> Result<(), V1KpdbError> { 364 | // Sensitive data (e.g. SecureString) is automatically dropped at the end of this 365 | // function as Rc is 0 then 366 | try!(self.remove_entry_from_db(&entry)); 367 | 368 | if let Some(ref group) = entry.borrow().group { 369 | try!(group.borrow_mut().drop_weak_entry_reference(&entry)); 370 | drop(group); 371 | } 372 | Ok(()) 373 | } 374 | } 375 | -------------------------------------------------------------------------------- /src/kpdb/crypter.rs: -------------------------------------------------------------------------------- 1 | use libc::{c_void, mlock, munlock, size_t}; 2 | use std::fs::File; 3 | use std::io::{Seek, SeekFrom, Read, Write}; 4 | 5 | use openssl::hash; 6 | use openssl::symm; 7 | use rustc_serialize::hex::FromHex; 8 | 9 | use sec_str::SecureString; 10 | use common::common::write_array_volatile; 11 | use kpdb::v1error::V1KpdbError; 12 | use kpdb::v1header::V1Header; 13 | 14 | // implements a crypter to de- and encrypt a KeePass DB 15 | pub struct Crypter { 16 | password: Option, 17 | keyfile: Option, 18 | } 19 | 20 | // Sensitive data in Crypter overall 21 | // * finalkey: created in transform_key, zeroed out in en-/decrypt_raw 22 | // * masterkey: created in get_finalkey, lock depends on method, zeroed out in transform_key 23 | // * decrypted_database: 24 | // ** decryption: created in decrypt_raw, moved out of Crypted 25 | // ** encryption: created and locked outside of Crypter, zeroed out in encrypt_raw 26 | // * passwordkey: created in get_passwordkey, zeroed out in get_finalkey 27 | // * keyfilekey: created in get_keyfilekey, zeroed out in get_finalkey 28 | // * masterkey_tmp: created in get_finalkey, moved into masterkey 29 | // * password: is a reference to a SecureString and is handled correctly in get_passwordkey 30 | // * password_string: is a reference to password.string 31 | // * keyfile: is a reference to a SecureString and is handled correctly in get_keyfilekey 32 | // * key: created in get_keyfilekey, moved into keyfilekey or is zeroed out in get_keyfile_key 33 | // * decoded_key: created in get_keyfilekey, moved into keyfilekey 34 | // * buf: created and zeroed out in get_keyfilekey 35 | impl Crypter { 36 | // Decrypt the database and return the raw data as Vec 37 | // Sensitive data in this function: 38 | // 39 | // * password 40 | // * keyfile 41 | // 42 | // Both are moved out of new into SecureString 43 | pub fn new(password: Option, 44 | keyfile: Option) 45 | -> Result { 46 | match (password, keyfile) { 47 | (Some(p), None) => Ok(Crypter { password: Some(SecureString::new(p)), 48 | keyfile: None, }), 49 | (None, Some(k)) => Ok(Crypter { password: None, 50 | keyfile: Some(SecureString::new(k)), }), 51 | (Some(p), Some(k)) => Ok(Crypter { password: Some(SecureString::new(p)), 52 | keyfile: Some(SecureString::new(k)), }), 53 | (None, None) => Err(V1KpdbError::PassErr), 54 | } 55 | 56 | } 57 | 58 | // Sensitive data in this function: 59 | // 60 | // * password moved out of function 61 | // 62 | // Old password's SecureString is deleted through Drop 63 | pub fn change_password(&mut self, password: Option) -> Result<(), V1KpdbError> { 64 | // Weird construct because self.keyfile in second match 65 | // creates compiler errors due to invalid borrowing 66 | let keyfile_option = match self.keyfile { 67 | Some(_) => Some(()), 68 | None => None, 69 | }; 70 | match (password, keyfile_option) { 71 | (None, None) => return Err(V1KpdbError::PassErr), 72 | (None, Some(())) => self.password = None, 73 | (Some(p), _) => self.password = Some(SecureString::new(p)), 74 | }; 75 | 76 | Ok(()) 77 | } 78 | 79 | // Sensitive data in this function: 80 | // 81 | // * keyfile moved out of function 82 | // 83 | // Old keyfiles's SecureString is deleted through Drop 84 | pub fn change_keyfile(&mut self, keyfile: Option) -> Result<(), V1KpdbError> { 85 | // Weird construct because self.password in second match 86 | // creates compiler errors due to invalid borrowing 87 | let password_option = match self.password { 88 | Some(_) => Some(()), 89 | None => None, 90 | }; 91 | 92 | match (password_option, keyfile) { 93 | (None, None) => return Err(V1KpdbError::PassErr), 94 | (Some(()), None) => self.keyfile = None, 95 | (_, Some(k)) => self.keyfile = Some(SecureString::new(k)), 96 | }; 97 | 98 | Ok(()) 99 | } 100 | 101 | // Sensitive data in this function: 102 | // * finalkey (locked: transform_key) 103 | // * decrypted_database (locked: decrypt_raw) 104 | // 105 | // At the end of this function: 106 | // * decrypted database moved out of function 107 | // * finalkey has moved to decrypt_raw 108 | // 109 | // decrypted database is locked through decrypt_raw 110 | pub fn decrypt_database(&mut self, header: &V1Header, encrypted_database: Vec) -> Result, V1KpdbError> { 111 | let finalkey = try!(self.get_finalkey(header)); 112 | let decrypted_database = try!(Crypter::decrypt_raw(header, encrypted_database, finalkey)); 113 | try!(Crypter::check_decryption_success(header, &decrypted_database)); 114 | try!(Crypter::check_content_hash(header, &decrypted_database)); 115 | 116 | Ok(decrypted_database) 117 | } 118 | 119 | // Sensitive data in this function: 120 | // * finalkey (locked: transform_key) 121 | // * decrypted_database 122 | // 123 | // At the end of this function: 124 | // * decrypted database has moved to encrypt_raw 125 | // * finalkey has moved to encrypt_raw 126 | pub fn encrypt_database(&mut self, header: &V1Header, decrypted_database: Vec) -> Result, V1KpdbError> { 127 | let finalkey = try!(self.get_finalkey(header)); 128 | Crypter::encrypt_raw(header, decrypted_database, finalkey) 129 | } 130 | 131 | // Sensitive data in this function: 132 | // * masterkey 133 | // * passwordkey (locked: get_passwordkey) 134 | // * keyfilekey (locked: get_keyfilekey) 135 | // * finalkey (locked: transform_key) 136 | // * masterkey_tmp 137 | // 138 | // At the end of this function: 139 | // * masterkey has moved to transform_key and is locked 140 | // * passwordkey is zeroed out 141 | // * keyfilekey is zeroed out 142 | // * finalkey moved out of function 143 | // * masterkey_tmp is locked and moved into masterkey 144 | // 145 | // passwordkey and keyfilekey are locked until procession 146 | // p and k are locked through SecureString 147 | fn get_finalkey(&mut self, header: &V1Header) -> Result, V1KpdbError> { 148 | let masterkey = match (&mut self.password, &mut self.keyfile) { 149 | // Only password provided 150 | (&mut Some(ref mut p), &mut None) => try!(Crypter::get_passwordkey(p)), 151 | // Only keyfile provided 152 | (&mut None, &mut Some(ref mut k)) => try!(Crypter::get_keyfilekey(k)), 153 | // Both provided 154 | (&mut Some(ref mut p), &mut Some(ref mut k)) => { 155 | // Get hashed keys... 156 | let passwordkey = try!(Crypter::get_passwordkey(p)); 157 | 158 | let keyfilekey = try!(Crypter::get_keyfilekey(k)); 159 | 160 | // ...and hash them together 161 | let mut hasher = hash::Hasher::new(hash::MessageDigest::sha256()).expect("Can't hash passwords!?"); 162 | try!(hasher.write_all(&passwordkey) 163 | .map_err(|_| V1KpdbError::DecryptErr)); 164 | try!(hasher.write_all(&keyfilekey) 165 | .map_err(|_| V1KpdbError::DecryptErr)); 166 | 167 | let masterkey_tmp = hasher.finish2().expect("Can't hash masterkey"); 168 | // Zero out unneeded keys and lock masterkey 169 | unsafe { 170 | write_array_volatile(passwordkey.as_ptr() as *mut u8, 171 | 0u8, 172 | passwordkey.len()); 173 | write_array_volatile(keyfilekey.as_ptr() as *mut u8, 174 | 0u8, 175 | keyfilekey.len()); 176 | munlock(passwordkey.as_ptr() as *const c_void, 177 | passwordkey.len() as size_t); 178 | munlock(keyfilekey.as_ptr() as *const c_void, 179 | keyfilekey.len() as size_t); 180 | mlock(masterkey_tmp.as_ptr() as *const c_void, 181 | masterkey_tmp.len() as size_t); 182 | } 183 | masterkey_tmp.to_vec() 184 | } 185 | (&mut None, &mut None) => return Err(V1KpdbError::PassErr), 186 | }; 187 | let finalkey = try!(Crypter::transform_key(masterkey, header)); 188 | 189 | Ok(finalkey) 190 | } 191 | 192 | // Hash the password string to create a decryption key from that 193 | // Sensitive data in this function: 194 | // * password 195 | // * password_string 196 | // * passwordkey 197 | // 198 | // At the end of this function: 199 | // * password is zeroed out 200 | // * password_string is deleted (is a reference to password.string) 201 | // * passwordkey is moved out of function and locked 202 | fn get_passwordkey(password: &mut SecureString) -> Result, V1KpdbError> { 203 | password.unlock(); 204 | let password_string = password.string.as_bytes(); 205 | 206 | let mut hasher = hash::Hasher::new(hash::MessageDigest::sha256()).expect("Can't creater hasher!?"); 207 | try!(hasher.write_all(password_string) 208 | .map_err(|_| V1KpdbError::DecryptErr)); 209 | password.delete(); 210 | 211 | // hasher.finish() is a move and therefore secure 212 | let passwordkey = hasher.finish2().expect("Can't hash password!?"); 213 | unsafe { 214 | mlock(passwordkey.as_ptr() as *const c_void, 215 | passwordkey.len() as size_t); 216 | } 217 | Ok(passwordkey.to_vec()) 218 | } 219 | 220 | // Get key from keyfile 221 | // Sensitive data in this function: 222 | // * keyfile 223 | // * key 224 | // * decoded_key 225 | // * buf 226 | // * file 227 | // 228 | // At the end of this function: 229 | // * keyfile is deleted 230 | // * key has moved out of function and is locked or is deleted (file_size==64) 231 | // * decoded_key has moved out of function and is locked 232 | // * buf is deleted 233 | // * file ... TODO 234 | // 235 | // buf and key are locked during procession 236 | fn get_keyfilekey(keyfile: &mut SecureString) -> Result, V1KpdbError> { 237 | keyfile.unlock(); 238 | 239 | let mut file = try!(File::open(&keyfile.string).map_err(|_| V1KpdbError::FileErr)); 240 | // unsafe { 241 | // mlock(file.as_ptr() as *const c_void, 242 | // file.len() as size_t); 243 | // } 244 | 245 | keyfile.delete(); 246 | 247 | let file_size = try!(file.seek(SeekFrom::End(0i64)) 248 | .map_err(|_| V1KpdbError::FileErr)); 249 | try!(file.seek(SeekFrom::Start(0u64)) 250 | .map_err(|_| V1KpdbError::FileErr)); 251 | 252 | if file_size == 32 { 253 | let mut key: Vec = vec![]; 254 | try!(file.read_to_end(&mut key).map_err(|_| V1KpdbError::ReadErr)); 255 | unsafe { 256 | mlock(key.as_ptr() as *const c_void, 257 | key.len() as size_t); 258 | // intrinsics::volatile_set_memory(&file as *mut c_void, 259 | // 0u8, 260 | // mem::size_of::()); 261 | 262 | } 263 | return Ok(key); 264 | } else if file_size == 64 { 265 | // interpret characters as encoded hex if possible (e.g. "FF" => 0xff) 266 | let mut key: String = "".to_string(); 267 | unsafe { 268 | mlock(key.as_ptr() as *const c_void, 269 | key.len() as size_t); 270 | } 271 | match file.read_to_string(&mut key) { 272 | Ok(_) => { 273 | match (&key[..]).from_hex() { 274 | Ok(decoded_key) => { 275 | unsafe { 276 | // intrinsics::volatile_set_memory(&file as *mut c_void, 277 | // 0u8, 278 | // mem::size_of::()); 279 | mlock(decoded_key.as_ptr() as *const c_void, 280 | decoded_key.len() as size_t); 281 | write_array_volatile(key.as_ptr() as *mut u8, 282 | 0u8, 283 | key.len()); 284 | munlock(key.as_ptr() as *const c_void, 285 | key.len() as size_t); 286 | 287 | } 288 | return Ok(decoded_key) 289 | }, 290 | Err(_) => {} 291 | } 292 | } 293 | Err(_) => { 294 | unsafe { 295 | write_array_volatile(key.as_ptr() as *mut u8, 296 | 0u8, 297 | key.len()); 298 | munlock(key.as_ptr() as *const c_void, 299 | key.len() as size_t); 300 | 301 | } 302 | try!(file.seek(SeekFrom::Start(0u64)) 303 | .map_err(|_| V1KpdbError::FileErr)); 304 | } 305 | } 306 | } 307 | 308 | // Read up to 2048 bytes and hash them 309 | let mut hasher = hash::Hasher::new(hash::MessageDigest::sha256()).expect("Can't create hasher!?"); 310 | let mut buf: Vec = vec![]; 311 | unsafe { 312 | mlock(buf.as_ptr() as *const c_void, 313 | buf.len() as size_t); 314 | } 315 | 316 | loop { 317 | buf = vec![0; 2048]; 318 | match file.read(&mut buf[..]) { 319 | Ok(n) => { 320 | if n == 0 { 321 | break; 322 | }; 323 | buf.truncate(n); 324 | try!(hasher.write_all(&buf[..]) 325 | .map_err(|_| V1KpdbError::DecryptErr)); 326 | unsafe { 327 | write_array_volatile(buf.as_ptr() as *mut u8, 0u8, buf.len()) 328 | }; 329 | 330 | } 331 | Err(_) => { 332 | return Err(V1KpdbError::ReadErr); 333 | } 334 | } 335 | } 336 | 337 | let key = hasher.finish2().expect("Can't hash key!?"); 338 | unsafe { 339 | // intrinsics::volatile_set_memory(&file as *mut c_void, 340 | // 0u8, 341 | // mem::size_of::()); 342 | munlock(buf.as_ptr() as *const c_void, 343 | buf.len() as size_t); 344 | mlock(key.as_ptr() as *const c_void, 345 | key.len() as size_t); 346 | 347 | } 348 | 349 | Ok(key.to_vec()) 350 | } 351 | 352 | // Create the finalkey from the masterkey by encrypting it with some 353 | // random seeds from the database header and AES_ECB 354 | // 355 | // Sensitive data in this function: 356 | // * masterkey (locked: get_finalkey) 357 | // * finalkey 358 | // 359 | // At the end of this function: 360 | // * masterkey is zeroed out 361 | // * finalkey is locked and moved out of function 362 | fn transform_key(mut masterkey: Vec, header: &V1Header) -> Result, V1KpdbError> { 363 | let mut crypter = symm::Crypter::new(symm::Cipher::aes_256_ecb(), 364 | symm::Mode::Encrypt, 365 | &header.transf_randomseed, 366 | None).expect("Can't create crypter!?"); 367 | let mut transformed_key = vec![0; masterkey.len() + symm::Cipher::aes_256_cbc().block_size()]; 368 | for _ in 0..header.key_transf_rounds { 369 | let _ = crypter.update(&masterkey, &mut transformed_key); 370 | transformed_key.truncate(masterkey.len()); 371 | masterkey = transformed_key.clone(); 372 | transformed_key = vec![0; masterkey.len() + symm::Cipher::aes_256_cbc().block_size()]; 373 | } 374 | 375 | // Because rust-openssl needs a vector length of masterkey.len() + block_size 376 | 377 | let mut hasher = hash::Hasher::new(hash::MessageDigest::sha256()).expect("Could not create Hasher!?"); 378 | try!(hasher.write_all(&masterkey) 379 | .map_err(|_| V1KpdbError::DecryptErr)); 380 | masterkey = hasher.finish2().expect("Could not hash masterkey!?").to_vec(); 381 | let mut hasher = hash::Hasher::new(hash::MessageDigest::sha256()).expect("Could not create Hasher!?"); 382 | try!(hasher.write_all(&header.final_randomseed) 383 | .map_err(|_| V1KpdbError::DecryptErr)); 384 | try!(hasher.write_all(&masterkey) 385 | .map_err(|_| V1KpdbError::DecryptErr)); 386 | let finalkey = hasher.finish2().expect("Could not hash finalkey!?"); 387 | 388 | // Zero out masterkey as it is not needed anymore 389 | unsafe { 390 | write_array_volatile(masterkey.as_ptr() as *mut u8, 391 | 0u8, 392 | masterkey.len()); 393 | munlock(masterkey.as_ptr() as *const c_void, 394 | masterkey.len() as size_t); 395 | mlock(finalkey.as_ptr() as *const c_void, finalkey.len() as size_t); 396 | 397 | } 398 | 399 | Ok(finalkey.to_vec()) 400 | } 401 | 402 | // Decrypt the raw data and return it 403 | // 404 | // Sensitive data in this function: 405 | // * finalkey (locked: transform_key) 406 | // * decrypted_database 407 | // 408 | // At the end of this function: 409 | // * finalkey is deleted 410 | // * decrypted_database is locked and moved out of function 411 | // 412 | // finalkey is locked through transform_key 413 | fn decrypt_raw(header: &V1Header, encrypted_database: Vec, finalkey: Vec) -> Result, V1KpdbError> { 414 | let mut decrypted_database = try!(symm::decrypt(symm::Cipher::aes_256_cbc(), 415 | &finalkey, 416 | Some(header.iv.as_slice()), 417 | &encrypted_database).map_err(|_| V1KpdbError::DecryptErr)); 418 | 419 | // Zero out finalkey as it is not needed anymore 420 | unsafe { 421 | write_array_volatile(finalkey.as_ptr() as *mut u8, 0u8, finalkey.len()); 422 | munlock(finalkey.as_ptr() as *const c_void, finalkey.len() as size_t); 423 | } 424 | 425 | // Delete padding from decrypted data 426 | let padding = decrypted_database[decrypted_database.len() - 1] as usize; 427 | let length = decrypted_database.len(); 428 | 429 | // resize() is safe as just padding is dropped 430 | decrypted_database.resize(length - padding, 0); 431 | unsafe { 432 | mlock(decrypted_database.as_ptr() as *const c_void, decrypted_database.len() as size_t); 433 | } 434 | 435 | Ok(decrypted_database) 436 | } 437 | 438 | fn encrypt_raw(header: &V1Header, decrypted_database: Vec, finalkey: Vec) -> Result, V1KpdbError> { 439 | let encrypted_database = try!(symm::encrypt(symm::Cipher::aes_256_cbc(), 440 | &finalkey, 441 | Some(header.iv.as_slice()), 442 | &decrypted_database).map_err(|_| V1KpdbError::EncryptErr)); 443 | 444 | // Zero out finalkey as it is not needed anymore 445 | unsafe { 446 | write_array_volatile(finalkey.as_ptr() as *mut u8, 0u8, finalkey.len()); 447 | munlock(finalkey.as_ptr() as *const c_void, finalkey.len() as size_t); 448 | write_array_volatile(decrypted_database.as_ptr() as *mut u8, 0u8, decrypted_database.len()); 449 | munlock(decrypted_database.as_ptr() as *const c_void, decrypted_database.len() as size_t); 450 | } 451 | 452 | Ok(encrypted_database) 453 | } 454 | 455 | // Check some conditions 456 | // Sensitive data in this function 457 | // * decrypted_content (locked: decrypt_raw) 458 | // 459 | // At the end of the function: 460 | // * decrypted_content hasn't changed (it's a reference) 461 | fn check_decryption_success(header: &V1Header, 462 | decrypted_content: &Vec) 463 | -> Result<(), V1KpdbError> { 464 | if (decrypted_content.len() > 2147483446) || 465 | (decrypted_content.len() == 0 && header.num_groups > 0) { 466 | return Err(V1KpdbError::DecryptErr); 467 | } 468 | Ok(()) 469 | } 470 | 471 | // Sensitive data in this function 472 | // * decrypted_content (locked: decrypt_raw or ...) 473 | // 474 | // At the end of the function: 475 | // * decrypted_content hasn't changed (it's a reference) 476 | pub fn get_content_hash(decrypted_content: &Vec) -> Result, V1KpdbError> { 477 | let mut hasher = hash::Hasher::new(hash::MessageDigest::sha256()).expect("Could not create hasher!?"); 478 | try!(hasher.write_all(&decrypted_content) 479 | .map_err(|_| V1KpdbError::DecryptErr)); 480 | Ok(hasher.finish2().expect("Can't hash decrypted concent!?").to_vec()) 481 | } 482 | 483 | // Check some more conditions 484 | // Sensitive data in this function 485 | // * decrypted_content (locked: decrypt_raw) 486 | // 487 | // At the end of the function: 488 | // * decrypted_content hasn't changed (it's a reference) 489 | fn check_content_hash(header: &V1Header, 490 | decrypted_content: &Vec) 491 | -> Result<(), V1KpdbError> { 492 | let content_hash = try!(Crypter::get_content_hash(decrypted_content)); 493 | if content_hash != header.content_hash { 494 | return Err(V1KpdbError::HashErr); 495 | } 496 | Ok(()) 497 | } 498 | } 499 | -------------------------------------------------------------------------------- /src/kpdb/parser.rs: -------------------------------------------------------------------------------- 1 | use libc::{c_void, munlock, size_t}; 2 | use std::cell::{RefCell, RefMut}; 3 | use std::rc::Rc; 4 | use std::str; 5 | 6 | use chrono::{DateTime, Local, TimeZone, Datelike, Timelike}; 7 | use rustc_serialize::hex::FromHex; 8 | use uuid::Uuid; 9 | 10 | use kpdb::v1entry::V1Entry; 11 | use kpdb::v1group::V1Group; 12 | use kpdb::v1header::V1Header; 13 | use kpdb::v1kpdb::V1Kpdb; 14 | use kpdb::v1error::V1KpdbError; 15 | use sec_str::SecureString; 16 | use common::common::{slice_to_u16, slice_to_u32, u16_to_vec_u8, u32_to_vec_u8, write_array_volatile}; 17 | 18 | pub struct HeaderLoadParser { 19 | header: Vec, 20 | } 21 | 22 | impl HeaderLoadParser { 23 | pub fn new(header: Vec) -> HeaderLoadParser { 24 | HeaderLoadParser { 25 | header: header, 26 | } 27 | } 28 | 29 | pub fn parse_header(&self) -> Result { 30 | // A static method to parse a KeePass v1.x header 31 | let mut final_randomseed: Vec = vec![]; 32 | let mut iv: Vec = vec![]; 33 | let mut content_hash: Vec = vec![]; 34 | let mut transf_randomseed: Vec = vec![]; 35 | 36 | let signature1 = try!(slice_to_u32(&self.header[0..4]).map_err(|_| V1KpdbError::ConvertErr)); 37 | let signature2 = try!(slice_to_u32(&self.header[4..8]).map_err(|_| V1KpdbError::ConvertErr)); 38 | let enc_flag = try!(slice_to_u32(&self.header[8..12]).map_err(|_| V1KpdbError::ConvertErr)); 39 | let version = try!(slice_to_u32(&self.header[12..16]).map_err(|_| V1KpdbError::ConvertErr)); 40 | final_randomseed.extend(&self.header[16..32]); 41 | iv.extend(&self.header[32..48]); 42 | let num_groups = try!(slice_to_u32(&self.header[48..52]).map_err(|_| V1KpdbError::ConvertErr)); 43 | let num_entries = try!(slice_to_u32(&self.header[52..56]).map_err(|_| V1KpdbError::ConvertErr)); 44 | content_hash.extend(&self.header[56..88]); 45 | transf_randomseed.extend(&self.header[88..120]); 46 | let key_transf_rounds = try!(slice_to_u32(&self.header[120..124]).map_err(|_| V1KpdbError::ConvertErr)); 47 | 48 | 49 | Ok(V1Header { 50 | signature1: signature1, 51 | signature2: signature2, 52 | enc_flag: enc_flag, 53 | version: version, 54 | final_randomseed: final_randomseed, 55 | iv: iv, 56 | num_groups: num_groups, 57 | num_entries: num_entries, 58 | content_hash: content_hash, 59 | transf_randomseed: transf_randomseed, 60 | key_transf_rounds: key_transf_rounds, 61 | }) 62 | } 63 | } 64 | 65 | pub struct HeaderSaveParser { 66 | header: V1Header 67 | } 68 | 69 | impl HeaderSaveParser { 70 | pub fn new(header: V1Header) -> HeaderSaveParser { 71 | HeaderSaveParser { 72 | header: header 73 | } 74 | } 75 | 76 | pub fn parse_header(&mut self) -> Vec { 77 | let mut header_raw: Vec = vec![]; 78 | 79 | header_raw.append(&mut u32_to_vec_u8(self.header.signature1)); 80 | header_raw.append(&mut u32_to_vec_u8(self.header.signature2)); 81 | header_raw.append(&mut u32_to_vec_u8(self.header.enc_flag)); 82 | header_raw.append(&mut u32_to_vec_u8(self.header.version)); 83 | header_raw.append(&mut self.header.final_randomseed); 84 | header_raw.append(&mut self.header.iv); 85 | header_raw.append(&mut u32_to_vec_u8(self.header.num_groups)); 86 | header_raw.append(&mut u32_to_vec_u8(self.header.num_entries)); 87 | header_raw.append(&mut self.header.content_hash); 88 | header_raw.append(&mut self.header.transf_randomseed); 89 | header_raw.append(&mut u32_to_vec_u8(self.header.key_transf_rounds)); 90 | 91 | header_raw 92 | } 93 | } 94 | 95 | // Implements a parser to load a KeePass DB 96 | pub struct LoadParser { 97 | pos: usize, 98 | decrypted_database: Vec, 99 | num_groups: u32, 100 | num_entries: u32, 101 | } 102 | 103 | impl LoadParser { 104 | pub fn new(decrypted_database: Vec, num_groups: u32, num_entries: u32) -> LoadParser { 105 | LoadParser { 106 | pos: 0usize, 107 | decrypted_database: decrypted_database, 108 | num_groups: num_groups, 109 | num_entries: num_entries, 110 | } 111 | } 112 | 113 | // Parse the groups and put them into a vector 114 | pub fn parse_groups(&mut self) -> Result<(Vec>>, Vec), V1KpdbError> { 115 | let mut group_number: u32 = 0; 116 | let mut levels: Vec = vec![]; 117 | let mut cur_group = Rc::new(RefCell::new(V1Group::new())); 118 | let mut groups: Vec>> = vec![]; 119 | 120 | let mut field_type: u16; 121 | let mut field_size: u32; 122 | 123 | while group_number < self.num_groups { 124 | field_type = try!(slice_to_u16(&self.decrypted_database[self.pos..self.pos + 2]).map_err(|_| V1KpdbError::ConvertErr)); 125 | self.pos += 2; 126 | 127 | if self.pos > self.decrypted_database.len() { 128 | return Err(V1KpdbError::OffsetErr); 129 | } 130 | 131 | field_size = try!(slice_to_u32(&self.decrypted_database[self.pos..self.pos + 4]).map_err(|_| V1KpdbError::ConvertErr)); 132 | self.pos += 4; 133 | 134 | if self.pos > self.decrypted_database.len() { 135 | return Err(V1KpdbError::OffsetErr); 136 | } 137 | 138 | let _ = self.read_group_field(cur_group.borrow_mut(), field_type, field_size); 139 | 140 | if field_type == 0x0008 { 141 | levels.push(cur_group.borrow().level); 142 | } else if field_type == 0xFFFF { 143 | groups.push(cur_group); 144 | group_number += 1; 145 | if group_number == self.num_groups { 146 | break; 147 | }; 148 | cur_group = Rc::new(RefCell::new(V1Group::new())); 149 | } 150 | 151 | self.pos += field_size as usize; 152 | 153 | if self.pos > self.decrypted_database.len() { 154 | return Err(V1KpdbError::OffsetErr); 155 | } 156 | } 157 | 158 | Ok((groups, levels)) 159 | } 160 | 161 | // Parse the entries and put them into a vector 162 | pub fn parse_entries(&mut self) -> Result>>, V1KpdbError> { 163 | let mut entry_number: u32 = 0; 164 | let mut cur_entry = Rc::new(RefCell::new(V1Entry::new())); 165 | let mut entries: Vec>> = vec![]; 166 | 167 | let mut field_type: u16; 168 | let mut field_size: u32; 169 | 170 | while entry_number < self.num_entries { 171 | field_type = try!(slice_to_u16(&self.decrypted_database[self.pos..self.pos + 2]).map_err(|_| V1KpdbError::ConvertErr)); 172 | self.pos += 2; 173 | 174 | if self.pos > self.decrypted_database.len() { 175 | return Err(V1KpdbError::OffsetErr); 176 | } 177 | 178 | field_size = try!(slice_to_u32(&self.decrypted_database[self.pos..self.pos + 4]).map_err(|_| V1KpdbError::ConvertErr)); 179 | self.pos += 4; 180 | 181 | if self.pos > self.decrypted_database.len() { 182 | return Err(V1KpdbError::OffsetErr); 183 | } 184 | 185 | let _ = self.read_entry_field(cur_entry.borrow_mut(), field_type, field_size); 186 | 187 | if field_type == 0xFFFF { 188 | entries.push(cur_entry); 189 | entry_number += 1; 190 | if entry_number == self.num_entries { 191 | break; 192 | }; 193 | cur_entry = Rc::new(RefCell::new(V1Entry::new())); 194 | } 195 | 196 | self.pos += field_size as usize; 197 | 198 | if self.pos > self.decrypted_database.len() { 199 | return Err(V1KpdbError::OffsetErr); 200 | } 201 | } 202 | 203 | Ok(entries) 204 | } 205 | 206 | // Read a group field from the raw data by it's field type 207 | fn read_group_field(&mut self, 208 | mut group: RefMut, 209 | field_type: u16, 210 | field_size: u32) 211 | -> Result<(), V1KpdbError> { 212 | let db_slice = if field_type == 0x0002 { 213 | &self.decrypted_database[self.pos..self.pos + (field_size - 1) as usize] 214 | } else { 215 | &self.decrypted_database[self.pos..self.pos + field_size as usize] 216 | }; 217 | 218 | match field_type { 219 | 0x0001 => group.id = try!(slice_to_u32(db_slice).map_err(|_| V1KpdbError::ConvertErr)), 220 | 0x0002 => { 221 | group.title = str::from_utf8(db_slice) 222 | .unwrap_or("") 223 | .to_string() 224 | } 225 | 0x0003 => group.creation = LoadParser::get_date(db_slice), 226 | 0x0004 => group.last_mod = LoadParser::get_date(db_slice), 227 | 0x0005 => group.last_access = LoadParser::get_date(db_slice), 228 | 0x0006 => group.expire = LoadParser::get_date(db_slice), 229 | 0x0007 => group.image = try!(slice_to_u32(db_slice).map_err(|_| V1KpdbError::ConvertErr)), 230 | 0x0008 => group.level = try!(slice_to_u16(db_slice).map_err(|_| V1KpdbError::ConvertErr)), 231 | 0x0009 => group.flags = try!(slice_to_u32(db_slice).map_err(|_| V1KpdbError::ConvertErr)), 232 | _ => (), 233 | } 234 | 235 | Ok(()) 236 | } 237 | 238 | // Read an entry field from the raw data by it's field type 239 | fn read_entry_field(&mut self, 240 | mut entry: RefMut, 241 | field_type: u16, 242 | field_size: u32) 243 | -> Result<(), V1KpdbError> { 244 | let db_slice = match field_type { 245 | 0x0004...0x0008 | 0x000D => { 246 | &self.decrypted_database[self.pos..self.pos + (field_size - 1) as usize] 247 | } 248 | _ => &self.decrypted_database[self.pos..self.pos + field_size as usize], 249 | }; 250 | 251 | match field_type { 252 | 0x0001 => entry.uuid = Uuid::from_bytes(db_slice).unwrap(), 253 | 0x0002 => entry.group_id = try!(slice_to_u32(db_slice).map_err(|_| V1KpdbError::ConvertErr)), 254 | 0x0003 => entry.image = try!(slice_to_u32(db_slice).map_err(|_| V1KpdbError::ConvertErr)), 255 | 0x0004 => { 256 | entry.title = str::from_utf8(db_slice) 257 | .unwrap_or("") 258 | .to_string() 259 | } 260 | 0x0005 => { 261 | entry.url = Some(str::from_utf8(db_slice) 262 | .unwrap_or("") 263 | .to_string()) 264 | } 265 | 0x0006 => { 266 | entry.username = Some(SecureString::new(str::from_utf8(db_slice) 267 | .unwrap() 268 | .to_string())) 269 | } 270 | 0x0007 => { 271 | entry.password = Some(SecureString::new(str::from_utf8(db_slice) 272 | .unwrap() 273 | .to_string())) 274 | } 275 | 0x0008 => entry.comment = Some(str::from_utf8(db_slice).unwrap_or("").to_string()), 276 | 0x0009 => entry.creation = LoadParser::get_date(db_slice), 277 | 0x000A => entry.last_mod = LoadParser::get_date(db_slice), 278 | 0x000B => entry.last_access = LoadParser::get_date(db_slice), 279 | 0x000C => entry.expire = LoadParser::get_date(db_slice), 280 | 0x000D => { 281 | entry.binary_desc = Some(str::from_utf8(db_slice) 282 | .unwrap_or("") 283 | .to_string()) 284 | } 285 | 0x000E => { 286 | entry.binary = Some((0..field_size as usize) 287 | .map(|i| db_slice[i]) 288 | .collect()) 289 | } 290 | _ => (), 291 | } 292 | 293 | Ok(()) 294 | } 295 | 296 | // Parse a date. Taken from original KeePass-code 297 | fn get_date(date_bytes: &[u8]) -> DateTime { 298 | let dw1 = date_bytes[0] as i32; 299 | let dw2 = date_bytes[1] as i32; 300 | let dw3 = date_bytes[2] as i32; 301 | let dw4 = date_bytes[3] as i32; 302 | let dw5 = date_bytes[4] as i32; 303 | 304 | let year = (dw1 << 6) | (dw2 >> 2); 305 | let month = (((dw2 & 0x03) << 2) | (dw3 >> 6)) as u32; 306 | let day = ((dw3 >> 1) & 0x1F) as u32; 307 | let hour = (((dw3 & 0x01) << 4) | (dw4 >> 4)) as u32; 308 | let minute = (((dw4 & 0x0F) << 2) | (dw5 >> 6)) as u32; 309 | let second = (dw5 & 0x3F) as u32; 310 | 311 | Local.ymd(year, month, day).and_hms(hour, minute, second) 312 | } 313 | 314 | // Create the group tree from the level data 315 | pub fn create_group_tree(db: &mut V1Kpdb, levels: Vec) -> Result<(), V1KpdbError> { 316 | if levels[0] != 0 { 317 | return Err(V1KpdbError::TreeErr); 318 | } 319 | 320 | for i in 0..db.groups.len() { 321 | // level 0 means that the group is not a sub group. Hence add it as a children 322 | // of the root 323 | if levels[i] == 0 { 324 | db.groups[i].borrow_mut().parent = Some(db.root_group.clone()); 325 | db.root_group 326 | .borrow_mut() 327 | .children 328 | .push(Rc::downgrade(&(db.groups[i].clone()))); 329 | continue; 330 | } 331 | 332 | let mut j = i - 1; 333 | loop { 334 | // Find the first group with a lower level than the current. 335 | // That's the parent 336 | if levels[j] < levels[i] { 337 | if levels[i] - levels[j] != 1 { 338 | return Err(V1KpdbError::TreeErr); 339 | } 340 | db.groups[i].borrow_mut().parent = Some(db.groups[j].clone()); 341 | db.groups[j] 342 | .borrow_mut() 343 | .children 344 | .push(Rc::downgrade(&(db.groups[i].clone()))); 345 | break; 346 | } 347 | // It's not possible that a group which comes after another 348 | // has a lower level. Hence all following groups which have not 349 | // level 0 are a subgroup of another. 350 | if j == 0 { 351 | return Err(V1KpdbError::TreeErr); 352 | } 353 | j -= 1; 354 | } 355 | } 356 | 357 | // Sort entries to their groups 358 | // iter is secure as it is just obfuscated 359 | // pointer arithmetic to the entries vector 360 | for e in db.entries.iter() { 361 | for g in db.groups.iter() { 362 | if e.borrow().group_id == g.borrow().id { 363 | g.borrow_mut().entries.push(Rc::downgrade(&e.clone())); 364 | e.borrow_mut().group = Some(g.clone()); 365 | } 366 | } 367 | } 368 | 369 | Ok(()) 370 | } 371 | 372 | pub fn delete_decrypted_content(&mut self) { 373 | // Zero out raw data as it's not needed anymore 374 | unsafe { 375 | write_array_volatile(self.decrypted_database.as_ptr() as *mut u8, 376 | 0u8, 377 | self.decrypted_database.len()); 378 | munlock(self.decrypted_database.as_ptr() as *const c_void, 379 | self.decrypted_database.len() as size_t); 380 | } 381 | } 382 | } 383 | 384 | impl Drop for LoadParser { 385 | fn drop(&mut self) { 386 | self.delete_decrypted_content(); 387 | } 388 | } 389 | 390 | // Implements a parser to save a KeePass DB 391 | pub struct SaveParser { 392 | pub database: Vec, 393 | } 394 | 395 | impl SaveParser { 396 | pub fn new() -> SaveParser { 397 | SaveParser { 398 | database: vec![], 399 | } 400 | } 401 | 402 | pub fn prepare(&mut self, database: &V1Kpdb) { 403 | self.save_groups(database); 404 | self.save_entries(database); 405 | } 406 | 407 | fn save_groups(&mut self, 408 | database: &V1Kpdb) { 409 | let mut ret: Vec; 410 | let mut ret_len: u32; 411 | for group in &database.groups { 412 | for field_type in 1..10 as u16 { 413 | ret = SaveParser::save_group_field(group.clone(), field_type); 414 | ret_len = ret.len() as u32; 415 | if ret_len > 0 { 416 | self.database.append(&mut u16_to_vec_u8(field_type)); 417 | self.database.append(&mut u32_to_vec_u8(ret_len)); 418 | self.database.append(&mut ret); 419 | } 420 | } 421 | self.database.append(&mut vec![0xFFu8, 0xFFu8]); 422 | self.database.append(&mut vec![0u8, 0u8, 0u8, 0u8]); 423 | } 424 | } 425 | 426 | fn save_entries(&mut self, 427 | database: &V1Kpdb) { 428 | let mut ret: Vec; 429 | let mut ret_len: u32; 430 | for entry in &database.entries { 431 | for field_type in 1..15 as u16 { 432 | ret = SaveParser::save_entry_field(entry.clone(), field_type); 433 | ret_len = ret.len() as u32; 434 | if ret_len > 0 { 435 | self.database.append(&mut u16_to_vec_u8(field_type)); 436 | self.database.append(&mut u32_to_vec_u8(ret_len)); 437 | self.database.append(&mut ret); 438 | } 439 | } 440 | self.database.append(&mut vec![0xFFu8, 0xFFu8]); 441 | self.database.append(&mut vec![0u8, 0u8, 0u8, 0u8]); 442 | } 443 | } 444 | 445 | fn save_group_field(group: Rc>, 446 | field_type: u16) -> Vec { 447 | match field_type { 448 | 0x0001 => return u32_to_vec_u8(group.borrow().id), 449 | 0x0002 => { 450 | let mut title = group.borrow().title.clone().into_bytes(); 451 | title.push(0); 452 | return title; 453 | }, 454 | 0x0003 => return SaveParser::pack_date(&group.borrow().creation), 455 | 0x0004 => return SaveParser::pack_date(&group.borrow().last_mod), 456 | 0x0005 => return SaveParser::pack_date(&group.borrow().last_access), 457 | 0x0006 => return SaveParser::pack_date(&group.borrow().expire), 458 | 0x0007 => return u32_to_vec_u8(group.borrow().image), 459 | 0x0008 => return u16_to_vec_u8(group.borrow().level), 460 | 0x0009 => return u32_to_vec_u8(group.borrow().flags), 461 | _ => (), 462 | } 463 | 464 | return vec![]; 465 | } 466 | 467 | fn save_entry_field(entry: Rc>, 468 | field_type: u16) -> Vec { 469 | match field_type { 470 | 0x0001 => return (&entry.borrow().uuid.simple().to_string()[..]).from_hex().unwrap(), //Should never fail 471 | 0x0002 => return u32_to_vec_u8(entry.borrow().group_id), 472 | 0x0003 => return u32_to_vec_u8(entry.borrow().image), 473 | 0x0004 => { 474 | let mut ret = entry.borrow().title.clone().into_bytes(); 475 | ret.push(0); 476 | return ret; 477 | }, 478 | 0x0005 => { 479 | if let Some(ref url) = entry.borrow().url { 480 | let mut ret = url.clone().into_bytes(); 481 | ret.push(0); 482 | return ret; 483 | } 484 | }, 485 | 0x0006 => { 486 | if let Some(ref mut username) = entry.borrow_mut().username { 487 | username.unlock(); 488 | let mut ret = username.string.clone().into_bytes(); 489 | ret.push(0); 490 | return ret; 491 | } 492 | }, 493 | 0x0007 => { 494 | if let Some(ref mut password) = entry.borrow_mut().password { 495 | password.unlock(); 496 | let mut ret = password.string.clone().into_bytes(); 497 | ret.push(0); 498 | return ret; 499 | } 500 | }, 501 | 0x0008 => { 502 | if let Some(ref comment) = entry.borrow().comment { 503 | let mut ret = comment.clone().into_bytes(); 504 | ret.push(0); 505 | return ret; 506 | } 507 | }, 508 | 0x0009 => return SaveParser::pack_date(&entry.borrow().creation), 509 | 0x000A => return SaveParser::pack_date(&entry.borrow().last_mod), 510 | 0x000B => return SaveParser::pack_date(&entry.borrow().last_access), 511 | 0x000C => return SaveParser::pack_date(&entry.borrow().expire), 512 | 0x000D => { 513 | if let Some(ref binary_desc) = entry.borrow().binary_desc { 514 | let mut ret = binary_desc.clone().into_bytes(); 515 | ret.push(0); 516 | return ret; 517 | } 518 | }, 519 | 0x000E => { 520 | if let Some(ref binary) = entry.borrow().binary { 521 | return binary.clone(); 522 | } 523 | }, 524 | _ => (), 525 | } 526 | 527 | return vec![]; 528 | } 529 | 530 | fn pack_date(date: &DateTime) -> Vec { 531 | let year = date.year() as i32; 532 | let month = date.month() as i32; 533 | let day = date.day() as i32; 534 | let hour = date.hour() as i32; 535 | let minute = date.minute() as i32; 536 | let second = date.second() as i32; 537 | 538 | let dw1 = (0x0000FFFF & ((year>>6) & 0x0000003F)) as u8; 539 | let dw2 = (0x0000FFFF & ((year & 0x0000003F)<<2 | ((month>>2) & 0x00000003))) as u8; 540 | let dw3 = (0x0000FFFF & (((month & 0x0000003)<<6) | ((day & 0x0000001F)<<1) | ((hour>>4) & 0x00000001))) as u8; 541 | let dw4 = (0x0000FFFF & (((hour & 0x0000000F)<<4) | ((minute>>2) & 0x0000000F))) as u8; 542 | let dw5 = (0x0000FFFF & (((minute & 0x00000003)<<6) | (second & 0x0000003F))) as u8; 543 | 544 | vec![dw1, dw2, dw3, dw4, dw5] 545 | } 546 | } 547 | 548 | --------------------------------------------------------------------------------