vault.rs

crates/veilvoice-crypto/src/vault.rs

veilvoice-crypto · 688 lines · read the source here · or on GitHub

Where the app lock is kept: two copies, unpredictable names, and a restore.

What is real here and what is only awkward

This module does three things to the app-lock file, and they are not worth the same amount. Saying which is which is the point of this section.

The second copy is real. A lock kept in one file is removed by deleting that file. A lock kept in two files in two different directories is not, unless the person deleting knows about both. When the first copy is gone or unreadable, Vault::load restores it from the second and reports the event, so the lock comes back and the owner is told it went.

The administrator-only copy is real, where the platform provides it. On Unix, when VeilVoice is run with enough privilege to write under /etc, the second copy is written there and is thereafter not writable by an ordinary user. Removing the lock then needs sudo, which is a genuine step up from needing a file manager. VeilVoice never asks for that privilege and never elevates itself: it uses what it already has and otherwise carries on. On Windows the equivalent needs an access-control list this crate does not link the API to set, so the second copy there is a second copy and nothing more, and this module says so rather than implying a protection it did not obtain.

The unpredictable name and the masked contents are neither. They are obscurity. The name is derived from a value in an index file that sits at a fixed, obvious path, because something has to, or nothing could ever find the lock again. Anybody who reads this source, or simply reads the index, recomputes both names in a second. What they buy is narrow and real enough to keep: a scan for the string VEILLOK1 across a disk finds nothing, a backup rule written against applock.bin misses, and advice of the form "just delete this file" does not survive being passed on. None of that stops an attacker who is paying attention, and none of it is counted as security anywhere in the documentation.

What none of it does

It does not stop somebody holding the disk. It does not stop somebody who knows the passphrase. It does not make the failed-attempt counter trustworthy: see crate::lock::AppLock::to_bytes for why that one cannot be authenticated at all. If the threat is the disk, the answer is still full-volume encryption.

In plain words

The lock is kept twice, in two places, under names that are not guessable from the outside, and scrambled so it does not look like a password file. If one copy goes missing, the other puts it back and you are told.

The scrambling and the odd names are speed bumps, not locks. They stop careless deletion and casual searching. They do not stop somebody who has decided to get in and has your disk. Where VeilVoice is already running with administrator rights, the spare copy is put somewhere an ordinary user cannot touch, and that part is not a speed bump.

WHAT THIS FILE CONTAINS

688 lines defining 12 functions (8 public), 2 types and 6 constants. Everything below is read out of the source, so it cannot disagree with the code.

The types it owns.

  • enum Found line 76 · What Vault::load found when it went looking.
  • struct Vault line 103 · The two files a lock lives in, and the index that names them.

What happens when it runs. These are the ways in: public, and nothing else in this file calls them, so they are what an outside caller reaches first.

  • Vault::at line 117 · Resolve the vault under base, creating the index if there is none.
    reaches name_for
  • Vault::primary line 164 · The file the lock is read from and written to.
  • Vault::shadow line 169 · The second copy.
  • Vault::index line 174 · The index that names both.
  • Vault::load line 184 · Read the lock, restoring one copy from the other if it has to.
    reaches read_masked, write_one, mask
  • Vault::store line 233 · Write both copies, and say whether the spare is now current.
    reaches write_one, mask
  • Vault::clear line 240 · Remove both copies, and the index with them.
  • admin_dir line 336 · A directory only an administrator can write to, if this process can make one there.

WHAT CALLS WHAT

Vault::at line 117 Vault::primary line 164 Vault::shadow line 169 Vault::index line 174 Vault::load line 184 Vault::store line 233 Vault::clear line 240 Vault::write_one line 260 Vault::read_masked line 279 name_for line 287 mask line 306 admin_dir line 336 entry: a way in: public, and nothing in this file calls it helper: private to this file dashed: a call that goes back up, or across a wrapped rank The functions this file defines, and the calls between them. An edge means the callee's name appears, called, inside the caller's body. This is a syntactic reading, not a type-resolved one.

The functions this file defines, and the calls between them. An edge means the callee's name appears, called, inside the caller's body. This is a syntactic reading, not a type-resolved one.

