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