Skip to content

Commit cd3bc91

Browse files
author
lee
committed
增加OTP支持;更新ReadMe.md
1 parent 1d84fdc commit cd3bc91

20 files changed

+5340
-238
lines changed

.gitignore

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
.trae
22
.vscode
3-
.fss
3+
.fssh
44
.env
5-
*.sock
5+
*.sock
6+
.claude
7+
CLAUDE.md
8+
fssh

README.md

Lines changed: 258 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -1,110 +1,289 @@
1-
# fssh
1+
# fssh - Secure and Simple SSH Key Management
22

33
![Touch ID Fingerprint](images/finger.png)
44

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.
5+
## What is fssh?
66

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`.
7+
fssh is a macOS-only SSH key management tool that solves two common pain points:
88

9-
## Use Cases
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
9+
1. **Entering private key passphrase every SSH login** → fssh lets you unlock with Touch ID or One-Time Password (OTP)
10+
2. **Forgetting server aliases in `~/.ssh/config`** → fssh provides an interactive shell with Tab completion for quick connections
1311

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
12+
## How It Works
13+
14+
```
15+
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
16+
│ SSH Client │ ──▶ │ fssh Agent │ ──▶ │Remote Server│
17+
└─────────────┘ └─────────────┘ └─────────────┘
18+
19+
Touch ID or OTP
20+
unlocks encrypted
21+
private keys
22+
```
23+
24+
Your SSH private keys are stored encrypted. They can only be decrypted after Touch ID fingerprint or OTP verification.
1825

1926
## Screenshots
20-
SSH connection with fingerprint unlock:
2127

22-
![Touch ID authentication](images/finger.png)
28+
**Touch ID prompt during SSH login:**
29+
30+
![Touch ID Authentication](images/finger.png)
31+
32+
**Interactive shell for viewing and connecting to servers:**
33+
34+
![Interactive Shell](images/shell.png)
35+
36+
---
37+
38+
## Quick Start
39+
40+
### Step 1: Install
41+
42+
```bash
43+
# After downloading the source code, build it
44+
go build ./cmd/fssh
45+
46+
# Install to system directory (requires admin privileges)
47+
sudo cp fssh /usr/local/bin/
48+
```
49+
50+
### Step 2: Initialize
51+
52+
Choose an authentication mode based on your device:
53+
54+
**If your Mac has Touch ID (MacBook Pro/Air 2016+, iMac with Magic Keyboard, etc.):**
55+
56+
```bash
57+
fssh init --mode touchid
58+
```
59+
60+
**If your Mac doesn't have Touch ID (Mac Mini, older Macs, VMs, etc.):**
61+
62+
```bash
63+
fssh init --mode otp
64+
```
65+
66+
During OTP mode initialization:
67+
1. You'll set a password (at least 12 characters)
68+
2. A TOTP secret will be displayed - add it to an authenticator app (e.g., Google Authenticator, Authy)
69+
3. 10 recovery codes will be shown - **save them securely**
70+
71+
### Step 3: Import SSH Private Key
2372

24-
Viewing hosts from `~/.ssh/config` in the interactive shell:
73+
```bash
74+
# Import your SSH private key (you'll be prompted for passphrase if the key has one)
75+
fssh import --alias mykey --file ~/.ssh/id_rsa --ask-passphrase
76+
```
77+
78+
Parameters:
79+
- `--alias`: A name for this key for easy reference
80+
- `--file`: Path to the private key file
81+
- `--ask-passphrase`: Add this if the private key is passphrase-protected
2582

26-
![Interactive shell](images/shell.png)
83+
### Step 4: Start the Agent
2784

28-
Connecting to a host from the interactive shell:
85+
```bash
86+
fssh agent
87+
```
2988

30-
![SSH connection](images/login.png)
89+
Once started, the Agent runs in the background, listening on `~/.fssh/agent.sock`.
3190

32-
## Features
33-
- Touch ID/Local authentication to read master key (stored in Keychain)
34-
- AES-256-GCM + HKDF encryption for private keys (independent `salt`/`nonce` per file)
35-
- Private key import/export (PKCS#8 PEM backup), listing and status checking
36-
- ssh-agent:
37-
- Supports fingerprint verification per signature, or configure TTL for repeat access within a short time window
38-
- Compatible with RSA-SHA2 (rsa-sha2-256/512)
39-
- Interactive Shell: parses `~/.ssh/config` hosts, Tab completion, default connection behavior
40-
- Configuration generator: automatically generates local `~/.ssh/config` entries with `IdentityAgent`
91+
### Step 5: Configure SSH to Use fssh Agent
4192

42-
## Installation & Configuration
43-
1. Build: `go build ./cmd/fssh`; install to `/usr/local/bin/fssh`
44-
2. Run `fssh init` to initialize the master key
45-
3. Run `fssh import -alias <string> -file <path/to/private> --ask-passphrase` to import private keys
46-
4. Start the SSH authentication agent: `fssh agent --unlock-ttl-seconds 600`
47-
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)
93+
Edit `~/.ssh/config` and add at the **very beginning**:
4894

49-
Configure the OpenSSH agent by adding the following at the beginning of `~/.ssh/config`:
5095
```
51-
host *
52-
ServerAliveInterval 30
53-
AddKeysToAgent yes
54-
ControlPersist 60
55-
ControlMaster auto
56-
IdentityAgent ~/.fssh/agent.sock
96+
Host *
97+
IdentityAgent ~/.fssh/agent.sock
98+
```
99+
100+
This routes all SSH connections through fssh Agent.
101+
102+
### Step 6: Start Using
103+
104+
```bash
105+
# Use SSH normally - Touch ID or OTP prompt will appear automatically
106+
ssh user@yourserver.com
57107
```
58108

59-
6. If needed, create and modify the configuration file: `~/.fssh/config.json`, example:
109+
---
110+
111+
## Auto-Start on Login
112+
113+
Tired of manually starting the Agent after each reboot? Set up auto-start:
114+
115+
```bash
116+
# Copy the launch configuration file
117+
cp contrib/com.fssh.agent.plist ~/Library/LaunchAgents/
118+
119+
# Load the service
120+
launchctl load -w ~/Library/LaunchAgents/com.fssh.agent.plist
60121
```
122+
123+
**Check service status:**
124+
125+
```bash
126+
launchctl list | grep fssh
127+
```
128+
129+
Normal output looks like: `- 0 com.fssh.agent` (0 means running)
130+
131+
**To restart the service:**
132+
133+
```bash
134+
launchctl kickstart -k gui/$(id -u)/com.fssh.agent
135+
```
136+
137+
**To stop the service:**
138+
139+
```bash
140+
launchctl unload ~/Library/LaunchAgents/com.fssh.agent.plist
141+
```
142+
143+
---
144+
145+
## Interactive Shell
146+
147+
Run `fssh` or `fssh shell` to enter interactive mode:
148+
149+
```bash
150+
$ fssh
151+
fssh> list # List all hosts from ~/.ssh/config
152+
fssh> search prod # Search for hosts containing "prod"
153+
fssh> connect myserver # Connect to myserver
154+
fssh> myserver # Or just type the hostname to connect
155+
fssh> exit # Exit the shell
156+
```
157+
158+
**Tab completion** is supported - type partial hostname and press Tab to autocomplete.
159+
160+
---
161+
162+
## Command Reference
163+
164+
| Command | Description |
165+
|---------|-------------|
166+
| `fssh init --mode touchid` | Initialize (Touch ID mode) |
167+
| `fssh init --mode otp` | Initialize (OTP mode) |
168+
| `fssh import --alias name --file path --ask-passphrase` | Import a private key |
169+
| `fssh list` | List imported keys |
170+
| `fssh export --alias name --out path` | Export a key (backup) |
171+
| `fssh remove --alias name` | Remove a key |
172+
| `fssh agent` | Start the Agent |
173+
| `fssh status` | Check status |
174+
| `fssh shell` | Enter interactive shell |
175+
176+
---
177+
178+
## Configuration
179+
180+
Configuration file location: `~/.fssh/config.json`
181+
182+
```json
61183
{
62-
"socket":"~/.fssh/agent.sock",
63-
"require_touch_id_per_sign":true,
64-
"unlock_ttl_seconds":600,
65-
"log_level":"info",
66-
"log_format":"plain"
184+
"socket": "~/.fssh/agent.sock",
185+
"require_touch_id_per_sign": true,
186+
"unlock_ttl_seconds": 600,
187+
"log_level": "info",
188+
"log_format": "plain"
67189
}
68190
```
69191

70-
- socket: SSH agent socket location
71-
- require_touch_id_per_sign: Whether to require Touch ID verification on every SSH signature
72-
- true: Security mode enabled, requires Touch ID on each signature (or after TTL expires)
73-
- false: Convenience mode, decrypts all keys once during startup and keeps them in memory
74-
- unlock_ttl_seconds: Cache time window after Touch ID unlock
75-
- log_level: Controls log output level
76-
- debug: Shows all logs (including cache hit information)
77-
- info: Shows general information (default)
78-
- warn: Shows warnings and errors only
79-
- error: Shows only errors
80-
- log_format: Controls log output format
81-
- plain: Human-readable plain format
82-
- json: Structured JSON format
83-
84-
## Auto-start on Login
85-
- Start agent: `fssh agent --unlock-ttl-seconds 600`
86-
- 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
192+
**Configuration options:**
87193

88-
## Interactive Shell
89-
- Launch: `fssh` or `fssh shell`
90-
- Commands:
91-
- `list` displays `id\thost(ip)`
92-
- `search <term>` filters by id/host/ip
93-
- `connect <id|host|ip>` initiates connection; non-command input defaults to connection
94-
- Tab completion covers commands and id/host/ip
194+
| Option | Description | Default |
195+
|--------|-------------|---------|
196+
| `socket` | Agent socket path | `~/.fssh/agent.sock` |
197+
| `require_touch_id_per_sign` | Require verification for each SSH signature (secure mode) | `true` |
198+
| `unlock_ttl_seconds` | Cache duration after verification (seconds) - no re-verification needed within this period | `600` (10 min) |
199+
| `log_level` | Log level: `debug`/`info`/`warn`/`error` | `info` |
200+
| `log_format` | Log format: `plain` (readable) / `json` (structured) | `plain` |
201+
202+
**Secure Mode vs Convenience Mode:**
203+
204+
- `require_touch_id_per_sign: true` (Secure): Verification required for each SSH connection (or within TTL cache period)
205+
- `require_touch_id_per_sign: false` (Convenience): All keys decrypted once at startup, no further verification needed
206+
207+
---
95208

96209
## Troubleshooting
97-
- `"incorrect signature type / no mutual signature supported"`
98-
- Confirm agent is running and set `SSH_AUTH_SOCK=~/.fssh/agent.sock`
99-
- Local entries should include `IdentityAgent ~/.fssh/agent.sock`
100-
- Server accepts RSA-SHA2 (use `sshd-align` or manually edit)
101-
- Input invisible after connection: use `ssh -tt` and suspend line editing during remote session
102-
- Logging: configure `log_out/log_err`, `log_level`, `log_format`; restart agent after changes
210+
211+
### 1. Error "incorrect signature type" or "no mutual signature supported"
212+
213+
**Cause**: SSH client isn't using fssh Agent, or server doesn't support RSA-SHA2.
214+
215+
**Solution**:
216+
1. Verify Agent is running: `launchctl list | grep fssh`
217+
2. Verify `~/.ssh/config` has `IdentityAgent ~/.fssh/agent.sock`
218+
3. Or set environment variable: `export SSH_AUTH_SOCK=~/.fssh/agent.sock`
219+
220+
### 2. No input display (cursor doesn't move)
221+
222+
**Cause**: Terminal control issue.
223+
224+
**Solution**: Use `ssh -tt` to force TTY allocation:
225+
226+
```bash
227+
ssh -tt user@server
228+
```
229+
230+
### 3. launchctl load error "Load failed: 5: Input/output error"
231+
232+
**Cause**: Service already loaded, or initialization incomplete.
233+
234+
**Solution**:
235+
236+
```bash
237+
# Unload first
238+
launchctl unload ~/Library/LaunchAgents/com.fssh.agent.plist
239+
240+
# Ensure initialization is complete
241+
fssh init --mode touchid # or otp
242+
243+
# Reload
244+
launchctl load -w ~/Library/LaunchAgents/com.fssh.agent.plist
245+
```
246+
247+
### 4. OTP verification code always wrong
248+
249+
**Cause**: Phone time out of sync, or TOTP was added incorrectly.
250+
251+
**Solution**:
252+
1. Ensure phone time is correct (enable automatic time setting)
253+
2. Delete the old entry in authenticator app and re-add
254+
3. If recovery is impossible, use recovery codes
255+
256+
### 5. Forgot OTP password?
257+
258+
If you saved recovery codes, use them to reset. Without recovery codes, you must reinitialize (losing imported keys):
259+
260+
```bash
261+
# Warning: This deletes all imported keys!
262+
rm -rf ~/.fssh
263+
fssh init --mode otp
264+
# Then re-import your keys
265+
```
266+
267+
---
103268

104269
## Security Notes
105-
- Security mode: per-signature unlock (or TTL cache) avoids long-term decrypted private keys in memory
106-
- Convenience mode: decrypts and keeps in memory on startup; only use this when prompted too frequently
107-
- Avoid plaintext leakage: don't store plaintext keys/passwords in repositories or logs
270+
271+
1. **Encrypted key storage**: Imported private keys are encrypted with AES-256-GCM - even if your computer is stolen, keys can't be decrypted without Touch ID/OTP
272+
2. **Enable FileVault**: macOS full-disk encryption provides additional protection
273+
3. **Protect recovery codes**: OTP recovery codes are like master keys - store them securely
274+
4. **Regular backups**: Use `fssh export` to backup important keys
275+
276+
---
277+
278+
## Technical Details
279+
280+
- **Encryption**: AES-256-GCM + HKDF (independent salt/nonce per key file)
281+
- **Key derivation**: PBKDF2 (100,000 iterations)
282+
- **TOTP standard**: RFC 6238
283+
- **Compatibility**: Fully compatible with OpenSSH ssh-agent protocol
284+
285+
---
108286

109287
## Credits
110-
- This project is assisted by TRAE AI software
288+
289+
This project was developed with assistance from TRAE AI software.

0 commit comments

Comments
 (0)