1# Copyright 2014 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, os
6import time
7
8from autotest_lib.client.common_lib import error
9
10
11_PASSWD_FILE = '/var/tmp/tpm_password'
12_RM_DIRS = ('/home/.shadow/* ' +
13            '/home/chronos/.oobe_completed ' +
14            '/home/chronos/Local\ State ' +
15            '/var/cache/app_pack ' +
16            '/var/cache/shill/default.profile ' +
17            '/var/lib/tpm ' +
18            '/var/lib/whitelist/* ')
19
20
21class NoTPMPasswordException(Exception):
22    """No TPM Password could be found."""
23    pass
24
25
26def TPMStatus(client):
27    """Returns a dictionary with TPM status.
28
29    @param client: client object to run commands on.
30    """
31    out = client.run('cryptohome --action=tpm_status').stdout.strip()
32    out = out.replace('TPM ', '')
33    lines = out.split('\n')
34    status = {}
35    for item in lines:
36        item = item.split(':')
37        if not item[0]:
38            continue
39        if len(item) == 1:
40            item.append('')
41        item = map(lambda x : x.strip(), item)
42        item[1] = True if item[1] == 'true' else item[1]
43        item[1] = False if item[1] == 'false' else item[1]
44        status[item[0]] = item[1]
45    return status
46
47
48def IsTPMAvailable(client):
49    """Returns True if the TPM is unowned and enabled.
50
51    @param client: client object to run commands on.
52    """
53    status = TPMStatus(client)
54    return status['Enabled'] and not status['Owned']
55
56
57def ClearTPMServer(client, out_dir):
58    """Clears the TPM and reboots from a server-side autotest.
59
60    @param client: client object to run commands on.
61    @param out_dir: temporary directory to store the retrieved password file.
62    """
63    if IsTPMAvailable(client):
64        logging.debug('TPM is not owned')
65        return
66
67    client.run('stop ui')
68    try:
69        password = TPMStatus(client)['Password']
70        if not password:
71            try:
72                client.get_file(_PASSWD_FILE, out_dir)
73            except error.AutoservRunError:
74                raise NoTPMPasswordException(
75                        'TPM Password file %s doesn\'t exist, falling back on '
76                        'clear_tpm_owner_request to clear the TPM. You may '
77                        'need to have the firmware clear the TPM, for instance '
78                        'by toggling the dev switch.' % _PASSWD_FILE)
79            with open(os.path.join(out_dir,
80                      os.path.basename(_PASSWD_FILE))) as f:
81                password = f.read().rstrip()
82        if not password:
83            raise NoTPMPasswordException(
84                    'TPM Password file %s empty, falling back on '
85                    'clear_tpm_owner_request to clear the TPM. You may need to '
86                    'have the firmware clear the TPM, for instance by toggling '
87                    'the dev switch.' % _PASSWD_FILE)
88
89        res = client.run('tpm_clear --pass ' + password).stdout.strip()
90        logging.warn(repr(res))
91    except NoTPMPasswordException as e:
92        logging.warn(e.args[0])
93        client.run('crossystem clear_tpm_owner_request=1')
94
95    CleanupAndReboot(client)
96
97
98def ClearTPMOwnerRequest(client, wait_for_ready=False, timeout=60):
99    """Clears the TPM using crossystem command.
100
101    @param client: client object to run commands on.
102    @param wait_for_ready: wait until the TPM status is ready
103    @param timeout: number of seconds to wait for the TPM to become ready.
104    """
105    if not client.run('crossystem clear_tpm_owner_request=1',
106                      ignore_status=True).exit_status == 0:
107        raise error.TestFail('Unable to clear TPM.')
108
109    CleanupAndReboot(client)
110
111    if wait_for_ready:
112        status = ''
113        end_time = time.time() + timeout
114        # Wait for cryptohome to send a successful reply.
115        while 'GetTpmStatus success' not in status and time.time() < end_time:
116            status = client.run('cryptohome --action=tpm_more_status',
117                    ignore_status=True).stdout.strip()
118            logging.debug(status)
119            time.sleep(1)
120
121
122def CleanupAndReboot(client):
123    """Cleanup and reboot the device.
124
125    @param client: client object to run commands on.
126    """
127    client.run('sudo rm -rf ' + _RM_DIRS, ignore_status=True)
128    client.run('sync', ignore_status=True)
129    client.reboot()
130