1use revault_lockbox_api::{
2 ArtifactKind, ContactKeyPair, ContactPublicKey, Error, FileLockScope, FormDefinition,
3 FormFieldDefinition, FormFieldKind, FormTypeId, ListOptions, Lockbox, LockboxEntryKind,
4 LockboxId, LockboxOpen, LockboxPath, LockboxProtection, OwnerSigningKeyPair,
5 OwnerSigningPublicKey, ReadOnly, Result, ScopedFileLock, SecretString, SecretVec, VariableName,
6};
7use sha2::{Digest, Sha256};
8use std::cell::{Cell, RefCell};
9use std::env;
10use std::fs::{self, File, OpenOptions};
11use std::io::{Read, Write};
12use std::path::{Path, PathBuf};
13use std::time::{SystemTime, UNIX_EPOCH};
14
15use crate::key_format::{export_private_key, import_private_key, KeyFormat};
16
17const VAULT_FILE_NAME: &str = "local-vault.lbox";
18const VAULT_BACKUP_MAGIC: &[u8; 8] = b"LBVBK001";
19const VAULT_STRUCTURE_VERSION_PATH: &str = "/vault/structure-version";
20const KNOWN_LOCKBOX_MAGIC: &[u8; 4] = b"LBKL";
21const KNOWN_LOCKBOX_VERSION: u16 = 1;
22const PROFILE_HISTORY_MAGIC: &[u8; 4] = b"LBPH";
23const PROFILE_HISTORY_VERSION: u16 = 1;
24const PROFILE_EMAIL_MAGIC: &[u8; 4] = b"LBPE";
25const PROFILE_EMAIL_VERSION: u16 = 1;
26const GENERATION_ACTIVE: u16 = 1;
27const GENERATION_RETIRED: u16 = 2;
28const GENERATION_COMPROMISED: u16 = 3;
29
30thread_local! {
31 static VAULT_LOCK_DEPTH: Cell<usize> = const { Cell::new(0) };
32}
33
34pub const CURRENT_VAULT_STRUCTURE_VERSION: u32 = 2;
36
37pub fn validate_vault_record_name(name: &str) -> Result<()> {
44 validate_record_name(name).map(|_| ())
45}
46
47#[derive(Debug, Clone, PartialEq, Eq)]
49pub struct StoredContact {
50 pub name: String,
52
53 pub key: ContactPublicKey,
55}
56
57#[derive(Debug, Clone, PartialEq, Eq)]
60pub struct KnownLockbox {
61 pub lockbox_id: LockboxId,
63
64 pub path: String,
66
67 pub last_seen_unix_ms: u64,
69}
70
71#[derive(Debug, Clone, PartialEq, Eq)]
73pub struct AccessSlotLabel {
74 pub lockbox_id: LockboxId,
76 pub slot_id: u64,
78 pub name: String,
80 pub updated_at_unix_ms: u64,
82}
83
84#[derive(Debug, Clone, PartialEq, Eq)]
86pub struct ProfileGeneration {
87 pub index: u16,
89 pub status: ProfileGenerationStatus,
91 pub contact_fingerprint: Vec<u8>,
93 pub created_at_unix_ms: u64,
95 pub retired_at_unix_ms: Option<u64>,
97}
98
99#[derive(Debug, Clone, Copy, PartialEq, Eq)]
100pub enum ProfileGenerationStatus {
102 Active,
104 Retired,
106 Compromised,
108}
109
110#[derive(Debug, Clone, PartialEq, Eq)]
112pub struct ProfileHistory {
113 pub name: String,
115 pub active_generation: u16,
117 pub generations: Vec<ProfileGeneration>,
119}
120
121#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
123pub struct VaultBackupManifest {
124 pub format_version: u16,
126
127 pub created_at_unix_ms: u64,
129
130 pub vault_file_name: String,
132
133 pub vault_size: u64,
135
136 pub vault_sha256: String,
138}
139
140#[derive(Debug)]
146pub struct VaultDirectory {
147 root: PathBuf,
148 path: PathBuf,
149 lockbox: RefCell<Lockbox>,
150}
151
152#[derive(Debug)]
158pub struct ReadOnlyVaultDirectory {
159 lockbox: RefCell<Lockbox<ReadOnly>>,
160}
161
162impl ReadOnlyVaultDirectory {
163 pub fn open_default(password: &SecretString) -> Result<Self> {
165 Self::open(default_vault_dir()?, password)
166 }
167
168 pub fn open(root: impl AsRef<Path>, password: &SecretString) -> Result<Self> {
170 let path = root.as_ref().join(VAULT_FILE_NAME);
171 if !path.exists() {
172 return Err(Error::VaultUnavailable(
173 "local vault is not initialized; run `lockbox vault init` first".to_string(),
174 ));
175 }
176 Self::open_file(path, password)
177 }
178
179 pub fn open_file(path: impl AsRef<Path>, password: &SecretString) -> Result<Self> {
186 let path = path.as_ref();
187 if !path.exists() {
188 return Err(Error::VaultUnavailable(format!(
189 "vault file does not exist: {}",
190 path.display()
191 )));
192 }
193 Ok(Self {
194 lockbox: RefCell::new(Lockbox::open(path, LockboxOpen::Password(password))?),
195 })
196 }
197
198 pub fn list_private_key_names(&self) -> Result<Vec<String>> {
200 let mut names = Vec::new();
201 for (variable_name, _) in self.lockbox.borrow().list_variables()? {
202 let Some(name) = private_key_name_from_variable(&variable_name) else {
203 continue;
204 };
205 names.push(name?);
206 }
207 names.sort();
208 names.dedup();
209 Ok(names)
210 }
211
212 pub fn list_contact_names(&self) -> Result<Vec<String>> {
214 list_read_only_record_names(&self.lockbox, "/contacts", ".pub")
215 }
216
217 pub fn list_form_aliases(&self) -> Result<Vec<String>> {
220 let mut aliases = self
221 .lockbox
222 .borrow()
223 .list_form_definitions()?
224 .into_iter()
225 .map(|definition| definition.alias)
226 .collect::<Vec<_>>();
227 aliases.sort();
228 aliases.dedup();
229 Ok(aliases)
230 }
231
232 pub fn list_known_lockboxes(&self) -> Result<Vec<KnownLockbox>> {
234 let mut out = Vec::new();
235 for name in list_read_only_record_names(&self.lockbox, "/known_lockboxes", ".lkl")? {
236 let path = LockboxPath::new(format!("/known_lockboxes/{name}.lkl"))?;
237 out.push(decode_known_lockbox(
238 &self.lockbox.borrow().get_file(&path)?,
239 )?);
240 }
241 out.sort_by(|left, right| left.path.cmp(&right.path));
242 Ok(out)
243 }
244}
245
246impl VaultDirectory {
247 pub const DEFAULT_KEY_NAME: &'static str = "default";
249
250 pub fn probe_structure_version(root: impl AsRef<Path>, password: &SecretString) -> Result<u32> {
254 let path = root.as_ref().join(VAULT_FILE_NAME);
255 let lockbox = Lockbox::open(&path, LockboxOpen::Password(password))?;
256 let record = lockbox.get_file(&vault_structure_version_record_path()?)?;
257 decode_structure_version(&record)
258 }
259
260 pub fn open_or_create_default(password: &SecretString) -> Result<Self> {
264 Self::open_or_create(default_vault_dir()?, password)
265 }
266
267 pub fn create_file(path: impl AsRef<Path>, password: &SecretString) -> Result<Self> {
277 let path = path.as_ref().to_path_buf();
278 if path.exists() {
279 return Err(Error::AlreadyExists(path.display().to_string()));
280 }
281 let root = path
282 .parent()
283 .filter(|parent| !parent.as_os_str().is_empty())
284 .unwrap_or_else(|| Path::new("."))
285 .to_path_buf();
286 fs::create_dir_all(&root).map_err(|err| Error::Io(err.to_string()))?;
287 let _guard = VaultFileLock::acquire(&path)?;
288 if path.exists() {
289 return Err(Error::AlreadyExists(path.display().to_string()));
290 }
291 let signing_key = OwnerSigningKeyPair::generate()?;
292 let lockbox = Lockbox::create_file_assuming_locked(
293 &path,
294 LockboxProtection::Password(password),
295 &signing_key,
296 )?;
297 set_private_file_permissions(&path)?;
298 let vault = Self {
299 root,
300 path,
301 lockbox: RefCell::new(lockbox),
302 };
303 vault.store_owner_signing_key_current_only(Self::DEFAULT_KEY_NAME, &signing_key)?;
304 vault.ensure_structure_version(true)?;
305 Ok(vault)
306 }
307
308 pub fn open_file(path: impl AsRef<Path>, password: &SecretString) -> Result<Self> {
315 let path = path.as_ref().to_path_buf();
316 if !path.exists() {
317 return Err(Error::VaultUnavailable(format!(
318 "vault file does not exist: {}",
319 path.display()
320 )));
321 }
322 let root = path
323 .parent()
324 .filter(|parent| !parent.as_os_str().is_empty())
325 .unwrap_or_else(|| Path::new("."))
326 .to_path_buf();
327 let _guard = VaultFileLock::acquire(&path)?;
328 let lockbox = open_vault_lockbox_for_write(&path, password)?;
329 let vault = Self {
330 root,
331 path,
332 lockbox: RefCell::new(lockbox),
333 };
334 vault.attach_or_create_default_owner_signing_key()?;
335 vault.ensure_structure_version(false)?;
336 Ok(vault)
337 }
338
339 pub fn replace_default(password: &SecretString) -> Result<Self> {
344 Self::replace(default_vault_dir()?, password)
345 }
346
347 pub fn change_default_password(
349 old_password: &SecretString,
350 new_password: &SecretString,
351 ) -> Result<()> {
352 Self::change_password(default_vault_dir()?, old_password, new_password)
353 }
354
355 pub fn change_password(
357 root: impl AsRef<Path>,
358 old_password: &SecretString,
359 new_password: &SecretString,
360 ) -> Result<()> {
361 let root = root.as_ref().to_path_buf();
362 let path = root.join(VAULT_FILE_NAME);
363 if !path.exists() {
364 return Err(Error::VaultUnavailable(
365 "local vault is not initialized; run `lockbox vault init` first".to_string(),
366 ));
367 }
368 let _guard = VaultFileLock::acquire(&path)?;
369 let lockbox = open_vault_lockbox_for_write(&path, old_password)?;
370 let vault = Self {
371 root,
372 path,
373 lockbox: RefCell::new(lockbox),
374 };
375 vault.attach_or_create_default_owner_signing_key()?;
376 vault
377 .lockbox
378 .borrow_mut()
379 .replace_password(old_password, new_password)?;
380 set_private_file_permissions(&vault.path)?;
381 let vault_id = vault.path.to_string_lossy().into_owned();
382 let _ = crate::forget_vault_unlock_key(&vault_id);
383 let _ = crate::forget_owner_signing_key(&vault_id, Self::DEFAULT_KEY_NAME);
384 Ok(())
385 }
386
387 pub fn replace(root: impl AsRef<Path>, password: &SecretString) -> Result<Self> {
389 let root = root.as_ref().to_path_buf();
390 create_private_dir(&root)?;
391 let path = root.join(VAULT_FILE_NAME);
392 let _guard = VaultFileLock::acquire(&path)?;
393 let vault_id = path.to_string_lossy().into_owned();
394 let _ = crate::forget_vault_unlock_key(&vault_id);
395 let _ = crate::forget_owner_signing_key(&vault_id, Self::DEFAULT_KEY_NAME);
396 if path.exists() {
397 fs::remove_file(&path).map_err(|err| Error::Io(err.to_string()))?;
398 }
399 let signing_key = OwnerSigningKeyPair::generate()?;
400 let lockbox = Lockbox::create_file_assuming_locked(
401 &path,
402 LockboxProtection::Password(password),
403 &signing_key,
404 )?;
405 set_private_file_permissions(&path)?;
406 let vault = Self {
407 root,
408 path,
409 lockbox: RefCell::new(lockbox),
410 };
411 vault.store_owner_signing_key_current_only(Self::DEFAULT_KEY_NAME, &signing_key)?;
412 vault.ensure_structure_version(true)?;
413 Ok(vault)
414 }
415
416 #[doc(hidden)]
421 pub fn replace_for_migration(root: impl AsRef<Path>, password: &SecretString) -> Result<Self> {
422 let root = root.as_ref().to_path_buf();
423 create_private_dir(&root)?;
424 let path = root.join(VAULT_FILE_NAME);
425 let _guard = VaultFileLock::acquire(&path)?;
426 if path.exists() {
427 return Err(Error::AlreadyExists(path.display().to_string()));
428 }
429 let signing_key = OwnerSigningKeyPair::generate()?;
430 let lockbox = Lockbox::create_file_assuming_locked(
431 &path,
432 LockboxProtection::Password(password),
433 &signing_key,
434 )?;
435 set_private_file_permissions(&path)?;
436 let vault = Self {
437 root,
438 path,
439 lockbox: RefCell::new(lockbox),
440 };
441 vault.ensure_structure_version(true)?;
442 Ok(vault)
443 }
444
445 pub fn open_or_create(root: impl AsRef<Path>, password: &SecretString) -> Result<Self> {
450 let root = root.as_ref().to_path_buf();
451 create_private_dir(&root)?;
452 let path = root.join(VAULT_FILE_NAME);
453 let _guard = VaultFileLock::acquire(&path)?;
454 let existed = path.exists();
455 let lockbox = if existed {
456 open_vault_lockbox_for_write(&path, password)?
457 } else {
458 let signing_key = OwnerSigningKeyPair::generate()?;
459 let lockbox = Lockbox::create_file_assuming_locked(
460 &path,
461 LockboxProtection::Password(password),
462 &signing_key,
463 )?;
464 set_private_file_permissions(&path)?;
465 let vault = Self {
466 root,
467 path,
468 lockbox: RefCell::new(lockbox),
469 };
470 vault.store_owner_signing_key_current_only(Self::DEFAULT_KEY_NAME, &signing_key)?;
471 vault.ensure_structure_version(true)?;
472 return Ok(vault);
473 };
474 let vault = Self {
475 root,
476 path,
477 lockbox: RefCell::new(lockbox),
478 };
479 vault.attach_or_create_default_owner_signing_key()?;
480 vault.ensure_structure_version(!existed)?;
481 Ok(vault)
482 }
483
484 pub fn root(&self) -> &Path {
486 &self.root
487 }
488
489 pub fn path(&self) -> &Path {
491 &self.path
492 }
493
494 fn attach_or_create_default_owner_signing_key(&self) -> Result<()> {
495 let signing_key = match self.load_owner_signing_key_existing(Self::DEFAULT_KEY_NAME) {
496 Ok(signing_key) => signing_key,
497 Err(Error::NotFound(_)) => {
498 let signing_key = OwnerSigningKeyPair::generate()?;
499 self.lockbox
500 .borrow_mut()
501 .set_owner_signing_key(signing_key.try_clone()?);
502 self.store_owner_signing_key_current_only(Self::DEFAULT_KEY_NAME, &signing_key)?;
503 return Ok(());
504 }
505 Err(err) => return Err(err),
506 };
507 self.lockbox.borrow_mut().set_owner_signing_key(signing_key);
508 Ok(())
509 }
510
511 pub fn structure_version(&self) -> Result<u32> {
513 self.read_structure_version()?.ok_or_else(|| {
514 Error::CorruptVaultRecord("vault structure version record is missing".to_string())
515 })
516 }
517
518 pub fn store_private_key(&self, name: &str, keypair: &ContactKeyPair) -> Result<()> {
522 let variable_name = private_key_variable_name(name)?;
523 let private_record = export_private_key(keypair, KeyFormat::RawHex)?;
524 let value = SecretString::from_secure_vec(private_record);
525 self.put_secret_variable_record(&variable_name, &value)?;
526 if !self.owner_signing_key_exists(name)? {
527 self.store_owner_signing_key_current_only(name, &OwnerSigningKeyPair::generate()?)?;
528 }
529 if self.read_profile_history(name)?.is_none() {
530 self.store_private_key_generation(name, 1, keypair)?;
531 let signing_key = self.load_owner_signing_key(name)?;
532 self.store_owner_signing_key_generation(name, 1, &signing_key)?;
533 let now = unix_ms(SystemTime::now());
534 self.write_profile_history(&ProfileHistory {
535 name: name.to_string(),
536 active_generation: 1,
537 generations: vec![ProfileGeneration {
538 index: 1,
539 status: ProfileGenerationStatus::Active,
540 contact_fingerprint: contact_fingerprint(&keypair.public_key()),
541 created_at_unix_ms: now,
542 retired_at_unix_ms: None,
543 }],
544 })?;
545 }
546 Ok(())
547 }
548
549 pub fn restore_private_key(
555 &self,
556 name: &str,
557 keypair: &ContactKeyPair,
558 signing_key: Option<&OwnerSigningKeyPair>,
559 overwrite: bool,
560 ) -> Result<()> {
561 if self.private_key_exists(name)? {
562 if !overwrite {
563 return Err(Error::AlreadyExists(format!("vault profile {name}")));
564 }
565 self.delete_private_key(name)?;
566 }
567 if let Some(signing_key) = signing_key {
568 self.store_owner_signing_key_current_only(name, signing_key)?;
569 }
570 self.store_private_key(name, keypair)
571 }
572
573 #[doc(hidden)]
578 pub fn restore_profile_generations(
579 &self,
580 history: ProfileHistory,
581 generations: Vec<(u16, ContactKeyPair, OwnerSigningKeyPair)>,
582 email: Option<&str>,
583 overwrite: bool,
584 ) -> Result<()> {
585 if generations.is_empty() {
586 return Err(Error::InvalidInput(
587 "a migrated profile must contain at least one generation".to_string(),
588 ));
589 }
590 if history.generations.len() != generations.len()
591 || !history
592 .generations
593 .iter()
594 .all(|item| generations.iter().any(|(index, _, _)| *index == item.index))
595 {
596 return Err(Error::InvalidInput(
597 "migrated profile generation keys do not match its history".to_string(),
598 ));
599 }
600 let Some((_, active_key, active_signing)) = generations
601 .iter()
602 .find(|(index, _, _)| *index == history.active_generation)
603 else {
604 return Err(Error::InvalidInput(
605 "migrated profile active generation is missing".to_string(),
606 ));
607 };
608 if self.private_key_exists(&history.name)? {
609 if !overwrite {
610 return Err(Error::AlreadyExists(format!(
611 "vault profile {}",
612 history.name
613 )));
614 }
615 self.delete_private_key(&history.name)?;
616 }
617 self.store_private_key_current_only(&history.name, active_key)?;
618 self.store_owner_signing_key_current_only(&history.name, active_signing)?;
619 for (index, key, signing) in generations {
620 self.store_private_key_generation(&history.name, index, &key)?;
621 self.store_owner_signing_key_generation(&history.name, index, &signing)?;
622 }
623 self.write_profile_history(&history)?;
624 if let Some(email) = email {
625 self.store_profile_email(&history.name, email)?;
626 }
627 Ok(())
628 }
629
630 pub fn load_private_key(&self, name: &str) -> Result<ContactKeyPair> {
632 let variable_name = private_key_variable_name(name)?;
633 let secret = self
634 .lockbox
635 .borrow()
636 .with_secret_variable(&variable_name, SecretString::try_clone)?
637 .transpose()?
638 .ok_or_else(|| Error::NotFound(format!("vault private key {name}")))?;
639 let mut bytes = SecretVec::new();
640 secret.append_to_secure_vec(&mut bytes)?;
641 import_private_key(bytes)
642 }
643
644 pub fn load_owner_signing_key(&self, name: &str) -> Result<OwnerSigningKeyPair> {
650 let vault_id = self.path.to_string_lossy().into_owned();
651 if let Ok(Some(key)) = crate::get_owner_signing_key(&vault_id, name) {
652 return Ok(key);
653 }
654 if !self.owner_signing_key_exists(name)? {
655 self.store_owner_signing_key_current_only(name, &OwnerSigningKeyPair::generate()?)?;
656 }
657 let key = self.load_owner_signing_key_existing(name)?;
658 let _ = crate::put_owner_signing_key(&vault_id, name, key.try_clone()?, None);
659 Ok(key)
660 }
661
662 pub fn load_owner_signing_key_cached(&self, name: &str) -> Result<OwnerSigningKeyPair> {
665 self.load_owner_signing_key(name)
666 }
667
668 pub fn private_key_exists(&self, name: &str) -> Result<bool> {
670 let lockbox = self.lockbox.borrow();
671 Ok(lockbox
672 .variable_sensitivity(&private_key_variable_name(name)?)?
673 .is_some())
674 }
675
676 pub fn list_private_keys(&self) -> Result<Vec<String>> {
678 let mut names = Vec::new();
679 let lockbox = self.lockbox.borrow();
680 for (variable_name, _) in lockbox.list_variables()? {
681 let Some(name) = private_key_name_from_variable(&variable_name) else {
682 continue;
683 };
684 names.push(name?);
685 }
686 names.sort();
687 names.dedup();
688 Ok(names)
689 }
690
691 pub fn delete_private_key(&self, name: &str) -> Result<()> {
693 if let Some(history) = self.read_profile_history(name)? {
694 for generation in history.generations {
695 self.delete_secret_variable_record_if_exists(
696 &private_key_generation_variable_name(name, generation.index)?,
697 )?;
698 self.delete_secret_variable_record_if_exists(
699 &owner_signing_key_generation_variable_name(name, generation.index)?,
700 )?;
701 }
702 self.delete_record_if_exists(&profile_history_record_path(name)?)?;
703 }
704 self.delete_record_if_exists(&profile_email_record_path(name)?)?;
705 self.delete_secret_variable_record_if_exists(&owner_signing_key_variable_name(name)?)?;
706 self.delete_secret_variable_record_if_exists(&private_key_variable_name(name)?)?;
707 let vault_id = self.path.to_string_lossy().into_owned();
708 let _ = crate::forget_owner_signing_key(&vault_id, name);
709 Ok(())
710 }
711
712 pub fn store_profile_email(&self, name: &str, email: &str) -> Result<()> {
714 if !self.private_key_exists(name)? {
715 return Err(Error::NotFound(format!("vault private key {name}")));
716 }
717 self.put_record_replace(
718 &profile_email_record_path(name)?,
719 &encode_profile_email(email),
720 )
721 }
722
723 pub fn profile_email(&self, name: &str) -> Result<Option<String>> {
725 let path = profile_email_record_path(name)?;
726 {
727 let lockbox = self.lockbox.borrow();
728 if lockbox.stat(&path).is_none() {
729 return Ok(None);
730 }
731 }
732 decode_profile_email(&self.get_record(&path)?).map(Some)
733 }
734
735 pub fn list_profile_generations(&self, name: &str) -> Result<ProfileHistory> {
738 self.ensure_profile_history(name)
739 }
740
741 pub fn rotate_private_key(&self, name: &str) -> Result<ProfileHistory> {
743 let mut history = self.ensure_profile_history(name)?;
744 let now = unix_ms(SystemTime::now());
745 for generation in &mut history.generations {
746 if generation.status == ProfileGenerationStatus::Active {
747 generation.status = ProfileGenerationStatus::Retired;
748 generation.retired_at_unix_ms = Some(now);
749 }
750 }
751 let new_index = history
752 .generations
753 .iter()
754 .map(|generation| generation.index)
755 .max()
756 .unwrap_or(0)
757 .saturating_add(1);
758 let keypair = ContactKeyPair::generate()?;
759 let signing_key = OwnerSigningKeyPair::generate()?;
760 self.store_private_key_current_only(name, &keypair)?;
761 self.store_owner_signing_key_current_only(name, &signing_key)?;
762 self.store_private_key_generation(name, new_index, &keypair)?;
763 self.store_owner_signing_key_generation(name, new_index, &signing_key)?;
764 history.active_generation = new_index;
765 history.generations.push(ProfileGeneration {
766 index: new_index,
767 status: ProfileGenerationStatus::Active,
768 contact_fingerprint: contact_fingerprint(&keypair.public_key()),
769 created_at_unix_ms: now,
770 retired_at_unix_ms: None,
771 });
772 self.write_profile_history(&history)?;
773 let vault_id = self.path.to_string_lossy().into_owned();
774 let _ = crate::forget_owner_signing_key(&vault_id, name);
775 Ok(history)
776 }
777
778 pub fn load_private_key_generation(&self, name: &str, index: u16) -> Result<ContactKeyPair> {
780 let variable_name = private_key_generation_variable_name(name, index)?;
781 let secret = self
782 .lockbox
783 .borrow()
784 .with_secret_variable(&variable_name, SecretString::try_clone)?
785 .transpose()?
786 .ok_or_else(|| {
787 Error::NotFound(format!("vault private key {name} generation {index}"))
788 })?;
789 let mut bytes = SecretVec::new();
790 secret.append_to_secure_vec(&mut bytes)?;
791 import_private_key(bytes)
792 }
793
794 pub fn load_owner_signing_key_generation(
796 &self,
797 name: &str,
798 index: u16,
799 ) -> Result<OwnerSigningKeyPair> {
800 let variable_name = owner_signing_key_generation_variable_name(name, index)?;
801 let secret = self
802 .lockbox
803 .borrow()
804 .with_secret_variable(&variable_name, SecretString::try_clone)?
805 .transpose()?
806 .ok_or_else(|| {
807 Error::NotFound(format!("vault owner signing key {name} generation {index}"))
808 })?;
809 let mut bytes = SecretVec::new();
810 secret.append_to_secure_vec(&mut bytes)?;
811 decode_hex_secret_in_place(&mut bytes)?;
812 OwnerSigningKeyPair::from_private_key_record(bytes)
813 }
814
815 pub fn store_contact(&self, name: &str, key: &ContactPublicKey) -> Result<()> {
819 self.put_record(&contact_record_path(name)?, &key.to_bytes())
820 }
821
822 pub fn store_contact_signing_key(&self, name: &str, key: &OwnerSigningPublicKey) -> Result<()> {
824 self.put_record_replace(&contact_signing_record_path(name)?, &key.to_bytes())
825 }
826
827 pub fn load_contact(&self, name: &str) -> Result<ContactPublicKey> {
829 ContactPublicKey::from_bytes(&self.get_record(&contact_record_path(name)?)?)
830 }
831
832 pub fn load_contact_signing_key(&self, name: &str) -> Result<OwnerSigningPublicKey> {
834 OwnerSigningPublicKey::from_bytes(&self.get_record(&contact_signing_record_path(name)?)?)
835 }
836
837 pub fn contact_exists(&self, name: &str) -> Result<bool> {
839 Ok(self
840 .lockbox
841 .borrow()
842 .stat(&contact_record_path(name)?)
843 .is_some())
844 }
845
846 pub fn delete_contact(&self, name: &str) -> Result<()> {
848 self.delete_record_if_exists(&contact_signing_record_path(name)?)?;
849 self.delete_record_if_exists(&contact_record_path(name)?)
850 }
851
852 pub fn list_contacts(&self) -> Result<Vec<StoredContact>> {
854 let mut out = Vec::new();
855 for name in self.list_record_names("/contacts", ".pub")? {
856 if name.ends_with(".signing") {
857 continue;
858 }
859 out.push(StoredContact {
860 key: self.load_contact(&name)?,
861 name,
862 });
863 }
864 Ok(out)
865 }
866
867 pub fn store_key_directory_backup(
872 &self,
873 lockbox_id: LockboxId,
874 key_directory: &[u8],
875 ) -> Result<()> {
876 self.put_record_replace(
877 &key_directory_backup_record_path(lockbox_id)?,
878 key_directory,
879 )
880 }
881
882 pub fn load_key_directory_backup(&self, lockbox_id: LockboxId) -> Result<Vec<u8>> {
884 self.get_record(&key_directory_backup_record_path(lockbox_id)?)
885 }
886
887 pub fn key_directory_backup_count(&self) -> Result<usize> {
889 Ok(self
890 .lockbox
891 .borrow()
892 .list(recursive_list("/key_directories")?)?
893 .filter_map(Result::ok)
894 .filter(|entry| entry.kind == LockboxEntryKind::File)
895 .count())
896 }
897
898 pub fn remember_known_lockbox(
900 &self,
901 lockbox_id: LockboxId,
902 path: impl AsRef<Path>,
903 ) -> Result<()> {
904 let path = path.as_ref().to_string_lossy().to_string();
905 let record = KnownLockbox {
906 lockbox_id,
907 path,
908 last_seen_unix_ms: unix_ms(SystemTime::now()),
909 };
910 self.put_record_replace(
911 &known_lockbox_record_path(record.path.as_str())?,
912 &encode_known_lockbox(&record),
913 )
914 }
915
916 #[doc(hidden)]
918 pub fn restore_known_lockbox(&self, record: KnownLockbox) -> Result<()> {
919 self.put_record_replace(
920 &known_lockbox_record_path(record.path.as_str())?,
921 &encode_known_lockbox(&record),
922 )
923 }
924
925 pub fn list_known_lockboxes(&self) -> Result<Vec<KnownLockbox>> {
927 let mut out = Vec::new();
928 for name in self.list_record_names("/known_lockboxes", ".lkl")? {
929 let path = LockboxPath::new(format!("/known_lockboxes/{name}.lkl"))?;
930 out.push(decode_known_lockbox(&self.get_record(&path)?)?);
931 }
932 out.sort_by(|left, right| left.path.cmp(&right.path));
933 Ok(out)
934 }
935
936 pub fn forget_known_lockbox(&self, path: impl AsRef<Path>) -> Result<()> {
939 self.delete_record_if_exists(&known_lockbox_record_path(path.as_ref())?)
940 }
941
942 pub fn remember_access_slot_label(
948 &self,
949 lockbox_id: LockboxId,
950 slot_id: u64,
951 name: impl Into<String>,
952 ) -> Result<()> {
953 let label = AccessSlotLabel {
954 lockbox_id,
955 slot_id,
956 name: name.into(),
957 updated_at_unix_ms: unix_ms(SystemTime::now()),
958 };
959 self.put_record_replace(
960 &access_slot_label_record_path(lockbox_id, slot_id)?,
961 &encode_access_slot_label(&label),
962 )
963 }
964
965 #[doc(hidden)]
967 pub fn restore_access_slot_label(&self, label: AccessSlotLabel) -> Result<()> {
968 self.put_record_replace(
969 &access_slot_label_record_path(label.lockbox_id, label.slot_id)?,
970 &encode_access_slot_label(&label),
971 )
972 }
973
974 pub fn list_access_slot_labels(&self, lockbox_id: LockboxId) -> Result<Vec<AccessSlotLabel>> {
976 let root = access_slot_label_root(lockbox_id);
977 let mut out = Vec::new();
978 for slot_id in self.list_record_names(&root, ".lbas")? {
979 let slot_id = slot_id.parse::<u64>().map_err(|_| {
980 Error::CorruptVaultRecord(format!("access slot label id {slot_id} is not numeric"))
981 })?;
982 out.push(decode_access_slot_label(&self.get_record(
983 &access_slot_label_record_path(lockbox_id, slot_id)?,
984 )?)?);
985 }
986 out.sort_by(|left, right| {
987 left.name
988 .cmp(&right.name)
989 .then_with(|| left.slot_id.cmp(&right.slot_id))
990 });
991 Ok(out)
992 }
993
994 pub fn find_access_slot_labels(
996 &self,
997 lockbox_id: LockboxId,
998 name: &str,
999 ) -> Result<Vec<AccessSlotLabel>> {
1000 Ok(self
1001 .list_access_slot_labels(lockbox_id)?
1002 .into_iter()
1003 .filter(|label| label.name == name)
1004 .collect())
1005 }
1006
1007 pub fn forget_access_slot_label(&self, lockbox_id: LockboxId, slot_id: u64) -> Result<()> {
1009 self.delete_record_if_exists(&access_slot_label_record_path(lockbox_id, slot_id)?)
1010 }
1011
1012 pub fn define_form(
1014 &self,
1015 alias: &str,
1016 name: &str,
1017 fields: Vec<FormFieldDefinition>,
1018 ) -> Result<FormDefinition> {
1019 let _guard = VaultFileLock::acquire(&self.path)?;
1020 let mut lockbox = self.lockbox.borrow_mut();
1021 let definition = lockbox.define_form(alias, name, fields)?;
1022 lockbox.commit()?;
1023 set_private_file_permissions(&self.path)?;
1024 Ok(definition)
1025 }
1026
1027 pub fn define_form_with_description(
1029 &self,
1030 alias: &str,
1031 name: &str,
1032 description: &str,
1033 fields: Vec<FormFieldDefinition>,
1034 ) -> Result<FormDefinition> {
1035 let _guard = VaultFileLock::acquire(&self.path)?;
1036 let mut lockbox = self.lockbox.borrow_mut();
1037 let definition = lockbox.define_form_with_description(alias, name, description, fields)?;
1038 lockbox.commit()?;
1039 set_private_file_permissions(&self.path)?;
1040 Ok(definition)
1041 }
1042
1043 pub fn define_form_with_type_id(
1045 &self,
1046 type_id: FormTypeId,
1047 alias: &str,
1048 name: &str,
1049 fields: Vec<FormFieldDefinition>,
1050 ) -> Result<FormDefinition> {
1051 let _guard = VaultFileLock::acquire(&self.path)?;
1052 let mut lockbox = self.lockbox.borrow_mut();
1053 let definition = lockbox.define_form_with_type_id(type_id, alias, name, fields)?;
1054 lockbox.commit()?;
1055 set_private_file_permissions(&self.path)?;
1056 Ok(definition)
1057 }
1058
1059 pub fn define_form_with_type_id_and_description(
1061 &self,
1062 type_id: FormTypeId,
1063 alias: &str,
1064 name: &str,
1065 description: &str,
1066 fields: Vec<FormFieldDefinition>,
1067 ) -> Result<FormDefinition> {
1068 let _guard = VaultFileLock::acquire(&self.path)?;
1069 let mut lockbox = self.lockbox.borrow_mut();
1070 let definition = lockbox.define_form_with_type_id_and_description(
1071 type_id,
1072 alias,
1073 name,
1074 description,
1075 fields,
1076 )?;
1077 lockbox.commit()?;
1078 set_private_file_permissions(&self.path)?;
1079 Ok(definition)
1080 }
1081
1082 pub fn import_form_definition(&self, definition: FormDefinition) -> Result<FormDefinition> {
1084 let _guard = VaultFileLock::acquire(&self.path)?;
1085 let mut lockbox = self.lockbox.borrow_mut();
1086 let definition = lockbox.import_form_definition(definition)?;
1087 lockbox.commit()?;
1088 set_private_file_permissions(&self.path)?;
1089 Ok(definition)
1090 }
1091
1092 pub fn resolve_form_definition(&self, reference: &str) -> Result<FormDefinition> {
1094 self.lockbox.borrow().resolve_form_definition(reference)
1095 }
1096
1097 pub fn list_form_definitions(&self) -> Result<Vec<FormDefinition>> {
1099 self.lockbox.borrow().list_form_definitions()
1100 }
1101
1102 pub fn list_form_definition_revisions(
1104 &self,
1105 type_id: &FormTypeId,
1106 ) -> Result<Vec<FormDefinition>> {
1107 self.lockbox
1108 .borrow()
1109 .list_form_definition_revisions(type_id)
1110 }
1111
1112 pub fn seed_default_form_definitions(&self) -> Result<usize> {
1114 let existing = self
1115 .list_form_definitions()?
1116 .into_iter()
1117 .map(|definition| definition.alias)
1118 .collect::<Vec<_>>();
1119 let mut seeded = 0usize;
1120 for template in default_form_templates() {
1121 if existing.iter().any(|alias| alias == template.alias) {
1122 continue;
1123 }
1124 self.define_form_with_type_id(
1125 FormTypeId::new(template.type_id)?,
1126 template.alias,
1127 template.name,
1128 template.fields,
1129 )?;
1130 seeded += 1;
1131 }
1132 Ok(seeded)
1133 }
1134
1135 pub fn remember_lockbox_password(
1137 &self,
1138 lockbox_id: LockboxId,
1139 password: &SecretString,
1140 ) -> Result<()> {
1141 self.put_secret_variable_record(&lockbox_password_variable_name(lockbox_id)?, password)
1142 }
1143
1144 pub fn remembered_lockbox_password(
1146 &self,
1147 lockbox_id: LockboxId,
1148 ) -> Result<Option<SecretString>> {
1149 Ok(self
1150 .lockbox
1151 .borrow()
1152 .with_secret_variable(
1153 &lockbox_password_variable_name(lockbox_id)?,
1154 SecretString::try_clone,
1155 )?
1156 .transpose()?)
1157 }
1158
1159 fn put_record(&self, path: &LockboxPath, bytes: &[u8]) -> Result<()> {
1160 self.put_record_with_replace(path, bytes, false)
1161 }
1162
1163 fn put_record_replace(&self, path: &LockboxPath, bytes: &[u8]) -> Result<()> {
1164 self.put_record_with_replace(path, bytes, true)
1165 }
1166
1167 fn put_record_with_replace(
1168 &self,
1169 path: &LockboxPath,
1170 bytes: &[u8],
1171 replace: bool,
1172 ) -> Result<()> {
1173 let _guard = VaultFileLock::acquire(&self.path)?;
1174 let mut lockbox = self.lockbox.borrow_mut();
1175 let replace = replace && lockbox.stat(path).is_some();
1176 lockbox.create_parent_dirs_for(path)?;
1177 lockbox.add_file(path, bytes, replace)?;
1178 lockbox.commit()?;
1179 set_private_file_permissions(&self.path)?;
1180 Ok(())
1181 }
1182
1183 fn put_secret_variable_record(&self, name: &VariableName, value: &SecretString) -> Result<()> {
1184 let _guard = VaultFileLock::acquire(&self.path)?;
1185 let mut lockbox = self.lockbox.borrow_mut();
1186 lockbox.set_secret_variable(name, value)?;
1187 lockbox.commit()?;
1188 set_private_file_permissions(&self.path)?;
1189 Ok(())
1190 }
1191
1192 fn get_record(&self, path: &LockboxPath) -> Result<Vec<u8>> {
1193 self.lockbox.borrow().get_file(path)
1194 }
1195
1196 fn delete_record_if_exists(&self, path: &LockboxPath) -> Result<()> {
1197 let _guard = VaultFileLock::acquire(&self.path)?;
1198 let mut lockbox = self.lockbox.borrow_mut();
1199 if lockbox.stat(path).is_some() {
1200 lockbox.delete(path)?;
1201 lockbox.commit()?;
1202 set_private_file_permissions(&self.path)?;
1203 }
1204 Ok(())
1205 }
1206
1207 fn delete_secret_variable_record_if_exists(&self, name: &VariableName) -> Result<()> {
1208 let _guard = VaultFileLock::acquire(&self.path)?;
1209 let mut lockbox = self.lockbox.borrow_mut();
1210 if lockbox.variable_sensitivity(name)?.is_some() {
1211 lockbox.delete_variable(name)?;
1212 lockbox.commit()?;
1213 set_private_file_permissions(&self.path)?;
1214 }
1215 Ok(())
1216 }
1217
1218 fn list_record_names(&self, root: &str, extension: &str) -> Result<Vec<String>> {
1219 let mut out = Vec::new();
1220 for entry in self.lockbox.borrow().list(recursive_list(root)?)? {
1221 let entry = entry?;
1222 if entry.kind != LockboxEntryKind::File || !entry.path.ends_with(extension) {
1223 continue;
1224 }
1225 let name = entry
1226 .path
1227 .rsplit('/')
1228 .next()
1229 .and_then(|file| file.strip_suffix(extension))
1230 .ok_or_else(|| {
1231 Error::CorruptVaultRecord(format!(
1232 "record path {} does not end with expected extension {extension}",
1233 entry.path
1234 ))
1235 })?;
1236 out.push(name.to_string());
1237 }
1238 out.sort();
1239 Ok(out)
1240 }
1241
1242 fn ensure_structure_version(&self, initialize_missing: bool) -> Result<()> {
1243 match self.read_structure_version()? {
1244 Some(CURRENT_VAULT_STRUCTURE_VERSION) => Ok(()),
1245 Some(version) => Err(Error::UnsupportedFormatVersion {
1246 artifact: ArtifactKind::Vault,
1247 found: version,
1248 supported: CURRENT_VAULT_STRUCTURE_VERSION,
1249 }),
1250 None if initialize_missing => self.write_structure_version(CURRENT_VAULT_STRUCTURE_VERSION),
1251 None => Err(Error::Configuration(
1252 "local vault structure version is missing; recreate the vault with this reVault build"
1253 .to_string(),
1254 )),
1255 }
1256 }
1257
1258 fn read_structure_version(&self) -> Result<Option<u32>> {
1259 let path = vault_structure_version_record_path()?;
1260 {
1261 let lockbox = self.lockbox.borrow();
1262 if lockbox.stat(&path).is_none() {
1263 return Ok(None);
1264 }
1265 }
1266 decode_structure_version(&self.get_record(&path)?).map(Some)
1267 }
1268
1269 fn write_structure_version(&self, version: u32) -> Result<()> {
1270 let bytes = format!("{version}\n");
1271 self.put_record_replace(&vault_structure_version_record_path()?, bytes.as_bytes())
1272 }
1273
1274 fn store_private_key_current_only(&self, name: &str, keypair: &ContactKeyPair) -> Result<()> {
1275 let private_record = export_private_key(keypair, KeyFormat::RawHex)?;
1276 let value = SecretString::from_secure_vec(private_record);
1277 self.put_secret_variable_record(&private_key_variable_name(name)?, &value)
1278 }
1279
1280 fn owner_signing_key_exists(&self, name: &str) -> Result<bool> {
1281 let lockbox = self.lockbox.borrow();
1282 Ok(lockbox
1283 .variable_sensitivity(&owner_signing_key_variable_name(name)?)?
1284 .is_some())
1285 }
1286
1287 fn load_owner_signing_key_existing(&self, name: &str) -> Result<OwnerSigningKeyPair> {
1288 load_owner_signing_key_existing_from_lockbox(&self.lockbox.borrow(), name)
1289 }
1290
1291 fn store_owner_signing_key_current_only(
1292 &self,
1293 name: &str,
1294 keypair: &OwnerSigningKeyPair,
1295 ) -> Result<()> {
1296 let value =
1297 SecretString::from_secure_vec(hex_encode_secret(keypair.private_key_record()?)?);
1298 self.put_secret_variable_record(&owner_signing_key_variable_name(name)?, &value)
1299 }
1300
1301 fn store_private_key_generation(
1302 &self,
1303 name: &str,
1304 index: u16,
1305 keypair: &ContactKeyPair,
1306 ) -> Result<()> {
1307 let private_record = export_private_key(keypair, KeyFormat::RawHex)?;
1308 let value = SecretString::from_secure_vec(private_record);
1309 self.put_secret_variable_record(&private_key_generation_variable_name(name, index)?, &value)
1310 }
1311
1312 fn store_owner_signing_key_generation(
1313 &self,
1314 name: &str,
1315 index: u16,
1316 keypair: &OwnerSigningKeyPair,
1317 ) -> Result<()> {
1318 let value =
1319 SecretString::from_secure_vec(hex_encode_secret(keypair.private_key_record()?)?);
1320 self.put_secret_variable_record(
1321 &owner_signing_key_generation_variable_name(name, index)?,
1322 &value,
1323 )
1324 }
1325
1326 fn ensure_profile_history(&self, name: &str) -> Result<ProfileHistory> {
1327 if let Some(history) = self.read_profile_history(name)? {
1328 return Ok(history);
1329 }
1330 let keypair = self.load_private_key(name)?;
1331 self.store_private_key_generation(name, 1, &keypair)?;
1332 let signing_key = self.load_owner_signing_key(name)?;
1333 self.store_owner_signing_key_generation(name, 1, &signing_key)?;
1334 let history = ProfileHistory {
1335 name: name.to_string(),
1336 active_generation: 1,
1337 generations: vec![ProfileGeneration {
1338 index: 1,
1339 status: ProfileGenerationStatus::Active,
1340 contact_fingerprint: contact_fingerprint(&keypair.public_key()),
1341 created_at_unix_ms: unix_ms(SystemTime::now()),
1342 retired_at_unix_ms: None,
1343 }],
1344 };
1345 self.write_profile_history(&history)?;
1346 Ok(history)
1347 }
1348
1349 fn read_profile_history(&self, name: &str) -> Result<Option<ProfileHistory>> {
1350 let path = profile_history_record_path(name)?;
1351 {
1352 let lockbox = self.lockbox.borrow();
1353 if lockbox.stat(&path).is_none() {
1354 return Ok(None);
1355 }
1356 }
1357 decode_profile_history(name, &self.get_record(&path)?).map(Some)
1358 }
1359
1360 fn write_profile_history(&self, history: &ProfileHistory) -> Result<()> {
1361 self.put_record_replace(
1362 &profile_history_record_path(&history.name)?,
1363 &encode_profile_history(history),
1364 )
1365 }
1366}
1367
1368pub fn backup_default_vault(
1373 output: impl AsRef<Path>,
1374 overwrite: bool,
1375) -> Result<VaultBackupManifest> {
1376 let root = default_vault_dir()?;
1377 let path = root.join(VAULT_FILE_NAME);
1378 if !path.exists() {
1379 return Err(Error::VaultUnavailable(
1380 "local vault is not initialized; run `lockbox vault init` first".to_string(),
1381 ));
1382 }
1383 let _guard = VaultFileLock::acquire(&path)?;
1384 let vault_bytes = fs::read(&path).map_err(|err| Error::Io(err.to_string()))?;
1385 let digest: [u8; 32] = Sha256::digest(&vault_bytes).into();
1386 let manifest = VaultBackupManifest {
1387 format_version: 1,
1388 created_at_unix_ms: unix_ms(SystemTime::now()),
1389 vault_file_name: VAULT_FILE_NAME.to_string(),
1390 vault_size: vault_bytes.len() as u64,
1391 vault_sha256: crate::encode_hex(&digest),
1392 };
1393 write_vault_backup_archive(output.as_ref(), overwrite, &manifest, &vault_bytes)?;
1394 Ok(manifest)
1395}
1396
1397pub fn restore_default_vault(
1401 input: impl AsRef<Path>,
1402 overwrite: bool,
1403) -> Result<VaultBackupManifest> {
1404 let (manifest, vault_bytes) = read_vault_backup_archive(input.as_ref())?;
1405 let root = default_vault_dir()?;
1406 create_private_dir(&root)?;
1407 let path = root.join(VAULT_FILE_NAME);
1408 let _guard = VaultFileLock::acquire(&path)?;
1409 if path.exists() && !overwrite {
1410 return Err(Error::AlreadyExists(format!(
1411 "{}; pass --overwrite to replace it",
1412 path.display()
1413 )));
1414 }
1415 let tmp = root.join("local-vault.lbox.restore.tmp");
1416 fs::write(&tmp, vault_bytes).map_err(|err| Error::Io(err.to_string()))?;
1417 set_private_file_permissions(&tmp)?;
1418 if path.exists() {
1419 fs::remove_file(&path).map_err(|err| Error::Io(err.to_string()))?;
1420 }
1421 fs::rename(&tmp, &path).map_err(|err| Error::Io(err.to_string()))?;
1422 set_private_file_permissions(&path)?;
1423 Ok(manifest)
1424}
1425
1426fn write_vault_backup_archive(
1427 output: &Path,
1428 overwrite: bool,
1429 manifest: &VaultBackupManifest,
1430 vault_bytes: &[u8],
1431) -> Result<()> {
1432 if output.exists() && !overwrite {
1433 return Err(Error::AlreadyExists(format!(
1434 "{}; pass --overwrite to replace it",
1435 output.display()
1436 )));
1437 }
1438 let manifest_bytes = serde_json::to_vec(manifest).map_err(|err| Error::Io(err.to_string()))?;
1439 let mut options = OpenOptions::new();
1440 options.write(true).create(true).truncate(true);
1441 if !overwrite {
1442 options.create_new(true);
1443 }
1444 let mut file = options
1445 .open(output)
1446 .map_err(|err| Error::Io(err.to_string()))?;
1447 file.write_all(VAULT_BACKUP_MAGIC)
1448 .map_err(|err| Error::Io(err.to_string()))?;
1449 file.write_all(&(manifest_bytes.len() as u64).to_be_bytes())
1450 .map_err(|err| Error::Io(err.to_string()))?;
1451 file.write_all(&manifest_bytes)
1452 .map_err(|err| Error::Io(err.to_string()))?;
1453 file.write_all(vault_bytes)
1454 .map_err(|err| Error::Io(err.to_string()))?;
1455 file.sync_all().map_err(|err| Error::Io(err.to_string()))
1456}
1457
1458fn read_vault_backup_archive(input: &Path) -> Result<(VaultBackupManifest, Vec<u8>)> {
1459 let mut file = File::open(input).map_err(|err| Error::Io(err.to_string()))?;
1460 let mut magic = [0u8; 8];
1461 file.read_exact(&mut magic)
1462 .map_err(|err| Error::Io(err.to_string()))?;
1463 if &magic != VAULT_BACKUP_MAGIC {
1464 return Err(Error::InvalidInput(
1465 "backup file is not a reVault vault backup archive".to_string(),
1466 ));
1467 }
1468 let mut len = [0u8; 8];
1469 file.read_exact(&mut len)
1470 .map_err(|err| Error::Io(err.to_string()))?;
1471 let manifest_len = u64::from_be_bytes(len);
1472 if manifest_len > 1024 * 1024 {
1473 return Err(Error::SecurityLimitExceeded(
1474 "vault backup manifest is too large".to_string(),
1475 ));
1476 }
1477 let mut manifest_bytes = vec![0u8; manifest_len as usize];
1478 file.read_exact(&mut manifest_bytes)
1479 .map_err(|err| Error::Io(err.to_string()))?;
1480 let manifest: VaultBackupManifest = serde_json::from_slice(&manifest_bytes)
1481 .map_err(|err| Error::InvalidInput(err.to_string()))?;
1482 if manifest.format_version != 1 {
1483 return Err(Error::InvalidInput(format!(
1484 "vault backup format version {} is not supported",
1485 manifest.format_version
1486 )));
1487 }
1488 if manifest.vault_file_name != VAULT_FILE_NAME {
1489 return Err(Error::InvalidInput(format!(
1490 "vault backup contains unexpected file {}",
1491 manifest.vault_file_name
1492 )));
1493 }
1494 let mut vault_bytes = Vec::new();
1495 file.read_to_end(&mut vault_bytes)
1496 .map_err(|err| Error::Io(err.to_string()))?;
1497 if vault_bytes.len() as u64 != manifest.vault_size {
1498 return Err(Error::InvalidInput(
1499 "vault backup size does not match manifest".to_string(),
1500 ));
1501 }
1502 let digest: [u8; 32] = Sha256::digest(&vault_bytes).into();
1503 if crate::encode_hex(&digest) != manifest.vault_sha256 {
1504 return Err(Error::InvalidInput(
1505 "vault backup checksum does not match manifest".to_string(),
1506 ));
1507 }
1508 Ok((manifest, vault_bytes))
1509}
1510
1511struct VaultFileLock {
1512 lock: Option<ScopedFileLock>,
1513 active: bool,
1514}
1515
1516impl VaultFileLock {
1517 fn acquire(path: &Path) -> Result<Self> {
1518 let nested = VAULT_LOCK_DEPTH.with(|depth| {
1519 let value = depth.get();
1520 depth.set(value.saturating_add(1));
1521 value > 0
1522 });
1523 if nested {
1524 return Ok(Self {
1525 lock: None,
1526 active: true,
1527 });
1528 }
1529 let lock = ScopedFileLock::acquire(path, FileLockScope::Vault).inspect_err(|_| {
1530 VAULT_LOCK_DEPTH.with(|depth| depth.set(depth.get().saturating_sub(1)));
1531 })?;
1532 Ok(Self {
1533 lock: Some(lock),
1534 active: true,
1535 })
1536 }
1537}
1538
1539impl Drop for VaultFileLock {
1540 fn drop(&mut self) {
1541 if !self.active {
1542 return;
1543 }
1544 self.lock.take();
1545 VAULT_LOCK_DEPTH.with(|depth| depth.set(depth.get().saturating_sub(1)));
1546 }
1547}
1548
1549fn recursive_list(path: &str) -> Result<ListOptions> {
1550 let path = LockboxPath::new(path)?;
1551 let mut options = ListOptions::new(&path);
1552 options.recursive = true;
1553 Ok(options)
1554}
1555
1556fn list_read_only_record_names(
1557 lockbox: &RefCell<Lockbox<ReadOnly>>,
1558 root: &str,
1559 extension: &str,
1560) -> Result<Vec<String>> {
1561 let mut out = Vec::new();
1562 for entry in lockbox.borrow().list(recursive_list(root)?)? {
1563 let entry = entry?;
1564 if entry.kind != LockboxEntryKind::File || !entry.path.ends_with(extension) {
1565 continue;
1566 }
1567 let name = entry
1568 .path
1569 .rsplit('/')
1570 .next()
1571 .and_then(|file| file.strip_suffix(extension))
1572 .ok_or_else(|| {
1573 Error::CorruptVaultRecord(format!(
1574 "record path {} does not end with expected extension {extension}",
1575 entry.path
1576 ))
1577 })?;
1578 out.push(name.to_string());
1579 }
1580 out.sort();
1581 Ok(out)
1582}
1583
1584fn private_key_variable_name(name: &str) -> Result<VariableName> {
1585 let name = validate_record_name(name)?;
1586 VariableName::new(format!(
1587 "LOCKBOX_VAULT_PRIVATE_KEY_{}",
1588 encode_name_hex(name)
1589 ))
1590}
1591
1592fn private_key_generation_variable_name(name: &str, index: u16) -> Result<VariableName> {
1593 let name = validate_record_name(name)?;
1594 VariableName::new(format!(
1595 "LOCKBOX_VAULT_PRIVATE_KEY_{}_GEN_{index:04}",
1596 encode_name_hex(name)
1597 ))
1598}
1599
1600fn open_vault_lockbox_for_write(path: &Path, password: &SecretString) -> Result<Lockbox> {
1601 Lockbox::open_for_write_with_signing_key_assuming_locked(
1602 path,
1603 LockboxOpen::Password(password),
1604 |lockbox| {
1605 load_owner_signing_key_existing_from_lockbox(lockbox, VaultDirectory::DEFAULT_KEY_NAME)
1606 .or_else(|err| match err {
1607 Error::NotFound(_) => OwnerSigningKeyPair::generate(),
1608 other => Err(other),
1609 })
1610 },
1611 )
1612}
1613
1614fn load_owner_signing_key_existing_from_lockbox<State>(
1615 lockbox: &Lockbox<State>,
1616 name: &str,
1617) -> Result<OwnerSigningKeyPair> {
1618 let secret = lockbox
1619 .with_secret_variable(
1620 &owner_signing_key_variable_name(name)?,
1621 SecretString::try_clone,
1622 )?
1623 .transpose()?
1624 .ok_or_else(|| Error::NotFound(format!("vault owner signing key {name}")))?;
1625 let mut bytes = SecretVec::new();
1626 secret.append_to_secure_vec(&mut bytes)?;
1627 decode_hex_secret_in_place(&mut bytes)?;
1628 OwnerSigningKeyPair::from_private_key_record(bytes)
1629}
1630
1631fn owner_signing_key_variable_name(name: &str) -> Result<VariableName> {
1632 let name = validate_record_name(name)?;
1633 VariableName::new(format!(
1634 "LOCKBOX_VAULT_SIGNING_KEY_{}",
1635 encode_name_hex(name)
1636 ))
1637}
1638
1639fn owner_signing_key_generation_variable_name(name: &str, index: u16) -> Result<VariableName> {
1640 let name = validate_record_name(name)?;
1641 VariableName::new(format!(
1642 "LOCKBOX_VAULT_SIGNING_KEY_{}_GEN_{index:04}",
1643 encode_name_hex(name)
1644 ))
1645}
1646
1647fn private_key_name_from_variable(name: &str) -> Option<Result<String>> {
1648 let name = name.strip_prefix('/').unwrap_or(name);
1649 let hex = name.strip_prefix("LOCKBOX_VAULT_PRIVATE_KEY_")?;
1650 if hex.contains("_GEN_") {
1651 return None;
1652 }
1653 Some(decode_name_hex(hex).ok_or_else(|| {
1654 Error::CorruptVaultRecord(format!("private key record name is not valid hex: {name}"))
1655 }))
1656}
1657
1658struct DefaultFormTemplate {
1659 type_id: &'static str,
1660 alias: &'static str,
1661 name: &'static str,
1662 fields: Vec<FormFieldDefinition>,
1663}
1664
1665fn default_form_templates() -> Vec<DefaultFormTemplate> {
1666 vec![
1667 DefaultFormTemplate {
1668 type_id: "00000000-0000-4000-8000-000000000001",
1669 alias: "login",
1670 name: "Login",
1671 fields: vec![
1672 form_field("username", "Username", FormFieldKind::Text, false),
1673 form_field("password", "Password", FormFieldKind::Secret, true),
1674 form_field("url", "Website", FormFieldKind::Url, false),
1675 form_field("notes", "Notes", FormFieldKind::Notes, false),
1676 ],
1677 },
1678 DefaultFormTemplate {
1679 type_id: "00000000-0000-4000-8000-000000000002",
1680 alias: "payment-card",
1681 name: "Payment Card",
1682 fields: vec![
1683 form_field("cardholder", "Cardholder", FormFieldKind::Text, true),
1684 form_field("number", "Card number", FormFieldKind::Secret, true),
1685 form_field("expiry", "Expiry", FormFieldKind::Month, false),
1686 form_field("cvv", "CVV", FormFieldKind::Secret, false),
1687 form_field("pin", "PIN", FormFieldKind::Secret, false),
1688 form_field("notes", "Notes", FormFieldKind::Notes, false),
1689 ],
1690 },
1691 DefaultFormTemplate {
1692 type_id: "00000000-0000-4000-8000-000000000003",
1693 alias: "bank-account",
1694 name: "Bank Account",
1695 fields: vec![
1696 form_field("bank", "Bank", FormFieldKind::Text, false),
1697 form_field("account_name", "Account name", FormFieldKind::Text, false),
1698 form_field("bsb", "BSB / routing", FormFieldKind::Text, false),
1699 form_field(
1700 "account_number",
1701 "Account number",
1702 FormFieldKind::Secret,
1703 true,
1704 ),
1705 form_field("iban", "IBAN", FormFieldKind::Secret, false),
1706 form_field("swift", "SWIFT / BIC", FormFieldKind::Text, false),
1707 form_field("notes", "Notes", FormFieldKind::Notes, false),
1708 ],
1709 },
1710 DefaultFormTemplate {
1711 type_id: "00000000-0000-4000-8000-000000000004",
1712 alias: "profile",
1713 name: "Profile Document",
1714 fields: vec![
1715 form_field("full_name", "Full name", FormFieldKind::Text, true),
1716 form_field("date_of_birth", "Date of birth", FormFieldKind::Date, false),
1717 form_field("email", "Email", FormFieldKind::Email, false),
1718 form_field("phone", "Phone", FormFieldKind::Text, false),
1719 form_field(
1720 "document_number",
1721 "Document number",
1722 FormFieldKind::Secret,
1723 false,
1724 ),
1725 form_field("expiry", "Expiry", FormFieldKind::Date, false),
1726 form_field("address", "Address", FormFieldKind::Notes, false),
1727 form_field("notes", "Notes", FormFieldKind::Notes, false),
1728 ],
1729 },
1730 DefaultFormTemplate {
1731 type_id: "00000000-0000-4000-8000-000000000005",
1732 alias: "server",
1733 name: "Server",
1734 fields: vec![
1735 form_field("host", "Host", FormFieldKind::Text, true),
1736 form_field("port", "Port", FormFieldKind::Number, false),
1737 form_field("username", "Username", FormFieldKind::Text, false),
1738 form_field("password", "Password", FormFieldKind::Secret, false),
1739 form_field("url", "URL", FormFieldKind::Url, false),
1740 form_field("ssh_key", "SSH key", FormFieldKind::Secret, false),
1741 form_field("notes", "Notes", FormFieldKind::Notes, false),
1742 ],
1743 },
1744 DefaultFormTemplate {
1745 type_id: "00000000-0000-4000-8000-000000000006",
1746 alias: "wifi",
1747 name: "Wi-Fi Network",
1748 fields: vec![
1749 form_field("ssid", "SSID", FormFieldKind::Text, true),
1750 form_field("password", "Password", FormFieldKind::Secret, false),
1751 form_field("security", "Security", FormFieldKind::Text, false),
1752 form_field("notes", "Notes", FormFieldKind::Notes, false),
1753 ],
1754 },
1755 DefaultFormTemplate {
1756 type_id: "00000000-0000-4000-8000-000000000007",
1757 alias: "secure-note",
1758 name: "Secure Note",
1759 fields: vec![
1760 form_field("title", "Title", FormFieldKind::Text, true),
1761 form_field("note", "Note", FormFieldKind::Notes, true),
1762 ],
1763 },
1764 ]
1765}
1766
1767fn form_field(
1768 id: &'static str,
1769 label: &'static str,
1770 kind: FormFieldKind,
1771 required: bool,
1772) -> FormFieldDefinition {
1773 FormFieldDefinition {
1774 id: id.to_string(),
1775 label: label.to_string(),
1776 kind,
1777 required,
1778 }
1779}
1780
1781fn contact_record_path(name: &str) -> Result<LockboxPath> {
1782 LockboxPath::new(format!("/contacts/{}.pub", validate_record_name(name)?))
1783}
1784
1785fn contact_signing_record_path(name: &str) -> Result<LockboxPath> {
1786 LockboxPath::new(format!(
1787 "/contacts/{}.signing.pub",
1788 validate_record_name(name)?
1789 ))
1790}
1791
1792fn profile_history_record_path(name: &str) -> Result<LockboxPath> {
1793 LockboxPath::new(format!(
1794 "/profile_histories/{}.lbih",
1795 validate_record_name(name)?
1796 ))
1797}
1798
1799fn profile_email_record_path(name: &str) -> Result<LockboxPath> {
1800 LockboxPath::new(format!(
1801 "/profile_emails/{}.lbie",
1802 validate_record_name(name)?
1803 ))
1804}
1805
1806fn key_directory_backup_record_path(lockbox_id: LockboxId) -> Result<LockboxPath> {
1807 LockboxPath::new(format!("/key_directories/{lockbox_id}.keydir"))
1808}
1809
1810fn lockbox_password_variable_name(lockbox_id: LockboxId) -> Result<VariableName> {
1811 VariableName::new(format!(
1812 "LOCKBOX_VAULT_LOCKBOX_PASSWORD_{}",
1813 crate::encode_hex(lockbox_id.as_bytes())
1814 ))
1815}
1816
1817fn known_lockbox_record_path(path: impl AsRef<Path>) -> Result<LockboxPath> {
1818 let mut hasher = Sha256::new();
1819 hasher.update(path.as_ref().to_string_lossy().as_bytes());
1820 let digest: [u8; 32] = hasher.finalize().into();
1821 let encoded = crate::encode_hex(&digest);
1822 LockboxPath::new(format!("/known_lockboxes/{encoded}.lkl"))
1823}
1824
1825fn access_slot_label_root(lockbox_id: LockboxId) -> String {
1826 format!("/access_slots/{}", crate::encode_hex(lockbox_id.as_bytes()))
1827}
1828
1829fn access_slot_label_record_path(lockbox_id: LockboxId, slot_id: u64) -> Result<LockboxPath> {
1830 LockboxPath::new(format!(
1831 "{}/{slot_id}.lbas",
1832 access_slot_label_root(lockbox_id)
1833 ))
1834}
1835
1836fn vault_structure_version_record_path() -> Result<LockboxPath> {
1837 LockboxPath::new(VAULT_STRUCTURE_VERSION_PATH)
1838}
1839
1840fn decode_structure_version(bytes: &[u8]) -> Result<u32> {
1841 let text = std::str::from_utf8(bytes).map_err(|_| {
1842 Error::CorruptVaultRecord("vault structure version is not valid UTF-8".to_string())
1843 })?;
1844 let text = text.strip_suffix('\n').unwrap_or(text);
1845 if text.is_empty() || !text.bytes().all(|byte| byte.is_ascii_digit()) {
1846 return Err(Error::CorruptVaultRecord(
1847 "vault structure version is not a decimal integer".to_string(),
1848 ));
1849 }
1850 text.parse::<u32>()
1851 .map_err(|_| Error::CorruptVaultRecord("vault structure version is too large".to_string()))
1852}
1853
1854fn hex_encode_secret(mut bytes: SecretVec) -> Result<SecretVec> {
1855 let original_len = bytes.len();
1856 bytes.resize_zeroed(original_len * 2)?;
1857 bytes.with_mut_bytes(|bytes| {
1858 for index in (0..original_len).rev() {
1859 let byte = bytes[index];
1860 bytes[index * 2] = secret_hex_char(byte >> 4);
1861 bytes[index * 2 + 1] = secret_hex_char(byte & 0x0f);
1862 }
1863 })?;
1864 Ok(bytes)
1865}
1866
1867fn decode_hex_secret_in_place(bytes: &mut SecretVec) -> Result<()> {
1868 bytes.with_mut_bytes(|bytes| {
1869 let len = bytes.len();
1870 if len % 2 != 0 {
1871 return Err(Error::InvalidKeyMaterial(
1872 "owner signing key hex has odd length".to_string(),
1873 ));
1874 }
1875 let mut write = 0usize;
1876 let mut read = 0usize;
1877 while read < len {
1878 let high = secret_hex_digit(bytes[read])?;
1879 let low = secret_hex_digit(bytes[read + 1])?;
1880 bytes[write] = (high << 4) | low;
1881 write += 1;
1882 read += 2;
1883 }
1884 for byte in &mut bytes[write..] {
1885 *byte = 0;
1886 }
1887 Ok::<_, Error>(write)
1888 })??;
1889 bytes.truncate(bytes.len() / 2)?;
1890 Ok(())
1891}
1892
1893fn secret_hex_digit(byte: u8) -> Result<u8> {
1894 match byte {
1895 b'0'..=b'9' => Ok(byte - b'0'),
1896 b'a'..=b'f' => Ok(byte - b'a' + 10),
1897 b'A'..=b'F' => Ok(byte - b'A' + 10),
1898 _ => Err(Error::InvalidKeyMaterial(
1899 "owner signing key hex contains non-hex digits".to_string(),
1900 )),
1901 }
1902}
1903
1904fn secret_hex_char(value: u8) -> u8 {
1905 b"0123456789abcdef"[value as usize]
1906}
1907
1908fn encode_known_lockbox(record: &KnownLockbox) -> Vec<u8> {
1909 let mut out = Vec::new();
1910 out.extend_from_slice(KNOWN_LOCKBOX_MAGIC);
1911 put_u16(&mut out, KNOWN_LOCKBOX_VERSION);
1912 out.extend_from_slice(record.lockbox_id.as_bytes());
1913 put_string(&mut out, &record.path);
1914 put_u64(&mut out, record.last_seen_unix_ms);
1915 out
1916}
1917
1918fn decode_known_lockbox(bytes: &[u8]) -> Result<KnownLockbox> {
1919 let mut reader = BinaryReader::new(bytes);
1920 if reader.bytes(4)? != KNOWN_LOCKBOX_MAGIC {
1921 return Err(Error::CorruptVaultRecord(
1922 "known lockbox record has invalid magic".to_string(),
1923 ));
1924 }
1925 let version = reader.u16()?;
1926 if version != KNOWN_LOCKBOX_VERSION {
1927 return Err(Error::CorruptVaultRecord(format!(
1928 "known lockbox record version {version} is not supported"
1929 )));
1930 }
1931 let id = reader.bytes(16)?;
1932 let lockbox_id = LockboxId::from_bytes(id.try_into().map_err(|_| {
1933 Error::CorruptVaultRecord("known lockbox id has invalid length".to_string())
1934 })?);
1935 let path = reader.string()?;
1936 let last_seen_unix_ms = reader.u64()?;
1937 reader.finish()?;
1938 Ok(KnownLockbox {
1939 lockbox_id,
1940 path,
1941 last_seen_unix_ms,
1942 })
1943}
1944
1945fn encode_access_slot_label(label: &AccessSlotLabel) -> Vec<u8> {
1946 let mut out = Vec::new();
1947 out.extend_from_slice(b"LBAS");
1948 put_u16(&mut out, 1);
1949 out.extend_from_slice(label.lockbox_id.as_bytes());
1950 put_u64(&mut out, label.slot_id);
1951 put_string(&mut out, &label.name);
1952 put_u64(&mut out, label.updated_at_unix_ms);
1953 out
1954}
1955
1956fn decode_access_slot_label(bytes: &[u8]) -> Result<AccessSlotLabel> {
1957 let mut reader = BinaryReader::new(bytes);
1958 if reader.bytes(4)? != b"LBAS" {
1959 return Err(Error::CorruptVaultRecord(
1960 "access slot label record has invalid magic".to_string(),
1961 ));
1962 }
1963 let version = reader.u16()?;
1964 if version != 1 {
1965 return Err(Error::CorruptVaultRecord(format!(
1966 "access slot label version {version} is not supported"
1967 )));
1968 }
1969 let id = reader.bytes(16)?;
1970 let lockbox_id = LockboxId::from_bytes(id.try_into().map_err(|_| {
1971 Error::CorruptVaultRecord("access slot label lockbox id has invalid length".to_string())
1972 })?);
1973 let slot_id = reader.u64()?;
1974 let name = reader.string()?;
1975 let updated_at_unix_ms = reader.u64()?;
1976 reader.finish()?;
1977 Ok(AccessSlotLabel {
1978 lockbox_id,
1979 slot_id,
1980 name,
1981 updated_at_unix_ms,
1982 })
1983}
1984
1985fn encode_profile_email(email: &str) -> Vec<u8> {
1986 let mut out = Vec::new();
1987 out.extend_from_slice(PROFILE_EMAIL_MAGIC);
1988 put_u16(&mut out, PROFILE_EMAIL_VERSION);
1989 put_string(&mut out, email);
1990 out
1991}
1992
1993fn decode_profile_email(bytes: &[u8]) -> Result<String> {
1994 let mut reader = BinaryReader::new(bytes);
1995 if reader.bytes(4)? != PROFILE_EMAIL_MAGIC {
1996 return Err(Error::CorruptVaultRecord(
1997 "profile email record has invalid magic".to_string(),
1998 ));
1999 }
2000 let version = reader.u16()?;
2001 if version != PROFILE_EMAIL_VERSION {
2002 return Err(Error::CorruptVaultRecord(format!(
2003 "profile email record version {version} is not supported"
2004 )));
2005 }
2006 let email = reader.string()?;
2007 reader.finish()?;
2008 Ok(email)
2009}
2010
2011fn encode_profile_history(history: &ProfileHistory) -> Vec<u8> {
2012 let mut out = Vec::new();
2013 out.extend_from_slice(PROFILE_HISTORY_MAGIC);
2014 put_u16(&mut out, PROFILE_HISTORY_VERSION);
2015 put_u16(&mut out, history.active_generation);
2016 put_u16(&mut out, history.generations.len() as u16);
2017 for generation in &history.generations {
2018 put_u16(&mut out, generation.index);
2019 put_u16(&mut out, generation_status_to_u16(generation.status));
2020 put_u64(&mut out, generation.created_at_unix_ms);
2021 match generation.retired_at_unix_ms {
2022 Some(retired_at) => {
2023 out.push(1);
2024 put_u64(&mut out, retired_at);
2025 }
2026 None => {
2027 out.push(0);
2028 put_u64(&mut out, 0);
2029 }
2030 }
2031 put_bytes(&mut out, &generation.contact_fingerprint);
2032 }
2033 out
2034}
2035
2036fn decode_profile_history(name: &str, bytes: &[u8]) -> Result<ProfileHistory> {
2037 let mut reader = BinaryReader::new(bytes);
2038 if reader.bytes(4)? != PROFILE_HISTORY_MAGIC {
2039 return Err(Error::CorruptVaultRecord(
2040 "profile history record has invalid magic".to_string(),
2041 ));
2042 }
2043 let version = reader.u16()?;
2044 if version != PROFILE_HISTORY_VERSION {
2045 return Err(Error::CorruptVaultRecord(format!(
2046 "profile history version {version} is not supported"
2047 )));
2048 }
2049 let active_generation = reader.u16()?;
2050 let count = reader.u16()? as usize;
2051 let mut generations = Vec::with_capacity(count);
2052 for _ in 0..count {
2053 let index = reader.u16()?;
2054 let status = generation_status_from_u16(reader.u16()?)?;
2055 let created_at_unix_ms = reader.u64()?;
2056 let retired_present = reader.u8()? != 0;
2057 let retired_at = reader.u64()?;
2058 let contact_fingerprint = reader.length_prefixed_bytes()?.to_vec();
2059 generations.push(ProfileGeneration {
2060 index,
2061 status,
2062 contact_fingerprint,
2063 created_at_unix_ms,
2064 retired_at_unix_ms: retired_present.then_some(retired_at),
2065 });
2066 }
2067 reader.finish()?;
2068 Ok(ProfileHistory {
2069 name: name.to_string(),
2070 active_generation,
2071 generations,
2072 })
2073}
2074
2075struct BinaryReader<'a> {
2076 bytes: &'a [u8],
2077 offset: usize,
2078}
2079
2080impl<'a> BinaryReader<'a> {
2081 fn new(bytes: &'a [u8]) -> Self {
2082 Self { bytes, offset: 0 }
2083 }
2084
2085 fn bytes(&mut self, len: usize) -> Result<&'a [u8]> {
2086 if self.offset + len > self.bytes.len() {
2087 return Err(Error::CorruptVaultRecord(
2088 "binary vault record is truncated".to_string(),
2089 ));
2090 }
2091 let out = &self.bytes[self.offset..self.offset + len];
2092 self.offset += len;
2093 Ok(out)
2094 }
2095
2096 fn u16(&mut self) -> Result<u16> {
2097 let bytes = self.bytes(2)?;
2098 Ok(u16::from_be_bytes([bytes[0], bytes[1]]))
2099 }
2100
2101 fn u8(&mut self) -> Result<u8> {
2102 Ok(self.bytes(1)?[0])
2103 }
2104
2105 fn u32(&mut self) -> Result<u32> {
2106 let bytes = self.bytes(4)?;
2107 Ok(u32::from_be_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]))
2108 }
2109
2110 fn u64(&mut self) -> Result<u64> {
2111 let bytes = self.bytes(8)?;
2112 Ok(u64::from_be_bytes([
2113 bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], bytes[7],
2114 ]))
2115 }
2116
2117 fn string(&mut self) -> Result<String> {
2118 let len = self.u32()? as usize;
2119 let bytes = self.bytes(len)?;
2120 String::from_utf8(bytes.to_vec()).map_err(|_| {
2121 Error::CorruptVaultRecord("binary vault string is not valid UTF-8".to_string())
2122 })
2123 }
2124
2125 fn length_prefixed_bytes(&mut self) -> Result<&'a [u8]> {
2126 let len = self.u32()? as usize;
2127 self.bytes(len)
2128 }
2129
2130 fn finish(&self) -> Result<()> {
2131 if self.offset != self.bytes.len() {
2132 return Err(Error::CorruptVaultRecord(
2133 "binary vault record has trailing bytes".to_string(),
2134 ));
2135 }
2136 Ok(())
2137 }
2138}
2139
2140fn put_u16(out: &mut Vec<u8>, value: u16) {
2141 out.extend_from_slice(&value.to_be_bytes());
2142}
2143
2144fn put_u32(out: &mut Vec<u8>, value: u32) {
2145 out.extend_from_slice(&value.to_be_bytes());
2146}
2147
2148fn put_u64(out: &mut Vec<u8>, value: u64) {
2149 out.extend_from_slice(&value.to_be_bytes());
2150}
2151
2152fn put_string(out: &mut Vec<u8>, value: &str) {
2153 put_u32(out, value.len() as u32);
2154 out.extend_from_slice(value.as_bytes());
2155}
2156
2157fn put_bytes(out: &mut Vec<u8>, value: &[u8]) {
2158 put_u32(out, value.len() as u32);
2159 out.extend_from_slice(value);
2160}
2161
2162fn generation_status_to_u16(status: ProfileGenerationStatus) -> u16 {
2163 match status {
2164 ProfileGenerationStatus::Active => GENERATION_ACTIVE,
2165 ProfileGenerationStatus::Retired => GENERATION_RETIRED,
2166 ProfileGenerationStatus::Compromised => GENERATION_COMPROMISED,
2167 }
2168}
2169
2170fn generation_status_from_u16(value: u16) -> Result<ProfileGenerationStatus> {
2171 match value {
2172 GENERATION_ACTIVE => Ok(ProfileGenerationStatus::Active),
2173 GENERATION_RETIRED => Ok(ProfileGenerationStatus::Retired),
2174 GENERATION_COMPROMISED => Ok(ProfileGenerationStatus::Compromised),
2175 _ => Err(Error::CorruptVaultRecord(format!(
2176 "unknown profile generation status {value}"
2177 ))),
2178 }
2179}
2180
2181fn contact_fingerprint(public_key: &ContactPublicKey) -> Vec<u8> {
2182 let mut hasher = Sha256::new();
2183 hasher.update(public_key.to_bytes());
2184 hasher.finalize()[..16].to_vec()
2185}
2186
2187fn unix_ms(time: SystemTime) -> u64 {
2188 time.duration_since(UNIX_EPOCH)
2189 .map(|duration| duration.as_millis() as u64)
2190 .unwrap_or(0)
2191}
2192
2193pub fn default_vault_dir() -> Result<PathBuf> {
2198 if let Ok(path) = env::var("LOCKBOX_VAULT_DIR") {
2199 return Ok(PathBuf::from(path));
2200 }
2201 default_vault_dir_for_os()
2202}
2203
2204pub fn default_vault_path() -> Result<PathBuf> {
2206 Ok(default_vault_dir()?.join(VAULT_FILE_NAME))
2207}
2208
2209#[cfg(target_os = "windows")]
2210fn default_vault_dir_for_os() -> Result<PathBuf> {
2211 let base = env::var_os("LOCALAPPDATA")
2212 .map(PathBuf::from)
2213 .ok_or_else(|| Error::Configuration("LOCALAPPDATA is not set".to_string()))?;
2214 Ok(base.join("reVault").join("vault"))
2215}
2216
2217#[cfg(target_os = "macos")]
2218fn default_vault_dir_for_os() -> Result<PathBuf> {
2219 let home = home_dir()?;
2220 Ok(home
2221 .join("Library")
2222 .join("Application Support")
2223 .join("reVault")
2224 .join("vault"))
2225}
2226
2227#[cfg(all(not(target_os = "windows"), not(target_os = "macos")))]
2228fn default_vault_dir_for_os() -> Result<PathBuf> {
2229 if let Ok(path) = env::var("XDG_DATA_HOME") {
2230 return Ok(PathBuf::from(path).join("lockbox").join("vault"));
2231 }
2232 Ok(home_dir()?
2233 .join(".local")
2234 .join("share")
2235 .join("lockbox")
2236 .join("vault"))
2237}
2238
2239#[cfg(not(target_os = "windows"))]
2240fn home_dir() -> Result<PathBuf> {
2241 env::var_os("HOME")
2242 .map(PathBuf::from)
2243 .ok_or_else(|| Error::Configuration("HOME is not set".to_string()))
2244}
2245
2246fn validate_record_name(name: &str) -> Result<&str> {
2247 let valid = !name.is_empty()
2248 && name
2249 .bytes()
2250 .all(|byte| byte.is_ascii_alphanumeric() || matches!(byte, b'-' | b'_'));
2251 if valid {
2252 Ok(name)
2253 } else {
2254 Err(Error::InvalidInput(format!(
2255 "vault record name must contain only ASCII letters, digits, '-' or '_': {name}"
2256 )))
2257 }
2258}
2259
2260fn encode_name_hex(name: &str) -> String {
2261 crate::encode_hex(name.as_bytes()).to_ascii_uppercase()
2262}
2263
2264fn decode_name_hex(hex: &str) -> Option<String> {
2265 let bytes = crate::decode_hex(hex).ok()?;
2266 String::from_utf8(bytes).ok()
2267}
2268
2269fn create_private_dir(path: &Path) -> Result<()> {
2270 fs::create_dir_all(path).map_err(|err| Error::Io(err.to_string()))?;
2271 set_private_dir_permissions(path)
2272}
2273
2274#[cfg(unix)]
2275fn set_private_dir_permissions(path: &Path) -> Result<()> {
2276 use std::os::unix::fs::PermissionsExt;
2277 fs::set_permissions(path, fs::Permissions::from_mode(0o700))
2278 .map_err(|err| Error::Io(err.to_string()))
2279}
2280
2281#[cfg(not(unix))]
2282fn set_private_dir_permissions(_path: &Path) -> Result<()> {
2283 Ok(())
2284}
2285
2286#[cfg(unix)]
2287fn set_private_file_permissions(path: &Path) -> Result<()> {
2288 use std::os::unix::fs::PermissionsExt;
2289 fs::set_permissions(path, fs::Permissions::from_mode(0o600))
2290 .map_err(|err| Error::Io(err.to_string()))
2291}
2292
2293#[cfg(not(unix))]
2294fn set_private_file_permissions(_path: &Path) -> Result<()> {
2295 Ok(())
2296}