Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/securesystemslib/_gpg/common.py: 8%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

203 statements  

1""" 

2<Module Name> 

3 common.py 

4 

5<Author> 

6 Santiago Torres-Arias <santiago@nyu.edu> 

7 

8<Started> 

9 Nov 15, 2017 

10 

11<Copyright> 

12 See LICENSE for licensing information. 

13 

14<Purpose> 

15 Provides algorithm-agnostic gpg public key and signature parsing functions. 

16 The functions select the appropriate functions for each algorithm and 

17 call them. 

18 

19""" 

20 

21import binascii 

22import collections 

23import logging 

24import struct 

25 

26from securesystemslib._gpg import util as gpg_util 

27from securesystemslib._gpg.constants import ( 

28 FULL_KEYID_SUBPACKET, 

29 GPG_HASH_ALGORITHM_STRING, 

30 KEY_EXPIRATION_SUBPACKET, 

31 PACKET_TYPE_PRIMARY_KEY, 

32 PACKET_TYPE_SIGNATURE, 

33 PACKET_TYPE_SUB_KEY, 

34 PACKET_TYPE_USER_ATTR, 

35 PACKET_TYPE_USER_ID, 

36 PARTIAL_KEYID_SUBPACKET, 

37 PRIMARY_USERID_SUBPACKET, 

38 SHA1, 

39 SHA256, 

40 SHA512, 

41 SIG_CREATION_SUBPACKET, 

42 SIGNATURE_TYPE_BINARY, 

43 SIGNATURE_TYPE_CERTIFICATES, 

44 SIGNATURE_TYPE_SUB_KEY_BINDING, 

45 SUPPORTED_PUBKEY_PACKET_VERSIONS, 

46 SUPPORTED_SIGNATURE_PACKET_VERSIONS, 

47) 

48from securesystemslib._gpg.exceptions import ( 

49 KeyNotFoundError, 

50 PacketParsingError, 

51 PacketVersionNotSupportedError, 

52 SignatureAlgorithmNotSupportedError, 

53) 

54from securesystemslib._gpg.handlers import ( 

55 SIGNATURE_HANDLERS, 

56 SUPPORTED_SIGNATURE_ALGORITHMS, 

57) 

58 

59log = logging.getLogger(__name__) 

60 

61 

62def parse_pubkey_payload(data): 

63 """ 

64 <Purpose> 

65 Parse the passed public-key packet (payload only) and construct a 

66 public key dictionary. 

67 

68 <Arguments> 

69 data: 

70 An RFC4880 public key packet payload as described in section 5.5.2. 

71 (version 4) of the RFC. 

72 

73 NOTE: The payload can be parsed from a full key packet (header + 

74 payload) by using securesystemslib._gpg.util.parse_packet_header. 

75 

76 WARNING: this doesn't support armored pubkey packets, so use with 

77 care. pubkey packets are a little bit more complicated than the 

78 signature ones 

79 

80 <Exceptions> 

81 ValueError 

82 If the passed public key data is empty. 

83 

84 securesystemslib._gpg.exceptions.PacketVersionNotSupportedError 

85 If the packet version does not match 

86 securesystemslib._gpg.constants.SUPPORTED_PUBKEY_PACKET_VERSIONS 

87 

88 securesystemslib._gpg.exceptions.SignatureAlgorithmNotSupportedError 

89 If the signature algorithm does not match one of 

90 securesystemslib._gpg.constants.SUPPORTED_SIGNATURE_ALGORITHMS 

91 

92 <Side Effects> 

93 None. 

94 

95 <Returns> 

96 A public key dict. 

97 

98 """ 

99 if not data: 

100 raise ValueError("Could not parse empty pubkey payload.") 

101 

102 ptr = 0 

103 keyinfo = {} 

104 version_number = data[ptr] 

105 ptr += 1 

106 if version_number not in SUPPORTED_PUBKEY_PACKET_VERSIONS: 

107 raise PacketVersionNotSupportedError( 

108 f"Pubkey packet version '{version_number}' not supported, " 

109 f"must be one of {SUPPORTED_PUBKEY_PACKET_VERSIONS}" 

110 ) 

111 

112 # NOTE: Uncomment this line to decode the time of creation 

113 time_of_creation = struct.unpack(">I", data[ptr : ptr + 4]) 

114 ptr += 4 

115 

116 algorithm = data[ptr] 

117 

118 ptr += 1 

119 

120 # TODO: Should we only export keys with signing capabilities? 

121 # Section 5.5.2 of RFC4880 describes a public-key algorithm octet with one 

