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

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 admin.onboard import do_onboard 

29import json 

30from ledger.hsm2dongle import HSM2Dongle, HSM2DongleError 

31 

32import logging 

33 

34 

35logging.disable(logging.CRITICAL) 

36 

37 

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" 

45 

46 DEVICE_KEY = { 

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

48 "message": "1122334455", 

49 "signature": "aabbccddee", 

50 } 

51 

52 ENDORSEMENT_KEY = { 

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

54 "message": "5544332211", 

55 "signature": "eeddccbbaa", 

56 } 

57 

58 INVALID_KEY = { 

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

60 "message": "invalid-message", 

61 "signature": "invalid-signature", 

62 } 

63 

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) 

74 

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

94 

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 

103 

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" 

111 

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

113 do_onboard(self.default_options) 

114 

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

120 

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) 

127 

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 

136 

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

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

139 

140 with self.assertRaises(AdminError) as e: 

141 do_onboard(self.default_options) 

142 

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) 

146 

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 

155 

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" 

163 

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

165 with self.assertRaises(HSM2DongleError) as e: 

166 do_onboard(self.default_options) 

167 

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) 

174 

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 

183 

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" 

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

202 

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 

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

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

230 

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 

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

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" 

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.assertTrue(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_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" 

271 

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

273 with self.assertRaises(AdminError) as e: 

274 do_onboard(self.default_options) 

275 

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) 

281 

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 

286 

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

290 

291 options = self.default_options 

292 options.output_file_path = None 

293 with self.assertRaises(AdminError) as e: 

294 do_onboard(options) 

295 

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

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

298 

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) 

304 

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

306 

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) 

311 

312 with self.assertRaises(AdminError) as e: 

313 do_onboard(self.default_options) 

314 

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

316 

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 

325 

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" 

331 

332 with self.assertRaises(ValueError) as e: 

333 do_onboard(self.default_options) 

334 

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

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

337 

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 

346 

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" 

352 

353 with self.assertRaises(ValueError) as e: 

354 do_onboard(self.default_options) 

355 

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

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