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

129 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 

23import json 

24import os 

25import secp256k1 as ec 

26 

27from unittest import TestCase 

28from unittest.mock import call, patch, mock_open 

29from admin.certificate import HSMCertificate, HSMCertificateRoot, HSMCertificateElement 

30 

31 

32class TestHSMCertificate(TestCase): 

33 def test_create_valid_certificate_ok(self): 

34 cert = HSMCertificate({ 

35 "version": 1, 

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

37 "elements": [ 

38 { 

39 "name": "attestation", 

40 "message": 'aa', 

41 "signature": 'bb', 

42 "signed_by": "device" 

43 }, 

44 { 

45 "name": "device", 

46 "message": 'cc', 

47 "signature": 'dd', 

48 "signed_by": "root" 

49 }] 

50 }) 

51 self.assertEqual({ 

52 "version": 1, 

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

54 "elements": [ 

55 { 

56 "name": "attestation", 

57 "message": 'aa', 

58 "signature": 'bb', 

59 "signed_by": "device" 

60 }, 

61 { 

62 "name": "device", 

63 "message": 'cc', 

64 "signature": 'dd', 

65 "signed_by": "root" 

66 }] 

67 }, cert.to_dict()) 

68 

69 def test_create_empty_certificate_ok(self): 

70 cert = HSMCertificate() 

71 self.assertEqual({'version': 1, 'targets': [], 'elements': []}, cert.to_dict()) 

72 

73 def test_create_certificate_invalid_version(self): 

74 with self.assertRaises(ValueError): 

75 HSMCertificate({ 

76 "version": 99, 

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

78 "elements": [ 

79 { 

80 "name": "attestation", 

81 "message": 'aa', 

82 "signature": 'bb', 

83 "signed_by": "device" 

84 }, 

85 { 

86 "name": "device", 

87 "message": 'cc', 

88 "signature": 'dd', 

89 "signed_by": "root" 

90 }] 

91 }) 

92 

93 def test_create_certificate_no_version(self): 

94 with self.assertRaises(ValueError): 

95 HSMCertificate({ 

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

97 "elements": [ 

98 { 

99 "name": "attestation", 

100 "message": 'aa', 

101 "signature": 'bb', 

102 "signed_by": "device" 

103 }, 

104 { 

105 "name": "device", 

106 "message": 'cc', 

107 "signature": 'dd', 

108 "signed_by": "root" 

109 }] 

110 }) 

111 

112 def test_create_certificate_missing_targets(self): 

113 with self.assertRaises(ValueError): 

114 HSMCertificate({ 

115 "version": 1, 

116 "elements": [ 

117 { 

118 "name": "attestation", 

119 "message": 'aa', 

120 "signature": 'bb', 

121 "signed_by": "device" 

122 }, 

123 { 

124 "name": "device", 

125 "message": 'cc', 

126 "signature": 'dd', 

127 "signed_by": "root" 

128 }] 

129 }) 

130 

131 def test_create_certificate_invalid_targets(self): 

132 with self.assertRaises(ValueError): 

133 HSMCertificate({ 

134 "version": 1, 

135 "targets": "invalid-targets", 

136 "elements": [ 

137 { 

138 "name": "attestation", 

139 "message": 'aa', 

140 "signature": 'bb', 

141 "signed_by": "device" 

142 }, 

143 { 

144 "name": "device", 

145 "message": 'cc', 

146 "signature": 'dd', 

147 "signed_by": "root" 

148 }] 

149 }) 

150 

151 def test_create_certificate_missing_elements(self): 

152 with self.assertRaises(ValueError): 

153 HSMCertificate({ 

154 "version": 1, 

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

156 }) 

157 

158 @patch('admin.certificate_v1.HSMCertificate.ELEMENT_FACTORY') 

159 def test_create_certificate_invalid_element(self, certElementMock): 

160 certElementMock.side_effect = ValueError() 

161 with self.assertRaises(ValueError): 

162 HSMCertificate({ 

163 "version": 1, 

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

165 "elements": [ 

166 { 

167 "name": "attestation", 

168 "message": 'aa', 

169 "signature": 'bb', 

170 "signed_by": "device" 

171 }, 

172 { 

173 "name": "device", 

174 "message": 'cc', 

175 "signature": 'dd', 

176 "signed_by": "root" 

177 }] 

178 }) 

179 

180 def test_create_certificate_target_not_in_elements(self): 

181 with self.assertRaises(ValueError): 

