Coverage for tests/admin/test_sgx_utils.py: 99%

352 statements  

« prev     ^ index     » next       coverage.py v7.5.3, created at 2025-10-30 06:22 +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 

23 

24from unittest import TestCase 

25from unittest.mock import patch, Mock 

26from parameterized import parameterized 

27from types import SimpleNamespace 

28from datetime import datetime 

29from cryptography import x509 

30from cryptography.hazmat.primitives.serialization import PublicFormat, Encoding 

31from hashlib import sha256 

32import ecdsa 

33from admin.sgx_utils import get_sgx_extensions, get_tcb_info, validate_tcb_info, \ 

34 get_qeid_info, validate_qeid_info, get_intel_pcs_x509_crl 

35import logging 

36 

37logging.disable(logging.CRITICAL) 

38 

39TEST_CERTIFICATE = """ 

40-----BEGIN CERTIFICATE----- 

41MIIE8jCCBJmgAwIBAgIVAJzdeT0t5GnBg8UERKXiUnECGiOPMAoGCCqGSM49BAMC 

42MHAxIjAgBgNVBAMMGUludGVsIFNHWCBQQ0sgUGxhdGZvcm0gQ0ExGjAYBgNVBAoM 

43EUludGVsIENvcnBvcmF0aW9uMRQwEgYDVQQHDAtTYW50YSBDbGFyYTELMAkGA1UE 

44CAwCQ0ExCzAJBgNVBAYTAlVTMB4XDTI1MDYyNDIyMDEyMFoXDTMyMDYyNDIyMDEy 

45MFowcDEiMCAGA1UEAwwZSW50ZWwgU0dYIFBDSyBDZXJ0aWZpY2F0ZTEaMBgGA1UE 

46CgwRSW50ZWwgQ29ycG9yYXRpb24xFDASBgNVBAcMC1NhbnRhIENsYXJhMQswCQYD 

47VQQIDAJDQTELMAkGA1UEBhMCVVMwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAAT8 

48IZG02RowuzOQKmXZCmBjiMPUPW0+E1kVl1ufUkd3yBtA71B8bgt8DHaoUFZ1YAxu 

49ZZhPDvV13zZF9fzdyUxXo4IDDjCCAwowHwYDVR0jBBgwFoAUlW9dzb0b4elAScnU 

509DPOAVcL3lQwawYDVR0fBGQwYjBgoF6gXIZaaHR0cHM6Ly9hcGkudHJ1c3RlZHNl 

51cnZpY2VzLmludGVsLmNvbS9zZ3gvY2VydGlmaWNhdGlvbi92My9wY2tjcmw/Y2E9 

52cGxhdGZvcm0mZW5jb2Rpbmc9ZGVyMB0GA1UdDgQWBBT2LlxjZqMdkMyID5G57bLZ 

53kj0QPDAOBgNVHQ8BAf8EBAMCBsAwDAYDVR0TAQH/BAIwADCCAjsGCSqGSIb4TQEN 

54AQSCAiwwggIoMB4GCiqGSIb4TQENAQEEELbSV7okFcKjOLO+IPh8XykwggFlBgoq 

55hkiG+E0BDQECMIIBVTAQBgsqhkiG+E0BDQECAQIBEDAQBgsqhkiG+E0BDQECAgIB 

56EDAQBgsqhkiG+E0BDQECAwIBAzAQBgsqhkiG+E0BDQECBAIBAzARBgsqhkiG+E0B 

57DQECBQICAP8wEQYLKoZIhvhNAQ0BAgYCAgD/MBAGCyqGSIb4TQENAQIHAgEBMBAG 

58CyqGSIb4TQENAQIIAgEAMBAGCyqGSIb4TQENAQIJAgEAMBAGCyqGSIb4TQENAQIK 

59AgEAMBAGCyqGSIb4TQENAQILAgEAMBAGCyqGSIb4TQENAQIMAgEAMBAGCyqGSIb4 

60TQENAQINAgEAMBAGCyqGSIb4TQENAQIOAgEAMBAGCyqGSIb4TQENAQIPAgEAMBAG 

61CyqGSIb4TQENAQIQAgEAMBAGCyqGSIb4TQENAQIRAgENMB8GCyqGSIb4TQENAQIS 

62BBAQEAMD//8BAAAAAAAAAAAAMBAGCiqGSIb4TQENAQMEAgAAMBQGCiqGSIb4TQEN 

63AQQEBgBgagAAADAPBgoqhkiG+E0BDQEFCgEBMB4GCiqGSIb4TQENAQYEEA1Xvw11 

64FROIHprYDubgabwwRAYKKoZIhvhNAQ0BBzA2MBAGCyqGSIb4TQENAQcBAQH/MBAG 

65CyqGSIb4TQENAQcCAQEAMBAGCyqGSIb4TQENAQcDAQEAMAoGCCqGSM49BAMCA0cA 

66MEQCICNTsVzcOYLck4STK/PAdCcTIqquVTmQRh40TSUBEaS4AiAzUN6Q3n/vEnSz 

67fQgkG+9VlNOMURjIGOl1qtg9jop4CQ== 

68-----END CERTIFICATE----- 

69""" 

70 

