|
15 | 15 | import sys |
16 | 16 | import locale |
17 | 17 | import os |
| 18 | +import re |
18 | 19 | import platform |
19 | 20 | import shlex |
20 | 21 | import subprocess |
21 | | -from typing import Optional, List |
| 22 | +from typing import Optional, List, Dict |
22 | 23 |
|
23 | 24 | TARGET_NATIVE = "native" |
24 | 25 | TARGET_X86 = "x86_64" |
@@ -115,10 +116,8 @@ def GetTargetArchPair(context): |
115 | 116 | def SupportsMacOSUniversalBinaries(): |
116 | 117 | if not MacOS(): |
117 | 118 | 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) |
122 | 121 |
|
123 | 122 | def GetSDKRoot(context) -> Optional[str]: |
124 | 123 | sdk = "macosx" |
@@ -163,36 +162,162 @@ def ExtractFilesRecursive(path, cond): |
163 | 162 | files.append(os.path.join(r, file)) |
164 | 163 | return files |
165 | 164 |
|
166 | | -def CodesignFiles(files): |
167 | | - SDKVersion = subprocess.check_output( |
168 | | - ['xcodebuild', '-version']).strip()[6:10] |
169 | | - codeSignIDs = subprocess.check_output( |
170 | | - ['security', 'find-identity', '-vp', 'codesigning']) |
171 | | - |
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): |
| 165 | +def _GetCodeSignStringFromTerminal(): |
| 166 | + """Return the output from the string codesigning variables""" |
| 167 | + codeSignIDs = GetCommandOutput(['security', 'find-identity', '-vp', 'codesigning']) |
| 168 | + return codeSignIDs |
| 169 | + |
| 170 | + |
| 171 | +def GetXcodeVersion(): |
| 172 | + output = GetCommandOutput(['xcodebuild', '-version']).split() |
| 173 | + version = float(output[1]) |
| 174 | + build = output[-1] |
| 175 | + |
| 176 | + return version, build |
| 177 | + |
| 178 | + |
| 179 | +def GetCodeSigningIdentifiers() -> Dict[str, str]: |
| 180 | + """Returns a dictionary of codesigning identifiers and their hashes""" |
| 181 | + XcodeVersion = GetXcodeVersion()[0] |
| 182 | + codeSignIDs = _GetCodeSignStringFromTerminal() |
| 183 | + |
| 184 | + if not codeSignIDs: |
| 185 | + return {"-": None} |
| 186 | + |
| 187 | + identifiers = {} |
| 188 | + for codeSignID in codeSignIDs.splitlines(): |
| 189 | + if "CSSMERR_TP_CERT_REVOKED" in codeSignID: |
| 190 | + continue |
| 191 | + if ")" not in codeSignID: |
| 192 | + continue |
| 193 | + if (XcodeVersion >= 11 and "Apple Development" in codeSignID) or "Mac Developer" in codeSignID: |
| 194 | + identifier = codeSignID.split()[1] |
| 195 | + identifier_hash = re.search(r'\(.*?\)', codeSignID) |
| 196 | + if identifier_hash: |
| 197 | + identifier_hash = identifier_hash[0][1:-1] |
| 198 | + else: |
| 199 | + identifier_hash = None |
| 200 | + |
| 201 | + identifiers[identifier] = identifier_hash |
| 202 | + |
| 203 | + if not identifiers: |
| 204 | + raise RuntimeError("Could not find a valid codesigning ID. Try re-logging into your Xcode developer account.") |
| 205 | + |
| 206 | + return identifiers |
| 207 | + |
| 208 | + |
| 209 | +def GetCodeSignID() -> str: |
| 210 | + """Return the first code signing identifier""" |
| 211 | + identifiers = GetCodeSigningIdentifiers() |
| 212 | + env_signing_id = os.environ.get('CODE_SIGN_ID') |
| 213 | + if env_signing_id: |
| 214 | + if env_signing_id in identifiers: |
| 215 | + return env_signing_id |
| 216 | + raise RuntimeError( |
| 217 | + f"Could not find environment specified identifier {env_signing_id} in registered code signing identifiers") |
| 218 | + |
| 219 | + return list(GetCodeSigningIdentifiers().keys())[0] |
| 220 | + |
| 221 | + |
| 222 | +def GetDevelopmentTeamID(identifier=None): |
| 223 | + if os.environ.get("DEVELOPMENT_TEAM"): |
| 224 | + return os.environ.get("DEVELOPMENT_TEAM") |
| 225 | + |
| 226 | + if not identifier: |
| 227 | + identifier = GetCodeSignID() |
| 228 | + |
| 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(["security", "find-certificate", "-c", identifier_hash, "-p"]) |
| 234 | + subject = GetCommandOutput(["openssl", "x509", "-subject"], input=certs) |
| 235 | + subject = subject.splitlines()[0] |
| 236 | + match = re.search("OU\s*=\s*(?P<team>([A-Za-z0-9_])+)", subject) |
| 237 | + if not match: |
| 238 | + raise RuntimeError("Could not parse the output certificate to find the team ID") |
| 239 | + |
| 240 | + groups = match.groupdict() |
| 241 | + team = groups.get("team") |
| 242 | + |
| 243 | + if not team: |
| 244 | + raise RuntimeError("Could not extract team id from certificate") |
| 245 | + |
| 246 | + return team |
| 247 | + |
| 248 | + |
| 249 | +def CodesignPath(path, identifier, team_identifier, force=False) -> bool: |
| 250 | + resign = force |
| 251 | + if not force: |
| 252 | + codesigning_info = GetCommandOutput(["codesign", "-vd", path]) |
| 253 | + if not codesigning_info: |
| 254 | + resign = True |
| 255 | + else: |
| 256 | + for line in codesigning_info.splitlines(): |
| 257 | + if line.startswith("TeamIdentifier="): |
| 258 | + current_team_identifier = line.split("=")[-1] |
| 259 | + if not team_identifier and "not set" in team_identifier: |
| 260 | + break |
| 261 | + elif team_identifier == current_team_identifier: |
| 262 | + break |
| 263 | + else: |
| 264 | + resign = True |
| 265 | + |
| 266 | + if not resign: |
| 267 | + return False |
| 268 | + |
| 269 | + # Frameworks need to be signed with different parameters than loose binaries |
| 270 | + if path.endswith(".framework"): |
| 271 | + subprocess.check_output( |
| 272 | + ["codesign", "--force", "--sign", identifier, "--generate-entitlement-der", "--verbose", path]) |
| 273 | + else: |
| 274 | + subprocess.check_call(["codesign", "--force", "--sign", identifier, path], stdout=devout, stderr=devout) |
| 275 | + return True |
| 276 | + |
| 277 | + |
| 278 | +def Codesign(install_path, identifier=None, force=False, verbose_output=False) -> bool: |
187 | 279 | if not MacOS(): |
188 | 280 | return False |
| 281 | + |
| 282 | + codeSignID = identifier or GetCodeSignID() |
| 283 | + |
189 | 284 | if verbose_output: |
190 | 285 | global devout |
191 | 286 | devout = sys.stdout |
| 287 | + print(f"Code-signing files in {install_path} with {identifier}", file=devout) |
192 | 288 |
|
193 | | - files = ExtractFilesRecursive(install_path, |
194 | | - (lambda file: '.so' in file or '.dylib' in file)) |
195 | | - CodesignFiles(files) |
| 289 | + try: |
| 290 | + team_identifier = GetDevelopmentTeamID(codeSignID) |
| 291 | + except: |
| 292 | + team_identifier = None |
| 293 | + |
| 294 | + for root, dirs, files in os.walk(install_path, topdown=True): |
| 295 | + for f in files: |
| 296 | + |
| 297 | + _, ext = os.path.splitext(f) |
| 298 | + if ext in (".dylib", ".so"): |
| 299 | + path = os.path.join(root, f) |
| 300 | + result = CodesignPath(path, identifier, team_identifier=team_identifier, force=force) |
| 301 | + if verbose_output: |
| 302 | + if result: |
| 303 | + print(f"Code-signed binary: {path}") |
| 304 | + else: |
| 305 | + print(f"Did not code-sign binary: {path}") |
| 306 | + |
| 307 | + # Bit annoying to have to do this twice, but seems the fastest way to skip traversing frameworks |
| 308 | + frameworks = [d for d in dirs if d.endswith(".framework")] |
| 309 | + dirs[:] = [d for d in dirs if not d.endswith(".framework")] |
| 310 | + |
| 311 | + for framework in frameworks: |
| 312 | + path = os.path.join(root, framework) |
| 313 | + result = CodesignPath(path, identifier, team_identifier=team_identifier, force=force) |
| 314 | + if verbose_output: |
| 315 | + if result: |
| 316 | + print(f"Code-signed framework: {path}") |
| 317 | + else: |
| 318 | + print(f"Did not code-sign framework: {path}") |
| 319 | + |
| 320 | + return True |
196 | 321 |
|
197 | 322 | def CreateUniversalBinaries(context, libNames, x86Dir, armDir): |
198 | 323 | if not MacOS(): |
|
0 commit comments