Skip to content

Commit 48cb715

Browse files
dgovilpixar-oss
authored andcommitted
APPLE: Make codesigning improvements
This PR overhauls the current code signing setup with the following changes: 1. Developers can now pass in code signing identifiers directly to the build_usd.py script. 2. The code signing identifier is calculated less often 3. Codesigning now supports signing frameworks 4. Codesigning supports checking previous signing status and only re-signing as needed. Note: Since USD's cmake always installs all targets again, they're always re-signed. Future work might help optimize this, but at the least we're not re-signing every dependency. 5. Codesigning is validated earlier to prevent running through a whole build just to find code signing fails This makes code signing much more reliable and faster overall. This work is needed to enable properly building OpenUSD as a framework Closes #3710 (Internal change: 2389581)
1 parent 0ba3847 commit 48cb715

2 files changed

Lines changed: 196 additions & 32 deletions

File tree

build_scripts/apple_utils.py

Lines changed: 186 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
# Licensed under the terms set forth in the LICENSE.txt file available at
55
# https://openusd.org/license.
66
#
7-
87
# Utilities for managing Apple OS build concerns.
98
#
109
# NOTE: This file and its contents may change significantly as we continue
@@ -15,10 +14,12 @@
1514
import sys
1615
import locale
1716
import os
17+
import re
1818
import platform
1919
import shlex
2020
import subprocess
21-
from typing import Optional, List
21+
import glob
22+
from typing import Optional, List, Dict
2223

2324
TARGET_NATIVE = "native"
2425
TARGET_X86 = "x86_64"
@@ -115,10 +116,8 @@ def GetTargetArchPair(context):
115116
def SupportsMacOSUniversalBinaries():
116117
if not MacOS():
117118
return False
118-
XcodeOutput = GetCommandOutput(["/usr/bin/xcodebuild", "-version"])
119-
XcodeFind = XcodeOutput.rfind('Xcode ', 0, len(XcodeOutput))
120-
XcodeVersion = XcodeOutput[XcodeFind:].split(' ')[1]
121-
return (XcodeVersion > '11.0')
119+
XcodeVersion = GetXcodeVersion()[0]
120+
return (XcodeVersion > 11)
122121

123122
def GetSDKRoot(context) -> Optional[str]:
124123
sdk = "macosx"
@@ -163,36 +162,195 @@ def ExtractFilesRecursive(path, cond):
163162
files.append(os.path.join(r, file))
164163
return files
165164

