Skip to content

Commit d97ad9e

Browse files
authored
Sign Mach-O binaries in _internal directory of conda-standalone (#1159)
* Add function to detect whether file is Mach-O * Sign all Mach-O binaries in _internal directory * Add tests * Add news * Use correct location of internal directory in test * Skip redundant is_dir check before glob
1 parent 2e5768d commit d97ad9e

4 files changed

Lines changed: 67 additions & 14 deletions

File tree

constructor/osxpkg.py

Lines changed: 41 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,25 @@
3838
logger = logging.getLogger(__name__)
3939

4040

41+
# Mach-O binaries start with magic bytes.
42+
# Definitions:
43+
# * https://github.com/apple-oss-distributions/xnu/blob/main/EXTERNAL_HEADERS/mach-o/loader.h
44+
# * https://github.com/apple-oss-distributions/xnu/blob/main/EXTERNAL_HEADERS/mach-o/fat.h
45+
# For now, only include little-endian 64-bit and fat binaries
46+
MACHO_MAGIC_BYTES = (
47+
b"\xcf\xfa\xed\xfe", # 64-bit
48+
b"\xbe\xba\xfe\xca", # universal binary
49+
)
50+
51+
52+
def is_macho_binary(file: Path) -> bool:
53+
if not file.is_file():
54+
return False
55+
with file.open(mode="rb") as f:
56+
magic = f.read(4)
57+
return magic in MACHO_MAGIC_BYTES
58+
59+
4160
def calculate_install_dir(yaml_file, subdir=None):
4261
contents = parse(yaml_file, subdir or conda_context.subdir)
4362
if contents.get("installer_type") == "sh":
@@ -88,6 +107,24 @@ def _detect_mimetype(path: str):
88107
return "text/plain"
89108

90109

110+
def sign_standalone_binary(
111+
exe_path: Path,
112+
codesigner: CodeSign,
113+
):
114+
entitlements = {
115+
"com.apple.security.cs.allow-jit": True,
116+
"com.apple.security.cs.allow-unsigned-executable-memory": True,
117+
"com.apple.security.cs.disable-executable-page-protection": True,
118+
"com.apple.security.cs.disable-library-validation": True,
119+
"com.apple.security.cs.allow-dyld-environment-variables": True,
120+
}
121+
codesigner.sign_bundle(exe_path, entitlements=entitlements)
122+
internal_dir = exe_path.parent / "_internal"
123+
for file in internal_dir.glob("**"):
124+
if is_macho_binary(file):
125+
codesigner.sign_bundle(file, entitlements=entitlements)
126+
127+
91128
def modify_xml(xml_path, info):
92129
# See
93130
# http://developer.apple.com/library/mac/#documentation/DeveloperTools/Reference/DistributionDefinitionRef/Chapters/Distribution_XML_Ref.html#//apple_ref/doc/uid/TP40005370-CH100-SW20
@@ -449,16 +486,16 @@ def pkgbuild_prepare_installation(info):
449486
shutil.rmtree(f"{pkg}.expanded")
450487

451488

452-
def create_plugins(pages: list = None, codesigner: CodeSign = None):
453-
def _build_xcode_projects(xcodeporj_dirs: list[Path]):
489+
def create_plugins(pages: list[str] | str | None = None, codesigner: CodeSign | None = None):
490+
def _build_xcode_projects(xcodeproj_dirs: list[Path]):
454491
xcodebuild = shutil.which("xcodebuild")
455492
if not xcodebuild:
456493
raise RuntimeError(
457494
"Plugin directory contains an uncompiled project, but xcodebuild is not available."
458495
)
459496
try:
460497
subprocess.run([xcodebuild, "--help"], check=True, capture_output=True)
461-
except subprocess.CalledSubprocessError:
498+
except subprocess.CalledProcessError:
462499
raise RuntimeError(
463500
"Plugin directory contains an uncompiled project, "
464501
"but xcodebuild requires XCode to compile plugins."
@@ -608,14 +645,7 @@ def create(info, verbose=False):
608645
codesigner = CodeSign(
609646
notarization_identity_name, prefix=info.get("reverse_domain_identifier", info["name"])
610647
)
611-
entitlements = {
612-
"com.apple.security.cs.allow-jit": True,
613-
"com.apple.security.cs.allow-unsigned-executable-memory": True,
614-
"com.apple.security.cs.disable-executable-page-protection": True,
615-
"com.apple.security.cs.disable-library-validation": True,
616-
"com.apple.security.cs.allow-dyld-environment-variables": True,
617-
}
618-
codesigner.sign_bundle(join(prefix, exe_name), entitlements=entitlements)
648+
sign_standalone_binary(Path(prefix, exe_name), codesigner)
619649

620650
# This script checks to see if the install location already exists and/or contains spaces
621651
# Not to be confused with the user-provided pre_install!

constructor/signing.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -228,7 +228,7 @@ def __init__(
228228
def get_signing_command(
229229
self,
230230
bundle: str | Path,
231-
entitlements: str | Path = None,
231+
entitlements: str | Path | None = None,
232232
) -> list:
233233
command = [
234234
self.executable,
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
### Enhancements
2+
3+
* <news item>
4+
5+
### Bug fixes
6+
7+
* Sign all Mach-O binaries in the `_internal` directory of `conda-standalone` to pass notarization. (#1045 via #1159)
8+
9+
### Deprecations
10+
11+
* <news item>
12+
13+
### Docs
14+
15+
* <news item>
16+
17+
### Other
18+
19+
* <news item>

tests/test_examples.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@
3333
from collections.abc import Generator, Iterable
3434

3535
if sys.platform == "darwin":
36-
from constructor.osxpkg import calculate_install_dir
36+
from constructor.osxpkg import calculate_install_dir, is_macho_binary
3737
elif sys.platform.startswith("win"):
3838
import winreg
3939

@@ -709,7 +709,7 @@ def test_macos_signing(tmp_path, self_signed_application_certificate_macos):
709709
with open(input_path / "construct.yaml", "a") as f:
710710
f.write(f"notarization_identity_name: {self_signed_application_certificate_macos}\n")
711711
output_path = tmp_path / "output"
712-
installer, install_dir = next(create_installer(input_path, output_path))
712+
installer, _ = next(create_installer(input_path, output_path))
713713

714714
# Check component signatures
715715
expanded_path = output_path / "expanded"
@@ -722,6 +722,10 @@ def test_macos_signing(tmp_path, self_signed_application_certificate_macos):
722722
Path(expanded_path, "prepare_installation.pkg", "Payload", "osx-pkg-test", conda_exe_name),
723723
Path(expanded_path, "Plugins", "ExtraPage.bundle"),
724724
]
725+
internal_dir = Path(
726+
expanded_path, "prepare_installation.pkg", "Payload", "osx-pkg-test", "_internal"
727+
)
728+
components.extend([file for file in internal_dir.glob("**") if is_macho_binary(file)])
725729
validated_signatures = []
726730
for component in components:
727731
p = subprocess.run(

0 commit comments

Comments
 (0)