mirror of
https://github.com/romanz/amodem.git
synced 2026-05-03 08:27:26 +08:00
Compare commits
25 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
18c80b4cca | ||
|
|
7eab4933ed | ||
|
|
d103ebee6f | ||
|
|
d8bcca3ccb | ||
|
|
67ef11419a | ||
|
|
d4d168c746 | ||
|
|
61cfcef35c | ||
|
|
0f627e8322 | ||
|
|
7bdfa7609d | ||
|
|
53b08f4968 | ||
|
|
15b0218bf2 | ||
|
|
f52e959639 | ||
|
|
d98f49445e | ||
|
|
ab6892f42f | ||
|
|
f03312d61f | ||
|
|
b75cf74976 | ||
|
|
363b4d633f | ||
|
|
b7d0ef0f94 | ||
|
|
8c3744c30c | ||
|
|
513b1259c4 | ||
|
|
5984a58f65 | ||
|
|
e437591dd5 | ||
|
|
94ad9648f8 | ||
|
|
ed64f94bd3 | ||
|
|
bf9f2593b5 |
@@ -1,7 +1,7 @@
|
||||
[bumpversion]
|
||||
commit = True
|
||||
tag = True
|
||||
current_version = 0.12.1
|
||||
current_version = 0.14.0
|
||||
|
||||
[bumpversion:file:setup.py]
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
[MESSAGES CONTROL]
|
||||
disable=invalid-name, missing-docstring, locally-disabled, unbalanced-tuple-unpacking,no-else-return,fixme,duplicate-code,cyclic-import
|
||||
disable=invalid-name, missing-docstring, locally-disabled, unbalanced-tuple-unpacking,no-else-return,fixme,duplicate-code,cyclic-import,import-outside-toplevel
|
||||
|
||||
[SIMILARITIES]
|
||||
min-similarity-lines=5
|
||||
|
||||
@@ -3,6 +3,8 @@ language: python
|
||||
python:
|
||||
- "3.5"
|
||||
- "3.6"
|
||||
- "3.7"
|
||||
- "3.8"
|
||||
|
||||
cache:
|
||||
directories:
|
||||
|
||||
16
README.md
16
README.md
@@ -16,6 +16,20 @@ See the following blog posts about this tool:
|
||||
|
||||
Currently [TREZOR One](https://trezor.io/), [TREZOR Model T](https://trezor.io/), [Keepkey](https://www.keepkey.com/), and [Ledger Nano S](https://www.ledgerwallet.com/products/ledger-nano-s) are supported.
|
||||
|
||||
## Components
|
||||
|
||||
This repository contains source code for one library as well as
|
||||
agents to interact with several different hardware devices:
|
||||
|
||||
* [`libagent`](https://pypi.org/project/libagent/): shared library
|
||||
* [`trezor-agent`](https://pypi.org/project/trezor-agent/): Using Trezor as hardware-based SSH/PGP agent
|
||||
* [`ledger_agent`](https://pypi.org/project/ledger_agent/): Using Ledger as hardware-based SSH/PGP agent
|
||||
* [`keepkey_agent`](https://pypi.org/project/keepkey_agent/): Using KeepKey as hardware-based SSH/PGP agent
|
||||
|
||||
|
||||
The [/releases](/releases) page on Github contains the `libagent`
|
||||
releases.
|
||||
|
||||
## Documentation
|
||||
|
||||
* **Installation** instructions are [here](doc/INSTALL.md)
|
||||
@@ -24,4 +38,4 @@ Currently [TREZOR One](https://trezor.io/), [TREZOR Model T](https://trezor.io/)
|
||||
Note: If you're using Windows, see [trezor-ssh-agent](https://github.com/martin-lizner/trezor-ssh-agent) by Martin Lízner.
|
||||
|
||||
* **GPG** instructions and common use cases are [here](doc/README-GPG.md)
|
||||
* Instructions to configure a Trezor-style **PIN entry** program are [here](doc/README-PINENTRY.md)
|
||||
* Instructions to configure a Trezor-style **PIN entry** program are [here](doc/README-PINENTRY.md)
|
||||
|
||||
@@ -11,7 +11,7 @@ setup(
|
||||
scripts=['trezor_agent.py'],
|
||||
install_requires=[
|
||||
'libagent>=0.13.0',
|
||||
'trezor[hidapi]>=0.11.0'
|
||||
'trezor[hidapi]>=0.12.0,<0.13'
|
||||
],
|
||||
platforms=['POSIX'],
|
||||
classifiers=[
|
||||
@@ -22,8 +22,6 @@ setup(
|
||||
'Intended Audience :: System Administrators',
|
||||
'License :: OSI Approved :: GNU Lesser General Public License v3 (LGPLv3)',
|
||||
'Operating System :: POSIX',
|
||||
'Programming Language :: Python :: 2.7',
|
||||
'Programming Language :: Python :: 3.4',
|
||||
'Programming Language :: Python :: 3.5',
|
||||
'Programming Language :: Python :: 3.6',
|
||||
'Topic :: Software Development :: Libraries :: Python Modules',
|
||||
|
||||
@@ -74,6 +74,7 @@ gpg (GnuPG) 2.1.15
|
||||
|
||||
```
|
||||
$ git clone https://github.com/romanz/trezor-agent
|
||||
$ pip3 install --user -e trezor-agent
|
||||
$ pip3 install --user -e trezor-agent/agents/trezor
|
||||
```
|
||||
|
||||
@@ -126,6 +127,7 @@ Then, install the latest [keepkey_agent](https://pypi.python.org/pypi/keepkey_ag
|
||||
|
||||
```
|
||||
$ git clone https://github.com/romanz/trezor-agent
|
||||
$ pip3 install --user -e trezor-agent
|
||||
$ pip3 install --user -e trezor-agent/agents/ledger
|
||||
```
|
||||
|
||||
|
||||
@@ -34,6 +34,11 @@ $ (trezor|keepkey|ledger)-agent identity@myhost -- COMMAND --WITH --ARGUMENTS
|
||||
to start the agent in the background and execute the command with environment variables set up to use the SSH agent. The specified identity is used for all SSH connections. The agent will exit after the command completes.
|
||||
Note the `--` separator, which is used to separate `trezor-agent`'s arguments from the SSH command arguments.
|
||||
|
||||
Example:
|
||||
```
|
||||
(trezor|keepkey|ledger)-agent -e ed25519 bob@example.com -- rsync up/ bob@example.com:/home/bob
|
||||
```
|
||||
|
||||
As a shortcut you can run
|
||||
|
||||
```
|
||||
@@ -42,7 +47,7 @@ $ (trezor|keepkey|ledger)-agent identity@myhost -s
|
||||
|
||||
to start a shell with the proper environment.
|
||||
|
||||
##### 2. Connect to a server directly via `(trezor|keepkey|ledger)-agent`
|
||||
##### 3. Connect to a server directly via `(trezor|keepkey|ledger)-agent`
|
||||
|
||||
If you just want to connect to a server this is the simplest way to do it:
|
||||
|
||||
@@ -124,6 +129,7 @@ Requires=trezor-ssh-agent.socket
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
Restart=always
|
||||
Environment="DISPLAY=:0"
|
||||
Environment="PATH=/bin:/usr/bin:/usr/local/bin:%h/.local/bin"
|
||||
ExecStart=/usr/bin/trezor-agent --foreground --sock-path %t/trezor-agent/S.ssh IDENTITY
|
||||
@@ -133,6 +139,13 @@ If you've installed `trezor-agent` locally you may have to change the path in `E
|
||||
|
||||
Replace `IDENTITY` with the identity you used when exporting the public key.
|
||||
|
||||
`IDENTITY` can be a path (starting with `/`) to a file containing a list of public keys
|
||||
generated by Trezor. I.e. `/home/myUser/.ssh/trezor.conf` with one public key per line.
|
||||
This is a more convenient way to have a systemd setup that has to handle multiple
|
||||
keys/hosts.
|
||||
|
||||
When updating the file, make sure to restart trezor-agent.
|
||||
|
||||
If you have multiple Trezors connected, you can select which one to use via a `TREZOR_PATH`
|
||||
environment variable. Use `trezorctl list` to find the correct path. Then add it
|
||||
to the agent with the following line:
|
||||
@@ -168,9 +181,13 @@ systemctl --user enable trezor-ssh-agent.socket
|
||||
##### 3. Add this line to your `.bashrc` or equivalent file:
|
||||
|
||||
```bash
|
||||
export SSH_AUTH_SOCK=$(systemctl show --user --property=Listen trezor-ssh-agent.socket | grep -o "/run.*")
|
||||
export SSH_AUTH_SOCK=$(systemctl show --user --property=Listen trezor-ssh-agent.socket | grep -o "/run.*" | cut -d " " -f 1)
|
||||
```
|
||||
|
||||
Make sure the SSH_AUTH_SOCK variable matches the location of the socket that trezor-agent
|
||||
is listening on: `ps -x | grep trezor-agent`. In this setup trezor-agent should start
|
||||
automatically when the socket is opened.
|
||||
|
||||
##### 4. SSH will now automatically use your device key in all terminals.
|
||||
|
||||
## 4. Troubleshooting
|
||||
|
||||
@@ -6,9 +6,18 @@ from keepkeylib.client import CallException, PinException
|
||||
from keepkeylib.client import KeepKeyClient as Client
|
||||
from keepkeylib.messages_pb2 import PassphraseAck, PinMatrixAck
|
||||
from keepkeylib.transport_hid import HidTransport
|
||||
from keepkeylib.transport_webusb import WebUsbTransport
|
||||
from keepkeylib.types_pb2 import IdentityType
|
||||
|
||||
get_public_node = Client.get_public_node
|
||||
sign_identity = Client.sign_identity
|
||||
Client.state = None
|
||||
|
||||
|
||||
def find_device():
|
||||
"""Returns first USB HID transport."""
|
||||
return next(HidTransport(p) for p in HidTransport.enumerate())
|
||||
"""Returns first WebUSB or HID transport."""
|
||||
for d in WebUsbTransport.enumerate():
|
||||
return WebUsbTransport(d)
|
||||
|
||||
for d in HidTransport.enumerate():
|
||||
return HidTransport(d)
|
||||
|
||||
@@ -26,7 +26,7 @@ class Trezor(interface.Device):
|
||||
required_version = '>=1.4.0'
|
||||
|
||||
ui = None # can be overridden by device's users
|
||||
cached_state = None
|
||||
cached_session_id = None
|
||||
|
||||
def _verify_version(self, connection):
|
||||
f = connection.features
|
||||
@@ -54,11 +54,14 @@ class Trezor(interface.Device):
|
||||
for _ in range(5): # Retry a few times in case of PIN failures
|
||||
connection = self._defs.Client(transport=transport,
|
||||
ui=self.ui,
|
||||
state=self.__class__.cached_state)
|
||||
session_id=self.__class__.cached_session_id)
|
||||
self._verify_version(connection)
|
||||
|
||||
try:
|
||||
connection.ping(msg='', pin_protection=True) # unlock PIN
|
||||
# unlock PIN and passphrase
|
||||
self._defs.get_address(connection,
|
||||
"Testnet",
|
||||
self._defs.PASSPHRASE_TEST_PATH)
|
||||
return connection
|
||||
except (self._defs.PinException, ValueError) as e:
|
||||
log.error('Invalid PIN: %s, retrying...', e)
|
||||
@@ -70,7 +73,7 @@ class Trezor(interface.Device):
|
||||
|
||||
def close(self):
|
||||
"""Close connection."""
|
||||
self.__class__.cached_state = self.conn.state
|
||||
self.__class__.cached_session_id = self.conn.session_id
|
||||
super().close()
|
||||
|
||||
def pubkey(self, identity, ecdh=False):
|
||||
|
||||
@@ -8,12 +8,12 @@ import mnemonic
|
||||
import semver
|
||||
import trezorlib
|
||||
|
||||
from trezorlib.client import TrezorClient as Client
|
||||
from trezorlib.client import TrezorClient as Client, PASSPHRASE_TEST_PATH
|
||||
from trezorlib.exceptions import TrezorFailure, PinException
|
||||
from trezorlib.transport import get_transport
|
||||
from trezorlib.messages import IdentityType
|
||||
|
||||
from trezorlib.btc import get_public_node
|
||||
from trezorlib.btc import get_address, get_public_node
|
||||
from trezorlib.misc import sign_identity, get_ecdh_session_key
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
@@ -4,6 +4,11 @@ import logging
|
||||
import os
|
||||
import subprocess
|
||||
|
||||
try:
|
||||
from trezorlib.client import PASSPHRASE_ON_DEVICE
|
||||
except ImportError:
|
||||
PASSPHRASE_ON_DEVICE = object()
|
||||
|
||||
from .. import util
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
@@ -23,6 +28,7 @@ class UI:
|
||||
default_pinentry)
|
||||
self.options_getter = create_default_options_getter()
|
||||
self.device_name = device_type.__name__
|
||||
self.cached_passphrase_ack = None
|
||||
|
||||
def get_pin(self, _code=None):
|
||||
"""Ask the user for (scrambled) PIN."""
|
||||
@@ -39,14 +45,27 @@ class UI:
|
||||
binary=self.pin_entry_binary,
|
||||
options=self.options_getter())
|
||||
|
||||
def get_passphrase(self):
|
||||
def get_passphrase(self, prompt='Passphrase:', available_on_device=False):
|
||||
"""Ask the user for passphrase."""
|
||||
return interact(
|
||||
title='{} passphrase'.format(self.device_name),
|
||||
prompt='Passphrase:',
|
||||
description=None,
|
||||
binary=self.passphrase_entry_binary,
|
||||
options=self.options_getter())
|
||||
passphrase = None
|
||||
if self.cached_passphrase_ack:
|
||||
passphrase = self.cached_passphrase_ack.get()
|
||||
if passphrase is None:
|
||||
env_passphrase = os.environ.get("TREZOR_PASSPHRASE")
|
||||
if env_passphrase is not None:
|
||||
passphrase = env_passphrase
|
||||
elif available_on_device:
|
||||
passphrase = PASSPHRASE_ON_DEVICE
|
||||
else:
|
||||
passphrase = interact(
|
||||
title='{} passphrase'.format(self.device_name),
|
||||
prompt=prompt,
|
||||
description=None,
|
||||
binary=self.passphrase_entry_binary,
|
||||
options=self.options_getter())
|
||||
if self.cached_passphrase_ack:
|
||||
self.cached_passphrase_ack.set(passphrase)
|
||||
return passphrase
|
||||
|
||||
def button_request(self, _code=None):
|
||||
"""Called by TrezorClient when device interaction is required."""
|
||||
|
||||
@@ -249,7 +249,7 @@ def run_agent(device_type):
|
||||
pubkey_bytes = keyring.export_public_keys(env=env)
|
||||
device_type.ui = device.ui.UI(device_type=device_type,
|
||||
config=vars(args))
|
||||
device_type.cached_passphrase_ack = util.ExpiringCache(
|
||||
device_type.ui.cached_passphrase_ack = util.ExpiringCache(
|
||||
seconds=float(args.cache_expiry_seconds))
|
||||
handler = agent.Handler(device=device_type(),
|
||||
pubkey_bytes=pubkey_bytes)
|
||||
@@ -296,7 +296,7 @@ def main(device_type):
|
||||
help='initialize hardware-based GnuPG identity')
|
||||
p.add_argument('user_id')
|
||||
p.add_argument('-e', '--ecdsa-curve', default='nist256p1')
|
||||
p.add_argument('-t', '--time', type=int, default=int(time.time()))
|
||||
p.add_argument('-t', '--time', type=int, default=0)
|
||||
p.add_argument('-v', '--verbose', default=0, action='count')
|
||||
p.add_argument('-s', '--subkey', default=False, action='store_true')
|
||||
|
||||
@@ -318,7 +318,7 @@ def main(device_type):
|
||||
|
||||
args = parser.parse_args()
|
||||
device_type.ui = device.ui.UI(device_type=device_type, config=vars(args))
|
||||
device_type.cached_passphrase_ack = util.ExpiringCache(
|
||||
device_type.ui.cached_passphrase_ack = util.ExpiringCache(
|
||||
seconds=float(args.cache_expiry_seconds))
|
||||
|
||||
return args.func(device_type=device_type, args=args)
|
||||
|
||||
@@ -118,8 +118,8 @@ class Handler:
|
||||
|
||||
def handle_get_passphrase(self, conn, _):
|
||||
"""Allow simple GPG symmetric encryption (using a passphrase)."""
|
||||
p1 = self.client.device.ui.get_passphrase('Symmetric encryption')
|
||||
p2 = self.client.device.ui.get_passphrase('Re-enter encryption')
|
||||
p1 = self.client.device.ui.get_passphrase('Symmetric encryption:')
|
||||
p2 = self.client.device.ui.get_passphrase('Re-enter encryption:')
|
||||
if p1 == p2:
|
||||
result = b'D ' + util.assuan_serialize(p1.encode('ascii'))
|
||||
keyring.sendline(conn, result, confidential=True)
|
||||
|
||||
@@ -173,9 +173,7 @@ def sign_digest(sock, keygrip, digest, sp=subprocess, environ=None):
|
||||
assert communicate(sock, 'PKSIGN') == b'OK'
|
||||
while True:
|
||||
line = recvline(sock).strip()
|
||||
if line.startswith(b'S PROGRESS'):
|
||||
continue
|
||||
else:
|
||||
if not line.startswith(b'S PROGRESS'):
|
||||
break
|
||||
line = unescape(line)
|
||||
log.debug('unescaped: %r', line)
|
||||
|
||||
@@ -78,8 +78,7 @@ def create_agent_parser(device_type):
|
||||
p.add_argument('--version', help='print the version info',
|
||||
action='version', version=versions)
|
||||
|
||||
curve_names = [name for name in formats.SUPPORTED_CURVES]
|
||||
curve_names = ', '.join(sorted(curve_names))
|
||||
curve_names = ', '.join(sorted(formats.SUPPORTED_CURVES))
|
||||
p.add_argument('-e', '--ecdsa-curve-name', metavar='CURVE',
|
||||
default=formats.CURVE_NIST256,
|
||||
help='specify ECDSA curve name: ' + curve_names)
|
||||
@@ -275,7 +274,7 @@ def main(device_type):
|
||||
|
||||
# override default PIN/passphrase entry tools (relevant for TREZOR/Keepkey):
|
||||
device_type.ui = device.ui.UI(device_type=device_type, config=vars(args))
|
||||
device_type.cached_passphrase_ack = util.ExpiringCache(
|
||||
device_type.ui.cached_passphrase_ack = util.ExpiringCache(
|
||||
args.cache_expiry_seconds)
|
||||
|
||||
conn = JustInTimeConnection(
|
||||
|
||||
@@ -242,7 +242,7 @@ def which(cmd):
|
||||
from shutil import which as _which
|
||||
except ImportError:
|
||||
# For Python 2
|
||||
from backports.shutil_which import which as _which # pylint: disable=relative-import
|
||||
from backports.shutil_which import which as _which
|
||||
full_path = _which(cmd)
|
||||
if full_path is None:
|
||||
raise OSError('Cannot find {!r} in $PATH'.format(cmd))
|
||||
|
||||
Reference in New Issue
Block a user