71TEST_CERTIFICATE_ROOT = """ 

72-----BEGIN CERTIFICATE----- 

73MIICjzCCAjSgAwIBAgIUImUM1lqdNInzg7SVUr9QGzknBqwwCgYIKoZIzj0EAwIw 

74aDEaMBgGA1UEAwwRSW50ZWwgU0dYIFJvb3QgQ0ExGjAYBgNVBAoMEUludGVsIENv 

75cnBvcmF0aW9uMRQwEgYDVQQHDAtTYW50YSBDbGFyYTELMAkGA1UECAwCQ0ExCzAJ 

76BgNVBAYTAlVTMB4XDTE4MDUyMTEwNDUxMFoXDTQ5MTIzMTIzNTk1OVowaDEaMBgG 

77A1UEAwwRSW50ZWwgU0dYIFJvb3QgQ0ExGjAYBgNVBAoMEUludGVsIENvcnBvcmF0 

78aW9uMRQwEgYDVQQHDAtTYW50YSBDbGFyYTELMAkGA1UECAwCQ0ExCzAJBgNVBAYT 

79AlVTMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEC6nEwMDIYZOj/iPWsCzaEKi7 

801OiOSLRFhWGjbnBVJfVnkY4u3IjkDYYL0MxO4mqsyYjlBalTVYxFP2sJBK5zlKOB 

81uzCBuDAfBgNVHSMEGDAWgBQiZQzWWp00ifODtJVSv1AbOScGrDBSBgNVHR8ESzBJ 

82MEegRaBDhkFodHRwczovL2NlcnRpZmljYXRlcy50cnVzdGVkc2VydmljZXMuaW50 

83ZWwuY29tL0ludGVsU0dYUm9vdENBLmRlcjAdBgNVHQ4EFgQUImUM1lqdNInzg7SV 

84Ur9QGzknBqwwDgYDVR0PAQH/BAQDAgEGMBIGA1UdEwEB/wQIMAYBAf8CAQEwCgYI 

85KoZIzj0EAwIDSQAwRgIhAOW/5QkR+S9CiSDcNoowLuPRLsWGf/Yi7GSX94BgwTwg 

86AiEA4J0lrHoMs+Xo5o/sX6O9QWxHRAvZUGOdRQ7cvqRXaqI= 

87-----END CERTIFICATE----- 

88""" 

89 

90 

91class TestSgxUtils(TestCase): 

92 def test_get_sgx_extensions_ok(self): 

93 certificate = x509.load_pem_x509_certificate(TEST_CERTIFICATE.encode()) 

94 extensions = get_sgx_extensions(certificate) 

95 self.assertEqual("b6d257ba2415c2a338b3be20f87c5f29", extensions["ppid"]) 

96 tcb = extensions["tcb"] 

97 self.assertEqual(0x10, tcb["comp01"]) 

98 self.assertEqual(0x10, tcb["comp02"]) 

99 self.assertEqual(0x03, tcb["comp03"]) 

100 self.assertEqual(0x03, tcb["comp04"]) 

101 self.assertEqual(0xff, tcb["comp05"]) 

102 self.assertEqual(0xff, tcb["comp06"]) 

103 self.assertEqual(0x01, tcb["comp07"]) 

104 self.assertEqual(0x00, tcb["comp08"]) 

105 self.assertEqual(0x00, tcb["comp09"]) 

106 self.assertEqual(0x00, tcb["comp10"]) 

107 self.assertEqual(0x00, tcb["comp11"]) 

108 self.assertEqual(0x00, tcb["comp12"]) 

109 self.assertEqual(0x00, tcb["comp13"]) 

110 self.assertEqual(0x00, tcb["comp14"]) 

111 self.assertEqual(0x00, tcb["comp15"]) 

112 self.assertEqual(0x00, tcb["comp16"]) 

113 self.assertEqual(0x0d, tcb["pcesvn"]) 

114 self.assertEqual("10100303ffff01000000000000000000", tcb["cpusvn"]) 

115 self.assertEqual("0000", extensions["pceid"]) 

116 self.assertEqual("00606a000000", extensions["fmspc"]) 

117 

118 def test_get_sgx_extensions_not_found(self): 

119 certificate = x509.load_pem_x509_certificate(TEST_CERTIFICATE_ROOT.encode()) 

120 self.assertIsNone(get_sgx_extensions(certificate)) 

121 

122 

123@patch("admin.sgx_utils.X509CertificateValidator") 

124@patch("admin.sgx_utils.x509") 

125@patch("admin.sgx_utils.split_pem_certificates") 

126@patch("admin.sgx_utils.requests") 

127class TestSgxUtilsGetTcbInfo(TestCase): 

128 def configure_mocks(self, requests, split_pem_certificates, mock_x509, 

129 X509CertificateValidator, issuer_sig=None): 

130 mock_response = Mock() 

131 requests.get.return_value = mock_response 

132 mock_response.status_code = 200 

133 mock_response.headers = { 

134 "Content-Type": "application/json", 

135 "warning": "this is a warning", 

136 "TCB-Info-Issuer-Chain": "the issuer chain", 

137 } 

