Coverage for admin/onboard.py: 90%
92 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
24import os
25from ledger.hsm2dongle import HSM2Dongle
26from ledger.pin import BasePin
27from .misc import (
28 info,
29 head,
30 bls,
31 get_hsm,
32 get_admin_hsm,
33 dispose_hsm,
34 PIN_ERROR_MESSAGE,
35 AdminError,
36 ask_for_pin,
37 wait_for_reconnection,
38)
39from .dongle_admin import DongleAdmin
40from .unlock import do_unlock
41from .certificate import HSMCertificate, HSMCertificateElement
42from comm.platform import Platform
44# TODO: this could perhaps be done with a different value.
45# Currently unused but necessary for the attestation setup process.
46ENDORSEMENT_CERTIFICATE = b"RSK_ENDORSEMENT_OK"
47SEED_SIZE = 32
50def do_onboard(options):
51 head("### -> Onboarding and attestation setup", fill="#")
52 hsm = None
54 # Ledger-only: require an output file
55 if Platform.is_ledger() and options.output_file_path is None:
56 raise AdminError("No output file path given")
58 # Validate pin (if given)
59 pin = None
60 if options.pin is not None:
61 if not BasePin.is_valid(options.pin.encode()):
62 raise AdminError(PIN_ERROR_MESSAGE)
63 pin = options.pin.encode()
65 # Connection
66 hsm = get_hsm(options.verbose)
68 # Mode check
69 info("Finding mode... ", options.verbose)
70 mode = hsm.get_current_mode()
71 info(f"Mode: {mode.name.capitalize()}")
73 # Require bootloader mode for onboarding
74 if mode != HSM2Dongle.MODE.BOOTLOADER:
75 raise AdminError("Device not in bootloader mode. "
76 f"{Platform.message('restart').capitalize()} and try again")
78 # Echo check
79 info("Sending echo... ", options.verbose)
80 if not hsm.echo():
81 raise AdminError("Echo error")
82 info("Echo OK")
84 info("Is device onboarded? ... ", options.verbose)
85 is_onboarded = hsm.is_onboarded()
86 info(f"Onboarded: {bls(is_onboarded)}")
88 if is_onboarded:
89 raise AdminError("Device already onboarded")
91 message = "The following operation will onboard the device."
92 head([
93 message,
94 "Do you want to proceed? Yes/No",
95 ])
96 while True:
97 info("> ", False)
98 answer = sys.stdin.readline().rstrip()
99 if answer.lower() in ["n", "no"]:
100 raise AdminError("Cancelled by user")
101 if answer.lower() == "yes":
102 break
103 info("Please answer 'Yes' or 'No'")
105 # If we get here, then it means we need to onboard
107 # Ask the user for a pin if one not given
108 if pin is None:
109 info("Please select a pin for the device.")
110 if not options.any_pin:
111 info("The pin must be 8 characters long and contain "
112 "at least one alphabetic character.")
113 pin = ask_for_pin(any_pin=options.any_pin)
115 # Generate a random seed
116 info("Generating a random seed... ", options.verbose)
117 seed = gen_seed()
118 info("Seed generated")
120 # Onboard
121 info("Onboarding... ", options.verbose)
122 hsm.onboard(seed, pin)
123 info("Onboarded")
125 dispose_hsm(hsm)
127 if Platform.is_sgx():
128 head(["Onboarding done"])
129 return
131 if not Platform.is_ledger():
132 raise AdminError("Unsupported platform")
134 # **** Attestation setup is Ledger only (for now) ****
135 head(
136 [
137 "Onboarding done",
138 "Please disconnect and re-connect the ledger to proceed with the attestation setup", # noqa E501
139 "Press [Enter] to continue",
140 ],
141 nl=False,
142 )
143 sys.stdin.readline()
145 # Wait for the dongle
146 wait_for_reconnection()
148 # Unlock without executing the signer
149 try:
150 do_unlock(options, no_exec=True, label=False)
151 except Exception as e:
152 raise AdminError(f"Failed to unlock device: {str(e)}")
154 # Wait for the UI
155 wait_for_reconnection()
157 # Connection for admin operations
158 hsm = get_admin_hsm(options.verbose)
160 # Attestation setup
161 info("Handshaking... ", options.verbose)
162 hsm.handshake()
163 info("Handshaking done")
165 info("Gathering device key... ", options.verbose)
166 device_key_info = hsm.get_device_key()
167 info("Device key gathered")
169 info("Setting up the attestation key... ", options.verbose)
170 attestation_key_info = hsm.setup_endorsement_key(
171 DongleAdmin.ENDORSEMENT_SCHEME.SCHEME_TWO, ENDORSEMENT_CERTIFICATE)
172 info("Attestation key setup complete")
174 dispose_hsm(hsm)
176 # Generate and save the attestation certificate
177 info("Generating the attestation certificate... ", options.verbose)
179 att_cert = HSMCertificate()
180 att_cert.add_element(
181 HSMCertificateElement({
182 "name": "attestation",
183 "message": attestation_key_info["message"],
184 "signature": attestation_key_info["signature"],
185 "signed_by": "device",
186 }))
187 att_cert.add_element(
188 HSMCertificateElement({
189 "name": "device",
190 "message": device_key_info["message"],
191 "signature": device_key_info["signature"],
192 "signed_by": "root",
193 }))
194 att_cert.add_target("attestation")
195 att_cert.save_to_jsonfile(options.output_file_path)
197 info(f"Attestation certificate saved to {options.output_file_path}")
199 head([
200 "Onboarding and attestation setup done",
201 "Please disconnect and re-connect the ledger before the first use",
202 ])
205def gen_seed():
206 return os.urandom(SEED_SIZE)