blob: 40ddf67312253fba109305b8de30256bff683296 [file] [log] [blame]
//! [TCG] (Trusted Computing Group) protocol for [TPM] (Trusted Platform
//! Module) 1.1 and 1.2.
//!
//! This protocol is defined in the [TCG EFI Protocol Specification _for
//! TPM Family 1.1 or 1.2_][spec].
//!
//! [spec]: https://trustedcomputinggroup.org/resource/tcg-efi-protocol-specification/
//! [TCG]: https://trustedcomputinggroup.org/
//! [TPM]: https://en.wikipedia.org/wiki/Trusted_Platform_Module
use super::{AlgorithmId, EventType, HashAlgorithm, PcrIndex};
use crate::data_types::{Align, PhysicalAddress};
use crate::proto::unsafe_protocol;
use crate::util::{ptr_write_unaligned_and_add, usize_from_u32};
use crate::{Error, Result, Status, StatusExt};
use core::fmt::{self, Debug, Formatter};
use core::marker::PhantomData;
use core::{mem, ptr};
use ptr_meta::Pointee;
#[cfg(feature = "alloc")]
use {crate::mem::make_boxed, alloc::boxed::Box};
#[cfg(all(feature = "unstable", feature = "alloc"))]
use alloc::alloc::Global;
/// 20-byte SHA-1 digest.
pub type Sha1Digest = [u8; 20];
/// This corresponds to the `AlgorithmId` enum, but in the v1 spec it's `u32`
/// instead of `u16`.
#[allow(non_camel_case_types)]
type TCG_ALGORITHM_ID = u32;
/// Information about the protocol and the TPM device.
///
/// Layout compatible with the C type `TCG_EFI_BOOT_SERVICE_CAPABILITY`.
#[repr(C)]
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Ord, PartialOrd)]
pub struct BootServiceCapability {
size: u8,
structure_version: Version,
protocol_spec_version: Version,
hash_algorithm_bitmap: u8,
tpm_present_flag: u8,
tpm_deactivated_flag: u8,
}
impl BootServiceCapability {
/// Version of the `BootServiceCapability` structure.
#[must_use]
pub const fn structure_version(&self) -> Version {
self.structure_version
}
/// Version of the `Tcg` protocol.
#[must_use]
pub const fn protocol_spec_version(&self) -> Version {
self.protocol_spec_version
}
/// Supported hash algorithms.
#[must_use]
pub fn hash_algorithm(&self) -> HashAlgorithm {
// Safety: the value should always be 0x1 (indicating SHA-1), but
// we don't care if it's some unexpected value.
HashAlgorithm::from_bits_retain(u32::from(self.hash_algorithm_bitmap))
}
/// Whether the TPM device is present.
#[must_use]
pub const fn tpm_present(&self) -> bool {
self.tpm_present_flag != 0
}
/// Whether the TPM device is deactivated.
#[must_use]
pub const fn tpm_deactivated(&self) -> bool {
self.tpm_deactivated_flag != 0
}
}
/// Version information.
///
/// Layout compatible with the C type `TCG_VERSION`.
#[repr(C)]
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Ord, PartialOrd)]
pub struct Version {
/// Major version.
pub major: u8,
/// Minor version.
pub minor: u8,
// Leave these two fields undocumented since it's not clear what
// they are for. The spec doesn't say, and they were removed in the
// v2 spec.
#[allow(missing_docs)]
pub rev_major: u8,
#[allow(missing_docs)]
pub rev_minor: u8,
}
/// Entry in the [`EventLog`].
///
/// Layout compatible with the C type `TCG_PCR_EVENT`.
///
/// Naming note: the spec refers to "event data" in two conflicting
/// ways: the `event_data` field and the data hashed in the digest
/// field. These two are independent; although the event data _can_ be
/// what is hashed in the digest field, it doesn't have to be.
#[repr(C, packed)]
#[derive(Eq, Pointee)]
pub struct PcrEvent {
pcr_index: PcrIndex,
event_type: EventType,
digest: Sha1Digest,
event_data_size: u32,
event_data: [u8],
}
impl PcrEvent {
pub(super) unsafe fn from_ptr<'a>(ptr: *const u8) -> &'a Self {
// Get the `event_size` field.
let ptr_u32: *const u32 = ptr.cast();
let event_size = ptr_u32.add(7).read_unaligned();
let event_size = usize_from_u32(event_size);
unsafe { &*ptr_meta::from_raw_parts(ptr.cast(), event_size) }
}
/// Create a new `PcrEvent` using a byte buffer for storage.
///
/// # Errors
///
/// Returns [`Status::BUFFER_TOO_SMALL`] if the `buffer` is not large
/// enough. The required size will be returned in the error data.
///
/// Returns [`Status::INVALID_PARAMETER`] if the `event_data` size is too
/// large.
pub fn new_in_buffer<'buf>(
buffer: &'buf mut [u8],
pcr_index: PcrIndex,
event_type: EventType,
digest: Sha1Digest,
event_data: &[u8],
) -> Result<&'buf mut Self, Option<usize>> {
let event_data_size = u32::try_from(event_data.len())
.map_err(|_| Error::new(Status::INVALID_PARAMETER, None))?;
let required_size = mem::size_of::<PcrIndex>()
+ mem::size_of::<EventType>()
+ mem::size_of::<Sha1Digest>()
+ mem::size_of::<u32>()
+ event_data.len();
if buffer.len() < required_size {
return Err(Error::new(Status::BUFFER_TOO_SMALL, Some(required_size)));
}
let mut ptr: *mut u8 = buffer.as_mut_ptr().cast();
unsafe {
ptr_write_unaligned_and_add(&mut ptr, pcr_index);
ptr_write_unaligned_and_add(&mut ptr, event_type);
ptr_write_unaligned_and_add(&mut ptr, digest);
ptr_write_unaligned_and_add(&mut ptr, event_data_size);
ptr::copy(event_data.as_ptr(), ptr, event_data.len());
let ptr: *mut Self =
ptr_meta::from_raw_parts_mut(buffer.as_mut_ptr().cast(), event_data.len());
Ok(&mut *ptr)
}
}
/// Create a new `PcrEvent` in a [`Box`].
///
/// # Errors
///
/// Returns [`Status::INVALID_PARAMETER`] if the `event_data` size is too
/// large.
#[cfg(feature = "alloc")]
pub fn new_in_box(
pcr_index: PcrIndex,
event_type: EventType,
digest: Sha1Digest,
event_data: &[u8],
) -> Result<Box<Self>> {
#[cfg(not(feature = "unstable"))]
{
make_boxed(|buf| Self::new_in_buffer(buf, pcr_index, event_type, digest, event_data))
}
#[cfg(feature = "unstable")]
{
make_boxed(
|buf| Self::new_in_buffer(buf, pcr_index, event_type, digest, event_data),
Global,
)
}
}
/// PCR index for the event.
#[must_use]
pub const fn pcr_index(&self) -> PcrIndex {
self.pcr_index
}
/// Type of event, indicating what type of data is stored in [`event_data`].
///
/// [`event_data`]: Self::event_data
#[must_use]
pub const fn event_type(&self) -> EventType {
self.event_type
}
/// Raw event data. The meaning of this data can be determined from
/// the [`event_type`].
///
/// Note that this data is independent of what is hashed [`digest`].
///
/// [`digest`]: Self::digest
/// [`event_type`]: Self::event_type
#[must_use]
pub const fn event_data(&self) -> &[u8] {
&self.event_data
}
/// SHA-1 digest of the data hashed for this event.
#[must_use]
pub const fn digest(&self) -> Sha1Digest {
self.digest
}
}
impl Align for PcrEvent {
fn alignment() -> usize {
1
}
}
// Manual `Debug` implementation since it can't be derived for a packed DST.
impl Debug for PcrEvent {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
f.debug_struct("PcrEvent")
.field("pcr_index", &{ self.pcr_index })
.field("event_type", &{ self.event_type })
.field("digest", &self.digest)
.field("event_data_size", &{ self.event_data_size })
.field("event_data", &&self.event_data)
.finish()
}
}
// Manual `PartialEq` implementation since it can't be derived for a packed DST.
impl PartialEq for PcrEvent {
fn eq(&self, rhs: &Self) -> bool {
self.pcr_index() == rhs.pcr_index()
&& self.event_type() == rhs.event_type()
&& self.digest == rhs.digest
&& self.event_data_size == rhs.event_data_size
&& self.event_data == rhs.event_data
}
}
opaque_type! {
/// Opaque type that should be used to represent a pointer to a [`PcrEvent`] in
/// foreign function interfaces. This type produces a thin pointer, unlike
/// [`PcrEvent`].
pub struct FfiPcrEvent;
}
/// TPM event log.
///
/// This type of event log always uses SHA-1 hashes. The [`v1::Tcg`]
/// protocol always uses this type of event log, but it can also be
/// provided by the [`v2::Tcg`] protocol via [`get_event_log_v2`].
///
/// [`v1::Tcg`]: Tcg
/// [`v2::Tcg`]: super::v2::Tcg
/// [`get_event_log_v2`]: super::v2::Tcg::get_event_log_v2
#[derive(Debug)]
pub struct EventLog<'a> {
// Tie the lifetime to the protocol, and by extension, boot services.
_lifetime: PhantomData<&'a Tcg>,
location: *const u8,
last_entry: *const u8,
is_truncated: bool,
}
impl<'a> EventLog<'a> {
pub(super) const unsafe fn new(
location: *const u8,
last_entry: *const u8,
is_truncated: bool,
) -> Self {
Self {
_lifetime: PhantomData,
location,
last_entry,
is_truncated,
}
}
/// Iterator of events in the log.
#[must_use]
pub const fn iter(&self) -> EventLogIter {
EventLogIter {
log: self,
location: self.location,
}
}
/// If true, the event log is missing one or more entries because
/// additional events would have exceeded the space allocated for
/// the log.
///
/// This value is not reported for the [`v1::Tcg`] protocol, so it
/// is always `false` in that case.
///
/// [`v1::Tcg`]: Tcg
#[must_use]
pub const fn is_truncated(&self) -> bool {
self.is_truncated
}
}
/// Iterator for events in [`EventLog`].
#[derive(Debug)]
pub struct EventLogIter<'a> {
log: &'a EventLog<'a>,
location: *const u8,
}
impl<'a> Iterator for EventLogIter<'a> {
type Item = &'a PcrEvent;
fn next(&mut self) -> Option<Self::Item> {
// The spec says that `last_entry` will be null if there are no
// events. Presumably `location` will be null as well, but check
// both just to be safe.
if self.location.is_null() || self.log.last_entry.is_null() {
return None;
}
// Safety: we trust that the protocol has given us a valid range
// of memory to read from.
let event = unsafe { PcrEvent::from_ptr(self.location) };
// If this is the last entry, set the location to null so that
// future calls to `next()` return `None`.
if self.location == self.log.last_entry {
self.location = ptr::null();
} else {
self.location = unsafe { self.location.add(mem::size_of_val(event)) };
}
Some(event)
}
}
/// Protocol for interacting with TPM 1.1 and 1.2 devices.
///
/// The corresponding C type is `EFI_TCG_PROTOCOL`.
#[derive(Debug)]
#[repr(C)]
#[unsafe_protocol("f541796d-a62e-4954-a775-9584f61b9cdd")]
pub struct Tcg {
status_check: unsafe extern "efiapi" fn(
this: *mut Tcg,
protocol_capability: *mut BootServiceCapability,
feature_flags: *mut u32,
event_log_location: *mut PhysicalAddress,
event_log_last_entry: *mut PhysicalAddress,
) -> Status,
// Note: we do not currently expose this function because the spec
// for this is not well written. The function allocates memory, but
// the spec doesn't say how to free it. Most likely
// `EFI_BOOT_SERVICES.FreePool` would work, but this is not
// mentioned in the spec so it is unsafe to rely on.
//
// Also, this function is not that useful in practice for a couple
// reasons. First, it takes an algorithm ID, but only SHA-1 is
// supported with TPM v1. Second, TPMs are not cryptographic
// accelerators, so it is very likely faster to calculate the hash
// on the CPU, e.g. with the `sha1` crate.
hash_all: unsafe extern "efiapi" fn() -> Status,
log_event: unsafe extern "efiapi" fn(
this: *mut Tcg,
// The spec does not guarantee that the `event` will not be mutated
// through the pointer, but it seems reasonable to assume and makes the
// public interface clearer, so use a const pointer.
event: *const FfiPcrEvent,
event_number: *mut u32,
flags: u32,
) -> Status,
pass_through_to_tpm: unsafe extern "efiapi" fn(
this: *mut Tcg,
tpm_input_parameter_block_size: u32,
tpm_input_parameter_block: *const u8,
tpm_output_parameter_block_size: u32,
tpm_output_parameter_block: *mut u8,
) -> Status,
hash_log_extend_event: unsafe extern "efiapi" fn(
this: *mut Tcg,
hash_data: PhysicalAddress,
hash_data_len: u64,
algorithm_id: TCG_ALGORITHM_ID,
event: *mut FfiPcrEvent,
event_number: *mut u32,
event_log_last_entry: *mut PhysicalAddress,
) -> Status,
}
/// Return type of [`Tcg::status_check`].
#[derive(Debug)]
pub struct StatusCheck<'a> {
/// Information about the protocol and the TPM device.
pub protocol_capability: BootServiceCapability,
/// Feature flags. The spec does not define any feature flags, so
/// this is always expected to be zero.
pub feature_flags: u32,
/// TPM event log.
pub event_log: EventLog<'a>,
}
impl Tcg {
/// Get information about the protocol and TPM device, as well as
/// the TPM event log.
pub fn status_check(&mut self) -> Result<StatusCheck> {
let mut protocol_capability = BootServiceCapability::default();
let mut feature_flags = 0;
let mut event_log_location = 0;
let mut event_log_last_entry = 0;
let status = unsafe {
(self.status_check)(
self,
&mut protocol_capability,
&mut feature_flags,
&mut event_log_location,
&mut event_log_last_entry,
)
};
if status.is_success() {
// The truncated field is just there for the v2 protocol;
// always set it to false for v1.
let truncated = false;
let event_log = unsafe {
EventLog::new(
event_log_location as *const u8,
event_log_last_entry as *const u8,
truncated,
)
};
Ok(StatusCheck {
protocol_capability,
feature_flags,
event_log,
})
} else {
Err(status.into())
}
}
/// Add an entry to the event log without extending a PCR.
///
/// Usually [`hash_log_extend_event`] should be used instead. An
/// entry added via `log_event` cannot be verified, so it is mainly
/// intended for adding an informational entry.
///
/// [`hash_log_extend_event`]: Self::hash_log_extend_event
pub fn log_event(&mut self, event: &PcrEvent) -> Result {
// This is the only valid value; it indicates that the extend
// operation should not be performed.
let flags = 0x1;
// Don't bother returning this, it's not very useful info.
let mut event_number = 0;
let event_ptr: *const PcrEvent = event;
unsafe { (self.log_event)(self, event_ptr.cast(), &mut event_number, flags).to_result() }
}
/// Extend a PCR and add an entry to the event log.
///
/// If `data_to_hash` is `None` then the `digest` field of the `event`
/// should be used as-is. Otherwise, the `digest` field will be overwritten
/// with the SHA-1 hash of the data.
pub fn hash_log_extend_event(
&mut self,
event: &mut PcrEvent,
data_to_hash: Option<&[u8]>,
) -> Result {
let hash_data;
let hash_data_len;
if let Some(data_to_hash) = data_to_hash {
hash_data = data_to_hash.as_ptr() as PhysicalAddress;
hash_data_len = u64::try_from(data_to_hash.len()).unwrap();
} else {
hash_data = 0;
hash_data_len = 0;
}
// Don't bother returning these, it's not very useful info.
let mut event_number = 0;
let mut event_log_last_entry = 0;
let event_ptr: *mut PcrEvent = event;
unsafe {
(self.hash_log_extend_event)(
self,
hash_data,
hash_data_len,
AlgorithmId::SHA1.0.into(),
event_ptr.cast(),
&mut event_number,
&mut event_log_last_entry,
)
.to_result()
}
}
/// Send a command directly to the TPM.
///
/// Constructing the input block and parsing the output block are outside
/// the scope of this crate. See the [TPM 1.2 Main Specification][spec]
/// documents for details of these blocks, in particular Part 3, Commands.
///
/// Note that TPM structures are big endian.
///
/// [spec]: https://trustedcomputinggroup.org/resource/tpm-main-specification/
pub fn pass_through_to_tpm(
&mut self,
input_parameter_block: &[u8],
output_parameter_block: &mut [u8],
) -> Result {
let input_parameter_block_len = u32::try_from(input_parameter_block.len())
.map_err(|_| Error::from(Status::BAD_BUFFER_SIZE))?;
let output_parameter_block_len = u32::try_from(output_parameter_block.len())
.map_err(|_| Error::from(Status::BAD_BUFFER_SIZE))?;
unsafe {
(self.pass_through_to_tpm)(
self,
input_parameter_block_len,
input_parameter_block.as_ptr(),
output_parameter_block_len,
output_parameter_block.as_mut_ptr(),
)
.to_result()
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use core::slice;
#[test]
fn test_new_pcr_event() {
let mut event_buf = [0; 256];
#[rustfmt::skip]
let digest = [
0x00, 0x01, 0x02, 0x03,
0x04, 0x05, 0x06, 0x07,
0x08, 0x09, 0x0a, 0x0b,
0x0c, 0x0d, 0x0e, 0x0f,
0x10, 0x11, 0x12, 0x13,
];
let data = [0x14, 0x15, 0x16, 0x17];
let event =
PcrEvent::new_in_buffer(&mut event_buf, PcrIndex(4), EventType::IPL, digest, &data)
.unwrap();
assert_eq!(event.pcr_index(), PcrIndex(4));
assert_eq!(event.event_type(), EventType::IPL);
assert_eq!(event.digest(), digest);
assert_eq!(event.event_data(), data);
let event_ptr: *const PcrEvent = event;
let bytes =
unsafe { slice::from_raw_parts(event_ptr.cast::<u8>(), mem::size_of_val(event)) };
#[rustfmt::skip]
assert_eq!(bytes, [
// PCR index
0x04, 0x00, 0x00, 0x00,
// Event type
0x0d, 0x00, 0x00, 0x00,
// Digest
0x00, 0x01, 0x02, 0x03,
0x04, 0x05, 0x06, 0x07,
0x08, 0x09, 0x0a, 0x0b,
0x0c, 0x0d, 0x0e, 0x0f,
0x10, 0x11, 0x12, 0x13,
// Event data len
0x04, 0x00, 0x00, 0x00,
// Event data
0x14, 0x15, 0x16, 0x17,
]);
// Check that `new_in_box` gives the same value.
assert_eq!(
event,
&*PcrEvent::new_in_box(PcrIndex(4), EventType::IPL, digest, &data).unwrap()
);
}
#[test]
fn test_event_log_v1() {
// This data comes from dumping the TPM event log in a VM
// (truncated to just two entries).
#[rustfmt::skip]
let bytes = [
// Event 1
// PCR index
0x00, 0x00, 0x00, 0x00,
// Event type
0x08, 0x00, 0x00, 0x00,
// SHA1 digest
0x14, 0x89, 0xf9, 0x23, 0xc4, 0xdc, 0xa7, 0x29, 0x17, 0x8b,
0x3e, 0x32, 0x33, 0x45, 0x85, 0x50, 0xd8, 0xdd, 0xdf, 0x29,
// Event data size
0x02, 0x00, 0x00, 0x00,
// Event data
0x00, 0x00,
// Event 2
// PCR index
0x00, 0x00, 0x00, 0x00,
// Event type
0x08, 0x00, 0x00, 0x80,
// SHA1 digest
0xc7, 0x06, 0xe7, 0xdd, 0x36, 0x39, 0x29, 0x84, 0xeb, 0x06,
0xaa, 0xa0, 0x8f, 0xf3, 0x36, 0x84, 0x40, 0x77, 0xb3, 0xed,
// Event data size
0x10, 0x00, 0x00, 0x00,
// Event data
0x00, 0x00, 0x82, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00,
];
let log = unsafe { EventLog::new(bytes.as_ptr(), bytes.as_ptr().add(34), false) };
let mut iter = log.iter();
// Entry 1
let entry = iter.next().unwrap();
assert_eq!(entry.pcr_index(), PcrIndex(0));
assert_eq!(entry.event_type(), EventType::CRTM_VERSION);
#[rustfmt::skip]
assert_eq!(
entry.digest(),
[
0x14, 0x89, 0xf9, 0x23, 0xc4, 0xdc, 0xa7, 0x29, 0x17, 0x8b,
0x3e, 0x32, 0x33, 0x45, 0x85, 0x50, 0xd8, 0xdd, 0xdf, 0x29,
]
);
assert_eq!(entry.event_data(), [0x00, 0x00]);
// Entry 2
let entry = iter.next().unwrap();
assert_eq!(entry.pcr_index(), PcrIndex(0));
assert_eq!(entry.event_type(), EventType::EFI_PLATFORM_FIRMWARE_BLOB);
#[rustfmt::skip]
assert_eq!(
entry.digest(),
[
0xc7, 0x06, 0xe7, 0xdd, 0x36, 0x39, 0x29, 0x84, 0xeb, 0x06,
0xaa, 0xa0, 0x8f, 0xf3, 0x36, 0x84, 0x40, 0x77, 0xb3, 0xed,
]
);
#[rustfmt::skip]
assert_eq!(
entry.event_data(),
[
0x00, 0x00, 0x82, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00,
]
);
}
}