xref: /aosp_15_r20/external/autotest/server/site_tests/autoupdate_CatchBadSignatures/control (revision 9c5db1993ded3edbeafc8092d69fe5de2ee02df7)
1# Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
5from autotest_lib.client.common_lib import utils
6
7AUTHOR = "Chromium OS"
8NAME = "autoupdate_CatchBadSignatures"
9TIME = "MEDIUM"
10TEST_CATEGORY = "Functional"
11TEST_CLASS = "platform"
12TEST_TYPE = "server"
13ATTRIBUTES = "suite:bvt-inline, suite:pvs-bvt-inline, suite:satlab-qual-bvt-inline"
14JOB_RETRIES = 2
15PY_VERSION = 3
16
17DOC = """
18This is a test to verify that update_engine correctly checks signatures in the
19metadata hash and the update payload itself. This is achieved by feeding updates
20to update_engine where the private key used to make the signature, intentionally
21does not match with the public key used for verification.
22
23By its very nature, this test requires an image signed with a well-known
24key. Since payload-generation is a resource-intensive process, we prepare the
25image ahead of time. Also, since the image is never successfully applied, we can
26get away with not caring that the image is built for one board but used on
27another.
28
29If you ever need to replace the test image and payloads, follow these simple
30(jk!) steps:
31
32  1. Build a test image:
33
34     $ cd ~/trunk/src/scripts
35     $ ./build_packages --board=${BOARD}
36     $ ./build_image --board=${BOARD} --noenable_rootfs_verification test
37
38     Alternatively, you can use any kind of ChromeOS image you already have or
39     downloaded.
40
41  2. Reduce the size of Rootfs and Kernel partitions. This is done so these
42     autotests don't have to download a huge payload as the payload is not going
43     to be applied fully anyway. This can be done many ways. One is:
44
45     $ sudo losetup -fP chromiumos_test_image.bin
46
47     At this point take a note of which loopback device was set up for the
48     image. e.g. /dev/loop1
49
50     $ sudo mkfs.ext4 -b 4k /dev/loop1p3 100  # For ROOT-A
51     $ sudo mkfs.ext4 -b 4k /dev/loop1p4 100  # For KERN-B
52     $ mkdir rootfs kernel
53     $ sudo mount /dev/loop1p3 rootfs
54     $ sudo mount /dev/loop1p4 kernel
55
56     Now you need a lsb-release file copied from any ChromeOS.
57
58     $ mkdir rootfs/etc && touch cp <lsb-release> rootfs/etc
59     $ touch kernel/fake-kernel.bin # Optional
60     $ sudo umount rootfs kernel
61
62     Now, the chromiumos_test_image.bin has very small Rootfs and Kernel
63     partitions.
64
65  3. Generate a full payload in which its metadata and payload signatures are
66     signed by two different private keys. An update payload has two signatures
67     embedded in it. The first is the signature of the header (metadata
68     signature) and the second is the signature of the entire payload (payload
69     signature). We do two tests here: One that makes sure the metadata
70     signature verification fails if signed incorrectly (we do this by sending a
71     different public key that doesn't verify the aforementioned signature). The
72     second one to make sure the metadata signature verification passes fine,
73     but the payload signature verification fails. One (not so) simple, but
74     available way of doing this is as follows:
75
76     Since we can't generate a payload with metadata and payload signatures
77     signed by different keys (simply we haven't designed tools for that), we
78     need to sign a payload two times with different keys and swap the payload
79     signature of one of them with the other.
80
81     $ mkdir key1 key2
82     $ cros_generate_update_payload \
83         --image chromiumos_test_image.bin \
84         --output key1/full.bin \
85         --work_dir key1 \
86         --private_key ~/trunk/src/aosp/system/update_engine/unittest_key.pem
87     $ cros_generate_update_payload \
88         --image chromiumos_test_image.bin \
89         --output key2/full.bin \
90         --work_dir key2 \
91         --private_key ~/trunk/src/aosp/system/update_engine/unittest_key2.pem
92
93     Now we should re-sign an unsigned image (key1/delta.bin) with metadata
94     signature from key1 and payload signature from key2 directories. Because we
95     passed the --work_dir flag, the intermediate temporary files (including
96     signature files) are saved in those directories.
97
98     $ delta_generator \
99        --in_file=key1/delta.bin \
100        --metadata_signature_file=key1/signature_metadata_<hash>.bin \
101        --payload_signature_file=key2/signature_payload_<hash>.bin \
102        --out_file=autoupdate_CatchBadSignatures.bin
103
104     This file is signed and ready to be tested.
105
106  4. Generate/modify a payload properties file. For each payload we need a
107     payload properties file in JSON format. This file has already been
108     generated in the previous step when we generated the signed image. We just
109     need to modify it.
110
111     $ cp key1/delta.bin.json autoupdate_CatchBadSignatures.bin.json
112
113     However, since we re-signed the payload, we need to calculate its SHA256
114     hash again. Easy way to do this is to use either delta_generator or
115     cros_generate_update_payload as:
116
117     $  cros_generate_update_payload \
118        --payload autoupdate_CatchBadSignatures.bin --output foo.json
119
120     Open autoupdate_CatchBadSignatures.bin.json and replace the value of
121     sha256_hex with the one from foo.json. Also empty the value of 'appid'
122     (keep its key) to empty string.
123
124  5. Replace _IMAGE_PUBLIC_KEY2 in this file with value in
125     key2/full.bin.json. (You don't need to do this step if you used the same
126     keys as mentioned above.)
127
128  6. Upload the generated payload and its properties file to the public
129     gsbucket.
130
131  7. Now run the test and ensure that it passes.
132
133    $ cd ~/trunk/src/scripts
134    $ test_that -b ${BOARD} --fast <DUT_IP> autoupdate_CatchBadSignatures
135
136With this in place, you can now run the test:
137
138  $ test_that <DUT_IP> autoupdate_CatchBadSignatures -b ${BOARD}
139
140"""
141
142def run_test(machine):
143    """Execute a test configuration on a given machine."""
144    host = hosts.create_host(machine)
145    job.run_test("autoupdate_CatchBadSignatures", host=host)
146
147# Invoke parallel tests.
148parallel_simple(run_test, machines)
149