Coverage for signmigration.py: 97%
119 statements
« prev ^ index » next coverage.py v7.5.3, created at 2025-07-10 13:43 +0000
« prev ^ index » next coverage.py v7.5.3, created at 2025-07-10 13:43 +0000
1# The MIT License (MIT)
2#
3# Copyright (c) 2021 RSK Labs Ltd
4#
5# Permission is hereby granted, free of charge, to any person obtaining a copy of
6# this software and associated documentation files (the "Software"), to deal in
7# the Software without restriction, including without limitation the rights to
8# use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
9# of the Software, and to permit persons to whom the Software is furnished to do
10# so, subject to the following conditions:
11#
12# The above copyright notice and this permission notice shall be included in all
13# copies or substantial portions of the Software.
14#
15# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21# SOFTWARE.
23import sys
24from os.path import isfile
25from argparse import ArgumentParser
26import ecdsa
27from admin.misc import (
28 get_eth_dongle,
29 dispose_eth_dongle,
30 info,
31 AdminError
32)
33from comm.utils import is_hex_string_of_length
34from comm.bip32 import BIP32Path
35from admin.sgx_migration_authorization import SGXMigrationAuthorization, SGXMigrationSpec
36from admin.ledger_utils import eth_message_to_printable
38# Default signing path
39DEFAULT_ETH_PATH = "m/44'/60'/0'/0/0"
42def _require_output_path(options, require_existing=False):
43 if options.output_path is None:
44 raise AdminError("Must provide an output path (-o/--output)")
45 if require_existing and not isfile(options.output_path):
46 raise AdminError(f"Invalid output path: {options.output_path}")
49def do_message(options):
50 if options.exporter_hash is None:
51 raise AdminError("Must provide an exporter hash (-e/--exporter)")
52 if options.importer_hash is None:
53 raise AdminError("Must provide an importer hash (-i/--importer)")
55 info("Computing the SGX migration authorization message...")
56 migration_spec = SGXMigrationSpec({
57 "exporter": options.exporter_hash,
58 "importer": options.importer_hash
59 })
60 sgx_authorization = SGXMigrationAuthorization.for_spec(migration_spec)
61 if options.output_path is None:
62 info(eth_message_to_printable(migration_spec.get_authorization_msg()))
63 else:
64 sgx_authorization.save_to_jsonfile(options.output_path)
65 info(f"SGX migration authorization saved to {options.output_path}")
68def do_manual_sign(options):
69 _require_output_path(options, require_existing=True)
70 if options.signature is None:
71 raise AdminError("Must provide a signature (-g/--signature)")
73 info(f"Opening SGX migration authorization file {options.output_path}...")
74 sgx_authorization = SGXMigrationAuthorization.from_jsonfile(options.output_path)
75 info("Adding signature...")
76 sgx_authorization.add_signature(options.signature)
77 sgx_authorization.save_to_jsonfile(options.output_path)
78 info(f"SGX migration authorization saved to {options.output_path}")
81def do_key(options):
82 _require_output_path(options, require_existing=True)
83 if options.key is None:
84 raise AdminError("Must provide a signing key (-k/--key)")
85 if not is_hex_string_of_length(options.key, 32, allow_prefix=True):
86 raise AdminError(f"Invalid key '{options.key}'")
88 info(f"Opening SGX migration authorization file {options.output_path}...")
89 sgx_authorization = SGXMigrationAuthorization.from_jsonfile(options.output_path)
90 migration_spec = sgx_authorization.migration_spec
91 info("Signing with key...")
92 sk = ecdsa.SigningKey.from_string(
93 bytes.fromhex(options.key),
94 curve=ecdsa.SECP256k1
95 )
96 signature = sk.sign_digest(
97 migration_spec.get_authorization_digest(),
98 sigencode=ecdsa.util.sigencode_der_canonize
99 )
100 # Add the signature to the authorization and save it to disk
101 sgx_authorization.add_signature(signature.hex())
102 sgx_authorization.save_to_jsonfile(options.output_path)
103 info(f"SGX migration authorization saved to {options.output_path}")
106def do_eth(options):
107 _require_output_path(options)
108 if options.path is None:
109 options.path = DEFAULT_ETH_PATH
110 # Parse path
111 path = BIP32Path(options.path)
112 eth = None
113 try:
114 # Get dongle access (must have ethereum app open)
115 eth = get_eth_dongle(options.verbose)
116 # Retrieve public key
117 info(f"Retrieving public key for path '{str(path)}'...")
118 pubkey = eth.get_pubkey(path)
119 info(f"Public key: {pubkey.hex()}")
121 # If options.pubkey is True, we just want to retrieve the public key
122 if options.pubkey:
123 info(f"Opening public key file {options.output_path}...")
124 info("Adding public key...")
125 with open(options.output_path, "w") as file:
126 file.write("%s\n" % pubkey.hex())
127 info(f"Public key saved to {options.output_path}")
128 return
130 # Is there an existing migration authorization? Read it
131 sgx_authorization = None
132 _require_output_path(options, require_existing=True)
134 info(f"Opening SGX migration authorization file {options.output_path}...")
135 sgx_authorization = SGXMigrationAuthorization.from_jsonfile(options.output_path)
136 migration_spec = sgx_authorization.migration_spec
137 info("Signing with dongle...")
138 try:
139 signature = eth.sign(path, migration_spec.msg.encode('ascii'))
140 vkey = ecdsa.VerifyingKey.from_string(pubkey, curve=ecdsa.SECP256k1)
142 if not vkey.verify_digest(
143 signature, migration_spec.get_authorization_digest(),
144 sigdecode=ecdsa.util.sigdecode_der
145 ):
146 raise Exception()
147 except Exception:
148 raise AdminError(f"Bad signature from dongle! (got '{signature.hex()}')")
149 # Add the signature to the authorization and save it to disk
150 sgx_authorization.add_signature(signature.hex())
151 sgx_authorization.save_to_jsonfile(options.output_path)
152 info(f"SGX migration authorization saved to {options.output_path}")
153 except AdminError:
154 raise
155 except Exception as e:
156 raise AdminError(f"Error signing with dongle: {e}")
157 finally:
158 dispose_eth_dongle(eth)
161def main():
162 parser = ArgumentParser(
163 description="powHSM SGX migration authorization generation and signing tool"
164 )
165 parser.add_argument("operation", choices=["message", "key", "eth", "manual"])
166 parser.add_argument(
167 "-o",
168 "--output",
169 dest="output_path",
170 help="Destination file for SGX migration authorization.",
171 )
172 parser.add_argument(
173 "-k",
174 "--key",
175 dest="key",
176 help="Private key used for signing (only for 'key' option)."
177 "Must be a 32-byte hex-encoded string.",
178 )
179 parser.add_argument(
180 "-p",
181 "--path",
182 dest="path",
183 help="Path used for signing (only for 'eth' option). "
184 f"Default \"{DEFAULT_ETH_PATH}\""
185 )
186 parser.add_argument(
187 "-g",
188 "--signature",
189 dest="signature",
190 help="Signature to add to SGX migration authorization (only for 'manual' option)."
191 "Must be a hex-encoded, der-encoded SECP256k1 signature.",
192 )
193 parser.add_argument(
194 "-b",
195 "--pubkey",
196 dest="pubkey",
197 action="store_true",
198 help="Retrieve public key (only for 'eth' option)."
199 )
200 parser.add_argument(
201 "-e",
202 "--exporter",
203 dest="exporter_hash",
204 help="The hash of the exporter enclave (only for 'message' option)."
205 )
206 parser.add_argument(
207 "-i",
208 "--importer",
209 dest="importer_hash",
210 help="The hash of the importer enclave (only for 'message' option)."
211 )
212 parser.add_argument(
213 "-v",
214 "--verbose",
215 dest="verbose",
216 action="store_const",
217 help="Enable verbose mode",
218 default=False,
219 const=True,
220 )
221 options = parser.parse_args()
223 try:
224 if options.operation == "message":
225 do_message(options)
226 elif options.operation == "key":
227 do_key(options)
228 elif options.operation == "eth":
229 do_eth(options)
230 elif options.operation == "manual":
231 do_manual_sign(options)
232 else:
233 raise AdminError(f"Invalid operation: {options.operation}")
234 sys.exit(0)
235 except Exception as e:
236 info(str(e))
237 sys.exit(1)
240if __name__ == "__main__":
241 main()