# 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.

import os
import urlparse

from autotest_lib.client.common_lib import error
from autotest_lib.client.common_lib.cros import dev_server
from autotest_lib.server import autotest, test

def _split_url(url):
    """Splits a URL into the URL base and path."""
    split_url = urlparse.urlsplit(url)
    url_base = urlparse.urlunsplit(
            (split_url.scheme, split_url.netloc, '', '', ''))
    url_path = split_url.path
    return url_base, url_path.lstrip('/')


class autoupdate_CatchBadSignatures(test.test):
    """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, follow these eight
    simple steps:

    1. Build a test image:

      $ cd ~/trunk/src/scripts
      $ ./build_packages --board=${BOARD}
      $ ./build_image --board=${BOARD} --noenable_rootfs_verification test

    2. Serve the image the DUT like this:

      $ cd ~/trunk/strc/platform/dev
      $ ./devserver.py --test_image                               \
                       --private_key                              \
                         ../update_engine/unittest_key.pem        \
                       --private_key_for_metadata_hash_signature  \
                         ../update_engine/unittest_key.pem        \
                       --public_key                               \
                          ../update_engine/unittest_key2.pub.pem

    3. Update the DUT - the update should fail at the metadata
       verification stage.

    4. From the update_engine logs (stored in /var/log/update_engine/)
       on the DUT, find the Omaha response sent to the DUT and update
       the following constants with values from the XML:

        _IMAGE_SHA256: set it to the 'sha256'
        _IMAGE_METADATA_SIZE: set it to the 'MetadataSize'
        _IMAGE_PUBLIC_KEY2: set it to the 'PublicKeyRsa'
        _IMAGE_METADATA_SIGNATURE_WITH_KEY1: set it to 'MetadataSignatureRsa'

       Also download the image payload ('url' and 'codebase' tags),
       upload it to Google Storage and update the _IMAGE_GS_URL and
       _IMAGE_SIZE constants with the resulting URL and the size.

    5. Serve the image to the DUT again and note the slightly different
       parameters this time. Note that the image served is the same,
       however the Omaha response will be different.

      $ cd ~/trunk/strc/platform/dev
      $ ./devserver.py --test_image                               \
                       --private_key                              \
                         ../update_engine/unittest_key.pem        \
                       --private_key_for_metadata_hash_signature  \
                         ../update_engine/unittest_key2.pem       \
                       --public_key                               \
                          ../update_engine/unittest_key2.pub.pem

    6. Update the DUT - the update should fail at the payload
       verification stage.

    7. Like in step 4., examine the update_engine logs and update the
       following constants:

        _IMAGE_METADATA_SIGNATURE_WITH_KEY2: set to 'MetadataSignatureRsa'

    8. Now run the test and ensure that it passes

      $ cd ~/trunk/src/scripts
      $ test_that -b ${BOARD} --fast <DUT_IP> autoupdate_CatchBadSignatures

    """
    version = 1

    # The test image to use and the values associated with it.
    _IMAGE_GS_URL='gs://chromiumos-test-assets-public/autoupdate/autoupdate_CatchBadSignatures-payload-lumpy-R33-4970.0.2013_11_15_1654-a1'
    _IMAGE_SIZE=369080798
    _IMAGE_METADATA_SIZE=58439
    _IMAGE_SHA256='FeWxyPLdz1/UDHxskkYSkQm64D5kQgaSb1IEXX3/sjA='
    _IMAGE_PUBLIC_KEY1='LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUlJQklqQU5CZ2txaGtpRzl3MEJBUUVGQUFPQ0FROEFNSUlCQ2dLQ0FRRUF4NmhxUytIYmM3ak44MmVrK09CawpISk52bFdYa3R5UzJYQ3VRdEd0bkhqRVY3T3U1aEhORjk2czV3RW44UkR0cmRMb2NtMGErU3FYWGY3S3ljRlJUClp4TGREYnFXQU1VbFBYT2FQSStQWkxXa0I3L0tWN0NkajZ2UEdiZXE3ZWx1K2FUL2J1ZGh6VHZxMnN0WXJyQWwKY3IvMjF0T1ZEUGlXdGZDZHlraDdGQ1hpNkZhWUhvTnk1QTZFS1FMZkxCdUpvVS9Rb0N1ZmxkbXdsRmFGREtsKwpLb29zNlIxUVlKZkNOWmZnb2NyVzFQSWgrOHQxSkl2dzZJem84K2ZUbWU3bmV2N09sMllaaU1XSnBSWUt4OE1nCnhXMlVnVFhsUnBtUU41NnBjOUxVc25WQThGTkRCTjU3K2dNSmorWG1kRG1idE1wT3N5WGZTTkVnbnV3TVBjRWMKbXdJREFRQUIKLS0tLS1FTkQgUFVCTElDIEtFWS0tLS0tCg=='
    _IMAGE_METADATA_SIGNATURE_WITH_KEY1='ZA3p2Gfh6qKNqf0as3LeoEou1AsfP73khfk0+hiJ0UFmqTTMj1b8PBeHSHzeRNoJvNZfZBD372PH0BSlKm4BeJ6qyySVDTyC55pKOQyQaC/c5tncvknId2acSEp0XSM5wvkXON0kS9sPfJi7qxDaTJnoCGi6gDKiMjEH3WhsE/1FG5AQ1HPibbeBK3RTtxGmqOIYses+RvJTag7wobdUnXe2Q7l6/c+wCD6m99yXK6l6Vm05gjAR7nhMd4ZyVfN2xaX8KSt9VybO3UuvG9yQUDhxy+ZURY0aaQPLdYcTsuocg/hqDlXctl6WBf6lKogeVqgfaypXqkPlYfgf0tHDGg=='
    _IMAGE_PUBLIC_KEY2='LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUlJQklqQU5CZ2txaGtpRzl3MEJBUUVGQUFPQ0FROEFNSUlCQ2dLQ0FRRUFxZE03Z25kNDNjV2ZRenlydDE2UQpESEUrVDB5eGcxOE9aTys5c2M4aldwakMxekZ0b01Gb2tFU2l1OVRMVXArS1VDMjc0ZitEeElnQWZTQ082VTVECkpGUlBYVXp2ZTF2YVhZZnFsalVCeGMrSlljR2RkNlBDVWw0QXA5ZjAyRGhrckduZi9ya0hPQ0VoRk5wbTUzZG8Kdlo5QTZRNUtCZmNnMUhlUTA4OG9wVmNlUUd0VW1MK2JPTnE1dEx2TkZMVVUwUnUwQW00QURKOFhtdzRycHZxdgptWEphRm1WdWYvR3g3K1RPbmFKdlpUZU9POUFKSzZxNlY4RTcrWlppTUljNUY0RU9zNUFYL2xaZk5PM1JWZ0cyCk83RGh6emErbk96SjNaSkdLNVI0V3daZHVobjlRUllvZ1lQQjBjNjI4NzhxWHBmMkJuM05wVVBpOENmL1JMTU0KbVFJREFRQUIKLS0tLS1FTkQgUFVCTElDIEtFWS0tLS0tCg=='
    _IMAGE_METADATA_SIGNATURE_WITH_KEY2='k+mg0w5Jy8DXdF9Vw0MJdJdAj1S4EYR3k9fR4ECZmZplhmzUFvyPWVAHEYDEGLtNBbdaa66+ErOE/clERfxvjkIHbIlTUWDnqgKKPYnZ5dNuEDrHn8875ild9OwBgHEK7NSxaNyRGThLfVCqIUKUzMnjBk/elAiiY0hlLIN9Owitw3f+p9E2chYSdh1dpqlcs14JCULcO/+p+ZfQdeNkN600tS02SGOBwV4W8wXt7EWYdu2awp39z+zDniudFShIpamhhUbddqAn7aZTNE6qGgYVZuWNv3O3kBY4dMb7NsSZInn+fkC39QlXlqoM+ShLVhlpJa/MdOpX7g1UQKLa9A=='

    @staticmethod
    def _string_has_strings(haystack, needles):
        """Returns True iff all the strings in the list |needles| are
        present in the string |haystack|."""
        for n in needles:
            if haystack.find(n) == -1:
                return False
        return True

    def _check_signature(self, metadata_signature, public_key,
                         expected_log_messages, failure_message):
        """Helper function for updating with a Canned Omaha response."""

        # Runs the update on the DUT and expect it to fail.
        client_host = autotest.Autotest(self._host)
        client_host.run_test(
                'autoupdate_CannedOmahaUpdate',
                image_url=self._staged_payload_url,
                image_size=self._IMAGE_SIZE,
                image_sha256=self._IMAGE_SHA256,
                allow_failure=True,
                metadata_size=self._IMAGE_METADATA_SIZE,
                metadata_signature=metadata_signature,
                public_key=public_key)

        cmdresult = self._host.run('cat /var/log/update_engine.log')
        if not self._string_has_strings(cmdresult.stdout,
                                        expected_log_messages):
            raise error.TestFail(failure_message)


    def _check_bad_metadata_signature(self):
        """Checks that update_engine rejects updates where the payload
        and Omaha response do not agree on the metadata signature."""

        expected_log_messages = [
                'Mandating payload hash checks since Omaha Response for '
                'unofficial build includes public RSA key',
                'Mandatory metadata signature validation failed']

        self._check_signature(
                metadata_signature=self._IMAGE_METADATA_SIGNATURE_WITH_KEY1,
                public_key=self._IMAGE_PUBLIC_KEY2,
                expected_log_messages=expected_log_messages,
                failure_message='Check for bad metadata signature failed.')


    def _check_bad_payload_signature(self):
        """Checks that update_engine rejects updates where the payload
        signature does not match what is expected."""

        expected_log_messages = [
                'Mandating payload hash checks since Omaha Response for '
                'unofficial build includes public RSA key',
                'Metadata hash signature matches value in Omaha response.',
                'Public key verification failed, thus update failed']

        self._check_signature(
                metadata_signature=self._IMAGE_METADATA_SIGNATURE_WITH_KEY2,
                public_key=self._IMAGE_PUBLIC_KEY2,
                expected_log_messages=expected_log_messages,
                failure_message='Check for payload signature failed.')


    def _stage_image(self, image_url):
        """Requests an image server from the lab to stage the image
        specified by |image_url| (typically a Google Storage
        URL). Returns the URL to the staged image."""

        # We don't have a build so just fake the string.
        build = 'x86-fake-release/R42-4242.0.0-a1-bFAKE'
        image_server = dev_server.ImageServer.resolve(build)
        archive_url = os.path.dirname(image_url)
        filename = os.path.basename(image_url)
        # ImageServer expects an image parameter, but we don't have one.
        image_server.stage_artifacts(image='fake_image',
                                     files=[filename],
                                     archive_url=archive_url)

        # ImageServer has no way to give us the URL of the staged file...
        base, name = _split_url(image_url)
        staged_url = '%s/static/%s' % (image_server.url(), name)
        return staged_url


    def cleanup(self):
        if self._host:
            self._host.reboot()


    def run_once(self, host):
        """Runs the test on the DUT represented by |host|."""

        self._host = host

        # First, stage the image.
        self._staged_payload_url = self._stage_image(self._IMAGE_GS_URL)

        # Then run the tests.
        self._check_bad_metadata_signature()
        self._check_bad_payload_signature()