cli: add cros_sign_image.py Add a wrapper script for sign_official_cos_build.sh to make it easier to sign image artifacts locally. This acts as syntax sugar for developers and doesn't expose all functionality of the underlying shell script. BUG=b/465211305 TEST=presubmit RELEASE_NOTE=None cos-patch: bug Change-Id: Ic57caabc68dcaf69c926d651867bc39cfb53154f
diff --git a/cli/cros/cros_sign_image.py b/cli/cros/cros_sign_image.py new file mode 100644 index 0000000..9a1a8ad --- /dev/null +++ b/cli/cros/cros_sign_image.py
@@ -0,0 +1,128 @@ +# Copyright 2026 The ChromiumOS Authors +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +"""Sign image artifacts + +Mostly just a nice wrapper for sign_official_cos_build.sh. +This script is intended as syntax sugar for local developers, and as such +does not expose all functionalities of the underlying shell script. + +Examples: + cros sign-image --board=<board> --ima-mode=hash test - sign a test image with IMA hash. + cros sign-image --board=<board> --ima-mode=sign - sign a dev image with IMA signature. +""" + +import argparse +import glob +import logging +import os +import sys + +from chromite.cli import command +from chromite.lib import build_target_lib +from chromite.lib import commandline +from chromite.lib import constants +from chromite.lib import cros_build_lib + + +@command.command_decorator("sign-image") +class SignImageCommand(command.CliCommand): + """Sign a ChromiumOS image with dev/local keys (including IMA).""" + + @classmethod + def AddParser(cls, parser: commandline.ArgumentParser): + """Build the parser.""" + super().AddParser(parser) + parser.description = __doc__ + + parser.add_argument( + "-b", + "--board", + "--build-target", + dest="board", + default=cros_build_lib.GetDefaultBoard(), + help="The board of the image to sign.", + ) + parser.add_argument( + "--keys-dir", + default="/usr/share/vboot/devkeys", + help="Directory containing developer/local keys (Default: %(default)s).", + ) + parser.add_argument( + "--key-origin", + default="local", + help="Key origin: local or kms (Default: %(default)s).", + ) + parser.add_argument( + "--type", + default="base", + help="The signing script TYPE argument (Default: %(default)s).", + ) + parser.add_argument( + "--ima-mode", + choices=["disabled", "hash", "sign"], + default="disabled", + help="IMA signing mode: disabled, hash (digest-based file labeling), " + "or sign (signature-based file labeling) (Default: %(default)s).", + ) + parser.add_argument( + "--service-account", + default="", + help="Name of GCP service account to use when signing with Cloud KMS (Default: %(default)s).", + ) + parser.add_argument( + "images", + nargs="*", + default=["dev"], + help="List of images to sign (e.g. test, base, dev) (Default: %(default)s).", + ) + + def Run(self): + # Require that the command is executed inside the chroot + commandline.RunInsideChroot() + + if not self.options.board: + self.options.parser.error("--board is required") + + if self.options.type == "update_payload": + cros_build_lib.Die("update_payload not supported - use sign_official_cos_build.sh directly.") + script_path = os.path.join( + constants.SOURCE_ROOT, + "src/platform/vboot_reference/scripts/image_signing/sign_official_cos_build.sh", + ) + + # NOTE: The input and output paths are currently hardcoded to the board's build + # directory. This is sufficient for local developer usage. However, if we ever + # want to support `--type update_payload`, these paths will need to be made + # configurable (e.g. via command line options) to allow passing a custom 32-byte + # update payload hash file. + image_dir = os.path.join(constants.DEFAULT_BUILD_ROOT, "images", self.options.board, "latest") + + for image_name in self.options.images: + image_file = f"chromiumos_{image_name}_image.bin" + input_image = os.path.join(image_dir, image_file) + output_image = os.path.join(image_dir, f"chromiumos_{image_name}_image_signed.bin") + + if not os.path.exists(input_image): + cros_build_lib.Die(f"Image not found at {input_image}") + + # Advanced parameters of the underlying script, such as version_file are currently + # omitted because local developers do not typically need to sign images with + # custom/incremented rollback protection versions. + cmd = [ + "sudo", + script_path, + self.options.type, + self.options.key_origin, + input_image, + self.options.keys_dir, + output_image, + self.options.service_account, + f"ima_{self.options.ima_mode}", + ] + + cros_build_lib.run(cmd) + + logging.info("Successfully signed %s image.", image_name) + logging.info("Output image: %s", output_image)