122 # of the values described in section 9.1 that could be used to determine the 

123 # capabilities. However, in case of RSA subkeys this field doesn't seem to 

124 # correctly encode the capabilities. It always has the value 1, i.e. 

125 # RSA (Encrypt or Sign). 

126 # For RSA public keys we would have to parse the subkey's signature created 

127 # with the master key, for the signature's key flags subpacket, identified 

128 # by the value 27 (see section 5.2.3.1.) containing a list of binary flags 

129 # as described in section 5.2.3.21. 

130 if algorithm not in SUPPORTED_SIGNATURE_ALGORITHMS: 

131 raise SignatureAlgorithmNotSupportedError( 

132 f"Signature algorithm '{algorithm}' not " 

133 "supported, please verify that your gpg configuration is creating " 

134 "either DSA, RSA, or EdDSA signatures (see RFC4880 9.1. Public-Key " 

135 "Algorithms)." 

136 ) 

137 

138 keyinfo["type"] = SUPPORTED_SIGNATURE_ALGORITHMS[algorithm]["type"] 

139 keyinfo["method"] = SUPPORTED_SIGNATURE_ALGORITHMS[algorithm]["method"] 

140 handler = SIGNATURE_HANDLERS[keyinfo["type"]] 

141 keyinfo["keyid"] = gpg_util.compute_keyid(data) 

142 key_params = handler.get_pubkey_params(data[ptr:]) 

143 

144 return { 

145 "method": keyinfo["method"], 

146 "type": keyinfo["type"], 

147 "hashes": [GPG_HASH_ALGORITHM_STRING], 

148 "creation_time": time_of_creation[0], 

149 "keyid": keyinfo["keyid"], 

150 "keyval": {"private": "", "public": key_params}, 

151 } 

152 

153 

154def parse_pubkey_bundle(data): 

155 """ 

156 <Purpose> 

157 Parse packets from passed gpg public key data, associating self-signatures 

158 with the packets they correspond to, based on the structure of V4 keys 

159 defined in RFC4880 12.1 Key Structures. 

160 

161 The returned raw key bundle may be used to further enrich the master key, 

162 with certified information (e.g. key expiration date) taken from 

163 self-signatures, and/or to verify that the parsed subkeys are bound to the 

164 primary key via signatures. 

165 

166 <Arguments> 

167 data: 

168 Public key data as written to stdout by gpg_export_pubkey_command. 

169 

170 <Exceptions> 

171 securesystemslib._gpg.exceptions.PacketParsingError 

172 If data is empty. 

173 If data cannot be parsed. 

174 

175 <Side Effects> 

176 None. 

177 

178 <Returns> 

179 A raw public key bundle where self-signatures are associated with their 

180 corresponding packets. See `key_bundle` for details. 

181 

182 """ 

183 if not data: 

184 raise PacketParsingError("Cannot parse keys from empty gpg data.") 

185 

186 # Temporary data structure to hold parsed gpg packets 

187 key_bundle = { 

188 PACKET_TYPE_PRIMARY_KEY: {"key": {}, "packet": None, "signatures": []}, 

189 PACKET_TYPE_USER_ID: collections.OrderedDict(), 

190 PACKET_TYPE_USER_ATTR: collections.OrderedDict(), 

191 PACKET_TYPE_SUB_KEY: collections.OrderedDict(), 

192 } 

193 

194 # Iterate over gpg data and parse out packets of different types 

195 position = 0 

196 while position < len(data): 

197 try: 

198 ( 

199 packet_type, 

200 header_len, 

201 body_len, 

202 packet_length, 

203 ) = gpg_util.parse_packet_header(data[position:]) 

204 

205 packet = data[position : position + packet_length] 

206 payload = packet[header_len:] 

207 # The first (and only the first) packet in the bundle must be the master 

208 # key. See RFC4880 12.1 Key Structures, V4 version keys 

209 # TODO: Do we need additional key structure assertions? e.g. 

210 # - there must be least one User ID packet, or 

211 # - order and type of signatures, or 

212 # - disallow duplicate packets 

213 if ( 

214 packet_type != PACKET_TYPE_PRIMARY_KEY 

215 and not key_bundle[PACKET_TYPE_PRIMARY_KEY]["key"] 

216 ): 

217 raise PacketParsingError( 

218 "First packet must be a primary key " 

219 f"('{PACKET_TYPE_PRIMARY_KEY}'), got '{packet_type}'." 

220 ) 

221 

222 if ( 

223 packet_type == PACKET_TYPE_PRIMARY_KEY 

224 and key_bundle[PACKET_TYPE_PRIMARY_KEY]["key"] 

225 ): 

