Coverage for tests/admin/test_onboard.py: 100%
267 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.
23from types import SimpleNamespace
24from unittest import TestCase
25from unittest.mock import Mock, call, patch, mock_open
26from admin.certificate import HSMCertificate, HSMCertificateElement
27from admin.misc import AdminError
28from comm.platform import Platform
29from admin.onboard import do_onboard
30import json
31from ledger.hsm2dongle import HSM2Dongle, HSM2DongleError
33import logging
36logging.disable(logging.CRITICAL)
39@patch("sys.stdout.write")
40@patch("time.sleep")
41@patch("admin.onboard.info")
42@patch("admin.onboard.get_hsm")
43class TestOnboard(TestCase):
44 VALID_PIN = "1234ABCD"
45 INVALID_PIN = "123456789"
47 DEVICE_KEY = {
48 "pubkey": "this-is-the-public-key",
49 "message": "1122334455",
50 "signature": "aabbccddee",
51 }
53 ENDORSEMENT_KEY = {
54 "pubkey": "this-is-another-public-key",
55 "message": "5544332211",
56 "signature": "eeddccbbaa",
57 }
59 INVALID_KEY = {
60 "pubkey": "this-is-the-public-key",
61 "message": "invalid-message",
62 "signature": "invalid-signature",
63 }
65 def setUp(self):
66 Platform.set(Platform.LEDGER)
67 self.certificate_path = "cert-path"
68 options = {
69 "pin": self.VALID_PIN,
70 "output_file_path": self.certificate_path,
71 "any_pin": False,
72 "no_exec": False,
73 "verbose": False
74 }
75 self.default_options = SimpleNamespace(**options)
77 self.expected_cert = HSMCertificate()
78 self.expected_cert.add_element(
79 HSMCertificateElement({
80 "name": "attestation",
81 "message": self.ENDORSEMENT_KEY["message"],
82 "signature": self.ENDORSEMENT_KEY["signature"],
83 "signed_by": "device",
84 })
85 )
86 self.expected_cert.add_element(
87 HSMCertificateElement({
88 "name": "device",
89 "message": self.DEVICE_KEY["message"],
90 "signature": self.DEVICE_KEY["signature"],
91 "signed_by": "root",
92 })
93 )
94 self.expected_cert.add_target("attestation")
95 self.dongle = Mock()
97 @patch("admin.onboard.get_admin_hsm")
98 @patch("admin.unlock.get_hsm")
99 @patch("sys.stdin.readline")
100 def test_onboard_ledger(self, readline, get_hsm_unlock, get_admin_hsm,
101 get_hsm_onboard, info_mock, *_):
102 get_hsm_onboard.return_value = self.dongle
103 get_hsm_unlock.return_value = self.dongle
104 get_admin_hsm.return_value = self.dongle
106 self.dongle.get_current_mode = Mock(return_value=HSM2Dongle.MODE.BOOTLOADER)
107 self.dongle.is_onboarded = Mock(side_effect=[False, True])
108 self.dongle.get_device_key = Mock(return_value=self.DEVICE_KEY)
109 self.dongle.setup_endorsement_key = Mock(return_value=self.ENDORSEMENT_KEY)
110 self.dongle.handshake = Mock()
111 self.dongle.onboard = Mock()
112 readline.return_value = "yes\n"
114 with patch("builtins.open", mock_open()) as file_mock:
115 do_onboard(self.default_options)
117 self.assertEqual(info_mock.call_args_list[5][0][0], "Onboarded: No")
118 self.assertEqual(info_mock.call_args_list[10][0][0], "Onboarded")
119 self.assertEqual(info_mock.call_args_list[14][0][0], "Device key gathered")
120 self.assertEqual(info_mock.call_args_list[16][0][0],
121 "Attestation key setup complete")
123 self.assertEqual([call(self.certificate_path, "w")], file_mock.call_args_list)
124 self.assertEqual([call("%s\n" %
125 json.dumps(self.expected_cert.to_dict(), indent=2))],
126 file_mock.return_value.write.call_args_list)
127 self.assertTrue(self.dongle.onboard.called)
128 self.assertTrue(self.dongle.handshake.called)
130 @patch("admin.onboard.get_admin_hsm")
131 @patch("admin.unlock.get_hsm")
132 @patch("sys.stdin.readline")
133 def test_onboard_sgx(self, readline, get_hsm_unlock, get_admin_hsm,
134 get_hsm_onboard, info_mock, *_):
135 Platform.set(Platform.SGX)
137 get_hsm_onboard.return_value = self.dongle
138 get_hsm_unlock.return_value = self.dongle
140 self.dongle.get_current_mode = Mock(return_value=HSM2Dongle.MODE.BOOTLOADER)
141 self.dongle.is_onboarded = Mock(side_effect=[False, True])
142 self.dongle.onboard = Mock()
143 readline.return_value = "yes\n"
145 with patch("builtins.open", mock_open()):
146 do_onboard(self.default_options)
148 self.assertEqual(info_mock.call_args_list[5][0][0], "Onboarded: No")
149 self.assertEqual(info_mock.call_args_list[10][0][0], "Onboarded")
151 self.assertTrue(self.dongle.onboard.called)
152 self.assertFalse(self.dongle.get_device_key.called)
153 self.assertFalse(self.dongle.setup_endorsement_key.called)
154 self.assertFalse(self.dongle.handshake.called)
156 @patch("admin.onboard.get_admin_hsm")
157 @patch("admin.unlock.get_hsm")
158 @patch("sys.stdin.readline")
159 def test_onboard_already_onboarded(self, readline, get_hsm_unlock, get_admin_hsm,
160 get_hsm_onboard, info_mock, *_):
161 get_hsm_onboard.return_value = self.dongle
162 get_hsm_unlock.return_value = self.dongle
163 get_admin_hsm.return_value = self.dongle
165 self.dongle.get_current_mode = Mock(return_value=HSM2Dongle.MODE.BOOTLOADER)
166 self.dongle.is_onboarded = Mock(return_value=True)
168 with self.assertRaises(AdminError) as e:
169 do_onboard(self.default_options)
171 self.assertEqual(info_mock.call_args_list[5][0][0], "Onboarded: Yes")
172 self.assertEqual(e.exception.args[0], "Device already onboarded")
173 self.assertFalse(self.dongle.onboard.called)
175 @patch("admin.onboard.get_admin_hsm")
176 @patch("admin.unlock.get_hsm")
177 @patch("sys.stdin.readline")
178 def test_onboard_onboard_error(self, readline, get_hsm_unlock, get_admin_hsm,
179 get_hsm_onboard, *_):
180 get_hsm_onboard.return_value = self.dongle
181 get_hsm_unlock.return_value = self.dongle
182 get_admin_hsm.return_value = self.dongle
184 self.dongle.get_current_mode = Mock(return_value=HSM2Dongle.MODE.BOOTLOADER)
185 self.dongle.is_onboarded = Mock(return_value=False)
186 self.dongle.get_device_key = Mock()
187 self.dongle.setup_endorsement_key = Mock()
188 self.dongle.handshake = Mock()
189 self.dongle.onboard = Mock(side_effect=HSM2DongleError("error-msg"))
190 readline.return_value = "yes\n"
192 with patch("builtins.open", mock_open()) as file_mock:
193 with self.assertRaises(HSM2DongleError) as e:
194 do_onboard(self.default_options)
196 self.assertTrue(self.dongle.onboard.called)
197 self.assertEqual("error-msg", str(e.exception))
198 self.assertFalse(self.dongle.get_device_key.called)
199 self.assertFalse(self.dongle.setup_endorsement_key.called)
200 self.assertFalse(self.dongle.handshake.called)
201 self.assertFalse(file_mock.return_value.write.called)
203 @patch("admin.onboard.get_admin_hsm")
204 @patch("admin.unlock.get_hsm")
205 @patch("sys.stdin.readline")
206 def test_onboard_handshake_error(self, readline, get_hsm_unlock, get_admin_hsm,
207 get_hsm_onboard, *_):
208 get_hsm_onboard.return_value = self.dongle
209 get_hsm_unlock.return_value = self.dongle
210 get_admin_hsm.return_value = self.dongle
212 self.dongle.get_current_mode = Mock(return_value=HSM2Dongle.MODE.BOOTLOADER)
213 self.dongle.is_onboarded = Mock(side_effect=[False, True])
214 self.dongle.get_device_key = Mock()
215 self.dongle.setup_endorsement_key = Mock()
216 self.dongle.handshake = Mock(side_effect=HSM2DongleError("error-msg"))
217 self.dongle.onboard = Mock()
218 readline.return_value = "yes\n"
220 with patch("builtins.open", mock_open()) as file_mock:
221 with self.assertRaises(HSM2DongleError) as e:
222 do_onboard(self.default_options)
224 self.assertEqual("error-msg", str(e.exception))
225 self.assertTrue(self.dongle.onboard.called)
226 self.assertTrue(self.dongle.handshake.called)
227 self.assertFalse(self.dongle.get_device_key.called)
228 self.assertFalse(self.dongle.setup_endorsement_key.called)
229 self.assertFalse(file_mock.return_value.write.called)
231 @patch("admin.onboard.get_admin_hsm")
232 @patch("admin.unlock.get_hsm")
233 @patch("sys.stdin.readline")
234 def test_onboard_getkey_error(self, readline, get_hsm_unlock, get_admin_hsm,
235 get_hsm_onboard, *_):
236 get_hsm_onboard.return_value = self.dongle
237 get_hsm_unlock.return_value = self.dongle
238 get_admin_hsm.return_value = self.dongle
240 self.dongle.get_current_mode = Mock(return_value=HSM2Dongle.MODE.BOOTLOADER)
241 self.dongle.is_onboarded = Mock(side_effect=[False, True])
242 self.dongle.get_device_key = Mock(side_effect=HSM2DongleError("error-msg"))
243 self.dongle.setup_endorsement_key = Mock()
244 self.dongle.handshake = Mock()
245 self.dongle.onboard = Mock()
246 readline.return_value = "yes\n"
248 with patch("builtins.open", mock_open()) as file_mock:
249 with self.assertRaises(HSM2DongleError) as e:
250 do_onboard(self.default_options)
252 self.assertEqual("error-msg", str(e.exception))
253 self.assertTrue(self.dongle.onboard.called)
254 self.assertTrue(self.dongle.handshake.called)
255 self.assertTrue(self.dongle.get_device_key.called)
256 self.assertFalse(self.dongle.setup_endorsement_key.called)
257 self.assertFalse(file_mock.return_value.write.called)
259 @patch("admin.onboard.get_admin_hsm")
260 @patch("admin.unlock.get_hsm")
261 @patch("sys.stdin.readline")
262 def test_onboard_setupkey_error(self, readline, get_hsm_unlock, get_admin_hsm,
263 get_hsm_onboard, *_):
264 get_hsm_onboard.return_value = self.dongle
265 get_hsm_unlock.return_value = self.dongle
266 get_admin_hsm.return_value = self.dongle
268 self.dongle.get_current_mode = Mock(return_value=HSM2Dongle.MODE.BOOTLOADER)
269 self.dongle.is_onboarded = Mock(side_effect=[False, True])
270 self.dongle.get_device_key = Mock()
271 self.dongle.setup_endorsement_key = Mock(side_effect=HSM2DongleError("error-msg"))
272 self.dongle.handshake = Mock()
273 self.dongle.onboard = Mock()
274 readline.return_value = "yes\n"
276 with patch("builtins.open", mock_open()) as file_mock:
277 with self.assertRaises(HSM2DongleError) as e:
278 do_onboard(self.default_options)
280 self.assertEqual("error-msg", str(e.exception))
281 self.assertTrue(self.dongle.onboard.called)
282 self.assertTrue(self.dongle.handshake.called)
283 self.assertTrue(self.dongle.get_device_key.called)
284 self.assertTrue(self.dongle.setup_endorsement_key.called)
285 self.assertFalse(file_mock.return_value.write.called)
287 @patch("admin.onboard.get_admin_hsm")
288 @patch("admin.unlock.get_hsm")
289 @patch("sys.stdin.readline")
290 def test_onboard_user_cancelled(self, readline, hsm_unlock, hsm_admin,
291 hsm_onboard, *_):
292 hsm_onboard.return_value = self.dongle
293 hsm_unlock.return_value = self.dongle
294 self.dongle.get_current_mode = Mock(return_value=HSM2Dongle.MODE.BOOTLOADER)
295 self.dongle.is_onboarded = Mock(return_value=False)
296 self.dongle.onboard = Mock()
297 hsm_admin.return_value = self.dongle
298 readline.return_value = "no\n"
300 with patch("builtins.open", mock_open()) as file_mock:
301 with self.assertRaises(AdminError) as e:
302 do_onboard(self.default_options)
304 self.assertEqual("Cancelled by user", str(e.exception))
305 self.assertFalse(self.dongle.onboard.called)
306 self.assertFalse(self.dongle.get_device_key.called)
307 self.assertFalse(self.dongle.setup_endorsement_key.called)
308 self.assertFalse(file_mock.return_value.write.called)
310 @patch("sys.stdin.readline")
311 def test_onboard_no_output_file_ledger(self, readline, get_hsm, *_):
312 readline.return_value = "yes\n"
313 get_hsm.return_value = self.dongle
315 self.dongle.get_current_mode = Mock(return_value=HSM2Dongle.MODE.BOOTLOADER)
316 self.dongle.is_onboarded = Mock(return_value=False)
317 self.dongle.onboard = Mock()
319 options = self.default_options
320 options.output_file_path = None
321 with self.assertRaises(AdminError) as e:
322 do_onboard(options)
324 self.assertEqual("No output file path given", str(e.exception))
325 self.assertFalse(self.dongle.onboard.called)
327 @patch("sys.stdin.readline")
328 def test_onboard_no_output_file_sgx(self, readline, get_hsm, *_):
329 Platform.set(Platform.SGX)
330 readline.return_value = "yes\n"
331 get_hsm.return_value = self.dongle
333 self.dongle.get_current_mode = Mock(return_value=HSM2Dongle.MODE.BOOTLOADER)
334 self.dongle.is_onboarded = Mock(return_value=False)
335 self.dongle.onboard = Mock()
337 options = self.default_options
338 options.output_file_path = None
340 do_onboard(options)
342 self.assertTrue(self.dongle.onboard.called)
344 def test_onboard_invalid_pin(self, *_):
345 options = self.default_options
346 options.pin = self.INVALID_PIN
347 with self.assertRaises(AdminError) as e:
348 do_onboard(options)
350 self.assertTrue(str(e.exception).startswith("Invalid pin given."))
352 def test_onboard_invalid_mode(self, get_hsm, *_):
353 get_hsm.return_value = self.dongle
354 self.dongle.get_current_mode = Mock(return_value=HSM2Dongle.MODE.SIGNER)
355 self.dongle.is_onboarded = Mock(return_value=False)
357 with self.assertRaises(AdminError) as e:
358 do_onboard(self.default_options)
360 self.assertTrue(str(e.exception).startswith("Device not in bootloader mode."))
362 @patch("admin.onboard.get_admin_hsm")
363 @patch("admin.unlock.get_hsm")
364 @patch("sys.stdin.readline")
365 def test_onboard_invalid_device_key(self, readline, get_hsm_unlock, get_admin_hsm,
366 get_hsm_onboard, *_):
367 get_hsm_onboard.return_value = self.dongle
368 get_hsm_unlock.return_value = self.dongle
369 get_admin_hsm.return_value = self.dongle
371 self.dongle.get_current_mode = Mock(return_value=HSM2Dongle.MODE.BOOTLOADER)
372 self.dongle.is_onboarded = Mock(side_effect=[False, True])
373 self.dongle.get_device_key = Mock(return_value=self.INVALID_KEY)
374 self.dongle.setup_endorsement_key = Mock(return_value=self.ENDORSEMENT_KEY)
375 readline.return_value = "yes\n"
377 with self.assertRaises(ValueError) as e:
378 do_onboard(self.default_options)
380 self.assertEqual(("Missing or invalid message for HSM certificate element"
381 " device"), str(e.exception))
383 @patch("admin.onboard.get_admin_hsm")
384 @patch("admin.unlock.get_hsm")
385 @patch("sys.stdin.readline")
386 def test_onboard_invalid_attestation_key(self, readline, get_hsm_unlock,
387 get_admin_hsm, get_hsm_onboard, *_):
388 get_hsm_onboard.return_value = self.dongle
389 get_hsm_unlock.return_value = self.dongle
390 get_admin_hsm.return_value = self.dongle
392 self.dongle.get_current_mode = Mock(return_value=HSM2Dongle.MODE.BOOTLOADER)
393 self.dongle.is_onboarded = Mock(side_effect=[False, True])
394 self.dongle.get_device_key = Mock(return_value=self.DEVICE_KEY)
395 self.dongle.setup_endorsement_key = Mock(return_value=self.INVALID_KEY)
396 readline.return_value = "yes\n"
398 with self.assertRaises(ValueError) as e:
399 do_onboard(self.default_options)
401 self.assertEqual(("Missing or invalid message for HSM certificate element"
402 " attestation"), str(e.exception))