bwssh 0.1.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- bwssh-0.1.1/PKG-INFO +196 -0
- bwssh-0.1.1/README.md +183 -0
- bwssh-0.1.1/pyproject.toml +102 -0
- bwssh-0.1.1/src/bwssh/__init__.py +7 -0
- bwssh-0.1.1/src/bwssh/agent_proto.py +66 -0
- bwssh-0.1.1/src/bwssh/bitwarden.py +237 -0
- bwssh-0.1.1/src/bwssh/cli.py +673 -0
- bwssh-0.1.1/src/bwssh/config.py +162 -0
- bwssh-0.1.1/src/bwssh/constants.py +33 -0
- bwssh-0.1.1/src/bwssh/control.py +344 -0
- bwssh-0.1.1/src/bwssh/daemon.py +413 -0
- bwssh-0.1.1/src/bwssh/data/autostart/bwssh-tray.desktop +9 -0
- bwssh-0.1.1/src/bwssh/data/config.toml.example +69 -0
- bwssh-0.1.1/src/bwssh/data/polkit/io.github.reidond.bwssh.policy +35 -0
- bwssh-0.1.1/src/bwssh/data/systemd/bwssh-agent.service +17 -0
- bwssh-0.1.1/src/bwssh/data/systemd/bwssh-agent.socket +11 -0
- bwssh-0.1.1/src/bwssh/data/systemd/bwssh-tray.service +16 -0
- bwssh-0.1.1/src/bwssh/keys.py +107 -0
- bwssh-0.1.1/src/bwssh/logging_config.py +41 -0
- bwssh-0.1.1/src/bwssh/main.py +12 -0
- bwssh-0.1.1/src/bwssh/peercred.py +153 -0
- bwssh-0.1.1/src/bwssh/polkit.py +241 -0
- bwssh-0.1.1/src/bwssh/py.typed +1 -0
- bwssh-0.1.1/src/bwssh/signing.py +117 -0
- bwssh-0.1.1/src/bwssh/tray.py +420 -0
- bwssh-0.1.1/src/bwssh/ui/__init__.py +76 -0
- bwssh-0.1.1/src/bwssh/ui/_base.py +42 -0
- bwssh-0.1.1/src/bwssh/ui/_detect.py +27 -0
- bwssh-0.1.1/src/bwssh/ui/_graphical.py +484 -0
- bwssh-0.1.1/src/bwssh/ui/_terminal.py +143 -0
- bwssh-0.1.1/src/bwssh/ui/_tui.py +313 -0
bwssh-0.1.1/PKG-INFO
ADDED
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: bwssh
|
|
3
|
+
Version: 0.1.1
|
|
4
|
+
Summary: Bitwarden-backed SSH agent for Linux
|
|
5
|
+
Requires-Dist: click>=8.3.1
|
|
6
|
+
Requires-Dist: cryptography>=46.0.4
|
|
7
|
+
Requires-Dist: dbus-fast>=4.0.0
|
|
8
|
+
Requires-Dist: textual>=7.5.0
|
|
9
|
+
Requires-Dist: pygobject>=3.42.0,<3.50 ; extra == 'gui'
|
|
10
|
+
Requires-Python: >=3.12
|
|
11
|
+
Provides-Extra: gui
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
|
|
14
|
+
# bwssh
|
|
15
|
+
|
|
16
|
+
Bitwarden-backed SSH agent for Linux. Store your SSH keys in Bitwarden and use
|
|
17
|
+
them seamlessly with any SSH client.
|
|
18
|
+
|
|
19
|
+
## Features
|
|
20
|
+
|
|
21
|
+
- **Bitwarden integration**: SSH keys stored securely in your Bitwarden vault
|
|
22
|
+
- **Standard SSH agent**: Works with `ssh`, `git`, and any SSH client
|
|
23
|
+
- **Systemd integration**: Runs as a user service, starts on login
|
|
24
|
+
- **Forwarding protection**: Blocks remote servers from using your keys
|
|
25
|
+
- **Optional polkit prompts**: Desktop authorization popups (disabled by default)
|
|
26
|
+
|
|
27
|
+
## Requirements
|
|
28
|
+
|
|
29
|
+
- Linux with systemd user services
|
|
30
|
+
- Python 3.12+
|
|
31
|
+
- Bitwarden CLI (`bw`) installed and logged in
|
|
32
|
+
|
|
33
|
+
## Installation
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
uv sync
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Bitwarden CLI
|
|
40
|
+
|
|
41
|
+
Install the Bitwarden CLI (`bw`) and log in before using bwssh. See
|
|
42
|
+
https://bitwarden.com/help/cli/ for installation instructions.
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
bw --version
|
|
46
|
+
bw login
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Quick start
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
uv run bwssh install --user-systemd
|
|
53
|
+
uv run bwssh start
|
|
54
|
+
uv run bwssh unlock
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
export SSH_AUTH_SOCK=${XDG_RUNTIME_DIR}/bwssh/agent.sock
|
|
59
|
+
ssh -T git@github.com
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Configuration
|
|
63
|
+
|
|
64
|
+
Config file: `~/.config/bwssh/config.toml`
|
|
65
|
+
|
|
66
|
+
### Quick Setup (Recommended)
|
|
67
|
+
|
|
68
|
+
The easiest way to configure bwssh is to use the init command:
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
# First, unlock Bitwarden
|
|
72
|
+
export BW_SESSION=$(bw unlock --raw)
|
|
73
|
+
|
|
74
|
+
# Then run init to auto-discover SSH keys
|
|
75
|
+
bwssh config init
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
This will find all SSH keys in your Bitwarden vault and create a config file.
|
|
79
|
+
|
|
80
|
+
### Manual Setup
|
|
81
|
+
|
|
82
|
+
If you prefer to configure manually, first find your SSH key IDs:
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
bw list items | jq -r '.[] | select(.sshKey != null) | "\(.id) \(.name)"'
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Then create `~/.config/bwssh/config.toml`:
|
|
89
|
+
|
|
90
|
+
```toml
|
|
91
|
+
[bitwarden]
|
|
92
|
+
bw_path = "/full/path/to/bw" # Use 'which bw' to find this
|
|
93
|
+
item_ids = [
|
|
94
|
+
"xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", # your-key-name
|
|
95
|
+
]
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### Full Config Example
|
|
99
|
+
|
|
100
|
+
```toml
|
|
101
|
+
[daemon]
|
|
102
|
+
log_level = "INFO"
|
|
103
|
+
|
|
104
|
+
[bitwarden]
|
|
105
|
+
bw_path = "/usr/bin/bw"
|
|
106
|
+
item_ids = [
|
|
107
|
+
"xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
|
|
108
|
+
]
|
|
109
|
+
|
|
110
|
+
[auth]
|
|
111
|
+
# Polkit authorization prompts (default: disabled)
|
|
112
|
+
require_polkit = false
|
|
113
|
+
|
|
114
|
+
# Block forwarded agent requests (recommended)
|
|
115
|
+
deny_forwarded_by_default = true
|
|
116
|
+
|
|
117
|
+
[ssh]
|
|
118
|
+
allow_ed25519 = true
|
|
119
|
+
allow_ecdsa = true
|
|
120
|
+
allow_rsa = true
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
### Environment Variables
|
|
124
|
+
|
|
125
|
+
- `BWSSH_RUNTIME_DIR`: Override socket directory
|
|
126
|
+
- `BWSSH_LOG_LEVEL`: Override log level
|
|
127
|
+
- `BW_SESSION`: Bitwarden session key (auto-detected by `bwssh unlock`)
|
|
128
|
+
|
|
129
|
+
## Security
|
|
130
|
+
|
|
131
|
+
### Default Mode
|
|
132
|
+
|
|
133
|
+
By default, bwssh allows all local signing requests without prompts. Security comes from:
|
|
134
|
+
|
|
135
|
+
- **Auto-lock on sleep**: Keys are cleared when your laptop sleeps (enabled by default)
|
|
136
|
+
- **Forwarded agent blocking**: Remote servers can't use your keys
|
|
137
|
+
- **Manual lock**: Run `bwssh lock` when stepping away
|
|
138
|
+
|
|
139
|
+
### Polkit Prompts (Optional)
|
|
140
|
+
|
|
141
|
+
For extra security, enable polkit to show desktop prompts for each signing request:
|
|
142
|
+
|
|
143
|
+
```toml
|
|
144
|
+
[auth]
|
|
145
|
+
require_polkit = true
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
This requires installing the polkit policy:
|
|
149
|
+
|
|
150
|
+
```bash
|
|
151
|
+
bwssh install --polkit | sudo tee /usr/share/polkit-1/actions/io.github.reidond.bwssh.policy > /dev/null
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
See `docs/` for detailed polkit setup instructions.
|
|
155
|
+
|
|
156
|
+
## CLI Commands
|
|
157
|
+
|
|
158
|
+
```bash
|
|
159
|
+
# Daemon control
|
|
160
|
+
bwssh start # Start the agent daemon
|
|
161
|
+
bwssh stop # Stop the agent daemon
|
|
162
|
+
bwssh status # Show daemon status
|
|
163
|
+
|
|
164
|
+
# Key management
|
|
165
|
+
bwssh unlock # Unlock vault and load keys
|
|
166
|
+
bwssh lock # Lock agent and clear keys
|
|
167
|
+
bwssh sync # Reload keys from Bitwarden
|
|
168
|
+
bwssh keys # List loaded SSH keys
|
|
169
|
+
|
|
170
|
+
# Configuration
|
|
171
|
+
bwssh config init # Auto-discover SSH keys and create config
|
|
172
|
+
bwssh config show # Show current configuration
|
|
173
|
+
|
|
174
|
+
# Installation
|
|
175
|
+
bwssh install --user-systemd # Install systemd user service
|
|
176
|
+
bwssh install --polkit # Print polkit policy file
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
## Documentation
|
|
180
|
+
|
|
181
|
+
Full documentation lives in `docs/` and can be served locally:
|
|
182
|
+
|
|
183
|
+
```bash
|
|
184
|
+
cd docs
|
|
185
|
+
bun install
|
|
186
|
+
bun run dev
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
## Development
|
|
190
|
+
|
|
191
|
+
```bash
|
|
192
|
+
uv run ruff check .
|
|
193
|
+
uv run ruff format .
|
|
194
|
+
uv run mypy src tests
|
|
195
|
+
uv run pytest
|
|
196
|
+
```
|
bwssh-0.1.1/README.md
ADDED
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
# bwssh
|
|
2
|
+
|
|
3
|
+
Bitwarden-backed SSH agent for Linux. Store your SSH keys in Bitwarden and use
|
|
4
|
+
them seamlessly with any SSH client.
|
|
5
|
+
|
|
6
|
+
## Features
|
|
7
|
+
|
|
8
|
+
- **Bitwarden integration**: SSH keys stored securely in your Bitwarden vault
|
|
9
|
+
- **Standard SSH agent**: Works with `ssh`, `git`, and any SSH client
|
|
10
|
+
- **Systemd integration**: Runs as a user service, starts on login
|
|
11
|
+
- **Forwarding protection**: Blocks remote servers from using your keys
|
|
12
|
+
- **Optional polkit prompts**: Desktop authorization popups (disabled by default)
|
|
13
|
+
|
|
14
|
+
## Requirements
|
|
15
|
+
|
|
16
|
+
- Linux with systemd user services
|
|
17
|
+
- Python 3.12+
|
|
18
|
+
- Bitwarden CLI (`bw`) installed and logged in
|
|
19
|
+
|
|
20
|
+
## Installation
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
uv sync
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Bitwarden CLI
|
|
27
|
+
|
|
28
|
+
Install the Bitwarden CLI (`bw`) and log in before using bwssh. See
|
|
29
|
+
https://bitwarden.com/help/cli/ for installation instructions.
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
bw --version
|
|
33
|
+
bw login
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Quick start
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
uv run bwssh install --user-systemd
|
|
40
|
+
uv run bwssh start
|
|
41
|
+
uv run bwssh unlock
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
export SSH_AUTH_SOCK=${XDG_RUNTIME_DIR}/bwssh/agent.sock
|
|
46
|
+
ssh -T git@github.com
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Configuration
|
|
50
|
+
|
|
51
|
+
Config file: `~/.config/bwssh/config.toml`
|
|
52
|
+
|
|
53
|
+
### Quick Setup (Recommended)
|
|
54
|
+
|
|
55
|
+
The easiest way to configure bwssh is to use the init command:
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
# First, unlock Bitwarden
|
|
59
|
+
export BW_SESSION=$(bw unlock --raw)
|
|
60
|
+
|
|
61
|
+
# Then run init to auto-discover SSH keys
|
|
62
|
+
bwssh config init
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
This will find all SSH keys in your Bitwarden vault and create a config file.
|
|
66
|
+
|
|
67
|
+
### Manual Setup
|
|
68
|
+
|
|
69
|
+
If you prefer to configure manually, first find your SSH key IDs:
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
bw list items | jq -r '.[] | select(.sshKey != null) | "\(.id) \(.name)"'
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
Then create `~/.config/bwssh/config.toml`:
|
|
76
|
+
|
|
77
|
+
```toml
|
|
78
|
+
[bitwarden]
|
|
79
|
+
bw_path = "/full/path/to/bw" # Use 'which bw' to find this
|
|
80
|
+
item_ids = [
|
|
81
|
+
"xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", # your-key-name
|
|
82
|
+
]
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### Full Config Example
|
|
86
|
+
|
|
87
|
+
```toml
|
|
88
|
+
[daemon]
|
|
89
|
+
log_level = "INFO"
|
|
90
|
+
|
|
91
|
+
[bitwarden]
|
|
92
|
+
bw_path = "/usr/bin/bw"
|
|
93
|
+
item_ids = [
|
|
94
|
+
"xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
|
|
95
|
+
]
|
|
96
|
+
|
|
97
|
+
[auth]
|
|
98
|
+
# Polkit authorization prompts (default: disabled)
|
|
99
|
+
require_polkit = false
|
|
100
|
+
|
|
101
|
+
# Block forwarded agent requests (recommended)
|
|
102
|
+
deny_forwarded_by_default = true
|
|
103
|
+
|
|
104
|
+
[ssh]
|
|
105
|
+
allow_ed25519 = true
|
|
106
|
+
allow_ecdsa = true
|
|
107
|
+
allow_rsa = true
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### Environment Variables
|
|
111
|
+
|
|
112
|
+
- `BWSSH_RUNTIME_DIR`: Override socket directory
|
|
113
|
+
- `BWSSH_LOG_LEVEL`: Override log level
|
|
114
|
+
- `BW_SESSION`: Bitwarden session key (auto-detected by `bwssh unlock`)
|
|
115
|
+
|
|
116
|
+
## Security
|
|
117
|
+
|
|
118
|
+
### Default Mode
|
|
119
|
+
|
|
120
|
+
By default, bwssh allows all local signing requests without prompts. Security comes from:
|
|
121
|
+
|
|
122
|
+
- **Auto-lock on sleep**: Keys are cleared when your laptop sleeps (enabled by default)
|
|
123
|
+
- **Forwarded agent blocking**: Remote servers can't use your keys
|
|
124
|
+
- **Manual lock**: Run `bwssh lock` when stepping away
|
|
125
|
+
|
|
126
|
+
### Polkit Prompts (Optional)
|
|
127
|
+
|
|
128
|
+
For extra security, enable polkit to show desktop prompts for each signing request:
|
|
129
|
+
|
|
130
|
+
```toml
|
|
131
|
+
[auth]
|
|
132
|
+
require_polkit = true
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
This requires installing the polkit policy:
|
|
136
|
+
|
|
137
|
+
```bash
|
|
138
|
+
bwssh install --polkit | sudo tee /usr/share/polkit-1/actions/io.github.reidond.bwssh.policy > /dev/null
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
See `docs/` for detailed polkit setup instructions.
|
|
142
|
+
|
|
143
|
+
## CLI Commands
|
|
144
|
+
|
|
145
|
+
```bash
|
|
146
|
+
# Daemon control
|
|
147
|
+
bwssh start # Start the agent daemon
|
|
148
|
+
bwssh stop # Stop the agent daemon
|
|
149
|
+
bwssh status # Show daemon status
|
|
150
|
+
|
|
151
|
+
# Key management
|
|
152
|
+
bwssh unlock # Unlock vault and load keys
|
|
153
|
+
bwssh lock # Lock agent and clear keys
|
|
154
|
+
bwssh sync # Reload keys from Bitwarden
|
|
155
|
+
bwssh keys # List loaded SSH keys
|
|
156
|
+
|
|
157
|
+
# Configuration
|
|
158
|
+
bwssh config init # Auto-discover SSH keys and create config
|
|
159
|
+
bwssh config show # Show current configuration
|
|
160
|
+
|
|
161
|
+
# Installation
|
|
162
|
+
bwssh install --user-systemd # Install systemd user service
|
|
163
|
+
bwssh install --polkit # Print polkit policy file
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
## Documentation
|
|
167
|
+
|
|
168
|
+
Full documentation lives in `docs/` and can be served locally:
|
|
169
|
+
|
|
170
|
+
```bash
|
|
171
|
+
cd docs
|
|
172
|
+
bun install
|
|
173
|
+
bun run dev
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
## Development
|
|
177
|
+
|
|
178
|
+
```bash
|
|
179
|
+
uv run ruff check .
|
|
180
|
+
uv run ruff format .
|
|
181
|
+
uv run mypy src tests
|
|
182
|
+
uv run pytest
|
|
183
|
+
```
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
#:schema false
|
|
2
|
+
|
|
3
|
+
[project]
|
|
4
|
+
name = "bwssh"
|
|
5
|
+
version = "0.1.1"
|
|
6
|
+
description = "Bitwarden-backed SSH agent for Linux"
|
|
7
|
+
readme = "README.md"
|
|
8
|
+
requires-python = ">=3.12"
|
|
9
|
+
dependencies = [
|
|
10
|
+
"click>=8.3.1",
|
|
11
|
+
"cryptography>=46.0.4",
|
|
12
|
+
"dbus-fast>=4.0.0",
|
|
13
|
+
"textual>=7.5.0",
|
|
14
|
+
]
|
|
15
|
+
|
|
16
|
+
[project.optional-dependencies]
|
|
17
|
+
gui = ["PyGObject>=3.42.0,<3.50"]
|
|
18
|
+
|
|
19
|
+
[project.scripts]
|
|
20
|
+
bwssh = "bwssh.cli:main"
|
|
21
|
+
bwssh-agentd = "bwssh.daemon:main_entry"
|
|
22
|
+
|
|
23
|
+
[dependency-groups]
|
|
24
|
+
dev = [
|
|
25
|
+
"ruff>=0.9",
|
|
26
|
+
"pytest>=8.0",
|
|
27
|
+
"pytest-cov>=6.0",
|
|
28
|
+
"mypy>=1.14",
|
|
29
|
+
"pytest-asyncio>=1.3.0",
|
|
30
|
+
]
|
|
31
|
+
|
|
32
|
+
[build-system]
|
|
33
|
+
requires = ["uv_build>=0.9.28,<0.10.0"]
|
|
34
|
+
build-backend = "uv_build"
|
|
35
|
+
|
|
36
|
+
[tool.ruff]
|
|
37
|
+
target-version = "py312"
|
|
38
|
+
line-length = 88
|
|
39
|
+
src = ["src", "tests"]
|
|
40
|
+
|
|
41
|
+
[tool.ruff.lint]
|
|
42
|
+
select = [
|
|
43
|
+
"E", # pycodestyle errors
|
|
44
|
+
"W", # pycodestyle warnings
|
|
45
|
+
"F", # pyflakes
|
|
46
|
+
"I", # isort
|
|
47
|
+
"B", # flake8-bugbear
|
|
48
|
+
"C4", # flake8-comprehensions
|
|
49
|
+
"UP", # pyupgrade
|
|
50
|
+
"ARG", # flake8-unused-arguments
|
|
51
|
+
"SIM", # flake8-simplify
|
|
52
|
+
"TCH", # flake8-type-checking
|
|
53
|
+
"PTH", # flake8-use-pathlib
|
|
54
|
+
"ERA", # eradicate (commented out code)
|
|
55
|
+
"PL", # pylint
|
|
56
|
+
"RUF", # ruff-specific rules
|
|
57
|
+
]
|
|
58
|
+
ignore = [
|
|
59
|
+
"PLR0913", # too many arguments
|
|
60
|
+
"PLR2004", # magic value comparison
|
|
61
|
+
]
|
|
62
|
+
|
|
63
|
+
[tool.ruff.lint.isort]
|
|
64
|
+
known-first-party = ["bwssh"]
|
|
65
|
+
|
|
66
|
+
[tool.ruff.format]
|
|
67
|
+
quote-style = "double"
|
|
68
|
+
indent-style = "space"
|
|
69
|
+
skip-magic-trailing-comma = false
|
|
70
|
+
line-ending = "auto"
|
|
71
|
+
|
|
72
|
+
[tool.mypy]
|
|
73
|
+
python_version = "3.12"
|
|
74
|
+
strict = true
|
|
75
|
+
warn_return_any = true
|
|
76
|
+
warn_unused_ignores = true
|
|
77
|
+
disallow_untyped_defs = true
|
|
78
|
+
disallow_incomplete_defs = true
|
|
79
|
+
|
|
80
|
+
[[tool.mypy.overrides]]
|
|
81
|
+
module = "textual.*"
|
|
82
|
+
ignore_missing_imports = true
|
|
83
|
+
|
|
84
|
+
[[tool.mypy.overrides]]
|
|
85
|
+
module = "gi.*"
|
|
86
|
+
ignore_missing_imports = true
|
|
87
|
+
|
|
88
|
+
[tool.pytest.ini_options]
|
|
89
|
+
testpaths = ["tests"]
|
|
90
|
+
pythonpath = ["src"]
|
|
91
|
+
addopts = ["-ra", "-q", "--strict-markers"]
|
|
92
|
+
|
|
93
|
+
[tool.coverage.run]
|
|
94
|
+
source = ["src/bwssh"]
|
|
95
|
+
branch = true
|
|
96
|
+
|
|
97
|
+
[tool.coverage.report]
|
|
98
|
+
exclude_lines = [
|
|
99
|
+
"pragma: no cover",
|
|
100
|
+
"if TYPE_CHECKING:",
|
|
101
|
+
"if __name__ == .__main__.:",
|
|
102
|
+
]
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"""SSH agent protocol message framing over asyncio streams.
|
|
2
|
+
|
|
3
|
+
Wire format (per IETF draft-miller-ssh-agent-17 §3):
|
|
4
|
+
[uint32 length][byte msg_type][payload...]
|
|
5
|
+
|
|
6
|
+
Length field = 1 (type byte) + len(payload). All multi-byte integers are big-endian.
|
|
7
|
+
|
|
8
|
+
SSH string format:
|
|
9
|
+
[uint32 length][data...]
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
import logging
|
|
15
|
+
import struct
|
|
16
|
+
from typing import TYPE_CHECKING
|
|
17
|
+
|
|
18
|
+
if TYPE_CHECKING:
|
|
19
|
+
import asyncio
|
|
20
|
+
|
|
21
|
+
logger = logging.getLogger(__name__)
|
|
22
|
+
|
|
23
|
+
_UINT32 = struct.Struct(">I")
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def pack_uint32(n: int) -> bytes:
|
|
27
|
+
return _UINT32.pack(n)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def unpack_uint32(data: bytes, offset: int) -> tuple[int, int]:
|
|
31
|
+
(value,) = _UINT32.unpack_from(data, offset)
|
|
32
|
+
return value, offset + 4
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def pack_string(data: bytes) -> bytes:
|
|
36
|
+
return _UINT32.pack(len(data)) + data
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def unpack_string(data: bytes, offset: int) -> tuple[bytes, int]:
|
|
40
|
+
length, offset = unpack_uint32(data, offset)
|
|
41
|
+
end = offset + length
|
|
42
|
+
if end > len(data):
|
|
43
|
+
raise ValueError(
|
|
44
|
+
f"SSH string at offset {offset - 4}: need {length} bytes, "
|
|
45
|
+
f"have {len(data) - offset}"
|
|
46
|
+
)
|
|
47
|
+
return data[offset:end], end
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
async def read_message(reader: asyncio.StreamReader) -> tuple[int, bytes]:
|
|
51
|
+
raw_length = await reader.readexactly(4)
|
|
52
|
+
(length,) = _UINT32.unpack(raw_length)
|
|
53
|
+
|
|
54
|
+
raw_body = await reader.readexactly(length)
|
|
55
|
+
msg_type = raw_body[0]
|
|
56
|
+
payload = raw_body[1:]
|
|
57
|
+
return msg_type, payload
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
async def write_message(
|
|
61
|
+
writer: asyncio.StreamWriter, msg_type: int, payload: bytes
|
|
62
|
+
) -> None:
|
|
63
|
+
length = 1 + len(payload)
|
|
64
|
+
header = _UINT32.pack(length) + bytes([msg_type])
|
|
65
|
+
writer.write(header + payload)
|
|
66
|
+
await writer.drain()
|