138 mock_response.text = """ 

139 { 

140 "tcbInfo": { 

141 "a": 1, 

142 "b": 2, 

143 "c": 3 

144 }, 

145 "something": "else", 

146 "another": "thing", 

147 "signature": "<SIG>" 

148 } 

149 """ 

150 self.mock_response = mock_response 

151 

152 issuer_privkey = ecdsa.SigningKey.generate(curve=ecdsa.NIST256p) 

153 issuer_pubkey = issuer_privkey.verifying_key 

154 tcb_info_digest = sha256('{"a":1,"b":2,"c":3}'.encode()).digest() 

155 if issuer_sig is None: 

156 self.issuer_sig = issuer_privkey.sign_digest( 

157 tcb_info_digest, sigencode=ecdsa.util.sigencode_string) 

158 else: 

159 self.issuer_sig = issuer_sig 

160 mock_response.text = mock_response.text.replace("<SIG>", self.issuer_sig.hex()) 

161 

162 split_pem_certificates.return_value = ["cert-0", "cert-1"] 

163 self.cert0 = Mock() 

164 self.cert1 = Mock() 

165 

166 self.cert0pk = Mock() 

167 self.cert0.public_key.return_value = self.cert0pk 

168 self.cert0pk.public_bytes.return_value = issuer_pubkey.to_string("compressed") 

169 

170 def load_cert(pem): 

171 if pem == b"cert-0": 

172 return self.cert0 

173 elif pem == b"cert-1": 

174 return self.cert1 

175 else: 

176 raise Exception("Unknown cert") 

177 

178 mock_x509.load_pem_x509_certificate.side_effect = load_cert 

179 

180 self.validator = Mock() 

181 self.validator.validate.return_value = { 

182 "valid": True, 

183 "warnings": ["w1", "w2"], 

184 } 

185 X509CertificateValidator.return_value = self.validator 

186 

187 @parameterized.expand([ 

188 ("early"), 

189 ("standard"), 

190 ]) 

191 def test_get_tcb_info_ok(self, requests, split_pem_certificates, mock_x509, 

192 X509CertificateValidator, update): 

193 self.configure_mocks(requests, split_pem_certificates, mock_x509, 

194 X509CertificateValidator) 

195 

196 self.assertEqual({ 

197 "tcb_info": { 

198 "tcbInfo": { 

199 "a": 1, 

200 "b": 2, 

201 "c": 3, 

202 }, 

203 "something": "else", 

204 "another": "thing", 

205 "signature": self.issuer_sig.hex(), 

206 }, 

207 "warnings": [f"Getting the-url?fmspc=an-fmspc&update={update}: " 

208 "this is a warning", "w1", "w2"], 

209 }, get_tcb_info("the-url", "an-fmspc", self.cert1, update=update)) 

210 

211 requests.get.assert_called_with(f"the-url?fmspc=an-fmspc&update={update}") 

212 X509CertificateValidator.assert_called_with(get_intel_pcs_x509_crl) 

213 self.validator.validate.assert_called_once() 

214 self.assertEqual(self.cert0, self.validator.validate.call_args_list[0].args[0]) 

215 self.assertEqual(self.cert1, self.validator.validate.call_args_list[0].args[1]) 

216 self.assertIsInstance(self.validator.validate.call_args_list[0].args[2], datetime) 

217 self.cert0pk.public_bytes.assert_called_once() 

218 self.cert0pk.public_bytes.assert_called_with( 

219 Encoding.X962, PublicFormat.CompressedPoint) 

220 

221 def test_get_tcb_info_err_get(self, requests, split_pem_certificates, mock_x509, 

222 X509CertificateValidator): 

223 self.configure_mocks(requests, split_pem_certificates, mock_x509, 

224 X509CertificateValidator) 

225 self.mock_response.status_code = 404 

226 

227 with self.assertRaises(RuntimeError) as e: 

228 get_tcb_info("the-url", "an-fmspc", self.cert1, update="upd") 

229 self.assertIn("replied with status", str(e.exception)) 

230 

231 requests.get.assert_called_with("the-url?fmspc=an-fmspc&update=upd") 

232 X509CertificateValidator.assert_not_called() 

233 self.validator.validate.assert_not_called() 

234 self.cert0pk.public_bytes.assert_not_called() 

235 

236 def test_get_tcb_info_err_ctype(self, requests, split_pem_certificates, mock_x509, 

237 X509CertificateValidator): 

238 self.configure_mocks(requests, split_pem_certificates, mock_x509, 

239 X509CertificateValidator) 

240 self.mock_response.headers["Content-Type"] = "not/json" 

241 

242 with self.assertRaises(RuntimeError) as e: 

243 get_tcb_info("the-url", "an-fmspc", self.cert1, update="upd") 

244 self.assertIn("content-type", str(e.exception)) 

245 

246 requests.get.assert_called_with("the-url?fmspc=an-fmspc&update=upd") 

247 X509CertificateValidator.assert_not_called() 

248 self.validator.validate.assert_not_called() 

249 self.cert0pk.public_bytes.assert_not_called() 

250 

251 def test_get_tcb_info_err_nochain(self, requests, split_pem_certificates, mock_x509, 

252 X509CertificateValidator): 

253 self.configure_mocks(requests, split_pem_certificates, mock_x509, 

254 X509CertificateValidator) 

