Skip to content

Commit

Permalink
Merge pull request #10 from dortania/amfipass
Browse files Browse the repository at this point in the history
Implement DMG-based packaging and back-end for custom signing
  • Loading branch information
khronokernel authored May 4, 2023
2 parents c066060 + f4affdd commit 8b1c03e
Show file tree
Hide file tree
Showing 3 changed files with 208 additions and 10 deletions.
24 changes: 14 additions & 10 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,28 +9,32 @@ on:
jobs:
build_mojave:
name: Zip and Upload Binaries
runs-on: ubuntu-latest
runs-on: macos-latest
steps:
- uses: actions/checkout@v2
- name: Pre-flight cleaning
run: sudo python3 clean.py
- name: Zip Binaries
run: zip -r --symlinks Universal-Binaries.zip Universal-Binaries
- uses: actions/checkout@v3
- uses: apple-actions/import-codesign-certs@8f3fb608891dd2244cdab3d69cd68c0d37a7fe93
with:
p12-file-base64: ${{ secrets.CERTIFICATES_P12 }}
p12-password: ${{ secrets.CERTIFICATES_P12_PASSWORD }}
- name: Clean, strip, and sign binaries
run: python3 ci.py
- name: Build DMG
run: ./build-dmg.sh
- name: Generate Checksums
run: sha256sum *.zip > sha256sum.txt
run: shasum -a 256 Universal-Binaries.dmg > sha256sum.txt
- name: Upload Universal Binaries to Artifacts
uses: actions/upload-artifact@v2
with:
name: Universal-Binaries
path: Universal-Binaries.zip
name: Universal Binaries
path: Universal-Binaries.dmg
- name: Upload Checksums to Artifacts
uses: actions/upload-artifact@v2
with:
name: sha256sum.txt
path: sha256sum.txt
- run: |
mkdir deploy
mv *.zip sha256sum.txt deploy/
mv *.dmg sha256sum.txt deploy/
- name: Upload to Release
if: github.event_name == 'release'
uses: svenstaro/upload-release-action@e74ff71f7d8a4c4745b560a485cc5fdb9b5b999d
Expand Down
7 changes: 7 additions & 0 deletions build-dmg.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#!/usr/bin/env bash

xattr -rc Universal-Binaries
find Universal-Binaries -name .DS_Store -delete
hdiutil create -srcfolder Universal-Binaries tmp.dmg -volname "OpenCore Patcher Resources (Root Patching)" -fs HFS+ -ov -format UDRO
hdiutil convert -format ULMO tmp.dmg -o Universal-Binaries.dmg -passphrase password -encryption -ov
rm tmp.dmg
187 changes: 187 additions & 0 deletions ci.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
"""
Postprocess binaries on CI
"""

# TODO: We need a proper way to identify and sign bundles. Unfortunately Apple does not make this easy.

import subprocess
import sys
from pathlib import Path

# Configurable options

IDENTITY = "OCLP Signing"
TARGET_DIR = Path("Universal-Binaries")
UNUSED = [
# Mojave and Catalina non-Metal Patches
# No longer used by us, but nice for reference
"10.13.6-18",
"10.13.6-19",
"10.14.4-18",
"10.14.4-19",
"10.14.6-19",
# Nvidia Web Driver Patches
# Remove .pkg, unused by patcher
"WebDriver-387.10.10.10.40.140/WebDriver-387.10.10.10.40.140.pkg",
"WebDriver-387.10.10.10.40.140/WebDriver-387.10.10.15.15.108.pkg",
]

# Constants

MACHO_MAGIC = {
"MH_MAGIC": b"\xfe\xed\xfa\xce",
"MH_CIGAM": b"\xce\xfa\xed\xfe",
"MH_MAGIC_64": b"\xfe\xed\xfa\xcf",
"MH_CIGAM_64": b"\xcf\xfa\xed\xfe",
"FAT_MAGIC": b"\xbe\xba\xfe\xca",
"FAT_CIGAM": b"\xca\xfe\xba\xbe",
}


def clean_unused():
"""
Remove unused files and folders
"""

for path in UNUSED:
path = TARGET_DIR / path
if path.exists():
print(f"Removing: {path}")
subprocess.run(["rm", "-rf", path], check=True)

for path in TARGET_DIR.rglob(".DS_Store"):
print(f"Removing: {path}")
path.unlink()


