WikifitaGitHub live67e8de5
outro · co-fita/co-fita-pam-setup

PAM macOS Setup -- Touch ID for sudo

Automated setup for Touch ID authentication on macOS sudo: integrity-verified compilation of pam-reattach, idempotent PAM deployment, and Homebrew distribution via private tap.

Baixar raw

PAM macOS Setup -- Touch ID for sudo

A hardened, automated setup for enabling Touch ID authentication on macOS sudo commands. The tool compiles pam-reattach from source with cryptographic integrity verification, deploys it idempotently to /etc/pam.d/sudo_local, and distributes the result via a private Homebrew tap for easy deployment across machines.

The central problem: macOS's native pam_tid.so works for Terminal.app but fails inside VS Code, Cursor, tmux, and other non-native sessions. pam-reattach re-attaches to the Touch ID session, making biometric sudo work everywhere.

See also: co-fita-infrastructure | co-fita-research-operations


1. The Problem

macOS ships with pam_tid.so for Touch ID sudo authentication. However, this module only works in Terminal.app sessions that have direct access to the Biometric framework. In practice, this means:

  • VS Code / Cursor integrated terminals -- no Touch ID
  • tmux sessions -- no Touch ID
  • SSH sessions with agent forwarding -- no Touch ID
  • Any non-native terminal emulator -- no Touch ID

The pam-reattach library (by fabianishere) solves this by re-attaching to the macOS Biometric session from any PAM context. The challenge is deploying it correctly: the library must be compiled for the host architecture, placed in the right path, and configured in /etc/pam.d/ without breaking the system.


2. Architecture

The setup has three layers:

setup.sh          ← Interactive entry point (AppleScript UI/UX)
    │
    ▼
pam_setup.py      ← Autonomous installer (PEP 723, uv run)
    │
    ├── check_integrity()    ← SHA256 verification against official release
    ├── build_pam_reattach() ← Universal binary compilation (arm64 + x86_64)
    └── deploy_and_configure() ← Idempotent PAM deployment + cleanup

setup.sh -- Interactive Entry Point

A bash script that provides native macOS UI via osascript dialogs:

  1. Introduction dialog -- explains what the tool does, warns about admin privileges
  2. Dependency check -- verifies uv is installed; offers to install it via astral.sh if missing
  3. Privilege escalation -- detects Touch ID availability via bioutil -r; either prompts for biometric auth or falls back to an AppleScript password dialog
  4. Delegates to pam_setup.py -- runs the actual installer via sudo env "PATH=$PATH" pam_setup.py
  5. Success dialog -- confirms Touch ID is now active

The script respects user choice: if the user prefers to run directly in Terminal (avoiding GUI prompts), they can use sudo ./setup.sh instead.

pam_setup.py -- Autonomous Installer

A PEP 723 Python script (run via uv run --script) that handles the full installation lifecycle.

Phase 1 -- Integrity Verification:

EXPECTED_VERSION = "1.3"
EXPECTED_SHA256 = "b1b735fa7832350a23457f7d36feb6ec939e5e1de987b456b6c28f5738216570"
TARBALL_URL = f"https://github.com/fabianishere/pam_reattach/archive/refs/tags/v{EXPECTED_VERSION}.tar.gz"

The script downloads the official release tarball, computes its SHA256, validates against the hash published by the Homebrew Registry, then performs a file-by-file comparison against the locally cloned source in repos/pam_reattach/. This catches both upstream tampering and local modification.

Phase 2 -- Compilation:

clang_cmd = [
    "clang",
    "-arch", "arm64", "-arch", "x86_64",
    "-shared", "-fPIC", "-undefined", "dynamic_lookup",
    f"-I{include_path}",
    "-o", final_out,
    src_pam, src_reattach, "-lpam"
]

Compiles a universal binary (arm64 + x86_64) using Apple Clang. No CMake required -- direct Clang invocation for simplicity and reproducibility.

Phase 3 -- Deployment:

The deployment is idempotent (safe to run multiple times):

  1. Creates /usr/local/lib/pam/ if it doesn't exist
  2. Copies pam_reattach.so to /usr/local/lib/pam/pam_reattach.so
  3. Creates /etc/pam.d/sudo_local with the correct PAM configuration:
# sudo_local: auth account password session
auth       optional       /usr/local/lib/pam/pam_reattach.so
auth       sufficient     pam_tid.so
  1. Sets ownership to root:wheel with permissions 0644
  2. Autocleanup: Audits /etc/pam.d/sudo and removes any duplicate pam_tid.so lines left by previous manual configurations

Why sudo_local instead of modifying sudo directly:

macOS's System Integrity Protection (SIP) protects /etc/pam.d/sudo. The sudo_local file is the Apple-sanctioned extension point -- it is read after sudo and survives macOS updates. This is the correct deployment target.


3. PAM Configuration Details

The two lines that matter

auth  optional   /usr/local/lib/pam/pam_reattach.so
auth  sufficient pam_tid.so

Line 1 (pam_reattach.so, optional):

  • Re-attaches to the macOS Biometric session
  • optional means failure doesn't block authentication -- falls back to password
  • This is the key enabler for non-native terminals

