blob: 0f30a5b7e91b9f2a4c65116ebead320eb5153319 [file] [log] [blame]
# 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):
"""Property pointing to the signing tool location."""
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)
env['CODESIGN_PATH'] = self.codesign_binary
def _keychain_setup(self, env, env_prefixes):
"""KeychainSetup adds flutter .p12 to a temporary keychain named 'build'.
Args:
env (dict): environment variables.
env_prefixes (dict) : environment paths.
"""
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:
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:
env (dict): environment variables.
env_prefixes (dict) : environment paths.
source_path (Path): path of the artifact to sign.
"""
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, 'unsigned_%s' % 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']
)