crates/veilvoice-gui/src/security.rs
veilvoice-gui · 2403 lines · read the source here · or on GitHub
The application lock, and the at-rest encryption of what VeilVoice writes.
Two passwords, and why
There are two, deliberately:
- the app lock, which decides whether VeilVoice will open at all, and
- the recording passphrase, which encrypts the files it produces.
Collapsing them into one would mean that unlocking the app also unseals every recording it has ever written, which is the opposite of what a lock is for. veilvoice_crypto::lock additionally domain-separates its verifier, so even a user who types the same string in both places does not end up with two copies of one value.
What the lock is worth
Not much against an attacker with the disk, and the UI says so in veilvoice_crypto::lock::SCOPE, shown on the unlock screen itself rather than buried in an about page. It stops the person who picks up your unlocked laptop. It does not stop someone who takes the drive.
A limitation of typing a password into a window
A text field owns a String, so a passphrase exists as ordinary heap bytes while it is being typed. That window cannot be removed, because something has to receive the keystrokes, but it can be kept short, and it is:
- the typing buffer is wiped the moment the passphrase is confirmed;
- the confirmed passphrase is held only as a
veilvoice_crypto::Secret, page-locked and zeroized on drop, for the rest of the session; - locking the app, or changing the passphrase, wipes both.
It used to be kept as a plain String for the whole session, which was a much larger window for no benefit.
None of this defends against someone who can read this process's memory. If they can, they have already won, and docs/WHITEPAPER.md §7 says so rather than implying otherwise. What it does is stop a passphrase lingering in a heap allocation long after it was needed, where a core dump or a swapped page could pick it up.
In plain words
The lock on the window, and the encryption of the files VeilVoice writes.
There are two passphrases and they do different jobs. One opens the application. The other encrypts a recording, and it is asked for separately because they protect different things and losing one should not mean losing the other.
The panel says what the lock is worth and what it is not: it stops somebody who picks up your unlocked computer, and it does not stop somebody who has the disk. Encrypting the recording is what protects the recording.
WHAT THIS FILE CONTAINS
2403 lines defining 45 functions (26 public), 4 types and 3 constants. Everything below is read out of the source, so it cannot disagree with the code.
The types it owns.
enum Sealingline 81 · How the recording that comes out of a job is protected.enum Opline 106 · What a background lock operation was trying to do.struct Securityline 129 · Everything about locking the app and sealing its output.enum Planline 1357 · What a finished job should do with its bytes.
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.
Security::loadline 279 · Read the lock file for this machine and start locked if one is set.
reachesdefaultSecurity::take_unlock_passphraseline 322 · Take the passphrase that just opened the lock, once.Security::take_unlock_store_keyline 331 · Collect the obfuscated store's key from the unlock that just happened.Security::prefer_app_lock_sealingline 340 · Start in Sealing::AppLock, because the user asked for that last time.Security::seals_with_app_lockline 348 · Whether the app-lock sealing mode is currently chosen, so the window can have the choice remembered.Security::tamperedline 356 · Whether the lock reported having been interfered with.Security::is_lockedline 361 · Whether the unlock screen should be shown instead of the app.Security::set_lock_from_setupline 371 · Whether a lock is configured at all.
reachesspawn,run_op,reopenSecurity::has_recording_passphraseline 378 · Whether a recording passphrase is held for this session.Security::set_recording_passphraseline 387 · Take a recording passphrase from the first-run setup.
reachesinto_secretSecurity::lock_after_idleline 415 · Lock because nobody has touched the window for a while.
reacheslock_inner,wipe_secretsSecurity::blocked_reasonline 476 · Why a job cannot start yet, for the button's tooltip.
reachesready_to_writeSecurity::planline 494 · How the next job should protect its output.Security::is_busyline 647 · Whether a lock operation is running, so the window keeps repainting and the spinner actually spins.
reachesbusySecurity::unlock_screenline 652 · The full-window unlock screen.
reachesbusy,poll,spawn,unlock_row,into_secret,wipe_form,run_op,reopenSecurity::tabline 854 · The security tab: manage the lock, and see what it is worth.
reachesbusy,button_column,has_lock,interference_banner,lock_now,password_row,poll,spawn,lock_inner,into_secret,wipe_form,run_opSecurity::load_mandateline 1031 · Read the baseline from disk and apply it to the checkbox.Security::mandate_requires_app_lockline 1046 · Whether the baseline insists on the app lock.Security::mandate_requires_encryptionline 1051 · Whether the baseline insists on encryption at rest.Security::mandate_historyline 1056 · The change log, for the panel that shows it.Security::recording_controlsline 1079 · The at-rest controls that sit inside the file tab.
reachesinto_secret,mandate_history_panel,password_row,record,mandate_history_rowsSecurity::disable_dialogueline 1299 · The dialogue shown when the user turns at-rest encryption off.
reachesrecordPlan::writeline 1382 · Seal wav if the plan says to, and write it.
WHAT CALLS WHAT
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. 22 of 45 functions are drawn; the diagram is bounded at 22 so it stays readable.
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_into_secret["into_secret<br/>line 72"]
n_default["Security::default<br/>line 236"]
n_drop["Security::drop<br/>line 272"]
n_load(["Security::load<br/>line 279"])
n_set_lock_from_setup(["Security::set_lock_from_setup<br/>line 371"])
n_set_recording_passphrase(["Security::<br/>set_recording_passphrase<br/>line 387"])
n_has_lock["Security::has_lock<br/>line 396"]
n_lock_now["Security::lock_now<br/>line 405"]
n_lock_after_idle(["Security::lock_after_idle<br/>line 415"])
n_lock_inner["Security::lock_inner<br/>line 424"]
n_wipe_secrets["Security::wipe_secrets<br/>line 433"]
n_ready_to_write["Security::ready_to_write<br/>line 464"]
n_blocked_reason(["Security::blocked_reason<br/>line 476"])
n_spawn["Security::spawn<br/>line 526"]
n_poll["Security::poll<br/>line 541"]
n_wipe_form["Security::wipe_form<br/>line 633"]
n_busy["Security::busy<br/>line 641"]
n_is_busy(["Security::is_busy<br/>line 647"])
n_unlock_screen(["Security::unlock_screen<br/>line 652"])
n_tab(["Security::tab<br/>line 854"])
n_recording_controls(["Security::recording_controls<br/>line 1079"])
n_disable_dialogue(["Security::disable_dialogue<br/>line 1299"])
n_blocked_reason --> n_ready_to_write
n_drop --> n_wipe_secrets
n_is_busy --> n_busy
n_load --> n_default
n_lock_after_idle --> n_lock_inner
n_lock_inner --> n_wipe_secrets
n_lock_now --> n_lock_inner
n_poll --> n_into_secret
n_poll --> n_wipe_form
n_recording_controls --> n_into_secret
n_set_lock_from_setup --> n_spawn
n_set_recording_passphrase --> n_into_secret
n_tab --> n_busy
n_tab --> n_has_lock
n_tab --> n_lock_now
n_tab --> n_poll
n_tab --> n_spawn
n_unlock_screen --> n_busy
n_unlock_screen --> n_poll
n_unlock_screen --> n_spawn
click n_into_secret href "https://github.com/tilas01/veilvoice/blob/main/crates/veilvoice-gui/src/security.rs#L72" "open the source"
click n_default href "https://github.com/tilas01/veilvoice/blob/main/crates/veilvoice-gui/src/security.rs#L236" "open the source"
click n_drop href "https://github.com/tilas01/veilvoice/blob/main/crates/veilvoice-gui/src/security.rs#L272" "open the source"
click n_load href "https://github.com/tilas01/veilvoice/blob/main/crates/veilvoice-gui/src/security.rs#L279" "open the source"
click n_set_lock_from_setup href "https://github.com/tilas01/veilvoice/blob/main/crates/veilvoice-gui/src/security.rs#L371" "open the source"
click n_set_recording_passphrase href "https://github.com/tilas01/veilvoice/blob/main/crates/veilvoice-gui/src/security.rs#L387" "open the source"
click n_has_lock href "https://github.com/tilas01/veilvoice/blob/main/crates/veilvoice-gui/src/security.rs#L396" "open the source"
click n_lock_now href "https://github.com/tilas01/veilvoice/blob/main/crates/veilvoice-gui/src/security.rs#L405" "open the source"
click n_lock_after_idle href "https://github.com/tilas01/veilvoice/blob/main/crates/veilvoice-gui/src/security.rs#L415" "open the source"
click n_lock_inner href "https://github.com/tilas01/veilvoice/blob/main/crates/veilvoice-gui/src/security.rs#L424" "open the source"
click n_wipe_secrets href "https://github.com/tilas01/veilvoice/blob/main/crates/veilvoice-gui/src/security.rs#L433" "open the source"
click n_ready_to_write href "https://github.com/tilas01/veilvoice/blob/main/crates/veilvoice-gui/src/security.rs#L464" "open the source"
click n_blocked_reason href "https://github.com/tilas01/veilvoice/blob/main/crates/veilvoice-gui/src/security.rs#L476" "open the source"
click n_spawn href "https://github.com/tilas01/veilvoice/blob/main/crates/veilvoice-gui/src/security.rs#L526" "open the source"
click n_poll href "https://github.com/tilas01/veilvoice/blob/main/crates/veilvoice-gui/src/security.rs#L541" "open the source"
click n_wipe_form href "https://github.com/tilas01/veilvoice/blob/main/crates/veilvoice-gui/src/security.rs#L633" "open the source"
click n_busy href "https://github.com/tilas01/veilvoice/blob/main/crates/veilvoice-gui/src/security.rs#L641" "open the source"
click n_is_busy href "https://github.com/tilas01/veilvoice/blob/main/crates/veilvoice-gui/src/security.rs#L647" "open the source"
click n_unlock_screen href "https://github.com/tilas01/veilvoice/blob/main/crates/veilvoice-gui/src/security.rs#L652" "open the source"
click n_tab href "https://github.com/tilas01/veilvoice/blob/main/crates/veilvoice-gui/src/security.rs#L854" "open the source"
click n_recording_controls href "https://github.com/tilas01/veilvoice/blob/main/crates/veilvoice-gui/src/security.rs#L1079" "open the source"
click n_disable_dialogue href "https://github.com/tilas01/veilvoice/blob/main/crates/veilvoice-gui/src/security.rs#L1299" "open the source"
classDef entry fill:#1f2335,stroke:#7aa2f7,color:#c0caf5
class n_load,n_set_lock_from_setup,n_set_recording_passphrase,n_lock_after_idle,n_blocked_reason,n_is_busy,n_unlock_screen,n_tab,n_recording_controls,n_disable_dialogue entry
classDef api fill:#1f2335,stroke:#7dcfff,color:#c0caf5
class n_has_lock,n_lock_now,n_ready_to_write api
classDef helper fill:#1f2335,stroke:#bb9af7,color:#c0caf5
class n_into_secret,n_default,n_drop,n_lock_inner,n_wipe_secrets,n_spawn,n_poll,n_wipe_form,n_busy 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
| Item | Line | Documentation |
|---|---|---|
into_secret fn | 72 | Move a typed passphrase out of its String and into page-locked storage, wiping the buffer it came from. |
Sealing pub enum | 81 | How the recording that comes out of a job is protected. |
Op enum | 106 | What a background lock operation was trying to do. |
OpResult type | 117 | A finished lock operation: the store as it now stands, and how it went. |
Security pub struct | 129 | Everything about locking the app and sealing its output. |
Security::default fn | 236 | The safe state, and deliberately free of I/O so tests and VeilVoiceApp::default() never touch the real lock file. |
Security::drop fn | 272 | |
Security::load pub fn | 279 | Read the lock file for this machine and start locked if one is set. |
Security::take_unlock_passphrase pub fn | 322 | Take the passphrase that just opened the lock, once. |
Security::take_unlock_store_key pub fn | 331 | Collect the obfuscated store's key from the unlock that just happened. |
Security::prefer_app_lock_sealing pub fn | 340 | Start in Sealing::AppLock, because the user asked for that last time. |
Security::seals_with_app_lock pub fn | 348 | Whether the app-lock sealing mode is currently chosen, so the window can have the choice remembered. |
Security::tampered pub fn | 356 | Whether the lock reported having been interfered with. |
Security::is_locked pub fn | 361 | Whether the unlock screen should be shown instead of the app. |
Security::set_lock_from_setup pub fn | 371 | Whether a lock is configured at all. |
Security::has_recording_passphrase pub fn | 378 | Whether a recording passphrase is held for this session. |
Security::set_recording_passphrase pub fn | 387 | Take a recording passphrase from the first-run setup. |
Security::has_lock pub fn | 396 | Whether an app lock is configured on this machine. |
Security::lock_now pub fn | 405 | Lock the app now, wiping the session passphrase with it. |
Security::lock_after_idle pub fn | 415 | Lock because nobody has touched the window for a while. |
Security::lock_inner fn | 424 | Lock, remembering whether the person did it or the idle timer did. |
Security::wipe_secrets fn | 433 | Wipe every plaintext secret this struct is holding. |
Security::ready_to_write pub fn | 464 | Whether a job may start: either encryption is off, or there is something to encrypt with. |
Security::blocked_reason pub fn | 476 | Why a job cannot start yet, for the button's tooltip. |
Security::plan pub fn | 494 | How the next job should protect its output. |
Security::spawn fn | 526 | Run a lock operation on a thread, so the window keeps drawing. |
Security::poll fn | 541 | Collect a finished lock operation. |
Security::wipe_form fn | 633 | Clear what was typed, so a passphrase does not sit in a field after use. |
Security::busy fn | 641 | Whether an operation is in flight, so the panel can refuse a second one. |
Security::is_busy pub fn | 647 | Whether a lock operation is running, so the window keeps repainting and the spinner actually spins. |
Security::unlock_screen pub fn | 652 | The full-window unlock screen. |
Security::interference_banner fn | 798 | The standing report that the lock file was interfered with. |
Security::tab pub fn | 854 | The security tab: manage the lock, and see what it is worth. |
Security::load_mandate pub fn | 1031 | Read the baseline from disk and apply it to the checkbox. |
Security::mandate_requires_app_lock pub fn | 1046 | Whether the baseline insists on the app lock. |
Security::mandate_requires_encryption pub fn | 1051 | Whether the baseline insists on encryption at rest. |
Security::mandate_history pub fn | 1056 | The change log, for the panel that shows it. |
Security::record fn | 1066 | Record a change to the baseline, and write it down. |
Security::recording_controls pub fn | 1079 | The at-rest controls that sit inside the file tab. |
Security::mandate_history_panel fn | 1269 | The log of every time a requirement was turned off or back on. |
Security::mandate_history_rows fn | 1288 | One coloured line per change, newest concern last: green for a requirement put back, yellow for one turned off. |
Security::disable_dialogue pub fn | 1299 | The dialogue shown when the user turns at-rest encryption off. |
DISABLE_WARNING pub const | 1341 | What the user is told before recordings stop being encrypted. |
Plan pub enum | 1357 | What a finished job should do with its bytes. |
Plan::write pub fn | 1382 | Seal wav if the plan says to, and write it. |
Plan::fmt fn | 1429 | |
run_op fn | 1440 | Run one lock operation, off the UI thread. |
reopen fn | 1509 | Re-open the lock store from disk, or None when there is nothing to open. |
PASSWORD_LABEL_WIDTH const | 1516 | The width every passphrase label is given, so every field starts level. |
PASSWORD_FIELD_WIDTH const | 1521 | How wide every passphrase field is drawn, on this tab and on the lock screen. |
button_column fn | 1555 | One labelled passphrase field, with the field in the same place every time. |
password_row fn | 1570 | One passphrase field with its label, at the shared width. |
unlock_row fn | 1615 | The password row on the lock screen: the label, the field and the button. |