diff --git a/agents/trezor/setup.py b/agents/trezor/setup.py index 2930fa6..95700b2 100644 --- a/agents/trezor/setup.py +++ b/agents/trezor/setup.py @@ -36,5 +36,6 @@ setup( 'trezor-agent = trezor_agent:ssh_agent', 'trezor-gpg = trezor_agent:gpg_tool', 'trezor-gpg-agent = trezor_agent:gpg_agent', + 'trezor-signify = trezor_agent:signify_tool', ]}, ) diff --git a/agents/trezor/trezor_agent.py b/agents/trezor/trezor_agent.py index 23628c4..c6fa935 100644 --- a/agents/trezor/trezor_agent.py +++ b/agents/trezor/trezor_agent.py @@ -1,7 +1,7 @@ -import libagent.gpg -import libagent.ssh +from libagent import signify, gpg, 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) +ssh_agent = lambda: ssh.main(DeviceType) +gpg_tool = lambda: gpg.main(DeviceType) +gpg_agent = lambda: gpg.run_agent(DeviceType) +signify_tool = lambda: signify.main(DeviceType) diff --git a/libagent/device/trezor.py b/libagent/device/trezor.py index f380a0d..81104f3 100644 --- a/libagent/device/trezor.py +++ b/libagent/device/trezor.py @@ -98,6 +98,11 @@ class Trezor(interface.Device): return result def sign(self, identity, blob): + """Sign given blob and return the signature (as bytes).""" + sig, _ = self.sign_with_pubkey(identity, blob) + return sig + + def sign_with_pubkey(self, identity, blob): """Sign given blob and return the signature (as bytes).""" curve_name = identity.get_curve_name(ecdh=False) log.debug('"%s" signing %r (%s) on %s', @@ -112,7 +117,7 @@ class Trezor(interface.Device): log.debug('result: %s', result) assert len(result.signature) == 65 assert result.signature[:1] == b'\x00' - return bytes(result.signature[1:]) + return bytes(result.signature[1:]), bytes(result.public_key) except self._defs.TrezorFailure as e: msg = '{} error: {}'.format(self, e) log.debug(msg, exc_info=True) diff --git a/libagent/signify/__init__.py b/libagent/signify/__init__.py new file mode 100644 index 0000000..faf4d3a --- /dev/null +++ b/libagent/signify/__init__.py @@ -0,0 +1,110 @@ +"""TREZOR support for Ed25519 signify signatures.""" + +import argparse +import binascii +import contextlib +import functools +import hashlib +import logging +import os +import re +import struct +import subprocess +import sys +import time + +import pkg_resources +import semver + + +from .. import formats, server, util +from ..device import interface, ui + +log = logging.getLogger(__name__) + + +def _create_identity(user_id): + result = interface.Identity(identity_str='signify://', curve_name='ed25519') + result.identity_dict['host'] = user_id + return result + + +class Client: + """Sign messages and get public keys from a hardware device.""" + + def __init__(self, device): + """C-tor.""" + self.device = device + + def pubkey(self, identity): + """Return public key as VerifyingKey object.""" + with self.device: + return bytes(self.device.pubkey(ecdh=False, identity=identity)) + + def sign_with_pubkey(self, identity, data): + """Sign the data and return a signature.""" + log.info('please confirm Signify signature on %s for "%s"...', + self.device, identity.to_string()) + log.debug('signing data: %s', util.hexlify(data)) + with self.device: + sig, pubkey = self.device.sign_with_pubkey(blob=data, identity=identity) + assert len(sig) == 64 + assert len(pubkey) == 33 + assert pubkey[:1] == b"\x00" + return sig, pubkey[1:] + + +def format_payload(pubkey, data): + """See http://www.openbsd.org/papers/bsdcan-signify.html for details.""" + keynum = hashlib.sha256(pubkey).digest()[:8] + return binascii.b2a_base64(b"Ed" + keynum + data).decode("ascii") + + +def run_pubkey(device_type, args): + """Export hardware-based Signify public key.""" + util.setup_logging(verbosity=args.verbose) + log.warning('This Signify tool is still in EXPERIMENTAL mode, ' + 'so please note that the key derivation, API, and features ' + 'may change without backwards compatibility!') + + identity = _create_identity(user_id=args.user_id) + pubkey = Client(device=device_type()).pubkey(identity=identity) + comment = f'untrusted comment: identity {identity.to_string()}\n' + result = comment + format_payload(pubkey=pubkey, data=pubkey) + print(result, end="") + + +def run_sign(device_type, args): + """Sign an input blob using Ed25519.""" + util.setup_logging(verbosity=args.verbose) + identity = _create_identity(user_id=args.user_id) + data = sys.stdin.buffer.read() + sig, pubkey = Client(device=device_type()).sign_with_pubkey(identity, data) + pubkey_str = format_payload(pubkey=pubkey, data=pubkey) + comment = f'untrusted comment: pubkey {pubkey_str}' + result = comment + format_payload(pubkey=pubkey, data=sig) + print(result, end="") + + +def main(device_type): + """Parse command-line arguments.""" + parser = argparse.ArgumentParser() + + subparsers = parser.add_subparsers(title='Action', dest='action') + subparsers.required = True + + p = subparsers.add_parser('pubkey') + p.add_argument('user_id') + p.add_argument('-v', '--verbose', default=0, action='count') + p.set_defaults(func=run_pubkey) + + p = subparsers.add_parser('sign') + p.add_argument('user_id') + p.add_argument('-v', '--verbose', default=0, action='count') + p.set_defaults(func=run_sign) + + args = parser.parse_args() + device_type.ui = ui.UI(device_type=device_type, config=vars(args)) + device_type.ui.cached_passphrase_ack = util.ExpiringCache(seconds=float(60)) + + return args.func(device_type=device_type, args=args) diff --git a/setup.py b/setup.py index be4d655..97b184a 100755 --- a/setup.py +++ b/setup.py @@ -12,7 +12,8 @@ setup( 'libagent', 'libagent.device', 'libagent.gpg', - 'libagent.ssh' + 'libagent.signify', + 'libagent.ssh', ], install_requires=[ 'docutils>=0.14',