Home | History | Annotate | Download | only in autoupdate_CatchBadSignatures
      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 
      5 import os
      6 import urlparse
      7 
      8 from autotest_lib.client.common_lib import error
      9 from autotest_lib.client.common_lib.cros import dev_server
     10 from autotest_lib.server import autotest, test
     11 
     12 def _split_url(url):
     13     """Splits a URL into the URL base and path."""
     14     split_url = urlparse.urlsplit(url)
     15     url_base = urlparse.urlunsplit(
     16             (split_url.scheme, split_url.netloc, '', '', ''))
     17     url_path = split_url.path
     18     return url_base, url_path.lstrip('/')
     19 
     20 
     21 class autoupdate_CatchBadSignatures(test.test):
     22     """This is a test to verify that update_engine correctly checks
     23     signatures in the metadata hash and the update payload
     24     itself. This is achieved by feeding updates to update_engine where
     25     the private key used to make the signature, intentionally does not
     26     match with the public key used for verification.
     27 
     28     By its very nature, this test requires an image signed with a
     29     well-known key. Since payload-generation is a resource-intensive
     30     process, we prepare the image ahead of time. Also, since the image
     31     is never successfully applied, we can get away with not caring that
     32     the image is built for one board but used on another.
     33 
     34     If you ever need to replace the test image, follow these eight
     35     simple steps:
     36 
     37     1. Build a test image:
     38 
     39       $ cd ~/trunk/src/scripts
     40       $ ./build_packages --board=${BOARD}
     41       $ ./build_image --board=${BOARD} --noenable_rootfs_verification test
     42 
     43     2. Serve the image the DUT like this:
     44 
     45       $ cd ~/trunk/strc/platform/dev
     46       $ ./devserver.py --test_image                               \
     47                        --private_key                              \
     48                          ../update_engine/unittest_key.pem        \
     49                        --private_key_for_metadata_hash_signature  \
     50                          ../update_engine/unittest_key.pem        \
     51                        --public_key                               \
     52                           ../update_engine/unittest_key2.pub.pem
     53 
     54     3. Update the DUT - the update should fail at the metadata
     55        verification stage.
     56 
     57     4. From the update_engine logs (stored in /var/log/update_engine/)
     58        on the DUT, find the Omaha response sent to the DUT and update
     59        the following constants with values from the XML:
     60 
     61         _IMAGE_SHA256: set it to the 'sha256'
     62         _IMAGE_METADATA_SIZE: set it to the 'MetadataSize'
     63         _IMAGE_PUBLIC_KEY2: set it to the 'PublicKeyRsa'
     64         _IMAGE_METADATA_SIGNATURE_WITH_KEY1: set it to 'MetadataSignatureRsa'
     65 
     66        Also download the image payload ('url' and 'codebase' tags),
     67        upload it to Google Storage and update the _IMAGE_GS_URL and
     68        _IMAGE_SIZE constants with the resulting URL and the size.
     69 
     70     5. Serve the image to the DUT again and note the slightly different
     71        parameters this time. Note that the image served is the same,
     72        however the Omaha response will be different.
     73 
     74       $ cd ~/trunk/strc/platform/dev
     75       $ ./devserver.py --test_image                               \
     76                        --private_key                              \
     77                          ../update_engine/unittest_key.pem        \
     78                        --private_key_for_metadata_hash_signature  \
     79                          ../update_engine/unittest_key2.pem       \
     80                        --public_key                               \
     81                           ../update_engine/unittest_key2.pub.pem
     82 
     83     6. Update the DUT - the update should fail at the payload
     84        verification stage.
     85 
     86     7. Like in step 4., examine the update_engine logs and update the
     87        following constants:
     88 
     89         _IMAGE_METADATA_SIGNATURE_WITH_KEY2: set to 'MetadataSignatureRsa'
     90 
     91     8. Now run the test and ensure that it passes
     92 
     93       $ cd ~/trunk/src/scripts
     94       $ test_that -b ${BOARD} --fast <DUT_IP> autoupdate_CatchBadSignatures
     95 
     96     """
     97     version = 1
     98 
     99     # The test image to use and the values associated with it.
    100     _IMAGE_GS_URL='gs://chromiumos-test-assets-public/autoupdate/autoupdate_CatchBadSignatures-payload-lumpy-R33-4970.0.2013_11_15_1654-a1'
    101     _IMAGE_SIZE=369080798
    102     _IMAGE_METADATA_SIZE=58439
    103     _IMAGE_SHA256='FeWxyPLdz1/UDHxskkYSkQm64D5kQgaSb1IEXX3/sjA='
    104     _IMAGE_PUBLIC_KEY1='LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUlJQklqQU5CZ2txaGtpRzl3MEJBUUVGQUFPQ0FROEFNSUlCQ2dLQ0FRRUF4NmhxUytIYmM3ak44MmVrK09CawpISk52bFdYa3R5UzJYQ3VRdEd0bkhqRVY3T3U1aEhORjk2czV3RW44UkR0cmRMb2NtMGErU3FYWGY3S3ljRlJUClp4TGREYnFXQU1VbFBYT2FQSStQWkxXa0I3L0tWN0NkajZ2UEdiZXE3ZWx1K2FUL2J1ZGh6VHZxMnN0WXJyQWwKY3IvMjF0T1ZEUGlXdGZDZHlraDdGQ1hpNkZhWUhvTnk1QTZFS1FMZkxCdUpvVS9Rb0N1ZmxkbXdsRmFGREtsKwpLb29zNlIxUVlKZkNOWmZnb2NyVzFQSWgrOHQxSkl2dzZJem84K2ZUbWU3bmV2N09sMllaaU1XSnBSWUt4OE1nCnhXMlVnVFhsUnBtUU41NnBjOUxVc25WQThGTkRCTjU3K2dNSmorWG1kRG1idE1wT3N5WGZTTkVnbnV3TVBjRWMKbXdJREFRQUIKLS0tLS1FTkQgUFVCTElDIEtFWS0tLS0tCg=='
    105     _IMAGE_METADATA_SIGNATURE_WITH_KEY1='ZA3p2Gfh6qKNqf0as3LeoEou1AsfP73khfk0+hiJ0UFmqTTMj1b8PBeHSHzeRNoJvNZfZBD372PH0BSlKm4BeJ6qyySVDTyC55pKOQyQaC/c5tncvknId2acSEp0XSM5wvkXON0kS9sPfJi7qxDaTJnoCGi6gDKiMjEH3WhsE/1FG5AQ1HPibbeBK3RTtxGmqOIYses+RvJTag7wobdUnXe2Q7l6/c+wCD6m99yXK6l6Vm05gjAR7nhMd4ZyVfN2xaX8KSt9VybO3UuvG9yQUDhxy+ZURY0aaQPLdYcTsuocg/hqDlXctl6WBf6lKogeVqgfaypXqkPlYfgf0tHDGg=='
    106     _IMAGE_PUBLIC_KEY2='LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUlJQklqQU5CZ2txaGtpRzl3MEJBUUVGQUFPQ0FROEFNSUlCQ2dLQ0FRRUFxZE03Z25kNDNjV2ZRenlydDE2UQpESEUrVDB5eGcxOE9aTys5c2M4aldwakMxekZ0b01Gb2tFU2l1OVRMVXArS1VDMjc0ZitEeElnQWZTQ082VTVECkpGUlBYVXp2ZTF2YVhZZnFsalVCeGMrSlljR2RkNlBDVWw0QXA5ZjAyRGhrckduZi9ya0hPQ0VoRk5wbTUzZG8Kdlo5QTZRNUtCZmNnMUhlUTA4OG9wVmNlUUd0VW1MK2JPTnE1dEx2TkZMVVUwUnUwQW00QURKOFhtdzRycHZxdgptWEphRm1WdWYvR3g3K1RPbmFKdlpUZU9POUFKSzZxNlY4RTcrWlppTUljNUY0RU9zNUFYL2xaZk5PM1JWZ0cyCk83RGh6emErbk96SjNaSkdLNVI0V3daZHVobjlRUllvZ1lQQjBjNjI4NzhxWHBmMkJuM05wVVBpOENmL1JMTU0KbVFJREFRQUIKLS0tLS1FTkQgUFVCTElDIEtFWS0tLS0tCg=='
    107     _IMAGE_METADATA_SIGNATURE_WITH_KEY2='k+mg0w5Jy8DXdF9Vw0MJdJdAj1S4EYR3k9fR4ECZmZplhmzUFvyPWVAHEYDEGLtNBbdaa66+ErOE/clERfxvjkIHbIlTUWDnqgKKPYnZ5dNuEDrHn8875ild9OwBgHEK7NSxaNyRGThLfVCqIUKUzMnjBk/elAiiY0hlLIN9Owitw3f+p9E2chYSdh1dpqlcs14JCULcO/+p+ZfQdeNkN600tS02SGOBwV4W8wXt7EWYdu2awp39z+zDniudFShIpamhhUbddqAn7aZTNE6qGgYVZuWNv3O3kBY4dMb7NsSZInn+fkC39QlXlqoM+ShLVhlpJa/MdOpX7g1UQKLa9A=='
    108 
    109     @staticmethod
    110     def _string_has_strings(haystack, needles):
    111         """Returns True iff all the strings in the list |needles| are
    112         present in the string |haystack|."""
    113         for n in needles:
    114             if haystack.find(n) == -1:
    115                 return False
    116         return True
    117 
    118     def _check_signature(self, metadata_signature, public_key,
    119                          expected_log_messages, failure_message):
    120         """Helper function for updating with a Canned Omaha response."""
    121 
    122         # Runs the update on the DUT and expect it to fail.
    123         client_host = autotest.Autotest(self._host)
    124         client_host.run_test(
    125                 'autoupdate_CannedOmahaUpdate',
    126                 image_url=self._staged_payload_url,
    127                 image_size=self._IMAGE_SIZE,
    128                 image_sha256=self._IMAGE_SHA256,
    129                 allow_failure=True,
    130                 metadata_size=self._IMAGE_METADATA_SIZE,
    131                 metadata_signature=metadata_signature,
    132                 public_key=public_key)
    133 
    134         cmdresult = self._host.run('cat /var/log/update_engine.log')
    135         if not self._string_has_strings(cmdresult.stdout,
    136                                         expected_log_messages):
    137             raise error.TestFail(failure_message)
    138 
    139 
    140     def _check_bad_metadata_signature(self):
    141         """Checks that update_engine rejects updates where the payload
    142         and Omaha response do not agree on the metadata signature."""
    143 
    144         expected_log_messages = [
    145                 'Mandating payload hash checks since Omaha Response for '
    146                 'unofficial build includes public RSA key',
    147                 'Mandatory metadata signature validation failed']
    148 
    149         self._check_signature(
    150                 metadata_signature=self._IMAGE_METADATA_SIGNATURE_WITH_KEY1,
    151                 public_key=self._IMAGE_PUBLIC_KEY2,
    152                 expected_log_messages=expected_log_messages,
    153                 failure_message='Check for bad metadata signature failed.')
    154 
    155 
    156     def _check_bad_payload_signature(self):
    157         """Checks that update_engine rejects updates where the payload
    158         signature does not match what is expected."""
    159 
    160         expected_log_messages = [
    161                 'Mandating payload hash checks since Omaha Response for '
    162                 'unofficial build includes public RSA key',
    163                 'Metadata hash signature matches value in Omaha response.',
    164                 'Public key verification failed, thus update failed']
    165 
    166         self._check_signature(
    167                 metadata_signature=self._IMAGE_METADATA_SIGNATURE_WITH_KEY2,
    168                 public_key=self._IMAGE_PUBLIC_KEY2,
    169                 expected_log_messages=expected_log_messages,
    170                 failure_message='Check for payload signature failed.')
    171 
    172 
    173     def _stage_image(self, image_url):
    174         """Requests an image server from the lab to stage the image
    175         specified by |image_url| (typically a Google Storage
    176         URL). Returns the URL to the staged image."""
    177 
    178         # We don't have a build so just fake the string.
    179         build = 'x86-fake-release/R42-4242.0.0-a1-bFAKE'
    180         image_server = dev_server.ImageServer.resolve(build)
    181         archive_url = os.path.dirname(image_url)
    182         filename = os.path.basename(image_url)
    183         # ImageServer expects an image parameter, but we don't have one.
    184         image_server.stage_artifacts(image='fake_image',
    185                                      files=[filename],
    186                                      archive_url=archive_url)
    187 
    188         # ImageServer has no way to give us the URL of the staged file...
    189         base, name = _split_url(image_url)
    190         staged_url = '%s/static/%s' % (image_server.url(), name)
    191         return staged_url
    192 
    193 
    194     def cleanup(self):
    195         if self._host:
    196             self._host.reboot()
    197 
    198 
    199     def run_once(self, host):
    200         """Runs the test on the DUT represented by |host|."""
    201 
    202         self._host = host
    203 
    204         # First, stage the image.
    205         self._staged_payload_url = self._stage_image(self._IMAGE_GS_URL)
    206 
    207         # Then run the tests.
    208         self._check_bad_metadata_signature()
    209         self._check_bad_payload_signature()
    210 
    211