Coverage for tests/sgx/test_hsm2dongle.py: 100%
185 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 unittest import TestCase
24from unittest.mock import Mock, patch, call
25from sgx.hsm2dongle import HSM2DongleSGX
26from ledger.hsm2dongle import HSM2DongleError
27from tests.ledger.test_hsm2dongle import \
28 TestHSM2DongleBase, HSM2DongleTestMode # noqa: F401
30import logging
32logging.disable(logging.CRITICAL)
35class TestHSM2DongleSGX(TestCase):
36 EXPECTED_DONGLE_TIMEOUT = 10
38 @patch("ledger.hsm2dongle_tcp.getDongle")
39 def setUp(self, getDongleMock):
40 self.dongle = Mock()
41 self.getDongleMock = getDongleMock
42 self.getDongleMock.return_value = self.dongle
43 self.hsm2dongle = HSM2DongleSGX("a-host", 1234, "a-debug-value")
45 self.getDongleMock.assert_not_called()
46 self.hsm2dongle.connect()
47 self.getDongleMock.assert_called_with("a-host", 1234, "a-debug-value")
48 self.assertEqual(self.hsm2dongle.dongle, self.dongle)
50 def assert_exchange_called(self, bs):
51 self.dongle.exchange.assert_called_with(bs, timeout=self.EXPECTED_DONGLE_TIMEOUT)
53 def assert_xchg_called_ith(self, i, bs):
54 self.assertGreaterEqual(self.dongle.exchange.call_count, i+1)
55 self.assertEqual(
56 call(bs, timeout=self.EXPECTED_DONGLE_TIMEOUT),
57 self.dongle.exchange.call_args_list[i], f"Call #{i} mismatch")
59 def test_echo_ok(self):
60 self.dongle.exchange.return_value = bytes([0x80, 0xA4, 0x41, 0x42, 0x43])
61 self.assertTrue(self.hsm2dongle.echo())
62 self.assert_exchange_called(bytes([0x80, 0xA4, 0x41, 0x42, 0x43]))
64 def test_echo_response_differs(self):
65 self.dongle.exchange.return_value = bytes([1, 2, 3])
66 self.assertFalse(self.hsm2dongle.echo())
67 self.assert_exchange_called(bytes([0x80, 0xA4, 0x41, 0x42, 0x43]))
69 def test_echo_error_triggered(self):
70 self.dongle.exchange.side_effect = RuntimeError("SomethingWentWrong")
71 with self.assertRaises(HSM2DongleError):
72 self.hsm2dongle.echo()
73 self.assert_exchange_called(bytes([0x80, 0xA4, 0x41, 0x42, 0x43]))
75 def test_unlock_ok(self):
76 self.dongle.exchange.return_value = bytes([0xAA, 0xBB, 0xCC, 0xDD])
77 self.assertTrue(self.hsm2dongle.unlock(b'a-password'))
78 self.assert_exchange_called(bytes([0x80, 0xA3, 0x00]) + b'a-password')
80 def test_unlock_wrong_pass(self):
81 self.dongle.exchange.return_value = bytes([0xAA, 0xBB, 0x00, 0xDD])
82 self.assertFalse(self.hsm2dongle.unlock(b'wrong-pass'))
83 self.assert_exchange_called(bytes([0x80, 0xA3, 0x00]) + b'wrong-pass')
85 def test_newpin_ok(self):
86 self.dongle.exchange.return_value = bytes([0xAA, 0xBB, 0x01, 0xDD])
87 self.assertTrue(self.hsm2dongle.new_pin(b'new-password'))
88 self.assert_exchange_called(bytes([0x80, 0xA5, 0x00]) + b'new-password')
90 def test_newpin_error(self):
91 self.dongle.exchange.return_value = bytes([0xAA, 0xBB, 0x55, 0xDD])
92 self.assertFalse(self.hsm2dongle.new_pin(b'new-password'))
93 self.assert_exchange_called(bytes([0x80, 0xA5, 0x00]) + b'new-password')
95 def test_get_retries(self):
96 self.dongle.exchange.return_value = bytes([0xAA, 0xBB, 0x05, 0xDD])
97 self.assertEqual(5, self.hsm2dongle.get_retries())
98 self.assert_exchange_called(bytes([0x80, 0xA2]))
100 def test_onboard_ok(self):
101 self.dongle.exchange.return_value = bytes([0xAA, 0xBB, 0x01])
102 self.assertTrue(self.hsm2dongle.onboard(bytes.fromhex("aa"*32), b"12345678"))
103 self.assert_exchange_called(bytes([0x80, 0xA0, 0x00]) +
104 bytes.fromhex("aa"*32) +
105 b"12345678")
107 def test_onboard_seed_invalid_type(self):
108 self.dongle.exchange.return_value = bytes([0xAA, 0xBB, 0x01])
110 with self.assertRaises(HSM2DongleError):
111 self.hsm2dongle.onboard(1234, b"12345678")
113 self.assertFalse(self.dongle.exchange.called)
115 def test_onboard_seed_invalid_length(self):
116 self.dongle.exchange.return_value = bytes([0xAA, 0xBB, 0x01])
118 with self.assertRaises(HSM2DongleError):
119 self.hsm2dongle.onboard(b"abcd", b"12345678")
121 self.assertFalse(self.dongle.exchange.called)
123 def test_onboard_pin_invalid_type(self):
124 self.dongle.exchange.return_value = bytes([0xAA, 0xBB, 0x01])
126 with self.assertRaises(HSM2DongleError):
127 self.hsm2dongle.onboard(bytes.fromhex("aa"*32), 4444)
129 self.assertFalse(self.dongle.exchange.called)
131 def test_onboard_error_result(self):
132 self.dongle.exchange.return_value = bytes([0xAA, 0xBB, 0xCC])
134 with self.assertRaises(HSM2DongleError):
135 self.hsm2dongle.onboard(bytes.fromhex("aa"*32), b'12345678')
137 self.assert_exchange_called(bytes([0x80, 0xA0, 0x00]) +
138 bytes.fromhex("aa"*32) +
139 b"12345678")
141 def test_onboard_error_xchg(self):
142 self.dongle.exchange.side_effect = RuntimeError("SomethingWentWrong")
144 with self.assertRaises(HSM2DongleError):
145 self.hsm2dongle.onboard(bytes.fromhex("aa"*32), b'12345678')
147 self.assert_exchange_called(bytes([0x80, 0xA0, 0x00]) +
148 bytes.fromhex("aa"*32) +
149 b"12345678")
151 def test_migrate_db_spec_ok(self):
152 self.dongle.exchange.side_effect = [
153 bytes([0xAA, 0xBB, 0xCC]),
154 bytes([0xAA, 0xBB, 0x01]),
155 bytes([0xAA, 0xBB, 0x01]),
156 bytes([0xAA, 0xBB, 0x00]),
157 ]
159 self.hsm2dongle.migrate_db_spec(
160 0x12, b"a"*5, b"b"*5,
161 [b"c"*10, b"d"*10, b"e"*10])
163 self.assertEqual(4, self.dongle.exchange.call_count)
164 self.assert_xchg_called_ith(0, bytes([0x80, 0xA6, 0x01, 0x12]) + b"aaaaabbbbb")
165 self.assert_xchg_called_ith(1, bytes([0x80, 0xA6, 0x02]) + b"cccccccccc")
166 self.assert_xchg_called_ith(2, bytes([0x80, 0xA6, 0x02]) + b"dddddddddd")
167 self.assert_xchg_called_ith(3, bytes([0x80, 0xA6, 0x02]) + b"eeeeeeeeee")
169 def test_migrate_db_spec_notenough_sigs(self):
170 self.dongle.exchange.side_effect = [
171 bytes([0xAA, 0xBB, 0xCC]),
172 bytes([0xAA, 0xBB, 0x01]),
173 bytes([0xAA, 0xBB, 0x01]),
174 ]
176 with self.assertRaises(HSM2DongleError) as e:
177 self.hsm2dongle.migrate_db_spec(
178 0x12, b"a"*5, b"b"*5,
179 [b"c"*10, b"d"*10])
181 self.assertIn("signatures gathered", e.exception.message)
183 self.assertEqual(3, self.dongle.exchange.call_count)
184 self.assert_xchg_called_ith(0, bytes([0x80, 0xA6, 0x01, 0x12]) + b"aaaaabbbbb")
185 self.assert_xchg_called_ith(1, bytes([0x80, 0xA6, 0x02]) + b"cccccccccc")
186 self.assert_xchg_called_ith(2, bytes([0x80, 0xA6, 0x02]) + b"dddddddddd")
188 def test_migrate_db_spec_err_raised(self):
189 self.dongle.exchange.side_effect = [
190 bytes([0xAA, 0xBB, 0xCC]),
191 HSM2DongleError("something happened"),
192 ]
194 with self.assertRaises(HSM2DongleError) as e:
195 self.hsm2dongle.migrate_db_spec(
196 0x12, b"a"*5, b"b"*5,
197 [b"c"*10, b"d"*10])
199 self.assertIn("something happened", e.exception.message)
201 self.assertEqual(2, self.dongle.exchange.call_count)
202 self.assert_xchg_called_ith(0, bytes([0x80, 0xA6, 0x01, 0x12]) + b"aaaaabbbbb")
203 self.assert_xchg_called_ith(1, bytes([0x80, 0xA6, 0x02]) + b"cccccccccc")
205 def test_migrate_db_get_evidence_ok(self):
206 self.dongle.exchange.side_effect = [
207 bytes([0xAA, 0xBB, 0x01]) + b"a"*3,
208 bytes([0xAA, 0xBB, 0x01]) + b"b"*5,
209 bytes([0xAA, 0xBB, 0x00]) + b"c"*7,
210 ]
212 self.assertEqual(b"aaabbbbbccccccc", self.hsm2dongle.migrate_db_get_evidence())
214 self.assertEqual(3, self.dongle.exchange.call_count)
215 for i in [0, 1, 2]:
216 self.assert_xchg_called_ith(i, bytes([0x80, 0xA6, 0x03]))
218 def test_migrate_db_get_evidence_err_raised(self):
219 self.dongle.exchange.side_effect = [
220 bytes([0xAA, 0xBB, 0x01]) + b"a"*3,
221 HSM2DongleError("oopsies"),
222 ]
224 with self.assertRaises(HSM2DongleError) as e:
225 self.hsm2dongle.migrate_db_get_evidence()
227 self.assertIn("oopsies", e.exception.message)
229 self.assertEqual(2, self.dongle.exchange.call_count)
230 for i in [0, 1]:
231 self.assert_xchg_called_ith(i, bytes([0x80, 0xA6, 0x03]))
233 def test_migrate_db_send_evidence_ok(self):
234 self.dongle.exchange.side_effect = [
235 bytes([0xAA, 0xBB, 0x01]),
236 bytes([0xAA, 0xBB, 0x01]),
237 bytes([0xAA, 0xBB, 0x01]),
238 bytes([0xAA, 0xBB, 0x01]),
239 bytes([0xAA, 0xBB, 0x01]),
240 bytes([0xAA, 0xBB, 0x01]),
241 bytes([0xAA, 0xBB, 0x00]),
242 ]
244 self.hsm2dongle.migrate_db_send_evidence(
245 b"1"*80 +
246 b"2"*80 +
247 b"3"*80 +
248 b"4"*80 +
249 b"5"*80 +
250 b"6"*80 +
251 b"7"*56
252 )
254 self.assertEqual(7, self.dongle.exchange.call_count)
255 self.assert_xchg_called_ith(0, bytes([0x80, 0xA6, 0x04, 0x02, 0x18]) + b"1"*80)
256 self.assert_xchg_called_ith(1, bytes([0x80, 0xA6, 0x04]) + b"2"*80)
257 self.assert_xchg_called_ith(2, bytes([0x80, 0xA6, 0x04]) + b"3"*80)
258 self.assert_xchg_called_ith(3, bytes([0x80, 0xA6, 0x04]) + b"4"*80)
259 self.assert_xchg_called_ith(4, bytes([0x80, 0xA6, 0x04]) + b"5"*80)
260 self.assert_xchg_called_ith(5, bytes([0x80, 0xA6, 0x04]) + b"6"*80)
261 self.assert_xchg_called_ith(6, bytes([0x80, 0xA6, 0x04]) + b"7"*56)
263 def test_migrate_db_send_evidence_noack(self):
264 self.dongle.exchange.side_effect = [
265 bytes([0xAA, 0xBB, 0x01]),
266 bytes([0xAA, 0xBB, 0x01]),
267 bytes([0xAA, 0xBB, 0x01]),
268 ]
270 with self.assertRaises(HSM2DongleError) as e:
271 self.hsm2dongle.migrate_db_send_evidence(
272 b"1"*80 +
273 b"2"*80 +
274 b"3"*67
275 )
277 self.assertIn("evidence ack", e.exception.message)
279 self.assertEqual(3, self.dongle.exchange.call_count)
280 self.assert_xchg_called_ith(0, bytes([0x80, 0xA6, 0x04, 0x00, 0xe3]) + b"1"*80)
281 self.assert_xchg_called_ith(1, bytes([0x80, 0xA6, 0x04]) + b"2"*80)
282 self.assert_xchg_called_ith(2, bytes([0x80, 0xA6, 0x04]) + b"3"*67)
284 def test_migrate_db_send_evidence_err_raised(self):
285 self.dongle.exchange.side_effect = [
286 bytes([0xAA, 0xBB, 0x01]),
287 bytes([0xAA, 0xBB, 0x01]),
288 HSM2DongleError("sgx made a boo boo")
289 ]
291 with self.assertRaises(HSM2DongleError) as e:
292 self.hsm2dongle.migrate_db_send_evidence(
293 b"1"*40 +
294 b"2"*40 +
295 b"3"*1280
296 )
298 self.assertIn("boo boo", e.exception.message)
300 self.assertEqual(3, self.dongle.exchange.call_count)
301 self.assert_xchg_called_ith(
302 0, bytes([0x80, 0xA6, 0x04, 0x05, 0x50]) + b"1"*40 + b"2"*40)
303 self.assert_xchg_called_ith(
304 1, bytes([0x80, 0xA6, 0x04]) + b"3"*80)
305 self.assert_xchg_called_ith(
306 2, bytes([0x80, 0xA6, 0x04]) + b"3"*80)
308 def test_migrate_db_get_data_ok(self):
309 self.dongle.exchange.return_value = \
310 bytes([0xAA, 0xBB, 0xCC]) + b"0123456789abcdef"
312 self.assertEqual(b"0123456789abcdef", self.hsm2dongle.migrate_db_get_data())
314 self.assertEqual(1, self.dongle.exchange.call_count)
315 self.assert_xchg_called_ith(0, bytes([0x80, 0xA6, 0x05]))
317 def test_migrate_db_get_data_nodata(self):
318 self.dongle.exchange.return_value = bytes([0xAA, 0xBB, 0xCC])
320 with self.assertRaises(HSM2DongleError) as e:
321 self.hsm2dongle.migrate_db_get_data()
323 self.assertIn("data gathering failed", e.exception.message)
325 self.assertEqual(1, self.dongle.exchange.call_count)
326 self.assert_xchg_called_ith(0, bytes([0x80, 0xA6, 0x05]))
328 def test_migrate_db_get_data_err_raised(self):
329 self.dongle.exchange.side_effect = HSM2DongleError("migration nana")
331 with self.assertRaises(HSM2DongleError) as e:
332 self.hsm2dongle.migrate_db_get_data()
334 self.assertIn("migration nana", e.exception.message)
336 self.assertEqual(1, self.dongle.exchange.call_count)
337 self.assert_xchg_called_ith(0, bytes([0x80, 0xA6, 0x05]))
339 def test_migrate_db_send_data_ok(self):
340 self.dongle.exchange.return_value = bytes([0xAA, 0xBB, 0xCC])
342 self.hsm2dongle.migrate_db_send_data(b"aabbccddeeff44556677")
344 self.assertEqual(1, self.dongle.exchange.call_count)
345 self.assert_xchg_called_ith(
346 0, bytes([0x80, 0xA6, 0x05]) + b"aabbccddeeff44556677")
348 def test_migrate_db_send_data_err_raised(self):
349 self.dongle.exchange.side_effect = HSM2DongleError("data bad bad")
351 with self.assertRaises(HSM2DongleError) as e:
352 self.hsm2dongle.migrate_db_send_data(b"aabbccddeeff44556677")
354 self.assertIn("bad bad", e.exception.message)
356 self.assertEqual(1, self.dongle.exchange.call_count)
357 self.assert_xchg_called_ith(
358 0, bytes([0x80, 0xA6, 0x05]) + b"aabbccddeeff44556677")