226 raise PacketParsingError("Unexpected primary key.") 

227 

228 # Fully parse master key to fail early, e.g. if key is malformed 

229 # or not supported, but also retain original packet for subkey binding 

230 # signature verification 

231 if packet_type == PACKET_TYPE_PRIMARY_KEY: 

232 key_bundle[PACKET_TYPE_PRIMARY_KEY] = { 

233 "key": parse_pubkey_payload(bytearray(payload)), 

234 "packet": packet, 

235 "signatures": [], 

236 } 

237 

238 # Other non-signature packets in the key bundle include User IDs and User 

239 # Attributes, required to verify primary key certificates, and subkey 

240 # packets. For each packet we create a new ordered dictionary entry. We 

241 # use a dictionary to aggregate signatures by packet below, 

242 # and it must be ordered because each signature packet belongs to the 

243 # most recently parsed packet of a type. 

244 elif packet_type in { 

245 PACKET_TYPE_USER_ID, 

246 PACKET_TYPE_USER_ATTR, 

247 PACKET_TYPE_SUB_KEY, 

248 }: 

249 key_bundle[packet_type][packet] = { 

250 "header_len": header_len, 

251 "body_len": body_len, 

252 "signatures": [], 

253 } 

254 

255 # The remaining relevant packets are signatures, required to bind subkeys 

256 # to the primary key, or to gather additional information about the 

257 # primary key, e.g. expiration date. 

258 # A signature corresponds to the most recently parsed packet of a type, 

259 # where the type is given by the availability of respective packets. 

260 # We test availability and assign accordingly as per the order of packet 

261 # types defined in RFC4880 12.1 (bottom-up). 

262 elif packet_type == PACKET_TYPE_SIGNATURE: 

263 for _type in [ 

264 PACKET_TYPE_SUB_KEY, 

265 PACKET_TYPE_USER_ATTR, 

266 PACKET_TYPE_USER_ID, 

267 ]: 

268 if key_bundle[_type]: 

269 # Add to most recently added packet's 

270 # signatures of matching type 

271 key_bundle[_type][next(reversed(key_bundle[_type]))][ 

272 "signatures" 

273 ].append(packet) 

274 break 

275 

276 else: 

277 # If no packets are available for any of above types (yet), the 

278 # signature belongs to the primary key 

279 key_bundle[PACKET_TYPE_PRIMARY_KEY]["signatures"].append(packet) 

280 

281 else: 

282 packets_list = [ 

283 PACKET_TYPE_PRIMARY_KEY, 

284 PACKET_TYPE_USER_ID, 

285 PACKET_TYPE_USER_ATTR, 

286 PACKET_TYPE_SUB_KEY, 

287 PACKET_TYPE_SIGNATURE, 

288 ] 

289 log.info( 

290 f"Ignoring gpg key packet '{packet_type}', " 

291 "we only handle packets of " 

292 f"types '{packets_list}' (see RFC4880 4.3. Packet Tags)." 

293 ) 

294 

295 # Both errors might be raised in parse_packet_header and in this loop 

296 except (PacketParsingError, IndexError) as e: 

297 raise PacketParsingError( 

298 f"Invalid public key data at position {position}: {e}." 

299 ) 

300 

301 # Go to next packet 

302 position += packet_length 

303 

304 return key_bundle 

305 

306 

307def _assign_certified_key_info(bundle): 

308 """ 

309 <Purpose> 

310 Helper function to verify User ID certificates corresponding to a gpg 

311 master key, in order to enrich the master key with additional information 

312 (e.g. expiration dates). The enriched master key is returned. 

313 

314 NOTE: Currently we only consider User ID certificates. We can do the same 

315 for User Attribute certificates by iterating over 

316 bundle[PACKET_TYPE_USER_ATTR] instead of bundle[PACKET_TYPE_USER_ID], and 

317 replacing the signed_content constant '\xb4' with '\xd1' (see RFC4880 

318 section 5.2.4. paragraph 4). 

319 

320 <Arguments> 

321 bundle: 

322 GPG key bundle as parsed in parse_pubkey_bundle(). 

323 

324 <Exceptions> 

325 None. 

326 

327 <Side Effects> 

328 None. 

329 

330 <Returns> 

331 A public key dict. 

332 

333 """ 

334 # Create handler shortcut 

335 handler = SIGNATURE_HANDLERS[bundle[PACKET_TYPE_PRIMARY_KEY]["key"]["type"]] 

336 

337 is_primary_user = False 

338 validity_period = None 

339 sig_creation_time = None 

340 

