1# Copyright 2017 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 logging
6
7from autotest_lib.client.common_lib import error
8from autotest_lib.server import autotest, test
9from autotest_lib.client.common_lib.cros import tpm_utils
10
11
12class firmware_Cr50InvalidateRW(test.test):
13    """
14    Verify the inactive Cr50 header on the first login after cryptohome
15    restarts.
16
17    There are two special cases this test covers: logging in after the TPM
18    owner is cleared and logging in as guest.
19
20    After the tpm owner is cleared, corrupting the header will be done on
21    the second login. During guest login the owner wont be cleared.
22    """
23    version = 1
24
25    GET_CRYPTOHOME_MESSAGE ='grep cryptohomed /var/log/messages'
26    SUCCESS = 'Successfully invalidated inactive Cr50 RW'
27    FAIL = 'Invalidating inactive Cr50 RW failed'
28    NO_ATTEMPT = 'Did not try to invalidate header'
29    LOGIN_ATTEMPTS = 5
30
31    def initialize(self, host):
32        """Initialize servo and cr50"""
33        super(firmware_Cr50InvalidateRW, self).initialize()
34
35        self.host = host
36        self.client_at = autotest.Autotest(self.host)
37
38        self.last_message = None
39        # get the messages already in /var/log/messages so we don't use them
40        # later.
41        self.check_for_invalidated_rw()
42
43
44    def check_for_invalidated_rw(self):
45        """Use /var/log/messages to see if the rw header was invalidated.
46
47        Returns a string NO_ATTEMPT if cryptohome did not try to invalidate the
48        header or the cryptohome message if the attempt failed or succeeded.
49        """
50        # Get the relevant messages from /var/log/messages
51        message_str = self.host.run(self.GET_CRYPTOHOME_MESSAGE,
52                                    verbose=False).stdout.strip()
53
54        # Remove the messages we have seen in the past
55        if self.last_message:
56            message_str = message_str.rsplit(self.last_message, 1)[-1]
57        messages = message_str.split('\n')
58
59        # Save the last message so we can identify new messages later
60        self.last_message = messages[-1]
61
62        rv = self.NO_ATTEMPT
63        # print all cryptohome messages.
64        for message in messages:
65            logging.debug(message)
66            # Return the message that is related to the RW invalidate attempt
67            if self.FAIL in message or self.SUCCESS in message:
68                rv = message
69        return rv
70
71
72    def login(self, use_guest):
73        """Run the test to login."""
74        if use_guest:
75            self.client_at.run_test('login_CryptohomeIncognito')
76        else:
77            self.client_at.run_test('login_LoginSuccess')
78
79
80    def login_and_verify(self, use_guest=False, corrupt_login=None):
81        """Verify the header is only invalidated on the specified login.
82
83        login LOGIN_ATTEMPTS times. Verify that cryptohome only tries to corrupt
84        the inactive cr50 header on the specified login. If it tries on a
85        different login or fails to corrupt the header, raise an error.
86
87        Args:
88            use_guest: True to login as guest
89            corrupt_login: The login attempt that we expect the header to be
90                           corrupted on
91
92        Raises:
93            TestError if the system attempts to corrupt the header on any login
94            that isn't corrupt_login or if an attepmt to corrupt the header
95            fails.
96        """
97        for i in xrange(self.LOGIN_ATTEMPTS):
98            attempt = i + 1
99
100            self.login(use_guest)
101            result = self.check_for_invalidated_rw()
102
103            message = '%slogin %d: %s' % ('guest ' if use_guest else '',
104                                          attempt, result)
105            logging.info(message)
106
107            # Anytime the invalidate attempt fails raise an error
108            if self.FAIL in result:
109                raise error.TestError(message)
110
111            # The header should be invalidated only on corrupt_login. Raise
112            # an error if it was invalidated on some other login or if
113            # cryptohome did not try on the first one.
114            if (attempt == corrupt_login) != (self.SUCCESS in result):
115                raise error.TestError('Unexpected result %s' % message)
116
117
118    def restart_cryptohome(self):
119        """Restart cryptohome
120
121        Cryptohome only sends the command to corrupt the header once. Once it
122        has been sent it wont be sent again until cryptohome is restarted.
123        """
124        self.host.run('restart cryptohomed')
125
126
127    def clear_tpm_owner(self):
128        """Clear the tpm owner."""
129        logging.info('Clearing the TPM owner')
130        tpm_utils.ClearTPMOwnerRequest(self.host, wait_for_ready=True)
131
132
133    def after_run_once(self):
134        """Print the run information after each successful run"""
135        logging.info('finished run %d', self.iteration)
136
137
138    def run_once(self, host):
139        """Login to validate ChromeOS corrupts the inactive header"""
140        # After clearing the tpm owner the header will be corrupted on the
141        # second login
142        self.clear_tpm_owner()
143        self.login_and_verify(corrupt_login=2)
144
145        # The header is corrupted on the first login after cryptohome is reset
146        self.restart_cryptohome()
147        self.login_and_verify(corrupt_login=1)
148
149        # Cryptohome is reset after reboot
150        self.host.reboot()
151        self.login_and_verify(corrupt_login=1)
152
153        # The header is not corrupted after guest login, but will be corrupted
154        # on the first login after that.
155        self.restart_cryptohome()
156        self.login_and_verify(use_guest=True)
157        self.login_and_verify(corrupt_login=1)
158