Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit e134399c authored by Tao Bao's avatar Tao Bao
Browse files

releasetools: Allow skipping PRESIGNED APEXes.

This CL adds support that allows treating an APEX as pre-signed. We can
skip signing an APEX with `-e <apex-name>=` and
`--extra_apex_payload_key <apex-name>=`. Note that the payload_key and
container_key must be in consistent state - either they're both
PRESIGNED or none of them is. CheckApkAndApexKeysAvailable() has been
updated to perform the sanity check.

Bug: 123716522
Test: Run sign_target_files_apks.py with the above flags.
Test: python -m unittest test_sign_target_files_apks
Change-Id: Id1e2f3f2facd4a97a385983cc9b78c028f7e7e73
parent e3f9dc61
Loading
Loading
Loading
Loading
+61 −19
Original line number Diff line number Diff line
@@ -166,7 +166,7 @@ def GetApkCerts(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.
  Presigned payload / container keys will be set accordingly.

  Args:
    keys_info: A dict that maps from APEX filenames to a tuple of (payload_key,
@@ -180,7 +180,8 @@ def GetApexKeys(keys_info, key_map):
  # 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)
    if not key:
      key = 'PRESIGNED'
    keys_info[apex] = (key, keys_info[apex][1])

  # Apply the key remapping to container keys.
@@ -192,7 +193,8 @@ def GetApexKeys(keys_info, key_map):
    # Skip non-APEX containers.
    if apex not in keys_info:
      continue
    assert key, 'Presigned APEX container for {} is not allowed'.format(apex)
    if not key:
      key = 'PRESIGNED'
    keys_info[apex] = (keys_info[apex][0], key_map.get(key, key))

  return keys_info
@@ -245,7 +247,7 @@ def GetApkFileInfo(filename, compressed_extension, skipped_prefixes):


def CheckApkAndApexKeysAvailable(input_tf_zip, known_keys,
                                 compressed_extension):
                                 compressed_extension, apex_keys):
  """Checks that all the APKs and APEXes have keys specified.

  Args:
@@ -253,6 +255,8 @@ def CheckApkAndApexKeysAvailable(input_tf_zip, known_keys,
    known_keys: A set of APKs and APEXes that have known signing keys.
    compressed_extension: The extension string of compressed APKs, such as
        '.gz', or None if there's no compressed APKs.
    apex_keys: A dict that contains the key mapping from APEX name to
        (payload_key, container_key).

  Raises:
    AssertionError: On finding unknown APKs and APEXes.
@@ -284,6 +288,31 @@ def CheckApkAndApexKeysAvailable(input_tf_zip, known_keys,
       "Use '-e <apkname>=' to specify a key (which may be an empty string to "
       "not sign this apk).".format("\n  ".join(unknown_files)))

  # For all the APEXes, double check that we won't have an APEX that has only
  # one of the payload / container keys set.
  if not apex_keys:
    return

  invalid_apexes = []
  for info in input_tf_zip.infolist():
    if (not info.filename.startswith('SYSTEM/apex') or
        not info.filename.endswith('.apex')):
      continue

    name = os.path.basename(info.filename)
    (payload_key, container_key) = apex_keys[name]
    if ((payload_key in common.SPECIAL_CERT_STRINGS and
         container_key not in common.SPECIAL_CERT_STRINGS) or
        (payload_key not in common.SPECIAL_CERT_STRINGS and
         container_key in common.SPECIAL_CERT_STRINGS)):
      invalid_apexes.append(
          "{}: payload_key {}, container_key {}".format(
              name, payload_key, container_key))

  assert not invalid_apexes, \
      "Invalid APEX keys specified:\n  {}\n".format(
          "\n  ".join(invalid_apexes))


def SignApk(data, keyname, pw, platform_api_level, codename_to_api_level_map,
            is_compressed):
@@ -468,8 +497,13 @@ def ProcessTargetFiles(input_tf_zip, output_tf_zip, misc_info,
      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))
      # We've asserted not having a case with only one of them PRESIGNED.
      if (payload_key not in common.SPECIAL_CERT_STRINGS and
          container_key not in common.SPECIAL_CERT_STRINGS):
        print("    signing: %-*s container (%s)" % (
            maxsize, name, container_key))
        print("           : %-*s payload   (%s)" % (
            maxsize, name, payload_key))

        (signed_apex, payload_key_name) = SignApex(
            data,
@@ -479,9 +513,14 @@ def ProcessTargetFiles(input_tf_zip, output_tf_zip, misc_info,
            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

      else:
        print(
            "NOT signing: %s\n"
            "        (skipped due to special cert string)" % (name,))
        common.ZipWriteStr(output_tf_zip, out_info, data)

    # 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/'):
@@ -557,8 +596,10 @@ def ProcessTargetFiles(input_tf_zip, output_tf_zip, misc_info,
      continue

    name = os.path.basename(filename)
    assert name in updated_apex_payload_keys, \
        'Unsigned APEX payload key: {}'.format(filename)

    # Skip PRESIGNED APEXes.
    if name not in updated_apex_payload_keys:
      continue

    key_path = updated_apex_payload_keys[name]
    if not os.path.exists(key_path) and not key_path.endswith('.pem'):
@@ -1181,7 +1222,8 @@ def main(argv):
  CheckApkAndApexKeysAvailable(
      input_zip,
      set(apk_keys.keys()) | set(apex_keys.keys()),
      compressed_extension)
      compressed_extension,
      apex_keys)

  key_passwords = common.GetKeyPasswords(
      set(apk_keys.values()) | set(itertools.chain(*apex_keys.values())))
+55 −25
Original line number Diff line number Diff line
@@ -33,6 +33,7 @@ class SignTargetFilesApksTest(test_utils.ReleaseToolsTestCase):
  <signer signature="{}"><seinfo value="media"/></signer>
</policy>"""

  # pylint: disable=line-too-long
  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"
"""
@@ -223,17 +224,50 @@ name="apex.apexd_test_different_app.apex" public_key="system/apex/apexd/apexd_te
        'App3.apk' : 'key3',
    }
    with zipfile.ZipFile(input_file) as input_zip:
      CheckApkAndApexKeysAvailable(input_zip, apk_key_map, None)
      CheckApkAndApexKeysAvailable(input_zip, apk_key_map, '.gz')
      CheckApkAndApexKeysAvailable(input_zip, apk_key_map, None, {})
      CheckApkAndApexKeysAvailable(input_zip, apk_key_map, '.gz', {})

      # 'App2.apk.gz' won't be considered as an APK.
      CheckApkAndApexKeysAvailable(input_zip, apk_key_map, None)
      CheckApkAndApexKeysAvailable(input_zip, apk_key_map, '.xz')
      CheckApkAndApexKeysAvailable(input_zip, apk_key_map, None, {})
      CheckApkAndApexKeysAvailable(input_zip, apk_key_map, '.xz', {})

      del apk_key_map['App2.apk']
      self.assertRaises(
          AssertionError, CheckApkAndApexKeysAvailable, input_zip, apk_key_map,
          '.gz')
          '.gz', {})

  def test_CheckApkAndApexKeysAvailable_invalidApexKeys(self):
    input_file = common.MakeTempFile(suffix='.zip')
    with zipfile.ZipFile(input_file, 'w') as input_zip:
      input_zip.writestr('SYSTEM/apex/Apex1.apex', "Apex1-content")
      input_zip.writestr('SYSTEM/apex/Apex2.apex', "Apex2-content")

    apk_key_map = {
        'Apex1.apex' : 'key1',
        'Apex2.apex' : 'key2',
        'Apex3.apex' : 'key3',
    }
    apex_keys = {
        'Apex1.apex' : ('payload-key1', 'container-key1'),
        'Apex2.apex' : ('payload-key2', 'container-key2'),
    }
    with zipfile.ZipFile(input_file) as input_zip:
      CheckApkAndApexKeysAvailable(input_zip, apk_key_map, None, apex_keys)

      # Fine to have both keys as PRESIGNED.
      apex_keys['Apex2.apex'] = ('PRESIGNED', 'PRESIGNED')
      CheckApkAndApexKeysAvailable(input_zip, apk_key_map, None, apex_keys)

      # Having only one of them as PRESIGNED is not allowed.
      apex_keys['Apex2.apex'] = ('payload-key2', 'PRESIGNED')
      self.assertRaises(
          AssertionError, CheckApkAndApexKeysAvailable, input_zip, apk_key_map,
          None, apex_keys)

      apex_keys['Apex2.apex'] = ('PRESIGNED', 'container-key1')
      self.assertRaises(
          AssertionError, CheckApkAndApexKeysAvailable, input_zip, apk_key_map,
          None, apex_keys)

  def test_GetApkFileInfo(self):
    (is_apk, is_compressed, should_be_skipped) = GetApkFileInfo(
@@ -358,16 +392,14 @@ name="apex.apexd_test_different_app.apex" public_key="system/apex/apexd/apexd_te
    with zipfile.ZipFile(target_files) as target_files_zip:
      keys_info = ReadApexKeysInfo(target_files_zip)

    self.assertEqual(
        {
    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)
        }, keys_info)

  def test_ReadApexKeysInfo_mismatchingKeys(self):
    # Mismatching payload public / private keys.
@@ -398,13 +430,11 @@ name="apex.apexd_test_different_app.apex" public_key="system/apex/apexd/apexd_te
    with zipfile.ZipFile(target_files) as target_files_zip:
      keys_info = ReadApexKeysInfo(target_files_zip)

    self.assertEqual(
        {
    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)
        }, keys_info)