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
« 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.
23import json
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
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 })
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
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
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)
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)
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)
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)
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)
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)
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)
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)
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)
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)
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)
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)
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)
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)