341 # Verify User ID signatures to gather information about primary key 

342 # (see Notes about certification signatures in RFC 4880 5.2.3.3.) 

343 for user_id_packet, packet_data in bundle[PACKET_TYPE_USER_ID].items(): 

344 # Construct signed content (see RFC4880 section 5.2.4. paragraph 4) 

345 signed_content = ( 

346 bundle[PACKET_TYPE_PRIMARY_KEY]["packet"] 

347 + b"\xb4\x00\x00\x00" 

348 + user_id_packet[1:] 

349 ) 

350 for signature_packet in packet_data["signatures"]: 

351 try: 

352 signature = parse_signature_packet( 

353 signature_packet, 

354 supported_hash_algorithms={SHA1, SHA256, SHA512}, 

355 supported_signature_types=SIGNATURE_TYPE_CERTIFICATES, 

356 include_info=True, 

357 ) 

358 # verify_signature requires a "keyid" even if it is short. 

359 # (see parse_signature_packet for more information about keyids) 

360 signature["keyid"] = signature["keyid"] or signature["short_keyid"] 

361 

362 # TODO: Revise exception taxonomy: 

363 # It's okay to ignore some exceptions (unsupported algorithms etc.) but 

364 # we should blow up if a signature is malformed (missing subpackets). 

365 except Exception as e: 

366 log.info(e) 

367 continue 

368 

369 if not bundle[PACKET_TYPE_PRIMARY_KEY]["key"]["keyid"].endswith( 

370 signature["keyid"] 

371 ): 

372 log.info( 

373 "Ignoring User ID certificate issued by '{}'.".format( 

374 signature["keyid"] 

375 ) 

376 ) 

377 continue 

378 

379 is_valid = handler.verify_signature( 

380 signature, 

381 bundle[PACKET_TYPE_PRIMARY_KEY]["key"], 

382 signed_content, 

383 signature["info"]["hash_algorithm"], 

384 ) 

385 

386 if not is_valid: 

387 log.info( 

388 "Ignoring invalid User ID self-certificate issued by '{}'.".format( 

389 signature["keyid"] 

390 ) 

391 ) 

392 continue 

393 

394 # If the signature is valid, we try to extract subpackets relevant to 

395 # the primary key, i.e. expiration time. 

396 # NOTE: There might be multiple User IDs per primary key and multiple 

397 # certificates per User ID. RFC4880 5.2.3.19. and last paragraph of 

398 # 5.2.3.3. provides some suggestions about ambiguity, but delegates the 

399 # responsibility to the implementer. 

400 

401 # Ambiguity resolution scheme: 

402 # We take the key expiration time from the most recent certificate, i.e. 

403 # the certificate with the highest signature creation time. Additionally, 

404 # we prioritize certificates with primary user id flag set True. Note 

405 # that, if the ultimately prioritized certificate does not have a key 

406 # expiration time subpacket, we don't assign one, even if there were 

407 # certificates of lower priority carrying that subpacket. 

408 tmp_validity_period = signature["info"]["subpackets"].get( 

409 KEY_EXPIRATION_SUBPACKET 

410 ) 

411 

412 # No key expiration time, go to next certificate 

413 if tmp_validity_period is None: 

414 continue 

415 

416 # Create shortcut to mandatory pre-parsed creation time subpacket 

417 tmp_sig_creation_time = signature["info"]["creation_time"] 

418 

419 tmp_is_primary_user = signature["info"]["subpackets"].get( 

420 PRIMARY_USERID_SUBPACKET 

421 ) 

422 

423 if tmp_is_primary_user is not None: 

424 tmp_is_primary_user = bool(tmp_is_primary_user[0]) 

425 

426 # If we already have a primary user certified expiration date and this 

427 # is none, we don't consider it, and go to next certificate 

428 if is_primary_user and not tmp_is_primary_user: 

429 continue 

430 

431 if not sig_creation_time or sig_creation_time < tmp_sig_creation_time: 

432 # This is the most recent certificate that has a validity_period and 

433 # doesn't have lower priority in regard to the primary user id flag. We 

434 # accept it the keys validty_period, until we get a newer value from 

435 # a certificate with higher priority. 

436 validity_period = struct.unpack(">I", tmp_validity_period)[0] 

437 # We also keep track of the used certificate's primary user id flag and 

438 # the signature creation time, for prioritization. 

439 is_primary_user = tmp_is_primary_user 

440 sig_creation_time = tmp_sig_creation_time 

441 

442 if validity_period is not None: 

443 bundle[PACKET_TYPE_PRIMARY_KEY]["key"]["validity_period"] = validity_period 

