Experimental, fail-closed Touch ID authentication for Intel Macs with an Apple
T2 chip. It talks to bridgeOS BiometricKit over BridgeXPC and exposes a minimal
fprintd-compatible D-Bus service for PAM clients.
This is research software, not an upstream libfprint driver. Enrollment and
deletion remain in macOS. The Linux side only verifies an identity already
enrolled for the configured macOS user ID.
See ROADMAP.md for the evidence-based reliability checklist.
The separate enrollment research publishes the
current protocol findings and deferred evidence-collection helpers. It does not
enable enrollment or deletion in the shipped service.
See the redacted conversation that produced this here: https://gist.github.com/jmurth1234/4a138019fd832dfabbed26475613db3a
Developed and verified on an Intel MacBookPro16,2, bridgeOS build 23P1072,
BridgeXPC 39, and Omarchy/Arch Linux. A positive right-index control and a
negative unenrolled-finger control were both verified at the raw bridge,
fprintd, and sudo/PAM layers.
*.kb, *.cat, exported archives, captures, Apple binaries,
device identifiers, or match-result payloads.This was reproduced with the t2bce stack on the proven configuration. The
kernel reported repeated NETDEV WATCHDOG transmit timeouts for the T2's
cdc_ncm interface after resuming from deep sleep. The systemd transport,
keybag, and fprintd services could still appear active because their existing
process state did not reflect loss of communication with bridgeOS.
Rebinding the cdc_ncm interface and deauthorizing/reauthorizing its virtual
USB device both recreated the interface but did not restore RemoteXPC. Do not
unload t2_sep_transport as a recovery attempt: its SEP-registered DMA memory
is deliberately pinned until reboot. The known recovery is:
fprintd.service if it is not already running.Treat suspend as unsupported until the underlying T2 BCE resume path is fixed or an alternative sleep mode has been validated on the specific Mac model.
src/t2_sep_transport.c: SEP endpoint-7 DMA/keybag transport.src/t2-aks-tool.c: narrowly allow-listed AppleKeyStore operations.src/discover-biometric-port.py: privacy-preserving RemoteXPC discovery.src/bridge-xpc-probe.py: BridgeXPC command and match implementation.src/t2-fprintd.py: verification-only fprintd facade.systemd/: system and audible-feedback units.pam/: clamshell-safe Omarchy PAM templates.tools/macos/: private export helpers; outputs must never be committed.enrollment_research/: sanitized enrollment, multi-user, Catacomb, and
rollback findings plus non-mutating/deferred collection helpers.tests/: hardware-free fail-closed lifecycle tests.Keep macOS available and enroll exactly the finger you intend to use.
Run the export helpers from macOS and transfer outputs privately.
On Linux, identify the T2 USB-network interface and link-local IPv6 address,
then run sudo ./install.sh. Edit /etc/t2-touchid.conf when prompted,
including the numeric macOS user ID and its corresponding special bag.
Start t2-sep-transport.service. The installer builds the module for the
running kernel and installs it with register_ool=1. Do not unload it;
reboot before rebuilding or replacing it. Re-run the installer after a
kernel upgrade.
Place the extracted keybag at /var/lib/t2-touchid/user.kb, owned by root
and mode 0600, then start t2-keybag-load.service.
Unlock the loaded normal handle and special user bag with the macOS password:
sudo /usr/local/sbin/t2-aks-tool unlock-keybag 1 HANDLE
sudo /usr/local/sbin/t2-aks-tool unlock-keybag 1 SPECIAL_BAG
Start fprintd.service. Run fprintd-verify once with the enrolled finger
and once with an unenrolled finger. Require verify-match and
verify-no-match, respectively.
Only after those controls pass, install the relevant files from pam/ into
/etc/pam.d/ with sudo tools/install-pam.sh. Keep password authentication
as fallback; sudo tools/rollback-pam.sh restores the originals.
The installer is safe to rerun and replaces only project-managed files. When
DKMS is available it registers the transport for kernel upgrades; otherwise it
warns that the installer must be rerun after an upgrade. sudo ./uninstall.sh
removes code and services but preserves configuration, credentials, keybags,
and PAM backups. Add --purge-private-data only when those secrets should be
permanently removed; PAM restoration remains an explicit operation.
If the macOS and Linux login passwords are identical, PAM can pass the password
already entered by the user to the keybag unlock helper. The password is kept
only in process memory and is not placed in argv, the environment, logs, or
persistent storage. The helper reads the boot-specific handle recorded under
/run by t2-keybag-load.service and always exits successfully so a T2 failure
cannot block password authentication.
After making a root-owned backup, add this at the end of the auth section in
/etc/pam.d/system-auth, after the successful pam_faillock.so authsucc line:
auth optional pam_exec.so quiet expose_authtok seteuid /usr/local/sbin/t2-pam-unlock
Omarchy uses SDDM autologin followed by a separate lock-screen PAM service, so
the initial desktop password does not traverse system-auth. On Omarchy, also
install pam/omarchy-lock-password as /etc/pam.d/omarchy-lock-password
after backing up the existing file. That template contains the same optional
hook after its successful pam_faillock.so authsucc line.
This unlocks the bags on the first successful password authentication through
an instrumented PAM service after boot. It cannot unlock them before a password
has been entered. The helper restricts itself to T2_TOUCHID_USER from
/etc/t2-touchid.conf.
For unattended keybag availability after SDDM autologin, provision an encrypted systemd credential:
sudo tools/provision-credential.sh
sudo systemctl enable t2-credential-unlock.service
The provisioning prompt is local and hidden. The plaintext password is piped
directly into systemd-creds; it is not placed in argv, the environment, or a
persistent plaintext file. At boot, systemd decrypts it into a protected,
service-scoped runtime credential, the one-shot helper unlocks both keybags,
and fprintd starts only after that attempt.
With an unattended credential present, t2-biometric-ready.service also waits
for the T2 network path, discovers the dynamic RemoteXPC port, and performs a
non-matching initialization/calibration/identity-list warm-up before fprintd
starts. This avoids exposing the first Omarchy lock-screen scan to the cold
BiometricKit startup race observed on the proven configuration. Its verified
dynamic port is cached root-only under /var/lib/t2-touchid; fprintd consumes
that cache and does not request a finger until discovery has completed. Cold
boot authentication has been verified with sudo, including after installing
the current configurable-identity and endpoint-recovery changes. Touch ID
unlock through an explicit omarchy system lock has also been verified on the proven
configuration, including wrong-finger rejection and password fallback; other
shell/login configurations may use a different PAM path.
Run the privacy-safe health report as root so it can inspect root-only runtime state and the encrypted credential metadata:
sudo t2-touchid-doctor
sudo t2-touchid-doctor --json
The report never prints configured addresses, usernames, ports, keybag handles, credential contents, identity UUIDs, or biometric payloads.
This machine has no usable TPM, so the credential is encrypted with systemd's host key. It protects against casual/offline disclosure without the decrypted Linux filesystem, but root can decrypt it. Since the credential is also the Linux and macOS login password on the proven configuration, understand this tradeoff before provisioning it.
Exact keybag extraction and hardware bring-up remain machine-sensitive. Read
src/README.md before loading the module.
python -m venv .venv
.venv/bin/pip install -r requirements.txt
.venv/bin/python -m unittest discover -s tests -v
python -m py_compile src/*.py
tools/privacy-check.sh
This project is licensed under the GNU General Public License version 2 only
(GPL-2.0-only). See LICENSE. The userspace-facing transport
header retains the standard Linux syscall-note exception.
22 commits
Hacker News (1)
Python
54.1%
Shell
23.1%
C
22.5%
Experimental, fail-closed Touch ID authentication for Intel Macs with an Apple
T2 chip. It talks to bridgeOS BiometricKit over BridgeXPC and exposes a minimal
fprintd-compatible D-Bus service for PAM clients.
This is research software, not an upstream libfprint driver. Enrollment and
deletion remain in macOS. The Linux side only verifies an identity already
enrolled for the configured macOS user ID.
See ROADMAP.md for the evidence-based reliability checklist.
The separate enrollment research publishes the
current protocol findings and deferred evidence-collection helpers. It does not
enable enrollment or deletion in the shipped service.
See the redacted conversation that produced this here: https://gist.github.com/jmurth1234/4a138019fd832dfabbed26475613db3a
Developed and verified on an Intel MacBookPro16,2, bridgeOS build 23P1072,
BridgeXPC 39, and Omarchy/Arch Linux. A positive right-index control and a
negative unenrolled-finger control were both verified at the raw bridge,
fprintd, and sudo/PAM layers.
*.kb, *.cat, exported archives, captures, Apple binaries,
device identifiers, or match-result payloads.This was reproduced with the t2bce stack on the proven configuration. The
kernel reported repeated NETDEV WATCHDOG transmit timeouts for the T2's
cdc_ncm interface after resuming from deep sleep. The systemd transport,
keybag, and fprintd services could still appear active because their existing
process state did not reflect loss of communication with bridgeOS.
Rebinding the cdc_ncm interface and deauthorizing/reauthorizing its virtual
USB device both recreated the interface but did not restore RemoteXPC. Do not
unload t2_sep_transport as a recovery attempt: its SEP-registered DMA memory
is deliberately pinned until reboot. The known recovery is:
fprintd.service if it is not already running.Treat suspend as unsupported until the underlying T2 BCE resume path is fixed or an alternative sleep mode has been validated on the specific Mac model.
src/t2_sep_transport.c: SEP endpoint-7 DMA/keybag transport.src/t2-aks-tool.c: narrowly allow-listed AppleKeyStore operations.src/discover-biometric-port.py: privacy-preserving RemoteXPC discovery.src/bridge-xpc-probe.py: BridgeXPC command and match implementation.src/t2-fprintd.py: verification-only fprintd facade.systemd/: system and audible-feedback units.pam/: clamshell-safe Omarchy PAM templates.tools/macos/: private export helpers; outputs must never be committed.enrollment_research/: sanitized enrollment, multi-user, Catacomb, and
rollback findings plus non-mutating/deferred collection helpers.tests/: hardware-free fail-closed lifecycle tests.Keep macOS available and enroll exactly the finger you intend to use.
Run the export helpers from macOS and transfer outputs privately.
On Linux, identify the T2 USB-network interface and link-local IPv6 address,
then run sudo ./install.sh. Edit /etc/t2-touchid.conf when prompted,
including the numeric macOS user ID and its corresponding special bag.
Start t2-sep-transport.service. The installer builds the module for the
running kernel and installs it with register_ool=1. Do not unload it;
reboot before rebuilding or replacing it. Re-run the installer after a
kernel upgrade.
Place the extracted keybag at /var/lib/t2-touchid/user.kb, owned by root
and mode 0600, then start t2-keybag-load.service.
Unlock the loaded normal handle and special user bag with the macOS password:
sudo /usr/local/sbin/t2-aks-tool unlock-keybag 1 HANDLE
sudo /usr/local/sbin/t2-aks-tool unlock-keybag 1 SPECIAL_BAG
Start fprintd.service. Run fprintd-verify once with the enrolled finger
and once with an unenrolled finger. Require verify-match and
verify-no-match, respectively.
Only after those controls pass, install the relevant files from pam/ into
/etc/pam.d/ with sudo tools/install-pam.sh. Keep password authentication
as fallback; sudo tools/rollback-pam.sh restores the originals.
The installer is safe to rerun and replaces only project-managed files. When
DKMS is available it registers the transport for kernel upgrades; otherwise it
warns that the installer must be rerun after an upgrade. sudo ./uninstall.sh
removes code and services but preserves configuration, credentials, keybags,
and PAM backups. Add --purge-private-data only when those secrets should be
permanently removed; PAM restoration remains an explicit operation.
If the macOS and Linux login passwords are identical, PAM can pass the password
already entered by the user to the keybag unlock helper. The password is kept
only in process memory and is not placed in argv, the environment, logs, or
persistent storage. The helper reads the boot-specific handle recorded under
/run by t2-keybag-load.service and always exits successfully so a T2 failure
cannot block password authentication.
After making a root-owned backup, add this at the end of the auth section in
/etc/pam.d/system-auth, after the successful pam_faillock.so authsucc line:
auth optional pam_exec.so quiet expose_authtok seteuid /usr/local/sbin/t2-pam-unlock
Omarchy uses SDDM autologin followed by a separate lock-screen PAM service, so
the initial desktop password does not traverse system-auth. On Omarchy, also
install pam/omarchy-lock-password as /etc/pam.d/omarchy-lock-password
after backing up the existing file. That template contains the same optional
hook after its successful pam_faillock.so authsucc line.
This unlocks the bags on the first successful password authentication through
an instrumented PAM service after boot. It cannot unlock them before a password
has been entered. The helper restricts itself to T2_TOUCHID_USER from
/etc/t2-touchid.conf.
For unattended keybag availability after SDDM autologin, provision an encrypted systemd credential:
sudo tools/provision-credential.sh
sudo systemctl enable t2-credential-unlock.service
The provisioning prompt is local and hidden. The plaintext password is piped
directly into systemd-creds; it is not placed in argv, the environment, or a
persistent plaintext file. At boot, systemd decrypts it into a protected,
service-scoped runtime credential, the one-shot helper unlocks both keybags,
and fprintd starts only after that attempt.
With an unattended credential present, t2-biometric-ready.service also waits
for the T2 network path, discovers the dynamic RemoteXPC port, and performs a
non-matching initialization/calibration/identity-list warm-up before fprintd
starts. This avoids exposing the first Omarchy lock-screen scan to the cold
BiometricKit startup race observed on the proven configuration. Its verified
dynamic port is cached root-only under /var/lib/t2-touchid; fprintd consumes
that cache and does not request a finger until discovery has completed. Cold
boot authentication has been verified with sudo, including after installing
the current configurable-identity and endpoint-recovery changes. Touch ID
unlock through an explicit omarchy system lock has also been verified on the proven
configuration, including wrong-finger rejection and password fallback; other
shell/login configurations may use a different PAM path.
Run the privacy-safe health report as root so it can inspect root-only runtime state and the encrypted credential metadata:
sudo t2-touchid-doctor
sudo t2-touchid-doctor --json
The report never prints configured addresses, usernames, ports, keybag handles, credential contents, identity UUIDs, or biometric payloads.
This machine has no usable TPM, so the credential is encrypted with systemd's host key. It protects against casual/offline disclosure without the decrypted Linux filesystem, but root can decrypt it. Since the credential is also the Linux and macOS login password on the proven configuration, understand this tradeoff before provisioning it.
Exact keybag extraction and hardware bring-up remain machine-sensitive. Read
src/README.md before loading the module.
python -m venv .venv
.venv/bin/pip install -r requirements.txt
.venv/bin/python -m unittest discover -s tests -v
python -m py_compile src/*.py
tools/privacy-check.sh
This project is licensed under the GNU General Public License version 2 only
(GPL-2.0-only). See LICENSE. The userspace-facing transport
header retains the standard Linux syscall-note exception.
Hacker News (1)
22 commits
Python
54.1%
Shell
23.1%
C
22.5%