From 79d21bb5f76631bf2c65c4b5648b60c0f6bf7a83 Mon Sep 17 00:00:00 2001 From: Ty Smith Date: Sun, 17 May 2026 12:36:07 -0700 Subject: [PATCH 1/2] docs: pre-launch audit across docs, man pages, config, and website Sweep of documentation accuracy, post-D-Bus-migration drift, and stale content surfaced by a full review of every standard CLI flag, doc chapter, default config field, CHANGELOG entry, and the marketing website ahead of the v0.1.0 public launch. Man pages - Remove stale Unix-socket / SO_PEERCRED claims; describe D-Bus system bus and GetConnectionUnixUser UID verification - Fix wrong TPM subcommand names (seal-db -> seal-key, unseal-db -> unseal-key) - Add missing encrypt, decrypt, restart, audit, and setup --non-interactive sections CLI reference - Add missing commands and flags to docs/cli.md and book/src/cli-reference.md (encrypt, decrypt, audit, three tpm subcommands, three bench subcommands, global --config, setup --non-interactive) - Correct understated GPU support (CPU/CUDA -> CPU/CUDA/ROCm/OpenVINO) - Clarify that encrypt --generate-key only generates key material - Sync both files; eliminate drift Config and latency - Fix device.warmup_frames default in config/facelock.toml (3 -> 2 to match code) - Add previously undocumented landmark_displacement_px, landmark_min_moving, and tpm.seal_database fields - Standardize daemon latency to "~200ms warm / ~600ms cold" across CHANGELOG, config, architecture, troubleshooting - Bring book/src/configuration.md into parity with docs/configuration.md (missing [device] keys, [security.pam_policy], custom-model SHA256 prose, FACELOCK_CONFIG privilege caveat) Contracts - book/src/contracts.md: add facelock-polkit-agent binary, encrypt / decrypt / audit subcommands, missing [device] and [security] keys, rate_limit table, sealed column, FACELOCK_CONFIG caveat; correct SCRFD 10G size 16MB -> 17MB Security - Remove stale "creating socket" / "socket already bound" comments in docs/security.md capability-dropping example - Add suppress_unknown to the security config reference - book/src/security.md: fix [notification] enabled -> mode, use canonical /usr/share/dbus-1/system.d/ path, document per-method UID authorization (Authenticate / Enroll / Shutdown) CHANGELOG - Add [Unreleased] section - Split monolithic Added block into Added / Security / Fixed - Add missing entries: expanded status command, APT two-channel structure, PAM install/uninstall fixes README and CONTRIBUTING - Update version label v0.1.0-alpha -> v0.1.0 - Expand crate table to 11 (was 9: add facelock-bench, facelock-test-support) - Replace stale "IPC protocol" with "D-Bus interface" - Drop unverified 10MB IPC message size claim Website - Fix install command (cd dist && makepkg -si -> just install) - Redirect all broken website/docs/*.html links to GitHub source as a stopgap until the mdbook is published to GitHub Pages (TODO marker in HTML) - Add Open Graph meta tags, inline favicon, aria-hidden on decorative SVGs, mdash for em dashes Other - Standardize PAM backup filename to sudo.facelock-backup across book/src/testing.md, book/src/troubleshooting.md, docs/testing-safety.md (was inconsistent sudo.bak in two places) - Update docs/testing-roadmap.md to reflect v0.1.0 release and active tag-driven distribution channels - Add CI-distribution hedge in book/src/quickstart.md package install section Manual follow-ups (intentionally not in this PR): - Push v0.1.0 git tag (release-trigger event) - Replace website docs/ stopgap URLs once mdbook is hosted on GitHub Pages Co-Authored-By: Claude Opus 4.7 (1M context) --- CHANGELOG.md | 32 ++++++++++-- CONTRIBUTING.md | 6 +-- README.md | 6 ++- book/src/cli-reference.md | 107 +++++++++++++++++++++++++++++++++++--- book/src/configuration.md | 26 +++++++-- book/src/contracts.md | 22 ++++++-- book/src/quickstart.md | 2 + book/src/security.md | 6 ++- book/src/testing.md | 4 +- config/facelock.toml | 21 ++++++-- docs/architecture.md | 2 +- docs/cli.md | 103 ++++++++++++++++++++++++++++++++++-- docs/configuration.md | 7 ++- docs/security.md | 5 +- docs/testing-roadmap.md | 17 +++--- docs/testing-safety.md | 4 +- docs/troubleshooting.md | 2 +- man/facelock.1 | 57 ++++++++++++++++---- man/pam_facelock.8 | 23 +++++--- website/index.html | 44 +++++++++------- website/style.css | 1 + 21 files changed, 407 insertions(+), 90 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4cc23d4..6faba45 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,16 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [Unreleased] + +### Added + +### Changed + +### Fixed + +### Security + ## [0.1.0] - 2026-05-17 Initial open-source release. @@ -16,14 +26,30 @@ Initial open-source release. - **Daemon**: Persistent process with model caching, ~200ms warm auth latency - **CLI**: Unified `facelock` binary — setup, enroll, test, preview, bench, audit, and more - **Anti-spoofing**: IR camera enforcement, frame variance checks, landmark liveness detection -- **Security**: Constant-time matching (subtle), AES-256-GCM encryption at rest, optional TPM key sealing, SHA256-verified models, persistent rate limiting, D-Bus method-level authorization, hardened PAM env handling - **D-Bus**: System bus interface (`org.facelock.Daemon`) with deny-all policy and caller UID verification -- **systemd**: Service hardening (ProtectSystem, NoNewPrivileges, InaccessiblePaths) - **GPU**: Runtime-selectable execution providers (CPU, CUDA, ROCm, OpenVINO) via `execution_provider` config — no compile-time flags - **Setup wizard**: Interactive model-quality and inference-device selection, streaming download progress bar, only downloads the models actually selected in config +- **Status command**: Reports inference provider and ORT library location, enrolled face count for the current user, security posture (IR enforcement, liveness, `min_auth_frames`), and notification state (`73a5c00`) - **Models**: Self-hosted ONNX assets distributed via GitHub release downloads (no third-party model fetches in the auth path) -- **Packaging**: deb, rpm, PKGBUILD (`facelock` and `facelock-git`), Nix flake, signed APT repository (TPM `main` + non-TPM `legacy` channels), systemd/D-Bus activation, OpenRC/runit/s6 +- **Packaging**: deb, rpm, PKGBUILD (`facelock` and `facelock-git`), Nix flake, signed APT repository with two channels — `main` (TPM-enabled, Debian trixie+ / Ubuntu 25.04+) and `legacy` (non-TPM, Debian bookworm / Ubuntu 24.04) — systemd/D-Bus activation, OpenRC/runit/s6 (`c70999b`) - **CI/CD**: Build/test/lint pipeline, TPM tests via swtpm, container PAM smoke tests, end-to-end `.deb` and `.rpm` package install validation - **Documentation**: mdBook, man pages, ADRs, security posture assessment, threat model +### Security + +- **Constant-time matching**: Embedding comparison via `subtle` crate to prevent timing side-channels +- **Encryption at rest**: AES-256-GCM software encryption for stored face embeddings +- **TPM key sealing**: Optional TPM-backed key protection for the encryption key +- **Model integrity**: SHA256 verification of ONNX model files at load time +- **Rate limiting**: 5 auth attempts per user per 60 seconds (default), enforced in daemon +- **D-Bus authorization**: Daemon verifies caller UID via `GetConnectionUnixUser` before executing methods +- **Enrollment restriction**: Root-required enrollment enforced in auth paths (`c01a655`) +- **PAM env hardening**: Hardened PAM environment handling to prevent injection (`c01a655`) +- **systemd hardening**: `ProtectSystem=strict`, `NoNewPrivileges`, `InaccessiblePaths`, and related service restrictions + +### Fixed + +- **PAM install output**: Conditional install messages — suppressed when PAM entry already present (`c12a970`) +- **PAM uninstall**: Uninstall now removes entries from all relevant PAM services, not just the primary one (`c12a970`) + [0.1.0]: https://github.com/tyvsmith/facelock/releases/tag/v0.1.0 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 8bed392..ba425f6 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -19,14 +19,14 @@ Facelock is a Cargo workspace with 11 crates: | Crate | Type | Purpose | |-------|------|---------| -| `facelock-core` | lib | Config, types, errors, IPC protocol, traits | +| `facelock-core` | lib | Config, types, errors, D-Bus interface, traits | | `facelock-camera` | lib | V4L2 capture, auto-detection, preprocessing | | `facelock-face` | lib | ONNX inference (SCRFD + ArcFace) | | `facelock-store` | lib | SQLite face embedding storage | | `facelock-daemon` | lib | Auth/enroll logic, rate limiting, liveness, audit | | `facelock-cli` | bin | Unified CLI (`facelock` binary, includes `bench` subcommand) | | `facelock-bench` | bin | Standalone benchmark and calibration utility | -| `pam-facelock` | cdylib | PAM module (libc + toml + serde, zbus only) | +| `pam-facelock` | cdylib | PAM module (libc, toml, serde, zbus only) | | `facelock-tpm` | lib | Optional TPM encryption | | `facelock-polkit` | bin | Polkit face authentication agent | | `facelock-test-support` | lib | Mocks and fixtures for testing | @@ -98,7 +98,7 @@ Read `docs/security.md` before implementing any auth-related code. Key rules: - `security.require_ir` defaults to **true**. Never weaken this default. - Frame variance checks must remain in the auth path. - Model files are SHA256-verified at load time. -- IPC messages have size limits (10MB max). Never allocate unbounded buffers. +- IPC messages have size limits enforced by the D-Bus daemon (see `dbus/org.facelock.Daemon.conf`). Never allocate unbounded buffers. - D-Bus system bus policy restricts daemon access. - The PAM module logs all auth attempts to syslog. - Rate limiting is enforced in the daemon (5 attempts/user/60s default). diff --git a/README.md b/README.md index db86901..5d467e1 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ # Facelock: Face Authentication for Linux -> **v0.1.0-alpha** — Pre-release. Under active development. Functional, daily-driveable, but experimental. APIs will change before 1.0. See [CHANGELOG.md](CHANGELOG.md). +> **v0.1.0** — Stable release. See [CHANGELOG.md](CHANGELOG.md) for details. A modern face authentication system for Linux PAM. Provides Windows Hello-style facial auth with IR anti-spoofing, configurable as a persistent daemon or daemonless one-shot. All inference runs locally on your hardware -- no cloud services, no network requests, no telemetry. Your biometric data never leaves your machine. @@ -113,15 +113,17 @@ facelock audit View structured audit log ## Architecture ``` -facelock-core Config, types, errors, D-Bus interface +facelock-core Config, types, errors, D-Bus interface, traits facelock-camera V4L2 capture, auto-detection, preprocessing facelock-face ONNX inference (SCRFD detection + ArcFace embedding) facelock-store SQLite face embedding storage facelock-daemon Auth/enroll logic, rate limiting, liveness, audit facelock-cli Unified CLI binary (facelock) +facelock-bench Standalone benchmark and calibration utility facelock-tpm TPM-sealed key encryption, software AES-256-GCM facelock-polkit Polkit authentication agent pam-facelock PAM module (libc + toml + serde + zbus only) +facelock-test-support Mocks and fixtures for testing ``` ### Face Recognition Pipeline diff --git a/book/src/cli-reference.md b/book/src/cli-reference.md index 4b39fc6..057feff 100644 --- a/book/src/cli-reference.md +++ b/book/src/cli-reference.md @@ -2,12 +2,21 @@ All commands are subcommands of the `facelock` binary. +## Global flags + +The following flag is accepted by every subcommand (declared `global = true`): + +| Flag | Description | +|------|-------------| +| `--config ` | Override the config file path. Takes precedence over `FACELOCK_CONFIG`. | + ## facelock setup -Interactive setup wizard. Walks through camera selection, model quality, inference device (CPU/CUDA), model downloads, encryption, enrollment, and PAM configuration. Can also be run with flags for individual setup tasks. +Interactive setup wizard. Walks through camera selection, model quality, inference device (CPU / CUDA / ROCm / OpenVINO), model downloads, encryption, enrollment, and PAM configuration. Can also be run with flags for individual setup tasks. ```bash facelock setup # interactive wizard +facelock setup --non-interactive # run wizard without prompts facelock setup --systemd # install systemd units facelock setup --systemd --disable # disable systemd units facelock setup --pam # install to /etc/pam.d/sudo @@ -49,6 +58,20 @@ facelock list --user alice # specific user facelock list --json # JSON output ``` +`--json` emits an array of objects: + +```json +[ + { + "id": 1, + "label": "office", + "user": "alice", + "created_at": 1700000000, + "embedder_model": "arcface_r50" + } +] +``` + ## facelock remove Remove a specific face model by ID. @@ -116,6 +139,7 @@ Run the persistent authentication daemon. ```bash facelock daemon # use default config +facelock daemon -c /path/to/config.toml # short alias for --config facelock daemon --config /path/to/config.toml ``` @@ -132,7 +156,11 @@ facelock auth --user alice --config /etc/facelock/config.toml Exit codes: 0 = matched, 1 = no match, 2 = error. -## facelock tpm status +## facelock tpm + +TPM integration status and management. + +### facelock tpm status Report TPM availability and configuration. @@ -140,21 +168,88 @@ Report TPM availability and configuration. facelock tpm status ``` +### facelock tpm seal-key + +Seal the AES encryption key with the TPM, migrating from a plaintext keyfile to TPM-backed storage. + +```bash +facelock tpm seal-key +``` + +### facelock tpm unseal-key + +Unseal the AES key from the TPM back to a plaintext keyfile, migrating from TPM-backed to keyfile storage. + +```bash +facelock tpm unseal-key +``` + +### facelock tpm pcr-baseline + +Display the current PCR values for all configured PCR indices. + +```bash +facelock tpm pcr-baseline +``` + ## facelock bench Benchmark and calibration tools. ```bash -facelock bench cold-auth # cold start authentication latency -facelock bench warm-auth # warm authentication latency -facelock bench model-load # model loading time +facelock bench cold-auth # cold start authentication latency (model load + first auth) +facelock bench warm-auth # warm authentication latency (pre-loaded models, 10 iterations) +facelock bench preview # frame capture + face detection latency +facelock bench enrollment # time to capture and embed snapshots (dry run, embeddings not stored) +facelock bench model-load # ONNX model load time (SCRFD + ArcFace) +facelock bench calibrate # sweep FAR/FRR thresholds and recommend optimal value facelock bench report # full benchmark report ``` +`cold-auth`, `warm-auth`, `calibrate`, and `report` require enrolled faces. When encryption method is `tpm`, these subcommands require root. + +## facelock encrypt + +Encrypt all unencrypted embeddings in the database with AES-256-GCM. + +```bash +facelock encrypt # encrypt using the configured key +facelock encrypt --generate-key # generate a new key file (or seal a new TPM key) WITHOUT re-encrypting embeddings +``` + +`--generate-key` only creates the key material. Run `facelock encrypt` (without the flag) afterwards to encrypt the embeddings. + +## facelock decrypt + +Decrypt all software-encrypted embeddings in the database (reverting AES-256-GCM encryption). + +```bash +facelock decrypt +``` + +## facelock audit + +View the structured audit log of authentication events. + +```bash +facelock audit # show last 20 entries (default) +facelock audit -l 50 # show last 50 entries +facelock audit --lines 50 # long form +facelock audit -f # follow mode: stream new entries as they arrive +facelock audit --follow # long form +``` + +| Flag | Short | Default | Description | +|------|-------|---------|-------------| +| `--follow` | `-f` | false | Watch for new entries (like `tail -f`) | +| `--lines N` | `-l` | 20 | Number of recent entries to display | + ## facelock restart Restart the persistent daemon. On systemd systems, runs `systemctl restart facelock-daemon.service`. Otherwise, sends a D-Bus shutdown request and the daemon restarts on next use via D-Bus activation. +Requires root. If run interactively as a non-root user, the CLI prompts to re-run via `sudo`. + ```bash facelock restart ``` @@ -171,5 +266,5 @@ For commands that accept `--user`: | Variable | Purpose | |----------|---------| -| `FACELOCK_CONFIG` | Override config file path | +| `FACELOCK_CONFIG` | Override config file path for unprivileged CLI commands. Ignored by privileged PAM/root auth flows; use `--config` there. | | `RUST_LOG` | Control log verbosity (e.g., `facelock_daemon=debug`) | diff --git a/book/src/configuration.md b/book/src/configuration.md index 259b0bb..3bf3301 100644 --- a/book/src/configuration.md +++ b/book/src/configuration.md @@ -1,6 +1,6 @@ # Configuration Reference -Facelock reads its configuration from `/etc/facelock/config.toml`. Override the path with the `FACELOCK_CONFIG` environment variable. +Facelock reads its configuration from `/etc/facelock/config.toml`. Override the path with the `FACELOCK_CONFIG` environment variable. Note: `FACELOCK_CONFIG` is ignored by privileged PAM and root auth flows, which always use either an explicit `--config` path or `/etc/facelock/config.toml`. All settings are optional. Facelock auto-detects the camera and uses sensible defaults. The annotated config file at `config/facelock.toml` in the repository serves as the canonical example. @@ -13,6 +13,11 @@ Camera settings. | `path` | string (optional) | Auto-detect | Camera device path (e.g., `/dev/video2`). When omitted, Facelock auto-detects the best available camera, preferring IR over RGB. | | `max_height` | u32 | `480` | Maximum frame height in pixels. Frames taller than this are downscaled to improve processing speed. | | `rotation` | u16 | `0` | Rotate captured frames. Values: `0`, `90`, `180`, `270`. Useful for cameras mounted sideways. | +| `warmup_frames` | u32 | `2` | Frames to discard immediately after opening the camera to let exposure and gain stabilize. Device quirks may override this. | +| `dark_threshold` | f32 | `0.6` | Fraction of pixels that must be darker than `dark_pixel_value` before the frame is treated as unusably dark. | +| `dark_pixel_value` | u8 | `10` | Pixel brightness cutoff used by the dark-frame check. | +| `ir_emitter` | bool | `false` | Attempt to enable a controllable IR emitter when the camera opens. Only needed for hardware that does not auto-enable its IR LED. | +| `camera_release_secs` | u32 | `5` | Seconds to keep the camera open after daemon-mode auth before releasing it, to avoid repeated warmup cost on back-to-back requests. | ## [recognition] @@ -24,8 +29,8 @@ Face detection and embedding parameters. | `timeout_secs` | u32 | `5` | Maximum seconds to attempt recognition before giving up. Must be > 0. | | `detection_confidence` | f32 | `0.5` | Minimum confidence for the face detector to report a detection. Lower values detect more faces but increase false positives. | | `nms_threshold` | f32 | `0.4` | Non-maximum suppression threshold for overlapping detections. | -| `detector_model` | string | `"scrfd_2.5g_bnkps.onnx"` | ONNX detector model filename. Must exist in `daemon.model_dir`. | -| `embedder_model` | string | `"w600k_r50.onnx"` | ONNX embedder model filename. Must exist in `daemon.model_dir`. | +| `detector_model` | string | `"scrfd_2.5g_bnkps.onnx"` | ONNX detector model filename. Must exist in `daemon.model_dir`. Bundled models are verified against the manifest; custom models require `detector_sha256`. | +| `embedder_model` | string | `"w600k_r50.onnx"` | ONNX embedder model filename. Must exist in `daemon.model_dir`. Bundled models are verified against the manifest; custom models require `embedder_sha256`. | | `execution_provider` | string | `"cpu"` | ONNX Runtime execution provider. Values: `"cpu"`, `"cuda"`, `"rocm"`, `"openvino"`. GPU providers require a GPU-enabled ONNX Runtime package installed on the system. | | `threads` | u32 | `4` | Number of CPU threads for ONNX inference. | @@ -50,6 +55,7 @@ Run `facelock test` to see your similarity scores, then set the threshold below | High accuracy | `det_10g.onnx` (17MB) | `glintr100.onnx` (249MB) | ~266MB | ~40-50ms slower, best accuracy | Run `facelock setup` to select a model tier interactively and download the required models. +If you point `detector_model` or `embedder_model` at a custom file, you must also set the matching SHA256 so the daemon can verify it at load time. ## [daemon] @@ -57,7 +63,7 @@ Controls how the PAM module reaches the face engine. | Key | Type | Default | Description | |-----|------|---------|-------------| -| `mode` | string | `"daemon"` | `"daemon"` connects to a persistent daemon via D-Bus system bus (~150-600ms depending on camera state). `"oneshot"` spawns `facelock auth` per PAM call (slower, ~700ms+, no background process). | +| `mode` | string | `"daemon"` | `"daemon"` connects to a persistent daemon via D-Bus system bus (~200ms warm, ~600ms cold). `"oneshot"` spawns `facelock auth` per PAM call (slower, ~600ms+, no background process). | | `model_dir` | string | `"/var/lib/facelock/models"` | Directory containing ONNX model files. | | `idle_timeout_secs` | u64 | `0` | Shut down the daemon after this many idle seconds. `0` means never. Useful with D-Bus activation. | @@ -77,6 +83,8 @@ Controls how the PAM module reaches the face engine. | `require_ir` | bool | `true` | Require an IR camera for authentication. RGB cameras are trivially spoofed with a printed photo. Only set to `false` for development/testing. | | `require_frame_variance` | bool | `true` | Require multiple frames with different embeddings before accepting. Defends against static photo attacks. | | `require_landmark_liveness` | bool | `false` | Require landmark movement between frames to pass liveness check. Detects static images by tracking facial landmark positions across frames. Experimental; off by default. | +| `landmark_displacement_px` | f32 | `1.5` | Minimum pixel displacement for a landmark to count as "moving" between frames. Only used when `require_landmark_liveness` is true. | +| `landmark_min_moving` | u32 | `3` | Number of facial landmarks (out of 5) that must show movement to pass the liveness check. Only used when `require_landmark_liveness` is true. | | `suppress_unknown` | bool | `false` | Suppress warnings for unknown users (users with no enrolled face). | | `min_auth_frames` | u32 | `3` | Minimum number of matching frames required before accepting. Only applies when `require_frame_variance` is true. | @@ -87,6 +95,13 @@ Controls how the PAM module reaches the face engine. | `max_attempts` | u32 | `5` | Maximum auth attempts per user per window. | | `window_secs` | u64 | `60` | Rate limit window in seconds. | +### [security.pam_policy] + +| Key | Type | Default | Description | +|-----|------|---------|-------------| +| `allowed_services` | list of strings | `[]` | If non-empty, only these PAM services may use facelock. | +| `denied_services` | list of strings | `[]` | PAM services that must always skip facelock, even if otherwise allowed. | + ## [notification] Controls how authentication feedback is delivered. @@ -117,6 +132,8 @@ Controls how face embeddings are encrypted at rest. | `key_path` | string | `"/etc/facelock/encryption.key"` | Path to AES-256-GCM key file for `keyfile` method. | | `sealed_key_path` | string | `"/etc/facelock/encryption.key.sealed"` | Path to TPM-sealed AES key for `tpm` method. | +With `method = "tpm"`, the 32-byte AES key is sealed by the TPM at rest. At daemon startup, the key is unsealed and held in memory. Embeddings use the same AES-256-GCM format as `keyfile` — no re-encryption needed when migrating between methods. Migration commands: `facelock tpm seal-key` (keyfile → tpm) and `facelock tpm unseal-key` (tpm → keyfile). + ## [audit] Structured audit logging of authentication events. @@ -133,6 +150,7 @@ TPM 2.0 settings for sealing the AES encryption key. These settings apply when ` | Key | Type | Default | Description | |-----|------|---------|-------------| +| `seal_database` | bool | `false` | Seal the SQLite database file with the TPM key in addition to the encryption key. | | `pcr_binding` | bool | `false` | Bind sealed key to boot state (PCR values). | | `pcr_indices` | list of u32 | `[0, 1, 2, 3, 7]` | PCR registers to verify on unseal. | | `tcti` | string | `"device:/dev/tpmrm0"` | TPM Communication Interface. | diff --git a/book/src/contracts.md b/book/src/contracts.md index 1561b5e..e701305 100644 --- a/book/src/contracts.md +++ b/book/src/contracts.md @@ -8,6 +8,7 @@ Stable contracts. Do not change without updating this document. |--------|-------|---------| | `facelock` | facelock-cli | Unified CLI (daemon, auth, enroll, test, setup, etc.) | | `pam_facelock.so` | pam-facelock | PAM authentication module | +| `facelock-polkit-agent` | facelock-polkit | Polkit face authentication agent (not production-ready — do not autostart; will steal polkit auth from the DE's agent) | ## CLI Subcommands @@ -28,6 +29,9 @@ Stable contracts. Do not change without updating this document. | `facelock daemon` | Run persistent daemon | | `facelock auth --user X` | One-shot auth (PAM helper) | | `facelock tpm status` | TPM status | +| `facelock encrypt` | Encrypt face database | +| `facelock decrypt` | Decrypt face database | +| `facelock audit` | View audit log | | `facelock bench` | Benchmarks | | `facelock restart` | Restart daemon | @@ -59,7 +63,7 @@ The CLI silently falls back to direct mode when the daemon is not available on D | `/usr/bin/facelock` | root:root | 755 | CLI binary | | `/lib/security/pam_facelock.so` | root:root | 755 | PAM module | -All paths overridable via config. `FACELOCK_CONFIG` env var overrides config location. +All paths overridable via config. `FACELOCK_CONFIG` is honored for unprivileged processes, but privileged PAM/root auth flows ignore the environment and use either an explicit `--config` path or `/etc/facelock/config.toml`. ## Config Schema @@ -69,11 +73,11 @@ TOML format. All keys optional -- camera auto-detected, sensible defaults for ev | Section | Key fields | |---------|-----------| -| `[device]` | `path` (Option), `max_height`, `rotation` | +| `[device]` | `path` (Option), `max_height`, `rotation`, `warmup_frames`, `dark_threshold`, `dark_pixel_value`, `ir_emitter`, `camera_release_secs` | | `[recognition]` | `threshold`, `timeout_secs`, `detector_model`, `embedder_model`, `threads`, `execution_provider` | | `[daemon]` | `mode` (DaemonMode enum), `model_dir`, `idle_timeout_secs` | | `[storage]` | `db_path` | -| `[security]` | `require_ir`, `require_frame_variance`, `min_auth_frames`, `abort_if_ssh`, `abort_if_lid_closed`, rate_limit sub-section | +| `[security]` | `disabled`, `suppress_unknown`, `require_ir`, `require_frame_variance`, `min_auth_frames`, `abort_if_ssh`, `abort_if_lid_closed`, `rate_limit` sub-section | | `[notification]` | `mode` (off/terminal/desktop/both), `notify_prompt`, `notify_on_success`, `notify_on_failure` | | `[snapshots]` | `mode` (off/all/failure/success), `dir` | | `[encryption]` | `method` (none/keyfile/tpm), `key_path`, `sealed_key_path` | @@ -104,10 +108,18 @@ CREATE TABLE face_models ( CREATE TABLE face_embeddings ( id INTEGER PRIMARY KEY AUTOINCREMENT, model_id INTEGER NOT NULL REFERENCES face_models(id) ON DELETE CASCADE, - embedding BLOB NOT NULL -- 512 x f32 = 2048 bytes + embedding BLOB NOT NULL, -- 512 x f32 = 2048 bytes (or encrypted blob) + sealed INTEGER NOT NULL DEFAULT 0 +); + +CREATE TABLE rate_limit ( + user TEXT NOT NULL, + attempt_time INTEGER NOT NULL ); ``` +Only failed authentication attempts are recorded in `rate_limit`. Daemon mode and oneshot mode share the same SQLite-backed window, so daemon restarts do not clear lockout state. + ## IPC Protocol D-Bus system bus (`org.facelock.Daemon`). Only used in daemon mode. The daemon exposes a D-Bus interface on the system bus, and both the PAM module and CLI connect as D-Bus clients. Access is controlled by D-Bus system bus policy (`/etc/dbus-1/system.d/org.facelock.Daemon.conf`). @@ -153,7 +165,7 @@ These defaults must not be weakened without security review. |-------|------|------|---------| | SCRFD 2.5G | `scrfd_2.5g_bnkps.onnx` | ~3MB | Yes | | ArcFace R50 | `w600k_r50.onnx` | ~166MB | Yes | -| SCRFD 10G | `det_10g.onnx` | ~16MB | Optional | +| SCRFD 10G | `det_10g.onnx` | ~17MB | Optional | | ArcFace R100 | `glintr100.onnx` | ~249MB | Optional | Configurable via `recognition.detector_model` and `recognition.embedder_model`. diff --git a/book/src/quickstart.md b/book/src/quickstart.md index af1ed99..6a6353a 100644 --- a/book/src/quickstart.md +++ b/book/src/quickstart.md @@ -2,6 +2,8 @@ ## Package Install +**Note:** Packages are published via tag-driven CI on release. If your distro doesn't see the latest version yet, fall back to building from source ([Development Setup](#development-setup)). + ### Arch Linux (AUR) ```bash diff --git a/book/src/security.md b/book/src/security.md index 0ea0dfa..ab9e7c3 100644 --- a/book/src/security.md +++ b/book/src/security.md @@ -110,7 +110,9 @@ For high-security deployments, embeddings can be encrypted with AES-256-GCM usin #### A. D-Bus System Bus Policy (Required) -The D-Bus system bus policy (`/etc/dbus-1/system.d/org.facelock.Daemon.conf`) restricts which users and groups can own the bus name and invoke methods. Only root and members of the `facelock` group are granted access. +The D-Bus system bus policy (`/usr/share/dbus-1/system.d/org.facelock.Daemon.conf`) restricts which users and groups can own the bus name and invoke methods. Only root and members of the `facelock` group are granted access. (`/etc/dbus-1/system.d/` is the admin-override location for local customization.) + +Beyond bus-level access, the daemon enforces per-method UID authorization. Before executing any method, the daemon calls `GetConnectionUnixUser` to verify the caller's UID. `Authenticate` allows the caller's own UID. `Enroll` and `Shutdown` are restricted to root (UID 0) only. This prevents a `facelock` group member from enrolling faces or shutting down the daemon without root privileges. #### B. D-Bus Message Size Limits (Required) @@ -166,7 +168,7 @@ require_landmark_liveness = false # Require landmark movement between frames (of min_auth_frames = 3 # Minimum frames before accepting (variance check) [notification] -enabled = true # Show "Identifying face..." on login screen +mode = "terminal" # Show "Identifying face..." on login screen [security.pam_policy] allowed_services = ["sudo", "polkit-1"] diff --git a/book/src/testing.md b/book/src/testing.md index ee5750b..cc6c81e 100644 --- a/book/src/testing.md +++ b/book/src/testing.md @@ -50,10 +50,10 @@ Disposable VM with snapshots. USB camera passthrough for real hardware testing. Safety checklist: 1. Open root shell in separate terminal -- **keep it open** -2. `sudo cp /etc/pam.d/sudo /etc/pam.d/sudo.bak` +2. `sudo cp /etc/pam.d/sudo /etc/pam.d/sudo.facelock-backup` 3. `sudo facelock setup --pam --service sudo` 4. Test in NEW terminal: `sudo echo test` -5. If broken, revert from root shell: `sudo cp /etc/pam.d/sudo.bak /etc/pam.d/sudo` +5. If broken, revert from root shell: `sudo cp /etc/pam.d/sudo.facelock-backup /etc/pam.d/sudo` 6. **Never** modify `system-auth` or `login` until sudo works perfectly Emergency recovery: boot from USB, mount partition, remove PAM line, reboot. diff --git a/config/facelock.toml b/config/facelock.toml index 06890bd..6558db8 100644 --- a/config/facelock.toml +++ b/config/facelock.toml @@ -29,8 +29,8 @@ # Frames to discard immediately after opening the camera so exposure # and gain can stabilize. Hardware quirks may override this. -# Default: 3 -# warmup_frames = 3 +# Default: 2 +# warmup_frames = 2 # Dark-frame rejection. If this fraction of pixels is below # dark_pixel_value, the frame is treated as unusably dark. @@ -179,6 +179,14 @@ # Default: false # require_landmark_liveness = false +# Minimum pixel displacement for a landmark to count as "moving" between frames. +# Default: 1.5 +# landmark_displacement_px = 1.5 + +# Number of facial landmarks (out of 5) that must show movement to pass liveness. +# Default: 3 +# landmark_min_moving = 3 + # Suppress warnings for unknown users (users with no enrolled face). # Default: false # suppress_unknown = false @@ -189,13 +197,17 @@ # min_auth_frames = 3 # Rate limiting — prevents brute-force attempts. +# # [security.rate_limit] -# max_attempts = 5 # Max auth attempts per user per window (default: 5) -# window_secs = 60 # Rate limit window in seconds (default: 60) +# # Max auth attempts per user per window (default: 5) +# max_attempts = 5 +# # Rate limit window in seconds (default: 60) +# window_secs = 60 # PAM service policy — allow/deny specific PAM service names. # If allowed_services is non-empty, only those services may use facelock. # denied_services always takes precedence. +# # [security.pam_policy] # allowed_services = ["sudo", "polkit-1"] # denied_services = ["login", "sshd", "su"] @@ -307,6 +319,7 @@ # 32-byte AES key is sealed/unsealed by the TPM at daemon startup. # # [tpm] +# seal_database = false # Seal the SQLite database file with the TPM key (default: false) # pcr_binding = false # Bind sealed key to boot state (PCR values) # pcr_indices = [0, 1, 2, 3, 7] # PCR registers to verify on unseal # tcti = "device:/dev/tpmrm0" # TPM communication interface diff --git a/docs/architecture.md b/docs/architecture.md index 39c6d3a..ea9b4b9 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -208,7 +208,7 @@ flowchart LR ### Daemon Mode The daemon (`facelock daemon`) runs persistently, holding ONNX models and camera resources in memory. The PAM module and CLI connect via D-Bus system bus. Benefits: -- ~600ms typical auth latency (~150ms with warm camera) +- ~200ms warm auth latency (camera open, models loaded); ~600ms cold auth (camera reopen) - Camera stays warm between back-to-back requests - Single point of resource management diff --git a/docs/cli.md b/docs/cli.md index 063f1b2..ba9158b 100644 --- a/docs/cli.md +++ b/docs/cli.md @@ -2,12 +2,21 @@ All commands are subcommands of the `facelock` binary. +## Global flags + +The following flag is accepted by every subcommand (declared `global = true`): + +| Flag | Description | +|------|-------------| +| `--config ` | Override the config file path. Takes precedence over `FACELOCK_CONFIG`. | + ## facelock setup -Interactive setup wizard. Walks through camera selection, model quality, inference device (CPU/CUDA), model downloads, encryption, enrollment, and PAM configuration. Can also be run with flags for individual setup tasks. +Interactive setup wizard. Walks through camera selection, model quality, inference device (CPU / CUDA / ROCm / OpenVINO), model downloads, encryption, enrollment, and PAM configuration. Can also be run with flags for individual setup tasks. ```bash facelock setup # interactive wizard +facelock setup --non-interactive # run wizard without prompts facelock setup --systemd # install systemd units facelock setup --systemd --disable # disable systemd units facelock setup --pam # install to /etc/pam.d/sudo @@ -49,6 +58,20 @@ facelock list --user alice # specific user facelock list --json # JSON output ``` +`--json` emits an array of objects: + +```json +[ + { + "id": 1, + "label": "office", + "user": "alice", + "created_at": 1700000000, + "embedder_model": "arcface_r50" + } +] +``` + ## facelock remove Remove a specific face model by ID. @@ -116,6 +139,7 @@ Run the persistent authentication daemon. ```bash facelock daemon # use default config +facelock daemon -c /path/to/config.toml # short alias for --config facelock daemon --config /path/to/config.toml ``` @@ -132,7 +156,11 @@ facelock auth --user alice --config /etc/facelock/config.toml Exit codes: 0 = matched, 1 = no match, 2 = error. -## facelock tpm status +## facelock tpm + +TPM integration status and management. + +### facelock tpm status Report TPM availability and configuration. @@ -140,17 +168,82 @@ Report TPM availability and configuration. facelock tpm status ``` +### facelock tpm seal-key + +Seal the AES encryption key with the TPM, migrating from a plaintext keyfile to TPM-backed storage. + +```bash +facelock tpm seal-key +``` + +### facelock tpm unseal-key + +Unseal the AES key from the TPM back to a plaintext keyfile, migrating from TPM-backed to keyfile storage. + +```bash +facelock tpm unseal-key +``` + +### facelock tpm pcr-baseline + +Display the current PCR values for all configured PCR indices. + +```bash +facelock tpm pcr-baseline +``` + ## facelock bench Benchmark and calibration tools. ```bash -facelock bench cold-auth # cold start authentication latency -facelock bench warm-auth # warm authentication latency -facelock bench model-load # model loading time +facelock bench cold-auth # cold start authentication latency (model load + first auth) +facelock bench warm-auth # warm authentication latency (pre-loaded models, 10 iterations) +facelock bench preview # frame capture + face detection latency +facelock bench enrollment # time to capture and embed snapshots (dry run, embeddings not stored) +facelock bench model-load # ONNX model load time (SCRFD + ArcFace) +facelock bench calibrate # sweep FAR/FRR thresholds and recommend optimal value facelock bench report # full benchmark report ``` +`cold-auth`, `warm-auth`, `calibrate`, and `report` require enrolled faces. When encryption method is `tpm`, these subcommands require root. + +## facelock encrypt + +Encrypt all unencrypted embeddings in the database with AES-256-GCM. + +```bash +facelock encrypt # encrypt using the configured key +facelock encrypt --generate-key # generate a new key file (or seal a new TPM key) WITHOUT re-encrypting embeddings +``` + +`--generate-key` only creates the key material. Run `facelock encrypt` (without the flag) afterwards to encrypt the embeddings. + +## facelock decrypt + +Decrypt all software-encrypted embeddings in the database (reverting AES-256-GCM encryption). + +```bash +facelock decrypt +``` + +## facelock audit + +View the structured audit log of authentication events. + +```bash +facelock audit # show last 20 entries (default) +facelock audit -l 50 # show last 50 entries +facelock audit --lines 50 # long form +facelock audit -f # follow mode: stream new entries as they arrive +facelock audit --follow # long form +``` + +| Flag | Short | Default | Description | +|------|-------|---------|-------------| +| `--follow` | `-f` | false | Watch for new entries (like `tail -f`) | +| `--lines N` | `-l` | 20 | Number of recent entries to display | + ## facelock restart Restart the persistent daemon. On systemd systems, runs `systemctl restart facelock-daemon.service`. Otherwise, sends a D-Bus shutdown request and the daemon restarts on next use via D-Bus activation. diff --git a/docs/configuration.md b/docs/configuration.md index 613085e..19f5da9 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -15,7 +15,7 @@ Camera settings. | `path` | string (optional) | Auto-detect | Camera device path (e.g., `/dev/video2`). When omitted, Facelock auto-detects the best available camera, preferring IR over RGB. | | `max_height` | u32 | `480` | Maximum frame height in pixels. Frames taller than this are downscaled to improve processing speed. | | `rotation` | u16 | `0` | Rotate captured frames. Values: `0`, `90`, `180`, `270`. Useful for cameras mounted sideways. | -| `warmup_frames` | u32 | `3` | Frames to discard immediately after opening the camera to let exposure and gain stabilize. Device quirks may override this. | +| `warmup_frames` | u32 | `2` | Frames to discard immediately after opening the camera to let exposure and gain stabilize. Device quirks may override this. | | `dark_threshold` | f32 | `0.6` | Fraction of pixels that must be darker than `dark_pixel_value` before the frame is treated as unusably dark. | | `dark_pixel_value` | u8 | `10` | Pixel brightness cutoff used by the dark-frame check. | | `ir_emitter` | bool | `false` | Attempt to enable a controllable IR emitter when the camera opens. Only needed for hardware that does not auto-enable its IR LED. | @@ -67,7 +67,7 @@ Controls how the PAM module reaches the face engine. | Key | Type | Default | Description | |-----|------|---------|-------------| -| `mode` | string | `"daemon"` | `"daemon"` connects to a persistent daemon via D-Bus system bus (~150-600ms depending on camera state). `"oneshot"` spawns `facelock auth` per PAM call (slower, ~700ms+, no background process). | +| `mode` | string | `"daemon"` | `"daemon"` connects to a persistent daemon via D-Bus system bus (~200ms warm, ~600ms cold). `"oneshot"` spawns `facelock auth` per PAM call (slower, ~600ms+, no background process). | | `model_dir` | string | `"/var/lib/facelock/models"` | Directory containing ONNX model files. | | `idle_timeout_secs` | u64 | `0` | Shut down the daemon after this many idle seconds. `0` means never. Useful with D-Bus activation. | @@ -87,6 +87,8 @@ Controls how the PAM module reaches the face engine. | `require_ir` | bool | `true` | Require an IR camera for authentication. RGB cameras are trivially spoofed with a printed photo. Only set to `false` for development/testing. | | `require_frame_variance` | bool | `true` | Require multiple frames with different embeddings before accepting. Defends against static photo attacks. | | `require_landmark_liveness` | bool | `false` | Require landmark movement between frames to pass liveness check. Detects static images by tracking facial landmark positions across frames. Experimental; off by default. | +| `landmark_displacement_px` | f32 | `1.5` | Minimum pixel displacement for a landmark to count as "moving" between frames. Only used when `require_landmark_liveness` is true. | +| `landmark_min_moving` | u32 | `3` | Number of facial landmarks (out of 5) that must show movement to pass the liveness check. Only used when `require_landmark_liveness` is true. | | `suppress_unknown` | bool | `false` | Suppress warnings for unknown users (users with no enrolled face). | | `min_auth_frames` | u32 | `3` | Minimum number of matching frames required before accepting. Only applies when `require_frame_variance` is true. | @@ -152,6 +154,7 @@ TPM 2.0 settings for sealing the AES encryption key. These settings apply when ` | Key | Type | Default | Description | |-----|------|---------|-------------| +| `seal_database` | bool | `false` | Seal the SQLite database file with the TPM key in addition to the encryption key. | | `pcr_binding` | bool | `false` | Bind sealed key to boot state (PCR values). | | `pcr_indices` | list of u32 | `[0, 1, 2, 3, 7]` | PCR registers to verify on unseal. | | `tcti` | string | `"device:/dev/tpmrm0"` | TPM Communication Interface. | diff --git a/docs/security.md b/docs/security.md index 5932878..1fcd844 100644 --- a/docs/security.md +++ b/docs/security.md @@ -268,12 +268,12 @@ denied_services = ["login", "sshd", "su"] After initialization, drop unnecessary capabilities: ```rust -// After opening camera, loading models, creating socket: +// After opening camera, loading models, connecting to D-Bus: // Drop all capabilities except what's needed for ongoing operation use caps::{CapSet, Capability}; caps::clear(None, CapSet::Effective)?; caps::clear(None, CapSet::Permitted)?; -// Only keep what's needed: nothing (camera fd already open, socket already bound) +// Only keep what's needed: nothing (camera fd already open, D-Bus session attached) ``` #### B. systemd Hardening (Implemented) @@ -302,6 +302,7 @@ require_ir = true # CRITICAL: refuse RGB-only cameras (anti-spoof) require_frame_variance = true # Reject static images (photo defense) require_landmark_liveness = false # Require landmark movement between frames (off by default) min_auth_frames = 3 # Minimum frames before accepting (variance check) +suppress_unknown = false # Log unknown faces (true = suppress unknown-face log entries) [notification] mode = "terminal" # Show "Identifying face..." on login screen diff --git a/docs/testing-roadmap.md b/docs/testing-roadmap.md index fa8b2bd..7f7f960 100644 --- a/docs/testing-roadmap.md +++ b/docs/testing-roadmap.md @@ -1,6 +1,6 @@ # Testing Strategy, Coverage Gaps, and Deployment Roadmap -Last updated: 2026-03-14 +Last updated: 2026-05-17 ## 1. Current Testing Tiers @@ -166,18 +166,19 @@ production-ready in the justfile install recipe. ## 4. Deployment Roadmap -### Current state: dev builds only -- `just install` / `just uninstall` for local development -- No published packages in any repository +### Current state: v0.1.0 released +- **v0.1.0 released on 2026-05-17** (tag-driven CI) +- Published packages: `.deb` (APT with signing key), `.rpm` (Fedora COPR), PKGBUILD (awaiting AUR submission) +- `just install` / `just uninstall` for local development still available ### Packaging status | Format | Location | Status | |--------|----------|--------| -| Raw binaries | `release.yml` | Working. Triggered on `v*` tags. Uploads `facelock-x86_64-linux-gnu`, `pam_facelock.so`, SHA256SUMS to GitHub Releases. | -| `.deb` | `release.yml` (build-deb job) | Working. Built in CI, uploaded to GitHub Release. Not in any PPA. | -| `.rpm` | `release.yml` (build-rpm job) | Working. Built on Fedora container in CI, uploaded to GitHub Release. Includes authselect profile. Not in COPR. | -| PKGBUILD (Arch) | `dist/PKGBUILD` | Exists but not submitted to AUR. References `facelock.install` file. | +| Raw binaries | `release.yml` | Released. Triggered on `v*` tags. Uploads `facelock-x86_64-linux-gnu`, `pam_facelock.so`, SHA256SUMS to GitHub Releases. | +| `.deb` | `release.yml` (build-deb job) | Released. Built in CI, uploaded to GitHub Release. Signed APT repository at `tysmith.me/facelock/apt` (main/legacy variants). | +| `.rpm` | `release.yml` (build-rpm job) | Released. Built in CI. Fedora COPR webhook (`tyvsmith/facelock`) active per `releasing.md`. | +| PKGBUILD (Arch) | `dist/PKGBUILD` | Exists. Automated via tag CI; awaiting first published push to AUR. References `facelock.install` file. | | Nix flake | `dist/nix/flake.nix` | Exists with NixOS module (`module.nix`), derivation (`default.nix`), and dev shell. Not in nixpkgs. `doCheck = false` (needs camera). | | openrc | `dist/openrc/facelock-daemon` | Init script exists. | | runit | `dist/runit/run`, `dist/runit/log/run` | Service scripts exist. | diff --git a/docs/testing-safety.md b/docs/testing-safety.md index 372bcb8..84de03c 100644 --- a/docs/testing-safety.md +++ b/docs/testing-safety.md @@ -50,10 +50,10 @@ Disposable VM with snapshots. USB camera passthrough for real hardware testing. Safety checklist: 1. Open root shell in separate terminal — **keep it open** -2. `sudo cp /etc/pam.d/sudo /etc/pam.d/sudo.bak` +2. `sudo cp /etc/pam.d/sudo /etc/pam.d/sudo.facelock-backup` 3. `sudo facelock setup --pam --service sudo` 4. Test in NEW terminal: `sudo echo test` -5. If broken, revert from root shell: `sudo cp /etc/pam.d/sudo.bak /etc/pam.d/sudo` +5. If broken, revert from root shell: `sudo cp /etc/pam.d/sudo.facelock-backup /etc/pam.d/sudo` 6. **Never** modify `system-auth` or `login` until sudo works perfectly Emergency recovery: boot from USB, mount partition, remove PAM line, reboot. diff --git a/docs/troubleshooting.md b/docs/troubleshooting.md index 6276976..00992c7 100644 --- a/docs/troubleshooting.md +++ b/docs/troubleshooting.md @@ -44,7 +44,7 @@ ### First-start latency (~700ms -- 2s) -The first authentication after boot (or after the daemon starts) is slow because ONNX models must be loaded into memory. This is normal. Subsequent auths in daemon mode typically take ~600ms (or ~150ms if the camera is still warm from a recent auth). +The first authentication after boot (or after the daemon starts) is slow because ONNX models must be loaded into memory. This is normal. Subsequent auths in daemon mode typically take ~200ms (warm: camera open, models loaded) or ~600ms (cold: camera reopen). ### Consistently slow (~700ms+ every time) diff --git a/man/facelock.1 b/man/facelock.1 index 12b63fd..eda79d4 100644 --- a/man/facelock.1 +++ b/man/facelock.1 @@ -127,8 +127,9 @@ availability, and model integrity. List available camera devices. .TP .B daemon -Run the persistent authentication daemon. Listens on a Unix domain socket -for authentication and enrollment requests from the PAM module and CLI. +Run the persistent authentication daemon. Registers on the D-Bus system bus +as \fBorg.facelock.Daemon\fR to service authentication and enrollment requests +from the PAM module and CLI. .RS .TP .B \-c\fR, \fB\-\-config \fIpath\fR @@ -180,15 +181,40 @@ TPM integration status and management. .B status Report TPM availability and configuration. .TP -.B seal\-db -Seal all unsealed embeddings in the database using TPM. +.B seal\-key +Seal the AES encryption key with TPM (migrate keyfile to TPM). .TP -.B unseal\-db -Unseal all sealed embeddings (migrate away from TPM). +.B unseal\-key +Unseal the AES key from TPM back to a plaintext keyfile (migrate TPM to keyfile). .TP .B pcr\-baseline Display current PCR values for configured indices. .RE +.TP +.B encrypt +Encrypt all unencrypted embeddings in the database with AES-256-GCM. +.RS +.TP +.B \-\-generate\-key +Generate a new encryption key without encrypting embeddings. +.RE +.TP +.B decrypt +Decrypt all software-encrypted embeddings in the database. +.TP +.B restart +Restart the facelock daemon via systemd. +.TP +.B audit +View the structured audit log. +.RS +.TP +.B \-f\fR, \fB\-\-follow +Follow mode: watch for new log entries. +.TP +.B \-n\fR, \fB\-\-lines \fIcount\fR +Number of recent entries to show (default: 20). +.RE .SH ENVIRONMENT .TP .B RUST_LOG @@ -211,8 +237,12 @@ by root:facelock. Directory containing ONNX model files (SCRFD and ArcFace). SHA256-verified at load time. Permissions should be 644, owned by root:facelock. .TP -.I /run/facelock/facelock.sock -Unix domain socket used for IPC between the PAM module and the daemon. +.I /usr/share/dbus-1/system.d/org.facelock.Daemon.conf +D-Bus system bus policy file. Enforces deny-all default, allowing access only +to root and members of the \fBfacelock\fR group. +.TP +.I /usr/share/dbus-1/system-services/org.facelock.Daemon.service +D-Bus activation file for the facelock daemon service. .TP .I /usr/lib/security/pam_facelock.so PAM module shared library. @@ -228,9 +258,14 @@ The default configuration requires an IR camera .RB ( security.require_ir=true ). Frame variance checks are performed during authentication to prevent photo-based spoofing. Rate limiting is enforced by the daemon (5 attempts -per user per 60 seconds by default). IPC messages are limited to 10MB and -socket peers are verified via -.BR SO_PEERCRED . +per user per 60 seconds by default). IPC uses the D-Bus system bus; the +daemon verifies caller UID via +.B GetConnectionUnixUser +before executing any method, and the system bus policy in +.I org.facelock.Daemon.conf +applies a deny-all default, restricting access to root and the +.B facelock +group. Message size limits are enforced by the D-Bus daemon. .SH SEE ALSO .BR pam_facelock (8), .BR pam (8), diff --git a/man/pam_facelock.8 b/man/pam_facelock.8 index e0521b7..b677452 100644 --- a/man/pam_facelock.8 +++ b/man/pam_facelock.8 @@ -8,7 +8,7 @@ pam_facelock \- PAM module for face authentication is a PAM module that provides face-based authentication for Linux systems. It is a thin client that first attempts to connect to the .BR facelock (1) -daemon via a Unix domain socket. If the daemon is unavailable, it falls back +daemon via the D-Bus system bus (\fBorg.facelock.Daemon\fR). If the daemon is unavailable, it falls back to spawning .B facelock auth as a one-shot process. @@ -88,11 +88,15 @@ presentation attacks (e.g., holding a photo or video in front of the camera). The daemon enforces rate limiting (5 attempts per user per 60 seconds by default) to prevent brute-force attacks. .SS IPC Security -Communication between the PAM module and daemon uses a Unix domain socket -.RI ( /run/facelock/facelock.sock ). -Peer credentials are verified via -.B SO_PEERCRED -and message sizes are limited to 10MB. +Communication between the PAM module and daemon uses the D-Bus system bus. +The system bus policy +.RI ( /usr/share/dbus-1/system.d/org.facelock.Daemon.conf ) +applies a deny-all default, restricting access to root and members of the +.B facelock +group. The daemon verifies the caller UID via +.B GetConnectionUnixUser +before executing any method. Message size limits are enforced by the D-Bus +daemon. .SS Syslog Logging All authentication attempts (success, failure, and error) are logged to syslog under the AUTH facility with the identifier @@ -126,8 +130,11 @@ Main configuration file. .I /usr/lib/security/pam_facelock.so The PAM module shared library. .TP -.I /run/facelock/facelock.sock -Unix domain socket for daemon communication. +.I /usr/share/dbus-1/system.d/org.facelock.Daemon.conf +D-Bus system bus policy file for the facelock daemon. +.TP +.I /usr/share/dbus-1/system-services/org.facelock.Daemon.service +D-Bus activation file for the facelock daemon service. .TP .I /var/lib/facelock/facelock.db Face embedding database. diff --git a/website/index.html b/website/index.html index 370bc5b..cddd010 100644 --- a/website/index.html +++ b/website/index.html @@ -5,6 +5,11 @@ Facelock - Face Authentication for Linux + + + + + @@ -19,7 +24,8 @@
  • Security
  • Privacy
  • Install
  • -
  • Docs
  • + +
  • Docs
  • GitHub
  • @@ -30,10 +36,10 @@
    Open Source / MIT + Apache 2.0

    Facelock

    -

    Face authentication for Linux. Windows Hello-style facial recognition with IR anti-spoofing, sub-second daemon-mode latency, and complete privacy -- all authentication runs locally on your hardware with no telemetry. After initial model download, Facelock never touches the network.

    +

    Face authentication for Linux. Windows Hello-style facial recognition with IR anti-spoofing, sub-second daemon-mode latency, and complete privacy — all authentication runs locally on your hardware with no telemetry. After initial model download, Facelock never touches the network.

    @@ -76,7 +82,7 @@

    Three-stage recognition pipeline

    - +

    Camera Capture

    V4L2 frame acquisition with auto-detection. Prefers IR cameras for anti-spoofing. CLAHE enhancement for consistent lighting.

    @@ -84,7 +90,7 @@

    Camera Capture

    - +

    Face Detection

    SCRFD neural network locates faces and extracts 5-point landmarks. Affine alignment produces a normalized 112x112 crop.

    @@ -92,7 +98,7 @@

    Face Detection

    - +

    Embedding Match

    ArcFace produces a 512-dim L2-normalized vector. Cosine similarity against stored embeddings determines match or reject.

    @@ -113,7 +119,7 @@

    Built for security and performance

    - +

    IR Anti-Spoofing

    Enforces infrared cameras by default. Phone screens and printed photos lack IR skin texture, blocking the most common attack vector.

    @@ -121,7 +127,7 @@

    IR Anti-Spoofing

    - +

    Sub-Second Auth

    Persistent daemon keeps ONNX models loaded. ~600ms typical, dropping to ~150ms on back-to-back auths when the camera stays warm.

    @@ -129,7 +135,7 @@

    Sub-Second Auth

    - +

    100% Local & Private

    All processing happens on-device via ONNX Runtime. No cloud services, no network requests, no telemetry, no analytics. Your face data never leaves your machine, ever.

    @@ -137,7 +143,7 @@

    100% Local & Private

    - +

    PAM Integration

    Drop-in PAM module for sudo, polkit, and login. Installs as a single pam_facelock.so with one line in your PAM config.

    @@ -145,7 +151,7 @@

    PAM Integration

    - +

    TPM Encryption

    Optional TPM 2.0 support for encrypting face embeddings at rest. Hardware-bound keys ensure biometric data stays protected.

    @@ -153,7 +159,7 @@

    TPM Encryption

    - +

    Daemon + Oneshot

    Choose persistent daemon mode for speed or oneshot mode for simplicity. The CLI auto-detects which mode is available.

    @@ -245,7 +251,7 @@

    Local-Only Processing

    NT

    No Telemetry

    -

    Zero analytics, tracking, or phone-home code. Models are downloaded once during facelock setup -- after that, Facelock never contacts any server.

    +

    Zero analytics, tracking, or phone-home code. Models are downloaded once during facelock setup — after that, Facelock never contacts any server.

    @@ -290,7 +296,7 @@

    Up and running in minutes

    # Build and install the package
    -
    $ cd dist && makepkg -si
    +
    $ just install
     
    # Download face detection models (~170MB)
    $ sudo facelock setup
    @@ -310,7 +316,7 @@

    Up and running in minutes

    -

    Also available as .deb, .rpm, and Nix flake. See docs for details.

    +

    Also available as .deb, .rpm, and Nix flake. See docs for details.

    @@ -407,12 +413,12 @@

    Facelock vs Howdy

    diff --git a/website/style.css b/website/style.css index ceed1d5..8ccafc7 100644 --- a/website/style.css +++ b/website/style.css @@ -689,6 +689,7 @@ section + section { font-size: 1.625rem; } + /* Nav hidden on mobile; hero CTAs and footer links provide navigation */ .nav-links { display: none; } From dc708c90c8d21efa690288be13d4b0661f821af6 Mon Sep 17 00:00:00 2001 From: Ty Smith Date: Sun, 17 May 2026 12:44:42 -0700 Subject: [PATCH 2/2] fix: restore relative docs/*.html website links The previous commit replaced relative website links to docs/*.html with GitHub source-blob URLs after the audit flagged them as broken. They were not broken: .github/workflows/pages.yml builds the mdbook into book/book/ and copies it to _site/docs/ on every push to main, so the relative links resolve correctly on the deployed site at https://tysmith.me/facelock/. Revert nav Docs, hero Get Started, install-note, and the three footer links back to their original docs/*.html relative paths. Remove the stale TODO comment. Co-Authored-By: Claude Opus 4.7 (1M context) --- website/index.html | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/website/index.html b/website/index.html index cddd010..b1f76d5 100644 --- a/website/index.html +++ b/website/index.html @@ -24,8 +24,7 @@
  • Security
  • Privacy
  • Install
  • - -
  • Docs
  • +
  • Docs
  • GitHub
  • @@ -39,7 +38,7 @@

    Facelock

    Face authentication for Linux. Windows Hello-style facial recognition with IR anti-spoofing, sub-second daemon-mode latency, and complete privacy — all authentication runs locally on your hardware with no telemetry. After initial model download, Facelock never touches the network.

    @@ -316,7 +315,7 @@

    Up and running in minutes

    -

    Also available as .deb, .rpm, and Nix flake. See docs for details.

    +

    Also available as .deb, .rpm, and Nix flake. See docs for details.

    @@ -416,9 +415,9 @@

    Facelock vs Howdy