444 

445 return bundle[PACKET_TYPE_PRIMARY_KEY]["key"] 

446 

447 

448def _get_verified_subkeys(bundle): 

449 """ 

450 <Purpose> 

451 Helper function to verify the subkey binding signature for all subkeys in 

452 the passed bundle in order to enrich subkeys with additional information 

453 (e.g. expiration dates). Only valid (i.e. parsable) subkeys that are 

454 verifiably bound to the the master key of the bundle are returned. All 

455 other subkeys are discarded. 

456 

457 <Arguments> 

458 bundle: 

459 GPG key bundle as parsed in parse_pubkey_bundle(). 

460 

461 <Exceptions> 

462 None. 

463 

464 <Side Effects> 

465 None. 

466 

467 <Returns> 

468 A dict of public keys dicts with keyids as dict keys. 

469 

470 """ 

471 # Create handler shortcut 

472 handler = SIGNATURE_HANDLERS[bundle[PACKET_TYPE_PRIMARY_KEY]["key"]["type"]] 

473 

474 # Verify subkey binding signatures and only keep verified keys 

475 # See notes about subkey binding signature in RFC4880 5.2.3.3 

476 verified_subkeys = {} 

477 for subkey_packet, packet_data in bundle[PACKET_TYPE_SUB_KEY].items(): 

478 try: 

479 # Parse subkey if possible and skip if invalid (e.g. not-supported) 

480 subkey = parse_pubkey_payload( 

481 bytearray(subkey_packet[-packet_data["body_len"] :]) 

482 ) 

483 

484 # TODO: Revise exception taxonomy 

485 except Exception as e: 

486 log.info(e) 

487 continue 

488 

489 # Construct signed content (see RFC4880 section 5.2.4. paragraph 3) 

490 signed_content = ( 

491 bundle[PACKET_TYPE_PRIMARY_KEY]["packet"] + b"\x99" + subkey_packet[1:] 

492 ) 

493 

494 # Filter sub key binding signature from other signatures, e.g. subkey 

495 # binding revocation signatures 

496 key_binding_signatures = [] 

497 for signature_packet in packet_data["signatures"]: 

498 try: 

499 signature = parse_signature_packet( 

500 signature_packet, 

501 supported_hash_algorithms={SHA1, SHA256, SHA512}, 

502 supported_signature_types={SIGNATURE_TYPE_SUB_KEY_BINDING}, 

503 include_info=True, 

504 ) 

505 # verify_signature requires a "keyid" even if it is short. 

506 # (see parse_signature_packet for more information about keyids) 

507 signature["keyid"] = signature["keyid"] or signature["short_keyid"] 

508 key_binding_signatures.append(signature) 

509 

510 # TODO: Revise exception taxonomy 

511 except Exception as e: 

512 log.info(e) 

513 continue 

514 # NOTE: As per the V4 key structure diagram in RFC4880 section 12.1., a 

515 # subkey must be followed by exactly one Primary-Key-Binding-Signature. 

516 # Based on inspection of real-world keys and other parts of the RFC (e.g. 

517 # the paragraph below the diagram and paragraph 0x18: Subkey Binding 

518 # Signature in section 5.2.1.) the mandated signature is actually a 

519 # *subkey binding signature*, which in case of a signing subkey, must have 

520 # an *embedded primary key binding signature*. 

521 if len(key_binding_signatures) != 1: 

522 log.info( 

523 "Ignoring subkey '{}' due to wrong amount of key binding " 

524 "signatures ({}), must be exactly 1.".format( 

525 subkey["keyid"], len(key_binding_signatures) 

526 ) 

527 ) 

528 continue 

529 is_valid = handler.verify_signature( 

530 signature, 

531 bundle[PACKET_TYPE_PRIMARY_KEY]["key"], 

532 signed_content, 

533 signature["info"]["hash_algorithm"], 

534 ) 

535 

536 if not is_valid: 

537 log.info( 

538 "Ignoring subkey '{}' due to invalid key binding signature.".format( 

539 subkey["keyid"] 

540 ) 

541 ) 

542 continue 

543 

544 # If the signature is valid, we may also extract relevant information from 

545 # its "info" field (e.g. subkey expiration date) and assign to it to the 

546 # subkey here 

547 validity_period = signature["info"]["subpackets"].get(KEY_EXPIRATION_SUBPACKET) 

548 if validity_period is not None: 

549 subkey["validity_period"] = struct.unpack(">I", validity_period)[0] 

550 

551 verified_subkeys[subkey["keyid"]] = subkey 

552 

