1use std::io::Read;
2
3use base64::{Engine as _, engine::general_purpose as base64_engine};
4use serde::Deserialize;
5use zeroize::{Zeroize, ZeroizeOnDrop};
6
7use crate::crypt::calculate_sha256;
8
9pub type KeyElement = Vec<u8>;
10pub type KeyElements = Vec<KeyElement>;
11
12#[derive(Debug, thiserror::Error)]
14pub enum DatabaseKeyError {
15 #[error("Incorrect key")]
16 IncorrectKey,
17 #[error(transparent)]
18 Cryptography(#[from] crate::crypt::CryptographyError),
19 #[error(transparent)]
20 Io(#[from] std::io::Error),
21 #[error(transparent)]
22 Xml(#[from] quick_xml::DeError),
23 #[error("Could not obtain a key from the keyfile")]
24 InvalidKeyFile,
25 #[cfg(feature = "challenge_response")]
26 #[error(transparent)]
27 ChallengeResponse(#[from] yubikey::ChallengeResponseKeyError),
28}
29
30#[cfg(feature = "challenge_response")]
31mod yubikey;
32
33#[cfg(feature = "challenge_response")]
34pub use yubikey::{ChallengeResponseKey, ChallengeResponseKeyError};
35
36fn parse_xml_keyfile(xml: &[u8]) -> Result<KeyElement, DatabaseKeyError> {
37 #[derive(Deserialize)]
38 struct KeyFileXml {
39 #[serde(rename = "Meta", default)]
40 meta: Option<KeyMetaXml>,
41 #[serde(rename = "Key", default)]
42 key: Option<KeyDataXml>,
43 }
44
45 #[derive(Deserialize)]
46 struct KeyMetaXml {
47 #[serde(rename = "Version", default)]
48 version: Option<String>,
49 }
50
51 #[derive(Deserialize)]
52 struct KeyDataXml {
53 #[serde(rename = "Data", default)]
54 data: Option<String>,
55 }
56
57 let parsed: KeyFileXml = quick_xml::de::from_reader(xml)?;
58 let key_version = parsed.meta.and_then(|meta| meta.version);
59 let key_value = parsed.key.and_then(|key| key.data).unwrap_or_default();
60
61 if key_value.is_empty() {
62 return Err(DatabaseKeyError::InvalidKeyFile);
63 }
64
65 let key_bytes = key_value.as_bytes().to_vec();
66
67 if key_version == Some("2.0".to_string()) {
68 let trimmed_key = key_value
71 .trim()
72 .replace(" ", "")
73 .replace("\n", "")
74 .replace("\t", "")
75 .replace("\r", "");
76
77 return if let Ok(key) = hex::decode(&trimmed_key) {
78 Ok(key)
79 } else {
80 Ok(key_bytes)
81 };
82 }
83
84 if let Ok(key) = base64_engine::STANDARD.decode(&key_bytes) {
86 Ok(key)
87 } else {
88 Ok(key_bytes)
89 }
90}
91
92fn parse_keyfile(buffer: &[u8]) -> KeyElement {
93 if let Ok(v) = parse_xml_keyfile(buffer) {
95 return v;
96 }
97
98 if buffer.len() == 32 {
100 return buffer.to_vec();
101 }
102
103 if buffer.len() == 64
105 && let Ok(key_bytes) = hex::decode(buffer)
106 {
107 return key_bytes;
108 }
109
110 calculate_sha256(&[buffer]).as_slice().to_vec()
112}
113
114#[derive(Debug, Clone, Default, PartialEq, Zeroize, ZeroizeOnDrop)]
116pub struct DatabaseKey {
117 password: Option<String>,
118 keyfile: Option<Vec<u8>>,
119 #[cfg(feature = "challenge_response")]
120 challenge_response_key: Option<ChallengeResponseKey>,
121 #[cfg(feature = "challenge_response")]
122 challenge_response_result: Option<KeyElement>,
123}
124
125impl DatabaseKey {
126 pub fn with_password(mut self, password: &str) -> Self {
127 self.password = Some(password.to_string());
128 self
129 }
130
131 #[cfg(feature = "utilities")]
132 pub fn with_password_from_prompt(mut self, prompt_message: &str) -> Result<Self, std::io::Error> {
133 self.password = Some(rpassword::prompt_password(prompt_message)?);
134 Ok(self)
135 }
136
137 #[cfg(all(feature = "challenge_response", feature = "utilities"))]
138 pub fn with_hmac_sha1_secret_from_prompt(mut self, prompt_message: &str) -> Result<Self, std::io::Error> {
139 self.challenge_response_key = Some(ChallengeResponseKey::LocalChallenge(rpassword::prompt_password(prompt_message)?));
140 Ok(self)
141 }
142
143 pub fn with_keyfile(mut self, keyfile: &mut dyn Read) -> Result<Self, std::io::Error> {
149 let mut buf = Vec::new();
150 keyfile.read_to_end(&mut buf)?;
151
152 self.keyfile = Some(buf);
153
154 Ok(self)
155 }
156
157 #[cfg(feature = "challenge_response")]
158 pub fn with_challenge_response_key(mut self, challenge_response_key: ChallengeResponseKey) -> Self {
159 self.challenge_response_key = Some(challenge_response_key);
160 self
161 }
162
163 #[cfg(feature = "challenge_response")]
164 pub fn perform_challenge(mut self, kdf_seed: &[u8]) -> Result<Self, DatabaseKeyError> {
165 if let Some(challenge_response_key) = &self.challenge_response_key {
166 let response = challenge_response_key.perform_challenge(kdf_seed)?;
167 self.challenge_response_result = Some(response);
168 }
169
170 Ok(self)
171 }
172
173 pub fn new() -> Self {
174 DatabaseKey::default()
175 }
176
177 pub(crate) fn get_key_elements(&self) -> Result<KeyElements, DatabaseKeyError> {
178 let mut out = Vec::new();
179
180 if let Some(p) = &self.password {
181 out.push(calculate_sha256(&[p.as_bytes()]).to_vec());
182 }
183
184 if let Some(ref f) = self.keyfile {
185 out.push(parse_keyfile(f));
186 }
187
188 if out.is_empty() {
189 return Err(DatabaseKeyError::IncorrectKey);
190 }
191
192 #[cfg(feature = "challenge_response")]
193 if let Some(result) = &self.challenge_response_result {
194 out.push(calculate_sha256(&[result]).as_slice().to_vec());
195 } else if self.challenge_response_key.is_some() {
196 return Err(DatabaseKeyError::ChallengeResponse(ChallengeResponseKeyError::NotPerformed));
197 }
198
199 Ok(out)
200 }
201
202 pub fn is_empty(&self) -> bool {
204 if self.password.is_some() || self.keyfile.is_some() {
205 return false;
206 }
207 #[cfg(feature = "challenge_response")]
208 if self.challenge_response_key.is_some() {
209 return false;
210 }
211 true
212 }
213}
214
215#[cfg(test)]
216mod key_tests {
217
218 use crate::error::DatabaseKeyError;
219
220 use super::DatabaseKey;
221
222 #[test]
223 fn test_key() -> Result<(), DatabaseKeyError> {
224 let ke = DatabaseKey::new().with_password("asdf").get_key_elements()?;
225 assert_eq!(ke.len(), 1);
226
227 let ke = DatabaseKey::new()
228 .with_keyfile(&mut "bare-key-file".as_bytes())?
229 .get_key_elements()?;
230 assert_eq!(ke.len(), 1);
231
232 let ke = DatabaseKey::new()
233 .with_keyfile(&mut "0123456789ABCDEF0123456789ABCDEF".as_bytes())?
234 .get_key_elements()?;
235 assert_eq!(ke.len(), 1);
236
237 let ke = DatabaseKey::new()
238 .with_password("asdf")
239 .with_keyfile(&mut "bare-key-file".as_bytes())?
240 .get_key_elements()?;
241 assert_eq!(ke.len(), 2);
242
243 let ke = DatabaseKey::new()
244 .with_keyfile(&mut "<KeyFile><Key><Data>0!23456789ABCDEF0123456789ABCDEF</Data></Key></KeyFile>".as_bytes())?
245 .get_key_elements()?;
246 assert_eq!(ke.len(), 1);
247
248 let ke = DatabaseKey::new()
249 .with_keyfile(&mut "<KeyFile><Key><Data>NXyYiJMHg3ls+eBmjbAjWec9lcOToJiofbhNiFMTJMw=</Data></Key></KeyFile>".as_bytes())?
250 .get_key_elements()?;
251 assert_eq!(ke.len(), 1);
252
253 let xml_keyfile_v2 = r###"
254 <?xml version="1.0" encoding="utf-8"?>
255 <KeyFile>
256 <Meta>
257 <Version>2.0</Version>
258 </Meta>
259 <Key>
260 <Data Hash="A65F0C2D">
261 36057B1C 35037FD9 62257893 C0A22403
262 EE3F8FBB 504D9981 08B821CB 00D28F89
263 </Data>
264 </Key>
265 </KeyFile>
266 "###;
267 let ke = DatabaseKey::new()
268 .with_keyfile(&mut xml_keyfile_v2.trim().as_bytes())?
269 .get_key_elements()?;
270 assert_eq!(ke.len(), 1);
271
272 let ke = DatabaseKey::new()
274 .with_keyfile(&mut "<Not><A><KeyFile></KeyFile></A></Not>".as_bytes())?
275 .get_key_elements()?;
276
277 assert_eq!(ke.len(), 1);
278
279 assert!(
280 DatabaseKey {
281 password: None,
282 keyfile: None,
283 #[cfg(feature = "challenge_response")]
284 challenge_response_key: None,
285 #[cfg(feature = "challenge_response")]
286 challenge_response_result: None,
287 }
288 .get_key_elements()
289 .is_err()
290 );
291
292 Ok(())
293 }
294}