From 86747ecab72df8d2124bf95da33ec321a96377f2 Mon Sep 17 00:00:00 2001 From: Levente Polyak Date: Thu, 21 Oct 2021 20:34:48 +0200 Subject: [PATCH] feature(keyringctl): use the export command purely to export keyrings This gives more control over the export command that may be useful to export a single packager to import it into gpg. This will also give more flexibility to chain this function to the future verify stage. By default the command exports the whole keyring directory. --- README.md | 21 +++++++++-- keyringctl | 103 ++++++++++++++++++++++++++++++++++++++--------------- 2 files changed, 94 insertions(+), 30 deletions(-) diff --git a/README.md b/README.md index ea7c0ac..08932eb 100644 --- a/README.md +++ b/README.md @@ -29,12 +29,12 @@ Import a new packager key by deriving the username from the filename. Alternatively import a file or directory and override the username ```bash -./keyringctl import --name +./keyringctl import --name ``` Updates to existing keys will automatically derive the username from the known fingerprint. ```bash -./keyringctl import +./keyringctl import ``` Main key imports support the same options plus a mandatory `--main` @@ -42,6 +42,23 @@ Main key imports support the same options plus a mandatory `--main` ./keyringctl import --main .asc ``` +### Export + +Export the whole keyring including main and packager to stdout +```bash +./keyringctl export +``` + +Limit to specific usernames using an output file +```bash +./keyringctl export --output +``` + +Only export specific certificate directories in [keyring](keyring) +```bash +./keyringctl export +``` + ## Installation To install archlinux-keyring system-wide use the included `Makefile`: diff --git a/keyringctl b/keyringctl index f24c380..4e25b3a 100755 --- a/keyringctl +++ b/keyringctl @@ -1067,6 +1067,69 @@ def get_fingerprints(working_dir: Path, decomposed_paths: List[Path]) -> Set[Fin return fingerprints +def export( + working_dir: Path, + keyring_root: Path, + sources: Optional[List[Path]] = None, + output: Optional[Path] = None, +) -> str: + """Export all provided PGP packet files to a single output file + + If sources contains directories, any .asc files below them are considered. + + Parameters + ---------- + working_dir: Path + A directory to use for temporary files + keyring_root: Path + The keyring root directory to look up username shorthand sources + sources: Optional[List[Path]] + A list of directories or files from which to read PGP packet information (defaults to `keyring_root`) + output: Optional[Path] + An output file that all PGP packet data is written to, return the result instead if None + + Returns + ------- + str + The result if no output file has been used + """ + + if not sources: + sources = [keyring_root] + + # resolve shorthand username exports for packager keys + for index, source in enumerate(sources): + packager_source = keyring_root / "packager" / source.name + if not source.exists() and packager_source.exists(): + sources[index] = packager_source + + # depth first search certificate paths + cert_dirs: Set[Path] = set() + visit: List[Path] = sources + while visit: + path = visit.pop() + # this level contains a certificate, abort depth search + if list(path.glob("*.asc")): + cert_dirs.add(path) + continue + visit.extend([path for path in path.iterdir() if path.is_dir()]) + + temp_dir = Path(mkdtemp(dir=working_dir, prefix="arch-keyringctl-export-join-")).absolute() + + certificates = [] + for cert_dir in sorted(cert_dirs): + cert_path = temp_dir / f"{cert_dir.name}.asc" + debug(f"Joining {cert_dir} in {cert_path}") + packet_join( + packets=sorted(cert_dir.glob("**/*.asc")), + output=cert_path, + force=True, + ) + certificates.append(cert_path) + + return keyring_merge(certificates, output) + + def export_keyring( working_dir: Path, main: List[Path], @@ -1194,30 +1257,13 @@ if __name__ == "__main__": "export", help="export a directory structure of PGP packet data to a combined file", ) - export_parser.add_argument("output", type=absolute_path, help="file to write PGP packet data to") + export_parser.add_argument("-o", "--output", type=absolute_path, help="file to write PGP packet data to") export_parser.add_argument( - "-m", - "--main", - action="append", - help="files or directories containing PGP packet data that is trusted (can be provided multiple times)", - required=True, + "source", + nargs="*", + help="username or files/directories containing PGP packet data (can be provided multiple times)", type=absolute_path, ) - export_parser.add_argument( - "-s", - "--source", - action="append", - help="files or directories containing PGP packet data (can be provided multiple times)", - required=True, - type=absolute_path, - ) - export_parser.add_argument( - "-p", - "--pacman-integration", - action="store_true", - default=False, - help="export trusted and revoked files (used by pacman) alongside the keyring", - ) args = parser.parse_args() @@ -1257,13 +1303,14 @@ if __name__ == "__main__": pacman_integration=True, ) elif "export" == args.subcommand: - export_keyring( - working_dir=working_dir, - main=args.main, - sources=args.source, - output=args.output, - force=args.force, - pacman_integration=args.pacman_integration, + print( + export( + working_dir=working_dir, + keyring_root=keyring_root, + sources=args.source, + output=args.output, + ), + end="", ) else: parser.print_help()