Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 # Copyright 2016 The Chromium Authors. All rights reserved. | 1 # Copyright 2016 The Chromium Authors. All rights reserved. |
| 2 # Use of this source code is governed by a BSD-style license that can be | 2 # Use of this source code is governed by a BSD-style license that can be |
| 3 # found in the LICENSE file. | 3 # found in the LICENSE file. |
| 4 | 4 |
| 5 import argparse | 5 import argparse |
| 6 import fnmatch | 6 import fnmatch |
| 7 import glob | 7 import glob |
| 8 import os | 8 import os |
| 9 import plistlib | 9 import plistlib |
| 10 import shutil | 10 import shutil |
| (...skipping 72 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 83 return self._data.get('Entitlements', {}).get('application-identifier', '') | 83 return self._data.get('Entitlements', {}).get('application-identifier', '') |
| 84 | 84 |
| 85 @property | 85 @property |
| 86 def team_identifier(self): | 86 def team_identifier(self): |
| 87 return self._data.get('TeamIdentifier', [''])[0] | 87 return self._data.get('TeamIdentifier', [''])[0] |
| 88 | 88 |
| 89 @property | 89 @property |
| 90 def entitlements(self): | 90 def entitlements(self): |
| 91 return self._data.get('Entitlements', {}) | 91 return self._data.get('Entitlements', {}) |
| 92 | 92 |
| 93 def ValidToSignBundle(self, bundle): | 93 def ValidToSignBundle(self, bundle_identifier): |
| 94 """Checks whether the provisioning profile can sign bundle_identifier. | 94 """Checks whether the provisioning profile can sign bundle_identifier. |
| 95 | 95 |
| 96 Args: | 96 Args: |
| 97 bundle: the Bundle object that needs to be signed. | 97 bundle_identifier: the identifier of the bundle that needs to be signed. |
| 98 | 98 |
| 99 Returns: | 99 Returns: |
| 100 True if the mobile provisioning profile can be used to sign a bundle | 100 True if the mobile provisioning profile can be used to sign a bundle |
| 101 with the corresponding bundle_identifier, False otherwise. | 101 with the corresponding bundle_identifier, False otherwise. |
| 102 """ | 102 """ |
| 103 return fnmatch.fnmatch( | 103 return fnmatch.fnmatch( |
| 104 '%s.%s' % (self.team_identifier, bundle.identifier), | 104 '%s.%s' % (self.team_identifier, bundle_identifier), |
| 105 self.application_identifier_pattern) | 105 self.application_identifier_pattern) |
| 106 | 106 |
| 107 def Install(self, bundle): | 107 def Install(self, bundle): |
| 108 """Copies mobile provisioning profile info the bundle.""" | 108 """Copies mobile provisioning profile info the bundle.""" |
| 109 installation_path = os.path.join(bundle.path, 'embedded.mobileprovision') | 109 installation_path = os.path.join(bundle.path, 'embedded.mobileprovision') |
| 110 shutil.copy2(self.path, installation_path) | 110 shutil.copy2(self.path, installation_path) |
| 111 | 111 |
| 112 | 112 |
| 113 class Entitlements(object): | 113 class Entitlements(object): |
| 114 """Wraps an Entitlement plist file.""" | 114 """Wraps an Entitlement plist file.""" |
| (...skipping 29 matching lines...) Expand all Loading... | |
| 144 | 144 |
| 145 def LoadDefaults(self, defaults): | 145 def LoadDefaults(self, defaults): |
| 146 for key, value in defaults.iteritems(): | 146 for key, value in defaults.iteritems(): |
| 147 if key not in self._data: | 147 if key not in self._data: |
| 148 self._data[key] = value | 148 self._data[key] = value |
| 149 | 149 |
| 150 def WriteTo(self, target_path): | 150 def WriteTo(self, target_path): |
| 151 plistlib.writePlist(self._data, target_path) | 151 plistlib.writePlist(self._data, target_path) |
| 152 | 152 |
| 153 | 153 |
| 154 def FindProvisioningProfile(bundle, provisioning_profile_short_name): | 154 def FindProvisioningProfile(bundle_identifier, provisioning_profile_short_name): |
| 155 """Finds mobile provisioning profile to use to sign bundle. | 155 """Finds mobile provisioning profile to use to sign bundle. |
| 156 | 156 |
| 157 Args: | 157 Args: |
| 158 bundle: the Bundle object to sign. | 158 bundle_identifier: the identifier of the bundle to sign. |
| 159 provisioning_profile_short_path: optional short name of the mobile | 159 provisioning_profile_short_path: optional short name of the mobile |
| 160 provisioning profile file to use to sign (will still be checked | 160 provisioning profile file to use to sign (will still be checked |
| 161 to see if it can sign bundle). | 161 to see if it can sign bundle). |
| 162 | 162 |
| 163 Returns: | 163 Returns: |
| 164 The ProvisioningProfile object that can be used to sign the Bundle | 164 The ProvisioningProfile object that can be used to sign the Bundle |
| 165 object. | 165 object. |
| 166 | 166 |
| 167 Raises: | 167 Raises: |
| 168 InstallationError if no mobile provisioning profile can be used to | 168 InstallationError if no mobile provisioning profile can be used to |
| (...skipping 15 matching lines...) Expand all Loading... | |
| 184 | 184 |
| 185 if not provisioning_profile_paths: | 185 if not provisioning_profile_paths: |
| 186 provisioning_profile_paths = glob.glob( | 186 provisioning_profile_paths = glob.glob( |
| 187 os.path.join(provisioning_profiles_dir, '*.mobileprovision')) | 187 os.path.join(provisioning_profiles_dir, '*.mobileprovision')) |
| 188 | 188 |
| 189 # Iterate over all installed mobile provisioning profiles and filter those | 189 # Iterate over all installed mobile provisioning profiles and filter those |
| 190 # that can be used to sign the bundle. | 190 # that can be used to sign the bundle. |
| 191 valid_provisioning_profiles = [] | 191 valid_provisioning_profiles = [] |
| 192 for provisioning_profile_path in provisioning_profile_paths: | 192 for provisioning_profile_path in provisioning_profile_paths: |
| 193 provisioning_profile = ProvisioningProfile(provisioning_profile_path) | 193 provisioning_profile = ProvisioningProfile(provisioning_profile_path) |
| 194 if provisioning_profile.ValidToSignBundle(bundle): | 194 if provisioning_profile.ValidToSignBundle(bundle_identifier): |
| 195 valid_provisioning_profiles.append(provisioning_profile) | 195 valid_provisioning_profiles.append(provisioning_profile) |
| 196 | 196 |
| 197 if not valid_provisioning_profiles: | 197 if not valid_provisioning_profiles: |
| 198 raise InstallationError( | 198 raise InstallationError( |
| 199 'no mobile provisioning profile for "%s"', | 199 'no mobile provisioning profile for "%s"', |
| 200 bundle.identifier) | 200 bundle_identifier) |
| 201 | 201 |
| 202 # Select the most specific mobile provisioning profile, i.e. the one with | 202 # Select the most specific mobile provisioning profile, i.e. the one with |
| 203 # the longest application identifier pattern. | 203 # the longest application identifier pattern. |
| 204 return max( | 204 return max( |
| 205 valid_provisioning_profiles, | 205 valid_provisioning_profiles, |
| 206 key=lambda p: len(p.application_identifier_pattern)) | 206 key=lambda p: len(p.application_identifier_pattern)) |
| 207 | 207 |
| 208 | 208 |
| 209 def InstallFramework(framework_path, bundle, args): | 209 def InstallFramework(framework_path, bundle, args): |
| 210 """Install framework from |framework_path| to |bundle| and code-re-sign it.""" | 210 """Install framework from |framework_path| to |bundle| and code-re-sign it.""" |
| 211 installed_framework_path = os.path.join( | 211 installed_framework_path = os.path.join( |
| 212 bundle.path, 'Frameworks', os.path.basename(framework_path)) | 212 bundle.path, 'Frameworks', os.path.basename(framework_path)) |
| 213 | 213 |
| 214 if os.path.exists(installed_framework_path): | 214 if os.path.exists(installed_framework_path): |
| 215 shutil.rmtree(installed_framework_path) | 215 shutil.rmtree(installed_framework_path) |
| 216 | 216 |
| 217 shutil.copytree(framework_path, installed_framework_path) | 217 shutil.copytree(framework_path, installed_framework_path) |
| 218 | 218 |
| 219 framework_bundle = Bundle(installed_framework_path) | 219 framework_bundle = Bundle(installed_framework_path) |
| 220 CodeSignBundle(framework_bundle.binary_path, framework_bundle, args, True) | 220 CodeSignBundle(framework_bundle.binary_path, framework_bundle, args, True) |
| 221 | 221 |
| 222 | 222 |
| 223 def GenerateEntitlements(bundle_identifier, provisioning_profile, args): | |
|
justincohen
2016/07/29 22:28:10
If the upstream bots can have an empty provisionin
sdefresne
2016/08/02 16:58:57
Done.
| |
| 224 """Generates an entitlements file. | |
| 225 | |
| 226 Args: | |
| 227 bundle_identifier: identifier of the bundle to sign. | |
| 228 provisioning_profile: ProvisioningProfile object. | |
| 229 output_path: path to the generated entitlements file. | |
| 230 """ | |
| 231 entitlements = Entitlements(args.entitlements_path) | |
| 232 entitlements.LoadDefaults(provisioning_profile.entitlements) | |
| 233 entitlements.ExpandVariables({ | |
| 234 'CFBundleIdentifier': bundle_identifier, | |
| 235 'AppIdentifierPrefix': '%s.' % (provisioning_profile.team_identifier,) | |
| 236 }) | |
| 237 return entitlements | |
| 238 | |
| 239 | |
| 223 def CodeSignBundle(binary, bundle, args, preserve=False): | 240 def CodeSignBundle(binary, bundle, args, preserve=False): |
| 224 """Cryptographically signs bundle. | 241 """Cryptographically signs bundle. |
| 225 | 242 |
| 226 Args: | 243 Args: |
| 227 bundle: the Bundle object to sign. | 244 bundle: the Bundle object to sign. |
| 228 args: a dictionary with configuration settings for the code signature, | 245 args: a dictionary with configuration settings for the code signature, |
| 229 need to define 'entitlements_path', 'provisioning_profile_short_name', | 246 need to define 'entitlements_path', 'provisioning_profile_short_name', |
| 230 'deep_signature' and 'identify' keys. | 247 'deep_signature' and 'identify' keys. |
| 231 """ | 248 """ |
| 232 provisioning_profile = FindProvisioningProfile( | 249 provisioning_profile = FindProvisioningProfile( |
| 233 bundle, args.provisioning_profile_short_name) | 250 bundle.identifier, args.provisioning_profile_short_name) |
| 234 provisioning_profile.Install(bundle) | 251 provisioning_profile.Install(bundle) |
| 235 | 252 |
| 236 if preserve: | 253 if preserve: |
| 237 process = subprocess.Popen([ | 254 process = subprocess.Popen([ |
| 238 'xcrun', 'codesign', '--force', '--sign', args.identity, | 255 'xcrun', 'codesign', '--force', '--sign', args.identity, |
| 239 '--deep', '--preserve-metadata=identifier,entitlements', | 256 '--deep', '--preserve-metadata=identifier,entitlements', |
| 240 '--timestamp=none', bundle.path], stderr=subprocess.PIPE) | 257 '--timestamp=none', bundle.path], stderr=subprocess.PIPE) |
| 241 _, stderr = process.communicate() | 258 _, stderr = process.communicate() |
| 242 if process.returncode: | 259 if process.returncode: |
| 243 sys.stderr.write(stderr) | 260 sys.stderr.write(stderr) |
| 244 sys.exit(process.returncode) | 261 sys.exit(process.returncode) |
| 245 for line in stderr.splitlines(): | 262 for line in stderr.splitlines(): |
| 246 # Ignore expected warning as we are replacing the signature on purpose. | 263 # Ignore expected warning as we are replacing the signature on purpose. |
| 247 if not line.endswith(': replacing existing signature'): | 264 if not line.endswith(': replacing existing signature'): |
| 248 sys.stderr.write(line + '\n') | 265 sys.stderr.write(line + '\n') |
| 249 else: | 266 else: |
| 250 signature_file = os.path.join( | 267 signature_file = os.path.join( |
| 251 bundle.path, '_CodeSignature', 'CodeResources') | 268 bundle.path, '_CodeSignature', 'CodeResources') |
| 252 if os.path.isfile(signature_file): | 269 if os.path.isfile(signature_file): |
| 253 os.unlink(signature_file) | 270 os.unlink(signature_file) |
| 254 | 271 |
| 255 if os.path.isfile(bundle.binary_path): | 272 if os.path.isfile(bundle.binary_path): |
| 256 os.unlink(bundle.binary_path) | 273 os.unlink(bundle.binary_path) |
| 257 shutil.copy(binary, bundle.binary_path) | 274 shutil.copy(binary, bundle.binary_path) |
| 258 | 275 |
| 259 entitlements = Entitlements(args.entitlements_path) | 276 entitlements = GenerateEntitlements( |
| 260 entitlements.LoadDefaults(provisioning_profile.entitlements) | 277 bundle.identifier, provisioning_profile, args) |
| 261 entitlements.ExpandVariables({ | |
| 262 'CFBundleIdentifier': bundle.identifier, | |
| 263 'AppIdentifierPrefix': '%s.' % (provisioning_profile.team_identifier,) | |
| 264 }) | |
| 265 | 278 |
| 266 with tempfile.NamedTemporaryFile(suffix='.xcent') as temporary_file_path: | 279 with tempfile.NamedTemporaryFile(suffix='.xcent') as temporary_file_path: |
| 267 entitlements.WriteTo(temporary_file_path.name) | 280 entitlements.WriteTo(temporary_file_path.name) |
| 268 subprocess.check_call([ | 281 subprocess.check_call([ |
| 269 'xcrun', 'codesign', '--force', '--sign', args.identity, | 282 'xcrun', 'codesign', '--force', '--sign', args.identity, |
| 270 '--entitlements', temporary_file_path.name, '--timestamp=none', | 283 '--entitlements', temporary_file_path.name, '--timestamp=none', |
| 271 bundle.path]) | 284 bundle.path]) |
| 272 | 285 |
| 273 | 286 |
| 287 def MainCodeSignBundle(args): | |
| 288 """Adapter to call CodeSignBundle from Main.""" | |
| 289 bundle = Bundle(args.path) | |
| 290 for framework in args.frameworks: | |
| 291 InstallFramework(framework, bundle, args) | |
| 292 CodeSignBundle(args.binary, bundle, args) | |
| 293 | |
| 294 | |
| 295 def MainGenerateEntitlements(args): | |
| 296 """Adapter to call GenerateEntitlements from Main.""" | |
| 297 info_plist = LoadPlistFile(args.info_plist) | |
| 298 bundle_identifier = info_plist['CFBundleIdentifier'] | |
| 299 provisioning_profile = FindProvisioningProfile( | |
| 300 bundle_identifier, args.provisioning_profile_short_name) | |
| 301 entitlements = GenerateEntitlements( | |
| 302 bundle_identifier, provisioning_profile, args) | |
| 303 entitlements.WriteTo(args.path) | |
| 304 | |
| 305 | |
| 274 def Main(): | 306 def Main(): |
| 275 parser = argparse.ArgumentParser('codesign iOS bundles') | 307 parser = argparse.ArgumentParser('codesign iOS bundles') |
| 276 parser.add_argument( | 308 parser.add_argument( |
| 277 'path', help='path to the iOS bundle to codesign') | |
| 278 parser.add_argument( | |
| 279 '--binary', '-b', required=True, | |
| 280 help='path to the iOS bundle binary') | |
| 281 parser.add_argument( | |
| 282 '--provisioning-profile', '-p', dest='provisioning_profile_short_name', | 309 '--provisioning-profile', '-p', dest='provisioning_profile_short_name', |
| 283 help='short name of the mobile provisioning profile to use (' | 310 help='short name of the mobile provisioning profile to use (' |
| 284 'if undefined, will autodetect the mobile provisioning ' | 311 'if undefined, will autodetect the mobile provisioning ' |
| 285 'to use)') | 312 'to use)') |
| 286 parser.add_argument( | 313 parser.add_argument( |
| 314 '--entitlements', '-e', dest='entitlements_path', | |
| 315 help='path to the entitlements file to use') | |
| 316 subparsers = parser.add_subparsers() | |
| 317 | |
| 318 code_sign_bundle_parser = subparsers.add_parser( | |
| 319 'code-sign-bundle', | |
| 320 help='code sign a bundle') | |
| 321 code_sign_bundle_parser.add_argument( | |
| 322 'path', help='path to the iOS bundle to codesign') | |
| 323 code_sign_bundle_parser.add_argument( | |
| 287 '--identity', '-i', required=True, | 324 '--identity', '-i', required=True, |
| 288 help='identity to use to codesign') | 325 help='identity to use to codesign') |
| 289 parser.add_argument( | 326 code_sign_bundle_parser.add_argument( |
| 290 '--entitlements', '-e', dest='entitlements_path', | 327 '--binary', '-b', required=True, |
| 291 help='path to the entitlements file to use') | 328 help='path to the iOS bundle binary') |
| 292 parser.add_argument( | 329 code_sign_bundle_parser.add_argument( |
| 293 '--framework', '-F', action='append', default=[], dest="frameworks", | 330 '--framework', '-F', action='append', default=[], dest="frameworks", |
| 294 help='install and resign system framework') | 331 help='install and resign system framework') |
| 332 code_sign_bundle_parser.set_defaults(func=MainCodeSignBundle) | |
| 333 | |
| 334 generate_entitlements_parser = subparsers.add_parser( | |
| 335 'generate-entitlements', | |
| 336 help='generate entitlements file') | |
| 337 generate_entitlements_parser.add_argument( | |
| 338 'path', help='path to the entitlements file to generate') | |
| 339 generate_entitlements_parser.add_argument( | |
| 340 '--info-plist', '-p', required=True, | |
| 341 help='path to the bundle Info.plist') | |
| 342 generate_entitlements_parser.set_defaults( | |
| 343 func=MainGenerateEntitlements) | |
| 344 | |
| 295 args = parser.parse_args() | 345 args = parser.parse_args() |
| 296 | 346 args.func(args) |
| 297 bundle = Bundle(args.path) | |
| 298 for framework in args.frameworks: | |
| 299 InstallFramework(framework, bundle, args) | |
| 300 | |
| 301 CodeSignBundle(args.binary, bundle, args) | |
| 302 | 347 |
| 303 | 348 |
| 304 if __name__ == '__main__': | 349 if __name__ == '__main__': |
| 305 sys.exit(Main()) | 350 sys.exit(Main()) |
| OLD | NEW |