1use std::io::Read;
2
3use base64::{Engine as _, engine::general_purpose as base64_engine};
4use xml::name::OwnedName;
5use xml::reader::{EventReader, XmlEvent};
6use zeroize::{Zeroize, ZeroizeOnDrop};
7
8use crate::{crypt::calculate_sha256, error::DatabaseKeyError};
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;
18
19fn parse_xml_keyfile(xml: &[u8]) -> Result<KeyElement, DatabaseKeyError> {
20 let parser = EventReader::new(xml);
21
22 let mut tag_stack = Vec::new();
23
24 let mut key_version: Option<String> = None;
25 let mut key_value = String::new();
26
27 for ev in parser {
28 match ev? {
29 XmlEvent::StartElement {
30 name: OwnedName { ref local_name, .. },
31 ..
32 } => {
33 tag_stack.push(local_name.clone());
34 }
35 XmlEvent::EndElement { .. } => {
36 tag_stack.pop();
37 }
38 XmlEvent::Characters(s) => {
39 if tag_stack == ["KeyFile", "Meta", "Version"] {
40 key_version = Some(s);
41 continue;
42 }
43
44 if tag_stack == ["KeyFile", "Key", "Data"] {
45 key_value.push_str(&s);
46 continue;
47 }
48 }
49 _ => {}
50 }
51 }
52
53 if key_value.is_empty() {
54 return Err(DatabaseKeyError::InvalidKeyFile);
55 }
56
57 let key_bytes = key_value.as_bytes().to_vec();
58
59 if key_version == Some("2.0".to_string()) {
60 let trimmed_key = key_value
63 .trim()
64 .replace(" ", "")
65 .replace("\n", "")
66 .replace("\t", "")
67 .replace("\r", "");
68
69 return if let Ok(key) = hex::decode(&trimmed_key) {
70 Ok(key)
71 } else {
72 Ok(key_bytes)
73 };
74 }
75
76 if let Ok(key) = base64_engine::STANDARD.decode(&key_bytes) {
78 Ok(key)
79 } else {
80 Ok(key_bytes)
81 }
82}
83
84fn parse_keyfile(buffer: &[u8]) -> KeyElement {
85 if let Ok(v) = parse_xml_keyfile(buffer) {
87 v
88 } else if buffer.len() == 32 {
89 buffer.to_vec()
91 } else {
92 calculate_sha256(&[buffer]).as_slice().to_vec()
93 }
94}
95
96#[derive(Debug, Clone, Default, PartialEq, Zeroize, ZeroizeOnDrop)]
98pub struct DatabaseKey {
99 password: Option<String>,
100 keyfile: Option<Vec<u8>>,
101 #[cfg(feature = "challenge_response")]
102 challenge_response_key: Option<ChallengeResponseKey>,
103 #[cfg(feature = "challenge_response")]
104 challenge_response_result: Option<KeyElement>,
105}
106
107impl DatabaseKey {
108 pub fn with_password(mut self, password: &str) -> Self {
109 self.password = Some(password.to_string());
110 self
111 }
112
113 #[cfg(feature = "utilities")]
114 pub fn with_password_from_prompt(mut self, prompt_message: &str) -> Result<Self, std::io::Error> {
115 self.password = Some(rpassword::prompt_password(prompt_message)?);
116 Ok(self)
117 }
118
119 #[cfg(all(feature = "challenge_response", feature = "utilities"))]
120 pub fn with_hmac_sha1_secret_from_prompt(mut self, prompt_message: &str) -> Result<Self, std::io::Error> {
121 self.challenge_response_key = Some(ChallengeResponseKey::LocalChallenge(rpassword::prompt_password(prompt_message)?));
122 Ok(self)
123 }
124
125 pub fn with_keyfile(mut self, keyfile: &mut dyn Read) -> Result<Self, std::io::Error> {
131 let mut buf = Vec::new();
132 keyfile.read_to_end(&mut buf)?;
133
134 self.keyfile = Some(buf);
135
136 Ok(self)
137 }
138
139 #[cfg(feature = "challenge_response")]
140 pub fn with_challenge_response_key(mut self, challenge_response_key: ChallengeResponseKey) -> Self {
141 self.challenge_response_key = Some(challenge_response_key);
142 self
143 }
144
145 #[cfg(feature = "challenge_response")]
146 pub fn perform_challenge(mut self, kdf_seed: &[u8]) -> Result<Self, DatabaseKeyError> {
147 if let Some(challenge_response_key) = &self.challenge_response_key {
148 let response = challenge_response_key.perform_challenge(kdf_seed)?;
149 self.challenge_response_result = Some(response);
150 }
151
152 Ok(self)
153 }
154
155 pub fn new() -> Self {
156 DatabaseKey::default()
157 }
158
159 pub(crate) fn get_key_elements(&self) -> Result<KeyElements, DatabaseKeyError> {
160 let mut out = Vec::new();
161
162 if let Some(p) = &self.password {
163 out.push(calculate_sha256(&[p.as_bytes()]).to_vec());
164 }
165
166 if let Some(ref f) = self.keyfile {
167 out.push(parse_keyfile(f));
168 }
169
170 if out.is_empty() {
171 return Err(DatabaseKeyError::IncorrectKey);
172 }
173
174 #[cfg(feature = "challenge_response")]
175 if let Some(result) = &self.challenge_response_result {
176 out.push(calculate_sha256(&[result]).as_slice().to_vec());
177 } else if self.challenge_response_key.is_some() {
178 return Err(DatabaseKeyError::ChallengeResponseKeyError(
179 "Challenge-response was not performed".to_string(),
180 ));
181 }
182
183 Ok(out)
184 }
185
186 pub fn is_empty(&self) -> bool {
188 if self.password.is_some() || self.keyfile.is_some() {
189 return false;
190 }
191 #[cfg(feature = "challenge_response")]
192 if self.challenge_response_key.is_some() {
193 return false;
194 }
195 true
196 }
197}
198
199#[cfg(test)]
200mod key_tests {
201
202 use crate::error::DatabaseKeyError;
203
204 use super::DatabaseKey;
205
206 #[test]
207 fn test_key() -> Result<(), DatabaseKeyError> {
208 let ke = DatabaseKey::new().with_password("asdf").get_key_elements()?;
209 assert_eq!(ke.len(), 1);
210
211 let ke = DatabaseKey::new()
212 .with_keyfile(&mut "bare-key-file".as_bytes())?
213 .get_key_elements()?;
214 assert_eq!(ke.len(), 1);
215
216 let ke = DatabaseKey::new()
217 .with_keyfile(&mut "0123456789ABCDEF0123456789ABCDEF".as_bytes())?
218 .get_key_elements()?;
219 assert_eq!(ke.len(), 1);
220
221 let ke = DatabaseKey::new()
222 .with_password("asdf")
223 .with_keyfile(&mut "bare-key-file".as_bytes())?
224 .get_key_elements()?;
225 assert_eq!(ke.len(), 2);
226
227 let ke = DatabaseKey::new()
228 .with_keyfile(&mut "<KeyFile><Key><Data>0!23456789ABCDEF0123456789ABCDEF</Data></Key></KeyFile>".as_bytes())?
229 .get_key_elements()?;
230 assert_eq!(ke.len(), 1);
231
232 let ke = DatabaseKey::new()
233 .with_keyfile(&mut "<KeyFile><Key><Data>NXyYiJMHg3ls+eBmjbAjWec9lcOToJiofbhNiFMTJMw=</Data></Key></KeyFile>".as_bytes())?
234 .get_key_elements()?;
235 assert_eq!(ke.len(), 1);
236
237 let xml_keyfile_v2 = r###"
238 <?xml version="1.0" encoding="utf-8"?>
239 <KeyFile>
240 <Meta>
241 <Version>2.0</Version>
242 </Meta>
243 <Key>
244 <Data Hash="A65F0C2D">
245 36057B1C 35037FD9 62257893 C0A22403
246 EE3F8FBB 504D9981 08B821CB 00D28F89
247 </Data>
248 </Key>
249 </KeyFile>
250 "###;
251 let ke = DatabaseKey::new()
252 .with_keyfile(&mut xml_keyfile_v2.trim().as_bytes())?
253 .get_key_elements()?;
254 assert_eq!(ke.len(), 1);
255
256 let ke = DatabaseKey::new()
258 .with_keyfile(&mut "<Not><A><KeyFile></KeyFile></A></Not>".as_bytes())?
259 .get_key_elements()?;
260
261 assert_eq!(ke.len(), 1);
262
263 assert!(
264 DatabaseKey {
265 password: None,
266 keyfile: None,
267 #[cfg(feature = "challenge_response")]
268 challenge_response_key: None,
269 #[cfg(feature = "challenge_response")]
270 challenge_response_result: None,
271 }
272 .get_key_elements()
273 .is_err()
274 );
275
276 Ok(())
277 }
278}