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

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

32 

33import logging 

34 

35 

36logging.disable(logging.CRITICAL) 

37 

38 

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" 

46 

47 DEVICE_KEY = { 

48 "pubkey": "this-is-the-public-key", 

49 "message": "1122334455", 

50 "signature": "aabbccddee", 

51 } 

52 

53 ENDORSEMENT_KEY = { 

54 "pubkey": "this-is-another-public-key", 

55 "message": "5544332211", 

56 "signature": "eeddccbbaa", 

57 } 

58 

59 INVALID_KEY = { 

60 "pubkey": "this-is-the-public-key", 

61 "message": "invalid-message", 

62 "signature": "invalid-signature", 

63 } 

64 

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) 

76 

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

96 

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 

105 

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" 

113 

114 with patch("builtins.open", mock_open()) as file_mock: 

115 do_onboard(self.default_options) 

116 

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

122 

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) 

129 

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) 

136 

137 get_hsm_onboard.return_value = self.dongle 

138 get_hsm_unlock.return_value = self.dongle 

139 

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" 

144 

145 with patch("builtins.open", mock_open()): 

146 do_onboard(self.default_options) 

147 

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

150 

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) 

155 

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 

164 

165 self.dongle.get_current_mode = Mock(return_value=HSM2Dongle.MODE.BOOTLOADER) 

166 self.dongle.is_onboarded = Mock(return_value=True) 

167 

168 with self.assertRaises(AdminError) as e: 

169 do_onboard(self.default_options) 

170 

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) 

174 

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 

183 

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" 

191 

192 with patch("builtins.open", mock_open()) as file_mock: 

193 with self.assertRaises(HSM2DongleError) as e: 

194 do_onboard(self.default_options) 

195 

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) 

202 

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 

211 

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" 

219 

220 with patch("builtins.open", mock_open()) as file_mock: 

221 with self.assertRaises(HSM2DongleError) as e: 

222 do_onboard(self.default_options) 

223 

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) 

230 

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 

239 

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" 

247 

248 with patch("builtins.open", mock_open()) as file_mock: 

249 with self.assertRaises(HSM2DongleError) as e: 

250 do_onboard(self.default_options) 

251 

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) 

258 

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 

267 

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" 

275 

276 with patch("builtins.open", mock_open()) as file_mock: 

277 with self.assertRaises(HSM2DongleError) as e: 

278 do_onboard(self.default_options) 

279 

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) 

286 

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" 

299 

300 with patch("builtins.open", mock_open()) as file_mock: 

301 with self.assertRaises(AdminError) as e: 

302 do_onboard(self.default_options) 

303 

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) 

309 

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 

314 

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

318 

319 options = self.default_options 

320 options.output_file_path = None 

321 with self.assertRaises(AdminError) as e: 

322 do_onboard(options) 

323 

324 self.assertEqual("No output file path given", str(e.exception)) 

325 self.assertFalse(self.dongle.onboard.called) 

326 

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 

332 

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

336 

337 options = self.default_options 

338 options.output_file_path = None 

339 

340 do_onboard(options) 

341 

342 self.assertTrue(self.dongle.onboard.called) 

343 

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) 

349 

350 self.assertTrue(str(e.exception).startswith("Invalid pin given.")) 

351 

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) 

356 

357 with self.assertRaises(AdminError) as e: 

358 do_onboard(self.default_options) 

359 

360 self.assertTrue(str(e.exception).startswith("Device not in bootloader mode.")) 

361 

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 

370 

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" 

376 

377 with self.assertRaises(ValueError) as e: 

378 do_onboard(self.default_options) 

379 

380 self.assertEqual(("Missing or invalid message for HSM certificate element" 

381 " device"), str(e.exception)) 

382 

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 

391 

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" 

397 

398 with self.assertRaises(ValueError) as e: 

399 do_onboard(self.default_options) 

400 

401 self.assertEqual(("Missing or invalid message for HSM certificate element" 

402 " attestation"), str(e.exception))