255 self.mock_response.headers["TCB-Info-Issuer-Chain"] = None 

256 

257 with self.assertRaises(RuntimeError) as e: 

258 get_tcb_info("the-url", "an-fmspc", self.cert1, update="upd") 

259 self.assertIn("certification chain", str(e.exception)) 

260 

261 requests.get.assert_called_with("the-url?fmspc=an-fmspc&update=upd") 

262 X509CertificateValidator.assert_not_called() 

263 self.validator.validate.assert_not_called() 

264 self.cert0pk.public_bytes.assert_not_called() 

265 

266 def test_get_tcb_info_err_shortchain(self, requests, split_pem_certificates, 

267 mock_x509, X509CertificateValidator): 

268 self.configure_mocks(requests, split_pem_certificates, mock_x509, 

269 X509CertificateValidator) 

270 split_pem_certificates.return_value = ["cert-0"] 

271 

272 with self.assertRaises(RuntimeError) as e: 

273 get_tcb_info("the-url", "an-fmspc", self.cert1, update="upd") 

274 self.assertIn("at least two certificates", str(e.exception)) 

275 

276 requests.get.assert_called_with("the-url?fmspc=an-fmspc&update=upd") 

277 X509CertificateValidator.assert_not_called() 

278 self.validator.validate.assert_not_called() 

279 self.cert0pk.public_bytes.assert_not_called() 

280 

281 def test_get_tcb_info_err_rot(self, requests, split_pem_certificates, 

282 mock_x509, X509CertificateValidator): 

283 self.configure_mocks(requests, split_pem_certificates, mock_x509, 

284 X509CertificateValidator) 

285 

286 other_root = Mock() 

287 other_root.subject = "other root" 

288 

289 with self.assertRaises(RuntimeError) as e: 

290 get_tcb_info("the-url", "an-fmspc", other_root, update="upd") 

291 self.assertIn("does not match", str(e.exception)) 

292 self.assertIn("other root", str(e.exception)) 

293 

294 requests.get.assert_called_with("the-url?fmspc=an-fmspc&update=upd") 

295 X509CertificateValidator.assert_not_called() 

296 self.validator.validate.assert_not_called() 

297 self.cert0pk.public_bytes.assert_not_called() 

298 

299 def test_get_tcb_info_err_chain_iv(self, requests, split_pem_certificates, 

300 mock_x509, X509CertificateValidator): 

301 self.configure_mocks(requests, split_pem_certificates, mock_x509, 

302 X509CertificateValidator) 

303 

304 self.validator.validate.return_value = { 

305 "valid": False, 

306 "reason": "oops" 

307 } 

308 

309 with self.assertRaises(RuntimeError) as e: 

310 get_tcb_info("the-url", "an-fmspc", self.cert1, update="upd") 

311 self.assertIn("issuer chain", str(e.exception)) 

312 self.assertIn("oops", str(e.exception)) 

313 

314 requests.get.assert_called_with("the-url?fmspc=an-fmspc&update=upd") 

315 X509CertificateValidator.assert_called_with(get_intel_pcs_x509_crl) 

316 self.validator.validate.assert_called_once() 

317 self.assertEqual(self.cert0, self.validator.validate.call_args_list[0].args[0]) 

318 self.assertEqual(self.cert1, self.validator.validate.call_args_list[0].args[1]) 

319 self.assertIsInstance(self.validator.validate.call_args_list[0].args[2], datetime) 

320 self.cert0pk.public_bytes.assert_not_called() 

321 

322 def test_get_tcb_info_err_tcb_sig(self, requests, split_pem_certificates, 

323 mock_x509, X509CertificateValidator): 

324 isig = bytes.fromhex("aa"*32+"bb"*32) 

325 self.configure_mocks(requests, split_pem_certificates, mock_x509, 

326 X509CertificateValidator, issuer_sig=isig) 

327 

328 with self.assertRaises(RuntimeError) as e: 

329 get_tcb_info("the-url", "an-fmspc", self.cert1, update="upd") 

330 self.assertIn("Signature verification failed", str(e.exception)) 

331 

332 requests.get.assert_called_with("the-url?fmspc=an-fmspc&update=upd") 

333 X509CertificateValidator.assert_called_with(get_intel_pcs_x509_crl) 

334 self.validator.validate.assert_called_once() 

335 self.assertEqual(self.cert0, self.validator.validate.call_args_list[0].args[0]) 

336 self.assertEqual(self.cert1, self.validator.validate.call_args_list[0].args[1]) 

337 self.assertIsInstance(self.validator.validate.call_args_list[0].args[2], datetime) 

338 self.cert0pk.public_bytes.assert_called_once() 

339 self.cert0pk.public_bytes.assert_called_with( 

340 Encoding.X962, PublicFormat.CompressedPoint) 

341 

342 

343class TestSgxUtilsValidateTcbInfo(TestCase): 

344 def setUp(self): 