182 HSMCertificate({ 

183 "version": 1, 

184 "targets": ["attestation", "device", "ui"], 

185 "elements": [ 

186 { 

187 "name": "attestation", 

188 "message": 'aa', 

189 "signature": 'bb', 

190 "signed_by": "device" 

191 }, 

192 { 

193 "name": "device", 

194 "message": 'cc', 

195 "signature": 'dd', 

196 "signed_by": "root" 

197 }] 

198 }) 

199 

200 def test_create_certificate_elements_without_path_to_root(self): 

201 with self.assertRaises(ValueError): 

202 HSMCertificate({ 

203 "version": 1, 

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

205 "elements": [ 

206 { 

207 "name": "attestation", 

208 "message": 'aa', 

209 "signature": 'bb', 

210 "signed_by": "attestation" 

211 }, 

212 { 

213 "name": "device", 

214 "message": 'cc', 

215 "signature": 'dd', 

216 "signed_by": "root" 

217 }] 

218 }) 

219 

220 def test_create_certificate_signer_not_in_elements(self): 

221 with self.assertRaises(ValueError): 

222 HSMCertificate({ 

223 "version": 1, 

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

225 "elements": [ 

226 { 

227 "name": "attestation", 

228 "message": 'aa', 

229 "signature": 'bb', 

230 "signed_by": "signer" 

231 }, 

232 { 

233 "name": "device", 

234 "message": 'cc', 

235 "signature": 'dd', 

236 "signed_by": "root" 

237 }] 

238 }) 

239 

240 def test_validate_and_get_values_ok(self): 

241 root_privkey = ec.PrivateKey() 

242 root_pubkey = root_privkey.pubkey.serialize(compressed=False).hex() 

243 root_of_trust = HSMCertificateRoot(root_pubkey) 

244 device_privkey = ec.PrivateKey() 

245 device_pubkey = device_privkey.pubkey.serialize(compressed=False).hex() 

246 att_pubkey = ec.PrivateKey().pubkey.serialize(compressed=False).hex() 

247 

248 att_msg = 'ff' + att_pubkey 

249 att_sig = device_privkey.ecdsa_serialize( 

250 device_privkey.ecdsa_sign(bytes.fromhex(att_msg))).hex() 

251 

252 device_msg = os.urandom(16).hex() + device_pubkey 

253 device_sig = root_privkey.ecdsa_serialize( 

254 root_privkey.ecdsa_sign(bytes.fromhex(device_msg))).hex() 

255 

256 cert = HSMCertificate({ 

257 "version": 1, 

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

259 "elements": [ 

260 { 

261 "name": "attestation", 

262 "message": att_msg, 

263 "signature": att_sig, 

264 "signed_by": "device" 

265 }, 

266 { 

267 "name": "device", 

268 "message": device_msg, 

269 "signature": device_sig, 

270 "signed_by": "root" 

271 }] 

272 }) 

273 

274 self.assertEqual({ 

275 'attestation': { 

276 "valid": True, 

277 "value": att_pubkey, 

278 "tweak": None, 

279 "collateral": {} 

280 }, 

281 'device': { 

282 "valid": True, 

283 "value": device_pubkey, 

284 "tweak": None, 

285 "collateral": {} 

286 }, 

287 }, cert.validate_and_get_values(root_of_trust)) 

288 

289 def test_validate_and_get_values_invalid_element(self): 

290 root_privkey = ec.PrivateKey() 

291 root_pubkey = root_privkey.pubkey.serialize(compressed=False).hex() 

292 root_of_trust = HSMCertificateRoot(root_pubkey) 

293 device_privkey = ec.PrivateKey() 

294 device_pubkey = device_privkey.pubkey.serialize(compressed=False).hex() 

295 att_pubkey = ec.PrivateKey().pubkey.serialize(compressed=False).hex() 

296 

297 att_msg = 'ff' + att_pubkey 

298 att_sig = 'aa' * 65 

299 

300 device_msg = os.urandom(16).hex() + device_pubkey 

301 device_sig = root_privkey.ecdsa_serialize( 

302 root_privkey.ecdsa_sign(bytes.fromhex(device_msg))).hex() 

303 

304 cert = HSMCertificate({ 

305 "version": 1, 

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

307 "elements": [ 

308 { 

309 "name": "attestation", 

310 "message": att_msg, 

311 "signature": att_sig, 

312 "signed_by": "device" 

313 }, 

314 { 

315 "name": "device", 

316 "message": device_msg, 

317 "signature": device_sig, 

318 "signed_by": "root" 

319 }] 

320 }) 

