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
22pub const KEEPASS_1_ID: u32 = 0xb54bfb65;
24pub const KEEPASS_2_ID: u32 = 0xb54bfb66;
26pub 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#[derive(Debug, Clone, PartialEq, Eq)]
37#[cfg_attr(feature = "serialization", derive(serde::Serialize))]
38pub enum DatabaseVersion {
39 KDB(u16),
41
42 KDB2(u16),
44
45 KDB3(u16),
47
48 KDB4(u16),
50}
51
52impl DatabaseVersion {
53 #[allow(clippy::indexing_slicing)] 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 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#[derive(Debug, Error)]
123#[non_exhaustive]
124pub enum DatabaseVersionParseError {
125 #[error("Unexpected end of file while reading database version")]
128 UnexpectedEof,
129
130 #[error("Invalid KDBX identifier")]
132 InvalidKDBXIdentifier,
133
134 #[error(
136 "Invalid KDBX version: {}.{}.{}",
137 version,
138 file_major_version,
139 file_minor_version
140 )]
141 InvalidKDBXVersion {
142 version: u32,
144
145 file_major_version: u32,
147
148 file_minor_version: u32,
150 },
151}