diff --git a/Cargo.toml b/Cargo.toml index ad538d1..d2401ff 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,7 +25,13 @@ serde_json = "1" jiff = "0.2" tar = { version = "0.4.46", default-features = false } flate2 = { version = "1.1", default-features = false, features = ["rust_backend"] } -ureq = { version = "3.4", default-features = false, features = ["rustls", "platform-verifier", "gzip"] } +ureq = { version = "3.4", default-features = false, features = ["rustls", "gzip"] } + +[features] +default = ["system-roots"] +# Verify TLS certificates against the operating system's trust store. Off, +# the bundled Mozilla roots are used (for cross-built macOS binaries). +system-roots = ["ureq/platform-verifier"] [dev-dependencies] aes = "0.9" diff --git a/SECURITY.md b/SECURITY.md index 9afe0ad..fe8b977 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -134,8 +134,9 @@ for the passphrase. ## Network -Downloads verify TLS certificates (rustls with the platform verifier; it is -never disabled), follow at most 10 redirects (http and https only; for +Downloads verify TLS certificates (rustls with the operating system's trust +store; macOS binaries cross-built on Linux use the bundled Mozilla roots +instead, see `docs/PLATFORMS.md`; verification is never disabled), follow at most 10 redirects (http and https only; for `mirror`, only to the same host), and are bounded by connect, response and whole-request deadlines and a byte budget counted after decompression. See `vault(1)` for the defaults and flags. diff --git a/build.sh b/build.sh index 16c2a30..f1369f0 100755 --- a/build.sh +++ b/build.sh @@ -14,7 +14,9 @@ # # How each target is built, and what the host needs: # zig cargo-zigbuild and zig on PATH (Linux, Windows GNU ABI) -# mac a Mac with Xcode (macOS) +# mac a Mac with Xcode, or zig (macOS; zig builds trust the +# bundled Mozilla roots, not the +# keychain) # cross `cross` and Docker (FreeBSD) # A requested target the host cannot build is reported, the others are still # built, and the script exits non-zero. See docs/PLATFORMS.md for the Go @@ -98,8 +100,16 @@ build_one() { { echo "SKIPPED $want: needs cargo-zigbuild and zig" >&2; return 1; } cmd=(cargo zigbuild) ;; mac) - [[ $HOST_OS == Darwin ]] || { echo "SKIPPED $want: needs a Mac with Xcode" >&2; return 1; } - cmd=(cargo build) ;; + if [[ $HOST_OS == Darwin ]]; then + cmd=(cargo build) + elif command -v cargo-zigbuild >/dev/null && command -v zig >/dev/null; then + # Without Apple's SDK there is no Security framework to link, so + # the cross-built binary trusts the bundled Mozilla roots rather + # than the macOS keychain. + cmd=(cargo zigbuild --no-default-features) + else + echo "SKIPPED $want: needs a Mac, or cargo-zigbuild and zig" >&2; return 1 + fi ;; cross) command -v cross >/dev/null && command -v docker >/dev/null || { echo "SKIPPED $want: needs cross and docker" >&2; return 1; } diff --git a/docs/PLATFORMS.md b/docs/PLATFORMS.md index 3634a86..e65b9a3 100644 --- a/docs/PLATFORMS.md +++ b/docs/PLATFORMS.md @@ -33,8 +33,8 @@ the port: the Go release's more exotic targets are no longer built). | windows/amd64 | x86_64-pc-windows-gnu | static CRT | zig | build; tests cross-compile cleanly; runtime in CI only (windows-2025 job, not yet run) | | windows/386 | i686-pc-windows-gnu | static CRT | zig | build only | | windows/arm64 | aarch64-pc-windows-gnullvm | static CRT | zig | build only | -| darwin/amd64 | x86_64-apple-darwin | system | a Mac (Xcode) | CI only (macos-15-intel job, not yet run) | -| darwin/arm64 | aarch64-apple-darwin | system | a Mac (Xcode) | CI only (macos-15 job, not yet run) | +| darwin/amd64 | x86_64-apple-darwin | system | a Mac, or zig (note 2) | build only from Linux; tested in CI on macos-15-intel | +| darwin/arm64 | aarch64-apple-darwin | system | a Mac, or zig (note 2) | build only from Linux; tested in CI on macos-15 | | freebsd/amd64 | x86_64-unknown-freebsd | system | cross 0.2.5 (Docker) | build only (no emulator) | | freebsd/386 | i686-unknown-freebsd | system | cross 0.2.5 (Docker) | build only | @@ -47,6 +47,12 @@ disables core dumps. See `SECURITY.md`. process spins). `build.sh` and `packaging/package.sh` build that target with `--cfg getrandom_backend="linux_raw"`, which makes the `getrandom` crate issue the system call itself; with that flag the binary passes. +2. Without Apple's SDK there is no Security framework to link, so `build.sh` + on Linux cross-builds macOS with `--no-default-features`: those binaries + verify TLS for `add URL` and `mirror` against the bundled Mozilla roots + instead of the macOS keychain (so a corporate CA added to the keychain is + not trusted). Built on a Mac, or through Homebrew + (`brew install andydixon/tap/vault`), they use the keychain. ## No longer released diff --git a/docs/guide/img/absorb.png b/docs/guide/img/absorb.png index aae4945..5a7383c 100644 Binary files a/docs/guide/img/absorb.png and b/docs/guide/img/absorb.png differ diff --git a/docs/guide/img/build.png b/docs/guide/img/build.png index 1686d99..e95a97f 100644 Binary files a/docs/guide/img/build.png and b/docs/guide/img/build.png differ diff --git a/docs/guide/img/cp-mv.png b/docs/guide/img/cp-mv.png index c2f7c29..21a8d3e 100644 Binary files a/docs/guide/img/cp-mv.png and b/docs/guide/img/cp-mv.png differ diff --git a/docs/guide/img/decoy-hidden.png b/docs/guide/img/decoy-hidden.png index 1c72641..54d63fa 100644 Binary files a/docs/guide/img/decoy-hidden.png and b/docs/guide/img/decoy-hidden.png differ diff --git a/docs/guide/img/decoy-views.png b/docs/guide/img/decoy-views.png index ecf1f2d..c99616a 100644 Binary files a/docs/guide/img/decoy-views.png and b/docs/guide/img/decoy-views.png differ diff --git a/docs/guide/img/genkey.png b/docs/guide/img/genkey.png index db8ff3d..a02cbb5 100644 Binary files a/docs/guide/img/genkey.png and b/docs/guide/img/genkey.png differ diff --git a/docs/guide/img/help.png b/docs/guide/img/help.png index 14df0d9..ce7250f 100644 Binary files a/docs/guide/img/help.png and b/docs/guide/img/help.png differ diff --git a/docs/guide/img/init.png b/docs/guide/img/init.png index 7028357..b693229 100644 Binary files a/docs/guide/img/init.png and b/docs/guide/img/init.png differ diff --git a/docs/guide/img/list.png b/docs/guide/img/list.png index 5efac4f..42a9a5a 100644 Binary files a/docs/guide/img/list.png and b/docs/guide/img/list.png differ diff --git a/docs/guide/img/locking.png b/docs/guide/img/locking.png index a7395e3..a7b7423 100644 Binary files a/docs/guide/img/locking.png and b/docs/guide/img/locking.png differ diff --git a/docs/guide/img/mv-rm.png b/docs/guide/img/mv-rm.png index bdb7492..3a63c05 100644 Binary files a/docs/guide/img/mv-rm.png and b/docs/guide/img/mv-rm.png differ diff --git a/docs/guide/img/on-disk.png b/docs/guide/img/on-disk.png index 65777bc..49dd036 100644 Binary files a/docs/guide/img/on-disk.png and b/docs/guide/img/on-disk.png differ diff --git a/docs/guide/img/rm-r.png b/docs/guide/img/rm-r.png index 2bf43c8..87e05a3 100644 Binary files a/docs/guide/img/rm-r.png and b/docs/guide/img/rm-r.png differ diff --git a/docs/guide/img/scripting.png b/docs/guide/img/scripting.png index 7f0c204..439e49f 100644 Binary files a/docs/guide/img/scripting.png and b/docs/guide/img/scripting.png differ diff --git a/docs/guide/img/unlock.png b/docs/guide/img/unlock.png index fd75a2b..7f92f9b 100644 Binary files a/docs/guide/img/unlock.png and b/docs/guide/img/unlock.png differ diff --git a/src/net/mod.rs b/src/net/mod.rs index 53e56d7..67c2c68 100644 --- a/src/net/mod.rs +++ b/src/net/mod.rs @@ -135,9 +135,14 @@ impl Client { Some(p) => Some(ureq::Proxy::new(p).map_err(|_| "invalid proxy setting in the environment".to_string())?), None => None, }; - let tls = ureq::tls::TlsConfig::builder() - .root_certs(ureq::tls::RootCerts::PlatformVerifier) - .build(); + // The operating system's trust store; builds without the + // `system-roots` feature (macOS binaries cross-built without + // Apple's SDK) use the bundled Mozilla roots instead. + #[cfg(feature = "system-roots")] + let roots = ureq::tls::RootCerts::PlatformVerifier; + #[cfg(not(feature = "system-roots"))] + let roots = ureq::tls::RootCerts::WebPki; + let tls = ureq::tls::TlsConfig::builder().root_certs(roots).build(); let l = &self.limits; let cfg = ureq::Agent::config_builder() .tls_config(tls) diff --git a/tests/cli.rs b/tests/cli.rs index 84fb04d..b7c0d03 100644 --- a/tests/cli.rs +++ b/tests/cli.rs @@ -313,7 +313,7 @@ fn hold_lock(e: &Env, bin: &str) -> Child { c.stdin.as_mut().unwrap().write_all(b"\0").unwrap(); // Wait until the lock file is held exclusively: probe with our own // fcntl lock attempt from this process. - let deadline = Instant::now() + Duration::from_secs(20); + let deadline = Instant::now() + Duration::from_secs(120); loop { if locked_exclusively(&e.dir.join("store/.lock")) { return c; @@ -350,13 +350,32 @@ fn lock_contention(holder: &str, waiter: &str) { .stderr(Stdio::piped()) .spawn() .unwrap(); - std::thread::sleep(Duration::from_millis(1500)); + // Wait (however long unlocking takes on a busy machine) until the + // reader says it is blocked on the lock, and is still running. + let mut err = w.stderr.take().unwrap(); + let (tx, rx) = std::sync::mpsc::channel(); + std::thread::spawn(move || { + use std::io::Read; + let mut all = Vec::new(); + let mut buf = [0u8; 256]; + while let Ok(n) = err.read(&mut buf) { + if n == 0 { + break; + } + all.extend_from_slice(&buf[..n]); + if String::from_utf8_lossy(&all).contains("waiting for another vault process to finish") { + let _ = tx.send(()); + } + } + }); + rx.recv_timeout(Duration::from_secs(120)) + .expect("reader never reported waiting for the lock"); + std::thread::sleep(Duration::from_millis(300)); assert!(w.try_wait().unwrap().is_none(), "reader did not wait for the writer"); h.kill().unwrap(); // death releases the lock let _ = h.wait(); let o = w.wait_with_output().unwrap(); - assert!(o.status.success()); - assert!(String::from_utf8_lossy(&o.stderr).contains("waiting for another vault process to finish")); + assert!(o.status.success(), "reader failed after the writer died"); } #[cfg(unix)]