Line 2 (pam_tid.so, sufficient):

  • The native macOS Touch ID module
  • sufficient means if Touch ID succeeds, authentication is complete (no password needed)
  • If Touch ID fails (e.g., on a Mac without Touch ID), PAM continues to the next module

The cleanup step

The installer also cleans /etc/pam.d/sudo of any pam_tid.so lines that were added by previous manual setups. This prevents conflicts where Touch ID would be checked twice or in the wrong order.


4. Security Properties

PropertyImplementation
Source integritySHA256 verification against official release tarball + file-by-file comparison
No network dependencies at runtimeAll downloads happen during installation; the compiled library is self-contained
Idempotent deploymentRunning the script multiple times produces the same result
AutocleanupRemoves duplicate PAM entries from previous configurations
Privilege boundarypam_setup.py only runs with sudo; password is handled via sudo -v (biometric) or sudo -S (cached), never stored
No password persistenceThe osascript password prompt is a one-time fallback; the password is piped directly to sudo -S and immediately discarded
Universal binaryCompiled for both arm64 (Apple Silicon) and x86_64 (Intel)

Threat model

This tool modifies PAM configuration, which is a sensitive system component. The threat model assumes:

  • The user running the script is the machine owner
  • The official pam-reattach release v1.3 is trustworthy (verified via hash)
  • The Homebrew Registry hash is authoritative (pinned in the script)

5. Homebrew Distribution

The pam-mac-setup tool is distributed via a private Homebrew tap at aleffita/homebrew-private.

The Formula

class PamMacSetup < Formula
  desc "macOS Sudo Touch ID Configuration Script"
  homepage "https://github.com/aleffita/pam-mac-setup"
  url "https://github.com/aleffita/pam-mac-setup.git", using: :git, tag: "v1.0.0"
  version "1.0.0"
  license "MIT"

  def install
    libexec.install "setup.sh", "pam_setup.py"
    (bin/"pam-mac-setup").write <<~EOS
      #!/bin/bash
      exec "#{libexec}/setup.sh" "$@"
    EOS
  end
end

Installation on new machines

Option 1 -- PAT token (HTTPS):

brew tap aleffita/private https://github_pat_...@github.com/aleffita/homebrew-private.git
brew install pam-mac-setup
pam-mac-setup

Option 2 -- SSH (cleaner):

brew tap aleffita/private git@github.com:aleffita/homebrew-private.git
brew install pam-mac-setup
pam-mac-setup

The formula is minimal: it installs setup.sh and pam_setup.py into libexec/ and creates a pam-mac-setup wrapper in the Homebrew bin/. The wrapper delegates to setup.sh, which handles the interactive flow.


6. Requirements

  • macOS Sonoma or later
  • Python 3.11+ (managed via uv)
  • uv package manager (auto-installed if missing)
  • Apple Command Line Tools (for Clang compilation)
  • Touch ID hardware (for biometric auth; password fallback available)

7. Repository Structure

pam-mac-setup/
├── README.md         # Documentation (bilingual PT/EN)
├── setup.sh          # Interactive entry point (AppleScript UI)
├── pam_setup.py      # Autonomous installer (PEP 723)
└── repos/
    └── pam_reattach/ # Source code (v1.3, verified, no .git metadata)

The repos/ directory contains the pam-reattach source without git metadata. This is intentional: the integrity verification compares the local source against the official tarball, and .git directories would cause false mismatches.


8. Connection to ClickFix

The ClickFix attack used a fake PAM dialog (Stage 2) to steal the user's password. The dialog used osascript display dialog ... with hidden answer to impersonate a "System Preferences" prompt. This is a social engineering attack that bypasses the real PAM entirely.

The PAM setup tool addresses a different problem (enabling Touch ID for sudo in non-native terminals), but the connection is meaningful: the more commonly Touch ID is used for sudo, the less frequently users type passwords into terminal prompts, and the fewer opportunities attackers have to capture passwords via fake dialogs.

A user who exclusively uses Touch ID for sudo never types their password into a terminal -- making the ClickFix Stage 2 password theft impossible even if the malware reaches that stage.


9. Design Decisions

  1. PEP 723 for the Python script -- uv run --script means zero setup; the user just needs uv installed. Dependencies are declared inline in the script header.

  2. AppleScript for UI -- native macOS dialogs feel trustworthy and integrate with the system. The password prompt uses with hidden answer (the same primitive the malware abused, but for a legitimate purpose here).

  3. Integrity verification before compilation -- the script downloads the official tarball and compares it against the local source before compiling. This catches supply chain attacks at the source level.

  4. Universal binary -- compiling for both arm64 and x86_64 means the same deployment works on both Apple Silicon and Intel Macs.

  5. sudo_local over sudo -- Apple designed sudo_local as the extension point. Modifying sudo directly would be overwritten by macOS updates and could break SIP.

  6. Autocleanup of old configurations -- users who previously added pam_tid.so to /etc/pam.d/sudo manually would have conflicts. The installer detects and removes these automatically.