Skip to main content

keepass_ng/format/
mod.rs

1pub(crate) mod kdb;
2pub(crate) mod kdbx3;
3pub(crate) mod kdbx4;
4pub(crate) mod kdbx_header_field_id;
5
6pub(crate) mod hmac_block_stream;
7pub(crate) mod variant_dictionary;
8pub(crate) mod xml_db;
9
10#[cfg(feature = "save_kdbx4")]
11mod io;
12
13#[cfg(feature = "save_kdbx4")]
14use std::io::Write;
15
16#[cfg(feature = "save_kdbx4")]
17use byteorder::WriteBytesExt;
18use byteorder::{ByteOrder, LittleEndian};
19
20const KDBX_IDENTIFIER: [u8; 4] = [0x03, 0xd9, 0xa2, 0x9a];
21
22/// Identifier for `KeePass` 1 format.
23pub const KEEPASS_1_ID: u32 = 0xb54b_fb65;
24/// Identifier for `KeePass` 2 pre-release format.
25pub const KEEPASS_2_ID: u32 = 0xb54b_fb66;
26/// Identifier for the latest `KeePass` formats.
27pub const KEEPASS_LATEST_ID: u32 = 0xb54b_fb67;
28
29pub const KDBX3_MAJOR_VERSION: u16 = 3;
30pub const KDBX4_MAJOR_VERSION: u16 = 4;
31
32pub const KDBX4_CURRENT_MINOR_VERSION: u16 = 1;
33
34/// Supported KDB database versions, with the associated
35/// minor version.
36#[allow(clippy::upper_case_acronyms)]
37#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
38#[cfg_attr(feature = "serialization", derive(serde::Serialize))]
39pub enum DatabaseVersion {
40    KDB(u16),
41    KDB2(u16),
42    KDB3(u16),
43    KDB4(u16),
44}
45
46impl DatabaseVersion {
47    pub fn parse(data: &[u8]) -> Result<DatabaseVersion, DatabaseVersionParseError> {
48        if data.len() < DatabaseVersion::get_version_header_size() {
49            return Err(DatabaseVersionParseError::UnexpectedEof);
50        }
51
52        // check identifier
53        if data.get(0..4) != Some(&KDBX_IDENTIFIER) {
54            return Err(DatabaseVersionParseError::InvalidKDBXIdentifier);
55        }
56
57        let version = data.get(4..8).map_or(0, LittleEndian::read_u32);
58        let file_minor_version = data.get(8..10).map_or(0, LittleEndian::read_u16);
59        let file_major_version = data.get(10..12).map_or(0, LittleEndian::read_u16);
60
61        let response = match version {
62            KEEPASS_1_ID => DatabaseVersion::KDB(file_minor_version),
63            KEEPASS_2_ID => DatabaseVersion::KDB2(file_minor_version),
64            KEEPASS_LATEST_ID if file_major_version == KDBX3_MAJOR_VERSION => DatabaseVersion::KDB3(file_minor_version),
65            KEEPASS_LATEST_ID if file_major_version == KDBX4_MAJOR_VERSION => DatabaseVersion::KDB4(file_minor_version),
66            _ => {
67                return Err(DatabaseVersionParseError::InvalidKDBXVersion {
68                    version,
69                    file_major_version: u32::from(file_major_version),
70                    file_minor_version: u32::from(file_minor_version),
71                });
72            }
73        };
74
75        Ok(response)
76    }
77
78    #[cfg(feature = "save_kdbx4")]
79    fn dump(&self, writer: &mut dyn Write) -> Result<(), std::io::Error> {
80        if let DatabaseVersion::KDB4(minor_version) = self {
81            _ = writer.write(&crate::format::KDBX_IDENTIFIER)?;
82            writer.write_u32::<LittleEndian>(KEEPASS_LATEST_ID)?;
83            writer.write_u16::<LittleEndian>(*minor_version)?;
84            writer.write_u16::<LittleEndian>(KDBX4_MAJOR_VERSION)?;
85
86            Ok(())
87        } else {
88            panic!("DatabaseVersion::dump only supports dumping KDBX4.");
89        }
90    }
91
92    pub(crate) fn get_version_header_size() -> usize {
93        12
94    }
95}
96
97impl std::fmt::Display for DatabaseVersion {
98    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
99        match self {
100            DatabaseVersion::KDB(_) => write!(f, "KDB"),
101            DatabaseVersion::KDB2(_) => write!(f, "KDBX2"),
102            DatabaseVersion::KDB3(minor_version) => write!(f, "KDBX3.{minor_version}"),
103            DatabaseVersion::KDB4(minor_version) => write!(f, "KDBX4.{minor_version}"),
104        }
105    }
106}
107
108#[derive(Debug, thiserror::Error)]
109pub enum DatabaseVersionParseError {
110    #[error("Unexpected end of file while reading database version")]
111    UnexpectedEof,
112
113    #[error("Invalid KDBX identifier")]
114    InvalidKDBXIdentifier,
115
116    #[error("Invalid KDBX version: {}.{}.{}", version, file_major_version, file_minor_version)]
117    InvalidKDBXVersion {
118        version: u32,
119        file_major_version: u32,
120        file_minor_version: u32,
121    },
122}