Metadata-Version: 2.4
Name: vault-touchid-gate
Version: 0.1.0
Summary: Touch ID gate for rbw/bw (Bitwarden CLI) — remote client for SSH hosts
Keywords: bitwarden,rbw,touchid,ssh,vault,security
Author: Tobias Hochgürtel
Author-email: Tobias Hochgürtel <tobias.hochguertel@googlemail.com>
License-Expression: MIT
Classifier: Development Status :: 4 - Beta
Classifier: Environment :: Console
Classifier: Intended Audience :: Developers
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Security
Requires-Dist: ruamel-yaml>=0.18
Requires-Python: >=3.11
Description-Content-Type: text/markdown

# vault-touchid-gate

Gate `rbw` and `bw` (Bitwarden CLI) behind macOS Touch ID — locally and over SSH. No sudo, no env-var juggling, auto-unlock from Keychain.

## Why

The official `bw` CLI requires you to manually lock/unlock and pass session keys via environment variables. `rbw` solves this with a background agent (like `ssh-agent`), but still requires typing your master password to unlock.

This project adds a Touch ID gate on top of `rbw`/`bw` so that:

- **Every vault access triggers a Touch ID prompt** — you approve per-action, biometrics only.
- **No master password typing** — it's stored in the macOS Keychain (encrypted, Touch ID-gated) and auto-fed to `rbw unlock` via `expect`.
- **No sudo, no root, no sudoers changes** — Touch ID is triggered via Apple's `LocalAuthentication` framework directly.
- **No env-var juggling** — the `rbw-agent` holds keys in memory; the wrapper just gates access to it.
- **Works over SSH** — a daemon on your Mac + SSH reverse tunnel lets you use `vault` on remote servers. Touch ID fires on your Mac, the result tunnels back. Works through jump hosts.

## Architecture

### Local (on the Mac)

```
vault rbw get "GitHub"
  │
  ├─→ touchid-gate (Swift / LocalAuthentication)
  │     🔑 Touch ID requested — waiting for fingerprint...
  │     ✓ Touch ID approved
  │
  ├─→ (if vault locked) retrieve master password from Keychain
  │                     → expect feeds it to `rbw unlock`
  │
  └─→ exec rbw get "GitHub"
```

### Remote (over SSH, including jump hosts)

```
[Remote server]                    [MacBook]
vault rbw get "GitHub"
  │
  ├─→ vault-agent-client
  │     connects to localhost:8423
  │         │
  │     SSH reverse tunnel ────────→ vault-agent (daemon, 127.0.0.1:8423)
  │                                      │
  │                                 touchid-gate (Touch ID prompt on Mac)
  │                                      │ approved
  │                                 rbw get "GitHub"
  │                                      │
  │     ←──── JSON response ─────────────┘
  │
  └─→ prints stdout/stderr, exits with code
```

The SSH reverse tunnel chains through jump hosts automatically:

```
MacBook ──SSH──→ srv-fedora ──SSH──→ vps9
  │                 │                  │
  vault-agent       tunnel             tunnel
  (127.0.0.1:8423)  (localhost:8423)   (localhost:8423)
                     → Mac:8423         → srv-fedora:8423 → Mac:8423
```

| Component | Description |
|---|---|
| `touchid-gate.swift` | Swift program using `LocalAuthentication` to trigger Touch ID. Compiled to a fast binary. Biometrics only (no password fallback). |
| `vault` | Bash wrapper. Detects local vs remote mode. Local: Touch ID gate + Keychain auto-unlock. Remote: connects to vault-agent via tunnel. |
| `vault-setup` | One-time setup. Stores Bitwarden master password in macOS Keychain. |
| `vault-agent` | Python daemon (Mac). Listens on `127.0.0.1:8423`. For each request: Touch ID → run rbw/bw → return result as JSON. |
| `vault-agent-client` | Python client (remote). Connects to `localhost:8423` via SSH tunnel, sends request, prints result. |

## Requirements

### Mac (local mode)

