1use std::io::Read;
2
3use base64::{engine::general_purpose as base64_engine, Engine as _};
4use quick_xml::{encoding::EncodingError, events::Event, reader::Reader};
5use thiserror::Error;
6use zeroize::{Zeroize, ZeroizeOnDrop};
7
8use crate::crypt::calculate_sha256;
9
10pub type KeyElement = Vec<u8>;
11pub type KeyElements = Vec<KeyElement>;
12
13#[cfg(feature = "challenge_response")]
14mod yubikey;
15
16#[cfg(feature = "challenge_response")]
17pub use yubikey::{ChallengeResponseKey, ChallengeResponseKeyError};
18
19fn parse_xml_keyfile(xml: &[u8]) -> Result<KeyElement, ParseXmlKeyFileError> {
20 let mut tag_stack = Vec::new();
21
22 let mut key_version: Option<String> = None;
23 let mut key_value: Option<String> = None;
24
25 let mut reader = Reader::from_reader(xml);
26 let mut buf = Vec::new();
27
28 loop {
29 match reader.read_event_into(&mut buf)? {
30 Event::Eof => break,
31
32 Event::Start(e) => {
33 tag_stack.push(String::from_utf8_lossy(e.name().as_ref()).to_string());
34 }
35
36 Event::End(_) => {
37 tag_stack.pop();
38 }
39
40 Event::Text(e) => {
41 let s = e.decode()?.into_owned();
42
43 if tag_stack == ["KeyFile", "Meta", "Version"] {
44 key_version = Some(s);
45 continue;
46 }
47
48 if tag_stack == ["KeyFile", "Key", "Data"] {
49 key_value = Some(s);
50 continue;
51 }
52 }
53
54 _ => (),
55 }
56 }
57
58 let key_value = key_value.ok_or(ParseXmlKeyFileError::EmptyKey)?;
59
60 let key_bytes = key_value.as_bytes().to_vec();
61
62 if key_version == Some("2.0".to_string()) {
63 let trimmed_key = key_value
66 .trim()
67 .replace(" ", "")
68 .replace("\n", "")
69 .replace("\t", "")
70 .replace("\r", "");
71
72 return if let Ok(key) = hex::decode(&trimmed_key) {
73 Ok(key)
74 } else {
75 Ok(key_bytes)
76 };
77 }
78
79 if let Ok(key) = base64_engine::STANDARD.decode(&key_bytes) {
81 Ok(key)
82 } else {
83 Ok(key_bytes)
84 }
85}
86
87#[derive(Debug, Error)]
89#[non_exhaustive]
90pub enum ParseXmlKeyFileError {
91 #[error("The XML keyfile is missing a key data element")]
93 EmptyKey,
94
95 #[error(transparent)]
97 Encoding(#[from] EncodingError),
98
99 #[error(transparent)]
101 Xml(#[from] quick_xml::Error),
102}
103
104fn parse_keyfile(buffer: &[u8]) -> Result<KeyElement, DatabaseKeyError> {
105 if let Ok(v) = parse_xml_keyfile(buffer) {
107 return Ok(v);
108 }
109
110 if buffer.len() == 32 {
112 return Ok(buffer.to_vec());
113 }
114
115 if buffer.len() == 64 {
117 if let Ok(key_bytes) = hex::decode(buffer) {
118 return Ok(key_bytes);
119 }
120 }
121
122 Ok(calculate_sha256(&[buffer]).as_slice().to_vec())
124}
125
126#[derive(Debug, Clone, Default, PartialEq, Zeroize, ZeroizeOnDrop)]
128pub struct DatabaseKey {
129 password: Option<String>,
130 keyfile: Option<Vec<u8>>,
131 #[cfg(feature = "challenge_response")]
132 challenge_response_key: Option<ChallengeResponseKey>,
133 #[cfg(feature = "challenge_response")]
134 challenge_response_result: Option<KeyElement>,
135}
136
137impl DatabaseKey {
138 pub fn with_password(mut self, password: &str) -> Self {
140 self.password = Some(password.to_string());
141 self
142 }
143
144 #[cfg(feature = "utilities")]
146 pub fn with_password_from_prompt(mut self, prompt_message: &str) -> Result<Self, std::io::Error> {
147 self.password = Some(rpassword::prompt_password(prompt_message)?);
148 Ok(self)
149 }
150
151 #[cfg(all(feature = "challenge_response", feature = "utilities"))]
154 pub fn with_hmac_sha1_secret_from_prompt(mut self, prompt_message: &str) -> Result<Self, std::io::Error> {
155 self.challenge_response_key = Some(ChallengeResponseKey::LocalChallenge(rpassword::prompt_password(
156 prompt_message,
157 )?));
158 Ok(self)
159 }
160
161 pub fn with_keyfile(mut self, keyfile: &mut dyn Read) -> Result<Self, std::io::Error> {
167 let mut buf = Vec::new();
168 keyfile.read_to_end(&mut buf)?;
169
170 self.keyfile = Some(buf);
171
172 Ok(self)
173 }
174
175 #[cfg(feature = "challenge_response")]
177 pub fn with_challenge_response_key(mut self, challenge_response_key: ChallengeResponseKey) -> Self {
178 self.challenge_response_key = Some(challenge_response_key);
179 self
180 }
181
182 #[cfg(feature = "challenge_response")]
185 pub fn perform_challenge(mut self, kdf_seed: &[u8]) -> Result<Self, DatabaseKeyError> {
186 if let Some(challenge_response_key) = &self.challenge_response_key {
187 let response = challenge_response_key.perform_challenge(kdf_seed)?;
188 self.challenge_response_result = Some(response);
189 }
190
191 Ok(self)
192 }
193
194 pub fn new() -> Self {
196 Default::default()
197 }
198
199 pub(crate) fn get_key_elements(&self) -> Result<KeyElements, DatabaseKeyError> {
200 let mut out = Vec::new();
201
202 if let Some(p) = &self.password {
203 out.push(calculate_sha256(&[p.as_bytes()]).to_vec());
204 }
205
206 if let Some(ref f) = self.keyfile {
207 out.push(parse_keyfile(f)?);
208 }
209
210 if out.is_empty() {
211 return Err(DatabaseKeyError::EmptyKey);
212 }
213
214 #[cfg(feature = "challenge_response")]
215 if let Some(result) = &self.challenge_response_result {
216 out.push(calculate_sha256(&[result]).as_slice().to_vec());
217 } else if self.challenge_response_key.is_some() {
218 return Err(DatabaseKeyError::ChallengeResponse(
219 crate::key::yubikey::ChallengeResponseKeyError::NotPerformed,
220 ));
221 }
222
223 Ok(out)
224 }
225
226 pub fn is_empty(&self) -> bool {
228 if self.password.is_some() || self.keyfile.is_some() {
229 return false;
230 }
231 #[cfg(feature = "challenge_response")]
232 if self.challenge_response_key.is_some() {
233 return false;
234 }
235 true
236 }
237}
238
239#[derive(Debug, Error)]
241#[non_exhaustive]
242pub enum DatabaseKeyError {
243 #[error("The key contains no components")]
245 EmptyKey,
246
247 #[error("Incorrect key")]
249 IncorrectKey,
250
251 #[error("I/O error reading keyfile: {0}")]
253 Io(#[from] std::io::Error),
254
255 #[error("XML error reading keyfile: {0}")]
257 Xml(#[from] quick_xml::Error),
258
259 #[error("Invalid keyfile format")]
261 InvalidKeyFile,
262
263 #[cfg(feature = "challenge_response")]
265 #[error("Challenge-response key error: {0}")]
266 ChallengeResponse(#[from] crate::key::yubikey::ChallengeResponseKeyError),
267}
268
269#[cfg(test)]
270mod key_tests {
271
272 use super::{DatabaseKey, DatabaseKeyError};
273
274 #[test]
275 fn test_key() -> Result<(), DatabaseKeyError> {
276 let ke = DatabaseKey::new().with_password("asdf").get_key_elements()?;
277 assert_eq!(ke.len(), 1);
278
279 let ke = DatabaseKey::new()
280 .with_keyfile(&mut "bare-key-file".as_bytes())?
281 .get_key_elements()?;
282 assert_eq!(ke.len(), 1);
283
284 let ke = DatabaseKey::new()
285 .with_keyfile(&mut "0123456789ABCDEF0123456789ABCDEF".as_bytes())?
286 .get_key_elements()?;
287 assert_eq!(ke.len(), 1);
288
289 let ke = DatabaseKey::new()
290 .with_password("asdf")
291 .with_keyfile(&mut "bare-key-file".as_bytes())?
292 .get_key_elements()?;
293 assert_eq!(ke.len(), 2);
294
295 let ke = DatabaseKey::new()
296 .with_keyfile(
297 &mut "<KeyFile><Key><Data>0!23456789ABCDEF0123456789ABCDEF</Data></Key></KeyFile>".as_bytes(),
298 )?
299 .get_key_elements()?;
300 assert_eq!(ke.len(), 1);
301
302 let ke = DatabaseKey::new()
303 .with_keyfile(
304 &mut "<KeyFile><Key><Data>NXyYiJMHg3ls+eBmjbAjWec9lcOToJiofbhNiFMTJMw=</Data></Key></KeyFile>"
305 .as_bytes(),
306 )?
307 .get_key_elements()?;
308 assert_eq!(ke.len(), 1);
309
310 let xml_keyfile_v2 = r###"
311 <?xml version="1.0" encoding="utf-8"?>
312 <KeyFile>
313 <Meta>
314 <Version>2.0</Version>
315 </Meta>
316 <Key>
317 <Data Hash="A65F0C2D">
318 36057B1C 35037FD9 62257893 C0A22403
319 EE3F8FBB 504D9981 08B821CB 00D28F89
320 </Data>
321 </Key>
322 </KeyFile>
323 "###;
324 let ke = DatabaseKey::new()
325 .with_keyfile(&mut xml_keyfile_v2.trim().as_bytes())?
326 .get_key_elements()?;
327 assert_eq!(ke.len(), 1);
328
329 let ke = DatabaseKey::new()
331 .with_keyfile(&mut "<Not><A><KeyFile></KeyFile></A></Not>".as_bytes())?
332 .get_key_elements()?;
333
334 assert_eq!(ke.len(), 1);
335
336 assert!(DatabaseKey {
337 password: None,
338 keyfile: None,
339 #[cfg(feature = "challenge_response")]
340 challenge_response_key: None,
341 #[cfg(feature = "challenge_response")]
342 challenge_response_result: None,
343 }
344 .get_key_elements()
345 .is_err());
346
347 Ok(())
348 }
349}