keyringctl: Dedicated functions for writing to file
keyringctl: Add `persist_basic_key()`, `persist_direct_keys()`, `persist_certifications()` and `persist_revocations()` to allow for dedicated writing of basic key material, direct key signatures, per UID certificates and per UID revocations (respectively). Change `convert_certificate()` to call the new dedicated write functions instead of implementing the functionality. Change `convert_certificate()` to raise on missing current_packet_key when trying to work on signature files (this is unlikely to occur, unless the input data is somehow broken, but it keeps the linter happy). Change `convert_certificate()` to handle direct_keys by issuer on a given root key (DirectKey signatures by the same issuer are combined). Change the argparse subparser for the 'convert' command to include a help text.
This commit is contained in:
parent
f626e40b84
commit
0d32d2f00a
178
keyringctl
178
keyringctl
@ -80,7 +80,7 @@ def system(cmd: List[str], exit_on_error: bool = True) -> str:
|
|||||||
def convert_certificate(working_dir: Path, certificate: Path, owner: str) -> Path:
|
def convert_certificate(working_dir: Path, certificate: Path, owner: str) -> Path:
|
||||||
certificate_fingerprint: Optional[str] = None
|
certificate_fingerprint: Optional[str] = None
|
||||||
pubkey: Optional[Path] = None
|
pubkey: Optional[Path] = None
|
||||||
direct_keys: List[Path] = []
|
direct_keys: Dict[str, Dict[str, List[Path]]] = {}
|
||||||
current_packet_mode: Optional[str] = None
|
current_packet_mode: Optional[str] = None
|
||||||
current_packet_key: Optional[str] = None
|
current_packet_key: Optional[str] = None
|
||||||
uids: Dict[str, Path] = {}
|
uids: Dict[str, Path] = {}
|
||||||
@ -116,15 +116,17 @@ def convert_certificate(working_dir: Path, certificate: Path, owner: str) -> Pat
|
|||||||
elif packet.name.endswith('--Signature'):
|
elif packet.name.endswith('--Signature'):
|
||||||
if not certificate_fingerprint:
|
if not certificate_fingerprint:
|
||||||
raise Exception('missing certificate fingerprint for "{packet.name}"')
|
raise Exception('missing certificate fingerprint for "{packet.name}"')
|
||||||
|
if not current_packet_key:
|
||||||
|
raise Exception('missing current packet key for "{packet.name}"')
|
||||||
|
|
||||||
issuer = packet_dump_field(packet, 'Issuer')
|
issuer = packet_dump_field(packet, 'Issuer')
|
||||||
signature_type = packet_dump_field(packet, 'Type')
|
signature_type = packet_dump_field(packet, 'Type')
|
||||||
|
|
||||||
# TODO: handle Revocation key via self Issuer
|
|
||||||
if signature_type == 'DirectKey':
|
if signature_type == 'DirectKey':
|
||||||
direct_keys.append(packet)
|
if not direct_keys.get(current_packet_key):
|
||||||
# TODO
|
direct_keys = direct_keys | {current_packet_key: defaultdict(list)}
|
||||||
breakpoint()
|
|
||||||
|
direct_keys[current_packet_key][issuer].append(packet)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if not current_packet_key:
|
if not current_packet_key:
|
||||||
@ -169,16 +171,136 @@ def convert_certificate(working_dir: Path, certificate: Path, owner: str) -> Pat
|
|||||||
root_dir = (working_dir / certificate_fingerprint)
|
root_dir = (working_dir / certificate_fingerprint)
|
||||||
root_dir.mkdir()
|
root_dir.mkdir()
|
||||||
|
|
||||||
# TODO: DirectKeys
|
persist_basic_key(
|
||||||
|
certificate_fingerprint=certificate_fingerprint,
|
||||||
|
pubkey=pubkey,
|
||||||
|
root_dir=root_dir,
|
||||||
|
subkey=subkey,
|
||||||
|
subkey_binding_sig=subkey_binding_sig,
|
||||||
|
uid_binding_sig=uid_binding_sig,
|
||||||
|
uids=uids,
|
||||||
|
)
|
||||||
|
|
||||||
|
persist_direct_keys(
|
||||||
|
direct_keys=direct_keys,
|
||||||
|
pubkey=pubkey,
|
||||||
|
root_dir=root_dir,
|
||||||
|
)
|
||||||
|
|
||||||
|
persist_certifications(
|
||||||
|
certifications=certifications,
|
||||||
|
pubkey=pubkey,
|
||||||
|
root_dir=root_dir,
|
||||||
|
uid_binding_sig=uid_binding_sig,
|
||||||
|
uids=uids,
|
||||||
|
)
|
||||||
|
|
||||||
|
persist_revocations(
|
||||||
|
pubkey=pubkey,
|
||||||
|
revocations=revocations,
|
||||||
|
root_dir=root_dir,
|
||||||
|
uid_binding_sig=uid_binding_sig,
|
||||||
|
uids=uids,
|
||||||
|
)
|
||||||
|
|
||||||
|
return root_dir
|
||||||
|
|
||||||
|
|
||||||
|
def persist_basic_key(
|
||||||
|
certificate_fingerprint: str,
|
||||||
|
pubkey: Path,
|
||||||
|
root_dir: Path,
|
||||||
|
subkey: Dict[str, Path],
|
||||||
|
subkey_binding_sig: Dict[str, Path],
|
||||||
|
uid_binding_sig: Dict[str, Path],
|
||||||
|
uids: Dict[str, Path],
|
||||||
|
) -> None:
|
||||||
|
"""Persist the basic key material of a root key to file
|
||||||
|
|
||||||
|
The basic key material consists of the root key's public key, any PublicSubkeys and their SubkeyBindings, all User
|
||||||
|
IDs and the per User ID PositiveCertifications.
|
||||||
|
The file is written to root_dir and is named after the root key's certificate fingerprint.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
certificate_fingerprint: str
|
||||||
|
The unique fingerprint of the public key
|
||||||
|
pubkey: Path
|
||||||
|
The path to the public key of the root key
|
||||||
|
root_dir: Path
|
||||||
|
The root directory below which the basic key material is persisted
|
||||||
|
subkey: Dict[str, Path]
|
||||||
|
The PublicSubkeys of a key
|
||||||
|
subkey_binding_sig: Dict[str, Path]
|
||||||
|
The SubkeyBinding signatures of a Public-Key (the root key)
|
||||||
|
uid_binding_sig: Dict[str, Path]
|
||||||
|
The PositiveCertifications of a User ID and Public-Key packet
|
||||||
|
uids: Dict[str, Path]
|
||||||
|
The User IDs of a Public-Key (the root key)
|
||||||
|
"""
|
||||||
|
|
||||||
packets: List[Path] = [pubkey]
|
packets: List[Path] = [pubkey]
|
||||||
packets.extend(direct_keys)
|
|
||||||
for key in uid_binding_sig.keys():
|
for key in uid_binding_sig.keys():
|
||||||
packets.extend([uids[key], uid_binding_sig[key]])
|
packets.extend([uids[key], uid_binding_sig[key]])
|
||||||
for key in subkey_binding_sig.keys():
|
for key in subkey_binding_sig.keys():
|
||||||
packets.extend([subkey[key], subkey_binding_sig[key]])
|
packets.extend([subkey[key], subkey_binding_sig[key]])
|
||||||
|
|
||||||
minimal_certificate = root_dir / f'{certificate_fingerprint}.asc'
|
packet_join(packets, root_dir / f'{certificate_fingerprint}.asc')
|
||||||
packet_join(packets, minimal_certificate)
|
|
||||||
|
|
||||||
|
def persist_direct_keys(
|
||||||
|
direct_keys: Dict[str, Dict[str, List[Path]]],
|
||||||
|
pubkey: Path,
|
||||||
|
root_dir: Path,
|
||||||
|
) -> None:
|
||||||
|
"""Persist the DirectKeys on a root key to file(s)
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
certifications: Dict[str, List[Path]]
|
||||||
|
The certifications to write to file
|
||||||
|
pubkey: Path
|
||||||
|
The path to the public key of the root key
|
||||||
|
root_dir: Path
|
||||||
|
The root directory below which the Directkeys are persisted
|
||||||
|
"""
|
||||||
|
|
||||||
|
for key, current_certifications in direct_keys.items():
|
||||||
|
for issuer, certifications in current_certifications.items():
|
||||||
|
direct_key_dir = root_dir / 'certification'
|
||||||
|
direct_key_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
packets = [pubkey] + certifications
|
||||||
|
output_file = direct_key_dir / f'{issuer}.asc'
|
||||||
|
debug(f'Writing file {output_file} from {[str(cert) for cert in certifications]}')
|
||||||
|
packet_join(packets, output_file)
|
||||||
|
|
||||||
|
|
||||||
|
def persist_certifications(
|
||||||
|
certifications: Dict[str, List[Path]],
|
||||||
|
pubkey: Path,
|
||||||
|
root_dir: Path,
|
||||||
|
uid_binding_sig: Dict[str, Path],
|
||||||
|
uids: Dict[str, Path],
|
||||||
|
) -> None:
|
||||||
|
"""Persist the certifications of a root key to file(s)
|
||||||
|
|
||||||
|
The certifications include all CasualCertifications, GenericCertifications, PersonaCertifications and
|
||||||
|
PositiveCertifications for all User IDs of the given root key.
|
||||||
|
All certifications are persisted in per User ID certification directories below root_dir.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
certifications: Dict[str, List[Path]]
|
||||||
|
The certifications to write to file
|
||||||
|
pubkey: Path
|
||||||
|
The path to the public key of the root key
|
||||||
|
root_dir: Path
|
||||||
|
The root directory below which certifications are persisted
|
||||||
|
uid_binding_sig: Dict[str, Path]
|
||||||
|
The PositiveCertifications of a User ID and Public-Key packet
|
||||||
|
uids: Dict[str, Path]
|
||||||
|
The User IDs of a Public-Key (the root key)
|
||||||
|
"""
|
||||||
|
|
||||||
for key, current_certifications in certifications.items():
|
for key, current_certifications in certifications.items():
|
||||||
for certification in current_certifications:
|
for certification in current_certifications:
|
||||||
@ -199,6 +321,33 @@ def convert_certificate(working_dir: Path, certificate: Path, owner: str) -> Pat
|
|||||||
debug(f'Writing file {output_file} from {certification}')
|
debug(f'Writing file {output_file} from {certification}')
|
||||||
packet_join(packets, output_file)
|
packet_join(packets, output_file)
|
||||||
|
|
||||||
|
|
||||||
|
def persist_revocations(
|
||||||
|
pubkey: Path,
|
||||||
|
revocations: Dict[str, List[Path]],
|
||||||
|
root_dir: Path,
|
||||||
|
uid_binding_sig: Dict[str, Path],
|
||||||
|
uids: Dict[str, Path],
|
||||||
|
) -> None:
|
||||||
|
"""Persist the revocations of a root key to file(s)
|
||||||
|
|
||||||
|
The revocations include all CertificationRevocations for all User IDs of the given root key.
|
||||||
|
All revocations are persisted in per User ID 'revocation' directories below root_dir.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
pubkey: Path
|
||||||
|
The path to the public key of the root key
|
||||||
|
revocations: Dict[str, List[Path]]
|
||||||
|
The revocations to write to file
|
||||||
|
root_dir: Path
|
||||||
|
The root directory below which revocations will be persisted
|
||||||
|
uid_binding_sig: Dict[str, Path]
|
||||||
|
The PositiveCertifications of a User ID and Public-Key packet
|
||||||
|
uids: Dict[str, Path]
|
||||||
|
The User IDs of a Public-Key (the root key)
|
||||||
|
"""
|
||||||
|
|
||||||
for key, current_revocations in revocations.items():
|
for key, current_revocations in revocations.items():
|
||||||
for revocation in current_revocations:
|
for revocation in current_revocations:
|
||||||
revocation_dir = root_dir / key / 'revocation'
|
revocation_dir = root_dir / key / 'revocation'
|
||||||
@ -214,8 +363,6 @@ def convert_certificate(working_dir: Path, certificate: Path, owner: str) -> Pat
|
|||||||
debug(f'Writing file {output_file} from {revocation}')
|
debug(f'Writing file {output_file} from {revocation}')
|
||||||
packet_join(packets, output_file)
|
packet_join(packets, output_file)
|
||||||
|
|
||||||
return root_dir
|
|
||||||
|
|
||||||
|
|
||||||
def packet_dump(packet: Path) -> str:
|
def packet_dump(packet: Path) -> str:
|
||||||
return system(['sq', 'packet', 'dump', str(packet)])
|
return system(['sq', 'packet', 'dump', str(packet)])
|
||||||
@ -288,9 +435,12 @@ if __name__ == '__main__':
|
|||||||
parser.add_argument('--wait', action='store_true', help='Block before cleaning up the temp directory')
|
parser.add_argument('--wait', action='store_true', help='Block before cleaning up the temp directory')
|
||||||
subcommands = parser.add_subparsers(dest="subcommand")
|
subcommands = parser.add_subparsers(dest="subcommand")
|
||||||
|
|
||||||
convert_parser = subcommands.add_parser('convert')
|
convert_parser = subcommands.add_parser(
|
||||||
convert_parser.add_argument('source', type=absolute_path, help='File or directory')
|
'convert',
|
||||||
convert_parser.add_argument('--target', type=absolute_path, help='Target directory')
|
help="convert a legacy-style PGP cert or directory containing PGP certs to the new format",
|
||||||
|
)
|
||||||
|
convert_parser.add_argument('source', type=absolute_path, help='File or directory to convert')
|
||||||
|
convert_parser.add_argument('--target', type=absolute_path, help='target directory')
|
||||||
|
|
||||||
import_parser = subcommands.add_parser('import')
|
import_parser = subcommands.add_parser('import')
|
||||||
import_parser.add_argument('source', type=absolute_path, help='File or directory')
|
import_parser.add_argument('source', type=absolute_path, help='File or directory')
|
||||||
|
Loading…
Reference in New Issue
Block a user