553 return verified_subkeys 

554 

555 

556def get_pubkey_bundle(data, keyid): 

557 """ 

558 <Purpose> 

559 Call function to extract and verify master key and subkeys from the passed 

560 gpg key data, where either the master key or one of the subkeys matches the 

561 passed keyid. 

562 

563 NOTE: 

564 - If the keyid matches one of the subkeys, a warning is issued to notify 

565 the user about potential privilege escalation 

566 - Subkeys with invalid key binding signatures are discarded 

567 

568 <Arguments> 

569 data: 

570 Public key data as written to stdout by 

571 securesystemslib._gpg.constants.gpg_export_pubkey_command. 

572 

573 keyid: 

574 The keyid of the master key or one of its subkeys expected to be 

575 contained in the passed gpg data. 

576 

577 <Exceptions> 

578 securesystemslib._gpg.exceptions.PacketParsingError 

579 If the key data could not be parsed 

580 

581 securesystemslib._gpg.exceptions.KeyNotFoundError 

582 If the passed data is empty. 

583 If no master key or subkeys could be found that matches the passed 

584 keyid. 

585 

586 

587 <Side Effects> 

588 None. 

589 

590 <Returns> 

591 A public key dict with optional subkeys. 

592 

593 """ 

594 if not data: 

595 raise KeyNotFoundError( 

596 f"Could not find gpg key '{keyid}' in empty exported key data." 

597 ) 

598 

599 # Parse out master key and subkeys (enriched and verified via certificates 

600 # and binding signatures) 

601 raw_key_bundle = parse_pubkey_bundle(data) 

602 master_public_key = _assign_certified_key_info(raw_key_bundle) 

603 sub_public_keys = _get_verified_subkeys(raw_key_bundle) 

604 

605 # Since GPG returns all pubkeys associated with a keyid (master key and 

606 # subkeys) we check which key matches the passed keyid. 

607 # If the matching key is a subkey, we warn the user because we return 

608 # the whole bundle (master plus all subkeys) and not only the subkey. 

609 # If no matching key is found we raise a KeyNotFoundError. 

610 for idx, public_key in enumerate( 

611 [master_public_key] + list(sub_public_keys.values()) 

612 ): 

613 if public_key and public_key["keyid"].endswith(keyid.lower()): 

614 if idx > 1: 

615 log.debug( 

616 "Exporting master key '{}' including subkeys '{}' for" 

617 " passed keyid '{}'.".format( 

618 master_public_key["keyid"], 

619 ", ".join(list(sub_public_keys.keys())), 

620 keyid, 

621 ) 

622 ) 

623 break 

624 

625 else: 

626 raise KeyNotFoundError( 

627 f"Could not find gpg key '{keyid}' in exported key data." 

628 ) 

629 

630 # Add subkeys dictionary to master pubkey "subkeys" field if subkeys exist 

631 if sub_public_keys: 

632 master_public_key["subkeys"] = sub_public_keys 

633 

634 return master_public_key 

635 

636 

637# ruff: noqa: PLR0912, PLR0915 

638def parse_signature_packet( 

639 data, 

640 supported_signature_types=None, 

641 supported_hash_algorithms=None, 

642 include_info=False, 

643): 

644 """ 

645 <Purpose> 

646 Parse the signature information on an RFC4880-encoded binary signature data 

647 buffer. 

648 

649 NOTE: Older gpg versions (< FULLY_SUPPORTED_MIN_VERSION) might only 

650 reveal the partial key id. It is the callers responsibility to determine 

651 the full keyid based on the partial keyid, e.g. by exporting the related 

652 public and replacing the partial keyid with the full keyid. 

653 

654 <Arguments> 

655 data: 

656 the RFC4880-encoded binary signature data buffer as described in 

657 section 5.2 (and 5.2.3.1). 

658 supported_signature_types: (optional) 

659 a set of supported signature_types, the signature packet may be 

660 (see securesystemslib._gpg.constants for available types). If None is 

661 specified the signature packet must be of type SIGNATURE_TYPE_BINARY. 

662 supported_hash_algorithms: (optional) 

663 a set of supported hash algorithm ids, the signature packet 

664 may use. Available ids are SHA1, SHA256, SHA512 (see 

665 securesystemslib._gpg.constants). If None is specified, the signature 

666 packet must use SHA256. 

667 include_info: (optional) 

668 a boolean that indicates whether an opaque dictionary should be 

669 added to the returned signature under the key "info". Default is 

670 False. 

671 

672 <Exceptions> 

673 ValueError: if the signature packet is not supported or the data is 

674 malformed 

675 IndexError: if the signature packet is incomplete 

676 

677 <Side Effects> 

678 None. 

679 

680 <Returns> 

681 A signature dict with the following special characteristics: 

682 - The "keyid" field is an empty string if it cannot be determined 

683 - The "short_keyid" is not added if it cannot be determined 

684 - At least one of non-empty "keyid" or "short_keyid" are part of the 

685 signature 

686 

687 """ 

