Files
fcry/Cargo.toml
T
ddidderr 898697016a refactor(secrets): back SecretBytes32/SecretVec with the secrets crate
Replace the homegrown `region::lock` + `Zeroizing` wrappers with thin
adapters over `secrets::SecretBox` and `secrets::SecretVec`. The
upstream crate already provides everything the local types were doing
by hand (mlock, zero-on-drop) and adds protections we didn't have:
guard pages around the allocation and `mprotect`-based access control
(`PROT_NONE` at rest, `PROT_READ` during a borrow, `PROT_READ|WRITE`
during a mut borrow). Net result is a security upgrade, not just a
dependency swap.

Why a local adapter still exists
--------------------------------
`secrets::SecretVec` is fixed-length — no `push`. The tty passphrase
reader needs to append bytes one at a time without ever reallocating
(a panicking partial read must not leave stale plaintext on the heap),
so `SecretVec` keeps a separate logical `len` over a fixed protected
allocation of `MAX_PASSPHRASE_LEN` bytes. Bytes past `len` stay
zero-padding and are never exposed through `with_slice`.

API shape: closure-scoped borrows
---------------------------------
The previous `as_slice` / `as_array` returned long-lived `&[u8]`
references, which would have kept the upstream pages in `PROT_READ`
for the full lifetime of the borrow. The new API uses
`with_array(|s| ...)`, `with_mut_array(|s| ...)`, `with_slice(|s| ...)`
so the unprotected window is exactly the closure body. This is uglier
at call sites (notably the nested closures in `derive_key`) but it's
the right tradeoff — minimizing the unprotected window is the whole
point of using the crate.

AEAD key copy footnote
----------------------
`XChaCha20Poly1305::new` copies the key into its own (unprotected)
state, which then lives in the `aead` binding for the entire
encrypt/decrypt loop. This is unchanged from before — the cipher
state was never protected — but it's now called out explicitly with
a comment at both call sites noting that `chacha20poly1305` zeroizes
that internal copy on drop. Future readers shouldn't have to
rediscover this by reading upstream source.

`from_vec` zeroing
------------------
`SecretVec::from_vec(v: Vec<u8>)` is used on the env-var path. It
calls `secrets::SecretVec::from(&mut [u8])`, which (verified against
secrets-1.3.0: `Box::from` -> `transfer` -> `memtransfer`) copies the
bytes into protected storage and zeroes the source slice. The
original Vec's allocation is then released through the normal
allocator — the bytes inside it are zero, but the heap block itself
isn't specially handled. The doc comment on `from_vec` reflects this
precisely. As before, the env-var path also leaves a copy in the
process `environ` table, which is a known accepted leak.

Cargo.toml
----------
Use `protected-secrets = { package = "secrets", version = "1.3" }`
with default features. The `secrets` crate has no pure-Rust backend
at v1.3 — disabling default features only switches *how* libsodium
is linked (bundled `libsodium-sys` vs. the crate's own bindings to a
system libsodium), and can break builds where the chosen path isn't
set up. Defaults are correct here. The `region` dependency is
dropped.

Test plan
---------
- `cargo build` clean.
- `cargo test` — 2 unit + 18 integration tests pass, including
  `roundtrip_passphrase_argon2id` which exercises the full
  passphrase -> argon2id -> AEAD key path through the new wrappers.
- `cargo clippy` (and `--tests`, `--benches`) clean.
- `cargo +nightly fmt` applied.
2026-05-02 19:14:42 +02:00

37 lines
750 B
TOML

[package]
authors = ["ddidderr <ddidderr@paul.network>"]
edition = "2024"
name = "fcry"
version = "0.10.0"
[dependencies]
argon2 = "0.5"
chacha20poly1305 = "0.10"
clap = {version = "4", features = ["derive"]}
getrandom = {version = "0.3"}
protected-secrets = {package = "secrets", version = "1.3"}
zeroize = {version = "1", features = ["derive"]}
[target.'cfg(unix)'.dependencies]
libc = "0.2"
rlimit = "0.10"
[target.'cfg(windows)'.dependencies]
windows-sys = {version = "0.59", features = [
"Win32_System_Console",
"Win32_Foundation",
"Win32_Storage_FileSystem",
"Win32_Security",
]}
[dev-dependencies]
assert_cmd = "2"
tempfile = "3"
[profile.release]
lto = false
debug = false
strip = true
panic = "unwind"
codegen-units = 1