Merge changes I3fa13e3d,I7b7f0017,I2ef318e0

* changes:
  releasetools: Support signing APEXes.
  releasetools: Add apex_utils.py.
  releasetools: check_target_files_signatures.py checks APEXes.
This commit is contained in:
Baligh Uddin
2019-03-17 18:49:48 +00:00
committed by Gerrit Code Review
7 changed files with 628 additions and 38 deletions

View File

@@ -0,0 +1,147 @@
#!/usr/bin/env python
#
# Copyright (C) 2019 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import logging
import os.path
import re
import shlex
import sys
import common
logger = logging.getLogger(__name__)
class ApexInfoError(Exception):
"""An Exception raised during Apex Information command."""
def __init__(self, message):
Exception.__init__(self, message)
class ApexSigningError(Exception):
"""An Exception raised during Apex Payload signing."""
def __init__(self, message):
Exception.__init__(self, message)
def SignApexPayload(payload_file, payload_key_path, payload_key_name, algorithm,
salt, signing_args=None):
"""Signs a given payload_file with the payload key."""
# Add the new footer. Old footer, if any, will be replaced by avbtool.
cmd = ['avbtool', 'add_hashtree_footer',
'--do_not_generate_fec',
'--algorithm', algorithm,
'--key', payload_key_path,
'--prop', 'apex.key:{}'.format(payload_key_name),
'--image', payload_file,
'--salt', salt]
if signing_args:
cmd.extend(shlex.split(signing_args))
try:
common.RunAndCheckOutput(cmd)
except common.ExternalError as e:
raise ApexSigningError, \
'Failed to sign APEX payload {} with {}:\n{}'.format(
payload_file, payload_key_path, e), sys.exc_info()[2]
# Verify the signed payload image with specified public key.
logger.info('Verifying %s', payload_file)
VerifyApexPayload(payload_file, payload_key_path)
def VerifyApexPayload(payload_file, payload_key):
"""Verifies the APEX payload signature with the given key."""
cmd = ['avbtool', 'verify_image', '--image', payload_file,
'--key', payload_key]
try:
common.RunAndCheckOutput(cmd)
except common.ExternalError as e:
raise ApexSigningError, \
'Failed to validate payload signing for {} with {}:\n{}'.format(
payload_file, payload_key, e), sys.exc_info()[2]
def ParseApexPayloadInfo(payload_path):
"""Parses the APEX payload info.
Args:
payload_path: The path to the payload image.
Raises:
ApexInfoError on parsing errors.
Returns:
A dict that contains payload property-value pairs. The dict should at least
contain Algorithm, Salt and apex.key.
"""
if not os.path.exists(payload_path):
raise ApexInfoError('Failed to find image: {}'.format(payload_path))
cmd = ['avbtool', 'info_image', '--image', payload_path]
try:
output = common.RunAndCheckOutput(cmd)
except common.ExternalError as e:
raise ApexInfoError, \
'Failed to get APEX payload info for {}:\n{}'.format(
payload_path, e), sys.exc_info()[2]
# Extract the Algorithm / Salt / Prop info from payload (i.e. an image signed
# with avbtool). For example,
# Algorithm: SHA256_RSA4096
PAYLOAD_INFO_PATTERN = (
r'^\s*(?P<key>Algorithm|Salt|Prop)\:\s*(?P<value>.*?)$')
payload_info_matcher = re.compile(PAYLOAD_INFO_PATTERN)
payload_info = {}
for line in output.split('\n'):
line_info = payload_info_matcher.match(line)
if not line_info:
continue
key, value = line_info.group('key'), line_info.group('value')
if key == 'Prop':
# Further extract the property key-value pair, from a 'Prop:' line. For
# example,
# Prop: apex.key -> 'com.android.runtime'
# Note that avbtool writes single or double quotes around values.
PROPERTY_DESCRIPTOR_PATTERN = r'^\s*(?P<key>.*?)\s->\s*(?P<value>.*?)$'
prop_matcher = re.compile(PROPERTY_DESCRIPTOR_PATTERN)
prop = prop_matcher.match(value)
if not prop:
raise ApexInfoError(
'Failed to parse prop string {}'.format(value))
prop_key, prop_value = prop.group('key'), prop.group('value')
if prop_key == 'apex.key':
# avbtool dumps the prop value with repr(), which contains single /
# double quotes that we don't want.
payload_info[prop_key] = prop_value.strip('\"\'')
else:
payload_info[key] = value
# Sanity check.
for key in ('Algorithm', 'Salt', 'apex.key'):
if key not in payload_info:
raise ApexInfoError(
'Failed to find {} prop in {}'.format(key, payload_path))
return payload_info

View File