- macOS with Touch ID (Apple Silicon or Touch ID-enabled Intel Mac)
- Xcode Command Line Tools (`swiftc`)
- [`rbw`](https://github.com/doy/rbw) (`brew install rbw`)
- `expect` (pre-installed on macOS or `brew install expect`)
- `pinentry-tty` (installed with `rbw` via Homebrew)
- Python 3 (pre-installed on macOS)
- [`bw`](https://bitwarden.com/help/cli/) (optional, only for `vault bw ...`)

### Remote servers (remote mode)

- Python 3
- SSH access from the Mac (with reverse tunnel configured)

## Installation

### On the Mac

```bash
git clone <repo-url>
cd vault-touchid-gate
./install.sh
```

This compiles `touchid-gate.swift` and installs all scripts to `~/.local/bin/`.

### On remote servers

```bash
# Clone the repo on the remote server
git clone <repo-url>
cd vault-touchid-gate
./install.sh --remote
```

This installs only `vault` and `vault-agent-client` (no Swift, no touchid-gate needed).

## Configuration

### 1. Configure rbw (on the Mac)

```bash
rbw config set base_url "https://your-bitwarden-server.com"
rbw config set email "you@example.com"
rbw config set pinentry "pinentry-tty"
rbw config set lock_timeout 300   # auto-lock after 5 min (default: 3600)
```

### 2. Register the device (official Bitwarden server only)

```bash
rbw register
```

Self-hosted servers may not require this.

### 3. Store your master password in Keychain

```bash
vault-setup
```

### 4. Start the vault-agent daemon (on the Mac)

```bash
# Foreground (for testing):
vault-agent

# Background:
vault-agent &

# Or auto-start on login via launchd:
mkdir -p ~/Library/LaunchAgents
# Edit launchd/com.hochguertel.vault-agent.plist to use your home path
cp launchd/com.hochguertel.vault-agent.plist ~/Library/LaunchAgents/
launchctl load ~/Library/LaunchAgents/com.hochguertel.vault-agent.plist
```

### 5. Test locally

```bash
vault rbw list
```

## Remote SSH Access

### SSH config setup

Add `RemoteForward` to your `~/.ssh/config` on the Mac:

```
# Direct connection to a server
Host srv-fedora
    HostName srv-fedora.example.com
    User youruser
    RemoteForward 8423 127.0.0.1:8423
    ExitOnForwardFailure yes

# Jump host: Mac → srv-fedora → vps9
Host vps9
    HostName vps9.internal
    User youruser
    ProxyJump srv-fedora
    RemoteForward 8423 127.0.0.1:8423
    ExitOnForwardFailure yes
```

With `ProxyJump`, the reverse tunnel chains automatically through the jump host. `ExitOnForwardFailure yes` ensures the SSH connection fails if the tunnel can't be set up (rather than silently continuing without vault access).

### Manual chaining (without ProxyJump)

If you SSH manually through hops (Mac → srv-fedora → vps9), add `RemoteForward` at each hop:

```bash
# On Mac:
ssh -R 8423:127.0.0.1:8423 srv-fedora

# On srv-fedora:
ssh -R 8423:127.0.0.1:8423 vps9
```

This chains the tunnel: vps9:8423 → srv-fedora:8423 → Mac:8423.

### Remote installation (Python package)

The remote components (`vault` + `vault-agent-client`) are published to the
private devpi registry as the `vault-touchid-gate` package. Install them on a
remote host with:

```bash
uv tool install --extra-index-url https://pypi.registry.hochguertel.work/root/private/+simple/ vault-touchid-gate
```

No clone, no build, no `mise.toml` on the host — just Python 3.12+. The
package depends on `ruamel.yaml` (resolved from PyPI; devpi serves only the
package itself, so always install with `--extra-index-url` as above). The
`vault` entry point is remote-mode only: if it detects `touchid-gate` (a
Mac), it refuses to run so it can never shadow the local-mode bash wrapper
from `install.sh`.

Update an existing install:

```bash
uv tool upgrade --extra-index-url https://pypi.registry.hochguertel.work/root/private/+simple/ vault-touchid-gate
```

### Test from a remote server

```bash
# On the remote server (after SSHing in with the tunnel):
vault rbw list
```

Touch ID fires on your Mac. You approve. The result appears on the remote.

### How it works

1. `vault-agent` daemon runs on your Mac, listening on `127.0.0.1:8423`
2. You SSH to a remote server with `RemoteForward 8423 127.0.0.1:8423`
3. On the remote, `localhost:8423` is tunneled back to your Mac's `8423`
4. `vault` on the remote detects it's not on a Mac (no `touchid-gate`) → uses `vault-agent-client`
5. `vault-agent-client` connects to `localhost:8423` → tunnel → `vault-agent` on Mac
6. `vault-agent` triggers Touch ID on your Mac
7. You approve → `vault-agent` runs `rbw`/`bw` → returns result as JSON
8. `vault-agent-client` prints the result on the remote

### Environment variables (remote)

| Variable | Default | Description |
|---|---|---|
| `VAULT_AGENT_HOST` | `127.0.0.1` | Host to connect to (use with non-standard tunnel setups) |
| `VAULT_AGENT_PORT` | `8423` | Port to connect to |
| `VAULT_AGENT_TIMEOUT` | `120` | Timeout in seconds (waiting for Touch ID) |

## Usage

```bash
# List all entries (local or remote — same command)
vault rbw list

# Get a password
vault rbw get "GitHub"

# Get all fields (username, password, notes, custom fields)
vault rbw get --full "GitHub"

# Get a TOTP code
vault rbw code "GitHub"

# Sync from server
vault rbw sync

# Use the official bw CLI (also gated behind Touch ID)
vault bw sync

# Manually lock the vault (on the Mac)
rbw lock

# Start/stop the daemon (on the Mac)
vault-agent &           # start
pkill -f vault-agent    # stop
```

## Security Model

### What this does

- **Per-action Touch ID approval.** Every `vault` call triggers a fresh Touch ID prompt. No time window, no standing access.
- **Biometrics only.** The Touch ID gate has no password fallback (`localizedFallbackTitle = ""`).
- **No sudo.** Touch ID is triggered via `LocalAuthentication`, not via `sudo`/PAM. No host-admin privileges are involved.
- **Auto-lock.** The `rbw-agent` purges keys from memory after `lock_timeout` seconds (default 300 = 5 min).
- **Localhost-only daemon.** `vault-agent` binds to `127.0.0.1` — not accessible from the network. Remote access is only via SSH tunnel.
- **No secrets on remote servers.** The remote `vault-agent-client` sends requests but never stores passwords, tokens, or keys. All secrets stay on the Mac.

### What this does NOT do

- **Per-item granularity.** Touch ID approval grants access to the entire vault for that one command.
- **Audit logging.** There's no log of which entries were read, when, or by whom.
- **Protect the Keychain entry.** The master password is stored in the macOS Keychain. If your Mac login password is compromised, an attacker could access the Keychain.
- **Encrypt the tunnel protocol.** The JSON protocol is plaintext over the SSH tunnel. This is fine because SSH itself is encrypted, but if you expose port 8423 directly (without SSH), the traffic would be unencrypted.

### Removing the master password from Keychain

```bash
security delete-generic-password -s "rbw-master-password" -a "$(id -un)"
```

## Files

```
vault-touchid-gate/
├── bin/
│   ├── touchid-gate.swift     # Swift source — LocalAuthentication Touch ID gate
│   ├── vault                  # Bash wrapper — detects local/remote, gates access
│   ├── vault-setup            # One-time setup — stores master password in Keychain
│   ├── vault-agent            # Python daemon (Mac) — Touch ID + rbw over TCP
│   └── vault-agent-client     # Python client (remote) — connects to agent via tunnel
├── launchd/
│   └── com.hochguertel.vault-agent.plist  # macOS auto-start for the daemon
├── docs/
│   └── ARCHITECTURE.md        # Design decisions and trust model
├── install.sh                 # Installation script (auto-detects Mac vs remote)
├── LICENSE                    # MIT
└── README.md
```

## License

MIT
