Move signing functionality to module.
To optimize the code signing steps we are moving the funtionality to a
module. This new approach allows us to code sign as soon as the
artifacts are generated and within a bcid context to avoid provenance
overriding.
Bug: https://github.com/flutter/flutter/issues/124055
Change-Id: I8eb7f3901bf0915581843c03b18fe59294ca309a
Reviewed-on: https://flutter-review.googlesource.com/c/recipes/+/41522
Reviewed-by: Xilai Zhang <xilaizhang@google.com>
Reviewed-by: Ricardo Amador <ricardoamador@google.com>
Commit-Queue: Godofredo Contreras <godofredoc@google.com>
(cherry picked from commit f67ac6d30498f4603c0355e804e58e39cbd194d2)
Reviewed-on: https://flutter-review.googlesource.com/c/recipes/+/41663
diff --git a/recipe_modules/signing/__init__.py b/recipe_modules/signing/__init__.py
new file mode 100644
index 0000000..0c7164d
--- /dev/null
+++ b/recipe_modules/signing/__init__.py
@@ -0,0 +1,17 @@
+# Copyright 2023 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.
+
+DEPS = [
+ 'flutter/flutter_deps',
+ 'flutter/kms',
+ 'flutter/zip',
+ 'recipe_engine/context',
+ 'recipe_engine/file',
+ 'recipe_engine/futures',
+ 'recipe_engine/path',
+ 'recipe_engine/platform',
+ 'recipe_engine/raw_io',
+ 'recipe_engine/runtime',
+ 'recipe_engine/step',
+]
diff --git a/recipe_modules/signing/api.py b/recipe_modules/signing/api.py
new file mode 100644
index 0000000..71cfc09
--- /dev/null
+++ b/recipe_modules/signing/api.py
@@ -0,0 +1,252 @@
+# Copyright 2023 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 zipfile
+from recipe_engine import recipe_api
+
+
+# File name inside artifacts that require signing with entitlements.
+ENTITLEMENTS_FILENAME = 'entitlements.txt'
+# File name inside artifacts that require signing without entitlements.
+WITHOUT_ENTITLEMENTS_FILENAME = 'without_entitlements.txt'
+
+
+class CodeSignApi(recipe_api.RecipeApi):
+ """Provides utilities to code sign binaries in mac."""
+
+ def __init__(self, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+ self._initialized = False
+ self._codesign_binary_path = None
+
+
+ def requires_signing(self, artifact_path):
+ """Validates if a file needs to be codesigned.
+
+ Args:
+ artifact_path: (str) the path to the zip file artifact
+ to validate if code signing is required.
+
+ Returns:
+ True if code sign is required, False if it is not.
+ """
+ if not self.m.platform.is_mac:
+ return False
+ file_list = self.m.zip.namelist('namelist', artifact_path)
+ return (
+ ENTITLEMENTS_FILENAME in file_list or
+ WITHOUT_ENTITLEMENTS_FILENAME in file_list)
+
+ @property
+ def codesign_binary(self):
+ self._ensure()
+ return self._codesign_binary_path
+
+ def _start(self, env, env_prefixes):
+ self._ensure()
+ self._codesign_environment(env, env_prefixes)
+ self._keychain_setup(env, env_prefixes)
+
+
+ def _stop(self):
+ self._keychain_cleanup()
+
+
+ def _ensure(self):
+ if not self._codesign_binary_path:
+ with self.m.step.nest('Codesign Dependencies'):
+ self._codesign_binary_path = self.m.flutter_deps.codesign({},{})
+
+ def code_sign(self, files_to_sign):
+ if not self.m.platform.is_mac:
+ return
+ env = {}
+ env_prefixes = {}
+ self._start(env, env_prefixes)
+ try:
+ self._signer_tasks(env, env_prefixes, files_to_sign)
+ finally:
+ if not self.m.runtime.in_global_shutdown:
+ self._stop()
+
+
+ def _codesign_environment(self, env, env_prefixes):
+ with self.m.step.nest('Setup codesign environment'):
+ secrets_dict = {
+ 'FLUTTER_P12': 'flutter_p12.encrypted',
+ 'FLUTTER_P12_PASSWORD': 'p12_password.encrypted',
+ 'CODESIGN_TEAM_ID': 'codesign_team_id.encrypted',
+ 'CODESIGN_APP_SPECIFIC_PASSWORD':
+ 'codesign_app_specific_password.encrypted',
+ 'CODESIGN_APP_STORE_ID': 'codesign_app_store_id.encrypted'
+ }
+ self.m.kms.decrypt_secrets(env, secrets_dict)
+
+
+ def _keychain_setup(self, env, env_prefixes):
+ """KeychainSetup adds flutter .p12 to a temporary keychain named 'build'.
+
+ Args:
+ codesign_path (str): path of codesign cipd package.
+ p12_filepath (str) : path of the .p12 file that has flutter credentials.
+ p12_password_raw (str) : the password to decode the .p12 flutter file.
+ """
+ with self.m.step.nest('Setup keychain'):
+ # Delete build.keychain if exists.
+ self.m.step(
+ 'delete previous keychain',
+ ['security', 'delete-keychain', 'build.keychain'],
+ ok_ret='any'
+ )
+ # Create build.keychain.
+ self.m.step(
+ 'create keychain',
+ ['security', 'create-keychain', '-p', '', 'build.keychain']
+ )
+ # Set build.keychain as default.
+ self.m.step(
+ 'default keychain',
+ ['security', 'default-keychain', '-s', 'build.keychain']
+ )
+ # Unlock build.keychain to allow sign commands to use its secrets.
+ self.m.step(
+ 'unlock build keychain',
+ ['security', 'unlock-keychain', '-p', '', 'build.keychain']
+ )
+ # Import flutter's certificate to the keychain.
+ self._import_certificate(env, env_prefixes)
+ # Sets a partition list to identify the app signatures allowed to use the key.
+ self.m.step(
+ 'set key partition list', [
+ 'security', 'set-key-partition-list', '-S',
+ 'apple-tool:,apple:,codesign:', '-s', '-k', '', 'build.keychain'
+ ]
+ )
+ # Grabs existing identities to find out if we are ready to sign with flutter's
+ # identity.
+ show_identities_step = self.m.step(
+ 'show-identities', ['security', 'find-identity', '-v'],
+ ok_ret='any',
+ stdout=self.m.raw_io.output_text(),
+ stderr=self.m.raw_io.output_text()
+ )
+ flutter_identity_name = 'FLUTTER.IO LLC'
+ if flutter_identity_name not in show_identities_step.stdout:
+ raise ValueError(
+ 'identities are %s, does not include flutter identity' %
+ (show_identities_step.stdout)
+ )
+
+ def _import_certificate(self, env, env_prefixes):
+ """Import flutter codesign identity into keychain.
+
+ This function triggers a shell script that supplies p12 password,
+ and grants codesign cipd and system codesign the correct access controls.
+ The p12 password is hidden from stdout.
+
+ Args:
+ env (dict): environment variables.
+ env_prefixes (dict) : environment paths.
+ """
+ resource_name = self.resource('import_certificate.sh')
+ self.m.step(
+ 'Set execute permission',
+ ['chmod', '755', resource_name],
+ infra_step=True,
+ )
+ # Only filepath with a .p12 suffix will be recognized.
+ p12_suffix_filepath = self.m.path['cleanup'].join('flutter.p12')
+ env['P12_SUFFIX_FILEPATH'] = p12_suffix_filepath
+ with self.m.context(env=env, env_prefixes=env_prefixes):
+ self.m.step('import certificate', [resource_name])
+
+
+ def _signer_tasks(self, env, env_prefixes, files_to_sign):
+ """Concurrently creates jobs to codesign each binary.
+
+ Args:
+ codesign_path (str): path of codesign cipd package.
+ env (dict): environment variables.
+ env_prefixes (dict) : environment paths.
+ """
+ signer_builds = []
+ for source_path in files_to_sign:
+ signer_builds.append(
+ self.m.futures.spawn(
+ self._run_signer_tool_command,
+ env,
+ env_prefixes,
+ source_path,
+ )
+ )
+
+ futures = self.m.futures.wait(signer_builds)
+ for future in futures:
+ future.result()
+
+ def _run_signer_tool_command(
+ self,
+ env,
+ env_prefixes,
+ source_path,
+ ):
+ """Runs code sign standalone app.
+
+ Args:
+ source_path (Path): path of the artifact to sign.
+ app_specific_password_filepath (str) : path of app specific password, one of
+ the code sign credentials.
+ appstore_id_filepath (str) : path of apple store id, one of the codesign
+ credentials.
+ team_id_filepath (str) : path of flutter team id used for codesign, one of the
+ codesign credentials.
+ codesign_string_path (str): the absolute path of the codesign standalone app
+ cipd package. This is to differentiate codesign cipd from mac system codesign.
+ """
+ app_specific_password_filepath = env['CODESIGN_APP_SPECIFIC_PASSWORD']
+ appstore_id_filepath = env['CODESIGN_APP_STORE_ID']
+ team_id_filepath = env['CODESIGN_TEAM_ID']
+ path, base_name = self.m.path.split(source_path)
+ unsigned_path = self.m.path.join(path, base_name)
+ self.m.file.move(
+ 'Move %s' % str(source_path),
+ source_path,
+ unsigned_path
+ )
+ with self.m.step.nest('Codesign %s' % str(unsigned_path)):
+ flutter_certificate_name = 'FLUTTER.IO LLC'
+ self.m.step(
+ 'unlock build keychain',
+ ['security', 'unlock-keychain', '-p', '', 'build.keychain']
+ )
+ with self.m.context(env=env, env_prefixes=env_prefixes):
+ self.m.step(
+ 'codesign Apple engine binaries',
+ [
+ self.codesign_binary,
+ '--codesign-cert-name',
+ flutter_certificate_name,
+ '--no-dryrun',
+ '--app-specific-password-file-path',
+ app_specific_password_filepath,
+ '--codesign-appstore-id-file-path',
+ appstore_id_filepath,
+ '--codesign-team-id-file-path',
+ team_id_filepath,
+ '--input-zip-file-path',
+ str(unsigned_path),
+ '--output-zip-file-path',
+ str(source_path),
+ ],
+ )
+
+ def _keychain_cleanup(self):
+ """Clean up temporary keychain used in codesign process."""
+ with self.m.step.nest('Keychain cleanup'):
+ self.m.step('delete keychain', ['security', 'delete-keychain', 'build.keychain'])
+ self.m.step(
+ 'Cleanup keychain.restore default keychain',
+ ['security', 'default-keychain', '-s', 'login.keychain']
+ )
+
diff --git a/recipe_modules/signing/examples/code_sign.expected/mac_require_signing.json b/recipe_modules/signing/examples/code_sign.expected/mac_require_signing.json
new file mode 100644
index 0000000..e07bd77
--- /dev/null
+++ b/recipe_modules/signing/examples/code_sign.expected/mac_require_signing.json
@@ -0,0 +1,556 @@
+[
+ {
+ "cmd": [],
+ "name": "Codesign Dependencies"
+ },
+ {
+ "cmd": [],
+ "name": "Codesign Dependencies.Installing Mac codesign CIPD pkg",
+ "~followup_annotations": [
+ "@@@STEP_NEST_LEVEL@1@@@"
+ ]
+ },
+ {
+ "cmd": [
+ "cipd",
+ "ensure",
+ "-root",
+ "[CLEANUP]/tmp_tmp_1",
+ "-ensure-file",
+ "flutter/codesign/${platform} latest",
+ "-max-threads",
+ "0",
+ "-json-output",
+ "/path/to/tmp/json"
+ ],
+ "name": "Codesign Dependencies.Installing Mac codesign CIPD pkg.ensure_installed",
+ "~followup_annotations": [
+ "@@@STEP_NEST_LEVEL@2@@@",
+ "@@@STEP_LOG_LINE@json.output@{@@@",
+ "@@@STEP_LOG_LINE@json.output@ \"result\": {@@@",
+ "@@@STEP_LOG_LINE@json.output@ \"\": [@@@",
+ "@@@STEP_LOG_LINE@json.output@ {@@@",
+ "@@@STEP_LOG_LINE@json.output@ \"instance_id\": \"resolved-instance_id-of-latest----------\", @@@",
+ "@@@STEP_LOG_LINE@json.output@ \"package\": \"flutter/codesign/resolved-platform\"@@@",
+ "@@@STEP_LOG_LINE@json.output@ }@@@",
+ "@@@STEP_LOG_LINE@json.output@ ]@@@",
+ "@@@STEP_LOG_LINE@json.output@ }@@@",
+ "@@@STEP_LOG_LINE@json.output@}@@@",
+ "@@@STEP_LOG_END@json.output@@@"
+ ]
+ },
+ {
+ "cmd": [],
+ "name": "Setup codesign environment"
+ },
+ {
+ "cmd": [
+ "cipd",
+ "ensure",
+ "-root",
+ "[START_DIR]/cloudkms",
+ "-ensure-file",
+ "infra/tools/luci/cloudkms/${platform} latest",
+ "-max-threads",
+ "0",
+ "-json-output",
+ "/path/to/tmp/json"
+ ],
+ "name": "Setup codesign environment.ensure_installed",
+ "~followup_annotations": [
+ "@@@STEP_NEST_LEVEL@1@@@",
+ "@@@STEP_LOG_LINE@json.output@{@@@",
+ "@@@STEP_LOG_LINE@json.output@ \"result\": {@@@",
+ "@@@STEP_LOG_LINE@json.output@ \"\": [@@@",
+ "@@@STEP_LOG_LINE@json.output@ {@@@",
+ "@@@STEP_LOG_LINE@json.output@ \"instance_id\": \"resolved-instance_id-of-latest----------\", @@@",
+ "@@@STEP_LOG_LINE@json.output@ \"package\": \"infra/tools/luci/cloudkms/resolved-platform\"@@@",
+ "@@@STEP_LOG_LINE@json.output@ }@@@",
+ "@@@STEP_LOG_LINE@json.output@ ]@@@",
+ "@@@STEP_LOG_LINE@json.output@ }@@@",
+ "@@@STEP_LOG_LINE@json.output@}@@@",
+ "@@@STEP_LOG_END@json.output@@@"
+ ]
+ },
+ {
+ "cmd": [
+ "python3",
+ "-u",
+ "RECIPE_MODULE[depot_tools::gsutil]/resources/gsutil_smart_retry.py",
+ "--",
+ "RECIPE_REPO[depot_tools]/gsutil.py",
+ "----",
+ "cp",
+ "gs://flutter_configs/flutter_p12.encrypted",
+ "[CLEANUP]/flutter_p12.encrypted"
+ ],
+ "infra_step": true,
+ "name": "Setup codesign environment.gsutil download",
+ "~followup_annotations": [
+ "@@@STEP_NEST_LEVEL@1@@@"
+ ]
+ },
+ {
+ "cmd": [
+ "[START_DIR]/cloudkms/cloudkms",
+ "decrypt",
+ "-input",
+ "[CLEANUP]/flutter_p12.encrypted",
+ "-output",
+ "[CLEANUP]/FLUTTER_P12",
+ "projects/flutter-infra-staging/locations/global/keyRings/luci/cryptoKeys/flutter-infra"
+ ],
+ "name": "Setup codesign environment.cloudkms get key",
+ "~followup_annotations": [
+ "@@@STEP_NEST_LEVEL@1@@@"
+ ]
+ },
+ {
+ "cmd": [
+ "cipd",
+ "ensure",
+ "-root",
+ "[START_DIR]/cloudkms",
+ "-ensure-file",
+ "infra/tools/luci/cloudkms/${platform} latest",
+ "-max-threads",
+ "0",
+ "-json-output",
+ "/path/to/tmp/json"
+ ],
+ "name": "Setup codesign environment.ensure_installed (2)",
+ "~followup_annotations": [
+ "@@@STEP_NEST_LEVEL@1@@@",
+ "@@@STEP_LOG_LINE@json.output@{@@@",
+ "@@@STEP_LOG_LINE@json.output@ \"result\": {@@@",
+ "@@@STEP_LOG_LINE@json.output@ \"\": [@@@",
+ "@@@STEP_LOG_LINE@json.output@ {@@@",
+ "@@@STEP_LOG_LINE@json.output@ \"instance_id\": \"resolved-instance_id-of-latest----------\", @@@",
+ "@@@STEP_LOG_LINE@json.output@ \"package\": \"infra/tools/luci/cloudkms/resolved-platform\"@@@",
+ "@@@STEP_LOG_LINE@json.output@ }@@@",
+ "@@@STEP_LOG_LINE@json.output@ ]@@@",
+ "@@@STEP_LOG_LINE@json.output@ }@@@",
+ "@@@STEP_LOG_LINE@json.output@}@@@",
+ "@@@STEP_LOG_END@json.output@@@"
+ ]
+ },
+ {
+ "cmd": [
+ "python3",
+ "-u",
+ "RECIPE_MODULE[depot_tools::gsutil]/resources/gsutil_smart_retry.py",
+ "--",
+ "RECIPE_REPO[depot_tools]/gsutil.py",
+ "----",
+ "cp",
+ "gs://flutter_configs/p12_password.encrypted",
+ "[CLEANUP]/p12_password.encrypted"
+ ],
+ "infra_step": true,
+ "name": "Setup codesign environment.gsutil download (2)",
+ "~followup_annotations": [
+ "@@@STEP_NEST_LEVEL@1@@@"
+ ]
+ },
+ {
+ "cmd": [
+ "[START_DIR]/cloudkms/cloudkms",
+ "decrypt",
+ "-input",
+ "[CLEANUP]/p12_password.encrypted",
+ "-output",
+ "[CLEANUP]/FLUTTER_P12_PASSWORD",
+ "projects/flutter-infra-staging/locations/global/keyRings/luci/cryptoKeys/flutter-infra"
+ ],
+ "name": "Setup codesign environment.cloudkms get key (2)",
+ "~followup_annotations": [
+ "@@@STEP_NEST_LEVEL@1@@@"
+ ]
+ },
+ {
+ "cmd": [
+ "cipd",
+ "ensure",
+ "-root",
+ "[START_DIR]/cloudkms",
+ "-ensure-file",
+ "infra/tools/luci/cloudkms/${platform} latest",
+ "-max-threads",
+ "0",
+ "-json-output",
+ "/path/to/tmp/json"
+ ],
+ "name": "Setup codesign environment.ensure_installed (3)",
+ "~followup_annotations": [
+ "@@@STEP_NEST_LEVEL@1@@@",
+ "@@@STEP_LOG_LINE@json.output@{@@@",
+ "@@@STEP_LOG_LINE@json.output@ \"result\": {@@@",
+ "@@@STEP_LOG_LINE@json.output@ \"\": [@@@",
+ "@@@STEP_LOG_LINE@json.output@ {@@@",
+ "@@@STEP_LOG_LINE@json.output@ \"instance_id\": \"resolved-instance_id-of-latest----------\", @@@",
+ "@@@STEP_LOG_LINE@json.output@ \"package\": \"infra/tools/luci/cloudkms/resolved-platform\"@@@",
+ "@@@STEP_LOG_LINE@json.output@ }@@@",
+ "@@@STEP_LOG_LINE@json.output@ ]@@@",
+ "@@@STEP_LOG_LINE@json.output@ }@@@",
+ "@@@STEP_LOG_LINE@json.output@}@@@",
+ "@@@STEP_LOG_END@json.output@@@"
+ ]
+ },
+ {
+ "cmd": [
+ "python3",
+ "-u",
+ "RECIPE_MODULE[depot_tools::gsutil]/resources/gsutil_smart_retry.py",
+ "--",
+ "RECIPE_REPO[depot_tools]/gsutil.py",
+ "----",
+ "cp",
+ "gs://flutter_configs/codesign_team_id.encrypted",
+ "[CLEANUP]/codesign_team_id.encrypted"
+ ],
+ "infra_step": true,
+ "name": "Setup codesign environment.gsutil download (3)",
+ "~followup_annotations": [
+ "@@@STEP_NEST_LEVEL@1@@@"
+ ]
+ },
+ {
+ "cmd": [
+ "[START_DIR]/cloudkms/cloudkms",
+ "decrypt",
+ "-input",
+ "[CLEANUP]/codesign_team_id.encrypted",
+ "-output",
+ "[CLEANUP]/CODESIGN_TEAM_ID",
+ "projects/flutter-infra-staging/locations/global/keyRings/luci/cryptoKeys/flutter-infra"
+ ],
+ "name": "Setup codesign environment.cloudkms get key (3)",
+ "~followup_annotations": [
+ "@@@STEP_NEST_LEVEL@1@@@"
+ ]
+ },
+ {
+ "cmd": [
+ "cipd",
+ "ensure",
+ "-root",
+ "[START_DIR]/cloudkms",
+ "-ensure-file",
+ "infra/tools/luci/cloudkms/${platform} latest",
+ "-max-threads",
+ "0",
+ "-json-output",
+ "/path/to/tmp/json"
+ ],
+ "name": "Setup codesign environment.ensure_installed (4)",
+ "~followup_annotations": [
+ "@@@STEP_NEST_LEVEL@1@@@",
+ "@@@STEP_LOG_LINE@json.output@{@@@",
+ "@@@STEP_LOG_LINE@json.output@ \"result\": {@@@",
+ "@@@STEP_LOG_LINE@json.output@ \"\": [@@@",
+ "@@@STEP_LOG_LINE@json.output@ {@@@",
+ "@@@STEP_LOG_LINE@json.output@ \"instance_id\": \"resolved-instance_id-of-latest----------\", @@@",
+ "@@@STEP_LOG_LINE@json.output@ \"package\": \"infra/tools/luci/cloudkms/resolved-platform\"@@@",
+ "@@@STEP_LOG_LINE@json.output@ }@@@",
+ "@@@STEP_LOG_LINE@json.output@ ]@@@",
+ "@@@STEP_LOG_LINE@json.output@ }@@@",
+ "@@@STEP_LOG_LINE@json.output@}@@@",
+ "@@@STEP_LOG_END@json.output@@@"
+ ]
+ },
+ {
+ "cmd": [
+ "python3",
+ "-u",
+ "RECIPE_MODULE[depot_tools::gsutil]/resources/gsutil_smart_retry.py",
+ "--",
+ "RECIPE_REPO[depot_tools]/gsutil.py",
+ "----",
+ "cp",
+ "gs://flutter_configs/codesign_app_specific_password.encrypted",
+ "[CLEANUP]/codesign_app_specific_password.encrypted"
+ ],
+ "infra_step": true,
+ "name": "Setup codesign environment.gsutil download (4)",
+ "~followup_annotations": [
+ "@@@STEP_NEST_LEVEL@1@@@"
+ ]
+ },
+ {
+ "cmd": [
+ "[START_DIR]/cloudkms/cloudkms",
+ "decrypt",
+ "-input",
+ "[CLEANUP]/codesign_app_specific_password.encrypted",
+ "-output",
+ "[CLEANUP]/CODESIGN_APP_SPECIFIC_PASSWORD",
+ "projects/flutter-infra-staging/locations/global/keyRings/luci/cryptoKeys/flutter-infra"
+ ],
+ "name": "Setup codesign environment.cloudkms get key (4)",
+ "~followup_annotations": [
+ "@@@STEP_NEST_LEVEL@1@@@"
+ ]
+ },
+ {
+ "cmd": [
+ "cipd",
+ "ensure",
+ "-root",
+ "[START_DIR]/cloudkms",
+ "-ensure-file",
+ "infra/tools/luci/cloudkms/${platform} latest",
+ "-max-threads",
+ "0",
+ "-json-output",
+ "/path/to/tmp/json"
+ ],
+ "name": "Setup codesign environment.ensure_installed (5)",
+ "~followup_annotations": [
+ "@@@STEP_NEST_LEVEL@1@@@",
+ "@@@STEP_LOG_LINE@json.output@{@@@",
+ "@@@STEP_LOG_LINE@json.output@ \"result\": {@@@",
+ "@@@STEP_LOG_LINE@json.output@ \"\": [@@@",
+ "@@@STEP_LOG_LINE@json.output@ {@@@",
+ "@@@STEP_LOG_LINE@json.output@ \"instance_id\": \"resolved-instance_id-of-latest----------\", @@@",
+ "@@@STEP_LOG_LINE@json.output@ \"package\": \"infra/tools/luci/cloudkms/resolved-platform\"@@@",
+ "@@@STEP_LOG_LINE@json.output@ }@@@",
+ "@@@STEP_LOG_LINE@json.output@ ]@@@",
+ "@@@STEP_LOG_LINE@json.output@ }@@@",
+ "@@@STEP_LOG_LINE@json.output@}@@@",
+ "@@@STEP_LOG_END@json.output@@@"
+ ]
+ },
+ {
+ "cmd": [
+ "python3",
+ "-u",
+ "RECIPE_MODULE[depot_tools::gsutil]/resources/gsutil_smart_retry.py",
+ "--",
+ "RECIPE_REPO[depot_tools]/gsutil.py",
+ "----",
+ "cp",
+ "gs://flutter_configs/codesign_app_store_id.encrypted",
+ "[CLEANUP]/codesign_app_store_id.encrypted"
+ ],
+ "infra_step": true,
+ "name": "Setup codesign environment.gsutil download (5)",
+ "~followup_annotations": [
+ "@@@STEP_NEST_LEVEL@1@@@"
+ ]
+ },
+ {
+ "cmd": [
+ "[START_DIR]/cloudkms/cloudkms",
+ "decrypt",
+ "-input",
+ "[CLEANUP]/codesign_app_store_id.encrypted",
+ "-output",
+ "[CLEANUP]/CODESIGN_APP_STORE_ID",
+ "projects/flutter-infra-staging/locations/global/keyRings/luci/cryptoKeys/flutter-infra"
+ ],
+ "name": "Setup codesign environment.cloudkms get key (5)",
+ "~followup_annotations": [
+ "@@@STEP_NEST_LEVEL@1@@@"
+ ]
+ },
+ {
+ "cmd": [],
+ "name": "Setup keychain"
+ },
+ {
+ "cmd": [
+ "security",
+ "delete-keychain",
+ "build.keychain"
+ ],
+ "name": "Setup keychain.delete previous keychain",
+ "~followup_annotations": [
+ "@@@STEP_NEST_LEVEL@1@@@"
+ ]
+ },
+ {
+ "cmd": [
+ "security",
+ "create-keychain",
+ "-p",
+ "",
+ "build.keychain"
+ ],
+ "name": "Setup keychain.create keychain",
+ "~followup_annotations": [
+ "@@@STEP_NEST_LEVEL@1@@@"
+ ]
+ },
+ {
+ "cmd": [
+ "security",
+ "default-keychain",
+ "-s",
+ "build.keychain"
+ ],
+ "name": "Setup keychain.default keychain",
+ "~followup_annotations": [
+ "@@@STEP_NEST_LEVEL@1@@@"
+ ]
+ },
+ {
+ "cmd": [
+ "security",
+ "unlock-keychain",
+ "-p",
+ "",
+ "build.keychain"
+ ],
+ "name": "Setup keychain.unlock build keychain",
+ "~followup_annotations": [
+ "@@@STEP_NEST_LEVEL@1@@@"
+ ]
+ },
+ {
+ "cmd": [
+ "chmod",
+ "755",
+ "RECIPE_MODULE[flutter::signing]/resources/import_certificate.sh"
+ ],
+ "infra_step": true,
+ "name": "Setup keychain.Set execute permission",
+ "~followup_annotations": [
+ "@@@STEP_NEST_LEVEL@1@@@"
+ ]
+ },
+ {
+ "cmd": [
+ "RECIPE_MODULE[flutter::signing]/resources/import_certificate.sh"
+ ],
+ "env": {
+ "CODESIGN_APP_SPECIFIC_PASSWORD": "[CLEANUP]/CODESIGN_APP_SPECIFIC_PASSWORD",
+ "CODESIGN_APP_STORE_ID": "[CLEANUP]/CODESIGN_APP_STORE_ID",
+ "CODESIGN_TEAM_ID": "[CLEANUP]/CODESIGN_TEAM_ID",
+ "FLUTTER_P12": "[CLEANUP]/FLUTTER_P12",
+ "FLUTTER_P12_PASSWORD": "[CLEANUP]/FLUTTER_P12_PASSWORD",
+ "P12_SUFFIX_FILEPATH": "[CLEANUP]/flutter.p12"
+ },
+ "name": "Setup keychain.import certificate",
+ "~followup_annotations": [
+ "@@@STEP_NEST_LEVEL@1@@@"
+ ]
+ },
+ {
+ "cmd": [
+ "security",
+ "set-key-partition-list",
+ "-S",
+ "apple-tool:,apple:,codesign:",
+ "-s",
+ "-k",
+ "",
+ "build.keychain"
+ ],
+ "name": "Setup keychain.set key partition list",
+ "~followup_annotations": [
+ "@@@STEP_NEST_LEVEL@1@@@"
+ ]
+ },
+ {
+ "cmd": [
+ "security",
+ "find-identity",
+ "-v"
+ ],
+ "name": "Setup keychain.show-identities",
+ "~followup_annotations": [
+ "@@@STEP_NEST_LEVEL@1@@@"
+ ]
+ },
+ {
+ "cmd": [
+ "vpython3",
+ "-u",
+ "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+ "--json-output",
+ "/path/to/tmp/json",
+ "move",
+ "file1.zip",
+ "file1.zip"
+ ],
+ "infra_step": true,
+ "name": "Move file1.zip"
+ },
+ {
+ "cmd": [],
+ "name": "Codesign file1.zip"
+ },
+ {
+ "cmd": [
+ "security",
+ "unlock-keychain",
+ "-p",
+ "",
+ "build.keychain"
+ ],
+ "name": "Codesign file1.zip.unlock build keychain",
+ "~followup_annotations": [
+ "@@@STEP_NEST_LEVEL@1@@@"
+ ]
+ },
+ {
+ "cmd": [
+ "[CLEANUP]/tmp_tmp_1/codesign",
+ "--codesign-cert-name",
+ "FLUTTER.IO LLC",
+ "--no-dryrun",
+ "--app-specific-password-file-path",
+ "[CLEANUP]/CODESIGN_APP_SPECIFIC_PASSWORD",
+ "--codesign-appstore-id-file-path",
+ "[CLEANUP]/CODESIGN_APP_STORE_ID",
+ "--codesign-team-id-file-path",
+ "[CLEANUP]/CODESIGN_TEAM_ID",
+ "--input-zip-file-path",
+ "file1.zip",
+ "--output-zip-file-path",
+ "file1.zip"
+ ],
+ "env": {
+ "CODESIGN_APP_SPECIFIC_PASSWORD": "[CLEANUP]/CODESIGN_APP_SPECIFIC_PASSWORD",
+ "CODESIGN_APP_STORE_ID": "[CLEANUP]/CODESIGN_APP_STORE_ID",
+ "CODESIGN_TEAM_ID": "[CLEANUP]/CODESIGN_TEAM_ID",
+ "FLUTTER_P12": "[CLEANUP]/FLUTTER_P12",
+ "FLUTTER_P12_PASSWORD": "[CLEANUP]/FLUTTER_P12_PASSWORD",
+ "P12_SUFFIX_FILEPATH": "[CLEANUP]/flutter.p12"
+ },
+ "name": "Codesign file1.zip.codesign Apple engine binaries",
+ "~followup_annotations": [
+ "@@@STEP_NEST_LEVEL@1@@@"
+ ]
+ },
+ {
+ "cmd": [],
+ "name": "Keychain cleanup"
+ },
+ {
+ "cmd": [
+ "security",
+ "delete-keychain",
+ "build.keychain"
+ ],
+ "name": "Keychain cleanup.delete keychain",
+ "~followup_annotations": [
+ "@@@STEP_NEST_LEVEL@1@@@"
+ ]
+ },
+ {
+ "cmd": [
+ "security",
+ "default-keychain",
+ "-s",
+ "login.keychain"
+ ],
+ "name": "Keychain cleanup.Cleanup keychain.restore default keychain",
+ "~followup_annotations": [
+ "@@@STEP_NEST_LEVEL@1@@@"
+ ]
+ },
+ {
+ "name": "$result"
+ }
+]
\ No newline at end of file
diff --git a/recipe_modules/signing/examples/code_sign.expected/no_signing_identity.json b/recipe_modules/signing/examples/code_sign.expected/no_signing_identity.json
new file mode 100644
index 0000000..186f496
--- /dev/null
+++ b/recipe_modules/signing/examples/code_sign.expected/no_signing_identity.json
@@ -0,0 +1,471 @@
+[
+ {
+ "cmd": [],
+ "name": "Codesign Dependencies"
+ },
+ {
+ "cmd": [],
+ "name": "Codesign Dependencies.Installing Mac codesign CIPD pkg",
+ "~followup_annotations": [
+ "@@@STEP_NEST_LEVEL@1@@@"
+ ]
+ },
+ {
+ "cmd": [
+ "cipd",
+ "ensure",
+ "-root",
+ "[CLEANUP]/tmp_tmp_1",
+ "-ensure-file",
+ "flutter/codesign/${platform} latest",
+ "-max-threads",
+ "0",
+ "-json-output",
+ "/path/to/tmp/json"
+ ],
+ "name": "Codesign Dependencies.Installing Mac codesign CIPD pkg.ensure_installed",
+ "~followup_annotations": [
+ "@@@STEP_NEST_LEVEL@2@@@",
+ "@@@STEP_LOG_LINE@json.output@{@@@",
+ "@@@STEP_LOG_LINE@json.output@ \"result\": {@@@",
+ "@@@STEP_LOG_LINE@json.output@ \"\": [@@@",
+ "@@@STEP_LOG_LINE@json.output@ {@@@",
+ "@@@STEP_LOG_LINE@json.output@ \"instance_id\": \"resolved-instance_id-of-latest----------\", @@@",
+ "@@@STEP_LOG_LINE@json.output@ \"package\": \"flutter/codesign/resolved-platform\"@@@",
+ "@@@STEP_LOG_LINE@json.output@ }@@@",
+ "@@@STEP_LOG_LINE@json.output@ ]@@@",
+ "@@@STEP_LOG_LINE@json.output@ }@@@",
+ "@@@STEP_LOG_LINE@json.output@}@@@",
+ "@@@STEP_LOG_END@json.output@@@"
+ ]
+ },
+ {
+ "cmd": [],
+ "name": "Setup codesign environment"
+ },
+ {
+ "cmd": [
+ "cipd",
+ "ensure",
+ "-root",
+ "[START_DIR]/cloudkms",
+ "-ensure-file",
+ "infra/tools/luci/cloudkms/${platform} latest",
+ "-max-threads",
+ "0",
+ "-json-output",
+ "/path/to/tmp/json"
+ ],
+ "name": "Setup codesign environment.ensure_installed",
+ "~followup_annotations": [
+ "@@@STEP_NEST_LEVEL@1@@@",
+ "@@@STEP_LOG_LINE@json.output@{@@@",
+ "@@@STEP_LOG_LINE@json.output@ \"result\": {@@@",
+ "@@@STEP_LOG_LINE@json.output@ \"\": [@@@",
+ "@@@STEP_LOG_LINE@json.output@ {@@@",
+ "@@@STEP_LOG_LINE@json.output@ \"instance_id\": \"resolved-instance_id-of-latest----------\", @@@",
+ "@@@STEP_LOG_LINE@json.output@ \"package\": \"infra/tools/luci/cloudkms/resolved-platform\"@@@",
+ "@@@STEP_LOG_LINE@json.output@ }@@@",
+ "@@@STEP_LOG_LINE@json.output@ ]@@@",
+ "@@@STEP_LOG_LINE@json.output@ }@@@",
+ "@@@STEP_LOG_LINE@json.output@}@@@",
+ "@@@STEP_LOG_END@json.output@@@"
+ ]
+ },
+ {
+ "cmd": [
+ "python3",
+ "-u",
+ "RECIPE_MODULE[depot_tools::gsutil]/resources/gsutil_smart_retry.py",
+ "--",
+ "RECIPE_REPO[depot_tools]/gsutil.py",
+ "----",
+ "cp",
+ "gs://flutter_configs/flutter_p12.encrypted",
+ "[CLEANUP]/flutter_p12.encrypted"
+ ],
+ "infra_step": true,
+ "name": "Setup codesign environment.gsutil download",
+ "~followup_annotations": [
+ "@@@STEP_NEST_LEVEL@1@@@"
+ ]
+ },
+ {
+ "cmd": [
+ "[START_DIR]/cloudkms/cloudkms",
+ "decrypt",
+ "-input",
+ "[CLEANUP]/flutter_p12.encrypted",
+ "-output",
+ "[CLEANUP]/FLUTTER_P12",
+ "projects/flutter-infra-staging/locations/global/keyRings/luci/cryptoKeys/flutter-infra"
+ ],
+ "name": "Setup codesign environment.cloudkms get key",
+ "~followup_annotations": [
+ "@@@STEP_NEST_LEVEL@1@@@"
+ ]
+ },
+ {
+ "cmd": [
+ "cipd",
+ "ensure",
+ "-root",
+ "[START_DIR]/cloudkms",
+ "-ensure-file",
+ "infra/tools/luci/cloudkms/${platform} latest",
+ "-max-threads",
+ "0",
+ "-json-output",
+ "/path/to/tmp/json"
+ ],
+ "name": "Setup codesign environment.ensure_installed (2)",
+ "~followup_annotations": [
+ "@@@STEP_NEST_LEVEL@1@@@",
+ "@@@STEP_LOG_LINE@json.output@{@@@",
+ "@@@STEP_LOG_LINE@json.output@ \"result\": {@@@",
+ "@@@STEP_LOG_LINE@json.output@ \"\": [@@@",
+ "@@@STEP_LOG_LINE@json.output@ {@@@",
+ "@@@STEP_LOG_LINE@json.output@ \"instance_id\": \"resolved-instance_id-of-latest----------\", @@@",
+ "@@@STEP_LOG_LINE@json.output@ \"package\": \"infra/tools/luci/cloudkms/resolved-platform\"@@@",
+ "@@@STEP_LOG_LINE@json.output@ }@@@",
+ "@@@STEP_LOG_LINE@json.output@ ]@@@",
+ "@@@STEP_LOG_LINE@json.output@ }@@@",
+ "@@@STEP_LOG_LINE@json.output@}@@@",
+ "@@@STEP_LOG_END@json.output@@@"
+ ]
+ },
+ {
+ "cmd": [
+ "python3",
+ "-u",
+ "RECIPE_MODULE[depot_tools::gsutil]/resources/gsutil_smart_retry.py",
+ "--",
+ "RECIPE_REPO[depot_tools]/gsutil.py",
+ "----",
+ "cp",
+ "gs://flutter_configs/p12_password.encrypted",
+ "[CLEANUP]/p12_password.encrypted"
+ ],
+ "infra_step": true,
+ "name": "Setup codesign environment.gsutil download (2)",
+ "~followup_annotations": [
+ "@@@STEP_NEST_LEVEL@1@@@"
+ ]
+ },
+ {
+ "cmd": [
+ "[START_DIR]/cloudkms/cloudkms",
+ "decrypt",
+ "-input",
+ "[CLEANUP]/p12_password.encrypted",
+ "-output",
+ "[CLEANUP]/FLUTTER_P12_PASSWORD",
+ "projects/flutter-infra-staging/locations/global/keyRings/luci/cryptoKeys/flutter-infra"
+ ],
+ "name": "Setup codesign environment.cloudkms get key (2)",
+ "~followup_annotations": [
+ "@@@STEP_NEST_LEVEL@1@@@"
+ ]
+ },
+ {
+ "cmd": [
+ "cipd",
+ "ensure",
+ "-root",
+ "[START_DIR]/cloudkms",
+ "-ensure-file",
+ "infra/tools/luci/cloudkms/${platform} latest",
+ "-max-threads",
+ "0",
+ "-json-output",
+ "/path/to/tmp/json"
+ ],
+ "name": "Setup codesign environment.ensure_installed (3)",
+ "~followup_annotations": [
+ "@@@STEP_NEST_LEVEL@1@@@",
+ "@@@STEP_LOG_LINE@json.output@{@@@",
+ "@@@STEP_LOG_LINE@json.output@ \"result\": {@@@",
+ "@@@STEP_LOG_LINE@json.output@ \"\": [@@@",
+ "@@@STEP_LOG_LINE@json.output@ {@@@",
+ "@@@STEP_LOG_LINE@json.output@ \"instance_id\": \"resolved-instance_id-of-latest----------\", @@@",
+ "@@@STEP_LOG_LINE@json.output@ \"package\": \"infra/tools/luci/cloudkms/resolved-platform\"@@@",
+ "@@@STEP_LOG_LINE@json.output@ }@@@",
+ "@@@STEP_LOG_LINE@json.output@ ]@@@",
+ "@@@STEP_LOG_LINE@json.output@ }@@@",
+ "@@@STEP_LOG_LINE@json.output@}@@@",
+ "@@@STEP_LOG_END@json.output@@@"
+ ]
+ },
+ {
+ "cmd": [
+ "python3",
+ "-u",
+ "RECIPE_MODULE[depot_tools::gsutil]/resources/gsutil_smart_retry.py",
+ "--",
+ "RECIPE_REPO[depot_tools]/gsutil.py",
+ "----",
+ "cp",
+ "gs://flutter_configs/codesign_team_id.encrypted",
+ "[CLEANUP]/codesign_team_id.encrypted"
+ ],
+ "infra_step": true,
+ "name": "Setup codesign environment.gsutil download (3)",
+ "~followup_annotations": [
+ "@@@STEP_NEST_LEVEL@1@@@"
+ ]
+ },
+ {
+ "cmd": [
+ "[START_DIR]/cloudkms/cloudkms",
+ "decrypt",
+ "-input",
+ "[CLEANUP]/codesign_team_id.encrypted",
+ "-output",
+ "[CLEANUP]/CODESIGN_TEAM_ID",
+ "projects/flutter-infra-staging/locations/global/keyRings/luci/cryptoKeys/flutter-infra"
+ ],
+ "name": "Setup codesign environment.cloudkms get key (3)",
+ "~followup_annotations": [
+ "@@@STEP_NEST_LEVEL@1@@@"
+ ]
+ },
+ {
+ "cmd": [
+ "cipd",
+ "ensure",
+ "-root",
+ "[START_DIR]/cloudkms",
+ "-ensure-file",
+ "infra/tools/luci/cloudkms/${platform} latest",
+ "-max-threads",
+ "0",
+ "-json-output",
+ "/path/to/tmp/json"
+ ],
+ "name": "Setup codesign environment.ensure_installed (4)",
+ "~followup_annotations": [
+ "@@@STEP_NEST_LEVEL@1@@@",
+ "@@@STEP_LOG_LINE@json.output@{@@@",
+ "@@@STEP_LOG_LINE@json.output@ \"result\": {@@@",
+ "@@@STEP_LOG_LINE@json.output@ \"\": [@@@",
+ "@@@STEP_LOG_LINE@json.output@ {@@@",
+ "@@@STEP_LOG_LINE@json.output@ \"instance_id\": \"resolved-instance_id-of-latest----------\", @@@",
+ "@@@STEP_LOG_LINE@json.output@ \"package\": \"infra/tools/luci/cloudkms/resolved-platform\"@@@",
+ "@@@STEP_LOG_LINE@json.output@ }@@@",
+ "@@@STEP_LOG_LINE@json.output@ ]@@@",
+ "@@@STEP_LOG_LINE@json.output@ }@@@",
+ "@@@STEP_LOG_LINE@json.output@}@@@",
+ "@@@STEP_LOG_END@json.output@@@"
+ ]
+ },
+ {
+ "cmd": [
+ "python3",
+ "-u",
+ "RECIPE_MODULE[depot_tools::gsutil]/resources/gsutil_smart_retry.py",
+ "--",
+ "RECIPE_REPO[depot_tools]/gsutil.py",
+ "----",
+ "cp",
+ "gs://flutter_configs/codesign_app_specific_password.encrypted",
+ "[CLEANUP]/codesign_app_specific_password.encrypted"
+ ],
+ "infra_step": true,
+ "name": "Setup codesign environment.gsutil download (4)",
+ "~followup_annotations": [
+ "@@@STEP_NEST_LEVEL@1@@@"
+ ]
+ },
+ {
+ "cmd": [
+ "[START_DIR]/cloudkms/cloudkms",
+ "decrypt",
+ "-input",
+ "[CLEANUP]/codesign_app_specific_password.encrypted",
+ "-output",
+ "[CLEANUP]/CODESIGN_APP_SPECIFIC_PASSWORD",
+ "projects/flutter-infra-staging/locations/global/keyRings/luci/cryptoKeys/flutter-infra"
+ ],
+ "name": "Setup codesign environment.cloudkms get key (4)",
+ "~followup_annotations": [
+ "@@@STEP_NEST_LEVEL@1@@@"
+ ]
+ },
+ {
+ "cmd": [
+ "cipd",
+ "ensure",
+ "-root",
+ "[START_DIR]/cloudkms",
+ "-ensure-file",
+ "infra/tools/luci/cloudkms/${platform} latest",
+ "-max-threads",
+ "0",
+ "-json-output",
+ "/path/to/tmp/json"
+ ],
+ "name": "Setup codesign environment.ensure_installed (5)",
+ "~followup_annotations": [
+ "@@@STEP_NEST_LEVEL@1@@@",
+ "@@@STEP_LOG_LINE@json.output@{@@@",
+ "@@@STEP_LOG_LINE@json.output@ \"result\": {@@@",
+ "@@@STEP_LOG_LINE@json.output@ \"\": [@@@",
+ "@@@STEP_LOG_LINE@json.output@ {@@@",
+ "@@@STEP_LOG_LINE@json.output@ \"instance_id\": \"resolved-instance_id-of-latest----------\", @@@",
+ "@@@STEP_LOG_LINE@json.output@ \"package\": \"infra/tools/luci/cloudkms/resolved-platform\"@@@",
+ "@@@STEP_LOG_LINE@json.output@ }@@@",
+ "@@@STEP_LOG_LINE@json.output@ ]@@@",
+ "@@@STEP_LOG_LINE@json.output@ }@@@",
+ "@@@STEP_LOG_LINE@json.output@}@@@",
+ "@@@STEP_LOG_END@json.output@@@"
+ ]
+ },
+ {
+ "cmd": [
+ "python3",
+ "-u",
+ "RECIPE_MODULE[depot_tools::gsutil]/resources/gsutil_smart_retry.py",
+ "--",
+ "RECIPE_REPO[depot_tools]/gsutil.py",
+ "----",
+ "cp",
+ "gs://flutter_configs/codesign_app_store_id.encrypted",
+ "[CLEANUP]/codesign_app_store_id.encrypted"
+ ],
+ "infra_step": true,
+ "name": "Setup codesign environment.gsutil download (5)",
+ "~followup_annotations": [
+ "@@@STEP_NEST_LEVEL@1@@@"
+ ]
+ },
+ {
+ "cmd": [
+ "[START_DIR]/cloudkms/cloudkms",
+ "decrypt",
+ "-input",
+ "[CLEANUP]/codesign_app_store_id.encrypted",
+ "-output",
+ "[CLEANUP]/CODESIGN_APP_STORE_ID",
+ "projects/flutter-infra-staging/locations/global/keyRings/luci/cryptoKeys/flutter-infra"
+ ],
+ "name": "Setup codesign environment.cloudkms get key (5)",
+ "~followup_annotations": [
+ "@@@STEP_NEST_LEVEL@1@@@"
+ ]
+ },
+ {
+ "cmd": [],
+ "name": "Setup keychain",
+ "~followup_annotations": [
+ "@@@STEP_EXCEPTION@@@"
+ ]
+ },
+ {
+ "cmd": [
+ "security",
+ "delete-keychain",
+ "build.keychain"
+ ],
+ "name": "Setup keychain.delete previous keychain",
+ "~followup_annotations": [
+ "@@@STEP_NEST_LEVEL@1@@@"
+ ]
+ },
+ {
+ "cmd": [
+ "security",
+ "create-keychain",
+ "-p",
+ "",
+ "build.keychain"
+ ],
+ "name": "Setup keychain.create keychain",
+ "~followup_annotations": [
+ "@@@STEP_NEST_LEVEL@1@@@"
+ ]
+ },
+ {
+ "cmd": [
+ "security",
+ "default-keychain",
+ "-s",
+ "build.keychain"
+ ],
+ "name": "Setup keychain.default keychain",
+ "~followup_annotations": [
+ "@@@STEP_NEST_LEVEL@1@@@"
+ ]
+ },
+ {
+ "cmd": [
+ "security",
+ "unlock-keychain",
+ "-p",
+ "",
+ "build.keychain"
+ ],
+ "name": "Setup keychain.unlock build keychain",
+ "~followup_annotations": [
+ "@@@STEP_NEST_LEVEL@1@@@"
+ ]
+ },
+ {
+ "cmd": [
+ "chmod",
+ "755",
+ "RECIPE_MODULE[flutter::signing]/resources/import_certificate.sh"
+ ],
+ "infra_step": true,
+ "name": "Setup keychain.Set execute permission",
+ "~followup_annotations": [
+ "@@@STEP_NEST_LEVEL@1@@@"
+ ]
+ },
+ {
+ "cmd": [
+ "RECIPE_MODULE[flutter::signing]/resources/import_certificate.sh"
+ ],
+ "env": {
+ "CODESIGN_APP_SPECIFIC_PASSWORD": "[CLEANUP]/CODESIGN_APP_SPECIFIC_PASSWORD",
+ "CODESIGN_APP_STORE_ID": "[CLEANUP]/CODESIGN_APP_STORE_ID",
+ "CODESIGN_TEAM_ID": "[CLEANUP]/CODESIGN_TEAM_ID",
+ "FLUTTER_P12": "[CLEANUP]/FLUTTER_P12",
+ "FLUTTER_P12_PASSWORD": "[CLEANUP]/FLUTTER_P12_PASSWORD",
+ "P12_SUFFIX_FILEPATH": "[CLEANUP]/flutter.p12"
+ },
+ "name": "Setup keychain.import certificate",
+ "~followup_annotations": [
+ "@@@STEP_NEST_LEVEL@1@@@"
+ ]
+ },
+ {
+ "cmd": [
+ "security",
+ "set-key-partition-list",
+ "-S",
+ "apple-tool:,apple:,codesign:",
+ "-s",
+ "-k",
+ "",
+ "build.keychain"
+ ],
+ "name": "Setup keychain.set key partition list",
+ "~followup_annotations": [
+ "@@@STEP_NEST_LEVEL@1@@@"
+ ]
+ },
+ {
+ "cmd": [
+ "security",
+ "find-identity",
+ "-v"
+ ],
+ "name": "Setup keychain.show-identities",
+ "~followup_annotations": [
+ "@@@STEP_NEST_LEVEL@1@@@"
+ ]
+ },
+ {
+ "name": "$result"
+ }
+]
\ No newline at end of file
diff --git a/recipe_modules/signing/examples/code_sign.expected/non_mac.json b/recipe_modules/signing/examples/code_sign.expected/non_mac.json
new file mode 100644
index 0000000..b6042b6
--- /dev/null
+++ b/recipe_modules/signing/examples/code_sign.expected/non_mac.json
@@ -0,0 +1,5 @@
+[
+ {
+ "name": "$result"
+ }
+]
\ No newline at end of file
diff --git a/recipe_modules/signing/examples/code_sign.py b/recipe_modules/signing/examples/code_sign.py
new file mode 100644
index 0000000..8b4370c
--- /dev/null
+++ b/recipe_modules/signing/examples/code_sign.py
@@ -0,0 +1,48 @@
+# Copyright 2023 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 contextlib
+from recipe_engine.post_process import (Filter)
+
+DEPS = [
+ 'flutter/signing',
+ 'recipe_engine/assertions',
+ 'recipe_engine/platform',
+ 'recipe_engine/properties',
+ 'recipe_engine/raw_io',
+]
+
+
+def RunSteps(api):
+ env = {}
+ env_prefixes = {}
+ files_to_sign = ['file1.zip']
+ if api.properties.get('raises'):
+ with api.assertions.assertRaises(ValueError):
+ api.signing.code_sign(
+ files_to_sign=files_to_sign,
+ )
+ else:
+ api.signing.code_sign(
+ files_to_sign=files_to_sign,
+ )
+
+
+def GenTests(api):
+ yield api.test(
+ 'non_mac',
+ api.platform.name('linux'),
+ api.properties(expected_result=False)
+ )
+ yield api.test(
+ 'mac_require_signing',
+ api.platform.name('mac'),
+ api.properties(expected_result=True),
+ api.signing.flutter_signing_identity(),
+ )
+ yield api.test(
+ 'no_signing_identity',
+ api.platform.name('mac'),
+ api.properties(expected_result=False, raises=True),
+ )
diff --git a/recipe_modules/signing/examples/requires_signing.expected/mac_does_not_require_signing.json b/recipe_modules/signing/examples/requires_signing.expected/mac_does_not_require_signing.json
new file mode 100644
index 0000000..02e3920
--- /dev/null
+++ b/recipe_modules/signing/examples/requires_signing.expected/mac_does_not_require_signing.json
@@ -0,0 +1,69 @@
+[
+ {
+ "cmd": [],
+ "name": "Create test file"
+ },
+ {
+ "cmd": [
+ "vpython3",
+ "-u",
+ "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+ "--json-output",
+ "/path/to/tmp/json",
+ "copy",
+ "myfile",
+ "[CLEANUP]/tmp_tmp_1/content/myfile.txt"
+ ],
+ "infra_step": true,
+ "name": "Create test file.write file",
+ "~followup_annotations": [
+ "@@@STEP_NEST_LEVEL@1@@@",
+ "@@@STEP_LOG_LINE@myfile.txt@myfile@@@",
+ "@@@STEP_LOG_END@myfile.txt@@@"
+ ]
+ },
+ {
+ "cmd": [
+ "python3",
+ "RECIPE_MODULE[flutter::zip]/resources/zip.py"
+ ],
+ "name": "Create test file.create zip",
+ "stdin": "{\"entries\": [{\"path\": \"[CLEANUP]/tmp_tmp_1/content\", \"type\": \"dir\"}], \"output\": \"[CLEANUP]/tmp_tmp_1/myzip.zip\", \"root\": \"[CLEANUP]/tmp_tmp_1/content\"}",
+ "~followup_annotations": [
+ "@@@STEP_NEST_LEVEL@1@@@"
+ ]
+ },
+ {
+ "cmd": [
+ "python3",
+ "RECIPE_MODULE[flutter::zip]/resources/namelist.py"
+ ],
+ "name": "Create test file.namelist",
+ "stdin": "{\"zip_file\": \"[CLEANUP]/tmp_tmp_1/myzip.zip\"}",
+ "~followup_annotations": [
+ "@@@STEP_NEST_LEVEL@1@@@",
+ "@@@STEP_LOG_END@json.output (invalid)@@@",
+ "@@@STEP_LOG_LINE@json.output (exception)@No JSON object could be decoded@@@",
+ "@@@STEP_LOG_END@json.output (exception)@@@"
+ ]
+ },
+ {
+ "cmd": [
+ "vpython3",
+ "-u",
+ "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+ "--json-output",
+ "/path/to/tmp/json",
+ "rmtree",
+ "[CLEANUP]/tmp_tmp_1"
+ ],
+ "infra_step": true,
+ "name": "Create test file.Delete tmp folder",
+ "~followup_annotations": [
+ "@@@STEP_NEST_LEVEL@1@@@"
+ ]
+ },
+ {
+ "name": "$result"
+ }
+]
\ No newline at end of file
diff --git a/recipe_modules/signing/examples/requires_signing.expected/mac_require_signing_entitlements.json b/recipe_modules/signing/examples/requires_signing.expected/mac_require_signing_entitlements.json
new file mode 100644
index 0000000..d6b55f4
--- /dev/null
+++ b/recipe_modules/signing/examples/requires_signing.expected/mac_require_signing_entitlements.json
@@ -0,0 +1,107 @@
+[
+ {
+ "cmd": [],
+ "name": "Create test file"
+ },
+ {
+ "cmd": [
+ "vpython3",
+ "-u",
+ "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+ "--json-output",
+ "/path/to/tmp/json",
+ "copy",
+ "myfile",
+ "[CLEANUP]/tmp_tmp_1/content/myfile.txt"
+ ],
+ "infra_step": true,
+ "name": "Create test file.write file",
+ "~followup_annotations": [
+ "@@@STEP_NEST_LEVEL@1@@@",
+ "@@@STEP_LOG_LINE@myfile.txt@myfile@@@",
+ "@@@STEP_LOG_END@myfile.txt@@@"
+ ]
+ },
+ {
+ "cmd": [
+ "vpython3",
+ "-u",
+ "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+ "--json-output",
+ "/path/to/tmp/json",
+ "copy",
+ "",
+ "[CLEANUP]/tmp_tmp_1/content/entitlements.txt"
+ ],
+ "infra_step": true,
+ "name": "Create test file.write entitlements.txt",
+ "~followup_annotations": [
+ "@@@STEP_NEST_LEVEL@1@@@",
+ "@@@STEP_LOG_END@entitlements.txt@@@"
+ ]
+ },
+ {
+ "cmd": [
+ "vpython3",
+ "-u",
+ "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+ "--json-output",
+ "/path/to/tmp/json",
+ "copy",
+ "",
+ "[CLEANUP]/tmp_tmp_1/content/without_entitlements.txt"
+ ],
+ "infra_step": true,
+ "name": "Create test file.write without_entitlements.txt",
+ "~followup_annotations": [
+ "@@@STEP_NEST_LEVEL@1@@@",
+ "@@@STEP_LOG_END@without_entitlements.txt@@@"
+ ]
+ },
+ {
+ "cmd": [
+ "python3",
+ "RECIPE_MODULE[flutter::zip]/resources/zip.py"
+ ],
+ "name": "Create test file.create zip",
+ "stdin": "{\"entries\": [{\"path\": \"[CLEANUP]/tmp_tmp_1/content\", \"type\": \"dir\"}], \"output\": \"[CLEANUP]/tmp_tmp_1/myzip.zip\", \"root\": \"[CLEANUP]/tmp_tmp_1/content\"}",
+ "~followup_annotations": [
+ "@@@STEP_NEST_LEVEL@1@@@"
+ ]
+ },
+ {
+ "cmd": [
+ "python3",
+ "RECIPE_MODULE[flutter::zip]/resources/namelist.py"
+ ],
+ "name": "Create test file.namelist",
+ "stdin": "{\"zip_file\": \"[CLEANUP]/tmp_tmp_1/myzip.zip\"}",
+ "~followup_annotations": [
+ "@@@STEP_NEST_LEVEL@1@@@",
+ "@@@STEP_LOG_LINE@json.output@[@@@",
+ "@@@STEP_LOG_LINE@json.output@ \"myfile.txt\", @@@",
+ "@@@STEP_LOG_LINE@json.output@ \"entitlements.txt\"@@@",
+ "@@@STEP_LOG_LINE@json.output@]@@@",
+ "@@@STEP_LOG_END@json.output@@@"
+ ]
+ },
+ {
+ "cmd": [
+ "vpython3",
+ "-u",
+ "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+ "--json-output",
+ "/path/to/tmp/json",
+ "rmtree",
+ "[CLEANUP]/tmp_tmp_1"
+ ],
+ "infra_step": true,
+ "name": "Create test file.Delete tmp folder",
+ "~followup_annotations": [
+ "@@@STEP_NEST_LEVEL@1@@@"
+ ]
+ },
+ {
+ "name": "$result"
+ }
+]
\ No newline at end of file
diff --git a/recipe_modules/signing/examples/requires_signing.expected/mac_require_signing_without_entitlements.json b/recipe_modules/signing/examples/requires_signing.expected/mac_require_signing_without_entitlements.json
new file mode 100644
index 0000000..d8d49c7
--- /dev/null
+++ b/recipe_modules/signing/examples/requires_signing.expected/mac_require_signing_without_entitlements.json
@@ -0,0 +1,107 @@
+[
+ {
+ "cmd": [],
+ "name": "Create test file"
+ },
+ {
+ "cmd": [
+ "vpython3",
+ "-u",
+ "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+ "--json-output",
+ "/path/to/tmp/json",
+ "copy",
+ "myfile",
+ "[CLEANUP]/tmp_tmp_1/content/myfile.txt"
+ ],
+ "infra_step": true,
+ "name": "Create test file.write file",
+ "~followup_annotations": [
+ "@@@STEP_NEST_LEVEL@1@@@",
+ "@@@STEP_LOG_LINE@myfile.txt@myfile@@@",
+ "@@@STEP_LOG_END@myfile.txt@@@"
+ ]
+ },
+ {
+ "cmd": [
+ "vpython3",
+ "-u",
+ "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+ "--json-output",
+ "/path/to/tmp/json",
+ "copy",
+ "",
+ "[CLEANUP]/tmp_tmp_1/content/entitlements.txt"
+ ],
+ "infra_step": true,
+ "name": "Create test file.write entitlements.txt",
+ "~followup_annotations": [
+ "@@@STEP_NEST_LEVEL@1@@@",
+ "@@@STEP_LOG_END@entitlements.txt@@@"
+ ]
+ },
+ {
+ "cmd": [
+ "vpython3",
+ "-u",
+ "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+ "--json-output",
+ "/path/to/tmp/json",
+ "copy",
+ "",
+ "[CLEANUP]/tmp_tmp_1/content/without_entitlements.txt"
+ ],
+ "infra_step": true,
+ "name": "Create test file.write without_entitlements.txt",
+ "~followup_annotations": [
+ "@@@STEP_NEST_LEVEL@1@@@",
+ "@@@STEP_LOG_END@without_entitlements.txt@@@"
+ ]
+ },
+ {
+ "cmd": [
+ "python3",
+ "RECIPE_MODULE[flutter::zip]/resources/zip.py"
+ ],
+ "name": "Create test file.create zip",
+ "stdin": "{\"entries\": [{\"path\": \"[CLEANUP]/tmp_tmp_1/content\", \"type\": \"dir\"}], \"output\": \"[CLEANUP]/tmp_tmp_1/myzip.zip\", \"root\": \"[CLEANUP]/tmp_tmp_1/content\"}",
+ "~followup_annotations": [
+ "@@@STEP_NEST_LEVEL@1@@@"
+ ]
+ },
+ {
+ "cmd": [
+ "python3",
+ "RECIPE_MODULE[flutter::zip]/resources/namelist.py"
+ ],
+ "name": "Create test file.namelist",
+ "stdin": "{\"zip_file\": \"[CLEANUP]/tmp_tmp_1/myzip.zip\"}",
+ "~followup_annotations": [
+ "@@@STEP_NEST_LEVEL@1@@@",
+ "@@@STEP_LOG_LINE@json.output@[@@@",
+ "@@@STEP_LOG_LINE@json.output@ \"myfile.txt\", @@@",
+ "@@@STEP_LOG_LINE@json.output@ \"without_entitlements.txt\"@@@",
+ "@@@STEP_LOG_LINE@json.output@]@@@",
+ "@@@STEP_LOG_END@json.output@@@"
+ ]
+ },
+ {
+ "cmd": [
+ "vpython3",
+ "-u",
+ "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+ "--json-output",
+ "/path/to/tmp/json",
+ "rmtree",
+ "[CLEANUP]/tmp_tmp_1"
+ ],
+ "infra_step": true,
+ "name": "Create test file.Delete tmp folder",
+ "~followup_annotations": [
+ "@@@STEP_NEST_LEVEL@1@@@"
+ ]
+ },
+ {
+ "name": "$result"
+ }
+]
\ No newline at end of file
diff --git a/recipe_modules/signing/examples/requires_signing.expected/non_mac.json b/recipe_modules/signing/examples/requires_signing.expected/non_mac.json
new file mode 100644
index 0000000..88881b6
--- /dev/null
+++ b/recipe_modules/signing/examples/requires_signing.expected/non_mac.json
@@ -0,0 +1,55 @@
+[
+ {
+ "cmd": [],
+ "name": "Create test file"
+ },
+ {
+ "cmd": [
+ "vpython3",
+ "-u",
+ "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+ "--json-output",
+ "/path/to/tmp/json",
+ "copy",
+ "myfile",
+ "[CLEANUP]/tmp_tmp_1/content/myfile.txt"
+ ],
+ "infra_step": true,
+ "name": "Create test file.write file",
+ "~followup_annotations": [
+ "@@@STEP_NEST_LEVEL@1@@@",
+ "@@@STEP_LOG_LINE@myfile.txt@myfile@@@",
+ "@@@STEP_LOG_END@myfile.txt@@@"
+ ]
+ },
+ {
+ "cmd": [
+ "python3",
+ "RECIPE_MODULE[flutter::zip]/resources/zip.py"
+ ],
+ "name": "Create test file.create zip",
+ "stdin": "{\"entries\": [{\"path\": \"[CLEANUP]/tmp_tmp_1/content\", \"type\": \"dir\"}], \"output\": \"[CLEANUP]/tmp_tmp_1/myzip.zip\", \"root\": \"[CLEANUP]/tmp_tmp_1/content\"}",
+ "~followup_annotations": [
+ "@@@STEP_NEST_LEVEL@1@@@"
+ ]
+ },
+ {
+ "cmd": [
+ "vpython3",
+ "-u",
+ "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+ "--json-output",
+ "/path/to/tmp/json",
+ "rmtree",
+ "[CLEANUP]/tmp_tmp_1"
+ ],
+ "infra_step": true,
+ "name": "Create test file.Delete tmp folder",
+ "~followup_annotations": [
+ "@@@STEP_NEST_LEVEL@1@@@"
+ ]
+ },
+ {
+ "name": "$result"
+ }
+]
\ No newline at end of file
diff --git a/recipe_modules/signing/examples/requires_signing.py b/recipe_modules/signing/examples/requires_signing.py
new file mode 100644
index 0000000..b0dbfcf
--- /dev/null
+++ b/recipe_modules/signing/examples/requires_signing.py
@@ -0,0 +1,61 @@
+# Copyright 2023 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 contextlib
+from recipe_engine.post_process import (Filter)
+
+DEPS = [
+ 'flutter/signing',
+ 'flutter/zip',
+ 'recipe_engine/assertions',
+ 'recipe_engine/file',
+ 'recipe_engine/path',
+ 'recipe_engine/platform',
+ 'recipe_engine/properties',
+ 'recipe_engine/step',
+]
+
+
+@contextlib.contextmanager
+def _create_zip(api, include_entitlements=False):
+ with api.step.nest('Create test file'):
+ directory = api.path.mkdtemp()
+ api.file.write_text('write file', directory.join('content', 'myfile.txt'), 'myfile')
+ if include_entitlements:
+ api.file.write_text('write entitlements.txt', directory.join('content', 'entitlements.txt'), '')
+ api.file.write_text('write without_entitlements.txt', directory.join('content', 'without_entitlements.txt'), '')
+ api.zip.directory('create zip', directory.join('content'), directory.join('myzip.zip'))
+ yield directory.join('myzip.zip')
+ api.file.rmtree('Delete tmp folder', directory)
+
+def RunSteps(api):
+ expected_result = api.properties.get('expected_result')
+ with _create_zip(api, expected_result) as zip_file_name:
+ result = api.signing.requires_signing(zip_file_name)
+ api.assertions.assertEqual(result, expected_result)
+
+
+def GenTests(api):
+ yield api.test(
+ 'non_mac',
+ api.platform.name('linux'),
+ api.properties(expected_result=False),
+ )
+ yield api.test(
+ 'mac_require_signing_entitlements',
+ api.platform.name('mac'),
+ api.properties(expected_result=True),
+ api.zip.namelist('Create test file.namelist', ['myfile.txt', 'entitlements.txt'])
+ )
+ yield api.test(
+ 'mac_require_signing_without_entitlements',
+ api.platform.name('mac'),
+ api.properties(expected_result=True),
+ api.zip.namelist('Create test file.namelist', ['myfile.txt', 'without_entitlements.txt'])
+ )
+ yield api.test(
+ 'mac_does_not_require_signing',
+ api.platform.name('mac'),
+ api.properties(expected_result=False),
+ )
diff --git a/recipe_modules/signing/resources/import_certificate.sh b/recipe_modules/signing/resources/import_certificate.sh
new file mode 100644
index 0000000..f4151e1
--- /dev/null
+++ b/recipe_modules/signing/resources/import_certificate.sh
@@ -0,0 +1,10 @@
+#!/bin/bash
+
+# Helper script to import a flutter p12 identity.
+# Note: do not enable -x to display expanded values of the variables, as this will leak the passwords.
+set -e
+
+RAW_PASSWORD=$(cat $FLUTTER_P12_PASSWORD)
+# Only filepath with a .p12 suffix will be recognized
+mv $FLUTTER_P12 $P12_SUFFIX_FILEPATH
+/usr/bin/security import $P12_SUFFIX_FILEPATH -k build.keychain -P $RAW_PASSWORD -T $CODESIGN_PATH -T /usr/bin/codesign
\ No newline at end of file
diff --git a/recipe_modules/signing/resources/runner.sh b/recipe_modules/signing/resources/runner.sh
new file mode 100644
index 0000000..a35286d
--- /dev/null
+++ b/recipe_modules/signing/resources/runner.sh
@@ -0,0 +1,12 @@
+#!/bin/bash
+
+# Helper script to unlock the keychain in the same session
+# as the test runner script.
+set -e
+
+if [ -f /usr/local/bin/unlock_login_keychain.sh ]
+then
+ /usr/local/bin/unlock_login_keychain.sh
+else
+ echo "This bot does not support codesigning"
+fi
\ No newline at end of file
diff --git a/recipe_modules/signing/test_api.py b/recipe_modules/signing/test_api.py
new file mode 100644
index 0000000..ac1fedb
--- /dev/null
+++ b/recipe_modules/signing/test_api.py
@@ -0,0 +1,15 @@
+import os
+import tempfile
+import zipfile
+
+from recipe_engine import recipe_test_api
+
+class RecipeTestingTestApi(recipe_test_api.RecipeTestApi):
+
+ def flutter_signing_identity(self):
+ return self.step_data(
+ 'Setup keychain.show-identities',
+ stdout=self.m.raw_io.output_text(
+ '1) ABCD "Developer ID Application: FLUTTER.IO LLC (ABCD)"'
+ )
+ )