@@ -168,6 +168,7 @@ def CertFromPKCS7(data, filename):
class APK(object): class APK(object):
def __init__(self, full_filename, filename): def __init__(self, full_filename, filename):
self.filename = filename self.filename = filename
self.certs = None self.certs = None
@@ -244,12 +245,12 @@ class TargetFiles(object):
# must decompress them individually before we perform any analysis. # must decompress them individually before we perform any analysis.
# This is the list of wildcards of files we extract from |filename|. # This is the list of wildcards of files we extract from |filename|.
apk_extensions = ['*.apk'] apk_extensions = ['*.apk', '*.apex']
self.certmap, compressed_extension = common.ReadApkCerts( self.certmap, compressed_extension = common.ReadApkCerts(
zipfile.ZipFile(filename, "r")) zipfile.ZipFile(filename))
if compressed_extension: if compressed_extension:
apk_extensions.append("*.apk" + compressed_extension) apk_extensions.append('*.apk' + compressed_extension)
d = common.UnzipTemp(filename, apk_extensions) d = common.UnzipTemp(filename, apk_extensions)
self.apks = {} self.apks = {}
@@ -272,7 +273,7 @@ class TargetFiles(object):
os.remove(os.path.join(dirpath, fn)) os.remove(os.path.join(dirpath, fn))
fn = uncompressed_fn fn = uncompressed_fn
if fn.endswith(".apk"): if fn.endswith(('.apk', '.apex')):
fullname = os.path.join(dirpath, fn) fullname = os.path.join(dirpath, fn)
displayname = fullname[len(d)+1:] displayname = fullname[len(d)+1:]
apk = APK(fullname, displayname) apk = APK(fullname, displayname)

View File

