Coverage for tests/admin/test_ledger_attestation.py: 100%

173 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 

23import json 

24 

25from types import SimpleNamespace 

26from unittest import TestCase 

27from unittest.mock import Mock, call, patch, mock_open 

28from admin.ledger_attestation import do_attestation 

29from admin.certificate import HSMCertificate 

30from admin.misc import AdminError 

31 

32 

33@patch("sys.stdout.write") 

34@patch("time.sleep") 

35@patch("admin.ledger_attestation.do_unlock") 

36@patch("admin.ledger_attestation.get_hsm") 

37@patch("admin.ledger_attestation.HSMCertificate.from_jsonfile") 

38class TestAttestation(TestCase): 

39 def setupMocks(self, from_jsonfile, get_hsm): 

40 from_jsonfile.return_value = HSMCertificate({ 

41 "version": 1, 

42 "targets": ["attestation", "device"], 

43 "elements": [ 

44 { 

45 "name": "attestation", 

46 "message": '11' * 32, 

47 "signature": '22' * 32, 

48 "signed_by": "device" 

49 }, 

50 { 

51 "name": "device", 

52 "message": '33' * 32, 

53 "signature": '44' * 32, 

54 "signed_by": "root" 

55 }] 

56 }) 

57 get_hsm.return_value = Mock() 

58 hsm = get_hsm.return_value 

59 hsm.get_ui_attestation = Mock(return_value={ 

60 'message': 'aa' * 32, 

61 'signature': 'bb' * 32, 

62 'app_hash': 'cc' * 32 

63 }) 

64 hsm.exit_menu = Mock() 

65 hsm.disconnect = Mock() 

66 hsm.get_powhsm_attestation = Mock(return_value={ 

67 'envelope': 'dd' * 32, 

68 'message': 'dd' * 32, 

69 'signature': 'ee' * 32, 

70 'app_hash': 'ff' * 32 

71 }) 

72 

73 def setupDefaultOptions(self): 

74 options = SimpleNamespace() 

75 options.output_file_path = 'out-path' 

76 options.attestation_certificate_file_path = 'att-cert-path' 

77 options.verbose = False 

78 options.attestation_ud_source = 'aa' * 32 

79 return options 

80 

81 @patch('admin.ledger_attestation.get_ud_value_for_attestation') 

82 def test_attestation_ok(self, get_ud_value_for_attestation, 

83 from_jsonfile, get_hsm, *_): 

84 self.setupMocks(from_jsonfile, get_hsm) 

85 get_ud_value_for_attestation.return_value = 'bb'*32 

86 

87 options = self.setupDefaultOptions() 

88 options.attestation_ud_source = 'an-url' 

89 with patch('builtins.open', mock_open()) as file_mock: 

90 do_attestation(options) 

91 

92 self.assertEqual([call(options.attestation_certificate_file_path)], 

93 from_jsonfile.call_args_list) 

94 self.assertEqual([call('an-url')], get_ud_value_for_attestation.call_args_list) 

95 self.assertNotEqual([call(options.attestation_ud_source)], 

96 get_hsm.return_value.get_ui_attestation.call_args_list) 

97 self.assertEqual([call('bb' * 32)], 

98 get_hsm.return_value.get_ui_attestation.call_args_list) 

99 self.assertEqual([call(options.verbose), call(options.verbose)], 

100 get_hsm.call_args_list) 

101 self.assertEqual([call(options.output_file_path, 'w')], file_mock.call_args_list) 

102 self.assertEqual([call("%s\n" % json.dumps({ 

103 'version': 1, 

104 'targets': [ 

105 'ui', 

106 'signer' 

107 ], 

108 'elements': [ 

109 { 

110 "name": "attestation", 

111 "message": '11' * 32, 

112 "signature": '22' * 32, 

113 "signed_by": "device" 

114 }, 

115 { 

116 "name": "device", 

117 "message": '33' * 32, 

118 "signature": '44' * 32, 

119 "signed_by": "root" 

120 }, 

121 { 

122 'name': 'ui', 

123 'message': 'aa' * 32, 

124 'signature': 'bb' * 32, 

125 'signed_by': 'attestation', 

126 'tweak': 'cc' * 32 

127 }, 

128 { 

129 'name': 'signer', 

130 'message': 'dd' * 32, 

131 'signature': 'ee' * 32, 

132 'signed_by': 'attestation', 

133 'tweak': 'ff' * 32 

134 } 

135 ] 

136 }, indent=2))], 

137 file_mock.return_value.write.call_args_list) 

