certify_bootimg: support --gki_info for a boot.img

The gki_info file can be used to append additional
'extra_args' during the certification process.

An usage example:
  certify_bootimg --boot_img_archive boot-img.tar.gz \
      --algorithm SHA256_RSA4096 \
      --key external/avb/test/data/testkey_rsa4096.pem \
      --extra_args "--prop foo:bar" \
      --gki_info /path/to/gki-info.txt \
      --output boot-certified-img.tar.gz

An example of the file content of the gki-info.txt:
  certify_bootimg_extra_args=--prop KERNEL_RELEASE:5.10.107-android13-1-00361-gf1e8564c5530-ab8332003

Bug: 230426945
Test: atest --host certify_bootimg_test
Change-Id: I484f77b4fd6eacb3cdfc2270543a2cd6d33d58c1
diff --git a/gki/certify_bootimg.py b/gki/certify_bootimg.py
index 8e3d1af..fc22fde 100755
--- a/gki/certify_bootimg.py
+++ b/gki/certify_bootimg.py
@@ -160,6 +160,14 @@
     return d
 
 
+def load_gki_info_file(gki_info_file, extra_args):
+    """Loads extra args from |gki_info_file| into |extra_args|."""
+    info_dict = load_dict_from_file(gki_info_file)
+    if 'certify_bootimg_extra_args' in info_dict:
+        extra_args.extend(
+            shlex.split(info_dict['certify_bootimg_extra_args']))
+
+
 def get_archive_name_and_format_for_shutil(path):
     """Returns archive name and format to shutil.make_archive() for the |path|.
 
@@ -189,6 +197,9 @@
                         help='signing algorithm for the certificate')
     parser.add_argument('--key', required=True,
                         help='path to the RSA private key')
+    parser.add_argument('--gki_info',
+                        help='path to a gki-info.txt to append additional'
+                             'properties into the boot signature')
     parser.add_argument('-o', '--output', required=True,
                         help='output file name')
 
@@ -198,11 +209,18 @@
 
     args = parser.parse_args()
 
+    if args.gki_info and args.boot_img_archive:
+        parser.error('--gki_info cannot be used with --boot_image_archive. '
+                     'The gki_info file should be included in the archive.')
+
     extra_args = []
     for a in args.extra_args:
         extra_args.extend(shlex.split(a))
     args.extra_args = extra_args
 
+    if args.gki_info:
+        load_gki_info_file(args.gki_info, args.extra_args)
+
     return args
 
 
@@ -230,10 +248,7 @@
 
         gki_info_file = os.path.join(unpack_dir, 'gki-info.txt')
         if os.path.exists(gki_info_file):
-            info_dict = load_dict_from_file(gki_info_file)
-            if 'certify_bootimg_extra_args' in info_dict:
-                extra_args.extend(
-                    shlex.split(info_dict['certify_bootimg_extra_args']))
+            load_gki_info_file(gki_info_file, extra_args)
 
         for boot_img in glob.glob(os.path.join(unpack_dir, 'boot-*.img')):
             print(f'Certifying {os.path.basename(boot_img)} ...')
diff --git a/gki/certify_bootimg_test.py b/gki/certify_bootimg_test.py
index b7d367e..c0de50a 100644
--- a/gki/certify_bootimg_test.py
+++ b/gki/certify_bootimg_test.py
@@ -323,6 +323,68 @@
             "    Prop: space -> 'nice to meet you'\n"
         )
 
+        self._EXPECTED_BOOT_SIGNATURE_WITH_GKI_INFO = (  # pylint: disable=C0103
+            'Minimum libavb version:   1.0\n'
+            'Header Block:             256 bytes\n'
+            'Authentication Block:     576 bytes\n'
+            'Auxiliary Block:          1600 bytes\n'
+            'Public key (sha1):        '
+            '2597c218aae470a130f61162feaae70afd97f011\n'
+            'Algorithm:                SHA256_RSA4096\n' # RSA4096
+            'Rollback Index:           0\n'
+            'Flags:                    0\n'
+            'Rollback Index Location:  0\n'
+            "Release String:           'avbtool 1.2.0'\n"
+            'Descriptors:\n'
+            '    Hash descriptor:\n'
+            '      Image Size:            8192 bytes\n'
+            '      Hash Algorithm:        sha256\n'
+            '      Partition Name:        boot\n'        # boot
+            '      Salt:                  d00df00d\n'
+            '      Digest:                '
+            'faf1da72a4fba97ddab0b8f7a410db86'
+            '8fb72392a66d1440ff8bff490c73c771\n'
+            '      Flags:                 0\n'
+            "    Prop: gki -> 'nice'\n"
+            "    Prop: space -> 'nice to meet you'\n"
+            "    Prop: KERNEL_RELEASE -> '5.10.42-android13-0-00544-"
+            "ged21d463f856'\n"
+            "    Prop: BRANCH -> 'android13-5.10-2022-05'\n"
+            "    Prop: BUILD_NUMBER -> 'ab8295296'\n"
+            "    Prop: GKI_INFO -> 'added here'\n"
+        )
+
+        self._EXPECTED_KERNEL_SIGNATURE_WITH_GKI_INFO = (# pylint: disable=C0103
+            'Minimum libavb version:   1.0\n'
+            'Header Block:             256 bytes\n'
+            'Authentication Block:     576 bytes\n'
+            'Auxiliary Block:          1600 bytes\n'
+            'Public key (sha1):        '
+            '2597c218aae470a130f61162feaae70afd97f011\n'
+            'Algorithm:                SHA256_RSA4096\n' # RSA4096
+            'Rollback Index:           0\n'
+            'Flags:                    0\n'
+            'Rollback Index Location:  0\n'
+            "Release String:           'avbtool 1.2.0'\n"
+            'Descriptors:\n'
+            '    Hash descriptor:\n'
+            '      Image Size:            4096 bytes\n'
+            '      Hash Algorithm:        sha256\n'
+            '      Partition Name:        generic_kernel\n' # generic_kernel
+            '      Salt:                  d00df00d\n'
+            '      Digest:                '
+            '762c877f3af0d50a4a4fbc1385d5c7ce'
+            '52a1288db74b33b72217d93db6f2909f\n'
+            '      Flags:                 0\n'
+            "    Prop: gki -> 'nice'\n"
+            "    Prop: space -> 'nice to meet you'\n"
+            "    Prop: KERNEL_RELEASE -> '5.10.42-android13-0-00544-"
+            "ged21d463f856'\n"
+            "    Prop: BRANCH -> 'android13-5.10-2022-05'\n"
+            "    Prop: BUILD_NUMBER -> 'ab8295296'\n"
+            "    Prop: GKI_INFO -> 'added here'\n"
+        )
+
         self._EXPECTED_BOOT_1_0_SIGNATURE1_RSA4096 = (   # pylint: disable=C0103
             'Minimum libavb version:   1.0\n'
             'Header Block:             256 bytes\n'
@@ -625,6 +687,52 @@
                 {'boot_signature1': self._EXPECTED_BOOT_SIGNATURE_RSA4096,
                  'boot_signature2': self._EXPECTED_KERNEL_SIGNATURE_RSA4096})
 
+    def test_certify_bootimg_with_gki_info(self):
+        """Tests certify_bootimg with --gki_info."""
+        with tempfile.TemporaryDirectory() as temp_out_dir:
+            boot_img = os.path.join(temp_out_dir, 'boot.img')
+            generate_test_boot_image(boot_img=boot_img,
+                                     avb_partition_size=128 * 1024)
+            self.assertTrue(has_avb_footer(boot_img))
+
+            gki_info = ('certify_bootimg_extra_args='
+                        '--prop KERNEL_RELEASE:5.10.42'
+                        '-android13-0-00544-ged21d463f856 '
+                        '--prop BRANCH:android13-5.10-2022-05 '
+                        '--prop BUILD_NUMBER:ab8295296 '
+                        '--prop GKI_INFO:"added here"\n')
+            gki_info_path = os.path.join(temp_out_dir, 'gki-info.txt')
+            with open(gki_info_path, 'w', encoding='utf-8') as f:
+                f.write(gki_info)
+
+            # Certifies the boot image with --gki_info.
+            boot_certified_img = os.path.join(temp_out_dir,
+                                              'boot-certified.img')
+            certify_bootimg_cmds = [
+                'certify_bootimg',
+                '--boot_img', boot_img,
+                '--algorithm', 'SHA256_RSA4096',
+                '--key', './testdata/testkey_rsa4096.pem',
+                '--extra_args', '--prop gki:nice '
+                '--prop space:"nice to meet you"',
+                '--gki_info', gki_info_path,
+                '--output', boot_certified_img,
+            ]
+            subprocess.run(certify_bootimg_cmds, check=True, cwd=self._exec_dir)
+
+            # Checks an AVB footer exists and the image size remains.
+            self.assertTrue(has_avb_footer(boot_certified_img))
+            self.assertEqual(os.path.getsize(boot_img),
+                             os.path.getsize(boot_certified_img))
+
+            extract_boot_signatures(boot_certified_img, temp_out_dir)
+            self._test_boot_signatures(
+                temp_out_dir,
+                {'boot_signature1':
+                    self._EXPECTED_BOOT_SIGNATURE_WITH_GKI_INFO,
+                 'boot_signature2':
+                    self._EXPECTED_KERNEL_SIGNATURE_WITH_GKI_INFO})
+
     def test_certify_bootimg_exceed_size(self):
         """Tests the boot signature size exceeded max size of the signature."""
         with tempfile.TemporaryDirectory() as temp_out_dir: