2018-01-29 14:02:32 +00:00
|
|
|
#!/usr/bin/env python3
|
|
|
|
import sys
|
2019-02-21 15:18:49 +00:00
|
|
|
import traceback
|
|
|
|
|
2020-01-03 13:19:02 +00:00
|
|
|
import click
|
2018-01-29 14:02:32 +00:00
|
|
|
import Pyro4
|
2020-01-03 13:19:02 +00:00
|
|
|
from trezorlib import cosi
|
|
|
|
from trezorlib.client import get_default_client
|
|
|
|
from trezorlib.tools import parse_path
|
|
|
|
from trezorlib._internal.firmware_headers import (
|
|
|
|
parse_image,
|
|
|
|
VendorHeader,
|
|
|
|
BootloaderImage,
|
|
|
|
FirmwareImage,
|
|
|
|
)
|
|
|
|
|
|
|
|
from typing import Tuple
|
2018-01-29 14:02:32 +00:00
|
|
|
|
2019-12-20 14:00:32 +00:00
|
|
|
Pyro4.config.SERIALIZER = "marshal"
|
|
|
|
|
2018-01-29 14:02:32 +00:00
|
|
|
PORT = 5001
|
2020-01-03 13:19:02 +00:00
|
|
|
indexmap = {
|
|
|
|
"bootloader": BootloaderImage,
|
|
|
|
"vendorheader": VendorHeader,
|
|
|
|
"firmware": FirmwareImage,
|
|
|
|
}
|
2018-01-29 14:02:32 +00:00
|
|
|
|
2020-01-03 13:19:02 +00:00
|
|
|
PATH = "10018h/{}h"
|
2018-07-31 09:35:09 +00:00
|
|
|
|
2020-01-06 15:30:55 +00:00
|
|
|
TREZOR = None
|
2018-01-29 14:02:32 +00:00
|
|
|
|
2020-01-06 15:30:55 +00:00
|
|
|
|
2021-07-18 21:11:09 +00:00
|
|
|
def make_commit(image_type, digest, public_keys):
|
|
|
|
path = PATH.format(image_type.BIP32_INDEX)
|
2020-01-03 13:19:02 +00:00
|
|
|
address_n = parse_path(path)
|
2020-01-06 15:30:55 +00:00
|
|
|
|
|
|
|
# device information - show only first time
|
|
|
|
click.echo(
|
2021-07-14 15:22:36 +00:00
|
|
|
"\nUsing device {} ".format(click.style(TREZOR.features.label, bold=True)) +
|
|
|
|
"at path {}".format(TREZOR.transport.get_path())
|
2020-01-06 15:30:55 +00:00
|
|
|
)
|
|
|
|
|
2020-01-03 13:19:02 +00:00
|
|
|
while True:
|
2020-01-06 15:30:55 +00:00
|
|
|
# signing information - repeat every time
|
2021-07-18 21:11:09 +00:00
|
|
|
click.echo("Commiting to {} hash:".format(click.style(image_type.NAME, bold=True)))
|
2020-01-06 15:30:55 +00:00
|
|
|
for partid in range(4):
|
|
|
|
digest_part = digest[partid * 8 : (partid + 1) * 8]
|
2020-01-22 16:05:41 +00:00
|
|
|
color = "red" if partid % 2 else "cyan"
|
2020-01-06 15:30:55 +00:00
|
|
|
digest_str = click.style(digest_part.hex().upper(), fg=color)
|
|
|
|
click.echo("\t" + digest_str)
|
2021-07-14 15:22:36 +00:00
|
|
|
click.echo("Using path: {}".format(click.style(path, bold=True)))
|
2020-01-06 15:30:55 +00:00
|
|
|
|
2020-01-03 13:19:02 +00:00
|
|
|
try:
|
2020-01-06 15:30:55 +00:00
|
|
|
commit = cosi.commit(TREZOR, address_n, digest)
|
|
|
|
if public_keys is not None and commit.pubkey not in public_keys:
|
2021-07-14 15:22:36 +00:00
|
|
|
click.echo("\n\nPublic key {} is unknown.".format(commit.pubkey.hex()))
|
2020-01-06 15:30:55 +00:00
|
|
|
if click.confirm("Retry with a different passphrase?", default=True):
|
|
|
|
TREZOR.init_device()
|
|
|
|
continue
|
|
|
|
|
2020-01-03 13:19:02 +00:00
|
|
|
return commit.pubkey, commit.commitment
|
|
|
|
except Exception as e:
|
2020-01-06 15:03:56 +00:00
|
|
|
click.echo(e)
|
2020-01-03 13:19:02 +00:00
|
|
|
traceback.print_exc()
|
2020-01-06 15:30:55 +00:00
|
|
|
click.echo("Trying again ...\n\n")
|
2018-01-29 14:02:32 +00:00
|
|
|
|
|
|
|
|
|
|
|
@Pyro4.expose
|
2020-01-03 13:19:02 +00:00
|
|
|
class KeyctlProxy:
|
2020-01-06 15:05:43 +00:00
|
|
|
def __init__(
|
|
|
|
self, daemon, image_type, digest: bytes, commit: Tuple[bytes, bytes]
|
|
|
|
) -> None:
|
|
|
|
self.daemon = daemon
|
2020-01-03 13:19:02 +00:00
|
|
|
self.name = image_type.NAME
|
|
|
|
self.address_n = parse_path(PATH.format(image_type.BIP32_INDEX))
|
|
|
|
self.digest = digest
|
|
|
|
self.commit = commit
|
2020-01-06 17:18:22 +00:00
|
|
|
self.signature = None
|
|
|
|
self.global_params = None
|
2020-01-03 13:19:02 +00:00
|
|
|
|
|
|
|
def _check_name_digest(self, name, digest):
|
|
|
|
if name != self.name or digest != self.digest:
|
2021-07-14 15:22:36 +00:00
|
|
|
click.echo("ERROR! Remote wants to sign {} with digest {}".format(name, digest.hex()))
|
|
|
|
click.echo("Expected: {} with digest {}".format(self.name, self.digest.hex()))
|
2020-01-03 13:19:02 +00:00
|
|
|
raise ValueError("Unexpected index/digest")
|
|
|
|
|
|
|
|
def get_commit(self, name, digest):
|
|
|
|
self._check_name_digest(name, digest)
|
2020-01-06 15:03:56 +00:00
|
|
|
click.echo("Sending commitment!")
|
2020-01-03 13:19:02 +00:00
|
|
|
return self.commit
|
|
|
|
|
2020-01-06 17:18:22 +00:00
|
|
|
def _make_signature(self, global_R, global_pk):
|
2020-01-03 13:19:02 +00:00
|
|
|
while True:
|
2018-01-29 14:28:17 +00:00
|
|
|
try:
|
2020-01-06 15:03:56 +00:00
|
|
|
click.echo("\n\n\nSigning...")
|
2020-01-06 15:30:55 +00:00
|
|
|
signature = cosi.sign(
|
2020-01-06 17:18:22 +00:00
|
|
|
TREZOR, self.address_n, self.digest, global_R, global_pk
|
2020-01-06 15:30:55 +00:00
|
|
|
)
|
2020-01-03 13:19:02 +00:00
|
|
|
return signature.signature
|
2018-01-29 14:28:17 +00:00
|
|
|
except Exception as e:
|
2020-01-06 15:03:56 +00:00
|
|
|
click.echo(e)
|
2019-02-21 15:18:49 +00:00
|
|
|
traceback.print_exc()
|
2020-01-06 15:03:56 +00:00
|
|
|
click.echo("Trying again ...")
|
2018-01-29 14:02:32 +00:00
|
|
|
|
2020-01-06 17:18:22 +00:00
|
|
|
|
|
|
|
def get_signature(self, name, digest, global_R, global_pk):
|
|
|
|
self._check_name_digest(name, digest)
|
|
|
|
global_params = global_R, global_pk
|
|
|
|
if global_params != self.global_params:
|
|
|
|
self.signature = self._make_signature(global_R, global_pk)
|
|
|
|
self.global_params = global_params
|
|
|
|
click.echo("Sending signature!")
|
|
|
|
return self.signature
|
|
|
|
|
2020-01-06 15:05:43 +00:00
|
|
|
@Pyro4.oneway
|
|
|
|
def finish(self):
|
|
|
|
click.echo("Done! \\(^o^)/")
|
|
|
|
self.daemon.shutdown()
|
|
|
|
|
2018-01-29 14:02:32 +00:00
|
|
|
|
2020-01-03 13:19:02 +00:00
|
|
|
@click.command()
|
|
|
|
@click.option(
|
|
|
|
"-l", "--listen", "ipaddr", default="0.0.0.0", help="Bind to particular ip address"
|
|
|
|
)
|
2021-07-14 15:27:31 +00:00
|
|
|
@click.option("-t", "--image-type", type=click.Choice(indexmap.keys()))
|
2020-01-03 13:19:02 +00:00
|
|
|
@click.option("-d", "--digest")
|
|
|
|
@click.argument("fw_file", type=click.File("rb"), required=False)
|
2021-07-14 15:27:31 +00:00
|
|
|
def cli(ipaddr, fw_file, image_type, digest):
|
2020-01-03 13:19:02 +00:00
|
|
|
"""Participate in signing of firmware.
|
|
|
|
|
|
|
|
Specify either fw_file to auto-detect type and digest, or use -t and -d to specify
|
|
|
|
the type and digest manually.
|
|
|
|
"""
|
2020-01-06 15:30:55 +00:00
|
|
|
global TREZOR
|
|
|
|
|
2020-01-03 13:19:02 +00:00
|
|
|
public_keys = None
|
|
|
|
if fw_file:
|
2021-07-14 15:27:31 +00:00
|
|
|
if image_type or digest:
|
2020-01-03 13:19:02 +00:00
|
|
|
raise click.ClickException("Do not specify fw_file together with -t/-d")
|
|
|
|
|
2021-07-14 15:27:31 +00:00
|
|
|
image_type = parse_image(fw_file.read())
|
|
|
|
digest = image_type.digest()
|
|
|
|
public_keys = image_type.public_keys
|
2020-01-03 13:19:02 +00:00
|
|
|
|
2021-07-14 15:27:31 +00:00
|
|
|
click.echo(image_type.format())
|
2020-01-03 13:19:02 +00:00
|
|
|
|
2021-07-14 15:27:31 +00:00
|
|
|
if not fw_file and (not image_type or not digest):
|
2021-07-14 15:22:54 +00:00
|
|
|
raise click.ClickException("Please specify either fw_file or -t and -d")
|
2020-01-03 13:19:02 +00:00
|
|
|
|
2020-01-06 15:30:55 +00:00
|
|
|
try:
|
|
|
|
TREZOR = get_default_client()
|
|
|
|
TREZOR.ui.always_prompt = True
|
|
|
|
except Exception as e:
|
|
|
|
raise click.ClickException("Please connect a Trezor and retry.") from e
|
|
|
|
|
2021-07-14 15:27:31 +00:00
|
|
|
pubkey, R = make_commit(image_type, digest, public_keys)
|
2020-01-03 13:19:02 +00:00
|
|
|
|
2018-01-29 14:49:55 +00:00
|
|
|
daemon = Pyro4.Daemon(host=ipaddr, port=PORT)
|
2021-07-14 15:27:31 +00:00
|
|
|
proxy = KeyctlProxy(daemon, image_type, digest, (pubkey, R))
|
2018-07-31 09:35:09 +00:00
|
|
|
uri = daemon.register(proxy, "keyctl")
|
2021-07-14 15:22:36 +00:00
|
|
|
click.echo("keyctl-proxy running at URI: {}".format(uri))
|
2020-01-03 13:19:02 +00:00
|
|
|
click.echo("Press Ctrl+C to abort.")
|
2018-01-29 14:02:32 +00:00
|
|
|
daemon.requestLoop()
|
2020-01-03 13:19:02 +00:00
|
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
cli()
|