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/+/41662
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)"' + ) + )