345 self.pck_info = { 

346 "ppid": "aa"*32, 

347 "tcb": { 

348 "comp01": 11, 

349 "comp02": 22, 

350 "comp03": 33, 

351 "comp04": 44, 

352 "comp05": 55, 

353 "comp06": 66, 

354 "comp07": 77, 

355 "comp08": 88, 

356 "comp09": 99, 

357 "comp10": 1010, 

358 "comp11": 1111, 

359 "comp12": 1212, 

360 "comp13": 1313, 

361 "comp14": 1414, 

362 "comp15": 1515, 

363 "comp16": 1616, 

364 "pcesvn": 1717, 

365 "cpusvn": "bb"*32, 

366 }, 

367 "pceid": "abcd", 

368 "fmspc": "cc"*6 

369 } 

370 

371 self.tcb_info = { 

372 "tcbInfo": { 

373 "id": "SGX", 

374 "version": 3, 

375 "issueDate": "2025-09-05T03:41:29Z", 

376 "nextUpdate": "2025-10-05T03:41:29Z", 

377 "fmspc": "00606a000000", 

378 "pceId": "0000", 

379 "tcbType": 0, 

380 "tcbEvaluationDataNumber": 1234, 

381 "tcbLevels": [ 

382 { 

383 "tcb": { 

384 "sgxtcbcomponents": [ 

385 { 

386 "svn": 10, 

387 }, 

388 { 

389 "svn": 20, 

390 }, 

391 { 

392 "svn": 30, 

393 }, 

394 { 

395 "svn": 3, 

396 }, 

397 { 

398 "svn": 255 

399 }, 

400 { 

401 "svn": 255 

402 }, 

403 { 

404 "svn": 1 

405 }, 

406 { 

407 "svn": 0 

408 }, 

409 { 

410 "svn": 0 

411 }, 

412 { 

413 "svn": 0 

414 }, 

415 { 

416 "svn": 0 

417 }, 

418 { 

419 "svn": 0 

420 }, 

421 { 

422 "svn": 0 

423 }, 

424 { 

425 "svn": 0 

426 }, 

427 { 

428 "svn": 0 

429 }, 

430 { 

431 "svn": 0 

432 } 

433 ], 

434 "pcesvn": 13 

435 }, 

436 "tcbDate": "doesntmatter", 

437 "tcbStatus": "neither", 

438 "advisoryIDs": [ 

439 "we-dont-care", 

440 "or-about-this-one" 

441 ] 

442 }, 

443 { 

444 "tcb": { 

445 "sgxtcbcomponents": [ 

446 { 

447 "svn": 10 

448 }, 

449 { 

450 "svn": 21 

451 }, 

452 { 

453 "svn": 32 

454 }, 

455 { 

456 "svn": 43 

457 }, 

458 { 

459 "svn": 54 

460 }, 

461 { 

462 "svn": 65 

463 }, 

464 { 

465 "svn": 76 

466 }, 

467 { 

468 "svn": 87 

469 }, 

470 { 

471 "svn": 98 

472 }, 

473 { 

474 "svn": 1009 

475 }, 

476 { 

477 "svn": 1110 

478 }, 

479 { 

480 "svn": 1211 

481 }, 

482 { 

483 "svn": 1312 

484 }, 

485 { 

486 "svn": 1413 

487 }, 

488 { 

489 "svn": 1514 

490 }, 

491 { 

492 "svn": 1615 

493 } 

494 ], 

495 "pcesvn": 1716 

496 }, 

497 "tcbDate": "the-date-we-want", 

498 "tcbStatus": "the-status-we-want", 

499 "advisoryIDs": [ 

500 "the-advisory-1", 

501 "the-advisory-2", 

502 "the-advisory-3" 

503 ] 

504 } 

505 ] 

506 }, 

507 "signature": "not-used-here" 

508 } 

509 

510 def test_validate_tcb_info_ok(self): 

511 self.assertEqual({ 

512 "valid": True, 

513 "status": "the-status-we-want", 

514 "date": "the-date-we-want", 

515 "advisories": ["the-advisory-1", "the-advisory-2", "the-advisory-3"], 

516 "svns": [ 

517 "Comp 01: 11 >= 10", 

518 "Comp 02: 22 >= 21", 

519 "Comp 03: 33 >= 32", 

520 "Comp 04: 44 >= 43", 

521 "Comp 05: 55 >= 54", 

522 "Comp 06: 66 >= 65", 

523 "Comp 07: 77 >= 76", 

524 "Comp 08: 88 >= 87", 

525 "Comp 09: 99 >= 98", 

526 "Comp 10: 1010 >= 1009", 

527 "Comp 11: 1111 >= 1110", 

528 "Comp 12: 1212 >= 1211", 

529 "Comp 13: 1313 >= 1312", 

530 "Comp 14: 1414 >= 1413", 

531 "Comp 15: 1515 >= 1514", 

532 "Comp 16: 1616 >= 1615", 

533 "PCESVN: 1717 >= 1716", 

534 ], 

535 "edn": 1234, 

536 }, validate_tcb_info(self.pck_info, self.tcb_info["tcbInfo"])) 

537 

538 def test_validate_tcb_info_notfound(self): 

539 self.tcb_info["tcbInfo"]["tcbLevels"] = self.tcb_info["tcbInfo"]["tcbLevels"][0:1] 

540 self.assertEqual({ 

541 "valid": False, 

542 "reason": "TCB level is unsupported", 

543 }, validate_tcb_info(self.pck_info, self.tcb_info["tcbInfo"])) 

