aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorPatrik Teivonen <patrik.teivonen@qt.io>2023-03-28 10:14:10 +0300
committerPatrik Teivonen <patrik.teivonen@qt.io>2023-04-03 12:31:01 +0000
commit0324fdb0865c13fc6065759bd723c4770b42da0c (patch)
tree00677ecd4d5f9ee54fde27c08bd30d3e19bb831d
parent2b69e6f4365cd8f32d5bd506ff3dc36344167ece (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-xpackaging-tools/release_repo_updater.py15
-rwxr-xr-xpackaging-tools/sign_installer.py58
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)