Merge "Sign OTA packages inside target_files during signing" into main

This commit is contained in:
Treehugger Robot
2023-12-05 21:46:55 +00:00
committed by Gerrit Code Review
5 changed files with 153 additions and 29 deletions

View File

@@ -333,6 +333,7 @@ python_library_host {
srcs: [ srcs: [
"ota_utils.py", "ota_utils.py",
"payload_signer.py", "payload_signer.py",
"ota_signing_utils.py",
], ],
libs: [ libs: [
"releasetools_common", "releasetools_common",
@@ -348,7 +349,6 @@ python_binary_host {
}, },
srcs: [ srcs: [
"merge_ota.py", "merge_ota.py",
"ota_signing_utils.py",
], ],
libs: [ libs: [
"ota_metadata_proto", "ota_metadata_proto",
@@ -501,7 +501,6 @@ python_binary_host {
name: "ota_from_raw_img", name: "ota_from_raw_img",
srcs: [ srcs: [
"ota_from_raw_img.py", "ota_from_raw_img.py",
"ota_signing_utils.py",
], ],
main: "ota_from_raw_img.py", main: "ota_from_raw_img.py",
defaults: [ defaults: [
@@ -552,6 +551,8 @@ python_binary_host {
defaults: ["releasetools_binary_defaults"], defaults: ["releasetools_binary_defaults"],
srcs: [ srcs: [
"sign_target_files_apks.py", "sign_target_files_apks.py",
"payload_signer.py",
"ota_signing_utils.py",
], ],
libs: [ libs: [
"releasetools_add_img_to_target_files", "releasetools_add_img_to_target_files",
@@ -615,7 +616,6 @@ python_defaults {
"sign_target_files_apks.py", "sign_target_files_apks.py",
"validate_target_files.py", "validate_target_files.py",
"merge_ota.py", "merge_ota.py",
"ota_signing_utils.py",
":releasetools_merge_sources", ":releasetools_merge_sources",
":releasetools_merge_tests", ":releasetools_merge_tests",

View File

@@ -3105,6 +3105,34 @@ def ZipWriteStr(zip_file, zinfo_or_arcname, data, perms=None,
zip_file.writestr(zinfo, data) zip_file.writestr(zinfo, data)
zipfile.ZIP64_LIMIT = saved_zip64_limit zipfile.ZIP64_LIMIT = saved_zip64_limit
def ZipExclude(input_zip, output_zip, entries, force=False):
"""Deletes entries from a ZIP file.
Args:
zip_filename: The name of the ZIP file.
entries: The name of the entry, or the list of names to be deleted.
"""
if isinstance(entries, str):
entries = [entries]
# If list is empty, nothing to do
if not entries:
shutil.copy(input_zip, output_zip)
return
with zipfile.ZipFile(input_zip, 'r') as zin:
if not force and len(set(zin.namelist()).intersection(entries)) == 0:
raise ExternalError(
"Failed to delete zip entries, name not matched: %s" % entries)
fd, new_zipfile = tempfile.mkstemp(dir=os.path.dirname(input_zip))
os.close(fd)
cmd = ["zip2zip", "-i", input_zip, "-o", new_zipfile]
for entry in entries:
cmd.append("-x")
cmd.append(entry)
RunAndCheckOutput(cmd)
os.replace(new_zipfile, output_zip)
def ZipDelete(zip_filename, entries, force=False): def ZipDelete(zip_filename, entries, force=False):
"""Deletes entries from a ZIP file. """Deletes entries from a ZIP file.
@@ -3119,20 +3147,7 @@ def ZipDelete(zip_filename, entries, force=False):
if not entries: if not entries:
return return
with zipfile.ZipFile(zip_filename, 'r') as zin: ZipExclude(zip_filename, zip_filename, entries, force)
if not force and len(set(zin.namelist()).intersection(entries)) == 0:
raise ExternalError(
"Failed to delete zip entries, name not matched: %s" % entries)
fd, new_zipfile = tempfile.mkstemp(dir=os.path.dirname(zip_filename))
os.close(fd)
cmd = ["zip2zip", "-i", zip_filename, "-o", new_zipfile]
for entry in entries:
cmd.append("-x")
cmd.append(entry)
RunAndCheckOutput(cmd)
os.replace(new_zipfile, zip_filename)
def ZipClose(zip_file): def ZipClose(zip_file):

View File

@@ -27,7 +27,8 @@ from common import (ZipDelete, DoesInputFileContain, ReadBytesFromInputFile, OPT
ZipWriteStr, BuildInfo, LoadDictionaryFromFile, ZipWriteStr, BuildInfo, LoadDictionaryFromFile,
SignFile, PARTITIONS_WITH_BUILD_PROP, PartitionBuildProps, SignFile, PARTITIONS_WITH_BUILD_PROP, PartitionBuildProps,
GetRamdiskFormat, ParseUpdateEngineConfig) GetRamdiskFormat, ParseUpdateEngineConfig)
from payload_signer import PayloadSigner import payload_signer
from payload_signer import PayloadSigner, AddSigningArgumentParse, GeneratePayloadProperties
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -785,8 +786,8 @@ def GetPartitionMaps(target_files_dir: str, ab_partitions):
class PayloadGenerator(object): class PayloadGenerator(object):
"""Manages the creation and the signing of an A/B OTA Payload.""" """Manages the creation and the signing of an A/B OTA Payload."""
PAYLOAD_BIN = 'payload.bin' PAYLOAD_BIN = payload_signer.PAYLOAD_BIN
PAYLOAD_PROPERTIES_TXT = 'payload_properties.txt' PAYLOAD_PROPERTIES_TXT = payload_signer.PAYLOAD_PROPERTIES_TXT
SECONDARY_PAYLOAD_BIN = 'secondary/payload.bin' SECONDARY_PAYLOAD_BIN = 'secondary/payload.bin'
SECONDARY_PAYLOAD_PROPERTIES_TXT = 'secondary/payload_properties.txt' SECONDARY_PAYLOAD_PROPERTIES_TXT = 'secondary/payload_properties.txt'
@@ -905,12 +906,7 @@ class PayloadGenerator(object):
""" """
assert self.payload_file is not None assert self.payload_file is not None
# 4. Dump the signed payload properties. # 4. Dump the signed payload properties.
properties_file = common.MakeTempFile(prefix="payload-properties-", properties_file = GeneratePayloadProperties(self.payload_file)
suffix=".txt")
cmd = ["delta_generator",
"--in_file=" + self.payload_file,
"--properties_file=" + properties_file]
self._Run(cmd)
with open(properties_file, "a") as f: with open(properties_file, "a") as f:

View File

@@ -17,7 +17,12 @@
import common import common
import logging import logging
import shlex import shlex
import argparse
import tempfile
import zipfile
import shutil
from common import OPTIONS, OptionHandler from common import OPTIONS, OptionHandler
from ota_signing_utils import AddSigningArgumentParse
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -26,6 +31,8 @@ OPTIONS.payload_signer_args = []
OPTIONS.payload_signer_maximum_signature_size = None OPTIONS.payload_signer_maximum_signature_size = None
OPTIONS.package_key = None OPTIONS.package_key = None
PAYLOAD_BIN = 'payload.bin'
PAYLOAD_PROPERTIES_TXT = 'payload_properties.txt'
class SignerOptions(OptionHandler): class SignerOptions(OptionHandler):
@@ -165,3 +172,52 @@ class PayloadSigner(object):
cmd = [self.signer] + self.signer_args + ['-in', in_file, '-out', out_file] cmd = [self.signer] + self.signer_args + ['-in', in_file, '-out', out_file]
common.RunAndCheckOutput(cmd) common.RunAndCheckOutput(cmd)
return out_file return out_file
def GeneratePayloadProperties(payload_file):
properties_file = common.MakeTempFile(prefix="payload-properties-",
suffix=".txt")
cmd = ["delta_generator",
"--in_file=" + payload_file,
"--properties_file=" + properties_file]
common.RunAndCheckOutput(cmd)
return properties_file
def SignOtaPackage(input_path, output_path):
payload_signer = PayloadSigner(
OPTIONS.package_key, OPTIONS.private_key_suffix,
None, OPTIONS.payload_signer, OPTIONS.payload_signer_args)
common.ZipExclude(input_path, output_path, [PAYLOAD_BIN, PAYLOAD_PROPERTIES_TXT])
with tempfile.NamedTemporaryFile() as unsigned_payload, zipfile.ZipFile(input_path, "r", allowZip64=True) as zfp:
with zfp.open("payload.bin") as payload_fp:
shutil.copyfileobj(payload_fp, unsigned_payload)
signed_payload = payload_signer.SignPayload(unsigned_payload.name)
properties_file = GeneratePayloadProperties(signed_payload)
with zipfile.ZipFile(output_path, "a", compression=zipfile.ZIP_STORED, allowZip64=True) as output_zfp:
common.ZipWrite(output_zfp, signed_payload, PAYLOAD_BIN)
common.ZipWrite(output_zfp, properties_file, PAYLOAD_PROPERTIES_TXT)
def main(argv):
parser = argparse.ArgumentParser(
prog=argv[0], description="Given a series of .img files, produces a full OTA package that installs thoese images")
parser.add_argument("input_ota", type=str,
help="Input OTA for signing")
parser.add_argument('output_ota', type=str,
help='Output OTA for the signed package')
parser.add_argument("-v", action="store_true",
help="Enable verbose logging", dest="verbose")
AddSigningArgumentParse(parser)
args = parser.parse_args(argv[1:])
input_ota = args.input_ota
output_ota = args.output_ota
if args.verbose:
OPTIONS.verbose = True
common.InitLogging()
if args.package_key:
OPTIONS.package_key = args.package_key
logger.info("Re-signing OTA package {}".format(input_ota))
SignOtaPackage(input_ota, output_ota)
if __name__ == "__main__":
import sys
main(sys.argv)

View File

@@ -146,6 +146,34 @@ Usage: sign_target_files_apks [flags] input_target_files output_target_files
--override_apex_keys <path> --override_apex_keys <path>
Replace all APEX keys with this private key Replace all APEX keys with this private key
-k (--package_key) <key>
Key to use to sign the package (default is the value of
default_system_dev_certificate from the input target-files's
META/misc_info.txt, or "build/make/target/product/security/testkey" if
that value is not specified).
For incremental OTAs, the default value is based on the source
target-file, not the target build.
--payload_signer <signer>
Specify the signer when signing the payload and metadata for A/B OTAs.
By default (i.e. without this flag), it calls 'openssl pkeyutl' to sign
with the package private key. If the private key cannot be accessed
directly, a payload signer that knows how to do that should be specified.
The signer will be supplied with "-inkey <path_to_key>",
"-in <input_file>" and "-out <output_file>" parameters.
--payload_signer_args <args>
Specify the arguments needed for payload signer.
--payload_signer_maximum_signature_size <signature_size>
The maximum signature size (in bytes) that would be generated by the given
payload signer. Only meaningful when custom payload signer is specified
via '--payload_signer'.
If the signer uses a RSA key, this should be the number of bytes to
represent the modulus. If it uses an EC key, this is the size of a
DER-encoded ECDSA signature.
""" """
from __future__ import print_function from __future__ import print_function
@@ -161,7 +189,6 @@ import os
import re import re
import shutil import shutil
import stat import stat
import subprocess
import sys import sys
import tempfile import tempfile
import zipfile import zipfile
@@ -170,6 +197,8 @@ from xml.etree import ElementTree
import add_img_to_target_files import add_img_to_target_files
import apex_utils import apex_utils
import common import common
import payload_signer
from payload_signer import SignOtaPackage, PAYLOAD_BIN
if sys.hexversion < 0x02070000: if sys.hexversion < 0x02070000:
@@ -240,6 +269,20 @@ def IsApexFile(filename):
return filename.endswith(".apex") or filename.endswith(".capex") return filename.endswith(".apex") or filename.endswith(".capex")
def IsOtaPackage(fp):
with zipfile.ZipFile(fp) as zfp:
if not PAYLOAD_BIN in zfp.namelist():
return False
with zfp.open(PAYLOAD_BIN, "r") as payload:
magic = payload.read(4)
return magic == b"CrAU"
def IsEntryOtaPackage(input_zip, filename):
with input_zip.open(filename, "r") as fp:
return IsOtaPackage(fp)
def GetApexFilename(filename): def GetApexFilename(filename):
name = os.path.basename(filename) name = os.path.basename(filename)
# Replace the suffix for compressed apex # Replace the suffix for compressed apex
@@ -514,6 +557,7 @@ def SignApk(data, keyname, pw, platform_api_level, codename_to_api_level_map,
return data return data
def IsBuildPropFile(filename): def IsBuildPropFile(filename):
return filename in ( return filename in (
"SYSTEM/etc/prop.default", "SYSTEM/etc/prop.default",
@@ -540,7 +584,7 @@ def IsBuildPropFile(filename):
filename.endswith("/prop.default") filename.endswith("/prop.default")
def ProcessTargetFiles(input_tf_zip, output_tf_zip, misc_info, def ProcessTargetFiles(input_tf_zip: zipfile.ZipFile, output_tf_zip, misc_info,
apk_keys, apex_keys, key_passwords, apk_keys, apex_keys, key_passwords,
platform_api_level, codename_to_api_level_map, platform_api_level, codename_to_api_level_map,
compressed_extension): compressed_extension):
@@ -628,6 +672,15 @@ def ProcessTargetFiles(input_tf_zip, output_tf_zip, misc_info,
" (skipped due to special cert string)" % (name,)) " (skipped due to special cert string)" % (name,))
common.ZipWriteStr(output_tf_zip, out_info, data) common.ZipWriteStr(output_tf_zip, out_info, data)
elif filename.endswith(".zip") and IsEntryOtaPackage(input_tf_zip, filename):
logger.info("Re-signing OTA package {}".format(filename))
with tempfile.NamedTemporaryFile() as input_ota, tempfile.NamedTemporaryFile() as output_ota:
with input_tf_zip.open(filename, "r") as in_fp:
shutil.copyfileobj(in_fp, input_ota)
input_ota.flush()
SignOtaPackage(input_ota.name, output_ota.name)
common.ZipWrite(output_tf_zip, output_ota.name, filename,
compress_type=zipfile.ZIP_STORED)
# System properties. # System properties.
elif IsBuildPropFile(filename): elif IsBuildPropFile(filename):
print("Rewriting %s:" % (filename,)) print("Rewriting %s:" % (filename,))
@@ -1501,7 +1554,7 @@ def main(argv):
"override_apk_keys=", "override_apk_keys=",
"override_apex_keys=", "override_apex_keys=",
], ],
extra_option_handler=option_handler) extra_option_handler=[option_handler, payload_signer.signer_options])
if len(args) != 2: if len(args) != 2:
common.Usage(__doc__) common.Usage(__doc__)
@@ -1515,6 +1568,10 @@ def main(argv):
allowZip64=True) allowZip64=True)
misc_info = common.LoadInfoDict(input_zip) misc_info = common.LoadInfoDict(input_zip)
if OPTIONS.package_key is None:
OPTIONS.package_key = misc_info.get(
"default_system_dev_certificate",
"build/make/target/product/security/testkey")
BuildKeyMap(misc_info, key_mapping_options) BuildKeyMap(misc_info, key_mapping_options)