138 

139 def test_attestation_no_output_file(self, from_jsonfile, get_hsm, *_): 

140 self.setupMocks(from_jsonfile, get_hsm) 

141 options = self.setupDefaultOptions() 

142 options.output_file_path = None 

143 with patch('builtins.open', mock_open()) as file_mock: 

144 with self.assertRaises(AdminError): 

145 do_attestation(options) 

146 self.assertFalse(from_jsonfile.called) 

147 self.assertFalse(get_hsm.called) 

148 self.assertFalse(file_mock.return_value.write.called) 

149 

150 def test_attestation_no_att_cert_file(self, from_jsonfile, get_hsm, *_): 

151 self.setupMocks(from_jsonfile, get_hsm) 

152 options = self.setupDefaultOptions() 

153 options.attestation_certificate_file_path = None 

154 with patch('builtins.open', mock_open()) as file_mock: 

155 with self.assertRaises(AdminError): 

156 do_attestation(options) 

157 self.assertFalse(from_jsonfile.called) 

158 self.assertFalse(get_hsm.called) 

159 self.assertFalse(file_mock.return_value.write.called) 

160 

161 def test_attestation_invalid_jsonfile(self, from_jsonfile, get_hsm, *_): 

162 self.setupMocks(from_jsonfile, get_hsm) 

163 from_jsonfile.side_effect = AdminError() 

164 options = self.setupDefaultOptions() 

165 with patch('builtins.open', mock_open()) as file_mock: 

166 with self.assertRaises(AdminError): 

167 do_attestation(options) 

168 self.assertTrue(from_jsonfile.called) 

169 self.assertFalse(get_hsm.called) 

170 self.assertFalse(file_mock.return_value.write.called) 

171 

172 @patch('admin.ledger_attestation.get_ud_value_for_attestation') 

173 def test_attestation_get_ud_value_for_attestation_error(self, 

174 get_ud_value_for_attestation, 

175 from_jsonfile, get_hsm, *_): 

176 self.setupMocks(from_jsonfile, get_hsm) 

177 get_ud_value_for_attestation.side_effect = AdminError('error-msg') 

178 options = self.setupDefaultOptions() 

179 options.attestation_ud_source = 'another-url' 

180 with patch('builtins.open', mock_open()) as file_mock: 

181 with self.assertRaises(AdminError): 

182 do_attestation(options) 

183 self.assertEqual([call('another-url')], 

184 get_ud_value_for_attestation.call_args_list) 

185 self.assertTrue(from_jsonfile.called) 

186 self.assertFalse(get_hsm.called) 

187 self.assertFalse(file_mock.return_value.write.called) 

188 

189 def test_attestation_unlock_error(self, from_jsonfile, get_hsm, do_unlock, *_): 

190 self.setupMocks(from_jsonfile, get_hsm) 

191 do_unlock.side_effect = Exception() 

192 options = self.setupDefaultOptions() 

193 with patch('builtins.open', mock_open()) as file_mock: 

194 with self.assertRaises(AdminError): 

195 do_attestation(options) 

196 self.assertTrue(from_jsonfile.called) 

197 self.assertFalse(get_hsm.called) 

198 self.assertFalse(file_mock.return_value.write.called) 

199 

200 def test_attestation_get_hsm_error(self, from_jsonfile, get_hsm, *_): 

201 self.setupMocks(from_jsonfile, get_hsm) 

202 get_hsm.side_effect = Exception() 

203 options = self.setupDefaultOptions() 

204 with patch('builtins.open', mock_open()) as file_mock: 

205 with self.assertRaises(Exception): 

206 do_attestation(options) 

207 self.assertTrue(from_jsonfile.called) 

208 self.assertTrue(get_hsm.called) 

209 self.assertFalse(file_mock.return_value.write.called) 

