Skip to main content

keepass/format/
mod.rs

1pub(crate) mod kdb;
2pub(crate) mod kdbx3;
3pub(crate) mod kdbx4;
4
5pub(crate) mod hmac_block_stream;
6pub(crate) mod variant_dictionary;
7pub(crate) mod xml_db;
8
9#[cfg(feature = "save_kdbx4")]
10mod io;
11
12#[cfg(feature = "save_kdbx4")]
13use std::io::Write;
14
15#[cfg(feature = "save_kdbx4")]
16use byteorder::WriteBytesExt;
17use byteorder::{ByteOrder, LittleEndian};
18use thiserror::Error;
19
20const KDBX_IDENTIFIER: [u8; 4] = [0x03, 0xd9, 0xa2, 0x9a];
21
22/// Identifier for KeePass 1 format.
23pub const KEEPASS_1_ID: u32 = 0xb54bfb65;
24/// Identifier for KeePass 2 pre-release format.
25pub const KEEPASS_2_ID: u32 = 0xb54bfb66;
26/// Identifier for the latest KeePass formats.
27pub const KEEPASS_LATEST_ID: u32 = 0xb54bfb67;
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#[derive(Debug, Clone, PartialEq, Eq)]
37#[cfg_attr(feature = "serialization", derive(serde::Serialize))]
38pub enum DatabaseVersion {
39    /// KeePass 1 format
40    KDB(u16),
41
42    /// KeePass 2 pre-release format
43    KDB2(u16),
44
45    /// KeePass 2 format, version 3.x
46    KDB3(u16),
47
48    /// KeePass 2 format, version 4.x
49    KDB4(u16),
50}
51
52impl DatabaseVersion {
53    /// Parses the database version from the given byte slice, which should contain the first 12
54    /// bytes of a KDBX file.
55    #[allow(clippy::indexing_slicing)] // we check slice length at the beginnning
56    pub fn parse(data: &[u8]) -> Result<DatabaseVersion, DatabaseVersionParseError> {
57        if data.len() < DatabaseVersion::get_version_header_size() {
58            return Err(DatabaseVersionParseError::UnexpectedEof);
59        }
60
61        // check identifier
62        if data[0..4] != KDBX_IDENTIFIER {
63            return Err(DatabaseVersionParseError::InvalidKDBXIdentifier);
64        }
65
66        let version = LittleEndian::read_u32(&data[4..8]);
67        let file_minor_version = LittleEndian::read_u16(&data[8..10]);
68        let file_major_version = LittleEndian::read_u16(&data[10..12]);
69
70        let response = match version {
71            KEEPASS_1_ID => DatabaseVersion::KDB(file_minor_version),
72            KEEPASS_2_ID => DatabaseVersion::KDB2(file_minor_version),
73            KEEPASS_LATEST_ID if file_major_version == KDBX3_MAJOR_VERSION => {
74                DatabaseVersion::KDB3(file_minor_version)
75            }
76            KEEPASS_LATEST_ID if file_major_version == KDBX4_MAJOR_VERSION => {
77                DatabaseVersion::KDB4(file_minor_version)
78            }
79            _ => {
80                return Err(DatabaseVersionParseError::InvalidKDBXVersion {
81                    version,
82                    file_major_version: u32::from(file_major_version),
83                    file_minor_version: u32::from(file_minor_version),
84                })
85            }
86        };
87
88        Ok(response)
89    }
90
91    #[cfg(feature = "save_kdbx4")]
92    fn dump(&self, writer: &mut dyn Write) -> Result<(), std::io::Error> {
93        if let DatabaseVersion::KDB4(minor_version) = self {
94            writer.write_all(&crate::format::KDBX_IDENTIFIER)?;
95            writer.write_u32::<LittleEndian>(KEEPASS_LATEST_ID)?;
96            writer.write_u16::<LittleEndian>(*minor_version)?;
97            writer.write_u16::<LittleEndian>(KDBX4_MAJOR_VERSION)?;
98
99            Ok(())
100        } else {
101            panic!("DatabaseVersion::dump only supports dumping KDBX4.");
102        }
103    }
104
105    pub(crate) fn get_version_header_size() -> usize {
106        12
107    }
108}
109
110impl std::fmt::Display for DatabaseVersion {
111    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
112        match self {
113            DatabaseVersion::KDB(_) => write!(f, "KDB"),
114            DatabaseVersion::KDB2(_) => write!(f, "KDBX2"),
115            DatabaseVersion::KDB3(minor_version) => write!(f, "KDBX3.{}", minor_version),
116            DatabaseVersion::KDB4(minor_version) => write!(f, "KDBX4.{}", minor_version),
117        }
118    }
119}
120
121/// Errors that can occur during parsing of the database version from a KDBX file
122#[derive(Debug, Error)]
123#[non_exhaustive]
124pub enum DatabaseVersionParseError {
125    /// Encountered an unexpected end of file while reading the database version header, which
126    /// should be at least 12 bytes long.
127    #[error("Unexpected end of file while reading database version")]
128    UnexpectedEof,
129
130    /// The first 4 bytes of a KDBX file did not match the expected KDBX identifier
131    #[error("Invalid KDBX identifier")]
132    InvalidKDBXIdentifier,
133
134    /// The version information in the KDBX file did not match any supported database versions
135    #[error(
136        "Invalid KDBX version: {}.{}.{}",
137        version,
138        file_major_version,
139        file_minor_version
140    )]
141    InvalidKDBXVersion {
142        /// The raw version field from the KDBX header
143        version: u32,
144
145        /// The major version field from the KDBX header
146        file_major_version: u32,
147
148        /// The minor version field from the KDBX header
149        file_minor_version: u32,
150    },
151}