166-
def CodesignFiles(files):
167-
SDKVersion = subprocess.check_output(
168-
['xcodebuild', '-version']).strip()[6:10]
169-
codeSignIDs = subprocess.check_output(
165+
def _GetCodeSignStringFromTerminal():
166+
"""Return the output from the string codesigning variables"""
167+
codeSignIDs = GetCommandOutput(
170168
['security', 'find-identity', '-vp', 'codesigning'])
169+
return codeSignIDs
170+
171+
172+
def GetXcodeVersion():
173+
output = GetCommandOutput(['xcodebuild', '-version']).split()
174+
version = float(output[1])
175+
build = output[-1]
176+
177+
return version, build
178+
179+
180+
def GetCodeSigningIdentifiers() -> Dict[str, str]:
181+
"""Returns a dictionary of codesigning identifiers and their hashes"""
182+
XcodeVersion = GetXcodeVersion()[0]
183+
codeSignIDs = _GetCodeSignStringFromTerminal()
184+
185+
identifiers = {}
186+
for codeSignID in (codeSignIDs or "").splitlines():
187+
if "CSSMERR_TP_CERT_REVOKED" in codeSignID:
188+
continue
189+
if ")" not in codeSignID:
190+
continue
191+
if ((XcodeVersion >= 11 and "Apple Development" in codeSignID)
192+
or "Mac Developer" in codeSignID):
193+
identifier = codeSignID.split()[1]
194+
identifier_hash = re.search(r'\(.*?\)', codeSignID)
195+
if identifier_hash:
196+
identifier_hash = identifier_hash[0][1:-1]
197+
else:
198+
identifier_hash = None
199+
200+
identifiers[identifier] = identifier_hash
201+
202+
identifiers["-"] = None
203+
return identifiers
204+
205+
206+
def GetCodeSignID() -> str:
207+
"""Return the first code signing identifier"""
208+
identifiers = GetCodeSigningIdentifiers()
209+
env_signing_id = os.environ.get('CODE_SIGN_ID')
210+
if env_signing_id:
211+
if env_signing_id in identifiers:
212+
return env_signing_id
213+
raise RuntimeError(
214+
f"Could not find environment specified identifier "
215+
f"{env_signing_id} in registered code signing identifiers")
216+
217+
return list(GetCodeSigningIdentifiers().keys())[0]
218+
219+
220+
def GetDevelopmentTeamID(identifier=None):
221+
if "DEVELOPMENT_TEAM" in os.environ:
222+
return os.environ.get("DEVELOPMENT_TEAM")
223+
224+
if not identifier:
225+
identifier = GetCodeSignID()
226+
if identifier == "-":
227+
return None
171228

172-
codeSignID = "-"
173-
if os.environ.get('CODE_SIGN_ID'):
174-
codeSignID = os.environ.get('CODE_SIGN_ID')
175-
elif float(SDKVersion) >= 11.0 and \
176-
codeSignIDs.find(b'Apple Development') != -1:
177-
codeSignID = "Apple Development"
178-
elif codeSignIDs.find(b'Mac Developer') != -1:
179-
codeSignID = "Mac Developer"
180-
181-
for f in files:
182-
subprocess.call(['codesign', '-f', '-s', '{codesignid}'
183-
.format(codesignid=codeSignID), f],
184-
stdout=devout, stderr=devout)
185-
186-
def Codesign(install_path, verbose_output=False):
229+
identifier_hash = GetCodeSigningIdentifiers().get(identifier)
230+
if not identifier_hash:
231+
raise RuntimeError("Could not get identifiers hash")
232+
233+
certs = subprocess.check_output(
234+
["security", "find-certificate", "-c", identifier_hash, "-p"])
235+
subject = GetCommandOutput(["openssl", "x509", "-subject"], input=certs)
236+
subject = subject.splitlines()[0]
237+
match = re.search("OU\s*=\s*(?P<team>([A-Za-z0-9_])+)", subject)
238+
if not match:
239+
raise RuntimeError("Could not parse the output "
240+
"certificate to find the team ID")
241+
242+
groups = match.groupdict()
243+
team = groups.get("team")
244+
245+
if not team:
246+
raise RuntimeError("Could not extract team id from certificate")
247+
248+
return team
249+
250+
251+
def CodesignPath(path, identifier, team_identifier,
252+
force=False, is_framework=False) -> bool:
253+
resign = force
254+
if not force:
255+
codesigning_info = GetCommandOutput(["codesign", "-vd", path])
256+
if not codesigning_info:
257+
resign = True
258+
else:
259+
# The output has multiple lines here
260+
for line in codesigning_info.splitlines():
261+
if line.startswith("TeamIdentifier="):
262+
current_team_identifier = line.split("=")[-1]
263+
if (not current_team_identifier
264+
or "not set" in current_team_identifier):
265+
resign = True
266+
break
267+
elif current_team_identifier == team_identifier:
268+
break
269+
else:
270+
resign = True
271+
272+
if not resign:
273+
return False
274+
275+
# Frameworks need to be signed with different parameters than loose binaries
276+
if is_framework:
277+
subprocess.check_call(
278+
["codesign", "--force", "--sign", identifier,
279+
"--generate-entitlement-der", "--verbose", path])
280+
else:
281+
subprocess.check_call(
282+
["codesign", "--force", "--sign", identifier, path],
283+
stdout=devout, stderr=devout)
284+
return True
285+
286+
287+
def Codesign(install_path, identifier=None, force=False,
288+
verbose_output=False) -> bool:
187289
if not MacOS():
188290
return False
291+
292+
identifier = identifier or GetCodeSignID()
293+
189294
if verbose_output:
190295
global devout
191296
devout = sys.stdout
297+
print(f"Code-signing files in {install_path} "
298+
f"with {identifier}", file=devout)
192299

193-
files = ExtractFilesRecursive(install_path,
194-
(lambda file: '.so' in file or '.dylib' in file))
195-
CodesignFiles(files)
300+
try:
301+
team_identifier = GetDevelopmentTeamID(identifier)
302+
except:
303+
if verbose_output:
304+
print("Could not get team_identifier")
305+
team_identifier = None
306+
307+
codesignPaths = [
308+
os.path.join(install_path, 'lib'),
309+
os.path.join(install_path, 'plugin'),
310+
os.path.join(install_path, 'share/usd'),
311+
os.path.join(install_path, "frameworks")
312+
]
313+
314+
for basePath in codesignPaths:
315+
if not os.path.exists(basePath):
316+
continue
317+
318+
for root, dirs, files in os.walk(basePath, topdown=True):
319+
for f in files:
320+
321+
_, ext = os.path.splitext(f)
322+
if ext in (".dylib", ".so"):
323+
path = os.path.join(root, f)
324+
result = CodesignPath(path, identifier,
325+
team_identifier=team_identifier,
326+
force=force, is_framework=False)
327+
if verbose_output:
328+
if result:
329+
print(f"Code-signed binary: {path}")
330+
else:
331+
print(f"Did not code-sign binary: {path}")
332+
333+
# Bit annoying to have to do this twice, but seems the fastest way
334+
# to skip traversing frameworks
335+
frameworks = [d for d in dirs if d.endswith(".framework")]
336+
dirs[:] = [d for d in dirs if not d.endswith(".framework")]
337+
338+
for framework in frameworks:
339+
framework_name = os.path.splitext(framework)[0]
340+
if (framework_name.lower() not in
341+
["openusd", "opensubdiv", "materialx"]):
342+
continue
343+
path = os.path.join(root, framework)
344+
result = CodesignPath(path, identifier,
345+
team_identifier=team_identifier,
346+
force=force, is_framework=True)
347+
if verbose_output:
348+
if result:
349+
print(f"Code-signed framework: {path}")
350+
else:
351+
print(f"Did not code-sign framework: {path}")
352+
353+
return True
196354

197355
def CreateUniversalBinaries(context, libNames, x86Dir, armDir):
198356
if not MacOS():

build_scripts/build_usd.py

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2082,6 +2082,9 @@ def InstallUSD(context, force, buildArgs):
20822082
default=codesignDefault, action="store_true",
20832083
help=("Enable code signing for macOS builds "
20842084
"(defaults to enabled on Apple Silicon)"))
2085+
group.add_argument("--codesign-id", dest="macos_codesign_id", type=str,
2086+
help=("A specific code-sign ID to use. If not provided, "
2087+
"the build will try and find one or use '-'"))
20852088

