Skip to content

Commit

Permalink
macOS Signing Scripts: Create the invoker module
Browse files Browse the repository at this point in the history
This module provides an Invoker class as an abstraction between the
lower-level commands module and higher-level signing/notarization
operations.

Bug: 1442256
Change-Id: I23c11bc6bd362ece1d789a15f1451fa29c5e0eb1
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/4559027
Reviewed-by: Jamie Walch <jamiewalch@chromium.org>
Reviewed-by: Joshua Pawlicki <waffles@chromium.org>
Commit-Queue: Robert Sesek <rsesek@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1150746}
  • Loading branch information
rsesek authored and Chromium LUCI CQ committed May 30, 2023
1 parent 45dd82e commit aab0c52
Show file tree
Hide file tree
Showing 15 changed files with 698 additions and 428 deletions.
2 changes: 2 additions & 0 deletions chrome/installer/mac/mac_signing_sources.gni
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,12 @@ mac_signing_sources = [
"signing/config.py",
"signing/config_factory.py",
"signing/driver.py",
"signing/invoker.py",
"signing/model.py",
"signing/modification.py",
"signing/notarize.py",
"signing/parts.py",
"signing/pipeline.py",
"signing/signing.py",
"signing/standard_invoker.py",
]
57 changes: 17 additions & 40 deletions chrome/installer/mac/notarize_thing.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,42 +9,14 @@

sys.path.append(os.path.dirname(__file__))

from signing import notarize
from signing import invoker, notarize
from signing.config import CodeSignConfig
from signing.model import NotarizationTool


def main():
parser = argparse.ArgumentParser(
description='Notarize and staple an application binary or archive.')
parser.add_argument(
'--user',
'-u',
required=True,
help='The username to access the Apple notary service.')
parser.add_argument(
'--password',
'-p',
required=True,
help='The password or password reference (e.g. @keychain, see '
'`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(
'--team-id',
help='The Apple Developer Team ID used to authenticate to the Apple '
'notary service.')
parser.add_argument(
'--notarization-tool',
choices=list(NotarizationTool),
type=NotarizationTool,
default=NotarizationTool.ALTOOL,
help='The tool to use to communicate with the Apple notary service.')
parser.add_argument(
'--no-staple',
action='store_true',
Expand All @@ -61,12 +33,9 @@ def main():
help='The file(s) to have notarized. Each file can be a zipped .app '
'bundle, a .dmg, or a .pkg. `xcrun altool -h` for information on '
'supported formats.')
args = parser.parse_args()

if args.notarization_tool == NotarizationTool.NOTARYTOOL:
if not args.team_id:
parser.error('The `--notarization-tool=notarytool` option requires '
'a --team-id.')
notarize.Invoker.register_arguments(parser)
args = parser.parse_args()

config_class = CodeSignConfig
if args.bundle_id:
Expand All @@ -79,14 +48,12 @@ def base_bundle_id(self):

config_class = OverrideBundleIDConfig

if not args.notarization_tool:
args.notarization_tool = NotarizationTool.ALTOOL

config = config_class(
identity='notused',
installer_identity='notused',
notary_user=args.user,
notary_password=args.password,
notary_asc_provider=args.asc_provider,
notary_team_id=args.team_id,
notarization_tool=args.notarization_tool)
invoker=lambda config: NotarizationInvoker(args, config))

uuids = []
for path in args.file:
Expand All @@ -103,5 +70,15 @@ def base_bundle_id(self):
notarize.staple(path)


class NotarizationInvoker(invoker.Interface):

def __init__(self, args, config):
self._notarizer = notarize.Invoker(args, config)

@property
def notarizer(self):
return self._notarizer