544 

545 def test_validate_tcb_info_malformed(self): 

546 self.tcb_info = { 

547 "tcbInfo": { 

548 "something": "else" 

549 } 

550 } 

551 

552 res = validate_tcb_info(self.pck_info, self.tcb_info["tcbInfo"]) 

553 self.assertFalse(res["valid"]) 

554 self.assertIn("While validating TCB information", res["reason"]) 

555 

556 

557@patch("admin.sgx_utils.X509CertificateValidator") 

558@patch("admin.sgx_utils.x509") 

559@patch("admin.sgx_utils.split_pem_certificates") 

560@patch("admin.sgx_utils.requests") 

561class TestSgxUtilsGetQeIdInfo(TestCase): 

562 def configure_mocks(self, requests, split_pem_certificates, mock_x509, 

563 X509CertificateValidator, issuer_sig=None): 

564 mock_response = Mock() 

565 requests.get.return_value = mock_response 

566 mock_response.status_code = 200 

567 mock_response.headers = { 

568 "Content-Type": "application/json", 

569 "warning": "this is a warning", 

570 "SGX-Enclave-Identity-Issuer-Chain": "the issuer chain", 

571 } 

572 mock_response.text = """ 

573 { 

574 "enclaveIdentity": { 

575 "a": 1, 

576 "b": 2, 

577 "c": 3 

578 }, 

579 "something": "else", 

580 "another": "thing", 

581 "signature": "<SIG>" 

582 } 

583 """ 

584 self.mock_response = mock_response 

585 

586 issuer_privkey = ecdsa.SigningKey.generate(curve=ecdsa.NIST256p) 

587 issuer_pubkey = issuer_privkey.verifying_key 

588 qeid_info_digest = sha256('{"a":1,"b":2,"c":3}'.encode()).digest() 

589 if issuer_sig is None: 

590 self.issuer_sig = issuer_privkey.sign_digest( 

591 qeid_info_digest, sigencode=ecdsa.util.sigencode_string) 

592 else: 

593 self.issuer_sig = issuer_sig 

594 mock_response.text = mock_response.text.replace("<SIG>", self.issuer_sig.hex()) 

595 

596 split_pem_certificates.return_value = ["cert-0", "cert-1"] 

597 self.cert0 = Mock() 

598 self.cert1 = Mock() 

599 

600 self.cert0pk = Mock() 

601 self.cert0.public_key.return_value = self.cert0pk 

602 self.cert0pk.public_bytes.return_value = issuer_pubkey.to_string("compressed") 

603 

604 def load_cert(pem): 

605 if pem == b"cert-0": 

606 return self.cert0 

607 elif pem == b"cert-1": 

608 return self.cert1 

609 else: 

610 raise Exception("Unknown cert") 

611 

612 mock_x509.load_pem_x509_certificate.side_effect = load_cert 

613 

614 self.validator = Mock() 

615 self.validator.validate.return_value = { 

616 "valid": True, 

617 "warnings": ["w1", "w2"], 

618 } 

619 X509CertificateValidator.return_value = self.validator 

620 

621 @parameterized.expand([ 

622 ("early"), 

623 ("standard"), 

624 ]) 

625 def test_get_qeid_info_ok(self, requests, split_pem_certificates, mock_x509, 

626 X509CertificateValidator, update): 

627 self.configure_mocks(requests, split_pem_certificates, mock_x509, 

628 X509CertificateValidator) 

629 

630 self.assertEqual({ 

631 "qeid_info": { 

632 "enclaveIdentity": { 

633 "a": 1, 

634 "b": 2, 

635 "c": 3, 

636 }, 

637 "something": "else", 

638 "another": "thing", 

639 "signature": self.issuer_sig.hex(), 

640 }, 

641 "warnings": [f"Getting the-url?update={update}: " 

642 "this is a warning", "w1", "w2"], 

643 }, get_qeid_info("the-url", self.cert1, update=update)) 

644 

645 requests.get.assert_called_with(f"the-url?update={update}") 

646 X509CertificateValidator.assert_called_with(get_intel_pcs_x509_crl) 

647 self.validator.validate.assert_called_once() 

648 self.assertEqual(self.cert0, self.validator.validate.call_args_list[0].args[0]) 

649 self.assertEqual(self.cert1, self.validator.validate.call_args_list[0].args[1]) 

650 self.assertIsInstance(self.validator.validate.call_args_list[0].args[2], datetime) 

651 self.cert0pk.public_bytes.assert_called_once() 

652 self.cert0pk.public_bytes.assert_called_with( 

653 Encoding.X962, PublicFormat.CompressedPoint) 

654 

655 def test_get_qeid_info_err_get(self, requests, split_pem_certificates, mock_x509, 

656 X509CertificateValidator): 

657 self.configure_mocks(requests, split_pem_certificates, mock_x509, 

658 X509CertificateValidator) 

659 self.mock_response.status_code = 404 

660 

661 with self.assertRaises(RuntimeError) as e: 

662 get_qeid_info("the-url", self.cert1, update="upd") 

663 self.assertIn("replied with status", str(e.exception)) 

664 

665 requests.get.assert_called_with("the-url?update=upd") 

666 X509CertificateValidator.assert_not_called() 

