1# Copyright 2019 The Chromium Authors. All rights reserved. 2# Use of this source code is governed by a BSD-style license that can be 3# found in the LICENSE file. 4 5""" 6Finds the team identifier to use for code signing bundle given its 7bundle identifier. 8""" 9 10import argparse 11import fnmatch 12import glob 13import json 14import os 15import plistlib 16import subprocess 17import sys 18 19 20class ProvisioningProfile(object): 21 22 def __init__(self, mobileprovision_path): 23 self._path = mobileprovision_path 24 self._data = plistlib.loads( 25 subprocess.check_output( 26 ['security', 'cms', '-D', '-i', mobileprovision_path])) 27 28 @property 29 def application_identifier_pattern(self): 30 return self._data.get('Entitlements', {}).get('application-identifier', '') 31 32 @property 33 def app_identifier_prefix(self): 34 return self._data.get('ApplicationIdentifierPrefix', [''])[0] 35 36 def ValidToSignBundle(self, bundle_identifier): 37 """Returns whether the provisioning profile can sign |bundle_identifier|.""" 38 return fnmatch.fnmatch( 39 self.app_identifier_prefix + '.' + bundle_identifier, 40 self.application_identifier_pattern) 41 42 43def GetProvisioningProfilesDir(): 44 """Returns the location of the locally installed provisioning profiles.""" 45 return os.path.join( 46 os.environ['HOME'], 'Library', 'MobileDevice', 'Provisioning Profiles') 47 48 49def ListProvisioningProfiles(): 50 """Returns a list of all installed provisioning profiles.""" 51 return glob.glob( 52 os.path.join(GetProvisioningProfilesDir(), '*.mobileprovision')) 53 54 55def LoadProvisioningProfile(mobileprovision_path): 56 """Loads the Apple Property List embedded in |mobileprovision_path|.""" 57 return ProvisioningProfile(mobileprovision_path) 58 59 60def ListValidProvisioningProfiles(bundle_identifier): 61 """Returns a list of provisioning profile valid for |bundle_identifier|.""" 62 result = [] 63 for mobileprovision_path in ListProvisioningProfiles(): 64 mobileprovision = LoadProvisioningProfile(mobileprovision_path) 65 if mobileprovision.ValidToSignBundle(bundle_identifier): 66 result.append(mobileprovision) 67 return result 68 69 70def FindProvisioningProfile(bundle_identifier): 71 """Returns the path to the provisioning profile for |bundle_identifier|.""" 72 return max( 73 ListValidProvisioningProfiles(bundle_identifier), 74 key=lambda p: len(p.application_identifier_pattern)) 75 76 77def GenerateSubsitutions(bundle_identifier, mobileprovision): 78 if mobileprovision: 79 app_identifier_prefix = mobileprovision.app_identifier_prefix + '.' 80 else: 81 app_identifier_prefix = '*.' 82 83 return { 84 'CFBundleIdentifier': bundle_identifier, 85 'AppIdentifierPrefix': app_identifier_prefix 86 } 87 88 89def ParseArgs(argv): 90 """Parses command line arguments.""" 91 parser = argparse.ArgumentParser( 92 description=__doc__, 93 formatter_class=argparse.ArgumentDefaultsHelpFormatter) 94 95 parser.add_argument( 96 '-b', '--bundle-identifier', required=True, 97 help='bundle identifier for the application') 98 parser.add_argument( 99 '-o', '--output', default='-', 100 help='path to the result; - means stdout') 101 102 return parser.parse_args(argv) 103 104 105def main(argv): 106 args = ParseArgs(argv) 107 108 mobileprovision = FindProvisioningProfile(args.bundle_identifier) 109 substitutions = GenerateSubsitutions(args.bundle_identifier, mobileprovision) 110 111 if args.output == '-': 112 sys.stdout.write(json.dumps(substitutions)) 113 else: 114 with open(args.output, 'w') as output: 115 output.write(json.dumps(substitutions)) 116 117 118if __name__ == '__main__': 119 sys.exit(main(sys.argv[1:])) 120