• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/env python3
2#
3# Copyright (C) 2024 The Android Open Source Project
4#
5# Licensed under the Apache License, Version 2.0 (the "License");
6# you may not use this file except in compliance with the License.
7# You may obtain a copy of the License at
8#
9#     http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
16"""Generate test data files for libgbl tests"""
17
18import argparse
19import gzip
20import os
21import pathlib
22import random
23import re
24import shutil
25import subprocess
26import tempfile
27
28SCRIPT_DIR = pathlib.Path(os.path.dirname(os.path.realpath(__file__)))
29AOSP_ROOT = SCRIPT_DIR.parents[4]
30GBL_ROOT = SCRIPT_DIR.parents[1]
31ANDROID_OUT = SCRIPT_DIR / "android"
32GPT_TOOL = GBL_ROOT / "tools" / "gen_gpt_disk.py"
33AVB_DIR = AOSP_ROOT / "external" / "avb"
34AVB_TOOL = AVB_DIR / "avbtool.py"
35MKBOOTIMG_TOOL = AOSP_ROOT / "tools" / "mkbootimg" / "mkbootimg.py"
36UNPACKBOOTIMG_TOOL = AOSP_ROOT / "tools" / "mkbootimg" / "unpack_bootimg.py"
37AVB_TEST_DATA_DIR = AVB_DIR / "test" / "data"
38DTC_TOOL = (
39    AOSP_ROOT / "prebuilts" / "kernel-build-tools" / "linux-x86" / "bin" / "dtc"
40)
41MKDTBOIMG_TOOL = (
42    AOSP_ROOT
43    / "prebuilts"
44    / "kernel-build-tools"
45    / "linux-x86"
46    / "bin"
47    / "mkdtboimg"
48)
49LZ4_TOOL = "lz4"
50SZ_KB = 1024
51
52# Manually downloaded from Android CI:
53# https://android-build.corp.google.com/build_explorer/branch/aosp_kernel-common-android-mainline
54GKI_BOOT_GZ = ANDROID_OUT / "gki_boot_gz.img"
55GKI_BOOT_LZ4 = ANDROID_OUT / "gki_boot_lz4.img"
56
57# RNG seed values. Keep the same seed value for a given file to ensure
58# reproducibility as much as possible; this will prevent adding a bunch of
59# unnecessary test binaries to the git history.
60RNG_SEED_SPARSE_TEST_RAW = 1
61RNG_SEED_ZIRCON = {"a": 2, "b": 3, "r": 4, "slotless": 5}
62RNG_SEED_ANDROID = {"a": 6, "b": 7}
63
64# AVB related constants.
65PSK = AVB_TEST_DATA_DIR / "testkey_cert_psk.pem"
66TEST_ROLLBACK_INDEX_LOCATION = 1
67TEST_ROLLBACK_INDEX = 2
68
69
70# A helper for writing bytes to a file at a given offset.
71def write_file(file, offset, data):
72    file.seek(offset, 0)
73    file.write(data)
74
75
76# Unpack kernel from boot image
77def unpack_boot(boot, into):
78    subprocess.run(
79        [
80            UNPACKBOOTIMG_TOOL,
81            "--boot_img", boot,
82            "--out", into,
83        ],
84        stderr=subprocess.STDOUT,
85        check=True,
86    )
87
88
89def uncompress_lz4(archive, into):
90    subprocess.run(
91        [
92            LZ4_TOOL,
93            "-f",  # always override
94            "-d", archive,
95            into,
96        ],
97        stderr=subprocess.STDOUT,
98        check=True,
99    )
100
101
102def uncompress_gz(archive, into):
103    with gzip.open(archive, "rb") as input, open(into, "wb") as output:
104        shutil.copyfileobj(input, output)
105
106
107# Unpack and uncompress GKI boot images
108def unpack_gkis():
109    with tempfile.TemporaryDirectory() as temp_dir:
110        temp_dir = pathlib.Path(temp_dir)
111
112        if shutil.which(LZ4_TOOL) is not None:
113            unpack_boot(GKI_BOOT_LZ4, temp_dir)
114            shutil.copyfile(temp_dir / "kernel",
115                            ANDROID_OUT / "gki_boot_lz4_kernel")
116            uncompress_lz4(ANDROID_OUT / "gki_boot_lz4_kernel",
117                           ANDROID_OUT / "gki_boot_lz4_kernel_uncompressed")
118        else:
119            print("Warning: lz4 tool isn't presented, skipping unpack lz4 gki boot")
120
121        unpack_boot(GKI_BOOT_GZ, temp_dir)
122        shutil.copyfile(temp_dir / "kernel",
123                        ANDROID_OUT / "gki_boot_gz_kernel")
124        uncompress_gz(ANDROID_OUT / "gki_boot_gz_kernel",
125                      ANDROID_OUT / "gki_boot_gz_kernel_uncompressed")
126
127
128# Generates sparse image for flashing test
129def gen_sparse_test_file():
130    out_file_raw = SCRIPT_DIR / "sparse_test_raw.bin"
131    random.seed(RNG_SEED_SPARSE_TEST_RAW)
132    with open(out_file_raw, "wb") as f:
133        # 4k filled with 0x78563412
134        write_file(f, 0, b"\x12\x34\x56\x78" * 1024)
135        # 8k file hole (will become dont-care with the "-s" option)
136        # 12k raw data
137        write_file(f, 12 * SZ_KB, random.randbytes(12 * SZ_KB))
138        # 8k filled with 0x78563412
139        write_file(f, 24 * SZ_KB, b"\x12\x34\x56\x78" * 1024 * 2)
140        # 12k raw data
141        write_file(f, 32 * SZ_KB, random.randbytes(12 * SZ_KB))
142        # 4k filled with 0x78563412
143        write_file(f, 44 * SZ_KB, b"\x12\x34\x56\x78" * 1024)
144        # 8k filled with 0xEFCDAB90
145        write_file(f, 48 * SZ_KB, b"\x90\xab\xcd\xef" * 1024 * 2)
146
147    # For now this requires that img2simg exists on $PATH.
148    # It can be built from an Android checkout via `m img2simg`; the resulting
149    # binary will be at out/host/linux-x86/bin/img2simg.
150    subprocess.run(
151        ["img2simg", "-s", out_file_raw, SCRIPT_DIR / "sparse_test.bin"]
152    )
153    subprocess.run(
154        [
155            "img2simg",
156            "-s",
157            out_file_raw,
158            SCRIPT_DIR / "sparse_test_blk1024.bin",
159            "1024",
160        ]
161    )
162
163
164def gen_dtb(input_dts, output_dtb):
165    subprocess.run(
166        [DTC_TOOL, "-I", "dts", "-O", "dtb", "-o", output_dtb, input_dts],
167        stderr=subprocess.STDOUT,
168        check=True,
169    )
170
171
172def gen_android_test_dtb():
173    out_dir = ANDROID_OUT
174    # Generates base test device tree.
175    gen_dtb(out_dir / "device_tree.dts", out_dir / "device_tree.dtb")
176    gen_dtb(
177        out_dir / "device_tree_custom.dts", out_dir / "device_tree_custom.dtb"
178    )
179    # Generates dtb to be used inside boot/vendor_boot
180    subprocess.run(
181        [
182            MKDTBOIMG_TOOL,
183            "create",
184            out_dir / "dtb.img",
185            "--id=0x1",
186            "--rev=0x0",
187            out_dir / "device_tree.dtb",
188        ],
189        stderr=subprocess.STDOUT,
190        check=True,
191    )
192
193    # Generates dtb_a/dtb_b
194    gen_dtb(out_dir / "device_tree_a.dts", out_dir / "device_tree_a.dtb")
195    gen_dtb(out_dir / "device_tree_b.dts", out_dir / "device_tree_b.dtb")
196    subprocess.run(
197        [
198            MKDTBOIMG_TOOL,
199            "create",
200            out_dir / "dtb_a.img",
201            "--id=0x1",
202            "--rev=0x0",
203            out_dir / "device_tree_a.dtb",
204        ],
205        stderr=subprocess.STDOUT,
206        check=True,
207    )
208    subprocess.run(
209        [
210            MKDTBOIMG_TOOL,
211            "create",
212            out_dir / "dtb_b.img",
213            "--id=0x1",
214            "--rev=0x0",
215            out_dir / "device_tree_b.dtb",
216        ],
217        stderr=subprocess.STDOUT,
218        check=True,
219    )
220
221    # Generates overlay
222    gen_dtb(out_dir / "overlay_a.dts", out_dir / "overlay_a.dtb")
223    gen_dtb(out_dir / "overlay_b.dts", out_dir / "overlay_b.dtb")
224
225    subprocess.run(
226        [
227            MKDTBOIMG_TOOL,
228            "create",
229            out_dir / "dtbo_a.img",
230            "--id=0x1",
231            "--rev=0x0",
232            out_dir / "overlay_a.dtb",
233        ],
234        stderr=subprocess.STDOUT,
235        check=True,
236    )
237    subprocess.run(
238        [
239            MKDTBOIMG_TOOL,
240            "create",
241            out_dir / "dtbo_b.img",
242            "--id=0x1",
243            "--rev=0x0",
244            out_dir / "overlay_b.dtb",
245        ],
246        stderr=subprocess.STDOUT,
247        check=True,
248    )
249
250
251# Generate vbmeta data for a set of images.
252def gen_android_test_vbmeta(partition_file_pairs, out_vbmeta):
253    with tempfile.TemporaryDirectory() as temp_dir:
254        desc_args = []
255        temp_dir = pathlib.Path(temp_dir)
256        for i, (part, image_file) in enumerate(partition_file_pairs):
257            out = temp_dir / f"{i}.vbmeta_desc"
258            desc_args += ["--include_descriptors_from_image", out]
259            subprocess.run(
260                [
261                    AVB_TOOL,
262                    "add_hash_footer",
263                    "--image",
264                    image_file,
265                    "--partition_name",
266                    part,
267                    "--do_not_append_vbmeta_image",
268                    "--output_vbmeta_image",
269                    out,
270                    "--salt",
271                    "9f06406a750581266f21865d115e63b54db441bc0d614195c78c14451b5ecb8abb14d8cd88d816c4750545ef89cb348a3834815aac4fa359e8b02a740483d975",
272                    "--partition_size",
273                    "209715200",  # Randomly chosen large enough value.
274                ],
275                stderr=subprocess.STDOUT,
276                check=True,
277            )
278
279        subprocess.run(
280            [
281                AVB_TOOL,
282                "make_vbmeta_image",
283                "--output",
284                out_vbmeta,
285                "--key",
286                PSK,
287                "--algorithm",
288                "SHA512_RSA4096",
289                "--rollback_index",
290                f"{TEST_ROLLBACK_INDEX}",
291                "--rollback_index_location",
292                f"{TEST_ROLLBACK_INDEX_LOCATION}",
293            ]
294            + desc_args,
295            stderr=subprocess.STDOUT,
296            check=True,
297        )
298
299        # Generates vbmeta digest file
300        out_digest = out_vbmeta.with_suffix(".digest.txt")
301        digest = subprocess.run(
302            [
303                AVB_TOOL,
304                "calculate_vbmeta_digest",
305                "--image",
306                out_vbmeta,
307                "--hash_algorithm",
308                "sha512",
309            ],
310
311
312            check=True,
313            text=True,
314            capture_output=True,
315        )
316        out_digest.write_text(digest.stdout)
317
318        extract_vbmeta_digests(out_vbmeta)
319
320
321# Extract digests from vbmeta data
322def extract_vbmeta_digests(vbmeta):
323    # Get vbmeta digests
324    digests = (
325        re.split(
326            "\n|: ",
327            subprocess.run(
328                [
329                    AVB_TOOL,
330                    "print_partition_digests",
331                    "--image",
332                    vbmeta,
333                ],
334
335
336                check=True,
337                text=True,
338                capture_output=True,
339            )
340            .stdout
341        )
342    )
343    digests = {digests[i]: digests[i+1] for i in range(0, len(digests), 2) if digests[i] in [
344        "boot", "vendor_boot", "init_boot", "dtbo", "dtb"]}
345
346    for key, value in digests.items():
347        out_digest = vbmeta.with_suffix(".{}.digest.txt".format(key))
348        out_digest.write_text(value + "\n")
349
350
351def gen_android_test_images():
352    unpack_gkis()
353    gen_android_test_dtb()
354
355    with tempfile.TemporaryDirectory() as temp_dir:
356        temp_dir = pathlib.Path(temp_dir)
357        out_dir = ANDROID_OUT
358        out_dir.mkdir(parents=True, exist_ok=True)
359        for slot in ["a", "b"]:
360            random.seed(RNG_SEED_ANDROID[slot])
361            kernel = out_dir / f"kernel_{slot}.img"
362            kernel.write_bytes(random.randbytes(4 * SZ_KB))
363
364            generic_ramdisk = out_dir / f"generic_ramdisk_{slot}.img"
365            generic_ramdisk.write_bytes(random.randbytes(8 * SZ_KB))
366
367            vendor_ramdisk = out_dir / f"vendor_ramdisk_{slot}.img"
368            vendor_ramdisk.write_bytes(random.randbytes(12 * SZ_KB))
369
370            vendor_bootconfig = temp_dir / f"vendor_bootconfig_{slot}.img"
371            vendor_bootconfig.write_bytes(
372                b"""\
373androidboot.config_1=val_1
374androidboot.config_2=val_2
375"""
376            )
377
378            boot_cmdline = "cmd_key_1=cmd_val_1,cmd_key_2=cmd_val_2"
379            vendor_cmdline = "cmd_vendor_key_1=cmd_vendor_val_1,cmd_vendor_key_2=cmd_vendor_val_2"
380
381            # Generate v3, v4 boot image without ramdisk (usecase for init_boot)
382            common = [
383                MKBOOTIMG_TOOL,
384                "--kernel",
385                kernel,
386                "--cmdline",
387                boot_cmdline,
388                "--dtb",
389                out_dir / "device_tree.dtb",
390            ]
391            for i in [3, 4]:
392                out = out_dir / f"boot_no_ramdisk_v{i}_{slot}.img"
393                subprocess.run(
394                    common + ["--header_version", f"{i}", "-o", out],
395                    check=True,
396                    stderr=subprocess.STDOUT,
397                )
398
399            # Generates v0 - v4 boot image that contains generic ramdisk.
400            common += [
401                "--ramdisk",
402                generic_ramdisk,
403            ]
404            for i in range(0, 5):
405                out = out_dir / f"boot_v{i}_{slot}.img"
406                subprocess.run(
407                    common + ["--header_version", f"{i}", "-o", out],
408                    check=True,
409                    stderr=subprocess.STDOUT,
410                )
411
412            # Generate v4 boot images for gzip and lz4 kernel compression.
413            if slot == "a":
414                for compression in ['gz', 'lz4']:
415                    out = out_dir / f"boot_v4_{compression}_{slot}.img"
416                    # Replace kernel
417                    common[2] = out_dir / f"gki_boot_{compression}_kernel"
418
419                    subprocess.run(
420                        common + ["--header_version", "4", "-o", out],
421                        check=True,
422                        stderr=subprocess.STDOUT,
423                    )
424
425            # Generates init_boot
426            subprocess.run(
427                [
428                    MKBOOTIMG_TOOL,
429                    "-o",
430                    out_dir / f"init_boot_{slot}.img",
431                    "--ramdisk",
432                    generic_ramdisk,
433                    # init_boot uses fixed version 4.
434                    "--header_version",
435                    "4",
436                ],
437                check=True,
438                stderr=subprocess.STDOUT,
439            )
440
441            # Generates vendor_boot images
442            common = [
443                MKBOOTIMG_TOOL,
444                "--vendor_cmdline",
445                vendor_cmdline,
446                "--vendor_ramdisk",
447                vendor_ramdisk,
448                "--dtb",
449                out_dir / "device_tree.dtb",
450            ]
451            # Generates vendor_boot v3 (no bootconfig)
452            subprocess.run(
453                common
454                + [
455                    "--dtb",
456                    out_dir / "device_tree.dtb",
457                    "--vendor_boot",
458                    out_dir / f"vendor_boot_v3_{slot}.img",
459                    "--header_version",
460                    "3",
461                ],
462                stderr=subprocess.STDOUT,
463                check=True,
464            )
465            # Generates vendor_boot v4
466            subprocess.run(
467                common
468                + [
469                    "--dtb",
470                    out_dir / "device_tree.dtb",
471                    "--vendor_boot",
472                    out_dir / f"vendor_boot_v4_{slot}.img",
473                    "--vendor_bootconfig",
474                    vendor_bootconfig,
475                    "--header_version",
476                    "4",
477                ],
478                stderr=subprocess.STDOUT,
479                check=True,
480            )
481            # Generates vendor_boot v4 with dttable structure
482            subprocess.run(
483                common
484                + [
485                    "--dtb",
486                    out_dir / "dtb.img",
487                    "--vendor_boot",
488                    out_dir / f"vendor_boot_v4_dttable_{slot}.img",
489                    "--vendor_bootconfig",
490                    vendor_bootconfig,
491                    "--header_version",
492                    "4",
493                ],
494                stderr=subprocess.STDOUT,
495                check=True,
496            )
497
498            # Generates a vbmeta data for v0 - v2 setup
499            for i in [0, 1, 2]:
500                parts = [
501                    (f"boot", out_dir / f"boot_v{i}_{slot}.img"),
502                    ("dtbo", out_dir / f"dtbo_{slot}.img"),
503                    ("dtb", out_dir / f"dtb_{slot}.img"),
504                ]
505                gen_android_test_vbmeta(
506                    parts, out_dir / f"vbmeta_v{i}_{slot}.img"
507                )
508
509            # Generates different combinations of v3/v4 boot/vendor_boot/init_boot setup.
510            for use_init_boot in [True, False]:
511                for boot_ver in [3, 4]:
512                    if use_init_boot:
513                        boot = (
514                            out_dir / f"boot_no_ramdisk_v{boot_ver}_{slot}.img"
515                        )
516                    else:
517                        boot = out_dir / f"boot_v{boot_ver}_{slot}.img"
518
519                    for vendor_ver in [3, 4]:
520                        vendor_boot = (
521                            out_dir / f"vendor_boot_v{vendor_ver}_{slot}.img"
522                        )
523
524                        parts = [
525                            (f"boot", boot),
526                            (f"vendor_boot", vendor_boot),
527                            ("dtbo", out_dir / f"dtbo_{slot}.img"),
528                            ("dtb", out_dir / f"dtb_{slot}.img"),
529                        ]
530                        prefix = f"vbmeta_v{boot_ver}_v{vendor_ver}"
531                        if use_init_boot:
532                            vbmeta_out = prefix + f"_init_boot_{slot}.img"
533                            parts += [
534                                (
535                                    "init_boot",
536                                    out_dir / f"init_boot_{slot}.img",
537                                )
538                            ]
539                        else:
540                            vbmeta_out = prefix + f"_{slot}.img"
541
542                        gen_android_test_vbmeta(parts, out_dir / vbmeta_out)
543
544            # Generate v4 vbmeta image for vendor_boot with dttable structure
545            vbmeta_out = out_dir / \
546                f"vbmeta_v4_dttable_{slot}.img"
547            parts = [
548                (f"boot", out_dir /
549                    f"boot_v4_{slot}.img"),
550                (f"vendor_boot", out_dir /
551                    f"vendor_boot_v4_dttable_{slot}.img"),
552                ("dtbo", out_dir / f"dtbo_{slot}.img"),
553                ("dtb", out_dir / f"dtb_{slot}.img"),
554            ]
555            gen_android_test_vbmeta(parts, vbmeta_out)
556
557            # Generate v4 vbmeta images for both gzip and lz4 kernel compression.
558            if slot == "a":
559                for compression in ["gz", "lz4"]:
560                    vbmeta_out = out_dir / \
561                        f"vbmeta_v4_{compression}_{slot}.img"
562                    parts = [
563                        (f"boot", out_dir /
564                         f"boot_v4_{compression}_{slot}.img"),
565                        (f"vendor_boot", out_dir /
566                         f"vendor_boot_v4_{slot}.img"),
567                        ("dtbo", out_dir / f"dtbo_{slot}.img"),
568                        ("dtb", out_dir / f"dtb_{slot}.img"),
569                    ]
570                    gen_android_test_vbmeta(parts, vbmeta_out)
571
572
573def gen_zircon_test_images(zbi_tool):
574    if not zbi_tool:
575        print(
576            "Warning: ZBI tool not provided. Skip regenerating zircon test"
577            " images"
578        )
579        return
580
581    ATX_METADATA = AVB_TEST_DATA_DIR / "cert_metadata.bin"
582
583    with tempfile.TemporaryDirectory() as temp_dir:
584        for slot in ["a", "b", "r", "slotless"]:
585            temp_dir = pathlib.Path(temp_dir)
586            random.seed(RNG_SEED_ZIRCON[slot])
587            out_kernel_bin_file = temp_dir / f"zircon_{slot}.bin"
588            # The first 16 bytes are two u64 integers representing `entry` and
589            # `reserve_memory_size`.
590            # Set `entry` value to 2048 and `reserve_memory_size` to 1024.
591            kernel_bytes = int(2048).to_bytes(8, "little") + int(1024).to_bytes(
592                8, "little"
593            )
594            kernel_bytes += random.randbytes(1 * SZ_KB - 16)
595            out_kernel_bin_file.write_bytes(kernel_bytes)
596            out_zbi_file = SCRIPT_DIR / f"zircon_{slot}.zbi"
597            # Puts image in a zbi container.
598            subprocess.run(
599                [
600                    zbi_tool,
601                    "--output",
602                    out_zbi_file,
603                    "--type=KERNEL_X64",
604                    out_kernel_bin_file,
605                ]
606            )
607
608            # Generates vbmeta descriptor.
609            vbmeta_desc = f"{temp_dir}/zircon_{slot}.vbmeta.desc"
610            subprocess.run(
611                [
612                    AVB_TOOL,
613                    "add_hash_footer",
614                    "--image",
615                    out_zbi_file,
616                    "--partition_name",
617                    "zircon",
618                    "--do_not_append_vbmeta_image",
619                    "--output_vbmeta_image",
620                    vbmeta_desc,
621                    "--partition_size",
622                    "209715200",
623                ]
624            )
625            # Generates two cmdline ZBI items to add as property descriptors to
626            # vbmeta image for test.
627            vbmeta_prop_args = []
628            for i in range(2):
629                prop_zbi_payload = f"{temp_dir}/prop_zbi_payload_{i}.bin"
630                subprocess.run(
631                    [
632                        zbi_tool,
633                        "--output",
634                        prop_zbi_payload,
635                        "--type=CMDLINE",
636                        f"--entry=vb_prop_{i}=val",
637                    ]
638                )
639                vbmeta_prop_args += [
640                    "--prop_from_file",
641                    f"zbi_vb_prop_{i}:{prop_zbi_payload}",
642                ]
643                # Also adds a property where the key name does not starts with
644                # "zbi". The item should not be processed.
645                vbmeta_prop_args += [
646                    "--prop_from_file",
647                    f"vb_prop_{i}:{prop_zbi_payload}",
648                ]
649            # Generates vbmeta image
650            vbmeta_img = SCRIPT_DIR / f"vbmeta_{slot}.bin"
651            subprocess.run(
652                [
653                    AVB_TOOL,
654                    "make_vbmeta_image",
655                    "--output",
656                    vbmeta_img,
657                    "--key",
658                    PSK,
659                    "--algorithm",
660                    "SHA512_RSA4096",
661                    "--public_key_metadata",
662                    ATX_METADATA,
663                    "--include_descriptors_from_image",
664                    vbmeta_desc,
665                    "--rollback_index",
666                    f"{TEST_ROLLBACK_INDEX}",
667                    "--rollback_index_location",
668                    f"{TEST_ROLLBACK_INDEX_LOCATION}",
669                ]
670                + vbmeta_prop_args
671            )
672
673
674# Generates test data for A/B slot Manager writeback test
675def gen_writeback_test_bin():
676    subprocess.run(
677        [
678            GPT_TOOL,
679            SCRIPT_DIR / "writeback_test_disk.bin",
680            "64K",
681            "--partition=test_partition,4k,/dev/zero",
682        ],
683        check=True,
684    )
685
686
687def sha256_hash(path: pathlib.Path) -> bytes:
688    """Returns the SHA256 hash of the given file."""
689    hash_hex = (
690        subprocess.run(
691            ["sha256sum", path],
692            check=True,
693            capture_output=True,
694            text=True,
695        )
696        .stdout.split()[0]  # output is "<hash> <filename>".
697        .strip()
698    )
699    return bytes.fromhex(hash_hex)
700
701
702def gen_vbmeta():
703    """Creates the vbmeta keys and signs some images."""
704    # Use the test vbmeta keys from libavb.
705    for name in [
706        "testkey_rsa4096.pem",
707        "testkey_rsa4096_pub.pem",
708        "testkey_cert_psk.pem",
709        "cert_metadata.bin",
710        "cert_permanent_attributes.bin",
711    ]:
712        shutil.copyfile(AVB_TEST_DATA_DIR / name, SCRIPT_DIR / name)
713
714    # We need the permanent attribute SHA256 hash for libavb_cert callbacks.
715    hash_bytes = sha256_hash(SCRIPT_DIR / "cert_permanent_attributes.bin")
716    (SCRIPT_DIR / "cert_permanent_attributes.hash").write_bytes(hash_bytes)
717
718    # Also creates a corrupted version of the permanent attributes to test failure.
719    # This is a little bit of a pain but we don't have an easy way to do a SHA256 in Rust
720    # at the moment so we can't generate it on the fly.
721    bad_attrs = bytearray(
722        (SCRIPT_DIR / "cert_permanent_attributes.bin").read_bytes()
723    )
724    bad_attrs[4] ^= 0x01  # Bytes 0-3 = version, byte 4 starts the public key.
725    (SCRIPT_DIR / "cert_permanent_attributes.bad.bin").write_bytes(bad_attrs)
726    hash_bytes = sha256_hash(SCRIPT_DIR / "cert_permanent_attributes.bad.bin")
727    (SCRIPT_DIR / "cert_permanent_attributes.bad.hash").write_bytes(hash_bytes)
728
729    # Convert the public key to raw bytes for use in verification.
730    subprocess.run(
731        [
732            AVB_TOOL,
733            "extract_public_key",
734            "--key",
735            SCRIPT_DIR / "testkey_rsa4096_pub.pem",
736            "--output",
737            SCRIPT_DIR / "testkey_rsa4096_pub.bin",
738        ],
739        check=True,
740    )
741
742    with tempfile.TemporaryDirectory() as temp_dir:
743        temp_dir = pathlib.Path(temp_dir)
744
745        # Create the hash descriptor. We only need this temporarily until we add
746        # it into the final vbmeta image.
747        hash_descriptor_path = temp_dir / "hash_descriptor.bin"
748        subprocess.run(
749            [
750                AVB_TOOL,
751                "add_hash_footer",
752                "--dynamic_partition_size",
753                "--do_not_append_vbmeta_image",
754                "--partition_name",
755                "zircon_a",
756                "--image",
757                SCRIPT_DIR / "zircon_a.zbi",
758                "--output_vbmeta_image",
759                hash_descriptor_path,
760                "--salt",
761                "2000",
762            ],
763            check=True,
764        )
765
766        # Create the final signed vbmeta including the hash descriptor.
767        subprocess.run(
768            [
769                AVB_TOOL,
770                "make_vbmeta_image",
771                "--key",
772                SCRIPT_DIR / "testkey_rsa4096.pem",
773                "--algorithm",
774                "SHA512_RSA4096",
775                "--include_descriptors_from_image",
776                hash_descriptor_path,
777                "--output",
778                SCRIPT_DIR / "zircon_a.vbmeta",
779            ],
780            check=True,
781        )
782
783        # Also creates a vbmeta using the libavb_cert extension.
784        subprocess.run(
785            [
786                AVB_TOOL,
787                "make_vbmeta_image",
788                "--key",
789                SCRIPT_DIR / "testkey_cert_psk.pem",
790                "--public_key_metadata",
791                SCRIPT_DIR / "cert_metadata.bin",
792                "--algorithm",
793                "SHA512_RSA4096",
794                "--include_descriptors_from_image",
795                hash_descriptor_path,
796                "--output",
797                SCRIPT_DIR / "zircon_a.vbmeta.cert",
798            ]
799        )
800
801
802def _parse_args() -> argparse.Namespace:
803    parser = argparse.ArgumentParser(
804        description=__doc__,
805        formatter_class=argparse.RawDescriptionHelpFormatter,
806    )
807
808    parser.add_argument(
809        "--zbi_tool", default="", help="Path to the Fuchsia ZBI tool"
810    )
811
812    return parser.parse_args()
813
814
815if __name__ == "__main__":
816    args = _parse_args()
817    gen_writeback_test_bin()
818    gen_sparse_test_file()
819    gen_zircon_test_images(args.zbi_tool)
820    gen_vbmeta()
821    gen_android_test_images()
822