667 self.validator.validate.assert_not_called() 

668 self.cert0pk.public_bytes.assert_not_called() 

669 

670 def test_get_qeid_info_err_ctype(self, requests, split_pem_certificates, mock_x509, 

671 X509CertificateValidator): 

672 self.configure_mocks(requests, split_pem_certificates, mock_x509, 

673 X509CertificateValidator) 

674 self.mock_response.headers["Content-Type"] = "not/json" 

675 

676 with self.assertRaises(RuntimeError) as e: 

677 get_qeid_info("the-url", self.cert1, update="upd") 

678 self.assertIn("content-type", str(e.exception)) 

679 

680 requests.get.assert_called_with("the-url?update=upd") 

681 X509CertificateValidator.assert_not_called() 

682 self.validator.validate.assert_not_called() 

683 self.cert0pk.public_bytes.assert_not_called() 

684 

685 def test_get_qeid_info_err_nochain(self, requests, split_pem_certificates, mock_x509, 

686 X509CertificateValidator): 

687 self.configure_mocks(requests, split_pem_certificates, mock_x509, 

688 X509CertificateValidator) 

689 self.mock_response.headers["SGX-Enclave-Identity-Issuer-Chain"] = None 

690 

691 with self.assertRaises(RuntimeError) as e: 

692 get_qeid_info("the-url", self.cert1, update="upd") 

693 self.assertIn("certification chain", str(e.exception)) 

694 

695 requests.get.assert_called_with("the-url?update=upd") 

696 X509CertificateValidator.assert_not_called() 

697 self.validator.validate.assert_not_called() 

698 self.cert0pk.public_bytes.assert_not_called() 

699 

700 def test_get_qeid_info_err_shortchain(self, requests, split_pem_certificates, 

701 mock_x509, X509CertificateValidator): 

702 self.configure_mocks(requests, split_pem_certificates, mock_x509, 

703 X509CertificateValidator) 

704 split_pem_certificates.return_value = ["cert-0"] 

705 

706 with self.assertRaises(RuntimeError) as e: 

707 get_qeid_info("the-url", self.cert1, update="upd") 

708 self.assertIn("at least two certificates", str(e.exception)) 

709 

710 requests.get.assert_called_with("the-url?update=upd") 

711 X509CertificateValidator.assert_not_called() 

712 self.validator.validate.assert_not_called() 

713 self.cert0pk.public_bytes.assert_not_called() 

714 

715 def test_get_qeid_info_err_rot(self, requests, split_pem_certificates, 

716 mock_x509, X509CertificateValidator): 

717 self.configure_mocks(requests, split_pem_certificates, mock_x509, 

718 X509CertificateValidator) 

719 

720 other_root = Mock() 

721 other_root.subject = "other root" 

722 

723 with self.assertRaises(RuntimeError) as e: 

724 get_qeid_info("the-url", other_root, update="upd") 

725 self.assertIn("does not match", str(e.exception)) 

726 self.assertIn("other root", str(e.exception)) 

727 

728 requests.get.assert_called_with("the-url?update=upd") 

729 X509CertificateValidator.assert_not_called() 

730 self.validator.validate.assert_not_called() 

731 self.cert0pk.public_bytes.assert_not_called() 

732 

733 def test_get_qeid_info_err_chain_iv(self, requests, split_pem_certificates, 

734 mock_x509, X509CertificateValidator): 

735 self.configure_mocks(requests, split_pem_certificates, mock_x509, 

736 X509CertificateValidator) 

737 

738 self.validator.validate.return_value = { 

739 "valid": False, 

740 "reason": "oops" 

741 } 

742 

743 with self.assertRaises(RuntimeError) as e: 

744 get_qeid_info("the-url", self.cert1, update="upd") 

745 self.assertIn("issuer chain", str(e.exception)) 

746 self.assertIn("oops", str(e.exception)) 

747 

748 requests.get.assert_called_with("the-url?update=upd") 

749 X509CertificateValidator.assert_called_with(get_intel_pcs_x509_crl) 

750 self.validator.validate.assert_called_once() 

751 self.assertEqual(self.cert0, self.validator.validate.call_args_list[0].args[0]) 

752 self.assertEqual(self.cert1, self.validator.validate.call_args_list[0].args[1]) 

753 self.assertIsInstance(self.validator.validate.call_args_list[0].args[2], datetime) 

754 self.cert0pk.public_bytes.assert_not_called() 

755 

756 def test_get_qeid_info_err_qeid_sig(self, requests, split_pem_certificates, 

757 mock_x509, X509CertificateValidator): 

758 isig = bytes.fromhex("aa"*32+"bb"*32) 

759 self.configure_mocks(requests, split_pem_certificates, mock_x509, 

760 X509CertificateValidator, issuer_sig=isig) 

761 

762 with self.assertRaises(RuntimeError) as e: 

763 get_qeid_info("the-url", self.cert1, update="upd") 

764 self.assertIn("Signature verification failed", str(e.exception)) 

765 

766 requests.get.assert_called_with("the-url?update=upd") 

767 X509CertificateValidator.assert_called_with(get_intel_pcs_x509_crl) 

768 self.validator.validate.assert_called_once() 

