From ecdd815e51662eb1171b8b4a2b092dbc547a711b Mon Sep 17 00:00:00 2001 From: Nathan Rossi Date: Thu, 2 Oct 2025 19:51:14 +1000 Subject: [PATCH] Add definition for NV_Extend Add a implementation for NV_Extend, which is used with a NV Index defined with the NvIndexType::Extend to provide a PCR equivalent that uses NV memory. The hashing algorithm used is defined by the index name algorithm. An example is included within the documentation for the function, setting up the NV index with the required type and typical attributes for performing a extend of the index. An integration test is also added for this function. Testing the same owner auth scenario of the example, and a platform auth variant, validating the result of the NV index after the extend operation in both tests. Signed-off-by: Nathan Rossi --- .../tpm_commands/non_volatile_storage.rs | 113 +++++++++++++++- .../non_volatile_storage_tests.rs | 122 ++++++++++++++++++ 2 files changed, 233 insertions(+), 2 deletions(-) diff --git a/tss-esapi/src/context/tpm_commands/non_volatile_storage.rs b/tss-esapi/src/context/tpm_commands/non_volatile_storage.rs index b1b7fb682..b8f2c4a3a 100644 --- a/tss-esapi/src/context/tpm_commands/non_volatile_storage.rs +++ b/tss-esapi/src/context/tpm_commands/non_volatile_storage.rs @@ -6,7 +6,7 @@ use crate::{ interface_types::reserved_handles::{NvAuth, Provision}, structures::{Auth, MaxNvBuffer, Name, NvPublic}, tss2_esys::{ - Esys_NV_DefineSpace, Esys_NV_Increment, Esys_NV_Read, Esys_NV_ReadPublic, + Esys_NV_DefineSpace, Esys_NV_Extend, Esys_NV_Increment, Esys_NV_Read, Esys_NV_ReadPublic, Esys_NV_UndefineSpace, Esys_NV_UndefineSpaceSpecial, Esys_NV_Write, }, Context, Result, ReturnCode, @@ -698,7 +698,116 @@ impl Context { ) } - // Missing function: NV_Extend + /// Extends data to the NV memory associated with a nv index. + /// + /// # Details + /// This method is used to extend a value to the nv memory in the TPM. + /// + /// Please beware that this method requires an authorization session handle to be present. + /// + /// Any NV index (that is not already used) can be defined as an extend type. However various specifications define + /// indexes that have specific purposes or are reserved, for example the TCG PC Client Platform Firmware Profile + /// Specification Section 3.3.6 defines indexes within the 0x01c40200-0x01c402ff range for instance measurements. + /// Section 2.2 of TCG Registry of Reserved TPM 2.0 Handles and Localities provides additional context for specific + /// NV index ranges. + /// + /// # Arguments + /// * `auth_handle` - Handle indicating the source of authorization value. + /// * `nv_index_handle` - The [NvIndexHandle] associated with NV memory + /// which will be extended by data hashed with the previous data. + /// * `data` - The data, in the form of a [MaxNvBuffer], that is to be written. + /// + /// # Example + /// ```rust + /// # use tss_esapi::{ + /// # Context, TctiNameConf, attributes::{SessionAttributes, NvIndexAttributes}, + /// # handles::NvIndexTpmHandle, interface_types::algorithm::HashingAlgorithm, + /// # structures::{SymmetricDefinition, NvPublic}, + /// # constants::SessionType, constants::nv_index_type::NvIndexType, + /// # }; + /// use tss_esapi::{ + /// interface_types::reserved_handles::{Provision, NvAuth}, structures::MaxNvBuffer, + /// }; + /// + /// # // Create context + /// # let mut context = + /// # Context::new( + /// # TctiNameConf::from_environment_variable().expect("Failed to get TCTI"), + /// # ).expect("Failed to create Context"); + /// # + /// # let session = context + /// # .start_auth_session( + /// # None, + /// # None, + /// # None, + /// # SessionType::Hmac, + /// # SymmetricDefinition::AES_256_CFB, + /// # tss_esapi::interface_types::algorithm::HashingAlgorithm::Sha256, + /// # ) + /// # .expect("Failed to create session") + /// # .expect("Received invalid handle"); + /// # let (session_attributes, session_attributes_mask) = SessionAttributes::builder() + /// # .with_decrypt(true) + /// # .with_encrypt(true) + /// # .build(); + /// # context.tr_sess_set_attributes(session, session_attributes, session_attributes_mask) + /// # .expect("Failed to set attributes on session"); + /// # context.set_sessions((Some(session), None, None)); + /// # + /// # let nv_index = NvIndexTpmHandle::new(0x01500028) + /// # .expect("Failed to create NV index tpm handle"); + /// # + /// // Create NV index attributes + /// let owner_nv_index_attributes = NvIndexAttributes::builder() + /// .with_owner_write(true) + /// .with_owner_read(true) + /// .with_orderly(true) + /// .with_nv_index_type(NvIndexType::Extend) + /// .build() + /// .expect("Failed to create owner nv index attributes"); + /// + /// // Create owner nv public. + /// let owner_nv_public = NvPublic::builder() + /// .with_nv_index(nv_index) + /// .with_index_name_algorithm(HashingAlgorithm::Sha256) + /// .with_index_attributes(owner_nv_index_attributes) + /// .with_data_area_size(32) + /// .build() + /// .expect("Failed to build NvPublic for owner"); + /// + /// let nv_index_handle = context + /// .nv_define_space(Provision::Owner, None, owner_nv_public.clone()) + /// .expect("Call to nv_define_space failed"); + /// + /// let data = MaxNvBuffer::try_from(vec![0x0]).unwrap(); + /// let result = context.nv_extend(NvAuth::Owner, nv_index_handle, data); + /// + /// # context + /// # .nv_undefine_space(Provision::Owner, nv_index_handle) + /// # .expect("Call to nv_undefine_space failed"); + /// ``` + pub fn nv_extend( + &mut self, + auth_handle: NvAuth, + nv_index_handle: NvIndexHandle, + data: MaxNvBuffer, + ) -> Result<()> { + ReturnCode::ensure_success( + unsafe { + Esys_NV_Extend( + self.mut_context(), + AuthHandle::from(auth_handle).into(), + nv_index_handle.into(), + self.required_session_1()?, + self.optional_session_2(), + self.optional_session_3(), + &data.into(), + ) + }, + |ret| error!("Error when extending NV: {:#010X}", ret), + ) + } + // Missing function: NV_SetBits // Missing function: NV_WriteLock // Missing function: NV_GlobalWriteLock diff --git a/tss-esapi/tests/integration_tests/context_tests/tpm_commands/non_volatile_storage_tests.rs b/tss-esapi/tests/integration_tests/context_tests/tpm_commands/non_volatile_storage_tests.rs index 7e6f232c7..9e12bc759 100644 --- a/tss-esapi/tests/integration_tests/context_tests/tpm_commands/non_volatile_storage_tests.rs +++ b/tss-esapi/tests/integration_tests/context_tests/tpm_commands/non_volatile_storage_tests.rs @@ -421,3 +421,125 @@ mod test_nv_increment { assert_eq!(first_value + 1, second_value); } } + +mod test_nv_extend { + use crate::common::create_ctx_with_session; + use tss_esapi::{ + attributes::NvIndexAttributesBuilder, + constants::nv_index_type::NvIndexType, + handles::NvIndexTpmHandle, + interface_types::{ + algorithm::HashingAlgorithm, + reserved_handles::{NvAuth, Provision}, + }, + structures::{MaxNvBuffer, NvPublicBuilder}, + }; + + #[test] + fn test_nv_extend() { + let mut context = create_ctx_with_session(); + let nv_index = NvIndexTpmHandle::new(0x01500029).unwrap(); + + // Create owner nv public. + let owner_nv_index_attributes = NvIndexAttributesBuilder::new() + .with_owner_write(true) + .with_owner_read(true) + .with_orderly(true) + .with_nv_index_type(NvIndexType::Extend) + .build() + .expect("Failed to create owner nv index attributes"); + + let owner_nv_public = NvPublicBuilder::new() + .with_nv_index(nv_index) + .with_index_name_algorithm(HashingAlgorithm::Sha256) + .with_index_attributes(owner_nv_index_attributes) + .with_data_area_size(32) + .build() + .expect("Failed to build NvPublic for owner"); + + let owner_nv_index_handle = context + .nv_define_space(Provision::Owner, None, owner_nv_public) + .expect("Call to nv_define_space failed"); + + // Attempt to read an un-"written"/uninitialized NV index that is defined as extend type + let nv_read_result = context.nv_read(NvAuth::Owner, owner_nv_index_handle, 32, 0); + assert!(nv_read_result.is_err()); + + // Extend NV index with data + let data = MaxNvBuffer::try_from(vec![0x0]).unwrap(); + context + .nv_extend(NvAuth::Owner, owner_nv_index_handle, data) + .expect("Failed to extend NV index"); + + // Validate the new state of the index, which was extended by the data + let nv_read_result = context.nv_read(NvAuth::Owner, owner_nv_index_handle, 32, 0); + let read_data = nv_read_result.expect("Call to nv_read failed"); + + // Expected value is sha256([0; 32] + [0; 1]) + assert_eq!( + [ + 0x7f, 0x9c, 0x9e, 0x31, 0xac, 0x82, 0x56, 0xca, 0x2f, 0x25, 0x85, 0x83, 0xdf, 0x26, + 0x2d, 0xbc, 0x7d, 0x6f, 0x68, 0xf2, 0xa0, 0x30, 0x43, 0xd5, 0xc9, 0x9a, 0x4a, 0xe5, + 0xa7, 0x39, 0x6c, 0xe9 + ], + read_data.as_ref() + ); + + // Clean up defined NV index + context + .nv_undefine_space(Provision::Owner, owner_nv_index_handle) + .expect("Call to nv_undefine_space failed"); + + // Create platform nv public that is cleared on TPM reset/shutdown + let platform_nv_index_attributes = NvIndexAttributesBuilder::new() + .with_pp_write(true) + .with_pp_read(true) + .with_orderly(true) + .with_platform_create(true) + .with_nv_index_type(NvIndexType::Extend) + .with_clear_stclear(true) + .build() + .expect("Failed to create owner nv index attributes"); + + let platform_nv_public = NvPublicBuilder::new() + .with_nv_index(nv_index) + .with_index_name_algorithm(HashingAlgorithm::Sha256) + .with_index_attributes(platform_nv_index_attributes) + .with_data_area_size(32) + .build() + .expect("Failed to build NvPublic for owner"); + + let platform_nv_index_handle = context + .nv_define_space(Provision::Platform, None, platform_nv_public) + .expect("Call to nv_define_space failed"); + + // Attempt to read an un-"written"/uninitialized NV index that is defined as extend type + let nv_read_result = context.nv_read(NvAuth::Platform, platform_nv_index_handle, 32, 0); + assert!(nv_read_result.is_err()); + + // Extend NV index with data + let data = MaxNvBuffer::try_from(vec![0x0]).unwrap(); + context + .nv_extend(NvAuth::Platform, platform_nv_index_handle, data) + .expect("Failed to extend NV index"); + + // Validate the new state of the index, which was extended by the data + let nv_read_result = context.nv_read(NvAuth::Platform, platform_nv_index_handle, 32, 0); + let read_data = nv_read_result.expect("Call to nv_read failed"); + + // Expected value is sha256([0; 32] + [0; 1]) + assert_eq!( + [ + 0x7f, 0x9c, 0x9e, 0x31, 0xac, 0x82, 0x56, 0xca, 0x2f, 0x25, 0x85, 0x83, 0xdf, 0x26, + 0x2d, 0xbc, 0x7d, 0x6f, 0x68, 0xf2, 0xa0, 0x30, 0x43, 0xd5, 0xc9, 0x9a, 0x4a, 0xe5, + 0xa7, 0x39, 0x6c, 0xe9 + ], + read_data.as_ref() + ); + + // Clean up defined NV index + context + .nv_undefine_space(Provision::Platform, platform_nv_index_handle) + .expect("Call to nv_undefine_space failed"); + } +}