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/src/platform/dev
     46       $ ./devserver.py --test_image                                             \
     47                        --private_key                                            \
     48                          ../../aosp/system/update_engine/unittest_key.pem       \
     49                        --private_key_for_metadata_hash_signature                \
     50                          ../../aosp/system/update_engine/unittest_key.pem       \
     51                        --public_key                                             \
     52                          ../../aosp/system/update_engine/unittest_key2.pub.pem
     53 
     54       Note that unittest_key2.pub.pem can be generated via
     55       $ openssl rsa                                                             \
     56                 -in ../../aosp/system/update_engine/unittest_key2.pem -pubout   \
     57                 -out ../../aosp/system/update_engine/unittest_key2.pub.pem
     58 
     59     3. Update the DUT - the update should fail at the metadata
     60        verification stage.
     61 
     62     4. From the update_engine logs (stored in /var/log/update_engine/)
     63        on the DUT, find the Omaha response sent to the DUT and update
     64        the following constants with values from the XML:
     65 
     66         _IMAGE_SHA256: set it to the 'sha256'
     67         _IMAGE_METADATA_SIZE: set it to the 'MetadataSize'
     68         _IMAGE_PUBLIC_KEY2: set it to the 'PublicKeyRsa'
     69         _IMAGE_METADATA_SIGNATURE_WITH_KEY1: set it to 'MetadataSignatureRsa'
     70 
     71        Also download the image payload (follow 'url' and 'codebase' tags for
     72        directory then 'package' for the file name and its size),
     73        upload it to Google Storage and update the _IMAGE_GS_URL and
     74        _IMAGE_SIZE constants with the resulting URL and the size.
     75 
     76     5. Serve the image to the DUT again and note the slightly different
     77        parameters this time. Note that the image served is the same,
     78        however the Omaha response will be different.
     79 
     80       $ cd ~/trunk/src/platform/dev
     81       $ ./devserver.py --test_image                                             \
     82                        --private_key                                            \
     83                          ../../aosp/system/update_engine/unittest_key.pem       \
     84                        --private_key_for_metadata_hash_signature                \
     85                          ../../aosp/system/update_engine/unittest_key2.pem      \
     86                        --public_key                                             \
     87                          ../../aosp/system/update_engine/unittest_key2.pub.pem
     88 
     89     6. Update the DUT - the update should fail at the payload
     90        verification stage.
     91 
     92     7. Like in step 4., examine the update_engine logs and update the
     93        following constants:
     94 
     95         _IMAGE_METADATA_SIGNATURE_WITH_KEY2: set to 'MetadataSignatureRsa'
     96 
     97     8. Now run the test and ensure that it passes
     98 
     99       $ cd ~/trunk/src/scripts
    100       $ test_that -b ${BOARD} --fast <DUT_IP> autoupdate_CatchBadSignatures
    101 
    102     """
    103     version = 1
    104 
    105     # The test image to use and the values associated with it.
    106     _IMAGE_GS_URL='gs://chromiumos-test-assets-public/autoupdate/autoupdate_CatchBadSignatures-payload-sentry-R54-8719.0.2016_08_18_2057-a1'
    107     _IMAGE_SIZE=405569018
    108     _IMAGE_METADATA_SIZE=49292
    109     _IMAGE_SHA256='PHjzsTdCRMvMM7s+8f7K8ZegKoFnAf1UNhG6qc7zjRU='
    110     _IMAGE_PUBLIC_KEY1='LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUlJQklqQU5CZ2txaGtpRzl3MEJBUUVGQUFPQ0FROEFNSUlCQ2dLQ0FRRUF4NmhxUytIYmM3ak44MmVrK09CawpISk52bFdYa3R5UzJYQ3VRdEd0bkhqRVY3T3U1aEhORjk2czV3RW44UkR0cmRMb2NtMGErU3FYWGY3S3ljRlJUClp4TGREYnFXQU1VbFBYT2FQSStQWkxXa0I3L0tWN0NkajZ2UEdiZXE3ZWx1K2FUL2J1ZGh6VHZxMnN0WXJyQWwKY3IvMjF0T1ZEUGlXdGZDZHlraDdGQ1hpNkZhWUhvTnk1QTZFS1FMZkxCdUpvVS9Rb0N1ZmxkbXdsRmFGREtsKwpLb29zNlIxUVlKZkNOWmZnb2NyVzFQSWgrOHQxSkl2dzZJem84K2ZUbWU3bmV2N09sMllaaU1XSnBSWUt4OE1nCnhXMlVnVFhsUnBtUU41NnBjOUxVc25WQThGTkRCTjU3K2dNSmorWG1kRG1idE1wT3N5WGZTTkVnbnV3TVBjRWMKbXdJREFRQUIKLS0tLS1FTkQgUFVCTElDIEtFWS0tLS0tCg=='
    111     _IMAGE_METADATA_SIGNATURE_WITH_KEY1='RH0QkJErsz9T6u5/0nNYKRjVPfH08+j/zdBU2BimU2jAa4zn7HElhIk4v7l92qtHJAdAfH8JzTCCUQcqxfBL0CLxoXClKbnFjb4DKKp5z4GHJ4Be7q5hq+OVFFTMs+WV6rVP+VPWfirKN3RQy2CaUnkcoaBsa9pgU3SfZYGK4EkgSY7Fwnjzeu1oCUb8v+VrY93Hia8vJgKRG1EBtK2a9qLy/2uZ9twHyKnykt5VeLXMUL7x2ChdY1IzJaPDfGMyneoUQ2k1Yr/ROuVaYWI08lZfM0W2Abj6uCCHNu/K2oPHevJs1yyy4lmRRr2aWDnZ93GA17Jb7XwJO4JgNUHQgQ=='
    112     _IMAGE_PUBLIC_KEY2='LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUlJQklqQU5CZ2txaGtpRzl3MEJBUUVGQUFPQ0FROEFNSUlCQ2dLQ0FRRUFxZE03Z25kNDNjV2ZRenlydDE2UQpESEUrVDB5eGcxOE9aTys5c2M4aldwakMxekZ0b01Gb2tFU2l1OVRMVXArS1VDMjc0ZitEeElnQWZTQ082VTVECkpGUlBYVXp2ZTF2YVhZZnFsalVCeGMrSlljR2RkNlBDVWw0QXA5ZjAyRGhrckduZi9ya0hPQ0VoRk5wbTUzZG8Kdlo5QTZRNUtCZmNnMUhlUTA4OG9wVmNlUUd0VW1MK2JPTnE1dEx2TkZMVVUwUnUwQW00QURKOFhtdzRycHZxdgptWEphRm1WdWYvR3g3K1RPbmFKdlpUZU9POUFKSzZxNlY4RTcrWlppTUljNUY0RU9zNUFYL2xaZk5PM1JWZ0cyCk83RGh6emErbk96SjNaSkdLNVI0V3daZHVobjlRUllvZ1lQQjBjNjI4NzhxWHBmMkJuM05wVVBpOENmL1JMTU0KbVFJREFRQUIKLS0tLS1FTkQgUFVCTElDIEtFWS0tLS0tCg=='
    113     _IMAGE_METADATA_SIGNATURE_WITH_KEY2='TWzj+aki+yS0nKCzy0/t+pTdHMh0N9ffjNbwIJhSx9bQ3813oYaF3vQsky29o0PuDWih1363uEHOoCjYrzxLnwuk1NK7/6Psb88kAYjc7gk1IgW/xuFTybxSL0bbOAN4p1QI6oRMPYrf5mZCE+VHhbSaMazK0AebFEg0fwvXkaoo/pQQWRWCH0u0GCqDBfGZlQATe/79inXda4mD19E9EvvMqm2ZStys7MMPCkkYyEsZWFmm6AFykiWtVMNyc6JWSTKh6ZKsme+l2Rr5orTubFbdwMuItcb6KySnsgOj5MAQ+HVFfjgJuEs4eRsP/Tfjn213v4bJVUSt6DwABg7/gw=='
    114 
    115     @staticmethod
    116     def _string_has_strings(haystack, needles):
    117         """Returns True iff all the strings in the list |needles| are
    118         present in the string |haystack|."""
    119         for n in needles:
    120             if haystack.find(n) == -1:
    121                 return False
    122         return True
    123 
    124     def _check_signature(self, metadata_signature, public_key,
    125                          expected_log_messages, failure_message):
    126         """Helper function for updating with a Canned Omaha response."""
    127 
    128         # Runs the update on the DUT and expect it to fail.
    129         client_host = autotest.Autotest(self._host)
    130         client_host.run_test(
    131                 'autoupdate_CannedOmahaUpdate',
    132                 image_url=self._staged_payload_url,
    133                 image_size=self._IMAGE_SIZE,
    134                 image_sha256=self._IMAGE_SHA256,
    135                 allow_failure=True,
    136                 metadata_size=self._IMAGE_METADATA_SIZE,
    137                 metadata_signature=metadata_signature,
    138                 public_key=public_key)
    139 
    140         cmdresult = self._host.run('cat /var/log/update_engine.log')
    141         if not self._string_has_strings(cmdresult.stdout,
    142                                         expected_log_messages):
    143             raise error.TestFail(failure_message)
    144 
    145 
    146     def _check_bad_metadata_signature(self):
    147         """Checks that update_engine rejects updates where the payload
    148         and Omaha response do not agree on the metadata signature."""
    149 
    150         expected_log_messages = [
    151                 'Mandating payload hash checks since Omaha Response for '
    152                 'unofficial build includes public RSA key',
    153                 'Mandatory metadata signature validation failed']
    154 
    155         self._check_signature(
    156                 metadata_signature=self._IMAGE_METADATA_SIGNATURE_WITH_KEY1,
    157                 public_key=self._IMAGE_PUBLIC_KEY2,
    158                 expected_log_messages=expected_log_messages,
    159                 failure_message='Check for bad metadata signature failed.')
    160 
    161 
    162     def _check_bad_payload_signature(self):
    163         """Checks that update_engine rejects updates where the payload
    164         signature does not match what is expected."""
    165 
    166         expected_log_messages = [
    167                 'Mandating payload hash checks since Omaha Response for '
    168                 'unofficial build includes public RSA key',
    169                 'Metadata hash signature matches value in Omaha response.',
    170                 'Public key verification failed, thus update failed']
    171 
    172         self._check_signature(
    173                 metadata_signature=self._IMAGE_METADATA_SIGNATURE_WITH_KEY2,
    174                 public_key=self._IMAGE_PUBLIC_KEY2,
    175                 expected_log_messages=expected_log_messages,
    176                 failure_message='Check for payload signature failed.')
    177 
    178 
    179     def _stage_image(self, image_url):
    180         """Requests an image server from the lab to stage the image
    181         specified by |image_url| (typically a Google Storage
    182         URL). Returns the URL to the staged image."""
    183 
    184         # We don't have a build so just fake the string.
    185         build = 'x86-fake-release/R42-4242.0.0-a1-bFAKE'
    186         image_server = dev_server.ImageServer.resolve(build,
    187                                                       self._host.hostname)
    188         archive_url = os.path.dirname(image_url)
    189         filename = os.path.basename(image_url)
    190         # ImageServer expects an image parameter, but we don't have one.
    191         image_server.stage_artifacts(image='fake_image',
    192                                      files=[filename],
    193                                      archive_url=archive_url)
    194 
    195         # ImageServer has no way to give us the URL of the staged file...
    196         base, name = _split_url(image_url)
    197         staged_url = '%s/static/%s' % (image_server.url(), name)
    198         return staged_url
    199 
    200 
    201     def cleanup(self):
    202         if self._host:
    203             self._host.reboot()
    204 
    205 
    206     def run_once(self, host):
    207         """Runs the test on the DUT represented by |host|."""
    208 
    209         self._host = host
    210 
    211         # First, stage the image.
    212         self._staged_payload_url = self._stage_image(self._IMAGE_GS_URL)
    213 
    214         # Then run the tests.
    215         self._check_bad_metadata_signature()
    216         self._check_bad_payload_signature()
    217 
    218