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

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. 

22 

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 

29 

30import logging 

31 

32logging.disable(logging.CRITICAL) 

33 

34 

35class TestHSM2DongleSGX(TestCase): 

36 EXPECTED_DONGLE_TIMEOUT = 10 

37 

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") 

44 

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) 

49 

50 def assert_exchange_called(self, bs): 

51 self.dongle.exchange.assert_called_with(bs, timeout=self.EXPECTED_DONGLE_TIMEOUT) 

52 

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") 

58 

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])) 

63 

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])) 

68 

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])) 

74 

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') 

79 

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') 

84 

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') 

89 

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') 

94 

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])) 

99 

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") 

106 

107 def test_onboard_seed_invalid_type(self): 

108 self.dongle.exchange.return_value = bytes([0xAA, 0xBB, 0x01]) 

109 

110 with self.assertRaises(HSM2DongleError): 

111 self.hsm2dongle.onboard(1234, b"12345678") 

112 

113 self.assertFalse(self.dongle.exchange.called) 

114 

115 def test_onboard_seed_invalid_length(self): 

116 self.dongle.exchange.return_value = bytes([0xAA, 0xBB, 0x01]) 

117 

118 with self.assertRaises(HSM2DongleError): 

119 self.hsm2dongle.onboard(b"abcd", b"12345678") 

120 

121 self.assertFalse(self.dongle.exchange.called) 

122 

123 def test_onboard_pin_invalid_type(self): 

124 self.dongle.exchange.return_value = bytes([0xAA, 0xBB, 0x01]) 

125 

126 with self.assertRaises(HSM2DongleError): 

127 self.hsm2dongle.onboard(bytes.fromhex("aa"*32), 4444) 

128 

129 self.assertFalse(self.dongle.exchange.called) 

130 

131 def test_onboard_error_result(self): 

132 self.dongle.exchange.return_value = bytes([0xAA, 0xBB, 0xCC]) 

133 

134 with self.assertRaises(HSM2DongleError): 

135 self.hsm2dongle.onboard(bytes.fromhex("aa"*32), b'12345678') 

136 

137 self.assert_exchange_called(bytes([0x80, 0xA0, 0x00]) + 

138 bytes.fromhex("aa"*32) + 

139 b"12345678") 

140 

141 def test_onboard_error_xchg(self): 

142 self.dongle.exchange.side_effect = RuntimeError("SomethingWentWrong") 

143 

144 with self.assertRaises(HSM2DongleError): 

145 self.hsm2dongle.onboard(bytes.fromhex("aa"*32), b'12345678') 

146 

147 self.assert_exchange_called(bytes([0x80, 0xA0, 0x00]) + 

148 bytes.fromhex("aa"*32) + 

149 b"12345678") 

150 

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 ] 

158 

159 self.hsm2dongle.migrate_db_spec( 

160 0x12, b"a"*5, b"b"*5, 

161 [b"c"*10, b"d"*10, b"e"*10]) 

162 

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") 

168 

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 ] 

175 

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]) 

180 

181 self.assertIn("signatures gathered", e.exception.message) 

182 

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") 

187 

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 ] 

193 

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]) 

198 

199 self.assertIn("something happened", e.exception.message) 

200 

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") 

204 

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 ] 

211 

212 self.assertEqual(b"aaabbbbbccccccc", self.hsm2dongle.migrate_db_get_evidence()) 

213 

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])) 

217 

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 ] 

223 

224 with self.assertRaises(HSM2DongleError) as e: 

225 self.hsm2dongle.migrate_db_get_evidence() 

226 

227 self.assertIn("oopsies", e.exception.message) 

228 

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])) 

232 

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 ] 

243 

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 ) 

253 

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) 

262 

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 ] 

269 

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 ) 

276 

277 self.assertIn("evidence ack", e.exception.message) 

278 

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) 

283 

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 ] 

290 

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 ) 

297 

298 self.assertIn("boo boo", e.exception.message) 

299 

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) 

307 

308 def test_migrate_db_get_data_ok(self): 

309 self.dongle.exchange.return_value = \ 

310 bytes([0xAA, 0xBB, 0xCC]) + b"0123456789abcdef" 

311 

312 self.assertEqual(b"0123456789abcdef", self.hsm2dongle.migrate_db_get_data()) 

313 

314 self.assertEqual(1, self.dongle.exchange.call_count) 

315 self.assert_xchg_called_ith(0, bytes([0x80, 0xA6, 0x05])) 

316 

317 def test_migrate_db_get_data_nodata(self): 

318 self.dongle.exchange.return_value = bytes([0xAA, 0xBB, 0xCC]) 

319 

320 with self.assertRaises(HSM2DongleError) as e: 

321 self.hsm2dongle.migrate_db_get_data() 

322 

323 self.assertIn("data gathering failed", e.exception.message) 

324 

325 self.assertEqual(1, self.dongle.exchange.call_count) 

326 self.assert_xchg_called_ith(0, bytes([0x80, 0xA6, 0x05])) 

327 

328 def test_migrate_db_get_data_err_raised(self): 

329 self.dongle.exchange.side_effect = HSM2DongleError("migration nana") 

330 

331 with self.assertRaises(HSM2DongleError) as e: 

332 self.hsm2dongle.migrate_db_get_data() 

333 

334 self.assertIn("migration nana", e.exception.message) 

335 

336 self.assertEqual(1, self.dongle.exchange.call_count) 

337 self.assert_xchg_called_ith(0, bytes([0x80, 0xA6, 0x05])) 

338 

339 def test_migrate_db_send_data_ok(self): 

340 self.dongle.exchange.return_value = bytes([0xAA, 0xBB, 0xCC]) 

341 

342 self.hsm2dongle.migrate_db_send_data(b"aabbccddeeff44556677") 

343 

344 self.assertEqual(1, self.dongle.exchange.call_count) 

345 self.assert_xchg_called_ith( 

346 0, bytes([0x80, 0xA6, 0x05]) + b"aabbccddeeff44556677") 

347 

348 def test_migrate_db_send_data_err_raised(self): 

349 self.dongle.exchange.side_effect = HSM2DongleError("data bad bad") 

350 

351 with self.assertRaises(HSM2DongleError) as e: 

352 self.hsm2dongle.migrate_db_send_data(b"aabbccddeeff44556677") 

353 

354 self.assertIn("bad bad", e.exception.message) 

355 

356 self.assertEqual(1, self.dongle.exchange.call_count) 

357 self.assert_xchg_called_ith( 

358 0, bytes([0x80, 0xA6, 0x05]) + b"aabbccddeeff44556677")