diff options
author | Patrik Teivonen <patrik.teivonen@qt.io> | 2023-03-28 10:14:10 +0300 |
---|---|---|
committer | Patrik Teivonen <patrik.teivonen@qt.io> | 2023-04-03 12:31:01 +0000 |
commit | 0324fdb0865c13fc6065759bd723c4770b42da0c (patch) | |
tree | 00677ecd4d5f9ee54fde27c08bd30d3e19bb831d | |
parent | 2b69e6f4365cd8f32d5bd506ff3dc36344167ece (diff) |
Sign macOS installer disk images before notarization
release_repo_updater.py (offline installers):
Previously, only the app bundle was signed prior notarization on macOS.
Codesign also the outer container (in this case, the .dmg disk image)
sign_installer.py (applies to online installers):
Accept any file types for signing (not just .app bundles).
Add CLI option to skip .dmg generation (--skip-dmg).
If .dmg creation is enabled, also sign the output .dmg container.
Task-number: QTBUG-112476
Change-Id: I26ef613d5eeb02da9115d6092631e107aec5784a
Reviewed-by: Antti Kokko <antti.kokko@qt.io>
-rwxr-xr-x | packaging-tools/release_repo_updater.py | 15 | ||||
-rwxr-xr-x | packaging-tools/sign_installer.py | 58 |
2 files changed, 43 insertions, 30 deletions
diff --git a/packaging-tools/release_repo_updater.py b/packaging-tools/release_repo_updater.py index f8bc47b06..7cd02c023 100755 --- a/packaging-tools/release_repo_updater.py +++ b/packaging-tools/release_repo_updater.py @@ -68,7 +68,7 @@ from release_task_reader import ( parse_config, ) from runner import run_cmd, run_cmd_async -from sign_installer import create_mac_dmg, sign_mac_app +from sign_installer import create_mac_dmg, sign_mac_content from sign_windows_installer import sign_executable if sys.version_info < (3, 7): @@ -1004,12 +1004,15 @@ async def sign_offline_installer(installer_path: str, installer_name: str) -> No log.info("Sign Windows installer") sign_executable(os.path.join(installer_path, installer_name) + '.exe') elif platform.system() == "Darwin": - log.info("Sign macOS .app bundle") - sign_mac_app(os.path.join(installer_path, installer_name + '.app'), get_pkg_value("SIGNING_IDENTITY")) - log.info("Create macOS dmg file") - create_mac_dmg(os.path.join(installer_path, installer_name) + '.app') + log.info("Codesign macOS app bundle (.app)") + app_bundle_path = Path(installer_path) / (installer_name + ".app") + sign_mac_content([app_bundle_path]) + log.info("Create macOS disk image file (.dmg)") + dmg_path = create_mac_dmg(app_bundle_path) + log.info("Codesign macOS disk image (.dmg)") + sign_mac_content([dmg_path]) log.info("Notarize macOS installer") - notarize(path=Path(installer_path, installer_name + '.dmg')) + notarize(path=dmg_path) else: log.info("No signing available for this host platform: %s", platform.system()) diff --git a/packaging-tools/sign_installer.py b/packaging-tools/sign_installer.py index 62fb77adb..902604d58 100755 --- a/packaging-tools/sign_installer.py +++ b/packaging-tools/sign_installer.py @@ -36,7 +36,7 @@ from contextlib import suppress from pathlib import Path from shutil import copy2, rmtree from subprocess import CalledProcessError -from typing import List, Tuple +from typing import List, Optional, Tuple from macholib import MachO # type: ignore from temppathlib import TemporaryDirectory @@ -175,7 +175,7 @@ def recursive_sign_notarize(pkg_dir: Path) -> None: embed_notarization(path) -def sign_mac_content(paths: List[Path]) -> None: +def sign_mac_content(paths: List[Path], identity: Optional[str] = None) -> None: """ Run codesign for the given paths @@ -187,13 +187,13 @@ def sign_mac_content(paths: List[Path]) -> None: """ run_cmd(cmd=["/Users/qt/unlock-keychain.sh"]) # unlock the keychain first count = len(paths) - log.info("Codesigning %s payload items", count) + log.info("Codesigning %s items", count) for idx, path in enumerate(paths): log.info("[%s/%s] Codesign: %s", idx, count, str(path)) cmd_args = [ 'codesign', '--verbose=3', str(path), '-r', get_pkg_value("SIGNING_FLAGS"), # signing requirements - '-s', get_pkg_value("SIGNING_IDENTITY"), # developer id identity + '-s', identity or get_pkg_value("SIGNING_IDENTITY"), # developer id identity '-o', 'runtime', # enable hardened runtime, required for notarization "--timestamp", # contact apple servers for time validation "--force" # resign all the code with different signature @@ -204,26 +204,24 @@ def sign_mac_content(paths: List[Path]) -> None: raise Exception(f"Failed to codesign: {str(path)}") from err -def sign_mac_app(app_path: str, signing_identity: str) -> None: - assert app_path.endswith(".app"), f"Not a valid path to .app bundle: {app_path}" - # we need to unlock the keychain first - unlock_script = "/Users/qt/unlock-keychain.sh" - run_cmd(cmd=[unlock_script]) - # "-o runtime" is required for notarization - cmd_args = ['codesign', '-o', 'runtime', '--verbose=3', '-r', get_pkg_value("SIGNING_FLAGS")] - cmd_args += ['-s', signing_identity, app_path] - run_cmd(cmd=cmd_args) - log.info("Successfully signed: %s", app_path) +def create_mac_dmg(src_path: Path) -> Path: + """ + Create a macOS disk image (.dmg) from the content source specified. + The .dmg file will be placed in the parent directory of the specified source path. + Args: + src_path: A folder/file to include inside the .dmg file. -def create_mac_dmg(app_path: str) -> None: - assert app_path.endswith(".app"), f"Not a valid path to .app bundle: {app_path}" - installer_name_base = os.path.basename(app_path).split(".app")[0] - destination_dmg_path = app_path.split(".app")[0] + '.dmg' - cmd_args = ['hdiutil', 'create', '-srcfolder', app_path, '-volname', installer_name_base] - cmd_args += ['-format', 'UDBZ', destination_dmg_path, '-ov', '-scrub', '-size', '4g'] + Returns: + Path to the generated .dmg file + """ + installer_name_base = src_path.stem + destination_dmg_path = src_path.with_suffix(".dmg") # replace last suffix with '.dmg' + cmd_args = ['hdiutil', 'create', '-srcfolder', str(src_path), '-volname', installer_name_base] + cmd_args += ['-format', 'UDBZ', str(destination_dmg_path), '-ov', '-scrub', '-size', '4g'] run_cmd(cmd=cmd_args) - log.info("Successfully created: %s", destination_dmg_path) + log.info("Successfully created: %s", str(destination_dmg_path)) + return destination_dmg_path def sign_windows_executable(file_path: str) -> None: @@ -262,7 +260,17 @@ def main() -> None: app_parser = subparsers.add_parser("mac") exe_parser = subparsers.add_parser("win") - app_parser.add_argument("--file", dest="file_path", required=True, help="Full path to .app file") + app_parser.add_argument( + "--file", + dest="file_path", + required=True, + type=Path, + help="Path to a signable macOS bundle/directory/file containing code.", + ) + app_parser.add_argument( + "--skip-dmg", dest="create_dmg", action="store_false", + help="Skip packing the file to a .dmg disk image and signing it" + ) app_parser.add_argument("--signing-identity", default=get_pkg_value("SIGNING_IDENTITY")) exe_parser.add_argument("--file", dest="file_path", required=True, help="Full path to .exe file") @@ -272,8 +280,10 @@ def main() -> None: args = parser.parse_args(sys.argv[1:]) if args.command == 'mac': - sign_mac_app(args.file_path, args.signing_identity) - create_mac_dmg(args.file_path) + sign_mac_content([args.file_path], args.signing_identity) + if args.create_dmg is True: + dmg_path = create_mac_dmg(args.file_path) + sign_mac_content([dmg_path], args.signing_identity) if args.command == 'win': sign_windows_executable(args.file_path) |