1use crate::kdf::KdfValue;
2use crate::utils::{b64_decode, b64_encode, secure_random_alnum, secure_random_vec};
3use crate::value::EncValue;
4use crate::CryptrError;
5use regex::Regex;
6use std::collections::HashMap;
7use std::env;
8use std::fmt::{Display, Formatter, Write};
9use std::sync::OnceLock;
10use tokio::fs;
11use tokio::fs::File;
12use tracing::error;
13
14static RE_KEY_ID: OnceLock<Regex> = OnceLock::new();
15
16#[allow(dead_code)]
17pub(crate) static ENC_KEYS: OnceLock<EncKeys> = OnceLock::new();
18
19#[derive(Debug)]
21pub struct EncKeysSealed(String);
22
23impl Display for EncKeysSealed {
24 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
25 write!(f, "{}", self.0)
26 }
27}
28
29impl EncKeysSealed {
30 pub fn from_b64(value: String) -> Self {
31 Self(value)
32 }
33
34 pub fn from_bytes(value: &[u8]) -> Self {
35 Self(b64_encode(value))
36 }
37
38 pub fn try_from_env() -> Result<Self, CryptrError> {
40 dotenvy::dotenv().ok();
41 let s = env::var("ENC_KEYS_SEALED")?;
42 Ok(Self(s))
43 }
44
45 pub fn seal(enc_keys: EncKeys, password: &str) -> Result<Self, CryptrError> {
47 let keys_bytes: Vec<u8> = enc_keys.into_bytes();
48 let enc = EncValue::encrypt_with_password(keys_bytes.as_slice(), password)?;
49 let s = b64_encode(enc.into_bytes().as_ref());
50 Ok(Self(s))
51 }
52
53 pub fn unseal(self, password: &str) -> Result<EncKeys, CryptrError> {
55 let bytes = b64_decode(&self.0)?;
56 let enc = EncValue::try_from_bytes(bytes)?;
57 let dec = enc.decrypt_with_password(password)?;
58 let keys = EncKeys::try_from(dec.as_ref())?;
59 Ok(keys)
60 }
61
62 pub async fn read_from_file(path: &str) -> Result<Self, CryptrError> {
63 let s = fs::read_to_string(path).await?;
64 Ok(Self(s))
65 }
66
67 pub async fn save_to_file(&self, path_full: &str) -> Result<(), CryptrError> {
68 if let Ok(file) = File::open(&path_full).await {
69 let meta = file.metadata().await?;
70 if meta.is_dir() {
71 return Err(CryptrError::File("target file is a directory"));
72 }
73 }
74
75 fs::write(&path_full, self.0.as_bytes()).await?;
76 Ok(())
77 }
78}
79
80#[derive(Debug, Default, PartialEq, bincode::Encode, bincode::Decode)]
85pub struct EncKeys {
86 pub enc_key_active: String,
87 pub enc_keys: Vec<(String, Vec<u8>)>,
88}
89
90impl TryFrom<&[u8]> for EncKeys {
91 type Error = CryptrError;
92
93 fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
94 let (slf, _) =
95 bincode::decode_from_slice(value, bincode::config::legacy()).map_err(|err| {
96 error!("Deserialization error: {:?}", err);
97 CryptrError::Deserialization("Cannot deserialize EncKeys from given bytes")
98 })?;
99 Ok(slf)
100 }
101}
102
103impl From<KdfValue> for EncKeys {
104 fn from(kdf: KdfValue) -> Self {
105 Self {
106 enc_key_active: kdf.enc_key_value(),
107 enc_keys: vec![(kdf.enc_key_value(), kdf.value())],
108 }
109 }
110}
111
112#[allow(dead_code)]
113impl EncKeys {
114 #[inline]
115 pub fn re_key_id() -> &'static Regex {
116 RE_KEY_ID.get_or_init(|| Regex::new(r"^[a-zA-Z0-9:_-]{2,20}$").unwrap())
117 }
118
119 pub fn try_parse(
120 enc_key_active: String,
121 enc_keys_unparsed: Vec<String>,
122 ) -> Result<Self, CryptrError> {
123 let mut enc_keys = Vec::with_capacity(enc_keys_unparsed.len());
124 for key in enc_keys_unparsed {
125 let Some((id, key_bytes)) = Self::parse_raw_key(&key)? else {
126 continue;
127 };
128 enc_keys.push((id, key_bytes));
129 }
130
131 Ok(Self {
132 enc_key_active,
133 enc_keys,
134 })
135 }
136
137 pub fn append_new_random(&mut self) -> Result<(), CryptrError> {
139 let id = secure_random_alnum(12);
140 self.append_new_random_with_id(id)
141 }
142
143 pub fn append_new_random_with_id(&mut self, id: String) -> Result<(), CryptrError> {
145 Self::validate_id(&id, Some(self))?;
146
147 let key = secure_random_vec(32)?;
148 self.enc_key_active.clone_from(&id);
149 self.enc_keys.push((id, key));
150
151 Ok(())
152 }
153
154 #[cfg(feature = "cli")]
158 pub async fn config_path() -> Result<String, CryptrError> {
159 let home_path = home::home_dir().ok_or(CryptrError::File("Cannot get $HOME"))?;
160 let home_str = home_path
161 .to_str()
162 .ok_or(CryptrError::File("Cannot convert $HOME path to str"))?;
163
164 fs::create_dir_all(format!("{home_str}/.cryptr")).await?;
165
166 #[cfg(target_family = "unix")]
167 let path = format!("{home_str}/.cryptr/config");
168 #[cfg(not(target_family = "unix"))]
169 let path = format!("{home_str}\\.cryptr\\config");
170 Ok(path)
171 }
172
173 pub fn delete(&mut self, enc_key_id: &str) -> Result<(), CryptrError> {
175 if self.enc_key_active == enc_key_id {
176 return Err(CryptrError::Keys("Cannot delete the currently active key"));
177 }
178
179 self.enc_keys = self
180 .enc_keys
181 .clone()
182 .into_iter()
183 .filter(|(id, _key)| id != enc_key_id)
184 .collect();
185
186 Ok(())
187 }
188
189 pub fn fmt_enc_keys_str_for_config(enc_keys: &str) -> (String, String) {
197 let value_v64 = b64_encode(enc_keys.as_bytes());
198
199 let cfg_value = format!("ENC_KEYS=\"\n{enc_keys}\"");
200 let secrets_value = format!("ENC_KEYS: {value_v64}");
201
202 (cfg_value, secrets_value)
203 }
204
205 #[cfg(feature = "cli")]
209 pub async fn read_from_config() -> Result<Self, CryptrError> {
210 let path = Self::config_path().await?;
211 if dotenvy::from_filename(path).is_err() {
212 Err(CryptrError::Config("Config has not been set up yet"))
213 } else {
214 Self::from_env()
215 }
216 }
217
218 pub fn read_from_file(path: &str) -> Result<Self, CryptrError> {
220 dotenvy::from_filename(path)?;
221 Self::from_env()
222 }
223
224 pub fn from_env() -> Result<Self, CryptrError> {
236 dotenvy::dotenv().ok();
237 let enc_key_active = env::var("ENC_KEY_ACTIVE")?;
238 let raw_enc_keys = env::var("ENC_KEYS")?;
239 let mut enc_keys: Vec<(String, Vec<u8>)> = Vec::with_capacity(2);
240
241 for key in raw_enc_keys.split('\n') {
242 if !key.is_empty() {
243 let Some((id, key_bytes)) = Self::parse_raw_key(key)? else {
244 continue;
245 };
246 enc_keys.push((id, key_bytes));
247 }
248 }
249
250 Ok(Self {
251 enc_key_active,
252 enc_keys,
253 })
254 }
255
256 fn parse_raw_key(input: &str) -> Result<Option<(String, Vec<u8>)>, CryptrError> {
257 let t: (&str, &str) = match input.split_once('/') {
258 None => {
259 return Ok(None);
260 }
261 Some(k) => k,
262 };
263 let id = t.0.trim();
264 let key_raw = t.1.trim();
265
266 if id.is_empty() || key_raw.is_empty() {
267 return Err(CryptrError::Keys(
268 "ENC_KEYS must not be empty. Format: \"<id>/<key> <id>/<key>\"",
269 ));
270 }
271
272 let key_bytes = b64_decode(key_raw)?;
273 if key_bytes.len() != 32 {
274 return Err(CryptrError::Keys(
275 "An encryption key must be exactly 32 bytes long",
276 ));
277 }
278
279 if !Self::re_key_id().is_match(id) {
280 return Err(CryptrError::Keys(
281 "The IDs for ENC_KEYS must match '^[a-zA-Z0-9:_-]{2,20}$'",
282 ));
283 }
284
285 Ok(Some((id.to_string(), key_bytes)))
286 }
287
288 fn into_bytes(self) -> Vec<u8> {
289 bincode::encode_to_vec(&self, bincode::config::legacy()).unwrap()
290 }
291
292 pub fn keys_as_b64(&self) -> Result<String, CryptrError> {
294 let mut keys = String::with_capacity(self.enc_keys.len() * 56);
295 for (id, key) in &self.enc_keys {
296 let kb64 = b64_encode(key);
297 writeln!(keys, "{id}/{kb64}")?;
298 }
299 Ok(keys)
300 }
301
302 pub fn keys_as_b64_vec(&self) -> Vec<String> {
304 self.enc_keys
305 .iter()
306 .map(|(id, key)| format!("{id}/{}", b64_encode(key)))
307 .collect::<Vec<_>>()
308 }
309
310 pub async fn save_to_file(&self, file: &str) -> Result<(), CryptrError> {
311 match file.rsplit_once('/') {
313 None => {
314 self.save_to_file_with_path("./", file).await
316 }
317 Some((path, file)) => self.save_to_file_with_path(path, file).await,
318 }
319 }
320
321 pub async fn save_to_file_with_path(
322 &self,
323 path: &str,
324 file_name: &str,
325 ) -> Result<(), CryptrError> {
326 if self.enc_keys.is_empty() {
327 return Err(CryptrError::Keys("EncKeys is empty - not saving anything"));
328 }
329
330 fs::create_dir_all(path).await?;
331 let path_full = format!("{path}/{file_name}");
332 if let Ok(file) = File::open(&path_full).await {
333 let meta = file.metadata().await?;
334 if meta.is_dir() {
335 return Err(CryptrError::Keys("target path is a directory"));
336 }
337 }
338
339 let mut keys = String::with_capacity(self.enc_keys.len() * 56);
340 for (id, key) in &self.enc_keys {
341 let kb64 = b64_encode(key);
342 writeln!(keys, "{id}/{kb64}")?;
343 }
344 let _ = keys.split_off(keys.len() - 1);
345
346 let content = format!(
347 "ENC_KEY_ACTIVE={}\nENC_KEYS=\"\n{keys}\n\"",
348 self.enc_key_active
349 );
350 fs::write(&path_full, content.as_bytes()).await?;
351 #[cfg(target_family = "unix")]
352 {
353 use std::fs::Permissions;
354 use std::os::unix::fs::PermissionsExt;
355 fs::set_permissions(&path_full, Permissions::from_mode(0o600)).await?;
356 }
357
358 Ok(())
359 }
360
361 pub fn get_key(&self, enc_key_id: &str) -> Result<&[u8], CryptrError> {
363 for (id, key) in &self.enc_keys {
364 if id.as_str() == enc_key_id {
365 return Ok(key.as_slice());
366 }
367 }
368 Err(CryptrError::Keys("EncKey ID {} does not exist"))
369 }
370
371 pub fn get_static<'a>() -> &'a Self {
379 ENC_KEYS
380 .get()
381 .expect("`init()` to have been called on valid EncKeys once before")
382 }
383
384 pub fn get_static_key<'a>(enc_key_id: &str) -> Result<&'a [u8], CryptrError> {
388 let keys = Self::get_static();
389 for (id, key) in &keys.enc_keys {
390 if id.as_str() == enc_key_id {
391 return Ok(key.as_slice());
392 }
393 }
394 Err(CryptrError::Keys("EncKey ID does not exist"))
395 }
396
397 pub fn get_key_active<'a>() -> Result<&'a [u8], CryptrError> {
401 let keys = Self::get_static();
402 let active_id = &keys.enc_key_active;
403 for (id, key) in &keys.enc_keys {
404 if id == active_id {
405 return Ok(key.as_slice());
406 }
407 }
408 Err(CryptrError::Keys("Active EncKey ID {} does not exist"))
409 }
410
411 pub fn init(self) -> Result<(), CryptrError> {
418 if ENC_KEYS.set(self).is_err() {
419 Err(CryptrError::Keys(
420 "EncKeys::init() has already been called before",
421 ))
422 } else {
423 Ok(())
424 }
425 }
426
427 pub fn generate() -> Result<Self, CryptrError> {
429 let id = secure_random_alnum(12);
430 Self::generate_with_id(id)
431 }
432
433 pub fn generate_multiple(number_of_keys: u16) -> Result<Self, CryptrError> {
435 if number_of_keys < 1 {
436 return Err(CryptrError::Keys("number_of_keys must be greater than 1"));
437 }
438
439 let mut enc_keys = Vec::with_capacity(number_of_keys as usize);
440 for _ in 0..number_of_keys {
441 let id = secure_random_alnum(12);
442 let key = secure_random_vec(32)?;
443 enc_keys.push((id, key))
444 }
445
446 Ok(Self {
447 enc_key_active: enc_keys.first().unwrap().0.clone(),
448 enc_keys,
449 })
450 }
451
452 pub fn generate_with_id(id: String) -> Result<Self, CryptrError> {
454 Self::validate_id(&id, None)?;
455 let key = secure_random_vec(32)?;
456
457 Ok(Self {
458 enc_key_active: id.clone(),
459 enc_keys: vec![(id, key)],
460 })
461 }
462
463 pub fn try_convert_legacy_keys(keys: &str) -> Result<String, CryptrError> {
467 let mut keys_map: HashMap<String, Vec<u8>> = HashMap::new();
468
469 let re = Self::re_key_id();
471
472 for k in keys.split(' ') {
473 if !k.is_empty() {
474 let t: (&str, &str) = k
475 .split_once('/')
476 .ok_or(CryptrError::Keys("Incorrect format for ENC_KEYS"))?;
477 let id = t.0.trim();
478 let key = t.1.trim();
479
480 if id.is_empty() || key.is_empty() {
481 return Err(CryptrError::Keys(
482 "ENC_KEYS must not be empty. Format: \"<id>/<key> <id>/<key>\"",
483 ));
484 }
485
486 if key.len() != 32 {
487 error!("Encryption Key for Enc Key Id '{id}' is not 32 bytes long");
488 return Err(CryptrError::Keys("Encryption Key is not 32 bytes long"));
489 }
490
491 if !re.is_match(id) {
492 return Err(CryptrError::Keys(
493 "The IDs for ENC_KEYS must match '^[a-zA-Z0-9:_-]{2,20}$'",
494 ));
495 }
496
497 keys_map.insert(String::from(id), Vec::from(key));
498 }
499 }
500
501 let mut res = String::with_capacity(keys_map.len() * 48);
502 for (id, key) in keys_map {
503 let key_b64 = b64_encode(&key);
504 writeln!(res, "{id}/{key_b64}")?;
505 }
506
507 Ok(res)
508 }
509
510 fn validate_id(id: &str, current: Option<&EncKeys>) -> Result<(), CryptrError> {
511 if let Some(curr) = current {
512 for (key_id, _) in &curr.enc_keys {
513 if key_id == id {
514 return Err(CryptrError::Keys("Key ID exists already"));
515 }
516 }
517 }
518
519 if Self::re_key_id().is_match(id) {
520 Ok(())
521 } else {
522 Err(CryptrError::Keys(
523 "An encryption key ID must match: ^[a-zA-Z0-9_-]{2,20}$",
524 ))
525 }
526 }
527}
528
529#[cfg(test)]
530mod tests {
531 use argon2::Params;
532
533 use super::*;
534
535 #[tokio::test]
536 #[ignore] async fn test_enc_from_env() {
538 unsafe {
539 env::set_var("ENC_KEY_ACTIVE", "zQac11NaE0Nn");
540 env::set_var(
541 "ENC_KEYS",
542 r#"
543 zQac11NaE0Nn/UZFxllgmmnA5KzBr7A6uS+p/ccLe2/L4M4Vs3CMhwQg=
544 nlL1mQjkQH58/lPfvTp7RojBOU8aNzZrfYQ44ykm0SR/DaZmvMZMmXkY=
545 26VvcHiaJP26/Cu8I2NEzD2tjKV+2Tl6Dwx2tkPOMyolYP1ydTcN+hik=
546 "#,
547 );
548 }
549
550 let keys = EncKeys::from_env().unwrap();
551 assert_eq!(keys.enc_key_active.as_str(), "zQac11NaE0Nn");
552 assert_eq!(keys.enc_keys.len(), 3);
553 }
554
555 #[tokio::test]
556 async fn test_enc_from_file() {
557 let keys_len = 3;
558 let keys = EncKeys::generate_multiple(keys_len).unwrap();
559
560 let path = "./test_files";
561 let file_name = "keys";
562 keys.save_to_file_with_path(path, file_name).await.unwrap();
563
564 let path_full = format!("{}/{}", path, file_name);
565
566 let keys_from = EncKeys::read_from_file(&path_full).unwrap();
567 assert_eq!(keys, keys_from);
568 assert_eq!(keys.enc_keys.len(), keys_len as usize);
569 }
570
571 #[tokio::test]
572 async fn test_append_delete() {
573 let keys = EncKeys::generate_multiple(3).unwrap();
574 assert_eq!(keys.enc_keys.len(), 3);
575
576 let curr_active = keys.enc_key_active.clone();
577 let (id, _key) = keys.enc_keys.get(2).unwrap().clone();
578
579 let mut keys = keys;
580 let res = keys.delete(&curr_active);
581 assert!(res.is_err());
582
583 keys.delete(&id).unwrap();
584 assert_eq!(keys.enc_keys.len(), 2);
585
586 keys.append_new_random().unwrap();
587 assert_ne!(keys.enc_key_active, curr_active);
588 assert_eq!(keys.enc_keys.len(), 3);
589 }
590
591 #[test]
592 fn test_fmt_config_str() {
593 let legacy_str = "bVCyTsGaggVy5yqQ/S9n7oCen53xSJLzcsmfdnBDvNrqQ63r4 q6u26onRvXVG4427/3CEC8RJWBcMkrBMkRXgx65AmJsNTghSA";
594 let converted = EncKeys::try_convert_legacy_keys(&legacy_str)
595 .expect("legacy key conversion to be successful");
596
597 let (cfg_value, _secrets_value) = EncKeys::fmt_enc_keys_str_for_config(&converted);
598
599 println!("\n{}\n", cfg_value);
600 assert!(
601 cfg_value.contains("q6u26onRvXVG4427/M0NFQzhSSldCY01rckJNa1JYZ3g2NUFtSnNOVGdoU0E=\n")
602 );
603 assert!(
604 cfg_value.contains("bVCyTsGaggVy5yqQ/UzluN29DZW41M3hTSkx6Y3NtZmRuQkR2TnJxUTYzcjQ=\n")
605 );
606 }
607
608 #[test]
609 fn test_from_kdf_value() {
610 let kdf_value = KdfValue::new("123");
611 let id = kdf_value.enc_key_value();
612 let enc_keys = EncKeys::from(kdf_value);
613
614 assert_eq!(enc_keys.enc_key_active, id);
615 assert_eq!(enc_keys.enc_keys.len(), 1);
616 }
617
618 #[test]
619 fn test_from_kdf_value_with_params() {
620 let params = Params::new(Params::MIN_M_COST, 5, 1, Some(32)).unwrap();
621 let kdf_value = KdfValue::new_with_params("123", params);
622 let id1 = kdf_value.enc_key_value();
623 let enc_keys1 = EncKeys::from(kdf_value);
624 assert_eq!(enc_keys1.enc_key_active, id1);
625 assert_eq!(enc_keys1.enc_keys.len(), 1);
626
627 let kdf_value = KdfValue::new("123");
629 let id2 = kdf_value.enc_key_value();
630 let enc_keys2 = EncKeys::from(kdf_value);
631 assert_eq!(enc_keys2.enc_key_active, id2);
632 assert_eq!(enc_keys2.enc_keys.len(), 1);
633
634 assert_ne!(enc_keys1.enc_key_active, enc_keys2.enc_key_active);
635 assert_ne!(enc_keys1.into_bytes(), enc_keys2.into_bytes());
636 }
637}