def get_machos(directory=TARGET_DIR):
"""
Get all machos in a directory
"""

machos: dict[Path, bytes] = {}
for file in directory.rglob("*"):
if not file.is_file() or file.is_symlink():
continue
with file.open("rb") as f:
magic = f.read(4)
if magic in MACHO_MAGIC.values():
machos[file] = magic
return dict(sorted(machos.items(), key=lambda item: item[0]))


def thin_macho(file: Path, magic: bytes):
"""
Run lipo to thin a fat macho
"""

if magic in (MACHO_MAGIC["FAT_MAGIC"], MACHO_MAGIC["FAT_CIGAM"]):
subprocess.check_output(["lipo", "-thin", "x86_64", "-output", file, file])


def signing_sanity_checks(file: Path) -> tuple[bool, bool]: # (valid, needs_signing)
"""
Run codesign -dvvv and codesign --verify to check if a binary is signed correctly
"""

with file.open("rb") as f:
magic = f.read(4)
if magic not in (MACHO_MAGIC["MH_CIGAM_64"], MACHO_MAGIC["FAT_CIGAM"]):
print(f"ERROR: {file} is not a 64-bit Mach-O")
print()
return False, False

result = subprocess.run(["codesign", "-dvvv", file], stdout=subprocess.PIPE, stderr=subprocess.PIPE, check=False)
# stdout has the entitlements, stderr has the details
if result.returncode != 0:
if "not signed at all" not in result.stderr.decode():
raise RuntimeError(f"codesign failed ({result.returncode}): {result.stderr.decode()}")

entitlements = result.stdout.decode()
binary_details = result.stderr.decode()

result = subprocess.run(
["/usr/bin/codesign", "--verify", "--verbose=4", "--deep", "--ignore-resources", file], capture_output=True, check=False
)
if result.returncode != 0:
error_reason = result.stderr.decode().replace(f"{file}: ", "").splitlines()[0].strip()
if "Signature=adhoc" in binary_details or "Authority=OCLP Signing" in binary_details:
# We will just resign it anyway
pass
# elif error_reason == "resource envelope is obsolete (custom omit rules)":
# # Known weird issue with Apple binaries
# pass
elif error_reason == "invalid Info.plist (plist or signature have been modified)":
# Usually from ramdisk binaries or bad copies, but they still work
pass
# elif error_reason == "unsealed contents present in the bundle root" and "Signature=adhoc" in binary_details:
# # Bundle was not properly deep signed, ignore
# pass
# elif "is not signed at all" in error_reason:
# # This is fine, we will sign it
# return True, True
else:
# invalid signature (code or signature have been modified)
print(f"ERROR: {file}: {error_reason}")
print("If this file is patched or a new binary, please adhoc sign it.")
print()
return False, False

if ": no signature" in binary_details:
assert "{file}: no signature" in binary_details
print(f"ERROR: {file} is not signed")
print("It seems that this binary may have been signed by ldid. Please resign it with codesign, or pass -Cadhoc to ldid.")
print()
return False, False

if "Authority=Apple Root CA" in binary_details:
# File is signed by Apple, and we have already checked that it is valid
return True, False

# Anything past here should be adhoc or OCLP signed
if not (f"Authority={IDENTITY}" in binary_details or "Signature=adhoc" in binary_details):
raise RuntimeError(f"Unknown signing authority: {binary_details}")

if entitlements.strip():
print(f"Warning: {file} has entitlements. We will preserve entitlements, but this is unusual.")

return True, True


def sign_macho(file: Path):
"""
Sign a macho
"""

print(f"Signing: {file}")
subprocess.run(
["codesign", "-f", "-s", IDENTITY, "--preserve-metadata=entitlements", "--generate-entitlement-der", file],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
check=True,
)


if __name__ == "__main__":
clean_unused()

machos = get_machos()
if not machos:
print("No machos found!")
sys.exit(1)
machos_to_sign = []
all_valid = True
for macho, magic in machos.items():
thin_macho(macho, magic)

valid, needs_signing = signing_sanity_checks(macho)
all_valid &= valid
if needs_signing:
machos_to_sign.append(macho)
if not all_valid:
sys.exit(1)

for macho in machos_to_sign:
sign_macho(macho)

print("Done!")

0 comments on commit 8b1c03e

Please sign in to comment.