321 

322 self.assertEqual({ 

323 'attestation': { 

324 "valid": False, 

325 "failed_element": "attestation" 

326 }, 

327 'device': { 

328 "valid": True, 

329 "value": device_pubkey, 

330 "tweak": None, 

331 "collateral": {} 

332 }, 

333 }, cert.validate_and_get_values(root_of_trust)) 

334 

335 def test_validate_and_get_values_invalid_elements(self): 

336 att_privkey = ec.PrivateKey() 

337 att_msg = os.urandom(66).hex() 

338 att_sig = 'aa' * 65 

339 

340 device_privkey = ec.PrivateKey() 

341 device_pubkey = device_privkey.pubkey.serialize(compressed=False).hex() 

342 device_msg = os.urandom(16).hex() + \ 

343 att_privkey.pubkey.serialize(compressed=False).hex() 

344 device_sig = 'bb' * 65 

345 

346 cert = HSMCertificate({ 

347 "version": 1, 

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

349 "elements": [ 

350 { 

351 "name": "attestation", 

352 "message": att_msg, 

353 "signature": att_sig, 

354 "signed_by": "device" 

355 }, 

356 { 

357 "name": "device", 

358 "message": device_msg, 

359 "signature": device_sig, 

360 "signed_by": "root" 

361 }] 

362 }) 

363 

364 self.assertEqual({ 

365 'attestation': { 

366 "valid": False, 

367 "failed_element": "device" 

368 }, 

369 'device': { 

370 "valid": False, 

371 "failed_element": "device" 

372 }, 

373 }, cert.validate_and_get_values(device_pubkey)) 

374 

375 def test_add_element_ok(self): 

376 cert = HSMCertificate() 

377 self.assertEqual({'version': 1, 'targets': [], 'elements': []}, cert.to_dict()) 

378 

379 cert.add_element(HSMCertificateElement({ 

380 "name": "device", 

381 "message": 'cc', 

382 "signature": 'dd', 

383 "signed_by": "root" 

384 })) 

385 self.assertEqual({'version': 1, 'targets': [], 'elements': [ 

386 { 

387 "name": "device", 

388 "message": 'cc', 

389 "signature": 'dd', 

390 "signed_by": "root" 

391 } 

392 ]}, cert.to_dict()) 

393 

394 def test_add_element_invalid_element(self): 

395 cert = HSMCertificate() 

396 self.assertEqual({'version': 1, 'targets': [], 'elements': []}, cert.to_dict()) 

397 with self.assertRaises(ValueError): 

398 cert.add_element('not-an-element') 

399 self.assertEqual({'version': 1, 'targets': [], 'elements': []}, cert.to_dict()) 

400 

401 def test_add_target_ok(self): 

402 cert = HSMCertificate({ 

403 "version": 1, 

404 "targets": [], 

405 "elements": [ 

406 { 

407 "name": "attestation", 

408 "message": 'aa', 

409 "signature": 'bb', 

410 "signed_by": "device" 

411 }, 

412 { 

413 "name": "device", 

414 "message": 'cc', 

415 "signature": 'dd', 

416 "signed_by": "root" 

417 }] 

418 }) 

419 cert.add_target('attestation') 

420 cert.add_target('device') 

421 self.assertEqual({ 

422 "version": 1, 

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

424 "elements": [ 

425 { 

426 "name": "attestation", 

427 "message": 'aa', 

428 "signature": 'bb', 

429 "signed_by": "device" 

430 }, 

431 { 

432 "name": "device", 

433 "message": 'cc', 

434 "signature": 'dd', 

435 "signed_by": "root" 

436 }] 

437 }, cert.to_dict()) 

438 

439 def test_add_target_not_in_elements(self): 

440 cert = HSMCertificate({ 

441 "version": 1, 

442 "targets": [], 

443 "elements": [ 

444 { 

445 "name": "attestation", 

446 "message": 'aa', 

447 "signature": 'bb', 

448 "signed_by": "device" 

449 }, 

450 { 

451 "name": "device", 

452 "message": 'cc', 

453 "signature": 'dd', 

454 "signed_by": "root" 

455 }] 

456 }) 

457 cert.add_target('attestation') 

458 with self.assertRaises(ValueError): 

459 cert.add_target('ui') 