@@ -21,11 +21,17 @@ target-files zip.
Usage: sign_target_files_apks [flags] input_target_files output_target_files Usage: sign_target_files_apks [flags] input_target_files output_target_files
-e (--extra_apks) <name,name,...=key> -e (--extra_apks) <name,name,...=key>
Add extra APK name/key pairs as though they appeared in Add extra APK/APEX name/key pairs as though they appeared in apkcerts.txt
apkcerts.txt (so mappings specified by -k and -d are applied). or apexkeys.txt (so mappings specified by -k and -d are applied). Keys
Keys specified in -e override any value for that app contained specified in -e override any value for that app contained in the
in the apkcerts.txt file. Option may be repeated to give apkcerts.txt file, or the container key for an APEX. Option may be
multiple extra packages. repeated to give multiple extra packages.
--extra_apex_payload_key <name=key>
Add a mapping for APEX package name to payload signing key, which will
override the default payload signing key in apexkeys.txt. Note that the
container key should be overridden via the `--extra_apks` flag above.
Option may be repeated for multiple APEXes.
--skip_apks_with_path_prefix <prefix> --skip_apks_with_path_prefix <prefix>
Skip signing an APK if it has the matching prefix in its path. The prefix Skip signing an APK if it has the matching prefix in its path. The prefix
@@ -90,7 +96,7 @@ Usage: sign_target_files_apks [flags] input_target_files output_target_files
Use the specified algorithm (e.g. SHA256_RSA4096) and the key to AVB-sign Use the specified algorithm (e.g. SHA256_RSA4096) and the key to AVB-sign
the specified image. Otherwise it uses the existing values in info dict. the specified image. Otherwise it uses the existing values in info dict.
--avb_{boot,system,vendor,dtbo,vbmeta}_extra_args <args> --avb_{apex,boot,system,vendor,dtbo,vbmeta}_extra_args <args>
Specify any additional args that are needed to AVB-sign the image Specify any additional args that are needed to AVB-sign the image
(e.g. "--signing_helper /path/to/helper"). The args will be appended to (e.g. "--signing_helper /path/to/helper"). The args will be appended to
the existing ones in info dict. the existing ones in info dict.
@@ -102,6 +108,7 @@ import base64
import copy import copy
import errno import errno
import gzip import gzip
import itertools
import logging import logging
import os import os
import re import re
@@ -114,6 +121,7 @@ import zipfile
from xml.etree import ElementTree from xml.etree import ElementTree
import add_img_to_target_files import add_img_to_target_files
import apex_utils
import common import common
@@ -127,6 +135,7 @@ logger = logging.getLogger(__name__)
OPTIONS = common.OPTIONS OPTIONS = common.OPTIONS
OPTIONS.extra_apks = {} OPTIONS.extra_apks = {}
OPTIONS.extra_apex_payload_keys = {}
OPTIONS.skip_apks_with_path_prefix = set() OPTIONS.skip_apks_with_path_prefix = set()
OPTIONS.key_map = {} OPTIONS.key_map = {}
OPTIONS.rebuild_recovery = False OPTIONS.rebuild_recovery = False
@@ -154,6 +163,41 @@ def GetApkCerts(certmap):
return certmap return certmap
def GetApexKeys(keys_info, key_map):
"""Gets APEX payload and container signing keys by applying the mapping rules.
We currently don't allow PRESIGNED payload / container keys.
Args:
keys_info: A dict that maps from APEX filenames to a tuple of (payload_key,
container_key).
key_map: A dict that overrides the keys, specified via command-line input.
Returns:
A dict that contains the updated APEX key mapping, which should be used for
the current signing.
"""
# Apply all the --extra_apex_payload_key options to override the payload
# signing keys in the given keys_info.
for apex, key in OPTIONS.extra_apex_payload_keys.items():
assert key, 'Presigned APEX payload for {} is not allowed'.format(apex)
keys_info[apex] = (key, keys_info[apex][1])
# Apply the key remapping to container keys.
for apex, (payload_key, container_key) in keys_info.items():
keys_info[apex] = (payload_key, key_map.get(container_key, container_key))
# Apply all the --extra_apks options to override the container keys.
for apex, key in OPTIONS.extra_apks.items():
# Skip non-APEX containers.
if apex not in keys_info:
continue
assert key, 'Presigned APEX container for {} is not allowed'.format(apex)
keys_info[apex][1] = key_map.get(key, key)
return keys_info
def GetApkFileInfo(filename, compressed_extension, skipped_prefixes): def GetApkFileInfo(filename, compressed_extension, skipped_prefixes):
"""Returns the APK info based on the given filename. """Returns the APK info based on the given filename.
@@ -200,34 +244,45 @@ def GetApkFileInfo(filename, compressed_extension, skipped_prefixes):
return (True, is_compressed, should_be_skipped) return (True, is_compressed, should_be_skipped)
def CheckAllApksSigned(input_tf_zip, apk_key_map, compressed_extension): def CheckApkAndApexKeysAvailable(input_tf_zip, known_keys,
"""Checks that all the APKs have keys specified, otherwise errors out. compressed_extension):
"""Checks that all the APKs and APEXes have keys specified.
Args: Args:
input_tf_zip: An open target_files zip file. input_tf_zip: An open target_files zip file.
apk_key_map: A dict of known signing keys key'd by APK names. known_keys: A set of APKs and APEXes that have known signing keys.
compressed_extension: The extension string of compressed APKs, such as compressed_extension: The extension string of compressed APKs, such as
".gz", or None if there's no compressed APKs. '.gz', or None if there's no compressed APKs.
Raises: Raises:
AssertionError: On finding unknown APKs. AssertionError: On finding unknown APKs and APEXes.
""" """
unknown_apks = [] unknown_files = []
for info in input_tf_zip.infolist(): for info in input_tf_zip.infolist():
# Handle APEXes first, e.g. SYSTEM/apex/com.android.tzdata.apex.
if (info.filename.startswith('SYSTEM/apex') and
info.filename.endswith('.apex')):
name = os.path.basename(info.filename)
if name not in known_keys:
unknown_files.append(name)
continue
# And APKs.
(is_apk, is_compressed, should_be_skipped) = GetApkFileInfo( (is_apk, is_compressed, should_be_skipped) = GetApkFileInfo(
info.filename, compressed_extension, OPTIONS.skip_apks_with_path_prefix) info.filename, compressed_extension, OPTIONS.skip_apks_with_path_prefix)
if not is_apk or should_be_skipped: if not is_apk or should_be_skipped:
continue continue
name = os.path.basename(info.filename) name = os.path.basename(info.filename)
if is_compressed: if is_compressed:
name = name[:-len(compressed_extension)] name = name[:-len(compressed_extension)]
if name not in apk_key_map: if name not in known_keys:
unknown_apks.append(name) unknown_files.append(name)
assert not unknown_apks, \ assert not unknown_files, \
("No key specified for:\n {}\n" ("No key specified for:\n {}\n"
"Use '-e <apkname>=' to specify a key (which may be an empty string to " "Use '-e <apkname>=' to specify a key (which may be an empty string to "
"not sign this apk).".format("\n ".join(unknown_apks))) "not sign this apk).".format("\n ".join(unknown_files)))
def SignApk(data, keyname, pw, platform_api_level, codename_to_api_level_map, def SignApk(data, keyname, pw, platform_api_level, codename_to_api_level_map,
@@ -293,9 +348,69 @@ def SignApk(data, keyname, pw, platform_api_level, codename_to_api_level_map,
return data return data
def SignApex(apex_data, payload_key, container_key, container_pw,
codename_to_api_level_map, signing_args=None):
"""Signs the current APEX with the given payload/container keys.
Args:
apex_data: Raw APEX data.
payload_key: The path to payload signing key (w/o extension).
container_key: The path to container signing key (w/o extension).
container_pw: The matching password of the container_key, or None.
codename_to_api_level_map: A dict that maps from codename to API level.
signing_args: Additional args to be passed to the payload signer.
Returns:
(signed_apex, payload_key_name): signed_apex is the path to the signed APEX
file; payload_key_name is a str of the payload signing key name (e.g.
com.android.tzdata).
"""
apex_file = common.MakeTempFile(prefix='apex-', suffix='.apex')
with open(apex_file, 'wb') as apex_fp:
apex_fp.write(apex_data)
APEX_PAYLOAD_IMAGE = 'apex_payload.img'
# Signing an APEX is a two step process.
# 1. Extract and sign the APEX_PAYLOAD_IMAGE entry with the given payload_key.
payload_dir = common.MakeTempDir(prefix='apex-payload-')
with zipfile.ZipFile(apex_file) as apex_fd:
payload_file = apex_fd.extract(APEX_PAYLOAD_IMAGE, payload_dir)
payload_info = apex_utils.ParseApexPayloadInfo(payload_file)
apex_utils.SignApexPayload(
payload_file,
payload_key,
payload_info['apex.key'],
payload_info['Algorithm'],
payload_info['Salt'],
signing_args)
common.ZipDelete(apex_file, APEX_PAYLOAD_IMAGE)
apex_zip = zipfile.ZipFile(apex_file, 'a')
common.ZipWrite(apex_zip, payload_file, arcname=APEX_PAYLOAD_IMAGE)
common.ZipClose(apex_zip)
# 2. Sign the overall APEX container with container_key.
signed_apex = common.MakeTempFile(prefix='apex-container-', suffix='.apex')
common.SignFile(
apex_file,
signed_apex,
container_key,
container_pw,
codename_to_api_level_map=codename_to_api_level_map)
signed_and_aligned_apex = common.MakeTempFile(
prefix='apex-container-', suffix='.apex')
common.RunAndCheckOutput(
['zipalign', '-f', '4096', signed_apex, signed_and_aligned_apex])
return (signed_and_aligned_apex, payload_info['apex.key'])
def ProcessTargetFiles(input_tf_zip, output_tf_zip, misc_info, def ProcessTargetFiles(input_tf_zip, output_tf_zip, misc_info,
apk_key_map, key_passwords, platform_api_level, apk_keys, apex_keys, key_passwords,
codename_to_api_level_map, platform_api_level, codename_to_api_level_map,
compressed_extension): compressed_extension):
# maxsize measures the maximum filename length, including the ones to be # maxsize measures the maximum filename length, including the ones to be
# skipped. # skipped.
@@ -304,6 +419,10 @@ def ProcessTargetFiles(input_tf_zip, output_tf_zip, misc_info,
if GetApkFileInfo(i.filename, compressed_extension, [])[0]]) if GetApkFileInfo(i.filename, compressed_extension, [])[0]])
system_root_image = misc_info.get("system_root_image") == "true" system_root_image = misc_info.get("system_root_image") == "true"
# A dict of APEX payload public keys that should be updated, i.e. the files
# under '/system/etc/security/apex/'.
updated_apex_payload_keys = {}
for info in input_tf_zip.infolist(): for info in input_tf_zip.infolist():
filename = info.filename filename = info.filename
if filename.startswith("IMAGES/"): if filename.startswith("IMAGES/"):
@@ -331,7 +450,7 @@ def ProcessTargetFiles(input_tf_zip, output_tf_zip, misc_info,
if is_compressed: if is_compressed:
name = name[:-len(compressed_extension)] name = name[:-len(compressed_extension)]
key = apk_key_map[name] key = apk_keys[name]
if key not in common.SPECIAL_CERT_STRINGS: if key not in common.SPECIAL_CERT_STRINGS:
print(" signing: %-*s (%s)" % (maxsize, name, key)) print(" signing: %-*s (%s)" % (maxsize, name, key))
signed_data = SignApk(data, key, key_passwords[key], platform_api_level, signed_data = SignApk(data, key, key_passwords[key], platform_api_level,
@@ -344,6 +463,30 @@ 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)
# Sign bundled APEX files.
elif filename.startswith("SYSTEM/apex") and filename.endswith(".apex"):
name = os.path.basename(filename)
payload_key, container_key = apex_keys[name]
print(" signing: %-*s container (%s)" % (maxsize, name, container_key))
print(" : %-*s payload (%s)" % (maxsize, name, payload_key))
(signed_apex, payload_key_name) = SignApex(
data,
payload_key,
container_key,
key_passwords[container_key],
codename_to_api_level_map,
OPTIONS.avb_extra_args.get('apex'))
common.ZipWrite(output_tf_zip, signed_apex, filename)
updated_apex_payload_keys[payload_key_name] = payload_key
# AVB public keys for the installed APEXes, which will be updated later.
elif (os.path.dirname(filename) == 'SYSTEM/etc/security/apex' and
filename != 'SYSTEM/etc/security/apex/'):
continue
# System properties. # System properties.
elif filename in ("SYSTEM/build.prop", elif filename in ("SYSTEM/build.prop",
"VENDOR/build.prop", "VENDOR/build.prop",
@@ -406,6 +549,30 @@ def ProcessTargetFiles(input_tf_zip, output_tf_zip, misc_info,
else: else:
common.ZipWriteStr(output_tf_zip, out_info, data) common.ZipWriteStr(output_tf_zip, out_info, data)
# Update APEX payload public keys.
for info in input_tf_zip.infolist():
filename = info.filename
if (os.path.dirname(filename) != 'SYSTEM/etc/security/apex' or
filename == 'SYSTEM/etc/security/apex/'):
continue
name = os.path.basename(filename)
assert name in updated_apex_payload_keys, \
'Unsigned APEX payload key: {}'.format(filename)
key_path = updated_apex_payload_keys[name]
if not os.path.exists(key_path) and not key_path.endswith('.pem'):
key_path = '{}.pem'.format(key_path)
assert os.path.exists(key_path), \
'Failed to find public key file {} for APEX {}'.format(
updated_apex_payload_keys[name], name)
print('Replacing APEX payload public key for {} with {}'.format(
name, key_path))
public_key = common.ExtractAvbPublicKey(key_path)
common.ZipWrite(output_tf_zip, public_key, arcname=filename)
if OPTIONS.replace_ota_keys: if OPTIONS.replace_ota_keys:
ReplaceOtaKeys(input_tf_zip, output_tf_zip, misc_info) ReplaceOtaKeys(input_tf_zip, output_tf_zip, misc_info)
@@ -821,6 +988,67 @@ def GetCodenameToApiLevelMap(input_tf_zip):
return result return result
def ReadApexKeysInfo(tf_zip):
"""Parses the APEX keys info from a given target-files zip.
Given a target-files ZipFile, parses the META/apexkeys.txt entry and returns a
dict that contains the mapping from APEX names (e.g. com.android.tzdata) to a
tuple of (payload_key, container_key).
Args:
tf_zip: The input target_files ZipFile (already open).
Returns:
(payload_key, container_key): payload_key contains the path to the payload
signing key; container_key contains the path to the container signing
key.
"""
keys = {}
for line in tf_zip.read("META/apexkeys.txt").split("\n"):
line = line.strip()
if not line:
continue
matches = re.match(
r'^name="(?P<NAME>.*)"\s+'
r'public_key="(?P<PAYLOAD_PUBLIC_KEY>.*)"\s+'
r'private_key="(?P<PAYLOAD_PRIVATE_KEY>.*)"\s+'
r'container_certificate="(?P<CONTAINER_CERT>.*)"\s+'
r'container_private_key="(?P<CONTAINER_PRIVATE_KEY>.*)"$',
line)
if not matches:
continue
name = matches.group('NAME')
payload_public_key = matches.group("PAYLOAD_PUBLIC_KEY")
payload_private_key = matches.group("PAYLOAD_PRIVATE_KEY")
def CompareKeys(pubkey, pubkey_suffix, privkey, privkey_suffix):
pubkey_suffix_len = len(pubkey_suffix)
privkey_suffix_len = len(privkey_suffix)
return (pubkey.endswith(pubkey_suffix) and
privkey.endswith(privkey_suffix) and
pubkey[:-pubkey_suffix_len] == privkey[:-privkey_suffix_len])
PAYLOAD_PUBLIC_KEY_SUFFIX = '.avbpubkey'
PAYLOAD_PRIVATE_KEY_SUFFIX = '.pem'
if not CompareKeys(
payload_public_key, PAYLOAD_PUBLIC_KEY_SUFFIX,
payload_private_key, PAYLOAD_PRIVATE_KEY_SUFFIX):
raise ValueError("Failed to parse payload keys: \n{}".format(line))
container_cert = matches.group("CONTAINER_CERT")
container_private_key = matches.group("CONTAINER_PRIVATE_KEY")
if not CompareKeys(
container_cert, OPTIONS.public_key_suffix,
container_private_key, OPTIONS.private_key_suffix):
raise ValueError("Failed to parse container keys: \n{}".format(line))
keys[name] = (payload_private_key,
container_cert[:-len(OPTIONS.public_key_suffix)])
return keys
def main(argv): def main(argv):
key_mapping_options = [] key_mapping_options = []
@@ -831,6 +1059,9 @@ def main(argv):
names = names.split(",") names = names.split(",")
for n in names: for n in names:
OPTIONS.extra_apks[n] = key OPTIONS.extra_apks[n] = key
elif o == "--extra_apex_payload_key":
apex_name, key = a.split("=")
OPTIONS.extra_apex_payload_keys[apex_name] = key
elif o == "--skip_apks_with_path_prefix": elif o == "--skip_apks_with_path_prefix":
# Sanity check the prefix, which must be in all upper case. # Sanity check the prefix, which must be in all upper case.
prefix = a.split('/')[0] prefix = a.split('/')[0]
@@ -887,6 +1118,8 @@ def main(argv):
OPTIONS.avb_algorithms['vendor'] = a OPTIONS.avb_algorithms['vendor'] = a
elif o == "--avb_vendor_extra_args": elif o == "--avb_vendor_extra_args":
OPTIONS.avb_extra_args['vendor'] = a OPTIONS.avb_extra_args['vendor'] = a
elif o == "--avb_apex_extra_args":
OPTIONS.avb_extra_args['apex'] = a
else: else:
return False return False
return True return True
@@ -896,6 +1129,7 @@ def main(argv):
extra_opts="e:d:k:ot:", extra_opts="e:d:k:ot:",
extra_long_opts=[ extra_long_opts=[
"extra_apks=", "extra_apks=",
"extra_apex_payload_key=",
"skip_apks_with_path_prefix=", "skip_apks_with_path_prefix=",
"default_key_mappings=", "default_key_mappings=",
"key_mapping=", "key_mapping=",
@@ -904,6 +1138,7 @@ def main(argv):
"replace_verity_public_key=", "replace_verity_public_key=",
"replace_verity_private_key=", "replace_verity_private_key=",
"replace_verity_keyid=", "replace_verity_keyid=",
"avb_apex_extra_args=",
"avb_vbmeta_algorithm=", "avb_vbmeta_algorithm=",
"avb_vbmeta_key=", "avb_vbmeta_key=",
"avb_vbmeta_extra_args=", "avb_vbmeta_extra_args=",
@@ -937,18 +1172,25 @@ def main(argv):
BuildKeyMap(misc_info, key_mapping_options) BuildKeyMap(misc_info, key_mapping_options)
certmap, compressed_extension = common.ReadApkCerts(input_zip) apk_keys_info, compressed_extension = common.ReadApkCerts(input_zip)
apk_key_map = GetApkCerts(certmap) apk_keys = GetApkCerts(apk_keys_info)
CheckAllApksSigned(input_zip, apk_key_map, compressed_extension)
key_passwords = common.GetKeyPasswords(set(apk_key_map.values())) apex_keys_info = ReadApexKeysInfo(input_zip)
apex_keys = GetApexKeys(apex_keys_info, apk_keys)
CheckApkAndApexKeysAvailable(
input_zip,
set(apk_keys.keys()) | set(apex_keys.keys()),
compressed_extension)
key_passwords = common.GetKeyPasswords(
set(apk_keys.values()) | set(itertools.chain(*apex_keys.values())))
platform_api_level, _ = GetApiLevelAndCodename(input_zip) platform_api_level, _ = GetApiLevelAndCodename(input_zip)
codename_to_api_level_map = GetCodenameToApiLevelMap(input_zip) codename_to_api_level_map = GetCodenameToApiLevelMap(input_zip)
ProcessTargetFiles(input_zip, output_zip, misc_info, ProcessTargetFiles(input_zip, output_zip, misc_info,
apk_key_map, key_passwords, apk_keys, apex_keys, key_passwords,
platform_api_level, platform_api_level, codename_to_api_level_map,
codename_to_api_level_map,
compressed_extension) compressed_extension)
common.ZipClose(input_zip) common.ZipClose(input_zip)

View File

@@ -0,0 +1,87 @@
#
# Copyright (C) 2019 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
import os
import os.path
import apex_utils
import common
import test_utils
class ApexUtilsTest(test_utils.ReleaseToolsTestCase):
# echo "foo" | sha256sum
SALT = 'b5bb9d8014a0f9b1d61e21e796d78dccdf1352f23cd32812f4850b878ae4944c'
def setUp(self):
self.testdata_dir = test_utils.get_testdata_dir()
# The default payload signing key.
self.payload_key = os.path.join(self.testdata_dir, 'testkey.key')
@staticmethod
def _GetTestPayload():
payload_file = common.MakeTempFile(prefix='apex-', suffix='.img')
with open(payload_file, 'wb') as payload_fp:
payload_fp.write(os.urandom(8192))
return payload_file
def test_ParseApexPayloadInfo(self):
payload_file = self._GetTestPayload()
apex_utils.SignApexPayload(
payload_file, self.payload_key, 'testkey', 'SHA256_RSA2048', self.SALT)
payload_info = apex_utils.ParseApexPayloadInfo(payload_file)
self.assertEqual('SHA256_RSA2048', payload_info['Algorithm'])
self.assertEqual(self.SALT, payload_info['Salt'])
self.assertEqual('testkey', payload_info['apex.key'])
def test_SignApexPayload(self):
payload_file = self._GetTestPayload()
apex_utils.SignApexPayload(
payload_file, self.payload_key, 'testkey', 'SHA256_RSA2048', self.SALT)
apex_utils.VerifyApexPayload(payload_file, self.payload_key)
def test_SignApexPayload_withSignerHelper(self):
payload_file = self._GetTestPayload()
payload_signer_args = '--signing_helper_with_files {}'.format(
os.path.join(self.testdata_dir, 'signing_helper.sh'))
apex_utils.SignApexPayload(
payload_file,
self.payload_key,
'testkey', 'SHA256_RSA2048', self.SALT,
payload_signer_args)
apex_utils.VerifyApexPayload(payload_file, self.payload_key)
def test_SignApexPayload_invalidKey(self):
self.assertRaises(
apex_utils.ApexSigningError,
apex_utils.SignApexPayload,
self._GetTestPayload(),
os.path.join(self.testdata_dir, 'testkey.x509.pem'),
'testkey',
'SHA256_RSA2048',
self.SALT)
def test_VerifyApexPayload_wrongKey(self):
payload_file = self._GetTestPayload()
apex_utils.SignApexPayload(
payload_file, self.payload_key, 'testkey', 'SHA256_RSA2048', self.SALT)
apex_utils.VerifyApexPayload(payload_file, self.payload_key)
self.assertRaises(
apex_utils.ApexSigningError,
apex_utils.VerifyApexPayload,
payload_file,
os.path.join(self.testdata_dir, 'testkey_with_passwd.key'))

View File

@@ -21,8 +21,8 @@ import zipfile
import common import common
import test_utils import test_utils
from sign_target_files_apks import ( from sign_target_files_apks import (
CheckAllApksSigned, EditTags, GetApkFileInfo, ReplaceCerts, CheckApkAndApexKeysAvailable, EditTags, GetApkFileInfo, ReadApexKeysInfo,
ReplaceVerityKeyId, RewriteProps) ReplaceCerts, ReplaceVerityKeyId, RewriteProps)
class SignTargetFilesApksTest(test_utils.ReleaseToolsTestCase): class SignTargetFilesApksTest(test_utils.ReleaseToolsTestCase):
@@ -33,6 +33,10 @@ class SignTargetFilesApksTest(test_utils.ReleaseToolsTestCase):
<signer signature="{}"><seinfo value="media"/></signer> <signer signature="{}"><seinfo value="media"/></signer>
</policy>""" </policy>"""
APEX_KEYS_TXT = """name="apex.apexd_test.apex" public_key="system/apex/apexd/apexd_testdata/com.android.apex.test_package.avbpubkey" private_key="system/apex/apexd/apexd_testdata/com.android.apex.test_package.pem" container_certificate="build/target/product/security/testkey.x509.pem" container_private_key="build/target/product/security/testkey.pk8"
name="apex.apexd_test_different_app.apex" public_key="system/apex/apexd/apexd_testdata/com.android.apex.test_package_2.avbpubkey" private_key="system/apex/apexd/apexd_testdata/com.android.apex.test_package_2.pem" container_certificate="build/target/product/security/testkey.x509.pem" container_private_key="build/target/product/security/testkey.pk8"
"""
def setUp(self): def setUp(self):
self.testdata_dir = test_utils.get_testdata_dir() self.testdata_dir = test_utils.get_testdata_dir()
@@ -207,7 +211,7 @@ class SignTargetFilesApksTest(test_utils.ReleaseToolsTestCase):
} }
self.assertEqual(output_xml, ReplaceCerts(input_xml)) self.assertEqual(output_xml, ReplaceCerts(input_xml))
def test_CheckAllApksSigned(self): def test_CheckApkAndApexKeysAvailable(self):
input_file = common.MakeTempFile(suffix='.zip') input_file = common.MakeTempFile(suffix='.zip')
with zipfile.ZipFile(input_file, 'w') as input_zip: with zipfile.ZipFile(input_file, 'w') as input_zip:
input_zip.writestr('SYSTEM/app/App1.apk', "App1-content") input_zip.writestr('SYSTEM/app/App1.apk', "App1-content")
@@ -219,16 +223,17 @@ class SignTargetFilesApksTest(test_utils.ReleaseToolsTestCase):
'App3.apk' : 'key3', 'App3.apk' : 'key3',
} }
with zipfile.ZipFile(input_file) as input_zip: with zipfile.ZipFile(input_file) as input_zip:
CheckAllApksSigned(input_zip, apk_key_map, None) CheckApkAndApexKeysAvailable(input_zip, apk_key_map, None)
CheckAllApksSigned(input_zip, apk_key_map, '.gz') CheckApkAndApexKeysAvailable(input_zip, apk_key_map, '.gz')
# 'App2.apk.gz' won't be considered as an APK. # 'App2.apk.gz' won't be considered as an APK.
CheckAllApksSigned(input_zip, apk_key_map, None) CheckApkAndApexKeysAvailable(input_zip, apk_key_map, None)
CheckAllApksSigned(input_zip, apk_key_map, '.xz') CheckApkAndApexKeysAvailable(input_zip, apk_key_map, '.xz')
del apk_key_map['App2.apk'] del apk_key_map['App2.apk']
self.assertRaises( self.assertRaises(
AssertionError, CheckAllApksSigned, input_zip, apk_key_map, '.gz') AssertionError, CheckApkAndApexKeysAvailable, input_zip, apk_key_map,
'.gz')
def test_GetApkFileInfo(self): def test_GetApkFileInfo(self):
(is_apk, is_compressed, should_be_skipped) = GetApkFileInfo( (is_apk, is_compressed, should_be_skipped) = GetApkFileInfo(
@@ -344,3 +349,62 @@ class SignTargetFilesApksTest(test_utils.ReleaseToolsTestCase):
self.assertRaises( self.assertRaises(
AssertionError, GetApkFileInfo, "SYSTEM_OTHER/preloads/apps/Chats.apk", AssertionError, GetApkFileInfo, "SYSTEM_OTHER/preloads/apps/Chats.apk",
None, None) None, None)
def test_ReadApexKeysInfo(self):
target_files = common.MakeTempFile(suffix='.zip')
with zipfile.ZipFile(target_files, 'w') as target_files_zip:
target_files_zip.writestr('META/apexkeys.txt', self.APEX_KEYS_TXT)
with zipfile.ZipFile(target_files) as target_files_zip:
keys_info = ReadApexKeysInfo(target_files_zip)
self.assertEqual(
{
'apex.apexd_test.apex': (
'system/apex/apexd/apexd_testdata/com.android.apex.test_package.pem',
'build/target/product/security/testkey'),
'apex.apexd_test_different_app.apex': (
'system/apex/apexd/apexd_testdata/com.android.apex.test_package_2.pem',
'build/target/product/security/testkey'),
},
keys_info)
def test_ReadApexKeysInfo_mismatchingKeys(self):
# Mismatching payload public / private keys.
apex_keys = self.APEX_KEYS_TXT + (
'name="apex.apexd_test_different_app2.apex" '
'public_key="system/apex/apexd/apexd_testdata/com.android.apex.test_package_2.avbpubkey" '
'private_key="system/apex/apexd/apexd_testdata/com.android.apex.test_package_3.pem" '
'container_certificate="build/target/product/security/testkey.x509.pem" '
'container_private_key="build/target/product/security/testkey.pk8"')
target_files = common.MakeTempFile(suffix='.zip')
with zipfile.ZipFile(target_files, 'w') as target_files_zip:
target_files_zip.writestr('META/apexkeys.txt', apex_keys)
with zipfile.ZipFile(target_files) as target_files_zip:
self.assertRaises(ValueError, ReadApexKeysInfo, target_files_zip)
def test_ReadApexKeysInfo_missingPrivateKey(self):
# Invalid lines will be skipped.
apex_keys = self.APEX_KEYS_TXT + (
'name="apex.apexd_test_different_app2.apex" '
'public_key="system/apex/apexd/apexd_testdata/com.android.apex.test_package_2.avbpubkey" '
'container_certificate="build/target/product/security/testkey.x509.pem" '
'container_private_key="build/target/product/security/testkey.pk8"')
target_files = common.MakeTempFile(suffix='.zip')
with zipfile.ZipFile(target_files, 'w') as target_files_zip:
target_files_zip.writestr('META/apexkeys.txt', apex_keys)
with zipfile.ZipFile(target_files) as target_files_zip:
keys_info = ReadApexKeysInfo(target_files_zip)
self.assertEqual(
{
'apex.apexd_test.apex': (
'system/apex/apexd/apexd_testdata/com.android.apex.test_package.pem',
'build/target/product/security/testkey'),
'apex.apexd_test_different_app.apex': (
'system/apex/apexd/apexd_testdata/com.android.apex.test_package_2.pem',
'build/target/product/security/testkey'),
},
keys_info)

21
tools/releasetools/testdata/signing_helper.sh vendored Executable file
View File

@@ -0,0 +1,21 @@
#!/bin/sh
#
# Copyright (C) 2019 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
tmpfile=$(mktemp)
cat $3 | openssl rsautl -sign -inkey $2 -raw > $tmpfile
cat $tmpfile > $3
rm $tmpfile

View File

@@ -0,0 +1,28 @@
-----BEGIN PRIVATE KEY-----
MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQCwaAOHPqgkCmqU
AFRnJW6LrAuSfy9EzWSRHSkltp811ByMIE0N6/Nttu8ZCL456lzArHNKt/zdoBik
eLB6gN9CTvQ8n4LMdSEmkRl3uXBtOPJuVObJ6ZUILz6L7WofWcr8DT81j2At7nHi
Wg8SkCsFXbFfpjljOlpqUG3Szt+48X8rcgG82s97BuRwNxUgfK1/8QzOiH9fDbMU
h6XI2jo2VwuBYOsJadJJWOf6oRRHZonrts0FXpV46CXykpLvLT2u5GXg1Pxd7i1K
v1P8bxZOzVbEVfkL2DnUCtUBAnP98r9UyjQDd4blk4Mwl+mzB5otPTacNzEGhmNK
Et+HB/cdAgMBAAECggEATsn2IXa7tHUuivHmwLb4O8vY01KY8xrleubSVPTPAUS+
h1t57ujerbcR7VV5WPay/J9JUyr/9qClwPfioqRikwQek+EOk3ERIF+YR1/8tdvE
c8DZ337DQIeRYP/l8SCyx4bHH43tADbKiLV+m+TmQhxJt5XPdeE/NtK7andZdwkv
xEoG9l2aONE4z9pY1x+c1SdDSsq92/iLHLgSkQJmWo+lrfeh6gshXgQgDY8n6rgY
GsCgSawLphvd8Tvo86CL04l0pWtY1gEW3s6sdYo1YDkpWQzSRCtGm0GlhEt2fyq5
coTK2sLHguE7NL5VZo4zlGtM3QBdvRksTO1mJOt6JQKBgQDaT4oGjZp1rtKdObvn
ElaUo5EOyJjmXkRBBndrbiG3078eOqTJHXx45DJUv8hj9+g6vSULiIeFk1FiiMQD
vcnsBEaGaSc886wXY6TQgIIzvVfzDHGYTuQydiYQbLClH6S28HLqdlZjUIlHwxb9
wBm8JwmTiVeAEvO8LTzeEqfkLwKBgQDO3He8Ei8XDeqtIK0lzcZ83yw9OGP23/gK
8GDaf8J+cOtOyYkDlcV0rBNFvE8+TzIpIUlo47b2RSaART3iPSfRJTaySZjKWCVo
s2A0/zQcrj7GgD2gaHRrgI9bmnWW1j95a9n/6AUEyEIJ6K8tYK819Vl4GAyhNHEQ
sRbxa69qcwKBgQC5F8jxx2tXLdM6JLIQtzabLZcWTrN8Vh5Od3oWpriF0EzxB02h
ipN3OBsISdZQE+dcrfNTtP0aHo5ZGZX/ihFCP1nAKjVvczXMWtppQRujXHzOABXr
ya+mrQ+Wy2B1j7+qr3DvI0gZSjYqltjOaeon4X04DrEWUHtAZ6Z8rpqUVwKBgQCB
o8mmI/8/A4m/Vmss9fke6P5gn6aGYXah5GPOi6Loevv9NHCZvpMwu2aYnZtMAXX+
MM5A3fUcAdpPKRXPY2RAvoG42kbXCMbpBwGUNRwDnW/aFySIEu5jMP6m+fYXwc2l
2uGUb2Q1ywsYCqs+VQl5V3nquaewn5z8SP+H7WTR4QKBgQCO5CRpyNOjEwMxTPR1
GYUKAEiVtmzknHAxUE6drTgGEZSquAXiau0B5+7+/G5gwqxCLGpnstMByI+dhkR6
+ybAc/bzb2aoGK4pZf/PuwxQQsHBnG0oaSFU6RZlbVV20j7FZ04+cYnKHwCYkKjN
DwA1Ae+H+u95raB4vYhk7IzD4A==
-----END PRIVATE KEY-----