#!/usr/bin/env python3 """Extract signing entitlements from an Apple provisioning profile.""" from __future__ import annotations import argparse import plistlib import subprocess import sys from pathlib import Path def decode_profile(path: Path) -> dict: commands = [ ["security", "cms", "-D", "-i", str(path)], ["openssl", "smime", "-inform", "DER", "-verify", "-noverify", "-in", str(path)], ["openssl", "cms", "-inform", "DER", "-verify", "-noverify", "-in", str(path)], ] errors: list[str] = [] for command in commands: try: proc = subprocess.run( command, check=False, stdout=subprocess.PIPE, stderr=subprocess.PIPE, ) except FileNotFoundError: continue if proc.returncode == 0 and proc.stdout: return plistlib.loads(proc.stdout) errors.append(f"{command[0]} exited {proc.returncode}: {proc.stderr.decode(errors='replace').strip()}") raise RuntimeError(f"unable to decode provisioning profile {path}: {'; '.join(errors)}") def main(argv: list[str]) -> int: parser = argparse.ArgumentParser(description=__doc__) parser.add_argument("profile", type=Path) parser.add_argument("--output", "-o", type=Path, required=True) args = parser.parse_args(argv) profile = decode_profile(args.profile) entitlements = profile.get("Entitlements") if not isinstance(entitlements, dict): raise RuntimeError(f"profile has no Entitlements dictionary: {args.profile}") args.output.parent.mkdir(parents=True, exist_ok=True) args.output.write_bytes(plistlib.dumps(entitlements, fmt=plistlib.FMT_XML)) return 0 if __name__ == "__main__": raise SystemExit(main(sys.argv[1:]))