diff --git a/.travis.yml b/.travis.yml index 378cc95..ec83ac3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,24 +10,17 @@ cache: directories: - $HOME/.cache/pip -addons: - apt: - packages: - - libudev-dev - - libusb-1.0-0-dev - before_install: - - pip install -U setuptools pylint coverage pep8 pydocstyle "pip>=7.0" wheel google - - pip install -e git+https://github.com/keepkey/python-keepkey@6e8baa8b935e830d05f87b6dfd9bc7c927a96dc3#egg=keepkey + - pip install -U setuptools pylint coverage pep8 pydocstyle "pip>=7.0" wheel install: - pip install -e . script: - - pep8 trezor_agent - - pylint --reports=no --rcfile .pylintrc trezor_agent - - pydocstyle trezor_agent - - coverage run --source trezor_agent/ -m py.test -v + - pep8 libagent + - pylint --reports=no --rcfile .pylintrc libagent + - pydocstyle libagent + - coverage run --source libagent/ -m py.test -v after_success: - coverage report diff --git a/agents/keepkey/keepkey_agent.py b/agents/keepkey/keepkey_agent.py new file mode 100644 index 0000000..03d8ee5 --- /dev/null +++ b/agents/keepkey/keepkey_agent.py @@ -0,0 +1,5 @@ +import libagent.gpg +import libagent.ssh +from libagent.device import keepkey + +ssh_agent = lambda: libagent.ssh.main(keepkey.KeepKey) diff --git a/agents/keepkey/setup.py b/agents/keepkey/setup.py new file mode 100644 index 0000000..303c266 --- /dev/null +++ b/agents/keepkey/setup.py @@ -0,0 +1,34 @@ +#!/usr/bin/env python +from setuptools import setup + +setup( + name='keepkey_agent', + version='0.9.0', + description='Using KeepKey as hardware SSH agent', + author='Roman Zeyde', + author_email='roman.zeyde@gmail.com', + url='http://github.com/romanz/trezor-agent', + scripts=['keepkey_agent.py'], + install_requires=['libagent>=0.9.0', 'keepkey>=0.7.3'], + platforms=['POSIX'], + classifiers=[ + 'Environment :: Console', + 'Development Status :: 4 - Beta', + 'Intended Audience :: Developers', + 'Intended Audience :: Information Technology', + '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', + 'Topic :: Software Development :: Libraries :: Python Modules', + 'Topic :: System :: Networking', + 'Topic :: Communications', + 'Topic :: Security', + 'Topic :: Utilities', + ], + entry_points={'console_scripts': [ + 'keepkey-agent = keepkey_agent:ssh_agent', + ]}, +) diff --git a/agents/ledger/ledger_agent.py b/agents/ledger/ledger_agent.py new file mode 100644 index 0000000..8ba4216 --- /dev/null +++ b/agents/ledger/ledger_agent.py @@ -0,0 +1,7 @@ +import libagent.gpg +import libagent.ssh +from libagent.device.ledger import LedgerNanoS as DeviceType + +ssh_agent = lambda: libagent.ssh.main(DeviceType) +gpg_tool = lambda: libagent.gpg.main(DeviceType) +gpg_agent = lambda: libagent.gpg.run_agent(DeviceType) diff --git a/agents/ledger/setup.py b/agents/ledger/setup.py new file mode 100644 index 0000000..7d8baf6 --- /dev/null +++ b/agents/ledger/setup.py @@ -0,0 +1,36 @@ +#!/usr/bin/env python +from setuptools import setup + +setup( + name='ledger_agent', + version='0.9.0', + description='Using Ledger as hardware SSH agent', + author='Roman Zeyde', + author_email='roman.zeyde@gmail.com', + url='http://github.com/romanz/trezor-agent', + scripts=['ledger_agent.py'], + install_requires=['libagent>=0.9.0', 'ledgerblue>=0.1.8'], + platforms=['POSIX'], + classifiers=[ + 'Environment :: Console', + 'Development Status :: 4 - Beta', + 'Intended Audience :: Developers', + 'Intended Audience :: Information Technology', + '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', + 'Topic :: Software Development :: Libraries :: Python Modules', + 'Topic :: System :: Networking', + 'Topic :: Communications', + 'Topic :: Security', + 'Topic :: Utilities', + ], + entry_points={'console_scripts': [ + 'ledger-agent = ledger_agent:ssh_agent', + 'ledger-gpg = ledger_agent:gpg_tool', + 'ledger-gpg-agent = ledger_agent:gpg_agent', + ]}, +) diff --git a/agents/trezor/setup.py b/agents/trezor/setup.py new file mode 100644 index 0000000..433796a --- /dev/null +++ b/agents/trezor/setup.py @@ -0,0 +1,36 @@ +#!/usr/bin/env python +from setuptools import setup + +setup( + name='trezor_agent', + version='0.9.0', + description='Using Trezor as hardware SSH agent', + author='Roman Zeyde', + author_email='roman.zeyde@gmail.com', + url='http://github.com/romanz/trezor-agent', + scripts=['trezor_agent.py'], + install_requires=['libagent>=0.9.0', 'trezor>=0.7.6'], + platforms=['POSIX'], + classifiers=[ + 'Environment :: Console', + 'Development Status :: 4 - Beta', + 'Intended Audience :: Developers', + 'Intended Audience :: Information Technology', + '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', + 'Topic :: Software Development :: Libraries :: Python Modules', + 'Topic :: System :: Networking', + 'Topic :: Communications', + 'Topic :: Security', + 'Topic :: Utilities', + ], + entry_points={'console_scripts': [ + 'trezor-agent = trezor_agent:ssh_agent', + 'trezor-gpg = trezor_agent:gpg_tool', + 'trezor-gpg-agent = trezor_agent:gpg_agent', + ]}, +) diff --git a/agents/trezor/trezor_agent.py b/agents/trezor/trezor_agent.py new file mode 100644 index 0000000..23628c4 --- /dev/null +++ b/agents/trezor/trezor_agent.py @@ -0,0 +1,7 @@ +import libagent.gpg +import libagent.ssh +from libagent.device.trezor import Trezor as DeviceType + +ssh_agent = lambda: libagent.ssh.main(DeviceType) +gpg_tool = lambda: libagent.gpg.main(DeviceType) +gpg_agent = lambda: libagent.gpg.run_agent(DeviceType) diff --git a/trezor_agent/__init__.py b/libagent/__init__.py similarity index 100% rename from trezor_agent/__init__.py rename to libagent/__init__.py diff --git a/trezor_agent/client.py b/libagent/client.py similarity index 100% rename from trezor_agent/client.py rename to libagent/client.py diff --git a/libagent/device/__init__.py b/libagent/device/__init__.py new file mode 100644 index 0000000..eb320c4 --- /dev/null +++ b/libagent/device/__init__.py @@ -0,0 +1,3 @@ +"""Cryptographic hardware device management.""" + +from . import interface diff --git a/trezor_agent/device/interface.py b/libagent/device/interface.py similarity index 100% rename from trezor_agent/device/interface.py rename to libagent/device/interface.py diff --git a/trezor_agent/device/keepkey.py b/libagent/device/keepkey.py similarity index 100% rename from trezor_agent/device/keepkey.py rename to libagent/device/keepkey.py diff --git a/trezor_agent/device/keepkey_defs.py b/libagent/device/keepkey_defs.py similarity index 87% rename from trezor_agent/device/keepkey_defs.py rename to libagent/device/keepkey_defs.py index 1a057ef..68802af 100644 --- a/trezor_agent/device/keepkey_defs.py +++ b/libagent/device/keepkey_defs.py @@ -1,6 +1,6 @@ """KeepKey-related definitions.""" -# pylint: disable=unused-import +# pylint: disable=unused-import,import-error from keepkeylib.client import CallException as Error from keepkeylib.client import KeepKeyClient as Client diff --git a/trezor_agent/device/ledger.py b/libagent/device/ledger.py similarity index 98% rename from trezor_agent/device/ledger.py rename to libagent/device/ledger.py index e54c83d..b2eceb4 100644 --- a/trezor_agent/device/ledger.py +++ b/libagent/device/ledger.py @@ -4,7 +4,7 @@ import binascii import logging import struct -from ledgerblue import comm +from ledgerblue import comm # pylint: disable=import-error from . import interface diff --git a/trezor_agent/device/trezor.py b/libagent/device/trezor.py similarity index 100% rename from trezor_agent/device/trezor.py rename to libagent/device/trezor.py diff --git a/trezor_agent/device/trezor_defs.py b/libagent/device/trezor_defs.py similarity index 88% rename from trezor_agent/device/trezor_defs.py rename to libagent/device/trezor_defs.py index 32406c7..6bf4e0e 100644 --- a/trezor_agent/device/trezor_defs.py +++ b/libagent/device/trezor_defs.py @@ -1,6 +1,6 @@ """TREZOR-related definitions.""" -# pylint: disable=unused-import +# pylint: disable=unused-import,import-error from trezorlib.client import CallException as Error from trezorlib.client import TrezorClient as Client diff --git a/trezor_agent/formats.py b/libagent/formats.py similarity index 100% rename from trezor_agent/formats.py rename to libagent/formats.py diff --git a/trezor_agent/gpg/__main__.py b/libagent/gpg/__init__.py old mode 100755 new mode 100644 similarity index 76% rename from trezor_agent/gpg/__main__.py rename to libagent/gpg/__init__.py index e1cceb0..3d4bbde --- a/trezor_agent/gpg/__main__.py +++ b/libagent/gpg/__init__.py @@ -1,5 +1,13 @@ -#!/usr/bin/env python -"""Create signatures and export public keys for GPG using TREZOR.""" +""" +TREZOR support for ECDSA GPG signatures. + +See these links for more details: + - https://www.gnupg.org/faq/whats-new-in-2.1.html + - https://tools.ietf.org/html/rfc4880 + - https://tools.ietf.org/html/rfc6637 + - https://tools.ietf.org/html/draft-irtf-cfrg-eddsa-05 +""" + import argparse import contextlib import logging @@ -15,14 +23,15 @@ from .. import device, formats, server, util log = logging.getLogger(__name__) -def export_public_key(args): +def export_public_key(device_type, args): """Generate a new pubkey for a new/existing GPG identity.""" log.warning('NOTE: in order to re-generate the exact same GPG key later, ' 'run this command with "--time=%d" commandline flag (to set ' 'the timestamp of the GPG key manually).', args.time) - d = client.Client(user_id=args.user_id, curve_name=args.ecdsa_curve) - verifying_key = d.pubkey(ecdh=False) - decryption_key = d.pubkey(ecdh=True) + c = client.Client(user_id=args.user_id, curve_name=args.ecdsa_curve, + device_type=device_type) + verifying_key = c.pubkey(ecdh=False) + decryption_key = c.pubkey(ecdh=True) if args.subkey: # add as subkey log.info('adding %s GPG subkey for "%s" to existing key', @@ -38,10 +47,10 @@ def export_public_key(args): primary_bytes = keyring.export_public_key(args.user_id) result = encode.create_subkey(primary_bytes=primary_bytes, subkey=signing_key, - signer_func=d.sign) + signer_func=c.sign) result = encode.create_subkey(primary_bytes=result, subkey=encryption_key, - signer_func=d.sign) + signer_func=c.sign) else: # add as primary log.info('creating new %s GPG primary key for "%s"', args.ecdsa_curve, args.user_id) @@ -56,15 +65,15 @@ def export_public_key(args): result = encode.create_primary(user_id=args.user_id, pubkey=primary, - signer_func=d.sign) + signer_func=c.sign) result = encode.create_subkey(primary_bytes=result, subkey=subkey, - signer_func=d.sign) + signer_func=c.sign) sys.stdout.write(protocol.armor(result, 'PUBLIC KEY BLOCK')) -def run_create(args): +def run_create(device_type, args): """Export public GPG key.""" util.setup_logging(verbosity=args.verbose) log.warning('This GPG tool is still in EXPERIMENTAL mode, ' @@ -74,26 +83,27 @@ def run_create(args): existing_gpg = keyring.gpg_version().decode('ascii') required_gpg = '>=2.1.11' if semver.match(existing_gpg, required_gpg): - export_public_key(args) + export_public_key(device_type, args) else: log.error('Existing gpg2 has version "%s" (%s required)', existing_gpg, required_gpg) -def run_unlock(args): +def run_unlock(device_type, args): """Unlock hardware device (for future interaction).""" util.setup_logging(verbosity=args.verbose) - d = device.detect() - log.info('unlocked %s device', d) + with device_type() as d: + log.info('unlocked %s device', d) -def run_agent(_): +def run_agent(device_type): """Run a simple GPG-agent server.""" - home_dir = os.environ.get('GNUPGHOME', os.path.expanduser('~/.gnupg/trezor')) - config_file = os.path.join(home_dir, 'gpg-agent.conf') - if not os.path.exists(config_file): - msg = 'No configuration file found: {}'.format(config_file) - raise IOError(msg) + parser = argparse.ArgumentParser() + parser.add_argument('--homedir', default=os.environ.get('GNUPGHOME')) + args, _ = parser.parse_known_args() + + assert args.homedir + config_file = os.path.join(args.homedir, 'gpg-agent.conf') lines = (line.strip() for line in open(config_file)) lines = (line for line in lines if line and not line.startswith('#')) @@ -106,7 +116,7 @@ def run_agent(_): for conn in agent.yield_connections(sock): with contextlib.closing(conn): try: - agent.handle_connection(conn) + agent.handle_connection(conn=conn, device_type=device_type) except StopIteration: log.info('stopping gpg-agent') return @@ -114,14 +124,11 @@ def run_agent(_): log.exception('gpg-agent failed: %s', e) -def main(): +def main(device_type): """Parse command-line arguments.""" parser = argparse.ArgumentParser() subparsers = parser.add_subparsers() - p = subparsers.add_parser('agent', help='Run GPG agent using a hardware device') - p.set_defaults(func=run_agent) - p = subparsers.add_parser('create', help='Export public GPG key') p.add_argument('user_id') p.add_argument('-e', '--ecdsa-curve', default='nist256p1') @@ -135,8 +142,4 @@ def main(): p.set_defaults(func=run_unlock) args = parser.parse_args() - return args.func(args) - - -if __name__ == '__main__': - main() + return args.func(device_type=device_type, args=args) diff --git a/trezor_agent/gpg/agent.py b/libagent/gpg/agent.py similarity index 88% rename from trezor_agent/gpg/agent.py rename to libagent/gpg/agent.py index 662e4eb..f4aa724 100644 --- a/trezor_agent/gpg/agent.py +++ b/libagent/gpg/agent.py @@ -36,7 +36,7 @@ def sig_encode(r, s): return b'(7:sig-val(5:ecdsa(1:r32:' + r + b')(1:s32:' + s + b')))' -def open_connection(keygrip_bytes): +def open_connection(keygrip_bytes, device_type): """ Connect to the device for the specified keygrip. @@ -51,7 +51,7 @@ def open_connection(keygrip_bytes): curve_name = protocol.get_curve_name_by_oid(pubkey_dict['curve_oid']) ecdh = (pubkey_dict['algo'] == protocol.ECDH_ALGO_ID) - conn = client.Client(user_id, curve_name=curve_name) + conn = client.Client(user_id, curve_name=curve_name, device_type=device_type) pubkey = protocol.PublicKey( curve_name=curve_name, created=pubkey_dict['created'], verifying_key=conn.pubkey(ecdh=ecdh), ecdh=ecdh) @@ -60,11 +60,11 @@ def open_connection(keygrip_bytes): return conn -def pksign(keygrip, digest, algo): +def pksign(keygrip, digest, algo, device_type): """Sign a message digest using a private EC key.""" log.debug('signing %r digest (algo #%s)', digest, algo) keygrip_bytes = binascii.unhexlify(keygrip) - conn = open_connection(keygrip_bytes) + conn = open_connection(keygrip_bytes, device_type=device_type) r, s = conn.sign(binascii.unhexlify(digest)) result = sig_encode(r, s) log.debug('result: %r', result) @@ -92,7 +92,7 @@ def parse_ecdh(line): return dict(items)[b'e'] -def pkdecrypt(keygrip, conn): +def pkdecrypt(keygrip, conn, device_type): """Handle decryption using ECDH.""" for msg in [b'S INQUIRE_MAXLEN 4096', b'INQUIRE CIPHERTEXT']: keyring.sendline(conn, msg) @@ -102,15 +102,16 @@ def pkdecrypt(keygrip, conn): remote_pubkey = parse_ecdh(line) keygrip_bytes = binascii.unhexlify(keygrip) - conn = open_connection(keygrip_bytes) + conn = open_connection(keygrip_bytes, device_type=device_type) return _serialize_point(conn.ecdh(remote_pubkey)) @util.memoize -def have_key(keygrip): +def have_key(keygrip, device_type): """Check if current keygrip correspond to a TREZOR-based key.""" try: - open_connection(keygrip_bytes=binascii.unhexlify(keygrip)) + open_connection(keygrip_bytes=binascii.unhexlify(keygrip), + device_type=device_type) return True except KeyError as e: log.warning('HAVEKEY(%s) failed: %s', keygrip, e) @@ -118,7 +119,7 @@ def have_key(keygrip): # pylint: disable=too-many-branches -def handle_connection(conn): +def handle_connection(conn, device_type): """Handle connection from GPG binary using the ASSUAN protocol.""" keygrip = None digest = None @@ -141,13 +142,13 @@ def handle_connection(conn): elif command == b'SETHASH': algo, digest = args elif command == b'PKSIGN': - sig = pksign(keygrip, digest, algo) + sig = pksign(keygrip, digest, algo, device_type=device_type) keyring.sendline(conn, b'D ' + sig) elif command == b'PKDECRYPT': - sec = pkdecrypt(keygrip, conn) + sec = pkdecrypt(keygrip, conn, device_type=device_type) keyring.sendline(conn, b'D ' + sec) elif command == b'HAVEKEY': - if not have_key(keygrip=args[0]): + if not have_key(keygrip=args[0], device_type=device_type): keyring.sendline(conn, b'ERR 67108881 No secret key ') return diff --git a/trezor_agent/gpg/client.py b/libagent/gpg/client.py similarity index 94% rename from trezor_agent/gpg/client.py rename to libagent/gpg/client.py index e585d9c..80ad50d 100644 --- a/trezor_agent/gpg/client.py +++ b/libagent/gpg/client.py @@ -10,9 +10,9 @@ log = logging.getLogger(__name__) class Client(object): """Sign messages and get public keys from a hardware device.""" - def __init__(self, user_id, curve_name): + def __init__(self, user_id, curve_name, device_type): """Connect to the device and retrieve required public key.""" - self.device = device.detect() + self.device = device_type() self.user_id = user_id self.identity = device.interface.Identity( identity_str='gpg://', curve_name=curve_name) diff --git a/trezor_agent/gpg/decode.py b/libagent/gpg/decode.py similarity index 100% rename from trezor_agent/gpg/decode.py rename to libagent/gpg/decode.py diff --git a/trezor_agent/gpg/encode.py b/libagent/gpg/encode.py similarity index 100% rename from trezor_agent/gpg/encode.py rename to libagent/gpg/encode.py diff --git a/trezor_agent/gpg/keyring.py b/libagent/gpg/keyring.py similarity index 100% rename from trezor_agent/gpg/keyring.py rename to libagent/gpg/keyring.py diff --git a/trezor_agent/gpg/protocol.py b/libagent/gpg/protocol.py similarity index 100% rename from trezor_agent/gpg/protocol.py rename to libagent/gpg/protocol.py diff --git a/trezor_agent/gpg/tests/088F8EB2D57AF4D64C40A5EA90AC201D7BFE5D13.gpg b/libagent/gpg/tests/088F8EB2D57AF4D64C40A5EA90AC201D7BFE5D13.gpg similarity index 100% rename from trezor_agent/gpg/tests/088F8EB2D57AF4D64C40A5EA90AC201D7BFE5D13.gpg rename to libagent/gpg/tests/088F8EB2D57AF4D64C40A5EA90AC201D7BFE5D13.gpg diff --git a/trezor_agent/gpg/tests/114D3A028A34F56550D403F6DD9DAA354E9AAB78.gpg b/libagent/gpg/tests/114D3A028A34F56550D403F6DD9DAA354E9AAB78.gpg similarity index 100% rename from trezor_agent/gpg/tests/114D3A028A34F56550D403F6DD9DAA354E9AAB78.gpg rename to libagent/gpg/tests/114D3A028A34F56550D403F6DD9DAA354E9AAB78.gpg diff --git a/trezor_agent/gpg/tests/181D005503DBE3ADC43D142D6FC4ECF01E42B367.gpg b/libagent/gpg/tests/181D005503DBE3ADC43D142D6FC4ECF01E42B367.gpg similarity index 100% rename from trezor_agent/gpg/tests/181D005503DBE3ADC43D142D6FC4ECF01E42B367.gpg rename to libagent/gpg/tests/181D005503DBE3ADC43D142D6FC4ECF01E42B367.gpg diff --git a/trezor_agent/gpg/tests/71B5A80A63FE12B0D74DABBFE4A883364AAF6E16.gpg b/libagent/gpg/tests/71B5A80A63FE12B0D74DABBFE4A883364AAF6E16.gpg similarity index 100% rename from trezor_agent/gpg/tests/71B5A80A63FE12B0D74DABBFE4A883364AAF6E16.gpg rename to libagent/gpg/tests/71B5A80A63FE12B0D74DABBFE4A883364AAF6E16.gpg diff --git a/trezor_agent/gpg/tests/77E9D99CBB9B4C961370BAF9AD4DD89F17138874.gpg b/libagent/gpg/tests/77E9D99CBB9B4C961370BAF9AD4DD89F17138874.gpg similarity index 100% rename from trezor_agent/gpg/tests/77E9D99CBB9B4C961370BAF9AD4DD89F17138874.gpg rename to libagent/gpg/tests/77E9D99CBB9B4C961370BAF9AD4DD89F17138874.gpg diff --git a/trezor_agent/gpg/tests/80615870F5BAD690333686D0F2AD85AC1E42B367.gpg b/libagent/gpg/tests/80615870F5BAD690333686D0F2AD85AC1E42B367.gpg similarity index 100% rename from trezor_agent/gpg/tests/80615870F5BAD690333686D0F2AD85AC1E42B367.gpg rename to libagent/gpg/tests/80615870F5BAD690333686D0F2AD85AC1E42B367.gpg diff --git a/trezor_agent/gpg/tests/86E6792FC27BFD478860C11091F3B339B9A02A3D.gpg b/libagent/gpg/tests/86E6792FC27BFD478860C11091F3B339B9A02A3D.gpg similarity index 100% rename from trezor_agent/gpg/tests/86E6792FC27BFD478860C11091F3B339B9A02A3D.gpg rename to libagent/gpg/tests/86E6792FC27BFD478860C11091F3B339B9A02A3D.gpg diff --git a/trezor_agent/gpg/tests/A4EB142E5FC2C898BAEC2C9B2BA8930D2B320C62.gpg b/libagent/gpg/tests/A4EB142E5FC2C898BAEC2C9B2BA8930D2B320C62.gpg similarity index 100% rename from trezor_agent/gpg/tests/A4EB142E5FC2C898BAEC2C9B2BA8930D2B320C62.gpg rename to libagent/gpg/tests/A4EB142E5FC2C898BAEC2C9B2BA8930D2B320C62.gpg diff --git a/trezor_agent/gpg/tests/ABAF11C65A2970B130ABE3C479BE3E4300411886.gpg b/libagent/gpg/tests/ABAF11C65A2970B130ABE3C479BE3E4300411886.gpg similarity index 100% rename from trezor_agent/gpg/tests/ABAF11C65A2970B130ABE3C479BE3E4300411886.gpg rename to libagent/gpg/tests/ABAF11C65A2970B130ABE3C479BE3E4300411886.gpg diff --git a/trezor_agent/gpg/tests/__init__.py b/libagent/gpg/tests/__init__.py similarity index 100% rename from trezor_agent/gpg/tests/__init__.py rename to libagent/gpg/tests/__init__.py diff --git a/trezor_agent/gpg/tests/test_decode.py b/libagent/gpg/tests/test_decode.py similarity index 100% rename from trezor_agent/gpg/tests/test_decode.py rename to libagent/gpg/tests/test_decode.py diff --git a/trezor_agent/gpg/tests/test_keyring.py b/libagent/gpg/tests/test_keyring.py similarity index 100% rename from trezor_agent/gpg/tests/test_keyring.py rename to libagent/gpg/tests/test_keyring.py diff --git a/trezor_agent/gpg/tests/test_protocol.py b/libagent/gpg/tests/test_protocol.py similarity index 100% rename from trezor_agent/gpg/tests/test_protocol.py rename to libagent/gpg/tests/test_protocol.py diff --git a/trezor_agent/protocol.py b/libagent/protocol.py similarity index 100% rename from trezor_agent/protocol.py rename to libagent/protocol.py diff --git a/trezor_agent/server.py b/libagent/server.py similarity index 100% rename from trezor_agent/server.py rename to libagent/server.py diff --git a/trezor_agent/__main__.py b/libagent/ssh/__init__.py similarity index 97% rename from trezor_agent/__main__.py rename to libagent/ssh/__init__.py index 4b31e02..8122058 100644 --- a/trezor_agent/__main__.py +++ b/libagent/ssh/__init__.py @@ -7,7 +7,7 @@ import re import subprocess import sys -from . import client, device, formats, protocol, server, util +from .. import client, device, formats, protocol, server, util log = logging.getLogger(__name__) @@ -169,7 +169,7 @@ class JustInTimeConnection(object): @handle_connection_error -def run_agent(client_factory=client.Client): +def main(device_type): """Run ssh-agent using given hardware client factory.""" args = create_agent_parser().parse_args() util.setup_logging(verbosity=args.verbose) @@ -195,7 +195,7 @@ def run_agent(client_factory=client.Client): command = os.environ['SHELL'] conn = JustInTimeConnection( - conn_factory=lambda: client_factory(device.detect()), + conn_factory=lambda: client.Client(device_type()), identities=identities) if command: return run_server(conn=conn, command=command, debug=args.debug, diff --git a/trezor_agent/tests/__init__.py b/libagent/tests/__init__.py similarity index 100% rename from trezor_agent/tests/__init__.py rename to libagent/tests/__init__.py diff --git a/trezor_agent/tests/test_client.py b/libagent/tests/test_client.py similarity index 100% rename from trezor_agent/tests/test_client.py rename to libagent/tests/test_client.py diff --git a/trezor_agent/tests/test_formats.py b/libagent/tests/test_formats.py similarity index 100% rename from trezor_agent/tests/test_formats.py rename to libagent/tests/test_formats.py diff --git a/trezor_agent/tests/test_protocol.py b/libagent/tests/test_protocol.py similarity index 100% rename from trezor_agent/tests/test_protocol.py rename to libagent/tests/test_protocol.py diff --git a/trezor_agent/tests/test_server.py b/libagent/tests/test_server.py similarity index 100% rename from trezor_agent/tests/test_server.py rename to libagent/tests/test_server.py diff --git a/trezor_agent/tests/test_util.py b/libagent/tests/test_util.py similarity index 100% rename from trezor_agent/tests/test_util.py rename to libagent/tests/test_util.py diff --git a/trezor_agent/util.py b/libagent/util.py similarity index 100% rename from trezor_agent/util.py rename to libagent/util.py diff --git a/scripts/gpg-agent b/scripts/gpg-agent deleted file mode 100755 index 0ea74e7..0000000 --- a/scripts/gpg-agent +++ /dev/null @@ -1,2 +0,0 @@ -#!/bin/bash -trezor-gpg agent diff --git a/scripts/gpg-init b/scripts/gpg-init index 83496d2..2941d84 100755 --- a/scripts/gpg-init +++ b/scripts/gpg-init @@ -4,31 +4,52 @@ set -eu gpg2 --version >/dev/null # verify that GnuPG 2 is installed USER_ID="${1}" -HOMEDIR=~/.gnupg/trezor +DEVICE=${DEVICE:="trezor"} # or "ledger" CURVE=${CURVE:="nist256p1"} # or "ed25519" TIMESTAMP=${TIMESTAMP:=`date +%s`} # key creation timestamp +HOMEDIR=~/.gnupg/${DEVICE} -# Prepare new GPG home directory for TREZOR-based identity +# Prepare new GPG home directory for hardware-based identity rm -rf "${HOMEDIR}" mkdir -p "${HOMEDIR}" chmod 700 "${HOMEDIR}" # Generate new GPG identity and import into GPG keyring -trezor-gpg create -v "${USER_ID}" -t "${TIMESTAMP}" -e "${CURVE}" > "${HOMEDIR}/pubkey.asc" -gpg2 --homedir "${HOMEDIR}" --import < "${HOMEDIR}/pubkey.asc" +$DEVICE-gpg create -v "${USER_ID}" -t "${TIMESTAMP}" -e "${CURVE}" > "${HOMEDIR}/pubkey.asc" +gpg2 --homedir "${HOMEDIR}" --import < "${HOMEDIR}/pubkey.asc" 2> /dev/null rm -f "${HOMEDIR}/S.gpg-agent" # (otherwise, our agent won't be started automatically) # Make new GPG identity with "ultimate" trust (via its fingerprint) FINGERPRINT=$(gpg2 --homedir "${HOMEDIR}" --list-public-keys --with-fingerprint --with-colons | sed -n -E 's/^fpr:::::::::([0-9A-F]+):$/\1/p' | head -n1) -echo "${FINGERPRINT}:6" | gpg2 --homedir "${HOMEDIR}" --import-ownertrust +echo "${FINGERPRINT}:6" | gpg2 --homedir "${HOMEDIR}" --import-ownertrust 2> /dev/null + +AGENT_PATH="$(which ${DEVICE}-gpg-agent)" # Prepare GPG configuration file -echo "# TREZOR-based GPG configuration -agent-program $(dirname ${0})/gpg-agent +echo "# Hardware-based GPG configuration +agent-program ${AGENT_PATH} personal-digest-preferences SHA512 -" | tee "${HOMEDIR}/gpg.conf" +" > "${HOMEDIR}/gpg.conf" -echo "# TREZOR-based GPG agent emulator +# Prepare GPG agent configuration file +echo "# Hardware-based GPG agent emulator log-file ${HOMEDIR}/gpg-agent.log verbosity 2 -" | tee "${HOMEDIR}/gpg-agent.conf" +" > "${HOMEDIR}/gpg-agent.conf" + +# Prepare a helper script for setting up the new identity +echo "#!/bin/bash +set -eu +export GNUPGHOME=${HOMEDIR} +COMMAND=\$* +if [ -z \"\${COMMAND}\" ] +then + \${SHELL} +else + \${COMMAND} +fi +" > "${HOMEDIR}/env" +chmod u+x "${HOMEDIR}/env" + +# Load agent and make sure it responds with the new identity +GNUPGHOME="$HOMEDIR" gpg2 -K 2> /dev/null diff --git a/scripts/gpg-shell b/scripts/gpg-shell deleted file mode 100755 index b4db164..0000000 --- a/scripts/gpg-shell +++ /dev/null @@ -1,28 +0,0 @@ -#!/bin/bash -set -eu - -gpg2 --version >/dev/null # verify that GnuPG 2 is installed - -export GNUPGHOME=~/.gnupg/trezor - -CONFIG_PATH="${GNUPGHOME}/gpg-agent.conf" -if [ ! -f ${CONFIG_PATH} ] -then - echo "No configuration found: ${CONFIG_PATH}" - exit 1 -fi - -# Make sure that the device is unlocked before starting the shell -trezor-gpg unlock - -# Make sure TREZOR-based gpg-agent is running -gpg-connect-agent --agent-program "$(dirname $0)/gpg-agent" =0.13', 'ed25519>=1.4', 'Cython>=0.23.4', 'protobuf>=3.0.0', 'semver>=2.2', - 'trezor>=0.7.6', 'keepkey>=0.7.3', 'ledgerblue>=0.1.8', - 'hidapi==0.7.99.post15' # until https://github.com/keepkey/python-keepkey/pull/8 is merged - ], + packages=['libagent', 'libagent.device', 'libagent.gpg', 'libagent.ssh'], + install_requires=['ecdsa>=0.13', 'ed25519>=1.4', 'semver>=2.2'], platforms=['POSIX'], classifiers=[ 'Environment :: Console', @@ -32,8 +28,4 @@ setup( 'Topic :: Security', 'Topic :: Utilities', ], - entry_points={'console_scripts': [ - 'trezor-agent = trezor_agent.__main__:run_agent', - 'trezor-gpg = trezor_agent.gpg.__main__:main', - ]}, ) diff --git a/tox.ini b/tox.ini index 270aea9..00975ec 100644 --- a/tox.ini +++ b/tox.ini @@ -15,10 +15,10 @@ deps= pydocstyle isort commands= - pep8 trezor_agent - isort --skip-glob .tox -c -r trezor_agent - pylint --reports=no --rcfile .pylintrc trezor_agent - pydocstyle trezor_agent - coverage run --omit='trezor_agent/__main__.py' --source trezor_agent -m py.test -v trezor_agent + pep8 libagent + isort --skip-glob .tox -c -r libagent + pylint --reports=no --rcfile .pylintrc libagent + pydocstyle libagent + coverage run --source libagent -m py.test -v libagent coverage report coverage html diff --git a/trezor_agent/device/__init__.py b/trezor_agent/device/__init__.py deleted file mode 100644 index 613c1cf..0000000 --- a/trezor_agent/device/__init__.py +++ /dev/null @@ -1,27 +0,0 @@ -"""Cryptographic hardware device management.""" - -import logging - -from . import trezor -from . import keepkey -from . import ledger -from . import interface - -log = logging.getLogger(__name__) - -DEVICE_TYPES = [ - trezor.Trezor, - keepkey.KeepKey, - ledger.LedgerNanoS, -] - - -def detect(): - """Detect the first available device and return it to the user.""" - for device_type in DEVICE_TYPES: - try: - with device_type() as d: - return d - except interface.NotFoundError as e: - log.debug('device not found: %s', e) - raise IOError('No device found!') diff --git a/trezor_agent/gpg/__init__.py b/trezor_agent/gpg/__init__.py deleted file mode 100644 index f0448b4..0000000 --- a/trezor_agent/gpg/__init__.py +++ /dev/null @@ -1,9 +0,0 @@ -""" -TREZOR support for ECDSA GPG signatures. - -See these links for more details: - - https://www.gnupg.org/faq/whats-new-in-2.1.html - - https://tools.ietf.org/html/rfc4880 - - https://tools.ietf.org/html/rfc6637 - - https://tools.ietf.org/html/draft-irtf-cfrg-eddsa-05 -"""