|
1 | | -# fssh — Touch ID–Protected SSH Agent and CLI |
| 1 | +# fssh |
| 2 | + |
| 3 | + |
| 4 | + |
| 5 | +A solution for macOS that eliminates the need to manually unlock private keys with passphrases every time you use public key authentication, and helps you quickly view and connect to SSH hosts configured in `~/.ssh/config` without having to open the config file each time. |
| 6 | + |
| 7 | +When SSH connects to a remote host, it prompts for fingerprint authentication, which decrypts and imports the private key upon successful verification. Additionally, fssh provides an interactive shell where you can quickly view and connect to hosts defined in `~/.ssh/config`. |
2 | 8 |
|
3 | 9 | ## Use Cases |
4 | | -- Plaintext SSH keys on disk are risky and easy to exfiltrate |
5 | | -- Encrypted keys require entering passphrases for every SSH login, which is inconvenient |
6 | | -- With many hosts in `~/.ssh/config`, aliases are easy to forget; you often need to open the file before every connection |
7 | | - |
8 | | -## Solution (fssh — finger‑ssh) |
9 | | -- On macOS, unlock a Touch ID–protected master key to decrypt local encrypted SSH keys for authentication |
10 | | -- Provide an OpenSSH‑compatible ssh‑agent |
11 | | -- An interactive shell that parses hosts from `~/.ssh/config`, supports search and direct connect |
12 | | - |
13 | | -## Overview |
14 | | -- Securely store and use SSH private keys on macOS with Touch ID (or equivalent local authentication) |
15 | | -- Provide an SSH agent that can operate in two modes: |
16 | | - - Secure per‑sign unlock: prompts for Touch ID on each signature (or within a configurable TTL window) |
17 | | - - Convenience preload: decrypts keys once and keeps them in memory for subsequent signatures |
18 | | -- Interactive shell to discover hosts from `~/.ssh/config` and connect fast with tab completion |
19 | | -- macOS login auto‑start via `launchd` and a generic LaunchAgent plist |
20 | | - |
21 | | -## Key Features |
22 | | -- Touch ID‑protected master key, stored in macOS Keychain |
23 | | -- Encrypted key store at `~/.fssh/keys/<alias>.enc` using PKCS#8 + AES‑GCM |
24 | | -- RSA‑SHA2 signatures (`rsa‑sha2‑256/512`) supported by the agent |
25 | | -- Multiple keys import with unique `alias` |
26 | | -- Configurable agent socket and logging via `~/.fssh/config.json` |
27 | | -- Optional Touch ID TTL (`unlock_ttl_seconds`) to avoid repeated prompts in secure mode |
28 | | -- `config-gen` to generate local `~/.ssh/config` entries with `IdentityAgent` |
29 | | -- `sshd-align` to align server‑side `sshd_config` for RSA‑SHA2 algorithms |
30 | | - |
31 | | -## Install (macOS) |
32 | | -- Build: `go build ./cmd/fssh` |
33 | | -- Place binary: `mv fssh /usr/local/bin/` |
34 | | -- Initialize: `fssh init` |
35 | | -- Import keys: `fssh import --alias work --file ~/.ssh/id_ed25519 --ask-passphrase` |
36 | | -- Config ssh agent,File: `~/.ssh/config` |
37 | | -- Example: |
| 10 | +- Plaintext SSH keys stored locally on disk pose security risks |
| 11 | +- Encrypted keys require entering passphrases every time you connect, which is inconvenient |
| 12 | +- When multiple hosts are configured in `~/.ssh/config`, their aliases are easy to forget over time, requiring you to check the config file before each connection |
| 13 | + |
| 14 | +## Solutions |
| 15 | +- Automatically prompts for Touch ID fingerprint verification when using SSH commands to connect to hosts on macOS, then uses the master key to decrypt SSH private keys for authentication |
| 16 | +- Compatible with OpenSSH's ssh-agent |
| 17 | +- Running `fssh` directly launches an interactive shell where you can use commands like `list` and `connect` to view host information from `~/.ssh/config`. In this shell, you can enter a host's ID, host name, or IP directly to connect via SSH |
| 18 | + |
| 19 | +## Screenshots |
| 20 | +SSH connection with fingerprint unlock: |
| 21 | + |
| 22 | + |
| 23 | +Viewing hosts from `~/.ssh/config` in the interactive shell: |
| 24 | + |
| 25 | + |
| 26 | +Connecting to a host from the interactive shell: |
| 27 | + |
| 28 | + |
| 29 | +## Features |
| 30 | +- Touch ID/Local authentication to read master key (stored in Keychain) |
| 31 | +- AES-256-GCM + HKDF encryption for private keys (independent `salt`/`nonce` per file) |
| 32 | +- Private key import/export (PKCS#8 PEM backup), listing and status checking |
| 33 | +- ssh-agent: |
| 34 | + - Supports fingerprint verification per signature, or configure TTL for repeat access within a short time window |
| 35 | + - Compatible with RSA-SHA2 (rsa-sha2-256/512) |
| 36 | +- Interactive Shell: parses `~/.ssh/config` hosts, Tab completion, default connection behavior |
| 37 | +- Configuration generator: automatically generates local `~/.ssh/config` entries with `IdentityAgent` |
| 38 | + |
| 39 | +## Installation & Configuration |
| 40 | +1. Build: `go build ./cmd/fssh`; install to `/usr/local/bin/fssh` |
| 41 | +2. Run `fssh init` to initialize the master key |
| 42 | +3. Run `fssh import -alias <string> -file <path/to/private> --ask-passphrase` to import private keys |
| 43 | +4. Start the SSH authentication agent: `fssh agent --unlock-ttl-seconds 600` |
| 44 | +5. Modify the `~/.ssh/config` file so all SSH connections go through the fssh agent (you can also use `export SSH_AUTH_SOCK=~/.fssh/agent.sock` to set the variable for specific use cases) |
| 45 | + |
| 46 | +Configure the OpenSSH agent by adding the following at the beginning of `~/.ssh/config`: |
38 | 47 | ``` |
39 | 48 | host * |
40 | 49 | ServerAliveInterval 30 |
41 | 50 | AddKeysToAgent yes |
42 | 51 | ControlPersist 60 |
43 | 52 | ControlMaster auto |
44 | | - ControlPath ~/.ssh/shareconn/master-%r@%h:%p |
45 | | - Ciphers +aes128-cbc,3des-cbc,aes192-cbc,aes256-cbc |
46 | | - HostKeyAlgorithms +ssh-rsa |
47 | | - KexAlgorithms +diffie-hellman-group1-sha1 |
48 | | - IdentityAgent /Users/leo/.fssh/agent.sock |
| 53 | + IdentityAgent ~/.fssh/agent.sock |
49 | 54 | ``` |
50 | 55 |
|
51 | | -## Configuration |
52 | | -- File: `~/.fssh/config.json` |
53 | | -- Example: |
| 56 | +6. If needed, create and modify the configuration file: `~/.fssh/config.json`, example: |
54 | 57 | ``` |
55 | 58 | { |
56 | | - "socket": "~/.fssh/agent.sock", |
57 | | - "require_touch_id_per_sign": true, |
58 | | - "unlock_ttl_seconds": 600, |
59 | | - "log_out": "/var/tmp/fssh-agent.out.log", |
60 | | - "log_err": "/var/tmp/fssh-agent.err.log", |
61 | | - "log_level": "info", |
62 | | - "log_format": "plain", |
63 | | - "log_time_format": "2006-01-02T15:04:05Z07:00" |
| 59 | + "socket":"~/.fssh/agent.sock", |
| 60 | + "require_touch_id_per_sign":true, |
| 61 | + "unlock_ttl_seconds":600, |
| 62 | + "log_level":"info", |
| 63 | + "log_format":"plain" |
64 | 64 | } |
65 | 65 | ``` |
66 | | -- Precedence: CLI flags > config file > defaults |
67 | 66 |
|
68 | | -## Start Agent |
69 | | -- Foreground: `fssh agent` |
70 | | -- With TTL: `fssh agent --unlock-ttl-seconds 600` |
71 | | -- Use in shell: `export SSH_AUTH_SOCK=~/.fssh/agent.sock` |
72 | | - |
73 | | -## Auto‑Start on Login (LaunchAgents) |
74 | | -- Copy plist: `cp contrib/com.fssh.agent.plist ~/Library/LaunchAgents/com.fssh.agent.plist` |
75 | | -- Load: `launchctl load -w ~/Library/LaunchAgents/com.fssh.agent.plist` |
76 | | -- Reload after config changes: |
77 | | - - Preferred: `launchctl kickstart -k gui/$(id -u)/com.fssh.agent` |
78 | | - - Legacy: `launchctl unload -w ~/Library/LaunchAgents/com.fssh.agent.plist && launchctl load -w ~/Library/LaunchAgents/com.fssh.agent.plist` |
| 67 | +- socket: SSH agent socket location |
| 68 | +- require_touch_id_per_sign: Whether to require Touch ID verification on every SSH signature |
| 69 | + - true: Security mode enabled, requires Touch ID on each signature (or after TTL expires) |
| 70 | + - false: Convenience mode, decrypts all keys once during startup and keeps them in memory |
| 71 | +- unlock_ttl_seconds: Cache time window after Touch ID unlock |
| 72 | +- log_level: Controls log output level |
| 73 | + - debug: Shows all logs (including cache hit information) |
| 74 | + - info: Shows general information (default) |
| 75 | + - warn: Shows warnings and errors only |
| 76 | + - error: Shows only errors |
| 77 | +- log_format: Controls log output format |
| 78 | + - plain: Human-readable plain format |
| 79 | + - json: Structured JSON format |
| 80 | + |
| 81 | +## Auto-start on Login |
| 82 | +- Start agent: `fssh agent --unlock-ttl-seconds 600` |
| 83 | +- Auto-start: Copy `contrib/com.fssh.agent.plist` to `~/Library/LaunchAgents/` and run `launchctl load -w`; after modifying configuration, use `launchctl kickstart -k gui/$(id -u)/com.fssh.agent` to reload |
79 | 84 |
|
80 | 85 | ## Interactive Shell |
81 | | -- Start: `fssh` or `fssh shell` |
| 86 | +- Launch: `fssh` or `fssh shell` |
82 | 87 | - Commands: |
83 | | - - `list` — show `id\thost(ip)` |
84 | | - - `search <term>` — filter by id/host/ip |
85 | | - - `connect <id|host|ip>` — connect via OpenSSH |
86 | | - - Tab completion for commands and host/id/ip |
87 | | - - Non‑command input defaults to `connect` |
88 | | - |
89 | | -## Config Generator |
90 | | -- Print block: `fssh config-gen --host backuphost --user root` |
91 | | -- Write to file: `fssh config-gen --host backuphost --user root --write` |
92 | | -- Overwrite existing: `fssh config-gen --host backuphost --overwrite --write` |
93 | | -- Global algorithms (optional): `fssh config-gen --global-algos --write` adds RSA‑SHA2 once to `Host *` |
94 | | -- Generated host block contains `IdentityAgent` and optional `User/Port`; per‑host algorithm lines are not added by default |
95 | | - |
96 | | -## Server Alignment (Optional) |
97 | | -- Align RSA‑SHA2 on server: `fssh sshd-align --host backuphost --sudo` |
98 | | -- Changes on remote `/etc/ssh/sshd_config`: |
99 | | - - `PubkeyAuthentication yes` |
100 | | - - `PubkeyAcceptedAlgorithms +rsa-sha2-512,rsa-sha2-256` |
101 | | - - `PubkeyAcceptedKeyTypes +rsa-sha2-512,rsa-sha2-256` (compat) |
| 88 | + - `list` displays `id\thost(ip)` |
| 89 | + - `search <term>` filters by id/host/ip |
| 90 | + - `connect <id|host|ip>` initiates connection; non-command input defaults to connection |
| 91 | + - Tab completion covers commands and id/host/ip |
102 | 92 |
|
103 | 93 | ## Troubleshooting |
104 | | -- “incorrect signature type / no mutual signature supported” |
105 | | - - Ensure agent is running and environment: `export SSH_AUTH_SOCK=~/.fssh/agent.sock` |
106 | | - - Client config for host uses agent: `IdentityAgent ~/.fssh/agent.sock` |
107 | | - - Server accepts RSA‑SHA2 (use `sshd-align` or edit `sshd_config`) |
108 | | -- Input not visible after connect |
109 | | - - Agent shell uses `ssh -tt` and suspends line editor during remote session |
110 | | -- Logging |
111 | | - - Configure `log_out/log_err`, `log_level`, `log_format`; restart agent after changes |
| 94 | +- "incorrect signature type / no mutual signature supported" |
| 95 | + - Confirm agent is running and set `SSH_AUTH_SOCK=~/.fssh/agent.sock` |
| 96 | + - Local entries should include `IdentityAgent ~/.fssh/agent.sock` |
| 97 | + - Server accepts RSA-SHA2 (use `sshd-align` or manually edit) |
| 98 | +- Input invisible after connection: use `ssh -tt` and suspend line editing during remote session |
| 99 | +- Logging: configure `log_out/log_err`, `log_level`, `log_format`; restart agent after changes |
112 | 100 |
|
113 | 101 | ## Security Notes |
114 | | -- Secure mode: per‑sign unlock (or TTL cache) reduces risk by avoiding long‑lived decrypted keys |
115 | | -- Convenience mode: preload all keys into memory; prefer only when prompts are impractical |
116 | | -- Never store plaintext secrets in the repo or logs; use Keychain and config paths |
117 | | - |
118 | | -## Comparison to Secretive |
119 | | -- Storage model |
120 | | - - Secretive: keys in Secure Enclave, non‑exportable by design |
121 | | - - fssh: PKCS#8 encrypted files in `~/.fssh/keys` with Touch ID‑protected master key in Keychain; optional password‑protected PEM export for recovery |
122 | | -- Access control |
123 | | - - Secretive: Touch ID/Apple Watch gate before key access; access notifications |
124 | | - - fssh: LocalAuthentication on each signature or within a configurable TTL window; no notifications yet |
125 | | -- Hardware support |
126 | | - - Secretive: Smart Card/YubiKey supported for Macs without SE |
127 | | - - fssh: no smart card support yet (roadmap) |
128 | | -- Agent and algorithms |
129 | | - - Secretive: signs with SE‑backed keys (non‑exportable) |
130 | | - - fssh: OpenSSH‑compatible agent with RSA‑SHA2 (`rsa‑sha2‑256/512`) extended signatures; includes `sshd-align` to align server algorithms |
131 | | -- Developer and ops tools |
132 | | - - Secretive: native app experience and Homebrew install |
133 | | - - fssh: CLI and interactive shell (host parsing, tab completion, default connect), `config-gen` to write `IdentityAgent` entries, generic `launchd` auto‑start, unified logging |
134 | | -- Platform |
135 | | - - Secretive: macOS with Secure Enclave |
136 | | - - fssh: macOS today; planned cross‑platform support |
| 102 | +- Security mode: per-signature unlock (or TTL cache) avoids long-term decrypted private keys in memory |
| 103 | +- Convenience mode: decrypts and keeps in memory on startup; only use this when prompted too frequently |
| 104 | +- Avoid plaintext leakage: don't store plaintext keys/passwords in repositories or logs |
137 | 105 |
|
138 | 106 | ## Credits |
139 | 107 | - This project is assisted by TRAE AI software |
140 | | - |
141 | | -## License |
142 | | -- Proprietary project (example). Adjust this section as appropriate for your distribution. |
0 commit comments