1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
// Copyright 2022 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

//! vTPM Proxy backend using the vtpmd on ChromeOS to virtualize TPM commands.

use std::time::Duration;

use base::error;
use protobuf::Message;
use remain::sorted;
use system_api::client::OrgChromiumVtpm;
use system_api::vtpm_interface::SendCommandRequest;
use system_api::vtpm_interface::SendCommandResponse;
use thiserror::Error;

use super::virtio::TpmBackend;

// 5 minutes is the default timeout for tpm commands.
const VTPM_DBUS_TIMEOUT: Duration = Duration::from_secs(300);

// The response of TPM_RC_INSUFFICIENT
const TPM_RC_INSUFFICIENT_RESPONSE: &[u8] = &[
    0x80, 0x01, // TPM_ST_NO_SESSIONS
    0x00, 0x00, 0x00, 0x0A, // Header Size = 10
    0x00, 0x00, 0x00, 0x9A, // TPM_RC_INSUFFICIENT
];

// The response of TPM_RC_FAILURE
const TPM_RC_FAILURE_RESPONSE: &[u8] = &[
    0x80, 0x01, // TPM_ST_NO_SESSIONS
    0x00, 0x00, 0x00, 0x0A, // Header Size = 10
    0x00, 0x00, 0x01, 0x01, // TPM_RC_FAILURE
];

/// A proxy object that connects to the vtpmd on ChromeOS.
pub struct VtpmProxy {
    dbus_connection: Option<dbus::blocking::Connection>,
    buf: Vec<u8>,
}

impl VtpmProxy {
    /// Returns a proxy that can be connected to the vtpmd.
    pub fn new() -> Self {
        VtpmProxy {
            dbus_connection: None,
            buf: Vec::new(),
        }
    }

    fn get_or_create_dbus_connection(
        &mut self,
    ) -> anyhow::Result<&dbus::blocking::Connection, dbus::Error> {
        return match self.dbus_connection {
            Some(ref dbus_connection) => Ok(dbus_connection),
            None => {
                let dbus_connection = dbus::blocking::Connection::new_system()?;
                self.dbus_connection = Some(dbus_connection);
                return self.get_or_create_dbus_connection();
            }
        };
    }

    fn try_execute_command(&mut self, command: &[u8]) -> anyhow::Result<(), Error> {
        let dbus_connection = self
            .get_or_create_dbus_connection()
            .map_err(Error::DBusError)?;

        let proxy = dbus_connection.with_proxy(
            "org.chromium.Vtpm",
            "/org/chromium/Vtpm",
            VTPM_DBUS_TIMEOUT,
        );

        let mut proto = SendCommandRequest::new();
        proto.set_command(command.to_vec());

        let bytes = proto.write_to_bytes().map_err(Error::ProtobufError)?;

        let resp_bytes = proxy.send_command(bytes).map_err(Error::DBusError)?;

        let response =
            SendCommandResponse::parse_from_bytes(&resp_bytes).map_err(Error::ProtobufError)?;

        self.buf = response.response().to_vec();

        Ok(())
    }
}

impl Default for VtpmProxy {
    fn default() -> Self {
        Self::new()
    }
}

impl TpmBackend for VtpmProxy {
    fn execute_command<'a>(&'a mut self, command: &[u8]) -> &'a [u8] {
        match self.try_execute_command(command) {
            Ok(()) => &self.buf,
            Err(e) => {
                error!("{:#}", e);
                match e {
                    Error::ProtobufError(_) => TPM_RC_INSUFFICIENT_RESPONSE,
                    Error::DBusError(_) => TPM_RC_FAILURE_RESPONSE,
                }
            }
        }
    }
}

#[sorted]
#[derive(Error, Debug)]
enum Error {
    #[error("D-Bus failure: {0:#}")]
    DBusError(dbus::Error),
    #[error("protocol buffers failure: {0:#}")]
    ProtobufError(protobuf::Error),
}