20862089
if Linux():
20872090
group.add_argument("--use-cxx11-abi", type=int, choices=[0, 1],
@@ -2392,9 +2395,10 @@ def __init__(self, args):
23922395
if MacOS():
23932396
apple_utils.SetTarget(self, self.buildTarget)
23942397

2395-
self.macOSCodesign = \
2396-
(args.macos_codesign if hasattr(args, "macos_codesign")
2397-
else False)
2398+
self.macOSCodesign = False
2399+
if args.macos_codesign:
2400+
self.macOSCodesign = (args.macos_codesign_id or
2401+
apple_utils.GetCodeSignID())
23982402
if apple_utils.IsHostArm() and args.ignore_homebrew:
23992403
self.ignorePaths.append("/opt/homebrew")
24002404
else:
@@ -2977,7 +2981,9 @@ def FormatBuildArguments(buildArgs):
29772981

29782982
if MacOS():
29792983
if context.macOSCodesign:
2980-
apple_utils.Codesign(context.usdInstDir, verbosity > 1)
2984+
apple_utils.Codesign(context.usdInstDir,
2985+
identifier=context.macOSCodesign,
2986+
verbose_output=verbosity > 1)
29812987

29822988
additionalInstructions = any([context.buildPython, context.buildTools, context.buildPrman])
29832989
if additionalInstructions:

0 commit comments

Comments
 (0)