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

Unified 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: Fix code signing of "test" template 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 side-by-side diff with in-line comments
Download patch
« no previous file with comments | « no previous file | build/config/ios/rules.gni » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: build/config/ios/codesign.py
diff --git a/build/config/ios/codesign.py b/build/config/ios/codesign.py
new file mode 100644
index 0000000000000000000000000000000000000000..e2358965798ac2ba3c5346f8caf217d02e63420f
--- /dev/null
+++ b/build/config/ios/codesign.py
@@ -0,0 +1,366 @@
+# Copyright 2016 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import argparse
+import fnmatch
+import glob
+import os
+import shutil
+import subprocess
+import sys
+import tempfile
+
+import CoreFoundation
+
+
+class CoreFoundationError(Exception):
+
Robert Sesek 2016/06/15 15:30:59 nit: Remove the blank lines between class name and
sdefresne 2016/06/15 16:34:41 Done.
+ """Wraps a Core Foundation error as a python exception."""
+
+ def __init__(self, fmt, *args):
+ super(Exception, self).__init__(fmt % args)
+
+
+class InstallationError(Exception):
+
+ """Signals a local installation error that prevents code signing."""
+
+ def __init__(self, fmt, *args):
+ super(Exception, self).__init__(fmt % args)
+
+
+def GetProvisioningProfilesDir():
+ """Returns the location of the installed mobile provisioning profiles.
+
+ Returns:
+ The path to the directory containing the installed mobile provisioning
+ profiles as a string.
+ """
+ return os.path.join(
+ os.environ['HOME'], 'Library', 'MobileDevice', 'Provisioning Profiles')
+
+
+def LoadPlistFile(plist_path):
+ """Loads property list file at |plist_path|.
+
+ Args:
+ plist_path: path to the property list file to load, can either be any
+ format supported by Core Foundation (currently xml1, binary1).
+
+ Returns:
+ The content of the loaded property list, most likely a dictionary like
+ object (as a Core Foundation wrapped object).
+
+ Raises:
+ CoreFoundationError if Core Foundation returned an error while loading
+ the property list file.
+ """
+ with open(plist_path, 'rb') as plist_file:
+ plist_data = plist_file.read()
+ cfdata = CoreFoundation.CFDataCreate(None, plist_data, len(plist_data))
+ plist, plist_format, error = CoreFoundation.CFPropertyListCreateWithData(
+ None, cfdata, 0, None, None)
+ if error is not None:
+ raise CoreFoundationError('cannot load plist: %s', error)
+ return plist
+
+
+class Bundle(object):
+
+ """Wraps a bundle."""
+
+ def __init__(self, path, data):
+ self._path = path
+ self._data = data
+
+ @staticmethod
+ def Load(bundle_path):
+ """Loads and wraps a bundle.
+
+ Args:
+ bundle_path: path to the bundle.
+
+ Returns:
+ A Bundle instance with data loaded from the bundle Info.plist property
+ list file.
+ """
+ return Bundle(
+ bundle_path, LoadPlistFile(os.path.join(bundle_path, 'Info.plist')))
+
+ @property
+ def path(self):
+ return self._path
+
+ @property
+ def identifier(self):
+ return self._data['CFBundleIdentifier']
+
+ @property
+ def binary_path(self):
+ return os.path.join(self._path, self._data['CFBundleExecutable'])
+
+
+class ProvisioningProfile(object):
+
+ """Wraps a mobile provisioning profile file."""
+
+ def __init__(self, path, data):
+ self._path = path
+ self._data = data
+
+ @staticmethod
+ def Load(provisioning_profile_path):
+ """Loads and wraps a mobile provisioning profile file.
+
+ Args:
+ provisioning_profile_path: path to the mobile provisioning profile.
+
+ Returns:
+ A ProvisioningProfile instance with data loaded from the mobile
+ provisioning file.
+ """
+ with tempfile.NamedTemporaryFile() as temporary_file_path:
+ subprocess.check_call([
+ 'security', 'cms', '-D',
+ '-i', provisioning_profile_path,
+ '-o', temporary_file_path.name])
+ return ProvisioningProfile(
+ provisioning_profile_path,
+ LoadPlistFile(temporary_file_path.name))
+
+ @property
+ def path(self):
+ return self._path
+
+ @property
+ def application_identifier_pattern(self):
+ return self._data.get('Entitlements', {}).get('application-identifier', '')
+
+ @property
+ def team_identifier(self):
+ return self._data.get('TeamIdentifier', [''])[0]
+
+ @property
+ def entitlements(self):
+ return self._data.get('Entitlements', {})
+
+ def ValidToSignBundle(self, bundle):
+ """Checks whether the provisioning profile can sign bundle_identifier.
+
+ Args:
+ bundle: the Bundle object that needs to be signed.
+
+ Returns:
+ True if the mobile provisioning profile can be used to sign a bundle
+ with the corresponding bundle_identifier, False otherwise.
+ """
+ return fnmatch.fnmatch(
+ '%s.%s' % (self.team_identifier, bundle.identifier),
+ self.application_identifier_pattern)
+
+ def Install(self, bundle):
+ """Copies mobile provisioning profile info the bundle."""
+ installation_path = os.path.join(bundle.path, 'embedded.mobileprovision')
+ shutil.copy2(self.path, installation_path)
+
+
+class Entitlements(object):
Robert Sesek 2016/06/15 15:30:59 Document?
sdefresne 2016/06/15 16:34:41 Done.
+
+ def __init__(self, path, data):
+ self._path = path
+ self._data = data
+
+ @staticmethod
+ def Load(entitlements_path):
+ return Entitlements(
+ entitlements_path,
+ LoadPlistFile(entitlements_path))
+
+ @property
+ def path(self):
+ return self._path
+
+ def ExpandVariables(self, substitutions):
+ self._data = self._ExpandVariables(self._data, substitutions)
+
+ def _ExpandVariables(self, data, substitutions):
+ if hasattr(data, 'endswith'):
+ for key, substitution in substitutions.iteritems():
+ data = data.replace('$(%s)' % (key,), substitution)
+ return data
+
+ if hasattr(data, 'keys'):
+ copy = CoreFoundation.CFDictionaryCreateMutable(None, 0,
Robert Sesek 2016/06/15 15:30:59 It may be a little bit easier to use plistlib to l
sdefresne 2016/06/15 16:34:41 Changed the code to use plistlib.
+ CoreFoundation.kCFTypeDictionaryKeyCallBacks,
+ CoreFoundation.kCFTypeDictionaryValueCallBacks)
+ for key, value in data.iteritems():
+ copy[key] = self._ExpandVariables(value, substitutions)
+ return copy
+
+ if hasattr(data, 'append'):
+ copy = CoreFoundation.CFArrayCreateMutable(None, 0,
+ CoreFoundation.kCFTypeArrayCallBacks)
+ for value in data:
+ copy.append(self._ExpandVariables(value, substitutions))
+ return copy
+
+ return data
+
+ def LoadDefaults(self, defaults):
+ for key, value in defaults.iteritems():
+ if key not in self._data:
+ self._data[key] = value
+
+ def WriteTo(self, target_path):
+ cfdata, error = CoreFoundation.CFPropertyListCreateData(
+ None, self._data, CoreFoundation.kCFPropertyListXMLFormat_v1_0,
+ 0, None)
+ if error is not None:
+ raise CoreFoundationError('cannot write property list as data: %s', error)
+ data = CoreFoundation.CFDataGetBytes(
+ cfdata,
+ CoreFoundation.CFRangeMake(0, CoreFoundation.CFDataGetLength(cfdata)),
+ None)
+ with open(target_path, 'wb') as target_file:
+ target_file.write(data)
+ target_file.flush()
+
+
+def FindProvisioningProfile(bundle, provisioning_profile_short_name):
+ """Finds mobile provisioning profile to use to sign bundle.
+
+ Args:
+ bundle: the Bundle object to sign.
+ provisioning_profile_short_path: optional short name of the mobile
+ provisioning profile file to use to sign (will still be checked
+ to see if it can sign bundle).
+
+ Returns:
+ The ProvisioningProfile object that can be used to sign the Bundle
+ object.
+
+ Raises:
+ InstallationError if no mobile provisioning profile can be used to
+ sign the Bundle object.
+ """
+ provisioning_profiles_dir = GetProvisioningProfilesDir()
+
+ # First check if there is a mobile provisioning profile installed with
+ # the requested short name. If this is the case, restrict the search to
+ # that mobile provisioning profile, otherwise consider all the installed
+ # mobile provisioning profiles.
+ provisioning_profile_paths = []
+ if provisioning_profile_short_name:
+ provisioning_profile_path = os.path.join(
+ provisioning_profiles_dir,
+ provisioning_profile_short_name + '.mobileprovision')
+ if os.path.isfile(provisioning_profile_path):
+ provisioning_profile_paths.append(provisioning_profile_path)
+
+ if not provisioning_profile_paths:
+ provisioning_profile_paths = glob.glob(
+ os.path.join(provisioning_profiles_dir, '*.mobileprovision'))
+
+ # Iterate over all installed mobile provisioning profiles and filter those
+ # that can be used to sign the bundle.
+ valid_provisioning_profiles = []
+ for provisioning_profile_path in provisioning_profile_paths:
+ provisioning_profile = ProvisioningProfile.Load(provisioning_profile_path)
+ if provisioning_profile.ValidToSignBundle(bundle):
+ valid_provisioning_profiles.append(provisioning_profile)
+
+ if not valid_provisioning_profiles:
+ raise InstallationError(
+ 'no mobile provisioning profile for "%s"',
+ bundle.identifier)
+
+ # Select the most specific mobile provisioning profile, i.e. the one with
+ # the longest application identifier pattern.
+ valid_provisioning_profiles.sort(
+ key=lambda p: len(p.application_identifier_pattern))
+ return valid_provisioning_profiles[0]
+
+
+def CreateEntitlements(bundle, provisioning_profile, entitlements_path):
Robert Sesek 2016/06/15 15:30:59 Why have CreateEntitlements, Entitlements.Load, an
sdefresne 2016/06/15 16:34:41 Removed this method (it was converting a entitleme
+ """Creates entitlements using defaults from provisioning profile.
+
+ Args:
+ bundle: the Bundle object to sign.
+ provisition_profile: the ProvisioningProfile object used to sign.
+ entitlements_path: path to the template to use to generate the bundle
+ entitlements file, needs to be a property list file.
+ """
+ entitlements = Entitlements.Load(entitlements_path)
+ entitlements.ExpandVariables({
+ 'CFBundleIdentifier': bundle.identifier,
+ 'AppIdentifierPrefix': '%s.' % (provisioning_profile.team_identifier,)
+ })
+ entitlements.LoadDefaults(provisioning_profile.entitlements)
+ return entitlements
+
+
+def CodeSignBundle(binary, bundle, args):
+ """Cryptographically signs bundle.
+
+ Args:
+ bundle: the Bundle object to sign.
+ args: a dictionary with configuration settings for the code signature,
+ need to define 'entitlements_path', 'provisioning_profile_short_name',
+ 'deep_signature' and 'identify' keys.
+ """
+ provisioning_profile = FindProvisioningProfile(
+ bundle, args.provisioning_profile_short_name)
+ provisioning_profile.Install(bundle)
+
+ signature_file = os.path.join(bundle.path, "_CodeSignature", "CodeResources")
Robert Sesek 2016/06/15 15:30:59 nit: Switch to single quotes here for consistency
sdefresne 2016/06/15 16:34:41 Done.
+ if os.path.isfile(signature_file):
+ os.unlink(signature_file)
+
+ shutil.copy(binary, bundle.binary_path)
+
+ command = ['codesign', '--force', '--sign', args.identity, '--timestamp=none']
Robert Sesek 2016/06/15 15:30:59 Should invoke codesign through xcrun.
sdefresne 2016/06/15 16:34:41 Done.
+ if args.preserve:
+ command.extend(['--deep', '--preserve-metadata=identifier,entitlements'])
+ command.append(bundle.path)
+
+ subprocess.check_call(command)
+ else:
+ entitlements = CreateEntitlements(
+ bundle, provisioning_profile, args.entitlements_path)
+ with tempfile.NamedTemporaryFile(suffix='.xcent') as temporary_file_path:
+ entitlements.WriteTo(temporary_file_path.name)
+ command.extend(['--entitlements', temporary_file_path.name])
+ command.append(bundle.path)
+ subprocess.check_call(command)
+
+
+def Main():
+ parser = argparse.ArgumentParser('codesign iOS bundles')
+ parser.add_argument(
+ 'path', help='path to the iOS bundle to codesign')
+ parser.add_argument(
+ '--binary', '-b', required=True,
+ help='path to the iOS bundle binary')
+ parser.add_argument(
+ '--provisioning-profile', '-p', dest='provisioning_profile_short_name',
+ help='short name of the mobile provisioning profile to use ('
+ 'if undefined, will autodetect the mobile provisioning '
+ 'to use)')
+ parser.add_argument(
+ '--identity', '-i', required=True,
+ help='identity to use to codesign')
+ group = parser.add_mutually_exclusive_group(required=True)
+ group.add_argument(
+ '--entitlements', '-e', dest='entitlements_path',
+ help='path to the entitlements file to use')
+ group.add_argument(
+ '--deep', '-d', action='store_true', default=False, dest='preserve',
+ help='deep signature (default: %(default)s)')
+ args = parser.parse_args()
+
+ CodeSignBundle(args.binary, Bundle.Load(args.path), args)
+
+
+if __name__ == '__main__':
+ sys.exit(Main())
« 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