Commit 2b095ee0 authored by Mark Mentovai's avatar Mark Mentovai Committed by Commit Bot

Mac signing: support --asc-provider for notarization

For Apple Developer accounts associated with multiple development teams
(as mine is), altool requires --asc-provider to select a team any time
--username is used. Arguments are added to the sign_chrome.py and
notarize_thing.py notarization drivers whose values are propagated to
altool as needed.

For most Apple Developer accounts associated only with a single
development team, --asc-provider is not necessary.

This also removes --no-dir-entries from the zip command used to build an
archive for notarization, because if the code was signed with empty
directories, they need to be present for notarization too.

Bug: 980334
Change-Id: I646f3ee5729030df01ea200b9e72138626208931
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1682685
Commit-Queue: Mark Mentovai <mark@chromium.org>
Reviewed-by: default avatarRobert Sesek <rsesek@chromium.org>
Cr-Commit-Position: refs/heads/master@{#678370}
parent 97134d9c
...@@ -15,7 +15,7 @@ from signing.config import CodeSignConfig ...@@ -15,7 +15,7 @@ from signing.config import CodeSignConfig
def main(): def main():
parser = argparse.ArgumentParser( parser = argparse.ArgumentParser(
'Notarize and staple an application binary or archive.') description='Notarize and staple an application binary or archive.')
parser.add_argument( parser.add_argument(
'--user', '--user',
'-u', '-u',
...@@ -27,6 +27,13 @@ def main(): ...@@ -27,6 +27,13 @@ def main():
required=True, required=True,
help='The password or password reference (e.g. @keychain, see ' help='The password or password reference (e.g. @keychain, see '
'`xcrun altool -h`) to access the Apple notary service.') '`xcrun altool -h`) to access the Apple notary service.')
parser.add_argument(
'--asc-provider',
help='The ASC provider string to be used as the `--asc-provider` '
'argument to `xcrun altool`, to be used when --user is associated with '
'with multiple Apple developer teams. See `xcrun altool -h`. Run '
'`iTMSTransporter -m provider -account_type itunes_connect -v off -u '
'USERNAME -p PASSWORD` to list valid providers.')
parser.add_argument( parser.add_argument(
'--no-staple', '--no-staple',
action='store_true', action='store_true',
...@@ -56,7 +63,8 @@ def main(): ...@@ -56,7 +63,8 @@ def main():
config_class = OverrideBundleIDConfig config_class = OverrideBundleIDConfig
config = config_class('notused', None, args.user, args.password) config = config_class('notused', None, args.user, args.password,
args.asc_provider)
uuids = [] uuids = []
for path in args.file: for path in args.file:
......
...@@ -72,6 +72,13 @@ def main(): ...@@ -72,6 +72,13 @@ def main():
'--notary-password', '--notary-password',
help='The password or password reference (e.g. @keychain, see ' help='The password or password reference (e.g. @keychain, see '
'`xcrun altool -h`) used to authenticate to the Apple notary service.') '`xcrun altool -h`) used to authenticate to the Apple notary service.')
parser.add_argument(
'--notary-asc-provider',
help='The ASC provider string to be used as the `--asc-provider` '
'argument to `xcrun altool`, to be used when --notary-user is '
'associated with multiple Apple developer teams. See `xcrun altool -h. '
'Run `iTMSTransporter -m provider -account_type itunes_connect -v off '
'-u USERNAME -p PASSWORD` to list valid providers.')
parser.add_argument( parser.add_argument(
'--development', '--development',
action='store_true', action='store_true',
...@@ -114,8 +121,8 @@ def main(): ...@@ -114,8 +121,8 @@ def main():
parser.error('The --notary-user and --notary-password arguments ' parser.error('The --notary-user and --notary-password arguments '
'are required with --notarize.') 'are required with --notarize.')
config = create_config( config = create_config((args.identity, args.keychain, args.notary_user,
(args.identity, args.keychain, args.notary_user, args.notary_password), args.notary_password, args.notary_asc_provider),
args.development) args.development)
paths = model.Paths(args.input, args.output, None) paths = model.Paths(args.input, args.output, None)
......
...@@ -22,7 +22,8 @@ class CodeSignConfig(object): ...@@ -22,7 +22,8 @@ class CodeSignConfig(object):
identity, identity,
keychain=None, keychain=None,
notary_user=None, notary_user=None,
notary_password=None): notary_password=None,
notary_asc_provider=None):
"""Creates a CodeSignConfig that will sign the product using the static """Creates a CodeSignConfig that will sign the product using the static
properties on the class, using the code signing identity passed to the properties on the class, using the code signing identity passed to the
constructor, which is found in the specified keychain. constructor, which is found in the specified keychain.
...@@ -38,12 +39,16 @@ class CodeSignConfig(object): ...@@ -38,12 +39,16 @@ class CodeSignConfig(object):
notary_password: Optional string password or password reference notary_password: Optional string password or password reference
(e.g. @keychain, see `xcrun altool -h`) that will be used to (e.g. @keychain, see `xcrun altool -h`) that will be used to
authenticate to Apple's notary service if notarizing. authenticate to Apple's notary service if notarizing.
notary_asc_provider: Optitonal string that will be used as the
`--asc-provider` argument to `xcrun altool`, to be used when
notary_user is associatetd with multiple Apple developer teams.
""" """
assert identity assert identity
self._identity = identity self._identity = identity
self._keychain = keychain self._keychain = keychain
self._notary_user = notary_user self._notary_user = notary_user
self._notary_password = notary_password self._notary_password = notary_password
self._notary_asc_provider = notary_asc_provider
@property @property
def identity(self): def identity(self):
...@@ -71,6 +76,13 @@ class CodeSignConfig(object): ...@@ -71,6 +76,13 @@ class CodeSignConfig(object):
""" """
return self._notary_password return self._notary_password
@property
def notary_asc_provider(self):
"""Returns the ASC provider for authenticating to Apple's notary service
when notary_user is associatetd with multiple Apple developer teams.
"""
return self._notary_asc_provider
@property @property
def app_product(self): def app_product(self):
"""Returns the product name that is used for the outer .app bundle. """Returns the product name that is used for the outer .app bundle.
......
...@@ -257,7 +257,7 @@ class Distribution(object): ...@@ -257,7 +257,7 @@ class Distribution(object):
return DistributionCodeSignConfig( return DistributionCodeSignConfig(
base_config.identity, base_config.keychain, base_config.notary_user, base_config.identity, base_config.keychain, base_config.notary_user,
base_config.notary_password) base_config.notary_password, base_config.notary_asc_provider)
class Paths(object): class Paths(object):
......
...@@ -33,12 +33,15 @@ def submit(path, config): ...@@ -33,12 +33,15 @@ def submit(path, config):
Returns: Returns:
A UUID from the notary service that represents the request. A UUID from the notary service that represents the request.
""" """
output = commands.run_command_output([ command = [
'xcrun', 'altool', '--notarize-app', '--file', path, 'xcrun', 'altool', '--notarize-app', '--file', path,
'--primary-bundle-id', config.base_bundle_id, '--username', '--primary-bundle-id', config.base_bundle_id, '--username',
config.notary_user, '--password', config.notary_password, config.notary_user, '--password', config.notary_password,
'--output-format', 'xml' '--output-format', 'xml'
]) ]
if config.notary_asc_provider is not None:
command.extend(['--asc-provider', config.notary_asc_provider])
output = commands.run_command_output(command)
plist = plistlib.loads(output) plist = plistlib.loads(output)
uuid = plist['notarization-upload']['RequestUUID'] uuid = plist['notarization-upload']['RequestUUID']
print('Submitted {} for notarization, request UUID: {}.'.format(path, uuid)) print('Submitted {} for notarization, request UUID: {}.'.format(path, uuid))
...@@ -71,11 +74,15 @@ def wait_for_results(uuids, config): ...@@ -71,11 +74,15 @@ def wait_for_results(uuids, config):
while len(wait_set) > 0: while len(wait_set) > 0:
for uuid in list(wait_set): for uuid in list(wait_set):
try: try:
output = commands.run_command_output([ command = [
'xcrun', 'altool', '--notarization-info', uuid, 'xcrun', 'altool', '--notarization-info', uuid,
'--username', config.notary_user, '--password', '--username', config.notary_user, '--password',
config.notary_password, '--output-format', 'xml' config.notary_password, '--output-format', 'xml'
]) ]
if config.notary_asc_provider is not None:
command.extend(
['--asc-provider', config.notary_asc_provider])
output = commands.run_command_output(command)
except subprocess.CalledProcessError as e: except subprocess.CalledProcessError as e:
# A notarization request might report as "not found" immediately # A notarization request might report as "not found" immediately
# after submission, which causes altool to exit non-zero. Check # after submission, which causes altool to exit non-zero. Check
......
...@@ -40,6 +40,25 @@ class TestSubmit(unittest.TestCase): ...@@ -40,6 +40,25 @@ class TestSubmit(unittest.TestCase):
'--output-format', 'xml' '--output-format', 'xml'
]) ])
@mock.patch('signing.commands.run_command_output')
def test_valid_upload_with_asc_provider(self, run_command_output):
run_command_output.return_value = _make_plist({
'notarization-upload': {
'RequestUUID': '746f1537-0613-4e49-a9a0-869f2c9dc8e5'
},
})
config = test_config.TestConfig(
notary_asc_provider='[NOTARY-ASC-PROVIDER]')
uuid = notarize.submit('/tmp/file.dmg', config)
self.assertEqual('746f1537-0613-4e49-a9a0-869f2c9dc8e5', uuid)
run_command_output.assert_called_once_with([
'xcrun', 'altool', '--notarize-app', '--file', '/tmp/file.dmg',
'--primary-bundle-id', 'test.signing.bundle_id', '--username',
'[NOTARY-USER]', '--password', '[NOTARY-PASSWORD]',
'--output-format', 'xml', '--asc-provider', '[NOTARY-ASC-PROVIDER]'
])
class TestWaitForResults(unittest.TestCase): class TestWaitForResults(unittest.TestCase):
...@@ -65,6 +84,32 @@ class TestWaitForResults(unittest.TestCase): ...@@ -65,6 +84,32 @@ class TestWaitForResults(unittest.TestCase):
'--output-format', 'xml' '--output-format', 'xml'
]) ])
@mock.patch('signing.commands.run_command_output')
def test_success_with_asc_provider(self, run_command_output):
run_command_output.return_value = _make_plist({
'notarization-info': {
'Date': '2019-07-08T20:11:24Z',
'LogFileURL': 'https://example.com/log.json',
'RequestUUID': '0a88b2d8-4098-4d3a-8461-5b543b479d15',
'Status': 'success',
'Status Code': 0
}
})
uuid = '0a88b2d8-4098-4d3a-8461-5b543b479d15'
uuids = [uuid]
self.assertEqual(
uuids,
list(
notarize.wait_for_results(
uuids,
test_config.TestConfig(
notary_asc_provider='[NOTARY-ASC-PROVIDER]'))))
run_command_output.assert_called_once_with([
'xcrun', 'altool', '--notarization-info', uuid, '--username',
'[NOTARY-USER]', '--password', '[NOTARY-PASSWORD]',
'--output-format', 'xml', '--asc-provider', '[NOTARY-ASC-PROVIDER]'
])
@mock.patch('signing.commands.run_command_output') @mock.patch('signing.commands.run_command_output')
def test_failure(self, run_command_output): def test_failure(self, run_command_output):
run_command_output.return_value = _make_plist({ run_command_output.return_value = _make_plist({
......
...@@ -289,7 +289,7 @@ def sign_all(orig_paths, config, package_dmg=True, do_notarization=True): ...@@ -289,7 +289,7 @@ def sign_all(orig_paths, config, package_dmg=True, do_notarization=True):
dist_config.dmg_basename + '.zip') dist_config.dmg_basename + '.zip')
commands.run_command([ commands.run_command([
'zip', '--recurse-paths', '--symlinks', '--quiet', 'zip', '--recurse-paths', '--symlinks', '--quiet',
'--no-dir-entries', zip_file, dist_config.app_dir zip_file, dist_config.app_dir
], ],
cwd=dest_dir) cwd=dest_dir)
uuid = notarize.submit(zip_file, dist_config) uuid = notarize.submit(zip_file, dist_config)
......
...@@ -483,8 +483,7 @@ class TestSignAll(unittest.TestCase): ...@@ -483,8 +483,7 @@ class TestSignAll(unittest.TestCase):
# Prepare the app for notarization. # Prepare the app for notarization.
mock.call.run_command([ mock.call.run_command([
'zip', '--recurse-paths', '--symlinks', '--quiet', 'zip', '--recurse-paths', '--symlinks', '--quiet',
'--no-dir-entries', '$W_1/AppProduct-99.0.9999.99.zip', '$W_1/AppProduct-99.0.9999.99.zip', 'App Product.app'
'App Product.app'
], ],
cwd='$W_1/AppProduct-99.0.9999.99'), cwd='$W_1/AppProduct-99.0.9999.99'),
mock.call.submit('$W_1/AppProduct-99.0.9999.99.zip', mock.ANY), mock.call.submit('$W_1/AppProduct-99.0.9999.99.zip', mock.ANY),
...@@ -531,8 +530,7 @@ class TestSignAll(unittest.TestCase): ...@@ -531,8 +530,7 @@ class TestSignAll(unittest.TestCase):
# Prepare the app for notarization. # Prepare the app for notarization.
mock.call.run_command([ mock.call.run_command([
'zip', '--recurse-paths', '--symlinks', '--quiet', 'zip', '--recurse-paths', '--symlinks', '--quiet',
'--no-dir-entries', '$W_1/AppProduct-99.0.9999.99.zip', '$W_1/AppProduct-99.0.9999.99.zip', 'App Product.app'
'App Product.app'
], ],
cwd='$W_1/AppProduct-99.0.9999.99'), cwd='$W_1/AppProduct-99.0.9999.99'),
mock.call.submit('$W_1/AppProduct-99.0.9999.99.zip', mock.ANY), mock.call.submit('$W_1/AppProduct-99.0.9999.99.zip', mock.ANY),
......
...@@ -10,15 +10,17 @@ THIS_DIR = os.path.abspath(os.path.dirname(__file__)) ...@@ -10,15 +10,17 @@ THIS_DIR = os.path.abspath(os.path.dirname(__file__))
config = imp.load_source('signing.config', os.path.join(THIS_DIR, config = imp.load_source('signing.config', os.path.join(THIS_DIR,
'config.py.in')) 'config.py.in'))
class TestConfig(config.CodeSignConfig): class TestConfig(config.CodeSignConfig):
def __init__(self, def __init__(self,
identity='[IDENTITY]', identity='[IDENTITY]',
keychain='[KEYCHAIN]', keychain='[KEYCHAIN]',
notary_user='[NOTARY-USER]', notary_user='[NOTARY-USER]',
notary_password='[NOTARY-PASSWORD]'): notary_password='[NOTARY-PASSWORD]',
notary_asc_provider=None):
super(TestConfig, self).__init__(identity, keychain, notary_user, super(TestConfig, self).__init__(identity, keychain, notary_user,
notary_password) notary_password, notary_asc_provider)
@property @property
def app_product(self): def app_product(self):
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment