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 5import os 6import urlparse 7 8from autotest_lib.client.common_lib import error 9from autotest_lib.client.common_lib.cros import dev_server 10from autotest_lib.server import autotest, test 11 12def _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 21class 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