blob: 7229ab5a517345c80be007587df861eaf0e1ece7 [file] [log] [blame]
// Copyright 2021 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//! Structures used for communicating with the TPM.
//! The convention is that at rest, all values are stored little-endian.
//! Values are converted to/from big-endian when they're transmitted/received.
pub mod ccd;
pub mod pinweaver;
pub mod wp;
use super::status::{ExecuteError, TpmStatus};
use crate::util::{DeserializeError, Deserializer, Serializer};
use anyhow::Context;
use async_trait::async_trait;
use fidl_fuchsia_tpm::TpmDeviceProxy;
use num_derive::FromPrimitive;
use num_traits::FromPrimitive as _;
/// Trait for a TPM request which has a companion response type.
pub trait TpmRequest {
type ResponseType: Deserializable;
}
#[async_trait]
/// A TPM command that can be executed.
pub trait TpmCommand: Sync + Send {
type ResponseType;
async fn execute(self, tpm: &TpmDeviceProxy) -> Result<Self::ResponseType, ExecuteError>;
}
#[async_trait]
impl<T: TpmRequest + Serializable + Sync + Send> TpmCommand for T {
type ResponseType = <T as TpmRequest>::ResponseType;
async fn execute(self, tpm: &TpmDeviceProxy) -> Result<Self::ResponseType, ExecuteError> {
let mut serializer = Serializer::new();
self.serialize(&mut serializer);
let vec = serializer.into_vec();
let (rc, data): (u16, Vec<u8>) = tpm
.execute_vendor_command(0, &vec)
.await
.context("Sending execute request")?
.map_err(fuchsia_zircon::Status::from_raw)
.context("Executing TPM command")?;
let status = TpmStatus::from(rc);
if !status.is_ok() {
return Err(ExecuteError::Tpm(status));
}
let mut deserializer = Deserializer::new(data);
Ok(Self::ResponseType::deserialize(&mut deserializer).context("Deserialising response")?)
}
}
/// Trait used for serialising TPM commands to byte arrays.
pub trait Serializable {
fn serialize(&self, serializer: &mut Serializer);
}
impl<T: Serializable> Serializable for &[T] {
fn serialize(&self, serializer: &mut Serializer) {
for value in self.iter() {
value.serialize(serializer);
}
}
}
impl Serializable for u8 {
fn serialize(&self, serializer: &mut Serializer) {
serializer.put_u8(*self);
}
}
/// Trait used for deserialising TPM commands from byte arrays.
pub trait Deserializable: Sized {
fn deserialize(deserializer: &mut Deserializer) -> Result<Self, DeserializeError>;
}
impl Deserializable for () {
fn deserialize(_deserializer: &mut Deserializer) -> Result<Self, DeserializeError> {
Ok(())
}
}
#[repr(u16)]
#[derive(FromPrimitive, Debug, Copy, Clone, PartialEq, Eq)]
/// Vendor commands used by the TPM. These are stored in a u16 after the standard TPM header.
enum Subcommand {
/* Original extension commands */
ExtensionAes = 0,
ExtensionHash = 1,
ExtensionRsa = 2,
ExtensionEcc = 3,
ExtensionFwUpgrade = 4,
ExtensionHkdf = 5,
ExtensionEcies = 6,
ExtensionPostReset = 7,
LastExtensionCommand = 15,
/* Our TPMv2 vendor-specific command codes. 16 bits available. */
VendorCcGetLock = 16,
SetLock = 17,
Sysinfo = 18,
/*
* VENDOR_CC_IMMEDIATE_RESET may have an argument, which is a (uint16_t)
* time delay (in milliseconds) in doing a reset. Max value is 1000.
* The command may also be called without an argument, which will be
* regarded as zero time delay.
*/
ImmediateReset = 19,
InvalidateInactiveRw = 20,
CommitNvmem = 21,
/* DEPRECATED(22): deep sleep control command. */
ReportTpmState = 23,
TurnUpdateOn = 24,
GetBoardId = 25,
SetBoardId = 26,
U2fApdu = 27,
PopLogEntry = 28,
GetRecBtn = 29,
RmaChallengeResponse = 30,
/* DEPRECATED(31): CCD password command (now part of VENDOR_CC_CCD) */
/*
* Disable factory mode. Reset all ccd capabilities to default and reset
* write protect to follow battery presence.
*/
DisableFactory = 32,
/* DEPRECATED(33): Manage CCD password phase */
Ccd = 34,
GetAlertsData = 35,
SpiHash = 36,
Pinweaver = 37,
/*
* Check the factory reset settings. If they're all set correctly, do a
* factory reset to enable ccd factory mode. All capabilities will be
* set to Always and write protect will be permanently disabled. This
* mode can't be reset unless VENDOR_CC_DISABLE_FACTORY is called or
* the 'ccd reset' console command is run.
*/
ResetFactory = 38,
/*
* Get the write protect setting. This will return a single byte with
* bits communicating the write protect setting as described by the
* WPV subcommands.
*/
Wp = 39,
/*
* Either enable or disable TPM mode. This is allowed for one-time only
* until next TPM reset EVENT. In other words, once TPM mode is set,
* then it cannot be altered to the other mode value. The allowed input
* values are either TPM_MODE_ENABLED or TPM_MODE_DISABLED as defined
* in 'enum tpm_modes', tpm_registers.h.
* If the input size is zero, it won't change TPM_MODE.
* If either the input size is zero or the input value is valid,
* it will respond with the current tpm_mode value in uint8_t format.
*
* Return code:
* VENDOR_RC_SUCCESS: completed successfully.
* VENDOR_RC_INTERNAL_ERROR: failed for an internal reason.
* VENDOR_RC_NOT_ALLOWED: failed in changing TPM_MODE,
* since it is already set.
* VENDOR_RC_NO_SUCH_SUBCOMMAND: failed because the given input
* is undefined.
*/
TpmMode = 40,
/*
* Initializes INFO1 SN data space, and sets SN hash. Takes three
* int32 as parameters, which are written as the SN hash.
*/
SnSetHash = 41,
/*
* Increments the RMA count in the INFO1 SN data space. The space must
* have been previously initialized with the _SET_HASH command above for
* this to succeed. Takes one byte as parameter, which indicates the
* number to increment the RMA count by; this is typically 1 or 0.
*
* Incrementing the RMA count by 0 will set the RMA indicator, but not
* incremement the count. This is useful to mark that a device has been
* RMA'd, but that we were not able to log the new serial number.
*
* Incrementing the count by the maximum RMA count (currently 7) will
* always set the RMA count to the maximum value, regardless of the
* previous value. This can be used with any device, regardless of
* current state, to mark it as RMA'd but with an unknown RMA count.
*/
SnIncRma = 42,
/*
* Gets the latched state of a power button press to indicate user
* recent user presence. The power button state is automatically cleared
* after PRESENCE_TIMEOUT.
*/
GetPwrBtn = 43,
/*
* U2F commands.
*/
U2fGenerate = 44,
U2fSign = 45,
U2fAttest = 46,
FlogTimestamp = 47,
EndorsementSeed = 48,
U2fMode = 49,
/*
* HMAC-SHA256 DRBG invocation for ACVP tests
*/
DrbgTest = 50,
TrngTest = 51,
/* EC EFS(Early Firmware Selection) commands */
GetBootMode = 52,
ResetEc = 53,
SeedApRoCheck = 54,
FipsCmd = 55,
GetApRoHash = 56,
GetApRoStatus = 57,
LastVendorCommand = 65535,
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
/// Command header used by all vendor commands.
struct Header {
subcommand: Subcommand,
}
impl Header {
fn new(subcommand: Subcommand) -> Self {
Self { subcommand }
}
}
impl Serializable for Header {
fn serialize(&self, serializer: &mut Serializer) {
serializer.put_be_u16(self.subcommand as u16)
}
}
impl Deserializable for Header {
fn deserialize(deserializer: &mut Deserializer) -> Result<Self, DeserializeError> {
Ok(Header {
subcommand: Subcommand::from_u16(deserializer.take_be_u16()?)
.ok_or(DeserializeError::InvalidValue)?,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::cr50::command::ccd::{CcdCommand, CcdRequest};
use fidl_fuchsia_tpm::{TpmDeviceMarker, TpmDeviceRequest};
use fuchsia_async::Task;
use futures::TryStreamExt;
use std::sync::Arc;
struct FakeTpm {
response: Vec<u8>,
received: Vec<u8>,
}
impl FakeTpm {
pub fn new(response: Vec<u8>, received: Vec<u8>) -> Arc<Self> {
Arc::new(FakeTpm { response, received })
}
pub fn serve(self: Arc<Self>) -> TpmDeviceProxy {
let (proxy, mut stream) =
fidl::endpoints::create_proxy_and_stream::<TpmDeviceMarker>().unwrap();
Task::spawn(async move {
while let Some(req) = stream.try_next().await.unwrap() {
match req {
TpmDeviceRequest::ExecuteVendorCommand {
command_code,
data,
responder,
} => {
assert_eq!(command_code, 0);
assert_eq!(data, self.received);
responder.send(&mut Ok((0, self.response.clone()))).expect("Reply ok");
}
_ => unreachable!(),
}
}
})
.detach();
proxy
}
}
#[derive(Debug, PartialEq)]
struct DontCareResponse;
impl Deserializable for DontCareResponse {
fn deserialize(deserializer: &mut Deserializer) -> Result<Self, DeserializeError> {
assert_eq!(deserializer.take_be_u16()?, 0x22);
Ok(DontCareResponse)
}
}
#[fuchsia::test]
async fn test_execute() {
let request = CcdRequest::<DontCareResponse>::new(CcdCommand::Open);
let expected_req = vec![0x00, 0x22, 0x01]; /* Subcommand CCD, CcdCommand Open */
let reply = vec![0x00, 0x22];
let tpm = FakeTpm::new(reply, expected_req);
let proxy = tpm.serve();
assert_eq!(request.execute(&proxy).await.unwrap(), DontCareResponse);
}
}