if __name__ == '__main__':
main()
75 changes: 30 additions & 45 deletions chrome/installer/mac/signing/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,20 +30,20 @@ class CodeSignConfig(object):
"""

def __init__(self,
invoker=None,
identity=None,
installer_identity=None,
notary_user=None,
notary_password=None,
notary_asc_provider=None,
notary_team_id=None,
codesign_requirements_basic='',
notarization_tool=None,
notarize=NotarizeAndStapleLevel.STAPLE):
"""Creates a CodeSignConfig that will sign the product using the static
properties on the class, using the code signing identity passed to the
constructor.
Args:
invoker: The operation invoker. This may be either an instantaited
|invoker.Interface| or a 1-arg callable that takes this instance
of |CodeSignConfig| and returns an instance of
|invoker.Interface|.
identity: The name of the code signing identity to use for non-PKG
files. This can be any value that `codesign -s <identity>`
accepts, like the hex-encoded SHA1 hash of the certificate. Must
Expand All @@ -54,34 +54,23 @@ def __init__(self,
hash is not a valid option, as it is for |identity| above. The
common name of the cert will work. If there is any distribution
that is packaged in a PKG this must not be None.
notary_user: Optional string username that will be used to
authenticate to Apple's notary service if notarizing.
notary_password: Optional string password or password reference
(e.g. @keychain, see `xcrun altool -h`) that will be used to
authenticate to Apple's notary service if notarizing.
notary_asc_provider: Optional string that will be used as the
`--asc-provider` argument to `xcrun altool`, to be used when
notary_user is associated with multiple Apple developer teams.
codesign_requirements_basic: Optional string to specify the default
basic `codesign --requirements`.
notary_team_id: String for the Apple Team ID to use when notarizing.
Mandatory when using the notarytool `notarization_tool`, ignored
otherwise.
notarization_tool: The tool to use to communicate with the Apple
notary service. If None, the config will choose a default.
notarize: The |model.NotarizeAndStapleLevel|.
"""
assert identity is not None
assert type(identity) is str
assert invoker is not None
self._identity = identity
self._installer_identity = installer_identity
self._notary_user = notary_user
self._notary_password = notary_password
self._notary_asc_provider = notary_asc_provider
self._codesign_requirements_basic = codesign_requirements_basic
self._notary_team_id = notary_team_id
self._notarization_tool = notarization_tool
self._notarize = notarize
if callable(invoker):
# Create a placeholder for the invoker in case the initializer
# accesses the field on the config.
self._invoker = None
invoker = invoker(self)
self._invoker = invoker

@staticmethod
def is_chrome_branded():
Expand All @@ -93,6 +82,12 @@ def is_chrome_branded():
"""
raise ConfigError('is_chrome_branded')

@property
def invoker(self):
"""Returns the |invoker.Interface| instance for signing and notarizing.
"""
return self._invoker

@property
def enable_updater(self):
"""Returns True if the build should use updater-related resources.
Expand All @@ -113,39 +108,27 @@ def installer_identity(self):
"""
return self._installer_identity

@property
def notary_user(self):
"""Returns the username for authenticating to Apple's notary service."""
return self._notary_user

@property
def notary_password(self):
"""Returns the password or password reference for authenticating to
Apple's notary service.
"""
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

# TODO(rsesek): Remove from here and below after internal_config module
# stops needing them.
# vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
@property
def notary_team_id(self):
"""Returns the Apple Developer Team ID for authenticating to Apple's
notary service. Mandatory when notarization_tool is `NOTARYTOOL`.
notary service. Mandatory when notarization_tool is `NOTARYTOOL`. This
is the default team ID if one is not specified on the command line.
"""
return self._notary_team_id
return None

@property
def notarization_tool(self):
"""Returns the name of the tool to use for communicating with Apple's
notary service. The values are from the signing.model.NotarizationTool
enum.
"""
return self._notarization_tool or NotarizationTool.ALTOOL
default = NotarizationTool.ALTOOL
if self.invoker:
return self.invoker.notarizer.notarization_tool or default
return default

@property
def notarization_tool_path(self):
Expand All @@ -155,6 +138,8 @@ def notarization_tool_path(self):
appropriate for the active Config.notarization_tool.
"""
return None
# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
# TODO(rsesek): Remove from here and above.

@property
def notarize(self):
Expand Down
6 changes: 6 additions & 0 deletions chrome/installer/mac/signing/config_factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,9 @@ def get_class():
raise e

return config_class


def get_invoker_class():
"""Returns the subclass of |invoker.Interface| to use."""
from signing.standard_invoker import Invoker
return Invoker
65 changes: 17 additions & 48 deletions chrome/installer/mac/signing/driver.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import argparse
import os

from signing import config_factory, commands, logger, model, pipeline
from signing import config_factory, commands, invoker, logger, model, pipeline


def _create_config(config_args, development):
Expand Down Expand Up @@ -81,30 +81,6 @@ def main(args):
help='The identity to sign everything but PKGs with.')
parser.add_argument(
'--installer-identity', help='The identity to sign PKGs with.')
parser.add_argument(
'--notary-user',
help='The username used to authenticate to the Apple notary service.')
parser.add_argument(
'--notary-password',
help='The password or password reference (e.g. @keychain, see '
'`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(
'--notary-team-id',
help='The Apple Developer Team ID used to authenticate to the Apple '
'notary service.')
parser.add_argument(
'--notarization-tool',
choices=list(model.NotarizationTool),
type=model.NotarizationTool,
default=None,
help='The tool to use to communicate with the Apple notary service.')
parser.add_argument(
'--development',
action='store_true',
Expand Down Expand Up @@ -162,32 +138,25 @@ def main(args):
'has no option specified, that is the equivalent of `--notarize '
'staple`.')

invoker_cls = config_factory.get_invoker_class()
invoker_cls.register_arguments(parser)

args = parser.parse_args(args)

config = _create_config(
model.pick(args, (
'identity',
'installer_identity',
'notary_user',
'notary_password',
'notary_asc_provider',
'notary_team_id',
'notarization_tool',
'notarize',
)), args.development)

if config.notarize.should_notarize():
if not args.notary_user or not args.notary_password:
parser.error('The `--notary-user` and `--notary-password` '
'arguments are required if notarizing.')

if config.notarization_tool == model.NotarizationTool.NOTARYTOOL:
# Let the config override notary_team_id, including a potentially
# unspecified argument.
if not config.notary_team_id:
parser.error('The `--notarization-tool=notarytool` option requires '
'a --notary-team-id.')
config_args = model.pick(args, (
'identity',
'installer_identity',
'notarize',
))

def _create_invoker(config):
try:
return invoker_cls(args, config)
except invoker.InvokerConfigError as e:
parser.error(str(e))

config_args['invoker'] = _create_invoker
config = _create_config(config_args, args.development)
paths = model.Paths(args.input, args.output, None)

if not commands.file_exists(paths.output):
Expand Down
20 changes: 13 additions & 7 deletions chrome/installer/mac/signing/driver_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -162,8 +162,9 @@ def test_notarize_unspecified(self, sign_all, **kwargs):
self.assertEquals(1, sign_all.call_count)
config = sign_all.call_args.args[1]
self.assertEquals(model.NotarizeAndStapleLevel.STAPLE, config.notarize)
self.assertEquals('Notary-User', config.notary_user)
self.assertEquals('@env:NOTARY', config.notary_password)
self.assertEquals('Notary-User', config.invoker.notarizer._notary_user)
self.assertEquals('@env:NOTARY',
config.invoker.notarizer._notary_password)

def test_notarize_specific(self, sign_all, **kwargs):
driver.main([
Expand All @@ -174,8 +175,9 @@ def test_notarize_specific(self, sign_all, **kwargs):
self.assertEquals(1, sign_all.call_count)
config = sign_all.call_args.args[1]
self.assertEquals(model.NotarizeAndStapleLevel.NOWAIT, config.notarize)
self.assertEquals('Notary-User', config.notary_user)
self.assertEquals('@env:NOTARY', config.notary_password)
self.assertEquals('Notary-User', config.invoker.notarizer._notary_user)
self.assertEquals('@env:NOTARY',
config.invoker.notarizer._notary_password)

def test_notarize_missing_args(self, sign_all, **kwargs):
with self.assertRaises(SystemExit):
Expand Down Expand Up @@ -210,8 +212,12 @@ def test_notarize_notarytool(self, sign_all, **kwargs):
self.assertEquals(1, sign_all.call_count)
config = sign_all.call_args.args[1]
self.assertEquals(model.NotarizeAndStapleLevel.STAPLE, config.notarize)
self.assertEquals('Notary-User', config.notary_user)
self.assertEquals('@env:NOTARY', config.notary_password)
self.assertEquals('Team1', config.notary_team_id)
self.assertEquals('Notary-User', config.invoker.notarizer._notary_user)
self.assertEquals('@env:NOTARY',
config.invoker.notarizer._notary_password)
self.assertEquals('Team1',
config.invoker.notarizer._notarizer._notary_team_id)
self.assertEquals(model.NotarizationTool.NOTARYTOOL,
config.notarization_tool)
self.assertEquals(model.NotarizationTool.NOTARYTOOL,
config.invoker.notarizer.notarization_tool)

0 comments on commit aab0c52

Please sign in to comment.