From 83153b52b7b62db8c4455625e5e2efbadb5ec2d8 Mon Sep 17 00:00:00 2001 From: tychovrahe Date: Tue, 8 Nov 2022 14:58:04 +0100 Subject: [PATCH] Revert "refactor(core): remove remote signing functionality from headertool" This reverts commit 6905f9c4863095f66fec5ba1bae9ef6e5aebc0af. --- core/tools/headertool.py | 84 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 84 insertions(+) diff --git a/core/tools/headertool.py b/core/tools/headertool.py index 6d789d457c..0e0112e7bb 100755 --- a/core/tools/headertool.py +++ b/core/tools/headertool.py @@ -6,6 +6,16 @@ from trezorlib._internal import firmware_headers from typing import List, Tuple + +try: + import Pyro4 + + Pyro4.config.SERIALIZER = "marshal" +except ImportError: + Pyro4 = None + +PORT = 5001 + # =========================== signing ========================= @@ -46,6 +56,57 @@ def parse_privkey_args(privkey_data: List[str]) -> Tuple[int, List[bytes]]: return sigmask, privkeys +def process_remote_signers(fw, addrs: List[str]) -> Tuple[int, List[bytes]]: + if len(addrs) < fw.sigs_required: + raise click.ClickException( + f"Not enough signers (need at least {fw.sigs_required})" + ) + + digest = fw.digest() + name = fw.NAME + + def mkproxy(addr): + return Pyro4.Proxy(f"PYRO:keyctl@{addr}:{PORT}") + + sigmask = 0 + pks, Rs = [], [] + for addr in addrs: + click.echo(f"Connecting to {addr}...") + with mkproxy(addr) as proxy: + pk, R = proxy.get_commit(name, digest) + if pk not in fw.public_keys: + raise click.ClickException( + f"Signer at {addr} commits with unknown public key {pk.hex()}" + ) + idx = fw.public_keys.index(pk) + click.echo( + f"Signer at {addr} commits with public key #{idx + 1}: {pk.hex()}" + ) + sigmask |= 1 << idx + pks.append(pk) + Rs.append(R) + + # compute global commit + global_pk = cosi.combine_keys(pks) + global_R = cosi.combine_keys(Rs) + + # collect signatures + sigs = [] + for addr in addrs: + click.echo(f"Waiting for {addr} to sign... ", nl=False) + with mkproxy(addr) as proxy: + sig = proxy.get_signature(name, digest, global_R, global_pk) + sigs.append(sig) + click.echo("OK") + + for addr in addrs: + with mkproxy(addr) as proxy: + proxy.finish() + + # compute global signature + return sigmask, cosi.combine_sig(global_R, sigs) + + # ===================== CLI actions ========================= @@ -91,6 +152,13 @@ def do_replace_vendorheader(fw, vh_file) -> None: is_flag=True, help="Only output header digest for signing and exit.", ) +@click.option( + "-r", + "--remote", + metavar="IPADDR", + multiple=True, + help="IP address of remote signer. Can be repeated.", +) @click.argument("firmware_file", type=click.File("rb+")) def cli( firmware_file, @@ -102,6 +170,7 @@ def cli( insert_signature, replace_vendor_header, print_digest, + remote, ): """Manage trezor-core firmware headers. @@ -132,6 +201,14 @@ def cli( development keys. Signature validity is not checked in either of the two cases. + + To sign with remote participants: + + ./headertool.py firmware.bin -r 10.24.13.11 -r 10.24.13.190 ... + + Each participant must be running keyctl-proxy configured on the same file. Signers' + public keys must be in the list of known signers and are matched to indexes + automatically. """ firmware_data = firmware_file.read() @@ -177,6 +254,13 @@ def cli( for bit in sigmask_str.split(":"): sigmask |= 1 << (int(bit) - 1) + if remote: + if Pyro4 is None: + raise click.ClickException("Please install Pyro4 for remote signing.") + click.echo(fw) + click.echo(f"Signing with {len(remote)} remote participants.") + sigmask, signature = process_remote_signers(fw, remote) + if signature: fw.rehash() fw.insert_signature(signature, sigmask)