diff --git a/BUILD.gn b/BUILD.gn index 8f4a7266ffac8..38a2fe686e35b 100644 --- a/BUILD.gn +++ b/BUILD.gn @@ -188,8 +188,7 @@ group("create_dist") { ] } else { deps += [ - "build/mac:sign_dmg", - "build/mac:sign_pkg" + "build/mac:notarize_dmg_pkg" ] } } diff --git a/build/mac/BUILD.gn b/build/mac/BUILD.gn index f95d6ab9ccdeb..b2c0fd75044b3 100644 --- a/build/mac/BUILD.gn +++ b/build/mac/BUILD.gn @@ -10,6 +10,11 @@ declare_args() { mac_installer_signing_identifier = "" mac_signing_keychain = "login" mac_signing_output_prefix = "signing" + mac_signed_output_prefix = "signed" + notary_user = "" + notary_password = "" + notarize = false + notarize_argument = "False" } group("brave") {} @@ -103,7 +108,7 @@ if (skip_signing) { } } } else { - packaging_dir = "$root_out_dir/$chrome_product_full_name Packaging" + packaging_dir = "$root_out_dir/" + string_replace("$chrome_product_full_name Packaging", " ", "_") target_sign_app_path = "$root_out_dir/$mac_signing_output_prefix/" + string_replace("$chrome_product_full_name", " ", "") + "-$chrome_version_full/$brave_exe" unsigned_pkg_path = "$root_out_dir/unsigned/$brave_pkg" keychain_db = getenv("HOME") + "/Library/Keychains/${mac_signing_keychain}.keychain-db" @@ -130,6 +135,9 @@ if (skip_signing) { provisioning_profile = "//brave/build/mac/dummy.provisionprofile" is_development = "True" } + if (notarize) { + notarize_argument = "True" + } inputs = [ script, @@ -150,6 +158,9 @@ if (skip_signing) { rebase_path(provisioning_profile, root_out_dir), keychain_db, mac_signing_identifier, + notarize_argument, + notary_user, + notary_password ] } @@ -193,11 +204,11 @@ if (skip_signing) { shell_script, unsigned_pkg_path, ] - outputs = [ "${root_out_dir}/$mac_signing_output_prefix/$brave_pkg" ] + outputs = [ "${root_out_dir}/$mac_signed_output_prefix/$brave_pkg" ] args = [ rebase_path(shell_script, root_build_dir), rebase_path(unsigned_pkg_path), - rebase_path("$root_out_dir/$brave_pkg"), + rebase_path("$root_out_dir/$mac_signed_output_prefix/$brave_pkg"), keychain_db, mac_installer_signing_identifier, ] @@ -220,7 +231,7 @@ if (skip_signing) { "--source", "/var/empty", "--target", "unsigned/$brave_dmg", "--format", "UDBZ", - "--verbosity", "0", + "--verbosity", "2", "--volname", "$chrome_product_full_name", "--copy", rebase_path(target_sign_app_path, root_build_dir), "--tempdir", "/tmp", @@ -242,11 +253,11 @@ if (skip_signing) { shell_script, unsigned_dmg, ] - outputs = [ "${root_out_dir}/$mac_signing_output_prefix/$brave_dmg" ] + outputs = [ "${root_out_dir}/$mac_signed_output_prefix/$brave_dmg" ] args = [ rebase_path(shell_script, root_build_dir), rebase_path(unsigned_dmg), - rebase_path("$root_out_dir/$brave_dmg"), + rebase_path("$root_out_dir/$mac_signed_output_prefix/$brave_dmg"), keychain_db, mac_signing_identifier, "-r=designated => identifier \"$chrome_product_full_name\" and certificate leaf = H\"$mac_signing_identifier\"", @@ -254,4 +265,71 @@ if (skip_signing) { deps = [":create_dmg"] } + + copy("copy_notarize_script") { + + sources = [ "notarize_dmg_pkg.py" ] + + outputs = [ + "$packaging_dir/{{source_file_part}}" + ] + + } + + action("notarize_dmg_pkg") { + if (notarize) { + if (is_official_build) { + development_arg = "" + if (brave_channel == "") { + provisioning_profile = "//brave/build/mac/release.provisionprofile" + } else { + provisioning_profile = "//brave/build/mac/${brave_channel}.provisionprofile" + } + } else { + development_arg = "--development" + provisioning_profile = "//brave/build/mac/dummy.provisionprofile" + } + + script = "$packaging_dir/notarize_dmg_pkg.py" + inputs = [ + script, + "$root_out_dir/$mac_signed_output_prefix/$brave_dmg", + "$root_out_dir/$mac_signed_output_prefix/$brave_pkg", + ] + outputs = [ + "$root_out_dir/$brave_dmg", + "$root_out_dir/$brave_pkg", + ] + args = [ + "--keychain", + keychain_db, + "--identity", + mac_signing_identifier, + "--notary-user", + notary_user, + "--notary-password", + notary_password, + development_arg, + "--dmg", + rebase_path("$root_out_dir/$mac_signed_output_prefix/$brave_dmg"), + "--pkg", + rebase_path("$root_out_dir/$mac_signed_output_prefix/$brave_pkg"), + "--signed", + rebase_path("$root_out_dir/$mac_signed_output_prefix"), + "--outdir", + rebase_path("$root_out_dir"), + "--pkgdir", + rebase_path(packaging_dir), + "--mac_provisioning_profile", + rebase_path(provisioning_profile) + ] + deps = [ + ":sign_dmg", + ":sign_pkg", + ":copy_notarize_script" + ] + } else { + print("Notarization of .dmg and .pkg not requested.") + } + } } diff --git a/build/mac/notarize_dmg_pkg.py b/build/mac/notarize_dmg_pkg.py new file mode 100755 index 0000000000000..92ebfe2994bfa --- /dev/null +++ b/build/mac/notarize_dmg_pkg.py @@ -0,0 +1,184 @@ +#!/usr/bin/env python + +# Copyright (c) 2019 The Brave Authors. All rights reserved. +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this file, +# You can obtain one at http://mozilla.org/MPL/2.0/. */ + +import argparse +import os +import subprocess +import sys + +""" +The sign_chrome.py script does notarization of the app, but we don't use it to +create our .dmg and .pkg files. Instead we do our own signing and packaging, +but must perform notarization after those processes are complete. This script +performs notarizing and stapling of those files. +""" + +# Our CWD is the packaging directory (i.e. +# src/out/Release/Brave_Browser_CHANNEL_Packaging), the signing directory is +# relative to that +packaging_signing_path = os.path.realpath(os.path.dirname(os.path.realpath(__file__))) +sys.path.append(packaging_signing_path) + +# Import the entire module to avoid circular dependencies in the functions +from signing import config, commands, model, notarize, pipeline, signing + + +def run_command(args, **kwargs): + print('Running command: {}'.format(args)) + subprocess.check_call(args, **kwargs) + + +def create_config(config_args, development, mac_provisioning_profile): + """Creates the |model.CodeSignConfig| for the signing operations. + + If |development| is True, the config will be modified to not require + restricted internal assets, nor will the products be required to match + specific certificate hashes. + + Args: + config_args: List of args to expand to the config class's constructor. + development: Boolean indicating whether or not to modify the chosen + config for development testing. + + Returns: + An instance of |model.CodeSignConfig|. + """ + config_class = config.CodeSignConfig + """ + try: + import signing.internal_config + config_class = signing.internal_config.InternalCodeSignConfig + except ImportError as e: + # If the build specified Google Chrome as the product, then the + # internal config has to be available. + if config_class(identity, keychain).product == 'Google Chrome': + raise e + """ + + if development: + + class DevelopmentCodeSignConfig(config_class): + + @property + def codesign_requirements_basic(self): + return '' + + @property + def provisioning_profile_basename(self): + return None + + @property + def run_spctl_assess(self): + return False + + config_class = DevelopmentCodeSignConfig + + config_class = GetBraveSigningConfig(config_class, development, mac_provisioning_profile) + return config_class(*config_args) + + +def GetBraveSigningConfig(config_class, development, mac_provisioning_profile): + if development: + return config_class + + if mac_provisioning_profile: + provisioning_profile = mac_provisioning_profile + assert len(provisioning_profile), 'Argument mac_provisioning_profile not provided!' + + class ProvisioningProfileCodeSignConfig(config_class): + + @property + def provisioning_profile_basename(self): + return os.path.splitext(os.path.basename( + provisioning_profile))[0] + + @property + def run_spctl_assess(self): + return True + + return ProvisioningProfileCodeSignConfig + + +def NotarizeBraveDmgPkg(paths, config, dmg, pkg, outdir, signed, do_notarization=True): + """ + Notarize Brave .dmg and .pkg files. + """ + if do_notarization: + uuids_to_path_map = {} + for dist in config.distributions: + dist_config = dist.to_config(config) + uuid = notarize.submit(dmg, dist_config) + uuids_to_path_map[uuid] = dmg + uuid1 = notarize.submit(pkg, dist_config) + uuids_to_path_map[uuid1] = pkg + for result in notarize.wait_for_results( + uuids_to_path_map.keys(), config): + brave_path = uuids_to_path_map[result] + notarize.staple(brave_path) + for item in uuids_to_path_map.values(): + commands.copy_files(os.path.join(signed, item), outdir) + return 0 + +def main(): + + args = parse_args() + + if args.mac_provisioning_profile and args.development is not True: + config = create_config((args.identity, args.keychain, args.notary_user, + args.notary_password, args.notary_asc_provider), + args.development, args.mac_provisioning_profile) + else: + config = create_config((args.identity, args.keychain, args.notary_user, + args.notary_password, args.notary_asc_provider), + args.development) + paths = model.Paths(args.pkgdir, args.outdir, None) + + rc = NotarizeBraveDmgPkg(paths, config, args.dmg, args.pkg, args.outdir, args.signed) + return rc + +def parse_args(): + parser = argparse.ArgumentParser(description='Notarize Mac DMG and PKG') + parser.add_argument( + '--keychain', help='The keychain to load the identity from.') + parser.add_argument( + '--identity', required=True, help='The identity to sign 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( + '--development', + action='store_true', + help='The specified identity is for development. Certain codesign ' + 'requirements will be omitted.') + parser.add_argument('-d', '--dmg', help='Path to the dmg to notarize', + required=True) + parser.add_argument('-o', '--outdir', help='Output directory', + required=True) + parser.add_argument('--pkgdir', help='Packaging directory', + required=True) + parser.add_argument('-s', '--signed', help='Directory with signed DMG and PKG', + required=True) + parser.add_argument('-p', '--pkg', help='Path to the pkg to notarize', + required=True) + parser.add_argument('--mac_provisioning_profile', help='Provisioning profile(optional)') + return parser.parse_args() + + +if __name__ == '__main__': + import sys + sys.exit(main()) diff --git a/build/mac/sign_app.sh b/build/mac/sign_app.sh index 3f5e11e10abdc..840d4e00f0421 100755 --- a/build/mac/sign_app.sh +++ b/build/mac/sign_app.sh @@ -2,11 +2,17 @@ set -euo pipefail +usage() { + echo "usage: $0 " +} + if [[ ${#} -lt "7" ]]; then - echo "usage: $0 " + usage exit 1 fi +# TODO - ARGUMENT HANDLING SHOULD BE MORE ROBUST + SOURCE_DIR="${1}" DEST_DIR="${2}" PKG_DIR="${3}" @@ -25,6 +31,24 @@ if [[ -z ${7} ]]; then exit 1 fi +if [[ ${#} -gt "7" ]]; then + NOTARIZE="${8}" + if [[ "${NOTARIZE}" = "True" ]]; then + NOTARIZE="--notarize" + else + unset NOTARIZE + fi + NOTARY_USER="${9}" + NOTARY_PASSWORD="${10}" + if [[ -n "${NOTARIZE}" ]]; then + if [[ ( -z "${NOTARY_USER}" ) || ( -z "${NOTARY_PASSWORD}" ) ]]; then + echo "Error: when is True, both and must be provided. Cannot perform notarization." + usage + exit 1 + fi + fi +fi + function check_exit() { return=$?; if [[ $return -eq 0 ]]; then @@ -42,14 +66,8 @@ trap check_exit EXIT # sign_chrome.py export MAC_PROVISIONING_PROFILE -# Clear output directory. It seems GN auto-creates directory path to the -# expected outputs. However, the signing script doesn't expect the path to -# have been created and fails trying to create it again. -echo "Cleaning $DEST_DIR ..." -rm -rf $DEST_DIR/* - # Invoke python script to do the signing. -PARAMS="--input $SOURCE_DIR --output $DEST_DIR --keychain $MAC_SIGNING_KEYCHAIN --identity $MAC_SIGNING_IDENTIFIER --disable-packaging --no-notarize" +PARAMS="--input $SOURCE_DIR --output $DEST_DIR --keychain $MAC_SIGNING_KEYCHAIN --identity $MAC_SIGNING_IDENTIFIER --disable-packaging" if [[ -z "${DEVELOPMENT}" ]]; then # Copy mac_provisioning_profile to the packaging_dir since that's where the # signing scripts expects to find it. @@ -57,4 +75,11 @@ if [[ -z "${DEVELOPMENT}" ]]; then else PARAMS="$PARAMS $DEVELOPMENT" fi + +if [[ -z "${NOTARIZE}" ]]; then + PARAMS="$PARAMS --no-notarize" +else + PARAMS="$PARAMS ${NOTARIZE} --notary-user $NOTARY_USER --notary-password $NOTARY_PASSWORD" +fi + "${PKG_DIR}/sign_chrome.py" $PARAMS diff --git a/build/mac/sign_dmg.sh b/build/mac/sign_dmg.sh index aa81fa075cb40..21a0bb88f6720 100755 --- a/build/mac/sign_dmg.sh +++ b/build/mac/sign_dmg.sh @@ -38,7 +38,7 @@ cp "$SOURCE" "$DEST" set -v -/usr/bin/codesign --force --sign "$MAC_SIGNING_IDENTIFIER" --keychain "$MAC_SIGNING_KEYCHAIN" "$DEST" "$REQUIREMENTS" +/usr/bin/codesign --force --options runtime --timestamp --sign "$MAC_SIGNING_IDENTIFIER" --keychain "$MAC_SIGNING_KEYCHAIN" "$DEST" "$REQUIREMENTS" /usr/bin/codesign -vvvvd "$DEST" /usr/bin/codesign --verify --strict --deep -vvvv "$DEST" diff --git a/patches/chrome-installer-mac-BUILD.gn.patch b/patches/chrome-installer-mac-BUILD.gn.patch new file mode 100644 index 0000000000000..55daf1d9d041c --- /dev/null +++ b/patches/chrome-installer-mac-BUILD.gn.patch @@ -0,0 +1,13 @@ +diff --git a/chrome/installer/mac/BUILD.gn b/chrome/installer/mac/BUILD.gn +index 1425094d2cb2605fbc9ce11d68276471322d5640..23029d1e900dee50cc55fc7de4ac6235a50e3098 100644 +--- a/chrome/installer/mac/BUILD.gn ++++ b/chrome/installer/mac/BUILD.gn +@@ -15,7 +15,7 @@ group("mac") { + ] + } + +-_packaging_dir = "$root_out_dir/$chrome_product_full_name Packaging" ++_packaging_dir = "$root_out_dir/" + string_replace("$chrome_product_full_name Packaging", " ", "_") + + process_version_rc_template("sign_config") { + visibility = [ ":copy_signing" ] diff --git a/patches/chrome-installer-mac-signing-config.py.in.patch b/patches/chrome-installer-mac-signing-config.py.in.patch new file mode 100644 index 0000000000000..967ff7ae66e86 --- /dev/null +++ b/patches/chrome-installer-mac-signing-config.py.in.patch @@ -0,0 +1,10 @@ +diff --git a/chrome/installer/mac/signing/config.py.in b/chrome/installer/mac/signing/config.py.in +index 71e0dedeaaff81489748837fbaa72fcb58fb6edb..02c6b809472584e1e81e4feb9ed418a91d7b0c18 100644 +--- a/chrome/installer/mac/signing/config.py.in ++++ b/chrome/installer/mac/signing/config.py.in +@@ -189,4 +189,4 @@ class CodeSignConfig(object): + @property + def packaging_dir(self): + """Returns the path to the packaging and installer tools.""" +- return '{.product} Packaging'.format(self) ++ return '{}_Packaging'.format(self.product.replace(' ', '_')) diff --git a/patches/chrome-installer-mac-signing-model.py.patch b/patches/chrome-installer-mac-signing-model.py.patch new file mode 100644 index 0000000000000..8b06907e8610a --- /dev/null +++ b/patches/chrome-installer-mac-signing-model.py.patch @@ -0,0 +1,13 @@ +diff --git a/chrome/installer/mac/signing/model.py b/chrome/installer/mac/signing/model.py +index 51077351e901369426b20c33e833e83dde25362f..c566446652be895bb7e7a8be1094e9dee87b91f5 100644 +--- a/chrome/installer/mac/signing/model.py ++++ b/chrome/installer/mac/signing/model.py +@@ -295,7 +295,7 @@ class Paths(object): + Returns: + Path to the packaging directory. + """ +- return os.path.join(self.input, '{} Packaging'.format(config.product)) ++ return os.path.join(self.input, '{}_Packaging'.format(config.product.replace(' ', '_'))) + + def replace_work(self, new_work): + """Creates a new Paths with the same input and output directories, but diff --git a/patches/chrome-installer-mac-signing-pipeline.py.patch b/patches/chrome-installer-mac-signing-pipeline.py.patch index 91acd5261d19a..8365ebd09116e 100644 --- a/patches/chrome-installer-mac-signing-pipeline.py.patch +++ b/patches/chrome-installer-mac-signing-pipeline.py.patch @@ -1,8 +1,21 @@ diff --git a/chrome/installer/mac/signing/pipeline.py b/chrome/installer/mac/signing/pipeline.py -index 898c0d4542bb3474d654e71a1e4fd5a1355e3897..dad0718097522f2346f690ae7435dbe162cbb964 100644 +index 2a05fcefd9343daea2ddd611e898779f3fa12185..025f20f1bc7e8012ff305a0a76befff154985750 100644 --- a/chrome/installer/mac/signing/pipeline.py +++ b/chrome/installer/mac/signing/pipeline.py -@@ -221,7 +221,6 @@ def _package_installer_tools(paths, config): +@@ -190,12 +190,6 @@ def _package_dmg(paths, dist, config): + '--volname', config.app_product, + '--icon', os.path.join(packaging_dir, icon_file), + '--copy', '{}:/'.format(app_path), +- '--copy', +- '{}/keystone_install.sh:/.keystone_install'.format(packaging_dir), +- '--mkdir', '.background', +- '--copy', +- '{}/chrome_dmg_background.png:/.background/background.png'.format( +- packaging_dir), + '--copy', '{}/{}:/.DS_Store'.format(packaging_dir, dsstore_file), + '--symlink', '/Applications:/ ', + ]) +@@ -219,7 +213,6 @@ def _package_installer_tools(paths, config): 'dirdiffer.sh', 'dirpatcher.sh', 'dmgdiffer.sh', @@ -10,3 +23,12 @@ index 898c0d4542bb3474d654e71a1e4fd5a1355e3897..dad0718097522f2346f690ae7435dbe1 'pkg-dmg', ) +@@ -325,5 +318,8 @@ def sign_all(orig_paths, config, package_dmg=True, do_notarization=True): + uuids_to_dmg_path.keys(), config): + dmg_path = uuids_to_dmg_path[result] + notarize.staple(dmg_path) ++ else: ++ # Copy the notarized app to the orig_paths.output dir where the user expects it. ++ commands.copy_files(dest_dir, orig_paths.output) + + _package_installer_tools(orig_paths, config)