-
Notifications
You must be signed in to change notification settings - Fork 1.4k
APPLE: Make codesigning improvements #3710
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 5 commits
5f6973a
864a2b2
326bc61
0983b32
d857716
e84f930
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -15,10 +15,11 @@ | |
| import sys | ||
| import locale | ||
| import os | ||
| import re | ||
| import platform | ||
| import shlex | ||
| import subprocess | ||
| from typing import Optional, List | ||
| from typing import Optional, List, Dict | ||
|
|
||
| TARGET_NATIVE = "native" | ||
| TARGET_X86 = "x86_64" | ||
|
|
@@ -115,10 +116,8 @@ def GetTargetArchPair(context): | |
| def SupportsMacOSUniversalBinaries(): | ||
| if not MacOS(): | ||
| return False | ||
| XcodeOutput = GetCommandOutput(["/usr/bin/xcodebuild", "-version"]) | ||
| XcodeFind = XcodeOutput.rfind('Xcode ', 0, len(XcodeOutput)) | ||
| XcodeVersion = XcodeOutput[XcodeFind:].split(' ')[1] | ||
| return (XcodeVersion > '11.0') | ||
| XcodeVersion = GetXcodeVersion()[0] | ||
| return (XcodeVersion > 11) | ||
|
|
||
| def GetSDKRoot(context) -> Optional[str]: | ||
| sdk = "macosx" | ||
|
|
@@ -163,36 +162,164 @@ def ExtractFilesRecursive(path, cond): | |
| files.append(os.path.join(r, file)) | ||
| return files | ||
|
|
||
| def CodesignFiles(files): | ||
| SDKVersion = subprocess.check_output( | ||
| ['xcodebuild', '-version']).strip()[6:10] | ||
| codeSignIDs = subprocess.check_output( | ||
| ['security', 'find-identity', '-vp', 'codesigning']) | ||
|
|
||
| codeSignID = "-" | ||
| if os.environ.get('CODE_SIGN_ID'): | ||
| codeSignID = os.environ.get('CODE_SIGN_ID') | ||
| elif float(SDKVersion) >= 11.0 and \ | ||
| codeSignIDs.find(b'Apple Development') != -1: | ||
| codeSignID = "Apple Development" | ||
| elif codeSignIDs.find(b'Mac Developer') != -1: | ||
| codeSignID = "Mac Developer" | ||
|
|
||
| for f in files: | ||
| subprocess.call(['codesign', '-f', '-s', '{codesignid}' | ||
| .format(codesignid=codeSignID), f], | ||
| stdout=devout, stderr=devout) | ||
|
|
||
| def Codesign(install_path, verbose_output=False): | ||
| def _GetCodeSignStringFromTerminal(): | ||
| """Return the output from the string codesigning variables""" | ||
| codeSignIDs = GetCommandOutput(['security', 'find-identity', '-vp', 'codesigning']) | ||
| return codeSignIDs | ||
|
|
||
|
|
||
| def GetXcodeVersion(): | ||
| output = GetCommandOutput(['xcodebuild', '-version']).split() | ||
| version = float(output[1]) | ||
| build = output[-1] | ||
|
|
||
| return version, build | ||
|
|
||
|
|
||
| def GetCodeSigningIdentifiers() -> Dict[str, str]: | ||
| """Returns a dictionary of codesigning identifiers and their hashes""" | ||
| XcodeVersion = GetXcodeVersion()[0] | ||
| codeSignIDs = _GetCodeSignStringFromTerminal() | ||
|
|
||
| identifiers = {} | ||
| for codeSignID in (codeSignIDs or "").splitlines(): | ||
| if "CSSMERR_TP_CERT_REVOKED" in codeSignID: | ||
| continue | ||
| if ")" not in codeSignID: | ||
| continue | ||
| if (XcodeVersion >= 11 and "Apple Development" in codeSignID) or "Mac Developer" in codeSignID: | ||
| identifier = codeSignID.split()[1] | ||
| identifier_hash = re.search(r'\(.*?\)', codeSignID) | ||
| if identifier_hash: | ||
| identifier_hash = identifier_hash[0][1:-1] | ||
| else: | ||
| identifier_hash = None | ||
|
|
||
| identifiers[identifier] = identifier_hash | ||
|
|
||
| identifiers["-"] = None | ||
| return identifiers | ||
|
|
||
|
|
||
| def GetCodeSignID() -> str: | ||
| """Return the first code signing identifier""" | ||
| identifiers = GetCodeSigningIdentifiers() | ||
| env_signing_id = os.environ.get('CODE_SIGN_ID') | ||
| if env_signing_id: | ||
| if env_signing_id in identifiers: | ||
| return env_signing_id | ||
| raise RuntimeError( | ||
| f"Could not find environment specified identifier {env_signing_id} in registered code signing identifiers") | ||
|
|
||
| return list(GetCodeSigningIdentifiers().keys())[0] | ||
|
|
||
|
|
||
| def GetDevelopmentTeamID(identifier=None): | ||
| if "DEVELOPMENT_TEAM" in os.environ: | ||
| return os.environ.get("DEVELOPMENT_TEAM") | ||
|
|
||
| if not identifier: | ||
| identifier = GetCodeSignID() | ||
| if identifier == "-": | ||
| return None | ||
|
|
||
| identifier_hash = GetCodeSigningIdentifiers().get(identifier) | ||
| if not identifier_hash: | ||
| raise RuntimeError("Could not get identifiers hash") | ||
|
|
||
| certs = subprocess.check_output(["security", "find-certificate", "-c", identifier_hash, "-p"]) | ||
| subject = GetCommandOutput(["openssl", "x509", "-subject"], input=certs) | ||
| subject = subject.splitlines()[0] | ||
| match = re.search("OU\s*=\s*(?P<team>([A-Za-z0-9_])+)", subject) | ||
| if not match: | ||
| raise RuntimeError("Could not parse the output certificate to find the team ID") | ||
|
|
||
| groups = match.groupdict() | ||
| team = groups.get("team") | ||
|
|
||
| if not team: | ||
| raise RuntimeError("Could not extract team id from certificate") | ||
|
|
||
| return team | ||
|
|
||
|
|
||
| def CodesignPath(path, identifier, team_identifier, force=False, is_framework=False) -> bool: | ||
| resign = force | ||
| if not force: | ||
| codesigning_info = GetCommandOutput(["codesign", "-vd", path]) | ||
| if not codesigning_info: | ||
| resign = True | ||
| else: | ||
| # The output has multiple lines here | ||
| for line in codesigning_info.splitlines(): | ||
| if line.startswith("TeamIdentifier="): | ||
| current_team_identifier = line.split("=")[-1] | ||
| if not current_team_identifier or "not set" in current_team_identifier: | ||
| resign = True | ||
| break | ||
| elif current_team_identifier == team_identifier: | ||
| break | ||
| else: | ||
| resign = True | ||
|
|
||
| if not resign: | ||
| return False | ||
|
|
||
| # Frameworks need to be signed with different parameters than loose binaries | ||
| if is_framework: | ||
| subprocess.check_call( | ||
| ["codesign", "--force", "--sign", identifier, "--generate-entitlement-der", "--verbose", path]) | ||
| else: | ||
| subprocess.check_call(["codesign", "--force", "--sign", identifier, path], stdout=devout, stderr=devout) | ||
| return True | ||
|
|
||
|
|
||
| def Codesign(install_path, identifier=None, force=False, verbose_output=False) -> bool: | ||
| if not MacOS(): | ||
| return False | ||
|
|
||
| codeSignID = identifier or GetCodeSignID() | ||
|
|
||
| if verbose_output: | ||
| global devout | ||
| devout = sys.stdout | ||
| print(f"Code-signing files in {install_path} with {identifier}", file=devout) | ||
|
|
||
| files = ExtractFilesRecursive(install_path, | ||
| (lambda file: '.so' in file or '.dylib' in file)) | ||
| CodesignFiles(files) | ||
| try: | ||
| team_identifier = GetDevelopmentTeamID(codeSignID) | ||
| except: | ||
| team_identifier = None | ||
|
|
||
| for root, dirs, files in os.walk(install_path, topdown=True): | ||
| for f in files: | ||
|
|
||
| _, ext = os.path.splitext(f) | ||
| if ext in (".dylib", ".so"): | ||
| path = os.path.join(root, f) | ||
| result = CodesignPath(path, identifier, team_identifier=team_identifier, force=force, is_framework=False) | ||
| if verbose_output: | ||
| if result: | ||
| print(f"Code-signed binary: {path}") | ||
| else: | ||
| print(f"Did not code-sign binary: {path}") | ||
|
|
||
| # Bit annoying to have to do this twice, but seems the fastest way to skip traversing frameworks | ||
| frameworks = [d for d in dirs if d.endswith(".framework")] | ||
| dirs[:] = [d for d in dirs if not d.endswith(".framework")] | ||
|
|
||
| for framework in frameworks: | ||
| framework_name = os.path.splitext(framework)[0] | ||
| if framework_name.lower() not in ["openusd", "opensubdiv", "materialx"]: | ||
| continue | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Instead of hardcoding a list of names, what do you think about just limiting the search to all So something like: I like that this is more targeted -- the current implementation winds up signing files in unnecessary places like the
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh yeah that makes sense. I guess I hadn't considered that since I always install into a clean directory, but I'm assuming others may install into a communal directory?
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh I see. It's because the usdInstPath is not the final install directory but the full container directory.
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @sunyab Okay, I pushed an update that integrates your solution to filter. I still use my walk within the filtered set because there are some dylibs that are within nested folders that need to be signed. |
||
| path = os.path.join(root, framework) | ||
| result = CodesignPath(path, identifier, team_identifier=team_identifier, force=force, is_framework=True) | ||
| if verbose_output: | ||
| if result: | ||
| print(f"Code-signed framework: {path}") | ||
| else: | ||
| print(f"Did not code-sign framework: {path}") | ||
|
|
||
| return True | ||
|
|
||
| def CreateUniversalBinaries(context, libNames, x86Dir, armDir): | ||
| if not MacOS(): | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.