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 v
96 } else if buffer.len() == 32 {
97 buffer.to_vec()
99 } else {
100 calculate_sha256(&[buffer]).as_slice().to_vec()
101 }
102}
103
104#[derive(Debug, Clone, Default, PartialEq, Zeroize, ZeroizeOnDrop)]
106pub struct DatabaseKey {
107 password: Option<String>,
108 keyfile: Option<Vec<u8>>,
109 #[cfg(feature = "challenge_response")]
110 challenge_response_key: Option<ChallengeResponseKey>,
111 #[cfg(feature = "challenge_response")]
112 challenge_response_result: Option<KeyElement>,
113}
114
115impl DatabaseKey {
116 pub fn with_password(mut self, password: &str) -> Self {
117 self.password = Some(password.to_string());
118 self
119 }
120
121 #[cfg(feature = "utilities")]
122 pub fn with_password_from_prompt(mut self, prompt_message: &str) -> Result<Self, std::io::Error> {
123 self.password = Some(rpassword::prompt_password(prompt_message)?);
124 Ok(self)
125 }
126
127 #[cfg(all(feature = "challenge_response", feature = "utilities"))]
128 pub fn with_hmac_sha1_secret_from_prompt(mut self, prompt_message: &str) -> Result<Self, std::io::Error> {
129 self.challenge_response_key = Some(ChallengeResponseKey::LocalChallenge(rpassword::prompt_password(prompt_message)?));
130 Ok(self)
131 }
132
133 pub fn with_keyfile(mut self, keyfile: &mut dyn Read) -> Result<Self, std::io::Error> {
139 let mut buf = Vec::new();
140 keyfile.read_to_end(&mut buf)?;
141
142 self.keyfile = Some(buf);
143
144 Ok(self)
145 }
146
147 #[cfg(feature = "challenge_response")]
148 pub fn with_challenge_response_key(mut self, challenge_response_key: ChallengeResponseKey) -> Self {
149 self.challenge_response_key = Some(challenge_response_key);
150 self
151 }
152
153 #[cfg(feature = "challenge_response")]
154 pub fn perform_challenge(mut self, kdf_seed: &[u8]) -> Result<Self, DatabaseKeyError> {
155 if let Some(challenge_response_key) = &self.challenge_response_key {
156 let response = challenge_response_key.perform_challenge(kdf_seed)?;
157 self.challenge_response_result = Some(response);
158 }
159
160 Ok(self)
161 }
162
163 pub fn new() -> Self {
164 DatabaseKey::default()
165 }
166
167 pub(crate) fn get_key_elements(&self) -> Result<KeyElements, DatabaseKeyError> {
168 let mut out = Vec::new();
169
170 if let Some(p) = &self.password {
171 out.push(calculate_sha256(&[p.as_bytes()]).to_vec());
172 }
173
174 if let Some(ref f) = self.keyfile {
175 out.push(parse_keyfile(f));
176 }
177
178 if out.is_empty() {
179 return Err(DatabaseKeyError::IncorrectKey);
180 }
181
182 #[cfg(feature = "challenge_response")]
183 if let Some(result) = &self.challenge_response_result {
184 out.push(calculate_sha256(&[result]).as_slice().to_vec());
185 } else if self.challenge_response_key.is_some() {
186 return Err(DatabaseKeyError::ChallengeResponse(ChallengeResponseKeyError::NotPerformed));
187 }
188
189 Ok(out)
190 }
191
192 pub fn is_empty(&self) -> bool {
194 if self.password.is_some() || self.keyfile.is_some() {
195 return false;
196 }
197 #[cfg(feature = "challenge_response")]
198 if self.challenge_response_key.is_some() {
199 return false;
200 }
201 true
202 }
203}
204
205#[cfg(test)]
206mod key_tests {
207
208 use crate::error::DatabaseKeyError;
209
210 use super::DatabaseKey;
211
212 #[test]
213 fn test_key() -> Result<(), DatabaseKeyError> {
214 let ke = DatabaseKey::new().with_password("asdf").get_key_elements()?;
215 assert_eq!(ke.len(), 1);
216
217 let ke = DatabaseKey::new()
218 .with_keyfile(&mut "bare-key-file".as_bytes())?
219 .get_key_elements()?;
220 assert_eq!(ke.len(), 1);
221
222 let ke = DatabaseKey::new()
223 .with_keyfile(&mut "0123456789ABCDEF0123456789ABCDEF".as_bytes())?
224 .get_key_elements()?;
225 assert_eq!(ke.len(), 1);
226
227 let ke = DatabaseKey::new()
228 .with_password("asdf")
229 .with_keyfile(&mut "bare-key-file".as_bytes())?
230 .get_key_elements()?;
231 assert_eq!(ke.len(), 2);
232
233 let ke = DatabaseKey::new()
234 .with_keyfile(&mut "<KeyFile><Key><Data>0!23456789ABCDEF0123456789ABCDEF</Data></Key></KeyFile>".as_bytes())?
235 .get_key_elements()?;
236 assert_eq!(ke.len(), 1);
237
238 let ke = DatabaseKey::new()
239 .with_keyfile(&mut "<KeyFile><Key><Data>NXyYiJMHg3ls+eBmjbAjWec9lcOToJiofbhNiFMTJMw=</Data></Key></KeyFile>".as_bytes())?
240 .get_key_elements()?;
241 assert_eq!(ke.len(), 1);
242
243 let xml_keyfile_v2 = r###"
244 <?xml version="1.0" encoding="utf-8"?>
245 <KeyFile>
246 <Meta>
247 <Version>2.0</Version>
248 </Meta>
249 <Key>
250 <Data Hash="A65F0C2D">
251 36057B1C 35037FD9 62257893 C0A22403
252 EE3F8FBB 504D9981 08B821CB 00D28F89
253 </Data>
254 </Key>
255 </KeyFile>
256 "###;
257 let ke = DatabaseKey::new()
258 .with_keyfile(&mut xml_keyfile_v2.trim().as_bytes())?
259 .get_key_elements()?;
260 assert_eq!(ke.len(), 1);
261
262 let ke = DatabaseKey::new()
264 .with_keyfile(&mut "<Not><A><KeyFile></KeyFile></A></Not>".as_bytes())?
265 .get_key_elements()?;
266
267 assert_eq!(ke.len(), 1);
268
269 assert!(
270 DatabaseKey {
271 password: None,
272 keyfile: None,
273 #[cfg(feature = "challenge_response")]
274 challenge_response_key: None,
275 #[cfg(feature = "challenge_response")]
276 challenge_response_result: None,
277 }
278 .get_key_elements()
279 .is_err()
280 );
281
282 Ok(())
283 }
284}