Coverage for tests/admin/test_verify_sgx_attestation.py: 100%
159 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.
23from types import SimpleNamespace
24from unittest import TestCase
25from unittest.mock import Mock, patch
26from parameterized import parameterized
27from admin.misc import AdminError
28from admin.pubkeys import PATHS
29from admin.verify_sgx_attestation import do_verify_attestation, DEFAULT_ROOT_AUTHORITY
30import ecdsa
31import secp256k1 as ec
32import hashlib
33import logging
35logging.disable(logging.CRITICAL)
38@patch("sys.stdout.write")
39@patch("admin.verify_sgx_attestation.head")
40@patch("admin.verify_sgx_attestation.HSMCertificate")
41@patch("admin.verify_sgx_attestation.load_pubkeys")
42@patch("admin.verify_sgx_attestation.get_root_of_trust")
43class TestVerifySgxAttestation(TestCase):
44 def setUp(self):
45 self.certification_path = 'certification-path'
46 self.pubkeys_path = 'pubkeys-path'
47 self.options = SimpleNamespace(**{
48 'attestation_certificate_file_path': self.certification_path,
49 'pubkeys_file_path': self.pubkeys_path,
50 'root_authority': None
51 })
53 paths = []
54 for path in PATHS.values():
55 paths.append(str(path))
57 self.public_keys = {}
58 self.expected_pubkeys_output = []
59 pubkeys_hash = hashlib.sha256()
60 path_name_padding = max(map(len, paths))
61 for path in sorted(paths):
62 pubkey = ecdsa.SigningKey.generate(curve=ecdsa.SECP256k1).get_verifying_key()
63 self.public_keys[path] = ec.PublicKey(
64 pubkey.to_string('compressed'), raw=True)
65 pubkeys_hash.update(pubkey.to_string('uncompressed'))
66 self.expected_pubkeys_output.append(
67 f"{(path + ':').ljust(path_name_padding+1)} "
68 f"{pubkey.to_string('compressed').hex()}"
69 )
70 self.expected_pubkeys_hash = pubkeys_hash.digest().hex()
72 self.powhsm_msg = \
73 b"POWHSM:5.5::" + \
74 b'plf' + \
75 bytes.fromhex('aa'*32) + \
76 bytes.fromhex(self.expected_pubkeys_hash) + \
77 bytes.fromhex('bb'*32) + \
78 bytes.fromhex('cc'*8) + \
79 bytes.fromhex('00'*7 + 'cd')
81 self.mock_sgx_quote = SimpleNamespace(**{
82 "report_body": SimpleNamespace(**{
83 "mrenclave": bytes.fromhex("aabbccdd"),
84 "mrsigner": bytes.fromhex("1122334455"),
85 })
86 })
88 self.validate_result = {"quote": (
89 True, {
90 "sgx_quote": self.mock_sgx_quote,
91 "message": self.powhsm_msg.hex()
92 }, None)
93 }
95 def configure_mocks(self, get_root_of_trust, load_pubkeys,
96 HSMCertificate, head):
97 self.root_of_trust = Mock()
98 self.root_of_trust.is_valid.return_value = True
99 get_root_of_trust.return_value = self.root_of_trust
100 load_pubkeys.return_value = self.public_keys
101 self.mock_certificate = Mock()
102 self.mock_certificate.validate_and_get_values.return_value = self.validate_result
103 HSMCertificate.from_jsonfile.return_value = self.mock_certificate
105 @parameterized.expand([
106 ("default_root", None),
107 ("custom_root", "a-custom-root")
108 ])
109 def test_verify_attestation(self, get_root_of_trust, load_pubkeys,
110 HSMCertificate, head, _, __, custom_root):
111 self.configure_mocks(get_root_of_trust, load_pubkeys, HSMCertificate, head)
112 if custom_root:
113 self.options.root_authority = custom_root
115 do_verify_attestation(self.options)
117 if custom_root:
118 get_root_of_trust.assert_called_with(custom_root)
119 else:
120 get_root_of_trust.assert_called_with(DEFAULT_ROOT_AUTHORITY)
121 self.root_of_trust.is_valid.assert_called_with(self.root_of_trust)
122 load_pubkeys.assert_called_with(self.pubkeys_path)
123 HSMCertificate.from_jsonfile.assert_called_with(self.certification_path)
124 self.mock_certificate.validate_and_get_values \
125 .assert_called_with(self.root_of_trust)
126 head.assert_called_with([
127 "powHSM verified with public keys:"
128 ] + self.expected_pubkeys_output + [
129 f"Hash: {self.expected_pubkeys_hash}",
130 "",
131 "Installed powHSM MRENCLAVE: aabbccdd",
132 "Installed powHSM MRSIGNER: 1122334455",
133 "Installed powHSM version: 5.5",
134 "Platform: plf",
135 f"UD value: {'aa'*32}",
136 f"Best block: {'bb'*32}",
137 f"Last transaction signed: {'cc'*8}",
138 "Timestamp: 205",
139 ], fill="-")
141 def test_verify_attestation_err_get_root(self, get_root_of_trust, load_pubkeys,
142 HSMCertificate, head, _):
143 self.configure_mocks(get_root_of_trust, load_pubkeys, HSMCertificate, head)
144 get_root_of_trust.side_effect = ValueError("root of trust error")
146 with self.assertRaises(AdminError) as e:
147 do_verify_attestation(self.options)
148 self.assertIn("root of trust error", str(e.exception))
150 get_root_of_trust.assert_called_with(DEFAULT_ROOT_AUTHORITY)
151 self.root_of_trust.is_valid.assert_not_called()
152 load_pubkeys.assert_not_called()
153 HSMCertificate.from_jsonfile.assert_not_called()
154 self.mock_certificate.validate_and_get_values.assert_not_called()
156 def test_verify_attestation_err_root_invalid(self, get_root_of_trust, load_pubkeys,
157 HSMCertificate, head, _):
158 self.configure_mocks(get_root_of_trust, load_pubkeys, HSMCertificate, head)
159 self.root_of_trust.is_valid.return_value = False
161 with self.assertRaises(AdminError) as e:
162 do_verify_attestation(self.options)
163 self.assertIn("self-signed root of trust", str(e.exception))
165 get_root_of_trust.assert_called_with(DEFAULT_ROOT_AUTHORITY)
166 self.root_of_trust.is_valid.assert_called_with(self.root_of_trust)
167 load_pubkeys.assert_not_called()
168 HSMCertificate.from_jsonfile.assert_not_called()
169 self.mock_certificate.validate_and_get_values.assert_not_called()
171 def test_verify_attestation_err_load_pubkeys(self, get_root_of_trust, load_pubkeys,
172 HSMCertificate, head, _):
173 self.configure_mocks(get_root_of_trust, load_pubkeys, HSMCertificate, head)
174 load_pubkeys.side_effect = ValueError("pubkeys error")
176 with self.assertRaises(AdminError) as e:
177 do_verify_attestation(self.options)
178 self.assertIn("pubkeys error", str(e.exception))
180 get_root_of_trust.assert_called_with(DEFAULT_ROOT_AUTHORITY)
181 self.root_of_trust.is_valid.assert_called_with(self.root_of_trust)
182 load_pubkeys.assert_called_with(self.pubkeys_path)
183 HSMCertificate.from_jsonfile.assert_not_called()
184 self.mock_certificate.validate_and_get_values.assert_not_called()
186 def test_verify_attestation_err_load_cert(self, get_root_of_trust, load_pubkeys,
187 HSMCertificate, head, _):
188 self.configure_mocks(get_root_of_trust, load_pubkeys, HSMCertificate, head)
189 HSMCertificate.from_jsonfile.side_effect = ValueError("load cert error")
191 with self.assertRaises(AdminError) as e:
192 do_verify_attestation(self.options)
193 self.assertIn("load cert error", str(e.exception))
195 get_root_of_trust.assert_called_with(DEFAULT_ROOT_AUTHORITY)
196 self.root_of_trust.is_valid.assert_called_with(self.root_of_trust)
197 load_pubkeys.assert_called_with(self.pubkeys_path)
198 HSMCertificate.from_jsonfile.assert_called_with(self.certification_path)
199 self.mock_certificate.validate_and_get_values.assert_not_called()
201 def test_verify_attestation_validation_noquote(self, get_root_of_trust, load_pubkeys,
202 HSMCertificate, head, _):
203 self.configure_mocks(get_root_of_trust, load_pubkeys, HSMCertificate, head)
204 self.mock_certificate.validate_and_get_values.return_value = {"something": "else"}
206 with self.assertRaises(AdminError) as e:
207 do_verify_attestation(self.options)
208 self.assertIn("does not contain", str(e.exception))
210 get_root_of_trust.assert_called_with(DEFAULT_ROOT_AUTHORITY)
211 self.root_of_trust.is_valid.assert_called_with(self.root_of_trust)
212 load_pubkeys.assert_called_with(self.pubkeys_path)
213 HSMCertificate.from_jsonfile.assert_called_with(self.certification_path)
214 self.mock_certificate.validate_and_get_values \
215 .assert_called_with(self.root_of_trust)
217 def test_verify_attestation_validation_failed(self, get_root_of_trust, load_pubkeys,
218 HSMCertificate, head, _):
219 self.configure_mocks(get_root_of_trust, load_pubkeys, HSMCertificate, head)
220 self.mock_certificate.validate_and_get_values.return_value = {
221 "quote": (False, "a validation error")
222 }
224 with self.assertRaises(AdminError) as e:
225 do_verify_attestation(self.options)
226 self.assertIn("validation error", str(e.exception))
228 get_root_of_trust.assert_called_with(DEFAULT_ROOT_AUTHORITY)
229 self.root_of_trust.is_valid.assert_called_with(self.root_of_trust)
230 load_pubkeys.assert_called_with(self.pubkeys_path)
231 HSMCertificate.from_jsonfile.assert_called_with(self.certification_path)
232 self.mock_certificate.validate_and_get_values \
233 .assert_called_with(self.root_of_trust)
235 def test_verify_attestation_invalid_header(self, get_root_of_trust, load_pubkeys,
236 HSMCertificate, head, _):
237 self.configure_mocks(get_root_of_trust, load_pubkeys, HSMCertificate, head)
238 self.validate_result["quote"][1]["message"] = "aabbccdd"
240 with self.assertRaises(AdminError) as e:
241 do_verify_attestation(self.options)
242 self.assertIn("message header", str(e.exception))
244 get_root_of_trust.assert_called_with(DEFAULT_ROOT_AUTHORITY)
245 self.root_of_trust.is_valid.assert_called_with(self.root_of_trust)
246 load_pubkeys.assert_called_with(self.pubkeys_path)
247 HSMCertificate.from_jsonfile.assert_called_with(self.certification_path)
248 self.mock_certificate.validate_and_get_values \
249 .assert_called_with(self.root_of_trust)
251 def test_verify_attestation_invalid_message(self, get_root_of_trust, load_pubkeys,
252 HSMCertificate, head, _):
253 self.configure_mocks(get_root_of_trust, load_pubkeys, HSMCertificate, head)
254 self.validate_result["quote"][1]["message"] = b"POWHSM:5.5::plf".hex()
256 with self.assertRaises(AdminError) as e:
257 do_verify_attestation(self.options)
258 self.assertIn("parsing", str(e.exception))
260 get_root_of_trust.assert_called_with(DEFAULT_ROOT_AUTHORITY)
261 self.root_of_trust.is_valid.assert_called_with(self.root_of_trust)
262 load_pubkeys.assert_called_with(self.pubkeys_path)
263 HSMCertificate.from_jsonfile.assert_called_with(self.certification_path)
264 self.mock_certificate.validate_and_get_values \
265 .assert_called_with(self.root_of_trust)
267 def test_verify_attestation_pkh_mismatch(self, get_root_of_trust, load_pubkeys,
268 HSMCertificate, head, _):
269 self.configure_mocks(get_root_of_trust, load_pubkeys, HSMCertificate, head)
270 self.public_keys.popitem()
272 with self.assertRaises(AdminError) as e:
273 do_verify_attestation(self.options)
274 self.assertIn("hash mismatch", str(e.exception))
276 get_root_of_trust.assert_called_with(DEFAULT_ROOT_AUTHORITY)
277 self.root_of_trust.is_valid.assert_called_with(self.root_of_trust)
278 load_pubkeys.assert_called_with(self.pubkeys_path)
279 HSMCertificate.from_jsonfile.assert_called_with(self.certification_path)
280 self.mock_certificate.validate_and_get_values \
281 .assert_called_with(self.root_of_trust)