688 if not supported_signature_types: 

689 supported_signature_types = {SIGNATURE_TYPE_BINARY} 

690 

691 if not supported_hash_algorithms: 

692 supported_hash_algorithms = {SHA256} 

693 

694 _, header_len, _, packet_len = gpg_util.parse_packet_header( 

695 data, PACKET_TYPE_SIGNATURE 

696 ) 

697 

698 data = bytearray(data[header_len:packet_len]) 

699 

700 ptr = 0 

701 

702 # we get the version number, which we also expect to be v4, or we bail 

703 # FIXME: support v3 type signatures (which I haven't seen in the wild) 

704 version_number = data[ptr] 

705 ptr += 1 

706 if version_number not in SUPPORTED_SIGNATURE_PACKET_VERSIONS: 

707 raise ValueError( 

708 f"Signature version '{version_number}' not supported, " 

709 f"must be one of {SUPPORTED_SIGNATURE_PACKET_VERSIONS}." 

710 ) 

711 

712 # Per default we only parse "signatures of a binary document". Other types 

713 # may be allowed by passing type constants via `supported_signature_types`. 

714 # Types include revocation signatures, key binding signatures, persona 

715 # certifications, etc. (see RFC 4880 section 5.2.1.). 

716 signature_type = data[ptr] 

717 ptr += 1 

718 

719 if signature_type not in supported_signature_types: 

720 raise ValueError( 

721 f"Signature type '{signature_type}' not supported, " 

722 f"must be one of {supported_signature_types} " 

723 "(see RFC4880 5.2.1. Signature Types)." 

724 ) 

725 

726 signature_algorithm = data[ptr] 

727 ptr += 1 

728 

729 if signature_algorithm not in SUPPORTED_SIGNATURE_ALGORITHMS: 

730 raise ValueError( 

731 f"Signature algorithm '{signature_algorithm}' not " 

732 "supported, please verify that your gpg configuration is creating " 

733 "either DSA, RSA, or EdDSA signatures (see RFC4880 9.1. Public-Key " 

734 "Algorithms)." 

735 ) 

736 

737 key_type = SUPPORTED_SIGNATURE_ALGORITHMS[signature_algorithm]["type"] 

738 handler = SIGNATURE_HANDLERS[key_type] 

739 

740 hash_algorithm = data[ptr] 

741 ptr += 1 

742 

743 if hash_algorithm not in supported_hash_algorithms: 

744 raise ValueError( 

745 f"Hash algorithm '{hash_algorithm}' not supported, " 

746 f"must be one of {supported_hash_algorithms} " 

747 "(see RFC4880 9.4. Hash Algorithms)." 

748 ) 

749 

750 # Obtain the hashed octets 

751 hashed_octet_count = struct.unpack(">H", data[ptr : ptr + 2])[0] 

752 ptr += 2 

753 hashed_subpackets = data[ptr : ptr + hashed_octet_count] 

754 hashed_subpacket_info = gpg_util.parse_subpackets(hashed_subpackets) 

755 

756 if len(hashed_subpackets) != hashed_octet_count: # pragma: no cover 

757 raise ValueError( 

758 "Signature packet contains an unexpected amount of hashed octets" 

759 ) 

760 

761 ptr += hashed_octet_count 

762 other_headers_ptr = ptr 

763 

764 unhashed_octet_count = struct.unpack(">H", data[ptr : ptr + 2])[0] 

765 ptr += 2 

766 

767 unhashed_subpackets = data[ptr : ptr + unhashed_octet_count] 

768 unhashed_subpacket_info = gpg_util.parse_subpackets(unhashed_subpackets) 

769 

770 ptr += unhashed_octet_count 

771 

772 # Use the info dict to return further signature information that may be 

773 # needed for intermediate processing, but does not have to be on the eventual 

774 # signature datastructure 

775 info = { 

776 "signature_type": signature_type, 

777 "hash_algorithm": hash_algorithm, 

778 "creation_time": None, 

779 "subpackets": {}, 

780 } 

781 

782 keyid = "" 

783 short_keyid = "" 

784 

785 # Parse "Issuer" (short keyid) and "Issuer Fingerprint" (full keyid) type 

