Coverage for tests/admin/test_onboard.py: 100%
224 statements
« prev ^ index » next coverage.py v7.2.7, created at 2024-04-05 20:41 +0000
« prev ^ index » next coverage.py v7.2.7, created at 2024-04-05 20:41 +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 admin.onboard import do_onboard
29import json
30from ledger.hsm2dongle import HSM2Dongle, HSM2DongleError
32import logging
35logging.disable(logging.CRITICAL)
38@patch("sys.stdout.write")
39@patch("time.sleep")
40@patch("admin.onboard.info")
41@patch("admin.onboard.get_hsm")
42class TestOnboard(TestCase):
43 VALID_PIN = "1234ABCD"
44 INVALID_PIN = "123456789"
46 DEVICE_KEY = {
47 "pubkey": "this-is-the-public-key",
48 "message": "1122334455",
49 "signature": "aabbccddee",
50 }
52 ENDORSEMENT_KEY = {
53 "pubkey": "this-is-another-public-key",
54 "message": "5544332211",
55 "signature": "eeddccbbaa",
56 }
58 INVALID_KEY = {
59 "pubkey": "this-is-the-public-key",
60 "message": "invalid-message",
61 "signature": "invalid-signature",
62 }
64 def setUp(self):
65 self.certificate_path = "cert-path"
66 options = {
67 "pin": self.VALID_PIN,
68 "output_file_path": self.certificate_path,
69 "any_pin": False,
70 "no_exec": False,
71 "verbose": False
72 }
73 self.default_options = SimpleNamespace(**options)
75 self.expected_cert = HSMCertificate()
76 self.expected_cert.add_element(
77 HSMCertificateElement({
78 "name": "attestation",
79 "message": self.ENDORSEMENT_KEY["message"],
80 "signature": self.ENDORSEMENT_KEY["signature"],
81 "signed_by": "device",
82 })
83 )
84 self.expected_cert.add_element(
85 HSMCertificateElement({
86 "name": "device",
87 "message": self.DEVICE_KEY["message"],
88 "signature": self.DEVICE_KEY["signature"],
89 "signed_by": "root",
90 })
91 )
92 self.expected_cert.add_target("attestation")
93 self.dongle = Mock()
95 @patch("admin.onboard.get_admin_hsm")
96 @patch("admin.unlock.get_hsm")
97 @patch("sys.stdin.readline")
98 def test_onboard(self, readline, get_hsm_unlock, get_admin_hsm,
99 get_hsm_onboard, info_mock, *_):
100 get_hsm_onboard.return_value = self.dongle
101 get_hsm_unlock.return_value = self.dongle
102 get_admin_hsm.return_value = self.dongle
104 self.dongle.get_current_mode = Mock(return_value=HSM2Dongle.MODE.BOOTLOADER)
105 self.dongle.is_onboarded = Mock(side_effect=[False, True])
106 self.dongle.get_device_key = Mock(return_value=self.DEVICE_KEY)
107 self.dongle.setup_endorsement_key = Mock(return_value=self.ENDORSEMENT_KEY)
108 self.dongle.handshake = Mock()
109 self.dongle.onboard = Mock()
110 readline.return_value = "yes\n"
112 with patch("builtins.open", mock_open()) as file_mock:
113 do_onboard(self.default_options)
115 self.assertEqual(info_mock.call_args_list[5][0][0], "Onboarded: No")
116 self.assertEqual(info_mock.call_args_list[10][0][0], "Onboarded")
117 self.assertEqual(info_mock.call_args_list[14][0][0], "Device key gathered")
118 self.assertEqual(info_mock.call_args_list[16][0][0],
119 "Attestation key setup complete")
121 self.assertEqual([call(self.certificate_path, "w")], file_mock.call_args_list)
122 self.assertEqual([call("%s\n" %
123 json.dumps(self.expected_cert.to_dict(), indent=2))],
124 file_mock.return_value.write.call_args_list)
125 self.assertTrue(self.dongle.onboard.called)
126 self.assertTrue(self.dongle.handshake.called)
128 @patch("admin.onboard.get_admin_hsm")
129 @patch("admin.unlock.get_hsm")
130 @patch("sys.stdin.readline")
131 def test_onboard_already_onboarded(self, readline, get_hsm_unlock, get_admin_hsm,
132 get_hsm_onboard, info_mock, *_):
133 get_hsm_onboard.return_value = self.dongle
134 get_hsm_unlock.return_value = self.dongle
135 get_admin_hsm.return_value = self.dongle
137 self.dongle.get_current_mode = Mock(return_value=HSM2Dongle.MODE.BOOTLOADER)
138 self.dongle.is_onboarded = Mock(return_value=True)
140 with self.assertRaises(AdminError) as e:
141 do_onboard(self.default_options)
143 self.assertEqual(info_mock.call_args_list[5][0][0], "Onboarded: Yes")
144 self.assertEqual(e.exception.args[0], "Device already onboarded")
145 self.assertFalse(self.dongle.onboard.called)
147 @patch("admin.onboard.get_admin_hsm")
148 @patch("admin.unlock.get_hsm")
149 @patch("sys.stdin.readline")
150 def test_onboard_onboard_error(self, readline, get_hsm_unlock, get_admin_hsm,
151 get_hsm_onboard, *_):
152 get_hsm_onboard.return_value = self.dongle
153 get_hsm_unlock.return_value = self.dongle
154 get_admin_hsm.return_value = self.dongle
156 self.dongle.get_current_mode = Mock(return_value=HSM2Dongle.MODE.BOOTLOADER)
157 self.dongle.is_onboarded = Mock(return_value=False)
158 self.dongle.get_device_key = Mock()
159 self.dongle.setup_endorsement_key = Mock()
160 self.dongle.handshake = Mock()
161 self.dongle.onboard = Mock(side_effect=HSM2DongleError("error-msg"))
162 readline.return_value = "yes\n"
164 with patch("builtins.open", mock_open()) as file_mock:
165 with self.assertRaises(HSM2DongleError) as e:
166 do_onboard(self.default_options)
168 self.assertTrue(self.dongle.onboard.called)
169 self.assertEqual("error-msg", str(e.exception))
170 self.assertFalse(self.dongle.get_device_key.called)
171 self.assertFalse(self.dongle.setup_endorsement_key.called)
172 self.assertFalse(self.dongle.handshake.called)
173 self.assertFalse(file_mock.return_value.write.called)
175 @patch("admin.onboard.get_admin_hsm")
176 @patch("admin.unlock.get_hsm")
177 @patch("sys.stdin.readline")
178 def test_onboard_handshake_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(side_effect=[False, True])
186 self.dongle.get_device_key = Mock()
187 self.dongle.setup_endorsement_key = Mock()
188 self.dongle.handshake = Mock(side_effect=HSM2DongleError("error-msg"))
189 self.dongle.onboard = Mock()
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.assertEqual("error-msg", str(e.exception))
197 self.assertTrue(self.dongle.onboard.called)
198 self.assertTrue(self.dongle.handshake.called)
199 self.assertFalse(self.dongle.get_device_key.called)
200 self.assertFalse(self.dongle.setup_endorsement_key.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_getkey_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(side_effect=HSM2DongleError("error-msg"))
215 self.dongle.setup_endorsement_key = Mock()
216 self.dongle.handshake = Mock()
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.assertTrue(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_setupkey_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()
243 self.dongle.setup_endorsement_key = Mock(side_effect=HSM2DongleError("error-msg"))
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.assertTrue(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_user_cancelled(self, readline, hsm_unlock, hsm_admin,
263 hsm_onboard, *_):
264 hsm_onboard.return_value = self.dongle
265 hsm_unlock.return_value = self.dongle
266 self.dongle.get_current_mode = Mock(return_value=HSM2Dongle.MODE.BOOTLOADER)
267 self.dongle.is_onboarded = Mock(return_value=False)
268 self.dongle.onboard = Mock()
269 hsm_admin.return_value = self.dongle
270 readline.return_value = "no\n"
272 with patch("builtins.open", mock_open()) as file_mock:
273 with self.assertRaises(AdminError) as e:
274 do_onboard(self.default_options)
276 self.assertEqual("Cancelled by user", str(e.exception))
277 self.assertFalse(self.dongle.onboard.called)
278 self.assertFalse(self.dongle.get_device_key.called)
279 self.assertFalse(self.dongle.setup_endorsement_key.called)
280 self.assertFalse(file_mock.return_value.write.called)
282 @patch("sys.stdin.readline")
283 def test_onboard_no_output_file(self, readline, get_hsm, *_):
284 readline.return_value = "yes\n"
285 get_hsm.return_value = self.dongle
287 self.dongle.get_current_mode = Mock(return_value=HSM2Dongle.MODE.BOOTLOADER)
288 self.dongle.is_onboarded = Mock(return_value=False)
289 self.dongle.onboard = Mock()
291 options = self.default_options
292 options.output_file_path = None
293 with self.assertRaises(AdminError) as e:
294 do_onboard(options)
296 self.assertEqual("No output file path given", str(e.exception))
297 self.assertFalse(self.dongle.onboard.called)
299 def test_onboard_invalid_pin(self, *_):
300 options = self.default_options
301 options.pin = self.INVALID_PIN
302 with self.assertRaises(AdminError) as e:
303 do_onboard(options)
305 self.assertTrue(str(e.exception).startswith("Invalid pin given."))
307 def test_onboard_invalid_mode(self, get_hsm, *_):
308 get_hsm.return_value = self.dongle
309 self.dongle.get_current_mode = Mock(return_value=HSM2Dongle.MODE.SIGNER)
310 self.dongle.is_onboarded = Mock(return_value=False)
312 with self.assertRaises(AdminError) as e:
313 do_onboard(self.default_options)
315 self.assertTrue(str(e.exception).startswith("Device not in bootloader mode."))
317 @patch("admin.onboard.get_admin_hsm")
318 @patch("admin.unlock.get_hsm")
319 @patch("sys.stdin.readline")
320 def test_onboard_invalid_device_key(self, readline, get_hsm_unlock, get_admin_hsm,
321 get_hsm_onboard, *_):
322 get_hsm_onboard.return_value = self.dongle
323 get_hsm_unlock.return_value = self.dongle
324 get_admin_hsm.return_value = self.dongle
326 self.dongle.get_current_mode = Mock(return_value=HSM2Dongle.MODE.BOOTLOADER)
327 self.dongle.is_onboarded = Mock(side_effect=[False, True])
328 self.dongle.get_device_key = Mock(return_value=self.INVALID_KEY)
329 self.dongle.setup_endorsement_key = Mock(return_value=self.ENDORSEMENT_KEY)
330 readline.return_value = "yes\n"
332 with self.assertRaises(ValueError) as e:
333 do_onboard(self.default_options)
335 self.assertEqual(("Missing or invalid message for HSM certificate element"
336 " device"), str(e.exception))
338 @patch("admin.onboard.get_admin_hsm")
339 @patch("admin.unlock.get_hsm")
340 @patch("sys.stdin.readline")
341 def test_onboard_invalid_attestation_key(self, readline, get_hsm_unlock,
342 get_admin_hsm, get_hsm_onboard, *_):
343 get_hsm_onboard.return_value = self.dongle
344 get_hsm_unlock.return_value = self.dongle
345 get_admin_hsm.return_value = self.dongle
347 self.dongle.get_current_mode = Mock(return_value=HSM2Dongle.MODE.BOOTLOADER)
348 self.dongle.is_onboarded = Mock(side_effect=[False, True])
349 self.dongle.get_device_key = Mock(return_value=self.DEVICE_KEY)
350 self.dongle.setup_endorsement_key = Mock(return_value=self.INVALID_KEY)
351 readline.return_value = "yes\n"
353 with self.assertRaises(ValueError) as e:
354 do_onboard(self.default_options)
356 self.assertEqual(("Missing or invalid message for HSM certificate element"
357 " attestation"), str(e.exception))