The same graph as Mermaid source
%%{init: {"theme":"base","themeVariables":{"background":"#1a1b26","primaryColor":"#1f2335","primaryTextColor":"#c0caf5","primaryBorderColor":"#7aa2f7","secondaryColor":"#16161e","tertiaryColor":"#16161e","lineColor":"#737aa2","textColor":"#c0caf5","mainBkg":"#1f2335","nodeBorder":"#7aa2f7","clusterBkg":"#16161e","clusterBorder":"#2f3549","fontFamily":"ui-monospace, SFMono-Regular, Consolas, monospace","fontSize":"14px"}}}%%
flowchart TD
    n_at(["Vault::at<br/>line 117"])
    n_primary(["Vault::primary<br/>line 164"])
    n_shadow(["Vault::shadow<br/>line 169"])
    n_index(["Vault::index<br/>line 174"])
    n_load(["Vault::load<br/>line 184"])
    n_store(["Vault::store<br/>line 233"])
    n_clear(["Vault::clear<br/>line 240"])
    n_write_one["Vault::write_one<br/>line 260"]
    n_read_masked["Vault::read_masked<br/>line 279"]
    n_name_for["name_for<br/>line 287"]
    n_mask["mask<br/>line 306"]
    n_admin_dir(["admin_dir<br/>line 336"])
    n_at --> n_name_for
    n_load --> n_read_masked
    n_load --> n_write_one
    n_read_masked --> n_mask
    n_store --> n_write_one
    n_write_one --> n_mask
    click n_at href "https://github.com/tilas01/veilvoice/blob/main/crates/veilvoice-crypto/src/vault.rs#L117" "open the source"
    click n_primary href "https://github.com/tilas01/veilvoice/blob/main/crates/veilvoice-crypto/src/vault.rs#L164" "open the source"
    click n_shadow href "https://github.com/tilas01/veilvoice/blob/main/crates/veilvoice-crypto/src/vault.rs#L169" "open the source"
    click n_index href "https://github.com/tilas01/veilvoice/blob/main/crates/veilvoice-crypto/src/vault.rs#L174" "open the source"
    click n_load href "https://github.com/tilas01/veilvoice/blob/main/crates/veilvoice-crypto/src/vault.rs#L184" "open the source"
    click n_store href "https://github.com/tilas01/veilvoice/blob/main/crates/veilvoice-crypto/src/vault.rs#L233" "open the source"
    click n_clear href "https://github.com/tilas01/veilvoice/blob/main/crates/veilvoice-crypto/src/vault.rs#L240" "open the source"
    click n_write_one href "https://github.com/tilas01/veilvoice/blob/main/crates/veilvoice-crypto/src/vault.rs#L260" "open the source"
    click n_read_masked href "https://github.com/tilas01/veilvoice/blob/main/crates/veilvoice-crypto/src/vault.rs#L279" "open the source"
    click n_name_for href "https://github.com/tilas01/veilvoice/blob/main/crates/veilvoice-crypto/src/vault.rs#L287" "open the source"
    click n_mask href "https://github.com/tilas01/veilvoice/blob/main/crates/veilvoice-crypto/src/vault.rs#L306" "open the source"
    click n_admin_dir href "https://github.com/tilas01/veilvoice/blob/main/crates/veilvoice-crypto/src/vault.rs#L336" "open the source"
    classDef entry fill:#1f2335,stroke:#7aa2f7,color:#c0caf5
    class n_at,n_primary,n_shadow,n_index,n_load,n_store,n_clear,n_admin_dir entry
    classDef helper fill:#1f2335,stroke:#bb9af7,color:#c0caf5
    class n_write_one,n_read_masked,n_name_for,n_mask helper

This site loads no third-party script, so it cannot run Mermaid; the diagram above is the same nodes and edges drawn by the generator instead. GitHub renders the source below directly.

ITEMS

ItemLineDocumentation
SITE_LEN const61Length of the per-installation value the file names are derived from.
NAME_BYTES const64Bytes of hex in a derived file name.
INDEX_NAME const67Fixed name of the index.
LABEL_PRIMARY const70Domain separators, so the two names and the mask cannot coincide.
LABEL_SHADOW const71
LABEL_MASK const72
Found pub enum76What Vault::load found when it went looking.
Vault pub struct103The two files a lock lives in, and the index that names them.
Vault::at pub fn117Resolve the vault under base, creating the index if there is none.
Vault::primary pub fn164The file the lock is read from and written to.
Vault::shadow pub fn169The second copy.
Vault::index pub fn174The index that names both.
Vault::load pub fn184Read the lock, restoring one copy from the other if it has to.
Vault::store pub fn233Write both copies, and say whether the spare is now current.
Vault::clear pub fn240Remove both copies, and the index with them.
Vault::write_one fn260Mask bytes for this site and write them at path, creating the directory if it is not there.
Vault::read_masked fn279Read one file back and unmask it, or None if it is not a lock.
name_for fn287The file name derived from site under label.
mask fn306Exclusive-or bytes with a keystream derived from site.
admin_dir pub fn336A directory only an administrator can write to, if this process can make one there.