Browse Source

* Added retry logic to the persist_key method.
* Improved the logic for determining the first unused persistent handle.

Matthew Carr 2 years ago
parent
commit
03053b0d26
1 changed files with 71 additions and 19 deletions
  1. 71 19
      crates/btnode/src/crypto/tpm.rs

+ 71 - 19
crates/btnode/src/crypto/tpm.rs

@@ -10,7 +10,7 @@ use std::{
     path::{Path, PathBuf},
     fs::{OpenOptions},
     sync::{Arc, RwLock, RwLockWriteGuard},
-    mem::size_of
+    mem::size_of,
 };
 use openssl::{
     bn::BigNum,
@@ -30,7 +30,7 @@ use tss_esapi::{
         session_type::SessionType,
         response_code::Tss2ResponseCode,
         tss::{TPM2_RH_NULL, TPM2_PERSISTENT_FIRST},
-        CapabilityType,
+        CapabilityType, Tss2ResponseCodeKind,
     },
     tcti_ldr::{TctiNameConf, TabrmdConfig},
     interface_types::{
@@ -88,15 +88,7 @@ trait ContextExt {
     fn persistent_handles(&mut self) -> Result<Vec<PersistentTpmHandle>>;
 
     /// Returns the first free persistent handle available in the TPM.
-    fn first_free_persistent(&mut self) -> Result<Persistent> {
-        let mut handles = self.persistent_handles()?;
-        let handle = handles.pop()
-            .map_or_else(
-                || PersistentTpmHandle::try_from(TPM2_PERSISTENT_FIRST),
-                |v| PersistentTpmHandle::new(TPM2_HANDLE::from(v)+ 1))
-            .conv_err()?;
-        Ok(Persistent::Persistent(handle))
-    }
+    fn unused_persistent_primary_key(&mut self) -> Result<Persistent>;
 
     fn persist_key(&mut self, key_handle: KeyHandle) -> Result<TPM2_HANDLE>;
 
@@ -112,10 +104,12 @@ impl ContextExt for Context {
         let capability = CapabilityType::Handles;
         let property = TPM2_PERSISTENT_FIRST;
         let max = usize::try_from(TPM2_MAX_CAP_BUFFER).unwrap();
+        // This calculation was copied from tpm2_getcap.
         let count = (max - size_of::<TPM2_CAP>() - size_of::<u32>()) / size_of::<TPM2_HANDLE>();
         let property_count = u32::try_from(count).unwrap();
         let mut all_handles = Vec::new();
-        loop {
+        let more = false;
+        for _ in 0..255 {
             let (handles, more) = self.get_capability(capability, property, property_count)
                 .conv_err()?;
             let list = match handles {
@@ -131,17 +125,75 @@ impl ContextExt for Context {
                 break;
             }
         }
+        if more {
+            return Err(Error::custom(
+                "hit iteration limit before retrieving all persistent handles from the TPM"));
+        }
         all_handles.sort_unstable_by_key(|handle| TPM2_HANDLE::from(*handle));
         Ok(all_handles)
     }
 
+    fn unused_persistent_primary_key(&mut self) -> Result<Persistent> {
+        let mut used_handles = self.persistent_handles()?.into_iter();
+        // These address regions are defined in Registry of Reserved TPM 2.0 Handles and Localities
+        // The first regions for both are for primary keys and the second is marked as "Available".
+        let storage = (0x81_00_00_00..0x81_00_01_00u32).chain(0x81_00_80_00..0x81_01_00_00u32);
+        let endorsement = (0x81_01_00_00..0x81_01_01_00u32).chain(0x81_01_80_00..0x81_02_00_00u32);
+        let possible_handles = storage.chain(endorsement)
+            .map(|handle| PersistentTpmHandle::new(handle).unwrap());
+        let mut unused: Option<PersistentTpmHandle> = None;
+        // We simultaneously iterate over the possible handles and the used handles, breaking when
+        // we find a handle which is not equal to the current used handle, or when
+        // there are no more used handles. This works because the used handles are sorted.
+        for handle in possible_handles {
+            let used = match used_handles.next() {
+                Some(used) => used,
+                None => {
+                    unused = Some(handle);
+                    break;
+                }
+            };
+            if used != handle {
+                unused = Some(handle);
+                break;
+            }
+        }
+        match unused {
+            Some(unused) => Ok(Persistent::Persistent(unused)),
+            None => Err(Error::custom("failed to find an unused persistent handle")),
+        }
+    }
+
     fn persist_key(&mut self, key_handle: KeyHandle) -> Result<TPM2_HANDLE> {
-        let persistent = self.execute_with_session(None, |ctx| ctx.first_free_persistent())?;
-        self.evict_control(Provision::Owner, key_handle.into(), persistent).conv_err()?;
-        let Persistent::Persistent(inner) = persistent;
-        Ok(TPM2_HANDLE::from(inner))
+        let mut persistent: Option<Persistent> = None;
+        for _ in 0..255 {
+            let handle = self.execute_with_session(None, |ctx| {
+                ctx.unused_persistent_primary_key()
+            })?;
+            match self.evict_control(Provision::Owner, key_handle.into(), handle) {
+                Ok(_) => {
+                    persistent = Some(handle);
+                    break
+                },
+                Err(error) => {
+                    if let tss_esapi::Error::Tss2Error(rc) = error {
+                        if  Some(Tss2ResponseCodeKind::NvDefined) == rc.kind() {
+                            // This type of error occurs if another application used the persistent
+                            // handle we found before we could, so we try again with a different
+                            // handle.
+                            continue;
+                        }
+                    }
+                    return Err(error.into())
+                },
+            }
+        };
+        match persistent {
+            Some(Persistent::Persistent(inner)) => Ok(inner.into()),
+            None => Err(Error::custom("retry limit reached"))
+        }
     }
-    
+
     fn evict_key(&mut self, persistent: TPM2_HANDLE, key_handle: Option<KeyHandle>) -> Result<()> {
         let tpm_handle = TpmHandle::try_from(persistent).conv_err()?;
         let key_handle = match key_handle {
@@ -516,7 +568,7 @@ impl TpmCredStore {
         let result = {
             let mut guard = self.state.write().conv_err()?;
             guard.context.create_primary(
-                Hierarchy::Endorsement,
+                Hierarchy::Owner,
                 params.template()?,
                 params.auth,
                 None,
@@ -1147,7 +1199,7 @@ mod test {
     #[test]
     fn first_free_persistent() -> Result<()> {
         let mut context = Context::for_test()?;
-        let _first = context.first_free_persistent()?;
+        let _first = context.unused_persistent_primary_key()?;
         Ok(())
     }