786 # subpackets 

787 # Strategy: Loop over all unhashed and hashed subpackets (in that order!) and 

788 # store only the last of a type. Due to the order in the loop, hashed 

789 # subpackets are prioritized over unhashed subpackets (see NOTEs below). 

790 

791 # NOTE: A subpacket may be found either in the hashed or unhashed subpacket 

792 # sections of a signature. If a subpacket is not hashed, then the information 

793 # in it cannot be considered definitive because it is not part of the 

794 # signature proper. (see RFC4880 5.2.3.2.) 

795 # NOTE: Signatures may contain conflicting information in subpackets. In most 

796 # cases, an implementation SHOULD use the last subpacket, but MAY use any 

797 # conflict resolution scheme that makes more sense. (see RFC4880 5.2.4.1.) 

798 for idx, subpacket_tuple in enumerate( 

799 unhashed_subpacket_info + hashed_subpacket_info 

800 ): 

801 # The idx indicates if the info is from the unhashed (first) or 

802 # hashed (second) of the above concatenated lists 

803 is_hashed = idx >= len(unhashed_subpacket_info) 

804 subpacket_type, subpacket_data = subpacket_tuple 

805 

806 # Warn if expiration subpacket is not hashed 

807 if subpacket_type == KEY_EXPIRATION_SUBPACKET: 

808 if not is_hashed: 

809 log.warning( 

810 "Expiration subpacket not hashed, gpg client possibly " 

811 "exporting a weakly configured key." 

812 ) 

813 

814 # Full keyids are only available in newer signatures 

815 # (see RFC4880 and rfc4880bis-06 5.2.3.1.) 

816 if subpacket_type == FULL_KEYID_SUBPACKET: # pragma: no cover 

817 # Exclude from coverage for consistent results across test envs 

818 # NOTE: The first byte of the subpacket payload is a version number 

819 # (see rfc4880bis-06 5.2.3.28.) 

820 keyid = binascii.hexlify(subpacket_data[1:]).decode("ascii") 

821 

822 # We also return the short keyid, because the full might not be available 

823 if subpacket_type == PARTIAL_KEYID_SUBPACKET: 

824 short_keyid = binascii.hexlify(subpacket_data).decode("ascii") 

825 

826 if subpacket_type == SIG_CREATION_SUBPACKET: 

827 info["creation_time"] = struct.unpack(">I", subpacket_data)[0] 

828 

829 info["subpackets"][subpacket_type] = subpacket_data 

830 

831 # Fail if there is no keyid at all (this should not happen) 

832 if not (keyid or short_keyid): # pragma: no cover 

833 raise ValueError( 

834 "This signature packet seems to be corrupted. It does " 

835 "not have an 'Issuer' or 'Issuer Fingerprint' subpacket (see RFC4880 " 

836 "and rfc4880bis-06 5.2.3.1. Signature Subpacket Specification)." 

837 ) 

838 

839 # Fail if keyid and short keyid are specified but don't match 

840 if keyid and not keyid.endswith(short_keyid): # pragma: no cover 

841 raise ValueError( 

842 "This signature packet seems to be corrupted. The key ID " 

843 f"'{short_keyid}' of the 'Issuer' subpacket must match the " 

844 f"lower 64 bits of the fingerprint '{keyid}' of the 'Issuer " 

845 "Fingerprint' subpacket (see RFC4880 and rfc4880bis-06 5.2.3.28. " 

846 "Issuer Fingerprint)." 

847 ) 

848 

849 if not info["creation_time"]: # pragma: no cover 

850 raise ValueError( 

851 "This signature packet seems to be corrupted. It does " 

852 "not have a 'Signature Creation Time' subpacket (see RFC4880 5.2.3.4 " 

853 "Signature Creation Time)." 

854 ) 

855 

856 # Uncomment this variable to obtain the left-hash-bits information (used for 

857 # early rejection) 

858 # left_hash_bits = struct.unpack(">H", data[ptr:ptr+2])[0] 

859 ptr += 2 

860 

861 # Finally, fetch the actual signature (as opposed to signature metadata). 

862 signature = handler.get_signature_params(data[ptr:]) 

863 

864 signature_data = { 

865 "keyid": keyid, 

866 "other_headers": binascii.hexlify(data[:other_headers_ptr]).decode("ascii"), 

867 "signature": binascii.hexlify(signature).decode("ascii"), 

868 } 

869 

870 if short_keyid: # pragma: no branch 

871 signature_data["short_keyid"] = short_keyid 

872 

873 if include_info: 

874 signature_data["info"] = info 

875 

876 return signature_data