Skip to content

Commit 59fab6b

Browse files
committed
CM-53930: fix onedir signing issues on mac
1 parent 457022c commit 59fab6b

File tree

2 files changed

+41
-6
lines changed

2 files changed

+41
-6
lines changed

.github/workflows/build_executable.yml

Lines changed: 31 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,9 @@ jobs:
136136
# Main executable must be signed last after all its dependencies
137137
find dist/cycode-cli -type f ! -name "cycode-cli" | while read -r file; do
138138
if file -b "$file" | grep -q "Mach-O"; then
139-
codesign --force --sign "$APPLE_CERT_NAME" --timestamp --options runtime "$file"
139+
# override identifier to avoid framework-style identifiers (e.g. org.python.python)
140+
# that cause --strict verification to expect a missing Info.plist
141+
codesign --force --sign "$APPLE_CERT_NAME" --identifier "com.cycode.$(basename "$file")" --timestamp --options runtime "$file"
140142
fi
141143
done
142144
@@ -176,15 +178,39 @@ jobs:
176178
177179
# we can't staple the app because it's executable
178180
179-
- name: Test macOS signed executable
181+
- name: Verify macOS code signatures
180182
if: runner.os == 'macOS'
181183
run: |
182-
file -b $PATH_TO_CYCODE_CLI_EXECUTABLE
183-
time $PATH_TO_CYCODE_CLI_EXECUTABLE version
184+
# verify all Mach-O binaries in the output are properly signed
185+
FAILED=false
186+
while IFS= read -r file; do
187+
if file -b "$file" | grep -q "Mach-O"; then
188+
if ! codesign --verify --strict "$file" 2>&1; then
189+
echo "INVALID signature: $file"
190+
codesign -dv "$file" 2>&1 || true
191+
FAILED=true
192+
fi
193+
fi
194+
done < <(find dist/cycode-cli -type f)
195+
196+
if [ "$FAILED" = true ]; then
197+
echo "Found binaries with invalid signatures!"
198+
exit 1
199+
fi
184200
185-
# verify signature
201+
# verify main executable signature in detail
186202
codesign -dv --verbose=4 $PATH_TO_CYCODE_CLI_EXECUTABLE
187203
204+
- name: Test macOS signed executable (with quarantine)
205+
if: runner.os == 'macOS'
206+
run: |
207+
# simulate downloading from the internet by adding quarantine attribute
208+
# this triggers the same Gatekeeper/dlopen checks end users experience
209+
find dist/cycode-cli -type f -exec xattr -w com.apple.quarantine "0081;$(printf '%x' $(date +%s));CI;$(uuidgen)" {} \;
210+
211+
file -b $PATH_TO_CYCODE_CLI_EXECUTABLE
212+
time $PATH_TO_CYCODE_CLI_EXECUTABLE version
213+
188214
- name: Import cert for Windows and setup envs
189215
if: runner.os == 'Windows'
190216
env:

process_executable_file.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
import os
1414
import platform
1515
import shutil
16+
import subprocess
1617
from pathlib import Path
1718
from string import Template
1819
from typing import Union
@@ -140,6 +141,14 @@ def get_cli_archive_path(output_path: Path, is_onedir: bool) -> str:
140141
return os.path.join(output_path, get_cli_archive_filename(is_onedir))
141142

142143

144+
def archive_directory(input_path: Path, output_path: str) -> None:
145+
if get_os_name() == 'darwin':
146+
# use ditto on macOS to preserve code signature metadata
147+
subprocess.run(['ditto', '-c', '-k', str(input_path), output_path], check=True)
148+
else:
149+
shutil.make_archive(output_path.removesuffix(f'.{_ARCHIVE_FORMAT}'), _ARCHIVE_FORMAT, input_path)
150+
151+
143152
def process_executable_file(input_path: Path, is_onedir: bool) -> str:
144153
output_path = input_path.parent
145154
hash_file_path = get_cli_hash_path(output_path, is_onedir)
@@ -150,7 +159,7 @@ def process_executable_file(input_path: Path, is_onedir: bool) -> str:
150159
write_hashes_db_to_file(normalized_hashes, hash_file_path)
151160

152161
archived_file_path = get_cli_archive_path(output_path, is_onedir)
153-
shutil.make_archive(archived_file_path, _ARCHIVE_FORMAT, input_path)
162+
archive_directory(input_path, f'{archived_file_path}.{_ARCHIVE_FORMAT}')
154163
shutil.rmtree(input_path)
155164
else:
156165
file_hash = get_hash_of_file(input_path)

0 commit comments

Comments
 (0)