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

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

34 

35logging.disable(logging.CRITICAL) 

36 

37 

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

52 

53 paths = [] 

54 for path in PATHS.values(): 

55 paths.append(str(path)) 

56 

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

71 

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

80 

81 self.mock_sgx_quote = SimpleNamespace(**{ 

82 "report_body": SimpleNamespace(**{ 

83 "mrenclave": bytes.fromhex("aabbccdd"), 

84 "mrsigner": bytes.fromhex("1122334455"), 

85 }) 

86 }) 

87 

88 self.validate_result = {"quote": ( 

89 True, { 

90 "sgx_quote": self.mock_sgx_quote, 

91 "message": self.powhsm_msg.hex() 

92 }, None) 

93 } 

94 

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 

104 

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 

114 

115 do_verify_attestation(self.options) 

116 

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

140 

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

145 

146 with self.assertRaises(AdminError) as e: 

147 do_verify_attestation(self.options) 

148 self.assertIn("root of trust error", str(e.exception)) 

149 

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

155 

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 

160 

161 with self.assertRaises(AdminError) as e: 

162 do_verify_attestation(self.options) 

163 self.assertIn("self-signed root of trust", str(e.exception)) 

164 

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

170 

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

175 

176 with self.assertRaises(AdminError) as e: 

177 do_verify_attestation(self.options) 

178 self.assertIn("pubkeys error", str(e.exception)) 

179 

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

185 

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

190 

191 with self.assertRaises(AdminError) as e: 

192 do_verify_attestation(self.options) 

193 self.assertIn("load cert error", str(e.exception)) 

194 

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

200 

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

205 

206 with self.assertRaises(AdminError) as e: 

207 do_verify_attestation(self.options) 

208 self.assertIn("does not contain", str(e.exception)) 

209 

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) 

216 

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 } 

223 

224 with self.assertRaises(AdminError) as e: 

225 do_verify_attestation(self.options) 

226 self.assertIn("validation error", str(e.exception)) 

227 

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) 

234 

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" 

239 

240 with self.assertRaises(AdminError) as e: 

241 do_verify_attestation(self.options) 

242 self.assertIn("message header", str(e.exception)) 

243 

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) 

250 

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

255 

256 with self.assertRaises(AdminError) as e: 

257 do_verify_attestation(self.options) 

258 self.assertIn("parsing", str(e.exception)) 

259 

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) 

266 

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

271 

272 with self.assertRaises(AdminError) as e: 

273 do_verify_attestation(self.options) 

274 self.assertIn("hash mismatch", str(e.exception)) 

275 

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)