210 

211 def test_attestation_get_ui_attestation_error(self, from_jsonfile, get_hsm, *_): 

212 self.setupMocks(from_jsonfile, get_hsm) 

213 hsm = get_hsm.return_value 

214 hsm.get_ui_attestation.side_effect = Exception() 

215 options = self.setupDefaultOptions() 

216 with patch('builtins.open', mock_open()) as file_mock: 

217 with self.assertRaises(AdminError): 

218 do_attestation(options) 

219 self.assertTrue(from_jsonfile.called) 

220 self.assertTrue(get_hsm.called) 

221 self.assertFalse(file_mock.return_value.write.called) 

222 

223 def test_attestation_get_powhsm_attestation_error(self, from_jsonfile, get_hsm, *_): 

224 self.setupMocks(from_jsonfile, get_hsm) 

225 hsm = get_hsm.return_value 

226 hsm.get_powhsm_attestation.side_effect = Exception() 

227 options = self.setupDefaultOptions() 

228 with patch('builtins.open', mock_open()) as file_mock: 

229 with self.assertRaises(AdminError): 

230 do_attestation(options) 

231 self.assertTrue(from_jsonfile.called) 

232 self.assertTrue(get_hsm.called) 

233 self.assertFalse(file_mock.return_value.write.called) 

234 

235 def test_attestation_get_powhsm_attestation_envelope_msg_differ(self, from_jsonfile, 

236 get_hsm, *_): 

237 self.setupMocks(from_jsonfile, get_hsm) 

238 hsm = get_hsm.return_value 

239 hsm.get_powhsm_attestation.return_value["envelope"] = "11"*32 

240 hsm.get_powhsm_attestation.return_value["message"] = "22"*32 

241 options = self.setupDefaultOptions() 

242 with patch('builtins.open', mock_open()) as file_mock: 

243 with self.assertRaises(AdminError): 

244 do_attestation(options) 

245 self.assertTrue(from_jsonfile.called) 

246 self.assertTrue(get_hsm.called) 

247 self.assertFalse(file_mock.return_value.write.called) 

248 

249 @patch("admin.ledger_attestation.HSMCertificate.add_element") 

250 def test_attestation_add_element_error(self, add_element, from_jsonfile, get_hsm, *_): 

251 self.setupMocks(from_jsonfile, get_hsm) 

252 add_element.side_effect = Exception() 

253 options = self.setupDefaultOptions() 

254 with patch('builtins.open', mock_open()) as file_mock: 

255 with self.assertRaises(Exception): 

256 do_attestation(options) 

257 self.assertTrue(from_jsonfile.called) 

258 self.assertTrue(get_hsm.called) 

259 self.assertFalse(file_mock.return_value.write.called) 

260 

261 @patch("admin.ledger_attestation.HSMCertificate.add_target") 

262 def test_attestation_add_target_error(self, add_target, from_jsonfile, get_hsm, *_): 

263 self.setupMocks(from_jsonfile, get_hsm) 

264 add_target.side_effect = ValueError() 

265 options = self.setupDefaultOptions() 

266 with patch('builtins.open', mock_open()) as file_mock: 

267 with self.assertRaises(ValueError): 

268 do_attestation(options) 

269 self.assertTrue(from_jsonfile.called) 

270 self.assertTrue(get_hsm.called) 

271 self.assertFalse(file_mock.return_value.write.called) 

272 

273 @patch("admin.ledger_attestation.HSMCertificate.save_to_jsonfile") 

274 def test_attestation_save_to_jsonfile_error(self, 

275 save_to_jsonfile, 

276 from_jsonfile, 

277 get_hsm, 

278 *_): 

279 self.setupMocks(from_jsonfile, get_hsm) 

280 save_to_jsonfile.side_effect = Exception() 

281 options = self.setupDefaultOptions() 

282 with patch('builtins.open', mock_open()) as file_mock: 

283 with self.assertRaises(Exception): 

284 do_attestation(options) 

285 self.assertTrue(from_jsonfile.called) 

286 self.assertTrue(get_hsm.called) 

287 self.assertFalse(file_mock.return_value.write.called)