769 self.assertEqual(self.cert0, self.validator.validate.call_args_list[0].args[0]) 

770 self.assertEqual(self.cert1, self.validator.validate.call_args_list[0].args[1]) 

771 self.assertIsInstance(self.validator.validate.call_args_list[0].args[2], datetime) 

772 self.cert0pk.public_bytes.assert_called_once() 

773 self.cert0pk.public_bytes.assert_called_with( 

774 Encoding.X962, PublicFormat.CompressedPoint) 

775 

776 

777class TestSgxUtilsValidateQeIdInfo(TestCase): 

778 def setUp(self): 

779 self.qe_report_info = SimpleNamespace(**{ 

780 "mrsigner": bytes.fromhex("aa"*32), 

781 "isvprodid": 789, 

782 "isvsvn": 123, 

783 "miscselect": 0xcd6789ab, 

784 "attributes": SimpleNamespace(**{ 

785 "flags": 0x44aa33bb22cc11, 

786 "xfrm": 0x88dd77ee66ff55ee, 

787 }) 

788 }) 

789 

790 self.qeid_info = { 

791 "id": "QE", 

792 "version": 2, 

793 "issueDate": "the issue date", 

794 "nextUpdate": "the next update", 

795 "tcbEvaluationDataNumber": 456, 

796 "miscselect": "ab0000cd", 

797 "miscselectMask": "FF0000FF", 

798 "attributes": "11002200330044000055006600770088", 

799 "attributesMask": "FF00FF00FF00FF0000FF00FF00FF00FF", 

800 "mrsigner": "AA"*32, 

801 "isvprodid": 789, 

802 "tcbLevels": [ 

803 { 

804 "tcb": { 

805 "isvsvn": 130 

806 }, 

807 "tcbDate": "2024-03-13T00:00:00Z", 

808 "tcbStatus": "UpToDate" 

809 }, 

810 { 

811 "tcb": { 

812 "isvsvn": 125 

813 }, 

814 "tcbDate": "2021-11-10T00:00:00Z", 

815 "tcbStatus": "OutOfDate", 

816 "advisoryIDs": ["we don't", "care", "at", "all"] 

817 }, 

818 { 

819 "tcb": { 

820 "isvsvn": 120 

821 }, 

822 "tcbDate": "the tcb date", 

823 "tcbStatus": "the tcb status", 

824 "advisoryIDs": [ 

825 "advisory-1", 

826 "advisory-2" 

827 ] 

828 } 

829 ] 

830 } 

831 

832 def test_validate_qeid_info_ok(self): 

833 self.assertEqual({ 

834 "valid": True, 

835 "status": "the tcb status", 

836 "date": "the tcb date", 

837 "advisories": ["advisory-1", "advisory-2"], 

838 "edn": 456, 

839 "isvsvn": "123 >= 120", 

840 }, validate_qeid_info(self.qe_report_info, self.qeid_info)) 

841 

842 def test_validate_qeid_info_mrsigner(self): 

843 self.qe_report_info.mrsigner = "bb"*32 

844 

845 res = validate_qeid_info(self.qe_report_info, self.qeid_info) 

846 self.assertFalse(res["valid"]) 

847 self.assertIn("QE MRSIGNER", res["reason"]) 

848 

849 def test_validate_qeid_info_isvprodid(self): 

850 self.qe_report_info.isvprodid = 444 

851 

852 res = validate_qeid_info(self.qe_report_info, self.qeid_info) 

853 self.assertFalse(res["valid"]) 

854 self.assertIn("QE ISVPRODID", res["reason"]) 

855 

856 def test_validate_qeid_info_miscselect(self): 

857 self.qe_report_info.miscselect = 555 

858 

859 res = validate_qeid_info(self.qe_report_info, self.qeid_info) 

860 self.assertFalse(res["valid"]) 

861 self.assertIn("QE MISCSELECT", res["reason"]) 

862 

863 def test_validate_qeid_info_attribute_flags(self): 

864 self.qe_report_info.attributes.flags = 666 

865 

866 res = validate_qeid_info(self.qe_report_info, self.qeid_info) 

867 self.assertFalse(res["valid"]) 

868 self.assertIn("QE ATTRIBUTES", res["reason"]) 

869 

870 def test_validate_qeid_info_attribute_xfrm(self): 

871 self.qe_report_info.attributes.xfrm = 777 

872 

873 res = validate_qeid_info(self.qe_report_info, self.qeid_info) 

874 self.assertFalse(res["valid"]) 

875 self.assertIn("QE ATTRIBUTES", res["reason"]) 

876 

877 def test_validate_qeid_info_level_notfound(self): 

878 self.qe_report_info.isvsvn = 119 

879 

880 res = validate_qeid_info(self.qe_report_info, self.qeid_info) 

881 self.assertFalse(res["valid"]) 

882 self.assertIn("QE TCB level is not supported", res["reason"]) 

883 

884 def test_validate_qeid_info_malformed(self): 

885 self.qe_report_info = { 

886 "something": "else" 

887 } 

888 

889 res = validate_qeid_info(self.qe_report_info, self.qeid_info) 

890 self.assertFalse(res["valid"]) 

891 self.assertIn("While validating QE identity information", res["reason"])