460 self.assertEqual({ 

461 "version": 1, 

462 "targets": ["attestation"], 

463 "elements": [ 

464 { 

465 "name": "attestation", 

466 "message": 'aa', 

467 "signature": 'bb', 

468 "signed_by": "device" 

469 }, 

470 { 

471 "name": "device", 

472 "message": 'cc', 

473 "signature": 'dd', 

474 "signed_by": "root" 

475 }] 

476 }, cert.to_dict()) 

477 

478 def test_clear_targets(self): 

479 cert = HSMCertificate({ 

480 "version": 1, 

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

482 "elements": [ 

483 { 

484 "name": "attestation", 

485 "message": 'aa', 

486 "signature": 'bb', 

487 "signed_by": "device" 

488 }, 

489 { 

490 "name": "device", 

491 "message": 'cc', 

492 "signature": 'dd', 

493 "signed_by": "root" 

494 }] 

495 }) 

496 cert.clear_targets() 

497 self.assertEqual({ 

498 "version": 1, 

499 "targets": [], 

500 "elements": [ 

501 { 

502 "name": "attestation", 

503 "message": 'aa', 

504 "signature": 'bb', 

505 "signed_by": "device" 

506 }, 

507 { 

508 "name": "device", 

509 "message": 'cc', 

510 "signature": 'dd', 

511 "signed_by": "root" 

512 }] 

513 }, cert.to_dict()) 

514 

515 def test_save_to_jsonfile_ok(self): 

516 cert = HSMCertificate({ 

517 "version": 1, 

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

519 "elements": [ 

520 { 

521 "name": "attestation", 

522 "message": 'aa', 

523 "signature": 'bb', 

524 "signed_by": "device" 

525 }, 

526 { 

527 "name": "device", 

528 "message": 'cc', 

529 "signature": 'dd', 

530 "signed_by": "root" 

531 }] 

532 }) 

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

534 cert.save_to_jsonfile('file-path') 

535 self.assertEqual([call('file-path', 'w')], file_mock.call_args_list) 

536 self.assertEqual([call('{\n "version": 1,\n' 

537 ' "targets": [\n' 

538 ' "attestation",\n' 

539 ' "device"\n ],\n' 

540 ' "elements": [\n' 

541 ' {\n' 

542 ' "name": "attestation",\n' 

543 ' "message": "aa",\n' 

544 ' "signature": "bb",\n' 

545 ' "signed_by": "device"\n' 

546 ' },\n' 

547 ' {\n' 

548 ' "name": "device",\n' 

549 ' "message": "cc",\n' 

550 ' "signature": "dd",\n' 

551 ' "signed_by": "root"\n' 

552 ' }\n' 

553 ' ]\n' 

554 '}\n')], file_mock.return_value.write.call_args_list) 

555 

556 def test_save_to_jsonfile_write_error(self): 

557 cert = HSMCertificate({ 

558 "version": 1, 

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

560 "elements": [ 

561 { 

562 "name": "attestation", 

563 "message": 'aa', 

564 "signature": 'bb', 

565 "signed_by": "device" 

566 }, 

567 { 

568 "name": "device", 

569 "message": 'cc', 

570 "signature": 'dd', 

571 "signed_by": "root" 

572 }] 

573 }) 

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

575 file_mock.side_effect = Exception() 

576 with self.assertRaises(Exception): 

577 cert.save_to_jsonfile('file-path') 

578 self.assertEqual([call('file-path', 'w')], file_mock.call_args_list) 

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

580 

581 def test_from_jsonfile_ok(self): 

582 cert_dict = { 

583 "version": 1, 

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

585 "elements": [ 

586 { 

587 "name": "attestation", 

588 "message": 'aa', 

589 "signature": 'bb', 

590 "signed_by": "device" 

591 }, 

592 { 

593 "name": "device", 

594 "message": 'cc', 

595 "signature": 'dd', 

596 "signed_by": "root" 

597 }] 

598 } 

599 with patch('builtins.open', mock_open(read_data=json.dumps(cert_dict))) as file: 

600 certificate = HSMCertificate.from_jsonfile('file-path') 

601 self.assertEqual([call('file-path', 'r')], file.call_args_list) 

602 self.assertEqual(cert_dict, certificate.to_dict()) 

603 

604 def test_from_jsonfile_error(self): 

605 with patch('builtins.open', mock_open(read_data='invalid-data')) as file: 

606 with self.assertRaises(ValueError): 

607 HSMCertificate.from_jsonfile('file-path') 

608 self.assertEqual([call('file-path', 'r')], file.call_args_list)