| # Copyright (c) 2013 The Chromium OS Authors. All rights reserved. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| from autotest_lib.client.common_lib import utils |
| |
| AUTHOR = "Chromium OS" |
| NAME = "autoupdate_CatchBadSignatures" |
| TIME = "MEDIUM" |
| TEST_CATEGORY = "Functional" |
| TEST_CLASS = "platform" |
| TEST_TYPE = "server" |
| ATTRIBUTES = "suite:au-perbuild" |
| |
| DOC = """ |
| This is a test to verify that update_engine correctly checks signatures in the |
| metadata hash and the update payload itself. This is achieved by feeding updates |
| to update_engine where the private key used to make the signature, intentionally |
| does not match with the public key used for verification. |
| |
| By its very nature, this test requires an image signed with a well-known |
| key. Since payload-generation is a resource-intensive process, we prepare the |
| image ahead of time. Also, since the image is never successfully applied, we can |
| get away with not caring that the image is built for one board but used on |
| another. |
| |
| If you ever need to replace the test image and payloads, follow these simple |
| (jk!) steps: |
| |
| 1. Build a test image: |
| |
| $ cd ~/trunk/src/scripts |
| $ ./build_packages --board=${BOARD} |
| $ ./build_image --board=${BOARD} --noenable_rootfs_verification test |
| |
| Alternatively, you can use any kind of Chrome OS image you already have or |
| downloaded. |
| |
| 2. Reduce the size of Rootfs and Kernel partitions. This is done so these |
| autotests don't have to download a huge payload as the payload is not going |
| to be applied fully anyway. This can be done many ways. One is: |
| |
| $ sudo losetup -fP chromiumos_test_image.bin |
| |
| At this point take a note of which loopback device was set up for the |
| image. e.g. /dev/loop1 |
| |
| $ sudo mkfs.ext4 -b 4k /dev/loop1p3 100 # For ROOT-A |
| $ sudo mkfs.ext4 -b 4k /dev/loop1p4 100 # For KERN-B |
| $ mkdir rootfs kernel |
| $ sudo mount /dev/loop1p3 rootfs |
| $ sudo mount /dev/loop1p4 kernel |
| |
| Now you need a lsb-release file copied from any Chrome OS. |
| |
| $ mkdir rootfs/etc && touch cp <lsb-release> rootfs/etc |
| $ touch kernel/fake-kernel.bin # Optional |
| $ sudo umount rootfs kernel |
| |
| Now, the chromiumos_test_image.bin has very small Rootfs and Kernel |
| partitions. |
| |
| 3. Generate a full payload in which its metadata and payload signatures are |
| signed by two different private keys. An update payload has two signatures |
| embedded in it. The first is the signature of the header (metadata |
| signature) and the second is the signature of the entire payload (payload |
| signature). We do two tests here: One that makes sure the metadata |
| signature verification fails if signed incorrectly (we do this by sending a |
| different public key that doesn't verify the aforementioned signature). The |
| second one to make sure the metadata signature verification passes fine, |
| but the payload signature verification fails. One (not so) simple, but |
| available way of doing this is as follows: |
| |
| Since we can't generate a payload with metadata and payload signatures |
| signed by different keys (simply we haven't designed tools for that), we |
| need to sign a payload two times with different keys and swap the payload |
| signature of one of them with the other. |
| |
| $ mkdir key1 key2 |
| $ cros_generate_update_payload \ |
| --image chromiumos_test_image.bin \ |
| --output key1/full.bin \ |
| --work_dir key1 \ |
| --private_key ~/trunk/src/aosp/system/update_engine/unittest_key.pem |
| $ cros_generate_update_payload \ |
| --image chromiumos_test_image.bin \ |
| --output key2/full.bin \ |
| --work_dir key2 \ |
| --private_key ~/trunk/src/aosp/system/update_engine/unittest_key2.pem |
| |
| Now we should re-sign an unsigned image (key1/delta.bin) with metadata |
| signature from key1 and payload signature from key2 directories. Because we |
| passed the --work_dir flag, the intermediate temporary files (including |
| signature files) are saved in those directories. |
| |
| $ delta_generator \ |
| --in_file=key1/delta.bin \ |
| --metadata_signature_file=key1/signature_metadata_<hash>.bin \ |
| --payload_signature_file=key2/signature_payload_<hash>.bin \ |
| --out_file=autoupdate_CatchBadSignatures.bin |
| |
| This file is signed and ready to be tested. |
| |
| 4. Generate/modify a payload properties file. For each payload we need a |
| payload properties file in JSON format. This file has already been |
| generated in the previous step when we generated the signed image. We just |
| need to modify it. |
| |
| $ cp key1/delta.bin.json autoupdate_CatchBadSignatures.bin.json |
| |
| However, since we re-signed the payload, we need to calculate its SHA256 |
| hash again. Easy way to do this is to use either delta_generator or |
| cros_generate_update_payload as: |
| |
| $ cros_generate_update_payload \ |
| --payload autoupdate_CatchBadSignatures.bin --output foo.json |
| |
| Open autoupdate_CatchBadSignatures.bin.json and replace the value of |
| sha256_hex with the one from foo.json. Also empty the value of 'appid' |
| (keep its key) to empty string. |
| |
| 5. Replace _IMAGE_PUBLIC_KEY2 in this file with value in |
| key2/full.bin.json. (You don't need to do this step if you used the same |
| keys as mentioned above.) |
| |
| 6. Upload the generated payload and its properties file to the public |
| gsbucket. |
| |
| 7. Now run the test and ensure that it passes. |
| |
| $ cd ~/trunk/src/scripts |
| $ test_that -b ${BOARD} --fast <DUT_IP> autoupdate_CatchBadSignatures |
| |
| With this in place, you can now run the test: |
| |
| $ test_that <DUT_IP> autoupdate_CatchBadSignatures -b ${BOARD} |
| |
| """ |
| |
| def run_test(machine): |
| """Execute a test configuration on a given machine.""" |
| host = hosts.create_host(machine) |
| job.run_test("autoupdate_CatchBadSignatures", host=host) |
| |
| # Invoke parallel tests. |
| parallel_simple(run_test, machines) |