Support GKI boot.img v4 signing

Commit I9967d06bde0e18a12b84b5b0b568db09765fe305 supports adding a
generic boot_signature into boot.img v4. This change allows replacing
the boot_signture signing key with a release key during the release
process.

The default GKI signing key can be specified in a BoardConfig.mk via:

  BOARD_GKI_SIGNING_KEY_PATH := external/avb/test/data/testkey_rsa2048.pem
  BOARD_GKI_SIGNING_ALGORITHM := SHA256_RSA2048
  BOARD_GKI_SIGNING_SIGNATURE_ARGS := --prop foo:bar

The release signing key/algorithm can be specified by the following options
when invoking sign_target_files_apks:

  --gki_signing_key=external/avb/test/data/testkey_rsa4096.pem
  --gki_signing_algorithm=SHA256_RSA4096

Additional arguments for generating the GKI signature can be
specified as below:

  --gki_signing_extra_args="--prop gki:prop1 --prop gki:prop2"

Bug: 177862434
Test: make dist
Test: sign_target_files_apks \
        --gki_signing_key=external/avb/test/data/testkey_rsa4096.pem \
        --gki_signing_algorithm=SHA256_RSA4096 \
        --gki_signing_extra_args="--prop gki:prop1 --prop gki:prop2" \
        ./out/dist/*-target_files-eng.*.zip signed.zip
Test: Checks GKI boot_signature is expected after signing:
      `unzip signed.zip IMAGES/boot.img`
      `unpack_bootimg --boot_img IMAGES/boot.img --out unpack`
      `avbtool info_image --image unpack/boot_signature`
Test: unit test: releasetools_test and releasetools_py3_test

Change-Id: I61dadbc242360e4cab3dc70295931b4a5b9422a9
diff --git a/tools/releasetools/test_common.py b/tools/releasetools/test_common.py
index ecd759c..a516366 100644
--- a/tools/releasetools/test_common.py
+++ b/tools/releasetools/test_common.py
@@ -1670,6 +1670,127 @@
                   common.OPTIONS.aftl_key_path]
     common.RunAndCheckOutput(verify_cmd)
 
+  @test_utils.SkipIfExternalToolsUnavailable()
+  def test_AppendGkiSigningArgs_NoSigningKeyPath(self):
+    # A non-GKI boot.img has no gki_signing_key_path.
+    common.OPTIONS.info_dict = {
+        # 'gki_signing_key_path': pubkey,
+        'gki_signing_algorithm': 'SHA256_RSA4096',
+        'gki_signing_signature_args': '--prop foo:bar',
+    }
+
+    # Tests no --gki_signing_* args are appended if there is no
+    # gki_signing_key_path.
+    cmd = ['mkbootimg', '--header_version', '4']
+    expected_cmd = ['mkbootimg', '--header_version', '4']
+    common.AppendGkiSigningArgs(cmd)
+    self.assertEqual(cmd, expected_cmd)
+
+  def test_AppendGkiSigningArgs_NoSigningAlgorithm(self):
+    pubkey = os.path.join(self.testdata_dir, 'testkey_gki.pem')
+    with open(pubkey, 'wb') as f:
+      f.write(b'\x00' * 100)
+    self.assertTrue(os.path.exists(pubkey))
+
+    # Tests no --gki_signing_* args are appended if there is no
+    # gki_signing_algorithm.
+    common.OPTIONS.info_dict = {
+        'gki_signing_key_path': pubkey,
+        # 'gki_signing_algorithm': 'SHA256_RSA4096',
+        'gki_signing_signature_args': '--prop foo:bar',
+    }
+
+    cmd = ['mkbootimg', '--header_version', '4']
+    expected_cmd = ['mkbootimg', '--header_version', '4']
+    common.AppendGkiSigningArgs(cmd)
+    self.assertEqual(cmd, expected_cmd)
+
+  @test_utils.SkipIfExternalToolsUnavailable()
+  def test_AppendGkiSigningArgs(self):
+    pubkey = os.path.join(self.testdata_dir, 'testkey_gki.pem')
+    with open(pubkey, 'wb') as f:
+      f.write(b'\x00' * 100)
+    self.assertTrue(os.path.exists(pubkey))
+
+    common.OPTIONS.info_dict = {
+        'gki_signing_key_path': pubkey,
+        'gki_signing_algorithm': 'SHA256_RSA4096',
+        'gki_signing_signature_args': '--prop foo:bar',
+    }
+    cmd = ['mkbootimg', '--header_version', '4']
+    common.AppendGkiSigningArgs(cmd)
+
+    expected_cmd = [
+      'mkbootimg', '--header_version', '4',
+      '--gki_signing_key', pubkey,
+      '--gki_signing_algorithm', 'SHA256_RSA4096',
+      '--gki_signing_signature_args', '--prop foo:bar'
+    ]
+    self.assertEqual(cmd, expected_cmd)
+
+  @test_utils.SkipIfExternalToolsUnavailable()
+  def test_AppendGkiSigningArgs_KeyPathNotFound(self):
+    pubkey = os.path.join(self.testdata_dir, 'no_testkey_gki.pem')
+    self.assertFalse(os.path.exists(pubkey))
+
+    common.OPTIONS.info_dict = {
+        'gki_signing_key_path': pubkey,
+        'gki_signing_algorithm': 'SHA256_RSA4096',
+        'gki_signing_signature_args': '--prop foo:bar',
+    }
+    cmd = ['mkbootimg', '--header_version', '4']
+    self.assertRaises(common.ExternalError, common.AppendGkiSigningArgs, cmd)
+
+  @test_utils.SkipIfExternalToolsUnavailable()
+  def test_AppendGkiSigningArgs_SearchKeyPath(self):
+    pubkey = 'testkey_gki.pem'
+    self.assertFalse(os.path.exists(pubkey))
+
+    # Tests it should replace the pubkey with an existed key under
+    # OPTIONS.search_path, i.e., os.path.join(OPTIONS.search_path, pubkey).
+    search_path_dir = common.MakeTempDir()
+    search_pubkey = os.path.join(search_path_dir, pubkey)
+    with open(search_pubkey, 'wb') as f:
+      f.write(b'\x00' * 100)
+    self.assertTrue(os.path.exists(search_pubkey))
+
+    common.OPTIONS.search_path = search_path_dir
+    common.OPTIONS.info_dict = {
+        'gki_signing_key_path': pubkey,
+        'gki_signing_algorithm': 'SHA256_RSA4096',
+        'gki_signing_signature_args': '--prop foo:bar',
+    }
+    cmd = ['mkbootimg', '--header_version', '4']
+    common.AppendGkiSigningArgs(cmd)
+
+    expected_cmd = [
+      'mkbootimg', '--header_version', '4',
+      '--gki_signing_key', search_pubkey,
+      '--gki_signing_algorithm', 'SHA256_RSA4096',
+      '--gki_signing_signature_args', '--prop foo:bar'
+    ]
+    self.assertEqual(cmd, expected_cmd)
+
+  @test_utils.SkipIfExternalToolsUnavailable()
+  def test_AppendGkiSigningArgs_SearchKeyPathNotFound(self):
+    pubkey = 'no_testkey_gki.pem'
+    self.assertFalse(os.path.exists(pubkey))
+
+    # Tests it should raise ExternalError if no key found under
+    # OPTIONS.search_path.
+    search_path_dir = common.MakeTempDir()
+    search_pubkey = os.path.join(search_path_dir, pubkey)
+    self.assertFalse(os.path.exists(search_pubkey))
+
+    common.OPTIONS.search_path = search_path_dir
+    common.OPTIONS.info_dict = {
+        'gki_signing_key_path': pubkey,
+        'gki_signing_algorithm': 'SHA256_RSA4096',
+        'gki_signing_signature_args': '--prop foo:bar',
+    }
+    cmd = ['mkbootimg', '--header_version', '4']
+    self.assertRaises(common.ExternalError, common.AppendGkiSigningArgs, cmd)
+
 
 class InstallRecoveryScriptFormatTest(test_utils.ReleaseToolsTestCase):
   """Checks the format of install-recovery.sh.