diff --git a/setup.py b/setup.py index ef53069..b456217 100644 --- a/setup.py +++ b/setup.py @@ -24,6 +24,7 @@ setup( 'Topic :: Communications', ], entry_points={'console_scripts': [ - 'trezor-agent = sshagent.__main__:trezor_agent' + 'trezor-agent = sshagent.__main__:trezor_agent', + 'trezor-verify = sshagent.__main__:trezor_verify' ]}, ) diff --git a/sshagent/__main__.py b/sshagent/__main__.py index eb3acb1..77f3baf 100644 --- a/sshagent/__main__.py +++ b/sshagent/__main__.py @@ -39,7 +39,7 @@ def identity_from_gitconfig(): return 'ssh://{0}@{1}/{2}'.format(user, host, path) -def parse_args(): +def create_parser(): p = argparse.ArgumentParser() p.add_argument('-v', '--verbose', default=0, action='count') @@ -53,18 +53,21 @@ def parse_args(): help='connect to specified host via SSH') p.add_argument('command', type=str, nargs='*', metavar='ARGUMENT', help='command to run under the SSH agent') - return p.parse_args() + return p -def trezor_agent(): - args = parse_args() - +def setup_logging(verbosity): fmt = ('%(asctime)s %(levelname)-12s %(message)-100s ' '[%(filename)s:%(lineno)d]') levels = [logging.WARNING, logging.INFO, logging.DEBUG] - level = levels[min(args.verbose, len(levels) - 1)] + level = levels[min(verbosity, len(levels) - 1)] logging.basicConfig(format=fmt, level=level) + +def trezor_agent(): + args = create_parser().parse_args() + setup_logging(verbosity=args.verbose) + with trezor.Client(factory=trezor.TrezorLibrary) as client: label = args.identity @@ -110,3 +113,17 @@ def trezor_agent(): use_shell=use_shell) except KeyboardInterrupt: log.info('server stopped') + + +def trezor_verify(): + + p = argparse.ArgumentParser() + p.add_argument('-v', '--verbose', default=0, action='count') + p.add_argument('-a', '--address', default=None) + p.add_argument('identity') + args = p.parse_args() + + setup_logging(verbosity=args.verbose) + with trezor.Client(factory=trezor.TrezorLibrary) as client: + client.sign_identity(identity=args.identity, + expected_address=args.address) diff --git a/sshagent/trezor.py b/sshagent/trezor.py index 70b4e4c..f20bd6f 100644 --- a/sshagent/trezor.py +++ b/sshagent/trezor.py @@ -2,6 +2,8 @@ import io import re import struct import binascii +import time +import os from . import util from . import formats @@ -88,6 +90,38 @@ class Client(object): s = util.bytes2num(sig[32:]) return (r, s) + def sign_identity(self, identity, expected_address=None): + visual = time.strftime('%d/%m/%y %H:%M:%S') + hidden = os.urandom(64) + identity = self.get_identity(identity) + result = self.client.sign_identity(identity=identity, + challenge_hidden=hidden, + challenge_visual=visual) + + msg = sha256sum(hidden) + sha256sum(visual) + sig = result.signature[1:] + log.debug('verifying signature for address %s', result.address) + if expected_address: + assert expected_address == result.address + + curve = formats.ecdsa.SECP256k1 + verifying_key = formats.decompress_pubkey(result.public_key, + curve=curve) + + from bitcoin import electrum_sig_hash + from bitcoin import pubkey_to_address + assert pubkey_to_address(result.public_key) == result.address + + digest = electrum_sig_hash(msg) + r = util.bytes2num(sig[:32]) + s = util.bytes2num(sig[32:]) + verifying_key.verify_digest(signature=(r, s), digest=digest, + sigdecode=lambda sig, _: sig) + + +def sha256sum(data): + return formats.hashfunc(data).digest() + _identity_regexp = re.compile(''.join([ '^'