diff --git a/src/integrations/bootloader_interface.rs b/src/integrations/bootloader_interface.rs index 3e5b375..f0095b2 100644 --- a/src/integrations/bootloader_interface.rs +++ b/src/integrations/bootloader_interface.rs @@ -148,4 +148,16 @@ impl BootloaderInterface { VariableClass::BootAndRuntimeTemporary, ) } + + /// Tell the system what the number of active PCR banks is. + /// If this is zero, that is okay. + pub fn set_tpm2_active_pcr_banks(value: u32) -> Result<()> { + // Format the value into the specification format. + let value = format!("0x{:08x}", value); + Self::VENDOR.set_cstr16( + "LoaderTpm2ActivePcrBanks", + &value, + VariableClass::BootAndRuntimeTemporary, + ) + } } diff --git a/src/main.rs b/src/main.rs index fd36399..a2b288b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -13,6 +13,7 @@ use crate::options::SproutOptions; use crate::options::parser::OptionsRepresentable; use crate::phases::phase; use crate::platform::timer::PlatformTimer; +use crate::platform::tpm::PlatformTpm; use crate::secure::SecureBoot; use crate::utils::PartitionGuidForm; use anyhow::{Context, Result, bail}; @@ -92,6 +93,13 @@ fn run() -> Result<()> { BootloaderInterface::set_loader_info() .context("unable to set loader info in bootloader interface")?; + // Acquire the number of active PCR banks on the TPM. + // If no TPM is available, this will return zero. + let active_pcr_banks = PlatformTpm::active_pcr_banks()?; + // Tell the bootloader interface what the number of active PCR banks is. + BootloaderInterface::set_tpm2_active_pcr_banks(active_pcr_banks) + .context("unable to set tpm2 active PCR banks in bootloader interface")?; + // Parse the options to the sprout executable. let options = SproutOptions::parse().context("unable to parse options")?; diff --git a/src/platform.rs b/src/platform.rs index 66d17b4..7020413 100644 --- a/src/platform.rs +++ b/src/platform.rs @@ -1,2 +1,4 @@ /// timer: Platform timer support. pub mod timer; +/// tpm: Platform TPM support. +pub mod tpm; diff --git a/src/platform/tpm.rs b/src/platform/tpm.rs new file mode 100644 index 0000000..9dc04b8 --- /dev/null +++ b/src/platform/tpm.rs @@ -0,0 +1,96 @@ +use crate::utils; +use anyhow::{Context, Result}; +use uefi::boot::ScopedProtocol; +use uefi::proto::tcg::v2::Tcg; +use uefi_raw::protocol::tcg::v2::{Tcg2Protocol, Tcg2Version}; + +/// Represents the platform TPM. +pub struct PlatformTpm; + +/// Represents an open TPM handle. +pub struct TpmProtocolHandle { + /// The version of the TPM protocol. + version: Tcg2Version, + /// The protocol itself. + protocol: ScopedProtocol, +} + +impl TpmProtocolHandle { + /// Construct a new [TpmProtocolHandle] from the `version` and `protocol`. + pub fn new(version: Tcg2Version, protocol: ScopedProtocol) -> Self { + Self { version, protocol } + } + + /// Access the version provided by the tcg2 protocol. + pub fn version(&self) -> Tcg2Version { + self.version + } + + /// Access the protocol interface for tcg2. + pub fn protocol(&mut self) -> &mut ScopedProtocol { + &mut self.protocol + } +} + +impl PlatformTpm { + /// Acquire access to the TPM protocol handle, if possible. + /// Returns None if TPM is not available. + fn protocol() -> Result> { + // Attempt to acquire the TCG2 protocol handle. If it's not available, return None. + let Some(handle) = + utils::find_handle(&Tcg2Protocol::GUID).context("unable to determine tpm presence")? + else { + return Ok(None); + }; + + // If we reach here, we've already validated that the handle + // implements the TCG2 protocol. + let mut protocol = uefi::boot::open_protocol_exclusive::(handle) + .context("unable to open tcg2 protocol")?; + + // Acquire the capabilities of the TPM. + let capability = protocol + .get_capability() + .context("unable to get tcg2 boot service capability")?; + + // If the TPM is not present, return None. + if !capability.tpm_present() { + return Ok(None); + } + + // If the TPM is present, we need to determine the version of the TPM. + let version = capability.protocol_version; + + // We have a TPM, so return the protocol version and the protocol handle. + Ok(Some(TpmProtocolHandle::new(version, protocol))) + } + + /// Determines whether the platform TPM is present. + pub fn present() -> Result { + Ok(PlatformTpm::protocol()?.is_some()) + } + + /// Determine the number of active PCR banks on the TPM. + /// If no TPM is available, this will return zero. + pub fn active_pcr_banks() -> Result { + // Acquire access to the TPM protocol handle. + let Some(mut handle) = PlatformTpm::protocol()? else { + return Ok(0); + }; + + // Check if the TPM supports `GetActivePcrBanks`, and if it doesn't return zero. + if handle.version().major < 1 || handle.version().major == 1 && handle.version().minor < 1 { + return Ok(0); + } + + // The safe wrapper for this function will decode the bitmap. + // Strictly speaking, it's not future-proof to re-encode that, but in practice it will work. + let banks = handle + .protocol() + .get_active_pcr_banks() + .context("unable to get active pcr banks")?; + + // Return the number of active PCR banks. + Ok(banks.bits()) + } +}