Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(213)

Side by Side Diff: build/config/ios/codesign.py

Issue 2060943002: [iOS/GN] Add code signing rules to ios_app_bundle target. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@ios-code-signing-gn
Patch Set: Address comments Created 4 years, 6 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « no previous file | build/config/ios/rules.gni » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
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
3 # found in the LICENSE file.
4
5 import argparse
6 import fnmatch
7 import glob
8 import os
9 import plistlib
10 import shutil
11 import subprocess
12 import sys
13 import tempfile
14
15
16 class InstallationError(Exception):
17 """Signals a local installation error that prevents code signing."""
18
19 def __init__(self, fmt, *args):
20 super(Exception, self).__init__(fmt % args)
21
22
23 def GetProvisioningProfilesDir():
24 """Returns the location of the installed mobile provisioning profiles.
25
26 Returns:
27 The path to the directory containing the installed mobile provisioning
28 profiles as a string.
29 """
30 return os.path.join(
31 os.environ['HOME'], 'Library', 'MobileDevice', 'Provisioning Profiles')
32
33
34 def LoadPlistFile(plist_path):
35 """Loads property list file at |plist_path|.
36
37 Args:
38 plist_path: path to the property list file to load.
39
40 Returns:
41 The content of the property list file as a python object.
42 """
43 return plistlib.readPlistFromString(subprocess.check_output([
44 'xcrun', 'plutil', '-convert', 'xml1', '-o', '-', plist_path]))
45
46
47 class Bundle(object):
48 """Wraps a bundle."""
49
50 def __init__(self, bundle_path):
51 """Initializes the Bundle object with data from bundle Info.plist file."""
52 self._path = bundle_path
53 self._data = LoadPlistFile(os.path.join(self._path, 'Info.plist'))
54
55 @property
56 def path(self):
57 return self._path
58
59 @property
60 def identifier(self):
61 return self._data['CFBundleIdentifier']
62
63 @property
64 def binary_path(self):
65 return os.path.join(self._path, self._data['CFBundleExecutable'])
66
67
68 class ProvisioningProfile(object):
69 """Wraps a mobile provisioning profile file."""
70
71 def __init__(self, provisioning_profile_path):
72 """Initializes the ProvisioningProfile with data from profile file."""
73 self._path = provisioning_profile_path
74 self._data = plistlib.readPlistFromString(subprocess.check_output([
75 'xcrun', 'security', 'cms', '-D', '-i', provisioning_profile_path]))
76
77 @property
78 def path(self):
79 return self._path
80
81 @property
82 def application_identifier_pattern(self):
83 return self._data.get('Entitlements', {}).get('application-identifier', '')
84
85 @property
86 def team_identifier(self):
87 return self._data.get('TeamIdentifier', [''])[0]
88
89 @property
90 def entitlements(self):
91 return self._data.get('Entitlements', {})
92
93 def ValidToSignBundle(self, bundle):
94 """Checks whether the provisioning profile can sign bundle_identifier.
95
96 Args:
97 bundle: the Bundle object that needs to be signed.
98
99 Returns:
100 True if the mobile provisioning profile can be used to sign a bundle
101 with the corresponding bundle_identifier, False otherwise.
102 """
103 return fnmatch.fnmatch(
104 '%s.%s' % (self.team_identifier, bundle.identifier),
105 self.application_identifier_pattern)
106
107 def Install(self, bundle):
108 """Copies mobile provisioning profile info the bundle."""
109 installation_path = os.path.join(bundle.path, 'embedded.mobileprovision')
110 shutil.copy2(self.path, installation_path)
111
112
113 class Entitlements(object):
114 """Wraps an Entitlement plist file."""
115
116 def __init__(self, entitlements_path):
117 """Initializes Entitlements object from entitlement file."""
118 self._path = entitlements_path
119 self._data = LoadPlistFile(self._path)
120
121 @property
122 def path(self):
123 return self._path
124
125 def ExpandVariables(self, substitutions):
126 self._data = self._ExpandVariables(self._data, substitutions)
127
128 def _ExpandVariables(self, data, substitutions):
129 if isinstance(data, str):
130 for key, substitution in substitutions.iteritems():
131 data = data.replace('$(%s)' % (key,), substitution)
132 return data
133
134 if isinstance(data, dict):
135 for key, value in data.iteritems():
136 data[key] = self._ExpandVariables(value, substitutions)
137 return data
138
139 if isinstance(data, list):
140 for i, value in enumerate(data):
141 data[i] = self._ExpandVariables(value, substitutions)
142
143 return data
144
145 def LoadDefaults(self, defaults):
146 for key, value in defaults.iteritems():
147 if key not in self._data:
148 self._data[key] = value
149
150 def WriteTo(self, target_path):
151 plistlib.writePlist(self._data, target_path)
152
153
154 def FindProvisioningProfile(bundle, provisioning_profile_short_name):
155 """Finds mobile provisioning profile to use to sign bundle.
156
157 Args:
158 bundle: the Bundle object to sign.
159 provisioning_profile_short_path: optional short name of the mobile
160 provisioning profile file to use to sign (will still be checked
161 to see if it can sign bundle).
162
163 Returns:
164 The ProvisioningProfile object that can be used to sign the Bundle
165 object.
166
167 Raises:
168 InstallationError if no mobile provisioning profile can be used to
169 sign the Bundle object.
170 """
171 provisioning_profiles_dir = GetProvisioningProfilesDir()
172
173 # First check if there is a mobile provisioning profile installed with
174 # the requested short name. If this is the case, restrict the search to
175 # that mobile provisioning profile, otherwise consider all the installed
176 # mobile provisioning profiles.
177 provisioning_profile_paths = []
178 if provisioning_profile_short_name:
179 provisioning_profile_path = os.path.join(
180 provisioning_profiles_dir,
181 provisioning_profile_short_name + '.mobileprovision')
182 if os.path.isfile(provisioning_profile_path):
183 provisioning_profile_paths.append(provisioning_profile_path)
184
185 if not provisioning_profile_paths:
186 provisioning_profile_paths = glob.glob(
187 os.path.join(provisioning_profiles_dir, '*.mobileprovision'))
188
189 # Iterate over all installed mobile provisioning profiles and filter those
190 # that can be used to sign the bundle.
191 valid_provisioning_profiles = []
192 for provisioning_profile_path in provisioning_profile_paths:
193 provisioning_profile = ProvisioningProfile(provisioning_profile_path)
194 if provisioning_profile.ValidToSignBundle(bundle):
195 valid_provisioning_profiles.append(provisioning_profile)
196
197 if not valid_provisioning_profiles:
198 raise InstallationError(
199 'no mobile provisioning profile for "%s"',
200 bundle.identifier)
201
202 # Select the most specific mobile provisioning profile, i.e. the one with
203 # the longest application identifier pattern.
204 valid_provisioning_profiles.sort(
205 key=lambda p: len(p.application_identifier_pattern))
206 return valid_provisioning_profiles[0]
207
208
209 def CodeSignBundle(binary, bundle, args):
210 """Cryptographically signs bundle.
211
212 Args:
213 bundle: the Bundle object to sign.
214 args: a dictionary with configuration settings for the code signature,
215 need to define 'entitlements_path', 'provisioning_profile_short_name',
216 'deep_signature' and 'identify' keys.
217 """
218 provisioning_profile = FindProvisioningProfile(
219 bundle, args.provisioning_profile_short_name)
220 provisioning_profile.Install(bundle)
221
222 signature_file = os.path.join(bundle.path, '_CodeSignature', 'CodeResources')
223 if os.path.isfile(signature_file):
224 os.unlink(signature_file)
225
226 shutil.copy(binary, bundle.binary_path)
227
228 if args.preserve:
229 subprocess.check_call([
230 'xcrun', 'codesign', '--force', '--sign', args.identity,
231 '--deep', '--preserve-metadata=identifier,entitlements',
232 '--timestamp=none', bundle.path])
233 else:
234 entitlements = Entitlements(args.entitlements_path)
235 entitlements.LoadDefaults(provisioning_profile.entitlements)
236 entitlements.ExpandVariables({
237 'CFBundleIdentifier': bundle.identifier,
238 'AppIdentifierPrefix': '%s.' % (provisioning_profile.team_identifier,)
239 })
240
241 with tempfile.NamedTemporaryFile(suffix='.xcent') as temporary_file_path:
242 entitlements.WriteTo(temporary_file_path.name)
243 subprocess.check_call([
244 'xcrun', 'codesign', '--force', '--sign', args.identity,
245 '--entitlements', temporary_file_path.name, '--timestamp=none',
246 bundle.path])
247
248
249 def Main():
250 parser = argparse.ArgumentParser('codesign iOS bundles')
251 parser.add_argument(
252 'path', help='path to the iOS bundle to codesign')
253 parser.add_argument(
254 '--binary', '-b', required=True,
255 help='path to the iOS bundle binary')
256 parser.add_argument(
257 '--provisioning-profile', '-p', dest='provisioning_profile_short_name',
258 help='short name of the mobile provisioning profile to use ('
259 'if undefined, will autodetect the mobile provisioning '
260 'to use)')
261 parser.add_argument(
262 '--identity', '-i', required=True,
263 help='identity to use to codesign')
264 group = parser.add_mutually_exclusive_group(required=True)
265 group.add_argument(
266 '--entitlements', '-e', dest='entitlements_path',
267 help='path to the entitlements file to use')
268 group.add_argument(
269 '--deep', '-d', action='store_true', default=False, dest='preserve',
270 help='deep signature (default: %(default)s)')
271 args = parser.parse_args()
272
273 CodeSignBundle(args.binary, Bundle(args.path), args)
274
275
276 if __name__ == '__main__':
277 sys.exit(Main())
OLDNEW
« no previous file with comments | « no previous file | build/config/ios/rules.gni » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698